CTF Writeup SECCON 2018¶
結果&感想¶
某チームでSECCON2018に参加しました。
2問解きました。難しかった、そしてどの問題も重かった。
国内決勝上位12チームに入れるかどうかは、スコアボードに国名でないから何番かわかんないねw
期待しないで全裸で待ってる。
Forengics Unzip 101点¶
zipにはパスワードがかかっている。
makefile.sh
echo 'SECCON{'`cat key`'}' > flag.txt
zip -e --password=`perl -e "print time()"` flag.zip flag.txt
という訳でUnix通算秒がパスワードになってるはずですね。何時かわからんけど。
2018/10/27 0:10 の辺りだろうなと思ったんだけど、とりあえず現在時刻から過去に遡って総当たりしたら終わった。
#!/bin/bash
C=`date +"%s"`
while :
do
unzip -P $C flag.zip
if [ $? != 82 ]
then
echo "-------------------------------------------> $C"
echo $C >> timeline.txt
if cat flag.txt |grep SECCON
then
exit
fi
rm flag.txt
fi
: $((C--))
done
Archive: flag.zip skipping: flag.txt incorrect password Archive: flag.zip skipping: flag.txt incorrect password ・ ・ ・ Archive: flag.zip skipping: flag.txt incorrect password Archive: flag.zip inflating: flag.txt -------------------------------------------> 1540566641 SECCON{We1c0me_2_SECCONCTF2o18}
Reversing SpecialInstructions 262点¶
問題¶
Special Instructions Execute this file and get the flag. References: Assembly samples for many architectures http://kozos.jp/books/asm/cross-gcc494-v1.0.zip See the assembly samples. $ unzip cross-gcc494-v1.0.zip $ cd cross-gcc494/sample $ ls *.d See the sample programs running on GDB simulator. $ cd cross-gcc494/exec $ ls *.d
http://kozos.jp で作問者がわかってしまうw
調査¶
実行させるべく、まずはアーキテクチャを調べる。
$ file runme runme: ELF 32-bit MSB executable, *unknown arch 0xdf* version 1 (SYSV), statically linked, not stripped
0xdf…?こんなアーキあるのかなと思いましたが、チームの方がmoxieであることを教えてくれました。ありがたいです。
10進数になおして、223が Moxie processor family でした http://www.sco.com/developers/gabi/latest/ch4.eheader.html
さてこれを実行する環境ですが、作問者のお力を借りるのが最短ルートでしょう!
クロスコンパイル環境の構築はコンパイルに結構な時間を要します
熱血!/大熱血! アセンブラ入門 開発環境のVMイメージからVMイメージをダウンロードします。
SECCON2015にも他種類のCPUアーキテクチャの問題が出ていました。
ダウンロード後、以下で逆アセできます。
$ /usr/local/cross2-gcc494/bin/moxie-elf-objdump -d -M intel ./runme
逆アセしたのは、ここに置いておきます。
Reversing_SpecialInstructions_asm.txt
で、これ実行できないんですよね、gdbで実行しようとしても"Illegal instruction."って表示され、終了します
$ /usr/local/cross2-gcc494/bin/moxie-elf-gdb ./runme (gdb) target sim Connected to the simulator. (gdb) load Loading section .text, size 0x23e lma 0x1400 Loading section .rodata, size 0xb0 lma 0x1640 Loading section .data, size 0x60 lma 0x1800 Start address 0x1400 Transfer rate: 6768 bits in <1 sec. (gdb) run Starting program: /home/user/SECCON2018/Reversing_SpecialInstructions/runme Program received signal SIGILL, Illegal instruction. 0x0000154a in set_random_seed () (gdb)
原因は、ここ
0000154a <set_random_seed>: 154a: 16 20 bad 154c: 04 00 ret 0000154e <get_random_value>: 154e: 17 20 bad 1550: 04 00 ret
16と17の命令はmoxieに存在しません。
この命令をnopとかで潰して実行すると以下が出力されました。
This program uses special instructions. SETRSEED: (Opcode:0x16) RegA -> SEED GETRAND: (Opcode:0x17) xorshift32(SEED) -> SEED SEED -> RegA
16と17の命令は実装してね(はあと)って事ですね。
xorshift32¶
実装にあたって、xorshift32 から調べてみます。
wikipedia:https://ja.wikipedia.org/wiki/Xorshift
Xorshiftは疑似乱数列生成法の1つである。George Marsagliaが2003年に提案した。
Googleさんが採用してるやつかな?
Cの実装例が載っていました。最高です。
uint32_t xor(void) {
static uint32_t y = 2463534242;
y = y ^ (y << 13); y = y ^ (y >> 17);
return y = y ^ (y << 15);
}
アセンブラを読む¶
わからない命令はココで調べながら読みました。
解りやすいアーキでよかった。。
ざっくりこんな感じになっていました。
私の理解を#以降に書いています。
000015a2 <main>: 15a2: 06 18 push $sp, $r6 15a4: 91 18 dec $sp, 0x18 15a6: 01 20 92 d6 ldi.l $r0, 0x92d68ca2 # set_random_seedに渡す引数(SEED) 0x92d68ca2 15aa: 8c a2 15ac: 03 00 00 00 jsra 154a <set_random_seed> # set_random_seed関数呼び出し 15b0: 15 4a 中略 160c: 01 20 00 00 ldi.l $r0, 0x1800 # decode関数に渡す引数1 1610: 18 00 1612: 01 30 00 00 ldi.l $r1, 0x1820 # decode関数に渡す引数2 1616: 18 20 1618: 03 00 00 00 jsra 1552 <decode> # decode関数呼び出し 161c: 15 52 161e: 02 32 mov $r1, $r0 1620: 01 20 00 00 ldi.l $r0, 0x1 1624: 00 01 1626: 19 80 jsr $r6 1628: 01 20 00 00 ldi.l $r0, 0x1 162c: 00 01 162e: 01 30 00 00 ldi.l $r1, 0x167c 1632: 16 7c 1634: 19 80 jsr $r6 1636: 2e 22 xor $r0, $r0 1638: 03 00 00 00 jsra 144a <exit> 163c: 14 4a
00001552 <decode>: 1552: 06 18 push $sp, $r6 1554: 06 19 push $sp, $r7 1556: 06 1a push $sp, $r8 1558: 06 1b push $sp, $r9 155a: 06 1c push $sp, $r10 155c: 06 1d push $sp, $r11 155e: 91 18 dec $sp, 0x18 1560: 02 d2 mov $r11, $r0 # $r11 = $r1(最初が0x1800) 1562: 1c 42 ld.b $r2, ($r0) 1564: 2e 22 xor $r0, $r0 1566: 0e 42 cmp $r2, $r0 1568: c0 12 beq 158e <decode+0x3c> # if $r2 == $r0 then return 156a: 02 a3 mov $r8, $r1 # $r8 = $r1(最初が0x1820) 156c: 02 9d mov $r7, $r11 # $r7 = $r11 156e: 01 c0 00 00 ldi.l $r10, 0x154e # $r10 = 0x154e 1572: 15 4e 1574: 1c 8a ld.b $r6, ($r8) # $r6 = 0x1820の中身 1576: 2e 22 xor $r0, $r0 # $r0 = 0 1578: 19 c0 jsr $r10 # $r0 = 乱数取得32bit 157a: 2e 82 xor $r6, $r0 # $r6 = $r6 xor $r0 157c: 1c 29 ld.b $r0, ($r7) # $r0 = 0x1800の中身 157e: 2e 82 xor $r6, $r0 # $r6 = $r6 xor $r0 1580: 1e 98 st.b ($r7), $r6 # 0x1800の中身 = $r6 1582: 89 01 inc $r7, 0x1 # $r7 = 0x1800++ 1584: 8a 01 inc $r8, 0x1 # $r8 = 0x1820++ 1586: 1c 39 ld.b $r1, ($r7) # $r1 = 0x1800++の中身 1588: 2e 22 xor $r0, $r0 # $r0 = 0 158a: 0e 32 cmp $r1, $r0 158c: c7 f3 bne 1574 <decode+0x22> # if $r1 != NULL then goto 0x1574 158e: 02 2d mov $r0, $r11 1590: 02 e0 mov $r12, $fp 1592: 9e 18 dec $r12, 0x18 1594: 07 ed pop $r12, $r11 1596: 07 ec pop $r12, $r10 1598: 07 eb pop $r12, $r9 159a: 07 ea pop $r12, $r8 159c: 07 e9 pop $r12, $r7 159e: 07 e8 pop $r12, $r6 15a0: 04 00 ret
という事で、以下の動きになっていることがわかりました。
- r6 = 0x1820の中身(1byte)
- r0 = 乱数32bit(4byte)
- r6 = r6 ^ r0
- r0 = 0x1800の中身(1byte)
- r6 = r6 ^ r0
- 0x1800と0x1820は1増やして、1の処理に戻る
0x1800と0x1820あたりに、どんな値が入っているか調べておきます。
(gdb) x/100x 0x1800 0x1800 <flag>: 0x6d 0x72 0xc3 0xe2 0xcf 0x95 0x54 0x9d 0x1808 <flag+8>: 0xb6 0xac 0x03 0x84 0xc3 0xc2 0x35 0x93 0x1810 <flag+16>: 0xc3 0xd7 0x7c 0xe2 0xdd 0xd4 0xac 0x5e 0x1818 <flag+24>: 0x99 0xc9 0xa5 0x34 0xde 0x06 0x4e 0x00 0x1820 <randval>: 0x3d 0x05 0xdc 0x31 0xd1 0x8a 0xaf 0x29 0x1828 <randval+8>: 0x96 0xfa 0xcb 0x1b 0x01 0xec 0xe2 0xf7 0x1830 <randval+16>: 0x15 0x70 0x6c 0xf4 0x7e 0xa1 0x9e 0x0e 0x1838 <randval+24>: 0x01 0xf9 0xc2 0x4c 0xba 0xa0 0xa1 0x08 0x1840 <randval+32>: 0x70 0x24 0x85 0x8a 0x4d 0x2d 0x3c 0x02 0x1848 <randval+40>: 0xfc 0x6f 0x20 0xf0 0xc7 0xad 0x2f 0x97 0x1850 <randval+48>: 0x2b 0xcc 0xa3 0x34 0x23 0x53 0xc9 0xb7 0x1858 <randval+56>: 0x0c 0x10 0x6c 0x0e 0xfa 0xf9 0xa1 0x9a 0x1860: 0x00 0x00 0x00 0x00
xorshift32ですが、乱数といってもシード固定の計算値なので、予め計算しておきます。
$ cat xorshift32.c #include <stdio.h> #include <stdint.h> uint32_t xor(void) { static uint32_t y = 0x92d68ca2; int i; for(i=0;i<40;i++) { y = y ^ (y << 13); y = y ^ (y >> 17); y = y ^ (y << 15); printf("%x\n",y); } return y; } main(void){ xor(); } $ gcc xorshift32.c ;./a.out 35c36d03 c8fa2132 9f72275c 3ed1ca90 e32b4951 1c29ac51 e5e39880 7f0f53f9 89d0b941 f5e6563d cbf769ad 4ba4dacc 49a432b2 c557954b 40a4eeb4 dd74800d ce2e86b7 2a1de9cb f6084259 70ff1d78 b93854d0 b5228d01 8b22df40 8b583725 e54151fb e45c5644 1e13e10e 6a399017 f63e5c0a bcd582d5 9ec62492 7e8849b8 ce8cf267 4dc3ba07 99204746 f619cfa2 3bc7e854 43540b32 d727aa2b 4fefdb1a
実装¶
ここまで判ったらシェルでシュッてできるんやで
solv.sh
#!/bin/bash
IFS='
'
LIST='
35c36d03 6d 3d
c8fa2132 72 05
9f72275c c3 dc
3ed1ca90 e2 31
e32b4951 cf d1
1c29ac51 95 8a
e5e39880 54 af
7f0f53f9 9d 29
89d0b941 b6 96
f5e6563d ac fa
cbf769ad 03 cb
4ba4dacc 84 1b
49a432b2 c3 01
c557954b c2 ec
40a4eeb4 35 e2
dd74800d 93 f7
ce2e86b7 c3 15
2a1de9cb d7 70
f6084259 7c 6c
70ff1d78 e2 f4
b93854d0 dd 7e
b5228d01 d4 a1
8b22df40 ac 9e
8b583725 5e 0e
e54151fb 99 01
e45c5644 c9 f9
1e13e10e a5 c2
6a399017 34 4c
f63e5c0a de ba
bcd582d5 06 a0
9ec62492 4e a1
7e8849b8 00 08
'
for LINE in $LIST
do
SEED=$(echo $LINE|awk '{print $1}')
x1800=$(echo $LINE|awk '{print $2}')
x1820=$(echo $LINE|awk '{print $3}')
printf %x $(((0x$x1800^0x$SEED)^0x$x1820))|cut -c7-|xxd -p -r
done
$ ./solv.sh SECCON{MakeSpecialInstructions}
おまけ¶
Forengics History の問題、タイトルがずっと Hisotry だったんだけど、いつの間にかひっそりと修正されていた件。
チームで盛り上がった(変に深読みしたw)