SECCON 2020 Online CTF Writeup
SECCON 2020 Online CTF について
SECCON 2020 Online CTF が開催されました。
2020年10月10日午後3時から2020年10月11日の午後3時(24時間)
SECCON CTF 本戦にチームで参加してきました。本戦とだけあって、問題はかなり難しく私は1問解くだけで精一杯でした。 チームメンバが10問解いてくれて、結果は、14/579 位で 1678 点でした(チームメンバが強すぎる)。 私もメンバと協力し、実際に1問解くことができたので、そのWriteupを紹介します。
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 を使ってデコンパイルを試してみました。
途中まで、デコンパイルできてそうですが、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}