1人で1日1writeup advent calendar
はじめに
私は、CTFを初めて3年目ぐらいの、まだまだ成長期真っ只中の初心者だ。 だが最近全然実力が伸びない、というか成長を感じることができなくなった。 事実実力が伸び悩んでいるのかもしれないが、以前には感じていた問題を解くたびに成長したなぁという感覚というか感動がない。 問題が解けても「やるだけで特に面白みを感じなかった」というのが増えてきた。 面白い問題も勿論あるが、「へー勉強になったなぁ」ぐらいの感想しか出ない。 このままではモチベが崩壊してしまうので、今までの自分では挑戦してこなかった問題に挑んで、成長した喜びを噛み締めて初めの頃の気持ちを思い出そうと考えた。 「難しい問題に挑戦して、懐かしい昔の自分の感覚を思い出す」が、このadvent calendarの主旨である。
12/12 追記
修論などが予想以上に重く、現状は問題を解いてwriteupを書く時間が充分に確保できない。 一旦7日目で更新は途絶えるが、目処が立ったら再開する。
予定している問題
とりあえず今の所は以下の問題を予定している。
解けなかったら別の問題に差し替えることも有り得る。
自分の進捗管理も兼ねて、既に解けている問題に関してはdone
を付けている。
どの問題のwriteupから出すか特に決めていないので、リクエストがあれば応えたい。
writeupは、このブログで順次公開していき、下の表のリンク先を埋めていく (予定) 。
CTF | 問題名 | ジャンル | リンク | 補足 |
---|---|---|---|---|
NCSTISC CTF 2018 | babydriver | linux kernel | https://hama.hatenadiary.jp/entry/2018/12/03/000000 | done |
Blaze CTF 2018 | blazeme | linux kernel | https://hama.hatenadiary.jp/entry/2018/12/01/000100 | done |
SECT CTF 2018 | gh0st | linux kernel | https://hama.hatenadiary.jp/entry/2018/12/02/000000 | done |
Sharif CTF 2018 | kdb | linux kernel | https://hama.hatenadiary.jp/entry/2018/12/04/000000 | done |
0CTF 2017 Finals | cred_jar | linux kernel | https://hama.hatenadiary.jp/entry/2018/12/06/000000 | done |
QWB2018 | solid_core | linux kernel | exploit only (https://gist.github.com/hama7230/de338959ada9935e9f2dd5f1492e4478) | done |
Midnight Sun CTF Finals | Flitbip | linux kernel | https://hama.hatenadiary.jp/entry/2018/12/19/233626 | done |
0CTF 2017 Quals | KNOTE | linux kernel | exploit only (https://gist.github.com/hama7230/10772ba9e1a0abb610fa38834c30961b) | done |
N1CTF 2018 | network card | linux kernel | ||
WCTF 2018 (by Shellphish) | klist | linux kernel | exploit only (https://gist.github.com/hama7230/ca8df1d28243553e2a5eb995119cde09) | done |
WCTF 2018 (by Cykor) | cpf | linux kernel | exploit only (https://gist.github.com/hama7230/07cd52a0e6838ea8e6b562b852e37608) | done |
Blaze CTF 2018 | blazefox | js engine | done | |
33C3 CTF | feuerfuchs | js engine | ||
PlaidCTF 2018 | roll a d8 | js engine | ||
SECCON 2018 Online CTF | q-escape | qemu escape | ||
HITCON CTF 2018 | children tcache | heap | done | |
HITCON CTF 2018 | baby tcache | heap | done | |
34C3 CTF | 300 | heap | ||
CODE GLAY CTF | resured | race condition | https://hama.hatenadiary.jp/entry/2018/12/05/130055 | done |
HCTF 2018 the end, baby printf ver2, Heapstorm zero writeup
the end
バイナリは小さく単純である。最初にsleep()
関数のアドレスを教えてくれる。標準出力と標準エラーを閉じた後に、任意アドレスへの1byteずつの書き込みを5回行える。
int main(void) { char* buf; sleep(0); printf("here is a gift %p, good luck ;)\n", &sleep); close(1); close(2); for (int i=0; i<=4; i++) { read(0, &buf, 8); read(0, buf, 1); } exit(1337); }
exploit
libc
のアドレスを最初に得られるため、ライブラリ内への書き込みが可能となっている。
exit()
を実行する際に呼ばれる処理を追っていくと、__GI_exit()
-> __run_exit_handlers()
-> _dl_fini()
経由で rtld_lock_default_lock_recursive()
とrtld_lock_default_unlock_recursive()
が呼び出される。2つの関数はldのrwな領域に登録されており、これを書き換えると制御を奪える。ldとlibcのオフセットは固定なので、同じ環境を揃えてオフセットを求めた。問題バイナリに使用されたgccや配布されたlibcから、問題サーバーの環境をubuntu16.04と推測した。同じ環境を用意しオフセットを求めると、libc_base + 0x5f0f48
であり、このアドレスを書き換えると制御を奪えた。rtld_lock_default_lock_recursive()
をsystem()
に書き換え、呼び出し時にrdiが指している領域へshを書き込んでおくことで、system("sh")
が呼び出されシェルを取れる。
baby printf ver2
最初にバイナリがマッピングされているアドレスを得られる。
自明なfsbがあるが、__printf_chk()
を使用しているため任意オフセットからの読み出しや書き込みは出来ない。また_printf_chk()
呼び出し時に、スタックが0xdeadbbefで埋め尽くされるためアドレスのリークも行えない。入力は、0x200byteがdata領域に保存される。オーバーフローしており、stdoutを指すポインタを書き換えることができる。
入力後に、stdoutのvtableが入力前と同じか確認している。元のstdoutのvtableをローカル変数に保存し、書き換わっていた場合元の値に上書きしている。
# ./babyprintf_ver2 Welcome to babyprintf_v2.0 heap is too dangrous for printf :( So I change the buffer location to 0x564d0930d010 Have fun! %p.%p 0xdeadbeef.0xdeadbeef
int main(void) { puts("Welcome to babyprintf_v2.0"); puts("heap is too dangrous for printf :("); __printf_chk(1LL, "So I change the buffer location to %p\n", bin_base + 0x202010); while (1) { void* vtable = stdout.vtable; // 本当は1byteずつ読み込んでいるが面倒なので省略 read(0, bin_base + 0x202010, 0x200); if (vtable != stdout.vtable) stdout.vtable = vtable; // スタックを0xdeadbeefで埋める処理がある __printf_chk(1, bin_base + 0x202010); }; }
exploit
バイナリがマッピングされているアドレスが分かるので、偽のstdoutを用意し、stdoutが指す先を偽の方へ向ける。vtableは改竄されていた場合元に戻されるので適当な値にしておいてよい。
フラグやバッファのアドレスを任意に制御できるので、任意箇所の読み書きが可能である。FILE構造体を利用した諸々のテクニックについては、217のangelboyのスライドで詳細に解説されている(Play with FILE Structure - Yet Another Binary Exploit Technique)。シェルを取るまでの流れは、libcのアドレスをリークして__free_hook
へone gadget RCEを書き込んでおき、最後に__printf_chk()
内でmallocが呼ば出せばシェルが取れる。
HCTF 2018 baby printf ver2 · GitHub
heapstorm zero
競技中に解けなかった。 note管理系のアプリで、動作は以下の通りである。
Allocate
: 指定したサイズでcalloc()が実行され、確保された領域へ指定サイズ分の書き込みが行える。指定できるサイズは、0x1 <= size <= 0x38で、fastbinに該当する大きさだけである(とてもしんどい)。off-by-oneがあり、1byteだけnull byteで上書きできる。View
: 指定したインデックスの中身をputsで表示する。 存在しないチャンクを見るなどのバグはない。Delete
: 指定したインデックスが存在する場合freeを行う。 ポインタはクリアされるためUAFはない。
# ./heapstorm_zero ooooo ooooo .oooooo. ooooooooooooo oooooooooooo `888' `888' d8P' `Y8b 8' 888 `8 `888' `8 888 888 888 888 888 888ooooo888 888 888 888oooo8 888 888 888 888 888 " 888 888 `88b ooo 888 888 o888o o888o `Y8bood8P' o888o o888o ===== (fake) HEAP STORM ZERO ===== 1. Allocate 2. View 3. Delete 4. Exit Choice:
exploit
上記の動作だけからでは、シェルを取るところまでは到達できない。
実は動作の選択時の入力にはscanf("%d")
が呼ばれており、入力を工夫することでscanf()
内でmalloc/freeを呼び出すことができる。scanf()
では、1度の入力のサイズが大きいと一時的なバッファを確保するために、malloc()を呼び出す。バッファは使用後にはfree()される。scanf("%d")
に対して、'1'*0x400を入力すると内部では0x800サイズのチャンクが確保されて解放される。
これを利用すると、__malloc_consolidate
を呼び出すことができ、fastbinチャンクをまとめてsmallbin/largebinへ繋ぐことができる。
あとは、チャンクのPREV_INUSEをクリアしてチャンクをオーバーラップさせれば、いつも通りのheap系の問題になる。シェルをとるまでの流れは、unsotedbin attackをstdinの_IO_buf_end
に対して行い、オーバーフローが起きるようにする。最後に__malloc_hook
を書き換えてシェルを起動した。
HCTF 2018 heapstorm zero · GitHub
参考リンク
HackIT CTF 2018 writeup
はじめに
TokyoWesternsで参加した。 結果は8731点の2位だった。 トップ5にはHackITのカンファレンスチケットが貰えて、さらにトップ3にはスポンサーから何かしらのソフトウェアのライセンスが貰えるらしい。 自分はArmy, A Heap Interface, Bank Reimplemented, KAMIKAZE, HashManを解いた。つまりpwnを全完した。 良い問題だったので楽しかった。
Army
実行すると3つの選択肢を選ぶことができる。Beginner's Luck
としてlibcのアドレスを教えてくれるので、アドレスリークは必要ない。
Join the Army
でmalloc()
に失敗するようなサイズを入れると、heap上に持っているサイズと、bssで保持しているサイズが合わなくなる。
なのでalloc()
とread()
で呼ばれるサイズが異なり、stack bofを起こせる。
canaryはなくlibcのアドレスは既に得られているので、ropをする。
A Heap Interface
unsortedbin attackをglobal_max_fastに対して行う。通常0x80までがfastbinsに入るが、0x90以上のサイズのチャンクもfastbinsに入るようになる。あとは適当にlibcのアドレスをリークさせて、_dl_open_hook
に適当な値を書き込んでstdoutのvtableを上書きして制御を取った。
HackIT CTF 2018 HashMan · GitHub
Bank Reimplemented
直下のチャンクのサイズを1byteだけ書き換えることができるのでチャンクをオーバーラップさせる。__malloc_hook
は監視されているので、FSOPで_IO_str_jump
を使って制御を取った。
HackIT CTF 2018 Bank Reimplemented · GitHub
KAMIKAZE
MMAPEDビットを立ててcalloc()が0初期化しないようにする。calooc()の初期化が起きないと未初期化バグが発現する。
HackIT CTF 2018 KAMIKAZE · GitHub
HashMan
race conditionとtype confusion。sha1の処理で用いる構造体をxorの処理で用いる構造体に見せかけるために、race conditionを使う。 別スレッドでは同一のkeyがあるか定期的に確認しており、同一のkeyが登録されているとkeyを消す処理が実行される。これはkeyを0にすることと同じであり、xorのkeyの制限範囲と合致する。 同じkeyのsha1を2つ作り、edit/print時のkey入力時に別スレッドの処理を待つことでrace conditonが発動する。
MeePwn CTF 2018 Quals babysandbox, house_of_card, secure_message, 0xBAD MINTON writeup
はじめに
TokyoWesternsで参加して5920ptsで6位だった。finalsに行ける順位なのでベトナムに行くかもしれない。ワールドカップの試合の結果を予想して当てると点数が貰えたりする良いCTFだった。 共同で解いた分を含めて4問解いた。house_of_cardとsecure_messageはfirst solveだったので良かった。
babysandbox
pythonで書かれたサーバ部分では、投げられたペイロードに対してシステムコールのチェックを行っている。 open, read, write, execveといった、よくあるシステムコールが禁止されている。 チェックを通るとバイナリが実行される。 実行結果は、実行が成功したかどうかしか知ることができない。つまりバイナリ側の出力は分からない。
バイナリはシェルコードを受け取り実行するだけである。
以上から、システムコールが制限された状況でシェルコードを書くだけである。 方針は単純で、open, read, writeは禁止されているので、openat, readv, writevで代用する。 出力の受け取りは、socketを作ってサーバーで待ち受けるようにする。
MeePwn CTF 2018 Quals babysandbox · GitHub
% nc -lv 31337 Listening on [0.0.0.0] (family 0, port 31337) Connection from [178.128.100.75] port 31337 [tcp/*] accepted (family 2, sport 42588) MeePwnCTF{Unicorn_Engine_Is_So_Good_But_Not_Perfect}
(Unicorn関係あったか?)
house_of_card
first solveだった。ログを見ると問題が公開されて1時間程度で解けていた。
実行すると以下のようにnoteを作ったり編集したり削除ができる。編集と削除を行うときにnoteの中身を見ることができる。noteは以下の構造体で管理される。バグは、edit時に作った時のサイズよりも0x40多く指定しても問題ないので、heap bofが起きることである。
# ./house_of_card ============ 📚 Notes 📚 ============ 1. New Note 2. Edit Note 3. Del Note 4. Quit ⛩
struct note { char name[0x40]; int length; char description[length]; }; struct node { struct note* note; struct node* own; struct node* next; };
まずは、libcのアドレスをリークさせるためにチャンクのサイズを書き換えてオーバーラップを起こした。リーク後は、単方向リストを形成する方の構造体のポインタを書き換えて、__free_hookを書き換えた。
MeePwnCTF 2018 Quals house_of_card · GitHub
# python exploit.py r [*] '/root/ctf/MeePwnCTF/House_of_Card/house_of_card' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled [+] Opening connection to 178.128.87.12 on port 31336: Done [*] Pwning [*] libc_base: 0x7f01a3333000 [*] Switching to interactive mode >$ cat flag MeePwnCTF{no_moreeeeee_h43p}
secure_message
これもfirst solveだった。
適当にやっていたら解けたのでよくわかっていない。
urandomのfdを保持している箇所は、4番目のユーザーのパスワードを最大で入れるとnull終端時に0で上書きされる。つまりランダムっぽくしたい部分を任意にできる。
messageはmmap()
で取得された領域が使用され、第一引数はurandomから呼んでランダムになるはずである。上記のバグで任意の第一引数でmmap()
を実行できる。MAP_FIXED
が付いているので、おおよそ第一引数通りのアドレスが取得できる。
edit時にサイズを変えるとsegvで落ちるパターンがあり、落ちないように適当にやっていたら解けた。
MeePwn CTF 2018 Quals secure_message · GitHub
# python exploit.py r [*] '/root/ctf/MeePwnCTF/secure_message' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled [+] Opening connection to 178.128.87.12 on port 31337: Done [*] Pwning [*] bin_base: 0x557bac867000 [*] libc_base: 0x7fb8da3da000 [*] heap_base: 0x557bacb57000 [*] Switching to interactive mode $ cat /home/sm/flag MEEPWNCTF{MAP_FIXED_1s_v3ry_dangerous}
0xBAD MINTON
misc(web+pwn)。 pwnパートをやった。フロントエンドではアカウントのregister/loginができ、アカウントごとに固有のトークンが付与される。 3回までenrollでき、4回目以降は普通ではできなかった。
リスティングが有効でファイル一覧が取得できた。ファイル一覧では各種phpファイルとTODO.txt
、backend_server
が見れる。
TODOには、178.128.84.72:9997
で待ち受けている旨が書かれている。
実際にアクセスしてみると、backend_server
が動いていた。
backend_server
は、トークンをもとにDBからアカウントの情報を取得し諸々の処理を行う。
enrollしている場合、enrollした回数だけstack上にペイロードを投げることができる。ペイロードはstack上に保存される。
backend_server
のバグは、4回以上enrollしている場合stack bofとなる点である。しかし、SSPが有効なのでROPはできない。
フラグは事前にbss上に直置きされる。No PIEなのでアドレスは固定である。
方針は単純で、webパートで4回以上enrollしてstack bofを起こし、argv[0] leakを行う。チームメンバが4回enrollしたアカウントを作ってくれたので、あとはやるだけだった。pwnパートは以下の通りである。stderrがネットワーク越しに漏れてこないと面倒だったが特に気にしなくて良かった。
MeePwn CTF 2018 Quals 0xBAD MINTON · GitHub
# python exploit.py r [*] '/root/ctf/MeePwnCTF2018/0xBADMINTON/backend_server' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) [+] Opening connection to 178.128.84.72 on port 9997: Done [*] Pwning [*] Switching to interactive mode Okie let's recheck your courses again: #0 aaaaaaaaaaaaaaaa #1 aaaaaaaaaaaaaaaa #2 aaaaaaaaaaaaaaaa #3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxp@` Okay! Let's warm-up ----------------BADMINTON TRAINING---------------- 1. Push-up 2. Running 3. Give up > *** stack smashing detected ***: MeePwnCTF{web+pwn+oldschool+stuff+stack+terminated} terminated [*] Got EOF while reading in interactive
0CTF/TCTF Finals Baby Heap 18.04, freenote2018 writeup
Baby Heap 18.04
機能
起動すると以下のように機能を選択できる。
===== Baby Heap 18.04 ===== 1. Allocate 2. Update 3. Delete 4. View 5. Exit Command:
各機能は、
1. Allocate
0x1 ~ 0x58までのサイズでmallocを実行できる。取得された領域はmemsetによって取得したサイズ分0埋めされる。ポインタは、bss上にある配列でサイズ、管理フラグと共に保管される。2. Update
指定したindexの領域へ、サイズを指定して書き込みを行える。サイズはAllocateで指定したサイズ+1
まで指定できる。off-by-one overflow3. Delete
指定したindexをfreeする。管理フラグを0にする。4. View
指定したindexの領域を、Allocateした時のサイズ分だけwriteで出力する。
方針
Update
でoff-by-one overflowがあるので、直下のチャンクのサイズを任意の値にすることができる。
まずは、チャンクのoverlapを起こし、address leakを行う。heapのアドレスは簡単に取ることできるが、この問題の環境はubuntu 18.04でありglibc 2.27である。つまりtcacheが有効になっている。tcacheが有効な環境では、サイズが0x420以上のチャンクをfreeすれば、そのチャンクはunsorted binに入り、fd/bkにmain_arenaを指すポインタが置かれる。あとはこのアドレスを出力させればいい、
制御の奪取はとても簡単で、tcacheには、繋がっているチャンクのサイズの確認が一切なく、fake chunkのサイズに該当する部分がどのような値であろうと問題なく動作する。__free_hook
を指すようにtcacheのfdを書き換えて、mallocするだけで、__free_hook
を指す領域を取得できる。
exploit
0CTF/TCTF 2018 Finals Baby Heap 18.04 pwn · GitHub
freenote2018
機能
選択肢は以下の5つ。
1. init a note 2. edit a note 3. free a note 4. show a note 5. exit
各機能は以下の通りである。
init a note
指定したサイズでmallocを実行できる。サイズの制限は0x1 ~ 0x100となっている。確保した領域のポインタはbss上にある配列でポインタと一緒に管理される。edit a note
index指定で1で取得した領域へ書き込みができる。書き込みできるサイズは、initした時のサイズと同じであり、overflowはない。free a note
index指定で指定した領域をfreeできる。free後にポインタをNULL埋めしないため、double freeできる。shoe a note
選択肢として存在するだけで、何もしない(表示もしない)
freeしたポインタがそのまま残っており、editとfreeで何度でも指定することができる。ただし、address leakに使えそうな機能が存在しない。
方針
最近読んで覚えていたので、以下の手法を使った。 House_of_Roman.md · GitHub
手法の概要は、unsortedbinに繋がるチャンクをfastbinsの方へforgeし、さらにチャンクのfdのmain_arena辺りを指すポインタをpartialで書き換えることで、__malloc_hook
付近を指すように書き換えてfastbin attackを成立させる。これで__malloc_hook
付近を覆うようにmallocで取得できる。
さらに、__malloc_hook
へunsortedbin attackによってlibcのアドレスを書き込み、partialで下位3byteを書き換えることで、one gadget RCEのアドレスを作る、といったものである。当然だが、brute forceが必要であり提案者は12bit分だと言っている(体感だともっと悪い気がする)。
今回のバイナリでは、
- initでmallocはするが書き込みeditと別になっている
- UAFによるfd辺りの書き換えがeditで容易にできる
- double freeによってfastbin attackが用意である といった感じで都合が良い。
exploit
12bit brute forceが必要となる。
SECCON BeginnersCTF 2018 writeup
はじめに
ShenzhenWesternsで参加して全完した。普段はTokyoだが、0CTF Finalsで深圳に行っているメンバーがいたのでShenzhenになった(たぶん)
condition
main関数での[rbp-0x4]が0xdeadbeefであれば、flagが出力される。
# python -c 'print "\xef\xbe\xad\xde"*0x10' | nc pwn1.chall.beginners.seccon.jp 16268 Please tell me your name...OK! You have permission to get flag!! ctf4b{T4mp3r_4n07h3r_v4r14bl3_w17h_m3m0ry_c0rrup710n}
BBS
入力でgets()
を使っているので自明なstack bof。
バイナリ内でsystem
を使っているので、適当にROPするだけ。
'/bin/sh'が必要なので、先にgets()
でbssあたりに書き込み、system
を呼んだ。
SECCON BeginnersCTF 2018 BBS · GitHub
Seczon
comment機能にFSBがある。 まずは、libcとstackのアドレスをリークした。 制御を取るためにstackに積まれているmain関数の戻りアドレスを書き換えた。 commentとして入力した文字列は、stackにそのまま乗っているのでやるだけ。
SECCON BeginnersCTF 2018 Seczon · GitHub
Veni, vidi, vici
1行目は、ROT13。2行目は、https://quipqiup.com/ に投げた。3行目は読むだけ。
plain mail
zipファイルがbase64で見えるので、取り出す。zipのパスワードはメール中に書いてある。
てけいさん
やるだけ
RCTF 2018 writeup
babyheap
bug: 1. Alloc
にoff-by-one single byte null overflowがある。
下のチャンクのsizeが上書きでき、サイズの縮小とPREV_INUSEビットのクリアができる。
適当にやればチャンクのoverlapができるので、libcのアドレスをリークをしてfastbin attackをした。
__malloc_hookにone gadget rceを書き込んだ。
https://gist.github.com/hama7230/86643fbf8ad4b6f31521e77a3339b4cf
RNote4
editで入力するサイズの整合性を確認していないため、allocで作成した時のサイズよりも大きい値を入れればheap bofとなる。 editで書き込めるポインタがあるので、そこを書き換えることで任意アドレスの書き換えが出来るようになる。 リークができそうな部分がないが、No PIEでNo RELROとなっているので何とでも出来る。 No RELROなのでstrtabを偽装して、名前解決時にfreeをsystemへとすり替えた。
https://gist.github.com/hama7230/2483803ead0853b7b218b5402986b9fe
stringer
競技中には解けなかった。 UAFがありdouble freeができる。しかしcallocによって割り当てられた領域は0初期化されてしまうので、アドレスのリークができない。 callocでは、IS_MMAPPEDビットがたつチャンクの0初期化が行われないようになっているので、editで当該ビットを立てるようにする。
あとはfastbin attackから__malloc_hookへone gadget rceを書き込んだ。
https://gist.github.com/hama7230/9c74cf53fa2d553cb3baea56a881fbbc
cpushop
hash length extention attackをする。
https://gist.github.com/hama7230/c6b89247bb7a941cdc4e976e16232779
git
blobをzlibで解凍してみたら、flagがあった。