DEFCON 2014 writeup
5/17 ~ 5/19にかけて、DEFCON CTF 2014に出場していました。
結果は、獲得ポイント19ptで、1061チーム中31位と本戦出場には及びませんでしたが、目標としていたsutegoma2と同じ得点なので、ある程度満足する結果を残せたのではないかと思います。
それでは、メンバーごとのWriteupをどうぞ。
@potetisensei
こんにちわ、14pt入れたpotetisenseiです。
他のメンバーはもうダメです。
僕一人がEpsilonDeltaみたいなもんです。
僕がEpsilonDeltaです。
Baby's First1 heap
Heap feng shui not required
http://services.2014.shallweplayaga.me/babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c
babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c.2014.shallweplayaga.me:4088
メンバーが直したCのコード
要点は
- NX bitが有効、Partial RELROなのでGOTの書き換えが可能。
- 112行目のget_my_line(buff, 0x1000)でバッファーオーバーフローが発生する。
- 114行目のmemcpy(loc_table[0xa], buff, count)によって、同時にヒープオーバーフローが発生する。
- 122行目のexit_func(1)の呼び出しによって、main関数からreturnせずに終了してしまうため、そのままではバッファーオーバーフローを利用したROPを書くことが出来ない。
- 自前のheap allocatorを利用している。
の5つぐらいでしょうか。
これらの要点から考察すると、ヒープオーバーフローによって、free関数内のmalloc_chunkの繋ぎ直し処理を利用した任意のアドレス書き換えが必要になるのではないかと考えられます。
今年の文化祭の部誌の記事で、glibcのfree関数を調べていたので、ここまではすぐに気がつくことが出来ました。
では、実際に何を書き換えればいいのでしょうか?
malloc_chunkのunlink処理での任意のアドレス書き換えの制約として、
書き込まれるアドレスも、書き込むアドレスも、writableなメモリ領域でなければならない
というものがあります。 非常に古い記事ですが、Hackers Hut: Exploiting the heapを読めば何故なのかは理解できると思います。
さて、今、書き換えたいアドレスの候補としては、printfのGOTや、関数ポインタであるexit_func変数が上げられます。 しかし、書き込むアドレスは、writableでかつexecutableな領域でなければなりません。 通常、このようなメモリ領域は存在しません。
私もここでかなりの時間悩んだのですが、よくよく考えてみれば、この問題は1ptの問題で、そこまで難しいわけがないのです。 更に、12行目のputs("Did you forget to read the flag with your shellcode?")という不自然な書き込みが非常に気になりました。
まさか、と思い、調べてみると、malloc関数内で、mprotectを呼び出し、heapをPROT_READ | PROT_WRITE | PROT_EXECに設定している事が分かりました。
.text:08048BAF loc_8048BAF: ; CODE XREF: malloc_extend_top+58j
.text:08048BAF mov dword ptr [esp+8], 7 ; prot
.text:08048BB7 mov eax, [ebp+sbrk_size]
.text:08048BBA mov [esp+4], eax ; len
.text:08048BBE mov eax, [ebp+cp]
.text:08048BC1 mov [esp], eax ; addr
.text:08048BC4 call _mprotect
従って、heapはwritableでかつexecutableなメモリ領域になっているため、shellcodeを配置し、そこに飛ばしてやればいいことが分かります。
書いたコードがこちら。
本来ならexit_funcを書き換える問題なのでしょうが、何故かfreeでクラッシュしてしまうのでprintfのGOTを書き換えました。
こんなmprotectに気づかないと解けないような悪問のどこが初心者向けなのかさっぱり分からないし、僕はこの問題が嫌いです。
The flag is: Good job on that doubly linked list. Why don't you try something harder!!OMG!!
Gynophage2 shitsco
http://services.2014.shallweplayaga.me/shitsco_c8b1aa31679e945ee64bde1bdb19d035 is running at: shitsco_c8b1aa31679e945ee64bde1bdb19d035.2014.shallweplayaga.me:31337
Capture the flag.
中途半端に書きなおしたCのコード。
shitscoは簡易shellのようになっていて、入力を関数sub_8048A50でコマンド名、引数のリストにパースし、利用できるコマンドの情報が書かれている構造体のリストと入力したコマンド名を比較、入力したコマンド名と構造体のコマンド名が一致したら、構造体で定義されているcall backされる関数を呼び出すという処理をしています。
あまり詳しく見ていないので、予想ですが、関数sub_8048A50はパースして比較するだけの関数であるため、それ自体に何らかの脆弱性があっても、到底利用出来るものではないでしょう。 従って、callbackされる関数を見ていきます。
enableというコマンドを入力すると呼び出される関数です。 sub_8048A50から呼び出される時、argsのリストが引数に渡されます。 引数がある時、argv[1]を、引数が無い時、標準入力から入力を読み込み、passwordと比較します。 passwordと一致しない場合には、入力が出力されます。 passwordが一致した場合には、memo.c内のglobal_permissionという変数に1が代入され、flagというコマンドによってflagを出力出来るようになります。
少し見ただけでは、入力を読み込む配列、[esp+18h]のサイズは0x20byteであり、入力される文字列の最大長は0x20byteであるため、オーバーフローはなく、全く問題ないように思えます。 しかし、配列としては問題無くとも、文字列として、この状態で[esp+18h]を出力することには、問題があります。 本来、文字列には、NULL文字という文字列の終端を表す文字(デファクトは0x00)が付属するわけですが、0x20byteのchar配列に対して0x20byteの入力を行うことは、このNULL文字を消去してしまうということです。 従って、最大長を書き込んだ場合、俗にいう「stack leak」が発生します。
では、stack leakによってleakされる情報は何でしょうか? 出力する時点で、[esp+18h+20h]、すなわち[esp+38h]には、strcmpの戻り値が格納されています。 strcmpの戻り値は、引数の文字列s1, s2を辞書順で比較した場合の大小です。 すなわち、自分が入力した文字列と、passwordの辞書順での大小がleakされるわけです。 よって、これを元にpasswordを当てることが出来ます。
これによって探索すると、passwordが確かbruT3m3hartとなり、それをenableコマンドで入力し、flagコマンドを打てばflagが出力されます。
The flag is: Dinosaur vaginas
Gynophage3 sftp
Such a simple daemon.
http://services.2014.shallweplayaga.me/sftp_bf28442aa4ab1a4089ddca16729b29ac
sftp_bf28442aa4ab1a4089ddca16729b29ac.2014.shallweplayaga.me:115
sftp(SSH File Transfer Protocol)クライアントを模したソフトウェア。 まともに利用できるコマンドは、USER, PASS, LIST, RETR, STORぐらいで、LISTは与えられたディレクトリの中身を表示、RETRは与えられたファイルのサイズ、中身を表示、STORは与えられたファイルを作成、書き込みを行うコマンドです。
要点は
- NX bit, Stack canaryが有効
- libcはRETRによって読み出し可能
という2点だけでしょうか。
見つけた脆弱性は3つで、1つ目はLISTコマンドに"//////////////////////////////////"のような引数を渡してやり、そのディレクトリの中に、かなり長いファイル名を置いておけば、sprintf(buf, "%s/%s", directory_path, filename)のような処理でバッファーオーバーフローする、というものです。 これは、filenameには、途中にNULL文字を含めることが出来ないため、stack canaryが突破できず、利用できるものではありません。
2つめと3つめは、RETRコマンド内に存在しています。
RETRコマンドでは、まず読み出すファイルに"flag"という文字列がないか確認します。 この処理をbypassすることは不可能だと思われるので、RETRを用いてflagを頑張って読みだす、ということは出来ません。 次に、読み出すファイルのpathのbasenameの先頭文字が"."かどうかを確認します。 これは、特に何の問題はありません。 そして、xstatを呼び出し、ファイルが存在しているかを確認し、存在していればファイルサイズを出力します。 その後、stdinから入力を受けつけて、入力が"SEND"であればファイルを読みだして出力、そうでなければそのままreturnする、という処理を行っています。
2つめの脆弱性は、SENDを行わず、STOPも入力しなかった場合、stack leakが起こるというものです。 ただし、そこまでleakして有効なものは存在していないので、これも利用できるものではありません。
3つめの脆弱性が本命で、SENDでファイルを読み出しバッファに格納する際、バッファは、可変長配列を利用しています。 この時、可変長配列のサイズは、xstatで読み込んだファイルサイズを元に計算しているわけですが、SENDの入力待ちをしている間に、ファイルが書き換えられ、ファイルサイズがより大きくなっていた場合、想定しているファイルサイズと、実際に読み出されるファイルサイズが異なるため、バッファーオーバーフローが起こります。 競合という感じはしませんが、一種のrace conditionと言うことも出来るでしょう。
これを利用すると、最後にwriteによって出力される文字列サイズは、ローカル変数iで記憶されているため、書き換えることでstackの中身を全て見ることが出来、stack canaryをleakさせることが出来ます。 後は、leakさせたstackとRETRで読みだしたlibcを用いて、ROPを構築することで、任意のコード実行を行うことが出来ます。
The flag is: z0mgs0fuckings1mpl3
Gynophage4 polyglot
Just open /flag, and write it to stdout. How hard could it be?
polyglot_9d64fa98df6ee55e1a5baf0a170d3367.2014.2014.shallweplayaga.me 30000
Give me shellcode. You have up to 0x1000 bytes. All GPRs are 0. PC is 0x41000000. SP is 0x42000000.
Throwing shellcode against linux26-x86.(http://services.2014.shallweplayaga.me/polyglot_9d64fa98df6ee55e1a5baf0a170d3367)
Throwing shellcode against linux26-armel.(http://services.2014.shallweplayaga.me/polyglot_6a3875ce36a55889427542903cd43893)
Throwing shellcode against linux26-armeb.(http://services.2014.shallweplayaga.me/polyglot_c0e7a26d7ce539efbecc970c154de844)
Throwing shellcode against linux26-ppc.(http://services.2014.shallweplayaga.me/polyglot_5b78585342a3c116aebb5a9b45e88836)
題名の通り、x86 Linux, ARMel Linux, ARMeb Linux, PowerPCeb Linuxの4アーキテクチャーで/flagの中身を出力するpolyglot shellcodeを書く問題。(初め、それぞれのアーキテクチャに対してshellcodeを書くだけの問題だと思っていたので、4アーキテクチャ上で同じように動く一つのshellcodeを書く問題だと気づいた時にはつらい気持ちになった)
それにさえ気づいてしまえば、後はやるだけ、説明することなし。
The flag is: I can tie a knot in a cherry stem
HJ2 byhd
Who hath lived like hacker's life and refused the normalness must be rewarded with straw, sticks and bricks.
http://services.2014.shallweplayaga.me/byhd_147e0accdae13428910e909704b21b11
byhd_147e0accdae13428910e909704b21b11.2014.shallweplayaga.me:9730
ハフマン符号化したshellcodeを送ると、復号化して実行するバイナリ。 ほとんど読んでないのでそれ以上の詳細はよく知りません。 符号の対応表は定数であるため、gdbでheapメモリをダンプし、search.pyを使うことで求めました。
やるだけ。
The flag is: What a cold ass gershnoskel.
Jymbolia2 100lines
It's not broken, you just need more RAM.
http://services.2014.shallweplayaga.me/100lines_53ac15fc7aa93da92629d37a669e106c
100lines_53ac15fc7aa93da92629d37a669e106c.2014.shallweplayaga.me:20689
直して
ダンプして書くだけ
The flag is:#RadicalSpaceOptimization!
Gynophaseの問題が非常に良問で良かったと思います。 以上。
@hiromu1996
進捗ダメでした
routardedというクソ問はいくらでもwriteupがあると思うので,他を参照してください
zombies
計算が面倒臭かった以外は,組むだけ
水平距離と高さが与えられて,そこを通るように仰角を計算するだけ.
ゾンビが距離x,高さyの点にいるとして, 仰角をθ,弾丸の初速をvとおくと,速度のx方向の成分は 及びy方向の成分は となる. 水平距離をこの速度で割ると,到達までの時間が計算できて, となる. あとは,重力加速度を考えて, という等式を満たすθを求めればよい.
適当に式変形をすると, として
で求まる.
Level 1-10はこれでいけて,Level 11-100は,更にゾンビと子犬の座標が与えられて そこへtr秒間で動くというものになる. どうやらチェックが適当らしく,出力で返す(x, y)を仰角θ及び初速vで通り, さらに,その(x, y)がゾンビと子犬を結ぶ直線上にあれば正解と扱われるみたいだったので, tr - 2秒で届く場所に投げるように出力した.
ソースはこちら.