読者です 読者をやめる 読者になる 読者になる

SECCON 2014 大阪大会 Writeup

あらすじ

ucqもいるしnewbieの優勝は確実!!!

って、ぇぇぇえええええ?!??!!?!

同点決勝のかるたで負けて準優勝!?!?!!

一体私たち、これからどうなっちゃうの〜?!!!?!!

 

困った

ということで、newbieは大阪大会で優勝できず、またもや決勝進出権を逃してしまった。

一体どういうことなんだ。

原因の半分には、参加できなかった僕のせいというのがあるだろう。。。

というわけで、僕が参加していたらどうなっていたか、ということも気になったので、mageさんに頼まれていたwriteupを書くことにした。

 

shellcode問題のすゝめ

そもそも、大阪大会でどのような問題が出たかを知らない人もたくさんいると思う。

一般的に、shellcode問題とは、問題自体がshellcodeの入力を受け付けていて、条件に合ったshellcodeであれば実行してくれるという中々親切な問題の事を言う。

なので、普通のexploitationの問題とはちょっと違う。

脆弱性を見つける段階などがない上に、基本はshellcodeを書くだけでいいのだ。

DEFCON本戦で「ROPだとpayloadの自由性が低い上に検知されやすいから、ROPでmmapを呼んだ後で実行するための、xorしながらflagを読み出すshellcode」とかとは、基本的に異なる。

まあ気になる人はDEFCON 2013予選のshellcode問題を全て解いてみるといい。

基本的にx86の問題であるから、皆さんでも十分に解けるはずだし、shellcode 400のpenserなんかは単純に難しいので、如何に自分のshellcode力が足りないかを痛感できると思う。

 

時間の計測

とりあえず、問題を入手したので、本番と同じ条件、環境で解いて見ることにした。

時間の計測はTwitterで呟きながら解くことで、大体の時系列と解くのにかかった時間を記録している。

 

大体23:02ちょうどに回答を開始した。

 

dist_package.tgzというファイルだけが配布されるので、とりあえず展開して中身を見てみると、各ステージごとの実行ファイル、xinetdのconfファイル、Cのソースコードが入ってあることが分かった。

とりあえず、実行できないと試しようがないので、サービスを立てることにした。

 

23:09、約7分でとりあえずサービスを立てることが出来た。

使ったPythonのscriptはこんな感じ。

gist40a3b7d2b0604667574b

どうでもいいけど、初めrange(0, 10)をrange(0, 9)にしていたので、stage09を解く時に直したりした。

 

さて、サービスを正常に立ち上げられたので、stage00.cを読んでみる。

案の定、SECCON 2013 本戦の2.kakuと全く同じ形式だった。

とりあえずchecksec.shに掛けてみる。

No RELRO、No canary found、NX disabled

ま、それが普通ですわな。

 

2.kakuは嫌というほどよく知っているので、ほとんどバイナリを読まずにとりあえず以下のアセンブリコードを書いた。

gist7906cf0f80932abd8cba

 

まあいわゆるstagerである。

全ての問題が同じ環境、コンパイラコンパイルされていると仮定して、freadによるNULL文字のread、バイナリ内でcall eaxされているという2つ性質を用いてかなり短く書いている。

聞く所によると、ucqはstagerを全く使わずにほとんど問題を解こうとしていたらしいが、明らかにstagerという短いshellcodeに全て統一したほうがかなりコストが小さく、手動で何らかの変更を加える際にも有利である。

また、実際に実行したいshellcodeを切り替える際にもとても便利なので、stubやmodule化とも言えるかもしれない。

stagerだけではファイルは読み出せないので、keyword.txtからflagを読み出す用のshellcodeを"生成"する。

SECUINSIDE CTF 2014の本戦の時に、shellcodeの手書きに手間取って、int03さんに怒られたので、反省してある程度shellcodeの生成については自動化している。

metasploitは遅いし使う気が全く起きない(もちろん手書きが出来ないのも論外なので、自動化に頼るすぎるのも良くない)。

ちなみにこんな感じ。

f:id:potetisensei:20141111030852p:plain

 

必要なshellcodeは出来上がったので、shellodeに対する制約が何もないstage00に対するexploitを書く。

gist2722c574ded55d489fa9

これが、開始後約12分でようやく1問。

 

stage01は全くソースコードを読まずに、stage00のコードを使いまわせそうな気がしたのでportだけ変えてみると、案の定読めたのでstage00.pyをコピーしてstage01.pyにする。

これがstage00を解いた大体20秒後。

 

stage02のソースコードを読む。

どうやらpushが制限されるらしいが、あの短いstagerではほとんど影響がない。

適当にadhoc.sをコピーしてadhoc02.sを書いた。

gist19b559082e8e3d38c192

 

stage01を解いて約2分後。ウケる。

 

stage03のソースコード読む。

/bin/shという文字列が含まれているとNGらしいが、そもそもstagerなので全く関係ない。

stage00のコードを使いまわして終わり。

stage02を解いて約1分後。ここら辺にもstagerの強みが出る。

 

stage04, 05のソースコードを読む。

どうやらint 0x80やsysenterを制限するらしい。

stage06, 07, 08のソースコードを読む。

どうやら命令長制限がかかって、それぞれちょうど命令長が8, 4, 2でなければならないらしい。

