SECCON 2019 Final write-up (Factor the flag, Bad Mouse)

SECCON 2019 Finalでは、サーバ6のJeopardy問題2つ「Factor the flag」と「Bad Mouse」を作問しましたので、出題者の立場からの想定解write-upを公開します。

今年はCTFの競技ネットワークをクラウド環境に移行チャレンジした背景もあり、私が担当する出題テーマとして、ネットワークに依存せず、軽量で小さな問題であることを意識して作問しました。(万が一ネットワークトラブルがあっても解ける、お持ち帰りでも解析できる)

Jeopardy 6-3: Factor the flag

問題文:

I hid the SECCON{} flag in a big prime number.

1401111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111112220791111111111111111
1111111111111111111111111111122207911223089031988903088023088023088920012001200
2319889030879222080230880230890319887911122318879211992120012999912120013000013
0000131008920012001199121200120022089200130000119912119911121200120011992119912
1199121199121199121200130101000012001199121200120930009200130000119921199111121
2001200119921199121199121199121199121200130010208012002318879112120929999112120
9299991212103188892001200119912230890318889199121199121200130000131007911112119
9212092091991211992119912120013010111188791222079112129999121199121199121200130
0001200119911121200120012091992119921299992120013010099991112119911112129999121
1991211991212001300001200120012001209199223198889199212001209199213010099991112
1199212001299991212001300001300001300001200120012001299991111212091991212001209
1992130100999911121199122308903198890308802308802308892001200119922308903198879
2121031988903088011992130100999911121199111111111111111111111111111111111111111
2220791111111111111111111111111111111111111111111112220791111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111239593

出題の意図:

64bitを超える巨大な数に対して、素因数分解をパッとできるプログラム・ツールが手元に揃っているかどうかというサービス問題です。

解法:

素因数分解(factor)すると 13 x 97 x 1261桁の素数の掛け算となります。 1261桁の素数は、奇遇にもちょうど97文字で折り返すと13行になります。

1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111991111111111111111111111111111111111111111111111111991111
1199911999991199911199911199911911191119111999991199911119911199911199911999991111111999911119111
1911191911111911191911191911191991191119111911111911191119911911191911191111191111111911191119111
1911111911111911111911111911191919191119111911111911191191911911191911191111911111111911191119111
1911111911111911111911111911191911991119111999911111191191911111191191911111911999911911191119111
1199911999911911111911111911191911191991111111191111911911911111911119111119111919191999911111991
1111191911111911111911111911191911191119111111191119111911911119111191911119111919191911111119111
1111191911111911111911111911191911191119111911191191111999991191111911191191111919191911111119111
1911191911111911191911191911191911191119111911191911111111911911111911191191111919191911111119111
1199911999991199911199911199911911191119111199911999991111911999991199911191111919191911111119111
1111111111111111111111111111111111111111991111111111111111111111111111111111111111111111111991111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111213

フォントを小さくして1と9の文字を白黒のドットに見立てるとSECCON{524287mP}のフラグが見えます。 ペイントソフトで塗りつぶししても良いです。

スクリプト:find_the_factor.pl

素因数分解をして1を.に9を#に置き換えて表示する攻略スクリプトPerlワンライナーに近い記法で書いてみます。

