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}