TSALVIA技術メモ

CTFのWriteupや気になったツールについて書いていきます。また、このサイトはGoogle Analyticsを利用しています。

SECCON 2020 Online CTF Writeup

SECCON 2020 Online CTF について

SECCON 2020 Online CTF が開催されました。
2020年10月10日午後3時から2020年10月11日の午後3時(24時間)

score.lmt.seccon.jp

www.seccon.jp

SECCON CTF 本戦にチームで参加してきました。本戦とだけあって、問題はかなり難しく私は1問解くだけで精一杯でした。 チームメンバが10問解いてくれて、結果は、14/579 位で 1678 点でした(チームメンバが強すぎる)。 私もメンバと協力し、実際に1問解くことができたので、そのWriteupを紹介します。

f:id:tsalvia:20201011170106p:plain

SECCON 2020 Online CTF Writeup(1問)

Fixer (reversing)

問題

usage

$ python3.9 fixer.cpython-39.pyc
SECCON{DUMMY_FLAG}
wrong
$
Note: SECCON{DUMMY_FLAG} is not the actual flag.

添付ファイル

  • fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc

解答例

Python3.9 の pyc ファイルが渡されました。Pyhton3.9をインストールし、実行してみると以下のようになりました。正しいフラグを入力する必要があるようです。

$ python3.9 fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc
A
invalid flag
$ python3.9 fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc
SECCON{A}
wrong

pycファイルなので、zrax/pycdc を使ってデコンパイルを試してみました。

github.com

途中まで、デコンパイルできてそうですが、LOAD_METHOD 命令がサポートされておらず、上手くいきませんでした。

$ ./pycdc/pycdc ./fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc 
# Source Generated with Decompyle++
# File: fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc (Python 3.9)

Unsupported opcode: LOAD_METHOD
import re
s = input()
# WARNING: Decompyle incomplet

pycdc の issues を見てみると、Python3.7 から追加された LOAD_METHOD と CALL_METHOD は、残念ながら対応できていないようです。

Unsupported opcode: LOAD_METHOD on Python 3.7 files · Issue #163 · zrax/pycdc · GitHub

仕方ないので、pycdc のコードをざっと読み、エラーになっている箇所をつぶして無理やりデコンパイルさせました。 pycdc/ASTree.cpp:2135-2136 をコメントアウトすれば、とりあえずスキップできそうです。

https://github.com/zrax/pycdc/blob/master/ASTree.cpp#L2135

        default:
            fprintf(stderr, "Unsupported opcode: %s\n", Pyc::OpcodeName(opcode & 0xFF));
            // cleanBuild = false;
            // return new ASTNodeList(defblock->nodes());

make して再度実行すると、不完全ですがデコンパイルされました。

# Source Generated with Decompyle++
# File: fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc (Python 3.9)

import re
s = input()
m = s
if not m:
    print('invalid flag')
else:
    s = 1
    
    f = lambda s: (lambda a: (lambda b = None: a == b)
)(0x1F8DD85698FB84CC77D5D5046A176F6B51A9531952D4409D133FF48B68FL)((lambda a: None((lambda b = None: None((lambda c = None: b(b)(c)))
))
)((lambda f: (lambda b = None: (lambda c = None: (lambda d = None: if len(c) == 0:
dNone(f(b)(c[1:])(d))(c[0]))
)
)
))((lambda a: (lambda b = None: a * (lambda a: None((lambda b = None: None((lambda c = None: b(b)(c)))
))
)((lambda a: (lambda b = None: if b > 266:
b - 10None(a(b + 11)))
))(b) + b
)
))((lambda a: None((lambda b = None: None((lambda c = None: b(b)(c)))
))
)((lambda f: (lambda b = None: (lambda c = None: if len(c) == 0:
[][
None(ord(c[0]) - 65)] + f(b)(c[1:]))
)
))((lambda a: None((lambda b = None: None((lambda c = None: b(b)(c)))
))
)((lambda a: (lambda b = None: if b == 0:
1(None + 1) * a(b - 1) + 7 & 255)
)))(s))(0))

    if f(s):
        print('correct')
    else:
        print('wrong')

不完全な場所を pycdas(pycdc の Disassembler)の disassemble 結果と見比べながら、復元していきました。 これでも完全ではないですが、大体復元できました。

import re
s = input()
m = re.match(r'^SECCON{([A-Z]+)}$', s)
if not m:
    print('invalid flag')
