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)