SECCON 令和 CTF - Writeup
SECCON 令和 CTFに参加しました. 310点を獲得して53位でした.
解いた問題と競技後に解いた問題についてメモを残します.
Misc
フラグの例は?
問題文にFLAGがある.
bREInWAck
Brainf*ck問題. 問題文より, Brainf*ckで使われる記号が 平成令和「」。
の文字に置き換えられていると推測する.
令和和和和和和和和和和和和和和和和「令和 和和和和令和和和和令和和和和和和和令和和 和和和和令和和平平平平平成」令和和和。令 和和和和和。成成。。平成成成成。成。令令 和和和和和和和和和和和。令和和。平平平和 和和和。令和和。和和和和。令令和和和和和 和和和和和和和。平平平和和和和和和和和和 和和和和。成成成成成成成成。令成成成成成 成成成。令令。成成成成成。成成成成成成。 令和。平平和和。令令令和和和和和和和和和 和。
置き換えを何パターンか適当に試して, 以下のコードをインタプリタに投げていたら当たった.
>++++++++++++++++[>+ ++++>++++>+++++++>++ ++++>++<<<<<-]>+++.> +++++.--..<----.-.>> +++++++++++.>++.<<<+ +++.>++.++++.>>+++++ +++++++.<<<+++++++++ ++++.--------.>----- ---.>>.-----.------. >+.<<++.>>>+++++++++ +.
SECCON{bREIn_WAnic!}
Forensic
新元号発表
PDFファイルが与えられる. 元号が隠れて見えない.
PDFファイル内にJPGファイルが埋まっているのでそれを切り出して, 隠れている箇所(令和の形にくり抜かれたQRコード)に重ね合わせる.
出来たQRコードをiPhoneのカメラで読むとFLAGが得られた.
SECCON{overlay_overlap_overera}
Binary
和暦の流れ
x86 ELFファイルが与えられる.
hamasho@hamasho-virtual-machine:~$ file reiwa reiwa: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.24, BuildID[sha1]=d0002617bfdf8f3834b03237412cc81b88e56bd0, not stripped
実行すると入力を求められる. Crackme系の問題?
hamasho@hamasho-virtual-machine:~$ ./reiwa Welcome to New Era! Please answer the name of New Era! a Something is wrong...
解析してみると, 入力値とハードコードされた値(SHOWAとHEISEIだったと思う)を1文字ずつXORした値が特定の値になるかチェックしている.
1回目の入力で RAYWA
, 2回目で HEYSAY
と入力するとFLAGが得られる.
hamasho@hamasho-virtual-machine:~$ ./reiwa Welcome to New Era! Please answer the name of New Era! RAYWA Welcome to New Era! Please answer the name of Old Era! HEYSAY SECCON{M-T-S-H-R}
SECCON{M-T-S-H-R}
令和コード
競技時間中に解けなかったが, 終わった後で解いたので記載する.
runmeというファイル名のELFファイルが与えられる. アーキテクチャが Matsushita MN10300
となっている.
hamasho@hamasho-virtual-machine:~$ file runme runme: ELF 32-bit LSB executable, Matsushita MN10300, version 1 (SYSV), statically linked, not stripped
Matsushita MN10300についてGoogle先生に聞いてみると, MN103というWikipediaの記事がヒットする. Panasonicのマイコンらしい.
SECCON + 謎アーキテクチャということで, 昨年のSECCON CTFの時にも使用した仮想マシン(@kozossakaiさんが公開しているやつ)を用意する.
/usr/local/cross2-gcc494/bin配下を確認すると, MN10300に対応していると思われるobjdump等が確認できる.
これを使ってrunmeを逆アセンブルする. 逆アセンブル結果は以下の通り. 何となく既視感.
user@debian:/usr/local/cross2-gcc494/bin$ ./mn10300-elf-objdump -d /home/user/runme_mn10300 /home/user/runme_mn10300: file format elf32-mn10300 Disassembly of section .text: 00001400 <_start>: 1400: fc dc 20 1c mov 7200,a0 1404: 00 00 1406: f2 f0 mov a0,sp 1408: dd 54 01 00 call 155c <_main>,[],0 140c: 00 00 00 140f: 81 mov d0,d1 00001410 <___r_exit>: 1410: 80 01 mov 1,d0 1412: f0 c0 syscall 1414: f0 fc rets 00001416 <___r_read>: 1416: 80 04 mov 4,d0 1418: f0 c0 syscall 141a: f0 fc rets 0000141c <___r_write>: 141c: 80 05 mov 5,d0 141e: f0 c0 syscall 1420: f0 fc rets 00001422 <___exit>: 1422: f8 fe f4 add -12,sp 1425: 81 mov d0,d1 1426: 80 00 mov 0,d0 1428: dd e8 ff ff call 1410 <___r_exit>,[],0 142c: ff 00 00 0000142f <___read>: 142f: f8 fe ec add -20,sp 1432: f1 e0 mov d0,a0 1434: 46 0c mov d1,(12,sp) 1436: 58 20 mov (32,sp),d0 1438: 42 10 mov d0,(16,sp) 143a: 80 00 mov 0,d0 143c: f1 d1 mov a0,d1 143e: dd d8 ff ff call 1416 <___r_read>,[],0 1442: ff 00 00 1445: df 00 14 ret [],20 00001448 <___write>: 1448: f8 fe ec add -20,sp 144b: f1 e0 mov d0,a0 144d: 46 0c mov d1,(12,sp) 144f: 58 20 mov (32,sp),d0 1451: 42 10 mov d0,(16,sp) 1453: 80 00 mov 0,d0 1455: f1 d1 mov a0,d1 1457: dd c5 ff ff call 141c <___r_write>,[],0 145b: ff 00 00 145e: df 00 14 ret [],20 00001461 <_exit>: 1461: f8 fe f4 add -12,sp 1464: cd be ff 00 call 1422 <___exit>,[],0 1468: 00 00001469 <_read1>: 1469: f8 fe ec add -20,sp 146c: 85 01 mov 1,d1 146e: 46 0c mov d1,(12,sp) 1470: 3c mov sp,a0 1471: 20 13 add 19,a0 1473: f1 d1 mov a0,d1 1475: cd ba ff 00 call 142f <___read>,[],0 1479: 00 147a: f8 b8 13 movbu (19,sp),d0 147d: df 00 14 ret [],20 00001480 <_write1>: 1480: f8 fe f0 add -16,sp 1483: f8 96 18 movbu d1,(24,sp) 1486: 85 01 mov 1,d1 1488: 46 0c mov d1,(12,sp) 148a: 3c mov sp,a0 148b: 20 18 add 24,a0 148d: f1 d1 mov a0,d1 148f: cd b9 ff 00 call 1448 <___write>,[],0 1493: 00 1494: df 00 10 ret [],16 00001497 <_getchar>: 1497: f8 fe f4 add -12,sp 149a: 80 00 mov 0,d0 149c: cd cd ff 00 call 1469 <_read1>,[],0 14a0: 00 14a1: df 00 0c ret [],12 000014a4 <_putchar>: 14a4: cf 80 movm [d2],(sp) 14a6: f8 fe f4 add -12,sp 14a9: 82 mov d0,d2 14aa: 80 01 mov 1,d0 14ac: 89 mov d2,d1 14ad: 15 extbu d1 14ae: cd d2 ff 00 call 1480 <_write1>,[],0 14b2: 00 14b3: 88 mov d2,d0 14b4: df 80 10 ret [d2],16 000014b7 <_gets>: 14b7: cf 20 movm [a2],(sp) 14b9: f8 fe f4 add -12,sp 14bc: f1 e2 mov d0,a2 14be: f0 42 movbu (a2),d0 14c0: a0 00 cmp 0,d0 14c2: c8 14 beq 14d6 <.L10> 000014c4 <.L12>: 14c4: cd d3 ff 00 call 1497 <_getchar>,[],0 14c8: 00 14c9: a0 0a cmp 10,d0 14cb: c8 0b beq 14d6 <.L10> 14cd: f0 52 movbu d0,(a2) 14cf: 49 inc a2 14d0: f0 42 movbu (a2),d0 14d2: a0 00 cmp 0,d0 14d4: c9 f0 bne 14c4 <.L12> 000014d6 <.L10>: 14d6: 80 00 mov 0,d0 14d8: df 20 10 ret [a2],16 000014db <_puts>: 14db: cf 20 movm [a2],(sp) 14dd: f8 fe f4 add -12,sp 14e0: f1 e2 mov d0,a2 14e2: f0 42 movbu (a2),d0 14e4: 81 mov d0,d1 14e5: 15 extbu d1 14e6: a5 00 cmp 0,d1 14e8: c8 11 beq 14f9 <.L15> 000014ea <.L17>: 14ea: 14 extbu d0 14eb: cd b9 ff 00 call 14a4 <_putchar>,[],0 14ef: 00 14f0: 49 inc a2 14f1: f0 42 movbu (a2),d0 14f3: 81 mov d0,d1 14f4: 15 extbu d1 14f5: a5 00 cmp 0,d1 14f7: c9 f3 bne 14ea <.L17> 000014f9 <.L15>: 14f9: 80 00 mov 0,d0 14fb: df 20 10 ret [a2],16 000014fe <_random_init>: 14fe: fc 81 00 18 mov d0,(1800 <_gp>) 1502: 00 00 1504: de 00 00 retf [],0 00001507 <_get_random_value>: 1507: fc a4 00 18 mov (1800 <_gp>),d0 150b: 00 00 150d: 81 mov d0,d1 150e: f8 c1 0d asl 13,d1 1511: f2 24 xor d1,d0 1513: 81 mov d0,d1 1514: f8 c5 11 lsr 17,d1 1517: f2 24 xor d1,d0 1519: 81 mov d0,d1 151a: f8 c1 0f asl 15,d1 151d: f2 24 xor d1,d0 151f: fc 81 00 18 mov d0,(1800 <_gp>) 1523: 00 00 1525: de 00 00 retf [],0 00001528 <_decode>: 1528: cf 20 movm [a2],(sp) 152a: f8 fe f4 add -12,sp 152d: f1 e2 mov d0,a2 152f: f0 42 movbu (a2),d0 1531: a0 00 cmp 0,d0 1533: c8 24 beq 1557 <.L22> 00001535 <.L24>: 1535: cd d2 ff 00 call 1507 <_get_random_value>,[],0 1539: 00 0000153a <_destruct_code>: 153a: 22 fc add -4,a2 153c: 21 ff add -1,a1 153e: 22 ff add -1,a2 1540: f8 cb 02 asr 2,d3 1543: 20 ff add -1,a0 00001545 <_restruct_code>: 1545: cb nop 1546: cb nop 1547: cb nop 1548: cb nop 1549: cb nop 154a: f0 46 movbu (a2),d1 154c: f2 21 xor d0,d1 154e: f0 56 movbu d1,(a2) 1550: 49 inc a2 1551: f0 42 movbu (a2),d0 1553: a0 00 cmp 0,d0 1555: c9 e0 bne 1535 <.L24> 00001557 <.L22>: 1557: 80 00 mov 0,d0 1559: df 20 10 ret [a2],16 0000155c <_main>: 155c: cf 80 movm [d2],(sp) 155e: f8 fe f4 add -12,sp 1561: fc cc a2 8c mov -1831433054,d0 1565: d6 92 1567: cd 97 ff 00 call 14fe <_random_init>,[],0 156b: 00 156c: fc cc b1 15 mov 5553,d0 1570: 00 00 1572: cd 69 ff 00 call 14db <_puts>,[],0 1576: 00 1577: fc cc 45 15 mov 5445,d0 157b: 00 00 157d: cd 3a ff 00 call 14b7 <_gets>,[],0 1581: 00 1582: fc ce 04 18 mov 6148,d2 1586: 00 00 1588: 88 mov d2,d0 1589: cd 9f ff 00 call 1528 <_decode>,[],0 158d: 00 158e: fc cc c7 15 mov 5575,d0 1592: 00 00 1594: cd 47 ff 00 call 14db <_puts>,[],0 1598: 00 1599: 88 mov d2,d0 159a: cd 41 ff 00 call 14db <_puts>,[],0 159e: 00 159f: fc cc c5 15 mov 5573,d0 15a3: 00 00 15a5: cd 36 ff 00 call 14db <_puts>,[],0 15a9: 00 15aa: 80 00 mov 0,d0 15ac: cd b5 fe 00 call 1461 <_exit>,[],0 15b0: 00
もう少し調べてみると, 坂井さん(と他著者の方々)が書かれている書籍("31バイトでつくるアセンブラプログラミング: アセンブラ短歌の世界")の情報が見つかった.
この本によると, MN10300には以下のような特徴があるらしい. d1レジスタとd0レジスタを気にすると良さそう?
* 第一引数はd1レジスタで扱う * 第二引数, 第三引数はスタック上で扱う(SP+12が第二引数, SP+16が第三引数) * d0レジスタはシステムコール番号の格納等や戻り値に使用する
大熱血!アセンブラ入門のp.447以降にもMN10300についての記載があった. この辺を参考にコードを(雰囲気で)読んで行く.
main関数から読んで行く.
0000155c <_main>: 155c: cf 80 movm [d2],(sp) 155e: f8 fe f4 add -12,sp 1561: fc cc a2 8c mov -1831433054,d0 1565: d6 92 1567: cd 97 ff 00 call 14fe <_random_init>,[],0 156b: 00 156c: fc cc b1 15 mov 5553,d0 1570: 00 00 1572: cd 69 ff 00 call 14db <_puts>,[],0 1576: 00 1577: fc cc 45 15 mov 5445,d0 157b: 00 00 157d: cd 3a ff 00 call 14b7 <_gets>,[],0 1581: 00 1582: fc ce 04 18 mov 6148,d2 1586: 00 00 1588: 88 mov d2,d0 1589: cd 9f ff 00 call 1528 <_decode>,[],0 158d: 00 158e: fc cc c7 15 mov 5575,d0 1592: 00 00 1594: cd 47 ff 00 call 14db <_puts>,[],0 1598: 00 1599: 88 mov d2,d0 159a: cd 41 ff 00 call 14db <_puts>,[],0 159e: 00 159f: fc cc c5 15 mov 5573,d0 15a3: 00 00 15a5: cd 36 ff 00 call 14db <_puts>,[],0 15a9: 00 15aa: 80 00 mov 0,d0 15ac: cd b5 fe 00 call 1461 <_exit>,[],0 15b0: 00
まず最初に<_random_init>の直前のd0レジスタに変な値をmovしている箇所(mov -1831433054,d0)が気になる.
ここでmovされている値(16進数表記で 0x92D68CA2
)で検索すると, 昨年のSECCON CTFのWriteupが幾つかヒットする. これは xorshift
で使用されるSeed値らしい. 何となく見覚えがあった.
ファイル名とかその他諸々から薄々思っていたけれど, この問題は昨年のSECCON CTFのRev問の亜種っぽい.
ということは<random_init>と<get_random_value>はxorshiftで擬似乱数を生成するのかな?と予測しつつコードを読む.
Wikipediaの記載と少し違う箇所がある(0x151aの処理"asl 15,d1"が本来は"asl 5,d1"になる?)が, xorshiftっぽいことをやっているという推測で合ってそう.
000014fe <_random_init>: 14fe: fc 81 00 18 mov d0,(1800 <_gp>) 1502: 00 00 1504: de 00 00 retf [],0 00001507 <_get_random_value>: 1507: fc a4 00 18 mov (1800 <_gp>),d0 ; 0x1800 = <_random_init>でコピーした値(0x92D68CA2)を指す 150b: 00 00 150d: 81 mov d0,d1 150e: f8 c1 0d asl 13,d1 ; 13で算術左シフト 1511: f2 24 xor d1,d0 1513: 81 mov d0,d1 1514: f8 c5 11 lsr 17,d1 ; 17で算術右シフト 1517: f2 24 xor d1,d0 1519: 81 mov d0,d1 151a: f8 c1 0f asl 15,d1 ; 15で算術左シフト 151d: f2 24 xor d1,d0 151f: fc 81 00 18 mov d0,(1800 <_gp>) 1523: 00 00 1525: de 00 00 retf [],0
更に見ていくと, <_gets>の実行前にd0に5445( = 0x1545)を格納している.
0x1545は<_restruct_code>の先頭(NOP命令)に当たる. 0x1545以降に標準入力から受け取った値を書き込むと思われる.
00001545 <_restruct_code>: 1545: cb nop ... ...
重要そうな雰囲気の<_decode>の直前では, 6148( = 0x1804)をd2にmovし, 更にそれをd0にmovしている.
バイナリエディタで 0x1804 - 0x1000 = 0x804 を確認すると, 意味ありげなデータが格納されている. 上記のハードコードされた値を<_decode>で処理すると推測する.
※ 自分のコメントの中では0x1804以降のデータをenc_dataとしています
<_decode>以降は, 以下のコードでループすると思われる.
00001528 <_decode>: 1528: cf 20 movm [a2],(sp) 152a: f8 fe f4 add -12,sp 152d: f1 e2 mov d0,a2 ; d0 = 0x1804 = enc_data 152f: f0 42 movbu (a2),d0 1531: a0 00 cmp 0,d0 1533: c8 24 beq 1557 <.L22> 00001535 <.L24>: 1535: cd d2 ff 00 call 1507 <_get_random_value>,[],0 ; d0 = xorshift's result 1539: 00 0000153a <_destruct_code>: 153a: 22 fc add -4,a2 153c: 21 ff add -1,a1 153e: 22 ff add -1,a2 1540: f8 cb 02 asr 2,d3 1543: 20 ff add -1,a0 00001545 <_restruct_code>: 1545: cb nop 1546: cb nop 1547: cb nop 1548: cb nop 1549: cb nop 154a: f0 46 movbu (a2),d1 ; copy enc_data[i] to d1 154c: f2 21 xor d0,d1 ; xor 154e: f0 56 movbu d1,(a2) 1550: 49 inc a2 ; i++ 1551: f0 42 movbu (a2),d0 ; copy enc_data[i] to d0 1553: a0 00 cmp 0,d0 ; if enc_data[i] == 0x00, exit 1555: c9 e0 bne 1535 <.L24> 00001557 <.L22>: 1557: 80 00 mov 0,d0 1559: df 20 10 ret [a2],16
動きは何となくイメージできるが, 以下の部分はよく分からない. <_destruct_code>はレジスタの値を操作しており, これを実行すると0x1804に格納されているデータを先頭から処理できなくなる.
<gets>で受け取った値で<restruct_code>以降が上書きされることから, <destruct_code>で行った処理と逆の処理を<restruct_code>の先頭で行うのでは?と推測する(関数名も destruct_code と restruct_code なので)
<_gets>から何が入力されるのか分からなかったので, 以下のコードは一旦無視して考えることにした.
これを除くと, <_get_random_value>を実行し, 実行結果とenc_data[i]とXORしているだけに見える.
0000153a <_destruct_code>: 153a: 22 fc add -4,a2 153c: 21 ff add -1,a1 153e: 22 ff add -1,a2 1540: f8 cb 02 asr 2,d3 1543: 20 ff add -1,a0 00001545 <_restruct_code>: 1545: cb nop 1546: cb nop 1547: cb nop 1548: cb nop 1549: cb nop
Wikipediaに載っているxorshift32の5を15に変えたものを使ってSolverを書きたい.
uint32_t xor(void) { static uint32_t y = 2463534242; y = y ^ (y << 13); y = y ^ (y >> 17); return y = y ^ (y << 5); }
ctftimeから去年のSECCON CTFのWriteupを色々眺めて, 以下のWriteupを参考にさせてもらいつつ, Solverを書いた.
#!/usr/bin/env python3 import sys seed = 0x92d68ca2 mask = 0xffffffff # xorshift32 def get_rand(): global seed seed ^= (seed << 13) & mask seed ^= (seed >> 17) & mask seed ^= (seed << 15) & mask return seed # enc_data[] => from 0x804 to 0x81d enc_data = [0x50, 0x77, 0x1f, 0xd3, 0x1e, 0x1f, 0xfb, 0xb4, 0x20, 0x5e, 0xc5, 0xa5, 0xdc, 0x2e, 0xf7, 0x62, 0xd3, 0xae, 0x16, 0x1e, 0x82, 0x44, 0x09, 0x72, 0xba, 0x39] for i in range(len(enc_data)): key = get_rand() & 0xff tmp_flag = enc_data[i] ^ key sys.stdout.write(chr(tmp_flag))
FLAGは以下の通り.
C:\Users\hamasho\Desktop>python solver_seccon_reiwactf_runme.py SECCON{MachineCodeOfREIWA}
<_gets>で受け取る入力とかは結局何だったんだろう?と思っていたら@kusano_kさんのブログで当該箇所について言及されていた.
標準入力から REIWA
と入力するとNOPの箇所が上書きされて, 良い感じに実行可能なコードになるっぽい. 予想通り, 入力されたコードが<_destruct_code>と逆の動きをすると思われる.
仮想マシンでrunmeを実行してREIWAを与えてみたらFLAGが出力された.
user@debian:/usr/local/cross2-gcc494/bin$ ./mn10300-elf-run /home/user/runme_mn10300 Input restruct code. REIWA FLAG: SECCON{MachineCodeOfREIWA}