else:
    s = m.group(1)
    f = lambda s: (
        (lambda a: lambda b: a == b)(0x1F8DD85698FB84CC77D5D5046A176F6B51A9531952D4409D133FF48B68F)
        (
            (
                lambda a: lambda b: a(lambda c: b(b)(c))
            )
            (
                lambda f: lambda b: lambda c: lambda d: d if len(c) == 0 else b(f(b)(c[1:])(d))(c[0])
            )
            (
                lambda a: lambda b: a * (lambda a: lambda b: a(lambda c: b(b)(c)))(lambda a: lambda b: b - 10 if b > 266 else a(a(b + 11)))(b) + b
            )
            (
                (lambda f: lambda b: lambda c: [] if len(c) == 0 else [b(ord(c[0]) - 65)] + f(b)(c[1:]))
                (lambda a: lambda b: 1 if b == 0 else (b + 1) * a(b - 1) + 7 & 255)(s)
            )(0)
        )
    )

    if f(s):
        print('correct')
    else:
        print('wrong')

入力したフラグ文字列(中括弧内の文字列)を変換し、最後に 0x1F8DD85698FB84CC77D5D5046A176F6B51A9531952D4409D133FF48B68F になればいいようです。 多段にネストされた lambda を読むのはつらいので、Python バイトコードを直接書き換えて、以下のような処理になるように変換しました。

# a == b を a - b に書き換えて、差分を返すようにする
(lambda a: lambda b: a - b)(0x1F8DD85698FB84CC77D5D5046A176F6B51A9531952D4409D133FF48B68F)

# fs(s) を print(f(s)) に書き換えて、差分を出力させる
if print(f(s)):
    print('correct')

バイトコード書換え後の実行結果は、以下通りです。ちゃんと差分が出力されています。

$ echo 'SECCON{A}' | python3.9 patched_fixer.pyc 
13611142019359843741091679554812914051545792465993098606064046040462990
wrong

後は、0に近づくように徐々にフラグの文字列をすり寄せていきます。 まず、差分が最小になる文字列のサイズを求めます。 次にフラグの文字列を特定します。 後ろの文字列のほうが比重が大きいようなので、後ろからA-Zの文字列を割り当てて、0に近づくように総当たりします。

上記の手順を行うスクリプトを作成しました。

import subprocess

def run_script(flag):
    command = "echo '" + flag + "' | python3.9 patched_fixer.pyc"
    proc = subprocess.Popen(
        command,
        shell  = True,
        stdin  = subprocess.PIPE,
        stdout = subprocess.PIPE,
        stderr = subprocess.PIPE)

    stdout_data, stderr_data = proc.communicate()
    return int(stdout_data.decode().split('\n')[0])

large_num = 10**1000

body_size = 1
prev_num = large_num
while True:
    flag = 'SECCON{' + 'A' * body_size + '}'
    num = abs(run_script(flag))
    print(body_size, num)
    if prev_num < num:
        body_size -= 1
        break
    prev_num = num
    body_size += 1

print('[+] smallest body size:', body_size)
body = list('A' * body_size)

for i in reversed(range(body_size)):
    correct_ch = ''
    target_num = large_num 

    for ch in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
        body[i] = ch
        flag = 'SECCON{' + ''.join(body) + '}'
        num = abs(run_script(flag))

        if target_num > num:
            target_num = num
            correct_ch = ch

    body[i] = correct_ch
    flag = 'SECCON{' + ''.join(body) + '}'
    print(flag, target_num) 
            
print('[+] flag:', flag)

実行すると、以下のようになり、フラグの特定に成功しました。