めんどくさそうなので後に回す。

 

stage09のソースコードを読む。

出た。短歌か。

それなら俳句もあるんだろうと思い、ざっとstage10 ~ 15にも目を通す。

どうやら9, 10, 14が緩い俳句系(命令がその幅で区切れればよい)で、13, 15は厳しい俳句系(命令長がちょうど5, 7, 5などでなければならない)らしい。

正直後者はめんどくさそうなので後にするが、12byteのstagerを使ってるので、前者は余裕である(適当にnopで埋めるだけ)。

 

gist794e75c56d8cc8b03db0

gistae073f825172a4de4087

冗長であることよ(笑)

stage03を解いてから約5分後だった。

 

stage10も当然すぐに書ける。

gist3af6c1ac281f85ab3f19

gistdd9cf713f778356e9bbd

stage09を解いてから約1分。

 

ここで、ちょっとstage11に寄り道する。

制約が面白くて、push系の命令とret系の命令のみが許可されている。

ちょっと考えをこじらせて、一瞬printfへのret2libcに持ち込もうかなどと考えたが、push esp; retをしろと言ってるようなものだった。

NX bit enabledだったら書式指定子をstackにpushしてprintfに飛ばないといけなかったかも。

 

gist0743a621061e53f72933

アタマが悪いのでこのたかが4行をバグらせて少し悩む。

gist5cbd7689844132091ef6

結局デバッグせずに修正。ちょっとタイムロスしたかも。

これがstage10を解いてから約7分後。

 

次の3分でstage14を通す。

giste9c73073a0ed8c60b63f

gist935f3570edabda13ca8c

 

8ヶ月前の自分はこんなものが解けなかったのかと思うと呆れる。

 

めんどくさいのが多くなってきたので、とりあえず一番初めの4, 5に戻る。

一度飛ばした時点で解決策は見えていて、単純にshellcode内で1byteずつ命令を書き込めばいいんだけど、アドレス計算がだるかったので飛ばしていた。

gist3d01cfefefcfa9aa6dd1

gist68eb0b79b33592dc1dc1

 

stage05についても、stage04のコードを使いまわして解けるので、一気に2つ解いたことになる。

これが大体stage14の7分後。やはりアドレス計算は少し時間かかる。

 

命令長制限系と厳しい俳句系どっちを取るか迷うが、まだ楽そうなので命令長制限の方を先に解くことにする。

実は2byte制限が最も楽で、stage02のコードを使いまわすだけでいい。

ちょっと迷っていたせいか、7分も時間をかけてしまった。

 

さて、4byte制限と8byte制限をどうするか、という話になってくる。

まあ、それぞれの制約に合わせて全く別のコードを書いてもいいが、使い回したい。

と思っていた時、最近shellcodeの自動化に関連してpythonアセンブラライブラリを書いていたので、x86のprefixの面白い特徴を調べたのを思い出した。

1命令に、repz repnzという相反するprefixを付けると、無視されて1度だけ命令実行される。

これを用いると、2byte制限用のコードに対してprefixを付けるだけで4byte, 8byte制限も解くことが出来る。

nasmに怒られてしまったので直接exploitコードに書いた。

gist490c1fe02d6e5bf3eb55

gist7842e2027c3441bb1834

体感では8と6,7の間に割と時間があったように思ったが、この間約2分であった。冴えてる。

 

残り3問になった所で調子に乗って風呂に入る。

この間約1時間。競技中に1時間もスマブラするつもりか。

 

再開して、stage15の"バイナリ"を見る。

stage00以外で初めてバイナリを開いた気がする。

stage15の制約はちょうど長さが7, 7, 7, 5な4命令だけでshellcodeを書くことであり、明らかにsyscallを呼ぶことすら出来ない。

ここで、ret2libcに帰着することを考える。

普段から気持ち悪いROPはよく書いているので、一瞬で道筋は見えた。

main関数のfreadでshellcodeに入力している所に飛ばせばいい。

少なくとも、入力後には制約を満たしているか確認するfilter関数を通るので、当然filter関数は失敗するから、exitに飛んでしまうが、RELROがないため、exitのGOT Overwriteを上手く用いることが出来る。

gist9a15bf0f28db843af094

ちょっと環境依存なコードを書いてしまったが、まあ問題ないだろう。

giste8ee4e81516c46cfa82e

socketのshutdownの存在を忘れていて、クソみたいな時間の取り方をしてしまった。

再開してから30分ぐらい使ってしまった。

 

stage15が解けたら下位互換的存在な13も一瞬で解ける。

gistebcca1f8894193914ba0

この間約2分。

 

これで全て解ききったつもりになっていたが、よく考えるとまだstage12を解いていない。

alpha numericなshellcodeを書く問題だった。

またか。

8ヶ月前もこれに苦戦したせいで時間が足りなかったのだ。

でも、今回は成長しているので大丈夫。

metasploitのコマンドはググれば分かる。成長している。

と思ったが、新しくインストールしたUbuntu14.04にはmetasploitを入れてなかった...

残り22分、Pythonのscriptでアセンブラを吐くコードを書くのが間に合うわけもなく...

撃沈。

僕も全完出来ませんでした。

newbieの皆さんすいません(この後5分ぐらいで12も解けました)。

終わり。