#!/usr/bin/perl
use bigint try => 'GMP';
use Math::Prime::Util ':all';
my $p=join"",<DATA>; $p=~s/\s//g;
print join" x\n",map{s/(\d{97})/$1\n/g;s/9/#/g;s/1/./g;$_;}factor($p);
__DATA__
1401111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111112220791111111111111111
1111111111111111111111111111122207911223089031988903088023088023088920012001200
2319889030879222080230880230890319887911122318879211992120012999912120013000013
0000131008920012001199121200120022089200130000119912119911121200120011992119912
1199121199121199121200130101000012001199121200120930009200130000119921199111121
2001200119921199121199121199121199121200130010208012002318879112120929999112120
9299991212103188892001200119912230890318889199121199121200130000131007911112119
9212092091991211992119912120013010111188791222079112129999121199121199121200130
0001200119911121200120012091992119921299992120013010099991112119911112129999121
1991211991212001300001200120012001209199223198889199212001209199213010099991112
1199212001299991212001300001300001300001200120012001299991111212091991212001209
1992130100999911121199122308903198890308802308802308892001200119922308903198879
2121031988903088011992130100999911121199111111111111111111111111111111111111111
2220791111111111111111111111111111111111111111111112220791111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111239593

実行結果

$ sudo apt install libmath-prime-util-gmp-perl
$ perl find_the_factor.pl
.3 x
#7 x
.................................................................................................
........................................##.................................................##....
..###..#####..###...###...###..#...#...#...#####..###....##...###...###..#####.......####....#...
.#...#.#.....#...#.#...#.#...#.##..#...#...#.....#...#...##..#...#.#...#.....#.......#...#...#...
.#.....#.....#.....#.....#...#.#.#.#...#...#.....#...#..#.#..#...#.#...#....#........#...#...#...
.#.....#.....#.....#.....#...#.#..##...#...####......#..#.#......#..#.#.....#..####..#...#...#...
..###..####..#.....#.....#...#.#...#.##........#....#..#..#.....#....#.....#...#.#.#.####.....##.
.....#.#.....#.....#.....#...#.#...#...#.......#...#...#..#....#....#.#....#...#.#.#.#.......#...
.....#.#.....#.....#.....#...#.#...#...#...#...#..#....#####..#....#...#..#....#.#.#.#.......#...
.#...#.#.....#...#.#...#.#...#.#...#...#...#...#.#........#..#.....#...#..#....#.#.#.#.......#...
..###..#####..###...###...###..#...#...#....###..#####....#..#####..###...#....#.#.#.#.......#...
........................................##.................................................##....
..............................................................................................2.3

正解:SECCON{524287mP}

解けたチーム数:国内:15チーム、国際1314チーム

決勝に出場しても得点がゼロだと職場に戻れない、という過去の出場者の声を反映したサービス問題でした。

実際のところ出場した全チームが解けている問題で、練習問題相当として出題しました。簡単ですね。

回答者:@stereotype32さんによるwrite-up https://pub.harold.kim/ctf/SECCON-CTF-Finals-2019/chal6-challenges/factortheflag.html

Jeopardy 6-4: Bad Mouse

問題文:

Hack the USB HID device. (配布デバイスの写真)

No claim no return. Firmware dump list is as follows:

:10000000A5C1C9C1F5C1C7C194C5C5C1C4C1C3C1DA
:10001000C2C1C1C1C0C1BFC1BEC1BDC1BCC13D3EE5
:100020003F4061426344415569486B4A4B4C4D4ED9
:1000300051504F61535C5556575859606B656D6709
:100040006F69417163644579774C794E7B505D4DA2
:100050006F707172737475767778797A7B7C7D7E38
:100060004F487251434C45464748694E5B555D5772
:100070005F597158535455565758595A5B5C5D5ED9
:100080005F607D66456C476E49706D716B6C6B7D12
:100090005178537A557C777E777875417D443F4619
:1000A000414845464344414D49504B524D545152AD
:1000B0004F504D59555C575E596055615B5C5B6DA7
:(省略)
:100F90000895EE0FFF1F0590F491E02D0994FB01D9
:100FA000DC0104C08D910190801921F44150504022
:0C0FB000C8F7881B990B0895F894FFCF38
:040FBC000105FF5AD2
:00000001FF

ダウンロード先:badmouse.hex https://gist.githubusercontent.com/takesako/9171fd0111b37dcc9ee775ae23f3fad2/raw/16f10cc55bc3106f57d66f3c1cddb20d650affb6/badmouse.hex

出題の意図:

AVRアセンブリ読解、ファームウェアリバースエンジニアリング問題。

ハードウェア情報:

Digispark - USB Development Board ATtiny85マイコン搭載

プログラムメモリ: 8KByte → すごい大容量!
EEPROM: 512Byte 
SRAM: 512Byte 
内部クロック: 12.8/16.5MHz
動作電圧: 2.7V~5.5V

V-USBというライブラリを使って、AVRマイコンのGPIO端子からUSBプロトコルの信号を直接出力しているという低レイヤーハッカー向けの開発ボードです。eBayやAliExpressで中国から1個200円前後でDigispark互換機を安価に購入できます。このような素敵な開発ボードが安価に入手できる中国が本当に大好きです。私はこういった基板に触るのが好きで深センに2回訪問しました。

解法1:忍耐で突破(おススメできない)

Bad MouseをUSBポートに挿すと、マウスが勝手に動いてマウスが自動でクリックされます。マウスカーソルの動くスピードが徐々に遅くなっていくのですが、ペイントソフトを起動してBad MouseをUSBポートに挿したままずっと待てば正解のフラグが描かれます。ただ、このためには辛抱強く待つ必要があり、このアプローチだとフラグ文字列の横の長さがどこまであるのかわからないので途中大変不安になります。

回答者:@hikariumさんによるwrite-up https://hikalium.hatenablog.jp/entry/2019/12/22/232138

実はこの文字描画フォントがFactor the Flag問題と同じであることに気づくと、ビットマップデータがどういう形式で埋め込まれているかのヒントとなります(伏線)。

解法2:ビットマップデータを抽出

HEX形式の16進ダンプをリバースエンジニアリングしてビットマップデータを抽出します。

先頭の3行目以降のバイナリのASCII文字列を見るとopqrstuvwxyz{|}~のように1ずつ増えているパターンが見えます。 これはマルウェアの簡単な暗号化で見ることがあるunrollingという手法だと目grep時点で気づくことができます。

実際に逆アセンブルすると、64の剰余を計算してこの文字列テーブルから63引いた値から1ずつ減らしながら6bitの剰余を2回デコードしてY座標を12ドット描いてから右のX座標にずれて、またY座標の描画を繰り返していることがわかります。

C言語でビットパターンをデコードするプログラムを書くと以下になります。

#include <stdio.h>

const char a[] = "?@aBcDAUiHkJKLMNQPOaS\\UVWXY`kemgoiAqcdEywLyN{P]Mopqrstuvwxyz{|}~"
"OHrQCLEFGHiN[U]W_YqXSTUVWXYZ[\\]^_`}fElGnIpmqklk}QxSzU|w~wxuA}D?FAHEFCDAMIPKRMTQRO"
"PMYU\\W^Y`Ua[\\[mc`ibsdcughIjKlK}p@rBstwzY@kBeDCE?@CBERgGaHOJKLINQYS[UYQYWXyakemgo"
"iAgcdelwqys{uM}opw~KwUvO{?F{|IDQIcJUMQLGHgP]T_VaXsYSTSeg`ibkd}e_`cbedcuihkjklmt?yA"
"{C}UEwxuz}E?GAE}ECDAFIQKSMQIQOPqYc\\e^g`y^[\\UeshsjulEmghejmuowqumustS|I@KBMD_E?@?"
"QSLUNWPiQKLORqXCZ}\\[]WXQaodofqhAicdqlyqKr}uytopOxE|G~I@[A{|[DQHSJULgMGHIJ[T~]OXQR"
"STuYoZ_\\Yk]`_`gb{dEu?hojklMu?xAzC|UzwxYz[|yMa@cBCDELWQYS[Um]OPoXe\\g^i`{a[\\[mohq"
"jslEmghGp}t?vAxSystvFxHWI[|]~?@B";

int main()
{
    for (int i = 0; i < sizeof(a)-2; i += 2) {
        for (int j = 12-1; j >= 0; j--) {
            putchar((((a[i+(j/6)]-63-(i+(j/6)))%64)>>(j%6))&1 ? '#' : '.');
        }
        putchar('\n');
    }
}

実行結果:

$ gcc decode.c && ./a.out
............
......#.....
......#.....
..########..
......#...#.
......#...#.
............
............
..........#.
..#########.
..#.........
............
............
...##.......
..#..#.#....
..#..#.#....
..#..#.#....
..#####.....
............
.#..###.....
#..#...#....
#..#...#....
#..#...#....
.#######....
:
(略)

ダウンロード先: https://gist.github.com/takesako/be0abea85a75b07b3fd9b63744f736dd

AVRは初見だとハーバードアーキテクチャで16bit単位のメモリアクセスでI/O命令の扱いが少し特殊で読むのは大変なのですが、 Arduinoで使われている世界標準のマイコンなので(今やPICより主流)IoT時代に覚えておいて損はないと思います。 GhidraがAVRに対応していて便利な世の中になりましたが、プリミティブな命令が多いので、慣れないとちょっと時間がかかります。

回答者:@ptr-yudaiさんによるwrite-up https://ptr-yudai.hatenablog.com/entry/2019/12/23/001203

正攻法で頑張って解いていて本当に素晴らしいです。

解法3:

ファームウェアを書き換えてwaitの待ち時間を短縮させてフラグを表示する方法も想定解です。

リバースエンジニアリングすると、プログラムメモリの特定の番地を読み込んで0x3Dと比較して一致していなかったら待ち時間を延ばさない、といった処理が見えます。0x3Dが出現するのはプログラム中1個所のみで、HEXファイルで言うと2行目の後尾の方です。

micronucleusのビルド

Arduino IDEを使って書き込んでも良いのですが、今回はmicronucleusという軽量なコマンドラインツールを使ってDigisparkのファームウェアを書き込みます。micronucleusは、原理的にファームウェア上書き一方向のみの書き込みができて、読み込みには対応していません。

$ sudo apt install -y libusb-dev
$ git clone https://github.com/micronucleus/micronucleus.git
$ cd micronucleus/commandline
$ make

ファームウェアの更新

アセンブルの結果より、HEXファイル中の2行目の3Dを1bit書き換えます。

$ sed -i -e "s/3D3E/2D3E/" badmouse.hex
$ ./micronucleus --timeout 60 badmouse.hex
> Please plug in the device ...
> Press CTRL+C to terminate the program.
> Device is found!
connecting: 40% complete
> Device has firmware version 2.4
> Device signature: 0x1e930b
> Available space for user applications: 6522 bytes
> Suggested sleep time between sending pages: 7ms
> Whole page count: 102  page size: 64
> Erase function sleep duration: 714ms
parsing: 40% complete
parsing: 60% complete
> Erasing the memory ...
erasing: 80% complete
> Starting to upload ...
writing: 100% complete
>> Micronucleus done. Thank you!

正解:

SECCON{379eaX85bTa99c695b36855i4Ycfa5b5}

勝手に解けている動画(約10分)

解けたチーム数:国内2チーム、国際6チーム

懇親会で余った予備のBad USB欲しい人いる?と言ったところ全部すぐなくなり人気でした。 ハードウェア問題に挑戦した人は総じて楽しかったと言っていただけて出題者としては嬉しかったです。

出題者:

Perlが第二母国語で、TMTOWTDI (There's More Than One Way To Do It)「やり方はひとつじゃない」の哲学が好きです。SECCON創設者の一人で初代実行委員長です。

DAY2にNICTブースに立ち寄ったら@kamapuさんに撮影していただきました。

挑戦していただいたCTF playerの皆様、ありがとうございました。