$ python3 solve.py 
1 13611142019359843741091679554812914051545792465993098606064046040462990
2 13611142019359843741091679554812914051545792465993098606064046040462733
3 13611142019359843741091679554812914051545792465993098606064046040396684
4 13611142019359843741091679554812914051545792465993098606064046023422091
5 13611142019359843741091679554812914051545792465993098606064041660951690
6 13611142019359843741091679554812914051545792465993098606062920506058633
7 13611142019359843741091679554812914051545792465993098605774783698542984
8 13611142019359843741091679554812914051545792465993098531723624167021191
9 13611142019359843741091679554812914051545792465993079500575624565920390
10 13611142019359843741091679554812914051545792465988188495539727083014533
11 13611142019359843741091679554812914051545792464731200201314073976209284
12 13611142019359843741091679554812914051545792141685208585321225527260291
13 13611142019359843741091679554812914051545709118865363275159174147369090
14 13611142019359843741091679554812914051524372254165118563511969515330433
15 13611142019359843741091679554812914046040798026202227670180379081395584
16 13611142019359843741091679554812912636762221439739268083961637560139391
17 13611142019359843741091679554812550452168038718758654425745066597297790
18 13611142019359843741091679554719469011463079426740944264086329147006333
19 13611142019359843741091679530797538750288541378189432717790804422101884
20 13611142019359843741091673382861461628432262900450965319840950121658491
21 13611142019359843741090093363289641311368694121664844046728394907706490
22 13611142019359843740684028333331819826031517973631676856801704922042233
23 13611142019359843636325315634171698094377247929107709045642378606328184
24 13611142019359816816136151950020413059229846486447981577695515467817591
25 13611142019352924027521085123140159026347675722898022315351688870595190
26 13611142017581477353448910614914872575629789490558491892988253384438133
27 13611141562319682116900062001016254741133027779299173345585333442074484
28 13611024560038306323845968229071471275465267985654306663034908254616691
29 13580954973724727508943868839262120598851001018923569247575635077963890
30 5853071291134972079104325658258996708984390569124053474542428678194033
31 1980213035134432173389658271859543842986734495029351500194991616062659216
[+] smallest body size: 30
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA} 5853071291134972079104325658258996708984390569124053474542428678194033
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAAAAAZA} 19571546300681988097044035244965445816599023360994875443432407550639
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAAAAKZA} 32165310924548063384120466126679300713484668258889522426102099208
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAAAPKZA} 296985257989643981147562878264527393696515960571318030136643778
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAAKPKZA} 1153663419590838273940040994640392895814380036624303948415259
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAKKPKZA} 2567720874678184937617503317875382971183225205262212274292
SECCON{AAAAAAAAAAAAAAAAAAAAAAASKKPKZA} 19802904128190565539164314738330297073770253264053767957
SECCON{AAAAAAAAAAAAAAAAAAAAAAQSKKPKZA} 79107428049302531881657699915267157461140590383812696
SECCON{AAAAAAAAAAAAAAAAAAAAAZQSKKPKZA} 330812237485163726245527196832722615362733164946838
SECCON{AAAAAAAAAAAAAAAAAAAAEZQSKKPKZA} 588146974717459959652430533376534838693448978629
SECCON{AAAAAAAAAAAAAAAAAAASEZQSKKPKZA} 4093047390883613197045378973729602534906856294
SECCON{AAAAAAAAAAAAAAAAAAGSEZQSKKPKZA} 2397316222767190743070499313067806948195515
SECCON{AAAAAAAAAAAAAAAAAUGSEZQSKKPKZA} 70280205143208442627745271599370690909090
SECCON{AAAAAAAAAAAAAAAAZUGSEZQSKKPKZA} 16393871760572388695577584603899638496
SECCON{AAAAAAAAAAAAAAAWZUGSEZQSKKPKZA} 891807418121296140129178447165820373
SECCON{AAAAAAAAAAAAAANWZUGSEZQSKKPKZA} 3468393191307815409460796868374835
SECCON{AAAAAAAAAAAAANNWZUGSEZQSKKPKZA} 11821109868172122613646478112401
SECCON{AAAAAAAAAAAADNNWZUGSEZQSKKPKZA} 31869450138079602350533561859
SECCON{AAAAAAAAAAARDNNWZUGSEZQSKKPKZA} 210942959712303202536560545
SECCON{AAAAAAAAAAKRDNNWZUGSEZQSKKPKZA} 1025914576619133700083962
SECCON{AAAAAAAAAEKRDNNWZUGSEZQSKKPKZA} 3694524116559772759849
SECCON{AAAAAAAAZEKRDNNWZUGSEZQSKKPKZA} 2481404637159204455
SECCON{AAAAAAACZEKRDNNWZUGSEZQSKKPKZA} 37716372618985286
SECCON{AAAAAAJCZEKRDNNWZUGSEZQSKKPKZA} 258587641950916
SECCON{AAAAALJCZEKRDNNWZUGSEZQSKKPKZA} 722016547806
SECCON{AAAAILJCZEKRDNNWZUGSEZQSKKPKZA} 2208931641
SECCON{AAAJILJCZEKRDNNWZUGSEZQSKKPKZA} 2234551
SECCON{AACJILJCZEKRDNNWZUGSEZQSKKPKZA} 54934
SECCON{AYCJILJCZEKRDNNWZUGSEZQSKKPKZA} 193
SECCON{MYCJILJCZEKRDNNWZUGSEZQSKKPKZA} 0
[+] flag: SECCON{MYCJILJCZEKRDNNWZUGSEZQSKKPKZA}

FLAG

SECCON{MYCJILJCZEKRDNNWZUGSEZQSKKPKZA}