SECCON 2018 国内決勝の復習

12月23日に開催されたSECCON 2018国内決勝にTeam Enuのメンバーとして参加させていただきました。力不足だったので復習も兼ねてメモを残しておきます。主に関わった宮島という問題です。


宮島

30分に1問ずつ出題される問題の要件を満たすアセンブラのバイナリ(16進数 2桁揃え表記)を答える問題。要件を満たすことでアタックポイントを取得できます。また、他チームより短いバイナリを投稿することで、チームのフラグを設置できるためディフェンスポイントが取得できます。要は短いコードを早くだせば沢山ポイントがもらえるというルールです。

以下、各問題毎の復習です。
最短バイトは私が確認した限りでのトップのチームのバイト数を記載しています。
その時の回答については公開されていないので、私なりの回答案を書いています。

1. Increment

int型の引数に対して1加算する関数を実装してください。 

条件:
-15000000 <= a <= 15000000

普通に関数を書くと、15バイト

55              push   rbp
48 89 e5        mov    rbp,rsp
89 7d fc        mov    DWORD PTR [rbp-0x4],edi
8b 45 fc        mov    eax,DWORD PTR [rbp-0x4]
83 c0 01        add    eax,0x1
5d              pop    rbp
c3              ret    

この関数は最短4バイト

8d 47 01  lea eax,[rdi+1]
c3        ret
2. Decrement

int型の引数を1減算して返す関数を実装してください。

条件:
-15000000 <= a <= 15000000

最短: 4バイト

8d 47 ff  lea eax,[rdi-1]
c3        ret
3. Odd

int型の引数aを受け取って奇数なら1を、偶数なら0を返す関数を実装してください。

条件:
-15000000 <= a <= 15000000

最短: 5バイト

97        xchg edi,eax
83 e0 01  and eax,1
c3        ret
4. Product

long型の引数 a, b を受け取って、 a と b を乗算した結果を返す関数を実装してください。(返り値はlong型とし、longで扱えない範囲の値をとるa, bは入力されないものとします。)

条件:
-100000 <= a <= 100000
-100000 <= b <= 100000

最短: 6バイト

48 97     xchg rax,rdi
48 f7 ee  imul rsi
c3        ret
5. Division

int型の変数a, bを受け取って a を b で割ったときの商を返す関数を実装してください。

条件:
-2000000 <= a <= 2000000
1 <= b <= 2000000

最短: 5バイト

97     xchg edi,eax
99     cdq
f7 fe  idiv esi
c3     ret
6. Modulo

int型の引数a, bを受け取って、aをbで割ったときの余りを返す関数を実装してください。

条件:
-2000000 <= a <= 2000000
1 <= b <= 2000000

最短: 6バイト

97     xchg edi,eax
99     cdq
f7 fe  idiv esi
92     xchg edx,eax
c3     ret
7. Sum

int型の引数 a, b, c, d, e を受け取って、a + b + c + d + eを返す関数を実装してください。

条件:
-15000000 <= a <= 15000000
-15000000 <= b <= 15000000
-15000000 <= c <= 15000000
-15000000 <= d <= 15000000
-15000000 <= e <= 15000000

最短: 11バイト

01 fe        add esi,edi
01 f2        add edx,esi
01 d1        add ecx,edx
42 8d 04 01  lea eax,[rcx+r8]
c3           ret
8. Swap

int型のポインタaとbを受取り、2つのポインタ先の値を入れ替える関数を実装してください。

条件:
-1000000 <= a,bのポインタが指し示す中身の数値 <= 1000000

最短: 7バイト

8b 07  mov eax,[rdi]
87 06  xchg eax,[rsi]
89 07  mov [rdi],eax
c3     ret
9. Pow

int型の引数a, bを受け取って、aのb乗をlong型で返す関数を実装してください。

条件:
-100 <= a <= 100
0 <= b <= 30

最短: 18バイト(以下、16バイト)

b8 01 00 00 00  mov rax,1
85 f6           test esi,esi
74 06           jz short 8
87 ce           xchg ecx,esi
f7 ef           imul edi
e2 fc           loop -2
c3              ret
10. Average

int型の配列arrとその要素数lenを受け取って、配列の平均値を切り捨てで返却する関数を実装してください。

条件:
-10000 <= arrの各要素 <= 10000
1 <= len <= 10

最短: 16バイト(以下、15バイト)

48 31 c0     xor rax,rax
87 ce        xchg ecx,esi
03 44 8f fc  add eax,[rdi+rcx*4-4]
e2 fa        loop -4
99           cdq
f7 fe        idiv esi
c3           ret
11. Hello World

十分な余白領域を持ったメモリバッファbufが与えられるので、bufの先頭に"hello world"という文字列を書き込む関数を実装してください。文字列の末端にはヌル文字を含めるようにしてください。

最短: 21バイト

48 b8 68 65 6c 6c 6f 20 77 6f  mov rax,0x6f77206f6c6c6568
c7 47 08 72 6c 64 00           mov dword ptr[rdi+8],0x646c72
48 89 07                       mov [rdi],rax
c3                             ret
12. Xor

引数として文字列strと文字列長len、およびint型のkeyが与えられるので、文字列strの各文字をkeyでxorする関数を実装してください。

条件
str: 末尾がヌル文字の文字列
1 <= len <= 20
0 <= key <= 255

最短: 9バイト

87 ce        xchg ecx,esi
30 54 0f ff  xor [rdi+rcx-1], dl
e2 fa        loop -4
c3           ret

機械語の確認方法

gdb-pedaのassembleを使いました。

% gdb -q
gdb-peda$ assemble -b64
Warning: not running or target is remote
Instructions will be written to stdout
Type instructions (NASM syntax), one or more per line separated by ";"
End with a line saying just "end"
iasm|0xdeadbeef> lea eax,[rdi+1]
hexify: "\x8d\x47\x01"
iasm|0xdeadbef2> ret
hexify: "\xc3"
iasm|0xdeadbef3> end
Assembled instructions:
"\x8d\x47\x01"  # 0x00000000:    lea eax,[rdi+0x1]
"\xc3"          # 0x00000003:    ret

hexify: "\x8d\x47\x01\xc3"

まとめ

上位チームの早さと短さに圧倒されていました。普段、シェルコードを書く時くらいしか機械語の長さを気にしないのですが、それをより考えるきっかけになりました。
また、オフラインで集まってCTFに参加するのは楽しかったです。運営の皆さん、Team Enuの皆さん、貴重な経験をさせていただき、ありがとうございました。

誤りがあったり、より短く書けると言ったご意見があればご指摘ください。