0CTF 2017 writeup その3
その他writeup
0CTF 2017 writeup その2 - きゅうり。 (char)
0CTF 2017 writeup - きゅうり。 (integrity, oneTimePad)
残り解く予定(oneTimePad2、BabyHeap2017、diethard)
今回は、EasiestPrintfのwriteupです。と言っても、自力では解けないので他の人のwriteupを参考に勉強した記録です。
tl;dr
今回得られた知見
- printf内でのmalloc/free呼び出し
- hook関数
- one-gadget RCE
writeup
動作としては、任意のアドレスを1つだけ教えてくれた後、FSBのあるleave関数が呼び出されて即exitという流れ。
NX、Full RELROで制約は厳しい。
まず、任意のアドレスをリークできる部分で適当にlibcのアドレスを特定。
本来ならばここからデストラクタを書き換えたりGOT overwriteしたりしたいところだが、Full RELROなので思いつくことが諸々できない。
現状ではEIPを奪えていないので、まずはEIPを奪うことを考える。
結論から述べると、printf内で呼ばれるmalloc/freeを利用する。printfを追って行くとvfprintf.cなるものが呼ばれることがわかるが、ソースコードを読んでみると、widthという値がWORK_BUFFER_SIZE-32以上のときmallocが呼ばれる様子がわかる。(1475行目前後)
要するに、printfで表示する文字列がある程度長いときには一旦mallocで確保した領域に文字列を置き、表示後にfreeしているものと考えられる。
ではこのmalloc/freeをどう利用するかだが、各種hook関数(__malloc_hook, __free_hook)に注目する。hook関数を用いるとmallocなどの関数を独自関数に置き換えることができ、通常はデバッグ用などに使われる。ユーザが定義するので当然と言えば当然だが、この各種hook関数はlibcのうちwritableな領域にある。(画像一番下)
未定義の時は該当するアドレスには0が入っているため、おそらく最初にhook関数のアドレスが0かどうかを確認し、0でない時はそのアドレス先が呼ばれると思われる。
なので、FSBで__free_hookをoverwriteしつつ、一定量表示させるようにすることでEIPを奪うことができる。
最後にどうやってシェルを取るかだが、one-gadget RCEを用いた。いわゆる一発でシェルをとれるgadgetである。なおこのようなgadgetがlibcのどこにあるかは外部によくまとまっているブログがあったのでそこを参考にした。どうやら本番環境のlibcにも同様のgadgetはあるようなので問題ない、と思う。
EIPを奪えているので、スタックのアドレスをリークしつつmainに再び飛ばしても解けるとは思うが、せっかくなので新たなものを使ってみた。
0CTF 2017 writeup その2
その他writeup
0CTF 2017 writeup その3 - きゅうり。 (EasiestPrintf)
0CTF 2017 writeup - きゅうり。 (integrity, oneTimePad)
今更ですが、本番中には解けず、後から解いたもののwriteupシリーズです。
char
自明なbuffer over readがあり、offset=32で簡単にEIPが奪える。ただし入力はprintableなもの(asciiで0x20〜0x7e)しか受け付けないので、その範囲内でROPを組む必要がある。
libcが固定アドレスでprintableなアドレスに読み込まれるので、その中の使えるgadgetを組み合わせて頑張る。rp++の結果からprintableなものだけ抽出するスクリプトを適当に書いて、あとは泥臭く組み立てていっただけなので特に説明はないです。
ecxは{"/bin/sh",NULL}ってずっと思ってたものの、特になにもしなくてもシェルとれた。知見。
flag{Asc11_ea3y_d0_1t???}
Volga CTF 2017 Quals writeup
m1z0r3として参加して、450ptsで188位でした。
予定があってあまり時間がとれませんでしたが、cryptoを2問解いて250pts入れたのでそのwriteupを書きます。(今回はスクリプトは無し)
VC (crypto 50pts)
png画像が2枚渡される問題。B.pngはA.pngになんらかの情報が追加されてるように見えるので、2枚の差分をとるとフラグ。
Curved (crypto 200pts)
楕円曲線DSA(ECDSA)を用いた署名を使ってコマンド実行を行うサーバが動いており、そのスクリプトとexit、leaveコマンドの署名が渡される。
lsやcatなどを実行するには、正しい署名を生成する必要があるが、署名をするには秘密鍵が必要となる。
一般にECDSAでは署名を施す際にランダムな値kを用いる必要があり、これが固定値だと秘密鍵が逆算可能となってしまう。(詳しくは楕円曲線DSA - Wikipedia)
今回渡されたexitとleaveの署名を見ると、rの値が等しいことからkが同一であったことがわかる。したがって秘密鍵が逆算でき、得られた秘密鍵からcat flagを実行すればフラグが得られる。
実はこの問題、過去にチーム内CTFで出題したものとまったく同じコンセプトだったのでECDSAの処理を見た瞬間に見当がついたりもした。
所感
Oracleはやればできそうだったり、Casinoは面白そうな問題だなぁと思った。が、cryptoはどれもPoWがめんどくさすぎて途中で投げ出した。(予定があったのもあるけど)
ちゃんとPoW用のライブラリは用意しておくべきですね...
0CTF 2017 writeup
0CTF 2017にm1z0r3として参加しました。
266ptで111位でした。WelcomeとServeyを除いた222ptのうち189ptを入れました。
cryptoのintegrityとoneTimePadを解いたのでそのwriteupです。
その他writeup
0CTF 2017 writeup その3 - きゅうり。 (EasiestPrintf)
0CTF 2017 writeup その2 - きゅうり。 (char)
integrity (crypto 75pt)
・register
こちらからnameを送信すると、
md5(pad(name))+pad(name)
をAES-CBCで暗号化して、IVと暗号文が送られてくる。
ただしpad(name) == pad("admin")は弾かれる。
・login
こちらからIVと暗号文を送信すると、それを復号してmd5チェックをした後unpadして出てきたnameとしてログインする。
nameがadminならフラグ。
解法
name = md5(pad("admin")) + pad("admin")としてregisterして、返ってきた暗号文からIVを除いてそのままloginに送信すればフラグ。
flag{Easy_br0ken_scheme_cann0t_keep_y0ur_integrity}
oneTimePad (crypto 114pt)
keygenという自作PRNGから乱数を生成しOTPにより暗号化したもの。keygenでは乱数をseedとしてprocess関数から乱数を生成しているので、process関数が鍵になってくる。
process関数はの元でを返しているだけ。なお生成多項式はP。また、すべての演算は上で行われるのでxorと+は等価。
これを数式で表すと、
ただしとする。
これを利用して与えられた暗号文を変形していく。ただし、fake_secretからkey2,3は既に計算済みとする。
したがって、あとはの平方根をとればよい。
一般にの元で平方根をとるには、n-1回平方をとればよい。
flag{t0_B3_r4ndoM_en0Ugh_1s_nec3s5arY}
所感
oneTimePadは最初見たときに数学系の問題かな〜と思いつつxorこねくり回したり1bitずつ求めようとしたりして時間を浪費したのがもったいなかった。 さらに、平方根とるだけって気づいてからn-1回平方すればいいことを知るまでに無駄に時間を使った。
ので、この2問は瞬殺できるぐらいにはなりたい。ただ無駄に苦戦したおかげでoneTimePad解けたときはうれしかった(こなみ)。
oneTimePad2も解きたかったけど式がより複雑になってて断念しました。
今回はほぼソロ参加みたいな状況でpwnが手付かずで残ってたのに1問も解けてないので精進💪
ED CTF my_sandbox writeup
解いたのでwriteupです。
プログラムとlibcが渡されてます。
事前確認
$ ./my_sandbox Your name please: hoge Welcome to my echo program! This program has an bug. I couldn't fix this bug... :( But, I developed sandbox for this bug. Hackers will never be able to get shell, haha! :) Enter your message: 1 Entered: 1 (snip) Enter your message: 10 Entered: 10 It seems cracker couldn't crack my program, I'm winner! See you.
入力をそのまま返すのを10回繰り返すようなプログラム。バグがあるけどsandbox化してやったからシェルとれないやろ???みたいなやつ。
バグがあると言っている通り、FSBがあります。
続いてchecksec
CANARY : disabled FORTIFY : disabled NX : disabled PIE : disabled RELRO : FULL
方針
FSBがあるので、まずは素直にlibcのアドレスをリークします。この時点で最後はret2libcに帰着しそうなことがなんとなくわかります。
あとはreturnアドレスを書き換えるためにスタックのアドレスをリークする必要があります。しかし、sandbox化(?)により、FSBは通常のスタックではなくmallocによって確保された領域(擬似スタックと呼称)で発生します。なので、都合よくリークできそうなアドレスが積まれていません。
ここで擬似スタックがlibcの直前にあることに注目すると、libcのベースアドレスから、入力した値が積まれる擬似スタックのアドレスが計算できます。そして計算したアドレスからsnprintfの第一引数にあたるアドレスを計算しリークさせることでスタックのアドレスがリークできます。
これを図解すると、snprintf(buf,size,user_input);
が呼ばれる際の擬似スタックは、
addr_buf size addr_user_input xxx xxx xxx xxx xxx user_input
となっており、libcのベースアドレスからuser_inputのアドレスが計算できます。これが計算できるということは通常のスタックにあるbufのアドレス、つまりaddr_bufが計算できるためスタックのアドレスもリークできます。
ここまでできたら後はリークしたスタックのアドレスからreturnアドレスの場所を計算して書き換えてsystemを呼んで終了。
Codegate 2016 OldSchool writeup
いわゆるbataさんのリストのeasyの問題。
チーム内の勉強会で解いたのでそのwrituepです。
まずは挙動確認
$ ./oldschool YOUR INPUT :hoge RESPONSE :hoge $ ./oldschool YOUR INPUT :%08x RESPONSE :000003fc
どうやら入力をそのまま返してくるだけのプログラムで、FSBがあります。
ちなみにソースコードも渡される?っぽいので、そっちを見ればFSBは自明です。
続いてセキュリティ機構のチェック。
CANARY : ENABLED FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : disabled
NX有効。
方針
FSBがあるので、適当に情報をリークしてやればlibcのアドレスやstackのアドレスはわかりそう。各種アドレスがリークできれば、ret2libcでsystemを呼び出せば終わり。
ただし、これを実現するには2回FSBをする必要があります。(1回目でリーク、2回目でreturnアドレスの書き換え)
ですがプログラム自体はprintf(buf)直後に終了してしまうので、.fini_arrayを書き換えてmainをもう一度呼び出します。TW CTF 2016のgreetingを解いたことがあったのでここの発想はわりとすんなり出てきました。
あとはこれらを実装するだけ。
from m1z0r3 import * はsocket通信部分をpack, unpackを定義してるだけなので適宜置き換えてください。全部ローカルでのアドレスです。
後半のアドレス指定が雑感ハンパないのは許して。
SECCON2016 Qual Alpha Complex1 writeup
SECCON2016予選のcrypto 300の問題、Alpha Complex1のwriteupです。
4月からに向けてm1z0r3用のcrypto for beginnersみたいな記事(チーム用なので公開予定なし。たぶん。)を書いてたんですが、執筆にあたって色々調べてたら「これAlpha Complex解けんじゃね?」みたいな感じになったのでやってみたら解けました。
12月の記憶を呼び戻して書いてるので、問題の説明とか間違ってたらすいません。
問題
問題文は省略します。Complex.pyとserver.pyという2つのファイルが渡されます。
実はやっていることは単純(?)で、RSA暗号を複素平面上で定義しています。フラグをx座標、こちらから送った値をy座標として、を計算した結果が返ってきます。Nとeはわかっていて、今回のeは0x1337です。
なお、便宜上x座標y座標と呼んでいますが正しいかは謎なのでそこは目を瞑ってください。
解法
暗号文Cの式を展開すると、以下のようになります。
この式には実部と虚部があるのでわけて式を定義してみます。
この2つの式は、xにフラグ(mと定義)が入るとき当然ながら0となります。つまり言い換えると、はmを根に持ちます。なので以下のように表現できます。
、
これを見ると、2式の最大公約式をとればx-mが出てきてフラグを得ることができそうです。
というわけで実際に試します、が、いきなり本番の値でやると大変なのでまずは自分で用意したフラグと暗号文でやります。なおここではe=5にして暗号化してます。
これを実行してみるとちゃんと平文を得ることができました。なので後は本番用の値でやるだけなのですが、21~26行目を見てわかる通り展開した式をベタ書きしてます。本番のeでこれをやると0x1337乗なのでとんでもないことになります。
苦肉の策として、
- expandしたものを一旦テキストとしてファイルに出力
- 出力したものを読み込み、文字列として実部虚部に分割(虚部のIは削除)
- z_real=`実部` z_imag=`虚部`みたいな形でまたファイルに出力(expとする)
- 先ほどのsolverの24行目まで書いたものを用意(solver.sage)
- cat exp >> solver.sage; echo 27行目 >> solver.sage; echo 28行目 >> solver.sage
としました。
solver.sageは63MBになりました。
そんなものを貼るつもりは起きないですが、先ほどのsolverのパラメータを書き換えただけでやってる処理は同じです。
というわけでこれを実行することで無事フラグを得ることができました。
SECCON{3907c554839a6462b920f55274c159eea1949086}
あとがき
Alpha Complex2のほうはまだ問題すら見てないです...
あとこれ解いてたからcrypto for beginnersも書いてないです。許して。