defcon30ctf qual - router-niii
あまり時間が取れず観客係をやっていたところ、偉い人から解けそうな問題をいただいたので解いてみました。間に合ってよかったです。DEFCONはspeedrunやwarmupを除くと初めてのフラグ取得なのでうれしかったです。
夜中にrustなんもわからんをしていたところ、「router-niiiはvm pwnやぞ」と声を書けていただき、バイナリをもらい、起動方法のご指導をいただき、解析を始めます。うまくタスクを分けられる人がいてすごく助かります。
まずバイナリはxmmレジスタを使ったりスタックでオフセットを微妙な数字で加算したりしてブランチを隠していて、読みにくいです。vmovの10バイトをnopに変えてghidraに放り込んだところ、比較的見やすくなります。
with open('./vm', 'rb') as f:
binary = f.read()
edited = b''
flag = 0
for i in range(len(binary)):
if binary[i:i+4] == b'\xc4\xe1\xf9\x6e':
flag = 10
if flag != 0:
edited += b'\x90'
flag -= 1
else:
edited += binary[i]
with open('./edit', 'wb') as f:
f.write(edited)
これでもまだデコンパイラには分からないようでしたが、どこに飛ぶかが分かるのでほぼ問題ありません。 もう少し見にくかったらスタックの計算のところも触ろうかと思いましたら読めるのでヨシ。
0040119e 48 8d 04 LEA RAX,[0xf1aff]
25 ff 1a
0f 00
004011a6 90 NOP
004011a7 90 NOP
004011a8 90 NOP
004011a9 90 NOP
004011aa 90 NOP
004011ab 90 NOP
004011ac 90 NOP
004011ad 90 NOP
004011ae 90 NOP
004011af 90 NOP
004011b0 48 05 81 ADD RAX,0x30f581
f5 30 00
004011b6 ff e0 JMP RAX=><EXTERNAL>::memset
004011b8 0f 1f 84 NOP dword ptr [RAX + RAX*0x1]
00 00 00
00 00
あとは1つずつ命令を読み下して、穴がないかを探します。結論、穴はなさそうで、かなりおとなしく言われたことをやる必要がありました。
- ipはきまった
seed
からsrand(seed)
して、rand()&0xfff
のオフセットに飛ぶ /flag3
をopen, read, putcharするとよい- openは
flag
の文字列を通さないが、0x22のコマンドによる条件が整えば通す - 0x22はレジスタが前から
\x00dog
,\x01dog
になっていて、ペイロードに0x6Xが2回続かない(正確にはもう少しややこしい?)、0x6f(o
)と0x64(d
)は1文字も含まない。
ぱっと見やるだけで、実際もやるだけなのですが、ポイントが2つ
- ipの乱数が重複しています。同じところに書くことになるので、コードが正しく動きません。この重複部分については
mov r0, r0
のようなコードを埋めますが、1つずれて重なっているところもあったりして、うまく修正する必要があります。clear r2
のコマンドを重ねるなどして、その場でアドリブしました。 - flagの文字列や
d
やo
については、加算減算コマンドで文字を加工しますが、そもそも加算減算コマンドがd
,e
なので隣に文字列が隣接すると落ちます。面倒なのでd
,0o
だけでなく、すべての0x6Xをコマンド以外使わないようにして解決しました。
この後3時間くらいかけてようやく全部うまくいって、アップロードの仕方を教えてもらって実際に上げてみましたが、なんの反応もありません。「上げた後ってどうなるの?stdoutってどこにでるの?」みたいな質疑を繰り返しているうちに、「あっ、そういえば知らんかったが、シードはしょっちゅう変わってたで」「ア…!ア..」という会話をしました。調べてみると30秒くらいで変わります。決め打ちsrand(seed)
は沈没です。丁寧に作りこんだアドリブも死にました。悲しいね。
ただ冷静に考えて、普通に乱数列を作って重複しているところをnopにするだけでも、1ずれ2ずれがない場合は上手くいくので、何も考えずに実装を追加します。我ながらこれがかなり偉いムーブで、シードが高頻度で変わるので、結構な頻度でうまくフラグが表示されます。
あとはスクリプトをぽちぽちして、ローカルでフラグが表示された瞬間にファイルをアップロードするだけです。
ポチポチしていると、フラグが表示されました。いまだ!アップロード!!(朝5時)
作成したスクリプトはここ
pwnではないようにも思いますが、応援係りから昇格できたので良かったです。
(5/31追記) 後からコードをよく見ると、デバッグ用に重複チェックの関数をreturnにしたままだったのを発見。実はお構いなしに投げていたようでした。運がよかったです。