SECCON 2018 Writeup

今年は難しかった(毎年言ってる気がする)
kanata22日前に追加

CTF Writeup SECCON 2018

結果&感想

score.png

某チームでSECCON2018に参加しました。
2問解きました。難しかった、そしてどの問題も重かった。

国内決勝上位12チームに入れるかどうかは、スコアボードに国名でないから何番かわかんないねw
期待しないで全裸で待ってる。

Forengics Unzip 101点

zipにはパスワードがかかっている。

Forengics_Unzip.png

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

という事で、以下の動きになっていることがわかりました。

  1. r6 = 0x1820の中身(1byte)
  2. r0 = 乱数32bit(4byte)
  3. r6 = r6 ^ r0
  4. r0 = 0x1800の中身(1byte)
  5. r6 = r6 ^ r0
  6. 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)


コメント

クリップボードから画像を追加 (サイズの上限: 100 MB)