あまり時間が取れず観客係をやっていたところ、偉い人から解けそうな問題をいただいたので解いてみました。間に合ってよかったです。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の文字列やdoについては、加算減算コマンドで文字を加工しますが、そもそも加算減算コマンドがd,eなので隣に文字列が隣接すると落ちます。面倒なのでd,0oだけでなく、すべての0x6Xをコマンド以外使わないようにして解決しました。

この後3時間くらいかけてようやく全部うまくいって、アップロードの仕方を教えてもらって実際に上げてみましたが、なんの反応もありません。「上げた後ってどうなるの?stdoutってどこにでるの?」みたいな質疑を繰り返しているうちに、「あっ、そういえば知らんかったが、シードはしょっちゅう変わってたで」「ア…!ア..」という会話をしました。調べてみると30秒くらいで変わります。決め打ちsrand(seed)は沈没です。丁寧に作りこんだアドリブも死にました。悲しいね。

ただ冷静に考えて、普通に乱数列を作って重複しているところをnopにするだけでも、1ずれ2ずれがない場合は上手くいくので、何も考えずに実装を追加します。我ながらこれがかなり偉いムーブで、シードが高頻度で変わるので、結構な頻度でうまくフラグが表示されます。

あとはスクリプトをぽちぽちして、ローカルでフラグが表示された瞬間にファイルをアップロードするだけです。

ポチポチしていると、フラグが表示されました。いまだ!アップロード!!(朝5時)

作成したスクリプトはここ

pwnではないようにも思いますが、応援係りから昇格できたので良かったです。

(5/31追記) 後からコードをよく見ると、デバッグ用に重複チェックの関数をreturnにしたままだったのを発見。実はお構いなしに投げていたようでした。運がよかったです。