NCSTISC 2018 babydriver writeup
はじめに
一人writeup advent calendarの3日目です。1日1問分のwriteupを目標に頑張っていきます。 3日目の問題は、NCSTISC CTF 2018で出題された「babydriver」。kUAF (kernel Use After Free) を起点としたkernel exploit問題で初めての人にオススメです。
カーネルの情報 (セキュリティ機構など)
配布されるファイルは以下の通り。boot.shが起動スクリプトで、起動するとqemuが立ち上がる。 boot.shの中身を見ると、smepが有効になっている。また、起動してカーネルのバージョンを確認すると、4.4系であった。
% ls boot.sh* bzImage* rootfs.cpio* % cat boot.sh #! /bin/sh qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographic -smp cores=1,threads=1 -cpu kvm64,+smep [In qemu] / $ uname -a Linux (none) 4.4.72 #1 SMP Thu Jun 15 19:52:50 PDT 2017 x86_64 GNU/Linux / $ cat /proc/cpuinfo | grep flags flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx lm constant_tsc nopl pni cx16 x2apic hypervisor smep
解析
rootfs.cpioを展開すると、/lib/modules/4.4.72/babydriver.ko
に攻略対象となるカーネルモジュールがある。
babydriver_init()がモジュールの初期化関数で、/dev/babydev
を作成し登録している。
デバイスに対する動作は、以下の通りとなっている。
babyopen : ユーザから
/dev/babydev
をopen()すると呼ばれる。カネールモジュールのbss領域にあるbabydev_structを初期化する (構造は以下の通り)。device_bufにkmem_cache_alloc_trace()で得られたポインタを入れ、device_buf_lenに0x40を入れる。kmem_cache_alloc_trace()で取得されるチャンクのサイズは0x40となっている。babyrelease :
/dev/babydev
をclose()すると呼ばれる。kfree(babydev_struct.device_buf)を呼ぶ。babywrite :
/dev/babydev
にwrite()すると呼ばれる。device_buf_lenがwrite()で書き込むサイズよりも大きい場合、copy_from_user(device_buf, user_buf, len)が呼ばれる。babyread :
/dev/babydev
にread()すると呼ばれる。device_buf_lenがread()で読み込むサイズよりも大きい場合、copy_to_user(user_buf, device_buf, len)が呼ばれる。babyioctl :
/dev/babydev
にioctl()すると呼ばれる。cmdが0x10001で呼び出すと処理が実行される。ioctl(fd, 0x10001, size)といった感じで呼ぶと、元々のbabydev_struct.device_bufをkfree()して、新しくkmalloc(size)で取得されたポインタを入れる。device_buf_lenにはsizeが入る。
struct babydev_struct { char* device_buf; long device_buf_len; }
exploit
babydev_strudtはモジュールのbssで管理されており、/dev/babydev
を複数回開いてそれぞれのfd経由で呼び出しても同じ箇所を使用する。
そこで/dev/babydev
を2度open()し、片方をclose()する。
すると、close()する際にbabyrelease()が呼ばれて、babydev_struct.device_bufはkfree()されるが、もう片方のfd経由ではbabydev_struct.device_bufを参照している状態にあり、kUAFとなる。
kUAFを起こせるで、適当なオブジェクトを被せて制御を奪っていく。
今回は/dev/ptmx
が存在するので、これを使う。
/dev/ptmx
を開いた時、tty_struct構造体用の領域がkmalloc()で取得される。
このtty_struct構造体にはopsというメンバを持っており、これはtty_operationsで定義される関数テーブルとなっている。opsを書き換えることで、制御が奪える。
tty_struct構造体は、kmalloc-256に該当するオブジェクトなので、ioctl()で事前にサイズを256byteに変更しておく。
// https://elixir.bootlin.com/linux/v4.4.72/source/include/linux/tty.h#L259 struct tty_struct { int magic; struct kref kref; struct device *dev; struct tty_driver *driver; const struct tty_operations *ops; // これを書き換えたい int index; あとは省略 }
今回はsmapが無効なので、偽のtty_operationsをユーザ空間に用意できる。
kUAFで重複している/dev/ptmx
が指すopsメンバを、ユーザ空間に用意した偽のものを指すように書き換える。
root権限を取るまでの流れは、基本に忠実にropで実行してく。 ユーザ空間に予め偽のstackとrop chainを用意して、そこへpivotさせる。 ropでは、commit_creds(prepare_kernel_cred(0))を呼び出し、swapgs+iretqでユーザ空間へ復帰し、シェルを起動する。 pivotする先は、0x01740100で固定なのであらかじめmmapでその近辺を確保しておく。
kUAFで重複している/dev/ptmx
のopsを書き換えたままなので、そのままだとroot権限のコマンドを1回実行したらカーネルパニックとなってしまう (実際になった)。
なので、不正に上書きしたopsを正常なものに書き戻しておく必要がある。
exploit内では、一度正常な状態のttyを保存してから不正な状態に書き換えている。
そして上手くroot権限が取れた後に、正常な状態に書き戻している。
参照URL
SEC-T CTF gh0st writeup
はじめに
一人writeup advent calendarの2日目です。 1日1問分のwriteupを目標に頑張っていきます。 2日目の問題は、SEC-T CTF 2018で出題された「gh0st」。 out-of-boundsを起点としたkernel exploit問題で初めての人にオススメです。
カーネルの情報 (セキュリティ機構など)
配布ファイルを以下の通り。 READMEを読むと、kaslr, smep, smapがオフになっているらしい。 initは圧縮されたcpioファイルなので展開する。 展開するとgh0st.koが存在する。 このモジュールが攻略対象。
% ls README gdb.cmd* init run.sh* tiny.kernel* vmlinux.bin* % cat README First complete your exploit locally using the given qemu environment, it comes with symbols etc. ./run.sh -debug gdb target remote :1234 file ./vmlinux.bin Then pwn the remote system by connecting to the service and f.ex. dump your exploit bin via gzip/b64. NX is ON, KASLR/SMEP/SMAP is OFF!
解析
gh0st.koでは、簡易バイトコードを実行できる。 バイトコードそのものはカーネルモジュールのbss領域にbytecodeとして保持される。 また、バイトコード実行の管理領域として、stack_ptrなどがbss領域に存在する。
/dev/gh0st に対する主な挙動は以下の通りであった。
write: bytecodeへユーザ空間から0x1000byteのコピー
ioctl(0x1337B4B3): writeを呼び出してbytecodeに0x1000byte書き込み、bytecodeの内容がフォーマットに合っているか確認する。フォーマット通りであった場合、指定したサイズでkmalloc()を呼び出し、結果をstack_ptrとorig_stack_ptrへ書き込む。bytecodeのフォーマットは以下の通り。
- header: 4byte。"BFBF"である必要がある。
- size: 4byte。kmalloc()で呼び出すサイズの指定。スタックの大きさ。
- out: 0x100byte。命令用のバッファ。
- in: 0x100byte。命令用のバッファ。
- inst: 0x418byte。命令のバイトコード。使用できる命令は下述の6つ。
ioctl(0xAC1DC0DE): 先に0x1337B4B3を呼び出す必要がある。bytecodeの命令に従って処理を実行する。命令は0x418回実行されると終了する。実行できる命令は以下の6つ。out_counter、in_counterは0で初期化されてから実行される。
- '+', '>': stack_ptrをインクリメント
- '-', '<': stack_ptrをデクリメント
- ',': stack_ptrが指す先へout[out_counter]を書き込む。その後out_counterをインクリメントする。out_counterが0x100まで行ったら処理終了
- '.': stack_ptrが指す先からin[in_counter]を読み込む。その後in_counterをインクリメントする。in_counterが0x100まで行ったら処理終了
exploit
stack_ptrの操作に制限がないため、out-of-boundsで読み書きができる。 読みはできるが、ユーザ空間へ渡す方法が (簡単には) ないので、書き込みだけを使っていく。 stack_ptrが指す先はkmalloc()で取得された領域である。 そのため、その前後には同様にkmalloc()を用いて確保されたオブジェクトが存在している。
今回のカーネルはSLOBアロケータを使用しているため、近いサイズのオブジェクトは隣接して配置される可能性が高い。 具体的には、256byte未満、256byte以上1024byte未満、1024byte以上の3種類に分けられ、それぞれは別の領域でアロケートされる。 事前にターゲットとなるオブジェクトを配置しておき、ioctl(0x1337B4B3)でkmalloc()を呼び出す。 もしターゲットのオブジェクトとkmalloc()で取得された領域が連続している場合、stack_ptrをズラすことでターゲットのオブジェクトへ任意の書き込みが行える。
ターゲットとするオブジェクトは、ファイルを展開した際に使用されるfile構造体にした(Linux source code: include/linux/fs.h (v4.17) - Bootlin)。 理由は単純でメンバのf_opは関数テーブルを指しているので、これを上書きすれば制御を奪えるからである。 具体的な流れは、stack_ptrを適用な回数デクリメントさせて、真上に存在する(であろう)file構造体のf_opを指す。 書き込みを用いてf_opをこちらが制御できるアドレスで上書きする。
実際に書き込むアドレスは、gh0stのbytecodeの中にした。 自由に制御できるうえに、今回はKASLRが無効なのでbytecodeのアドレスは固定となっている。 あらかじめbytecode中に偽のfile_operationsを用意しておく。 ユーザ空間からread()を実行すると、f_op->read()が実行されるので、readの位置に呼び出したいアドレスを書いておく。 file構造体を狙うため、適当なファイルを複数回開いてからgh0stでkmalloc()を呼べば隣接する確率が高くなる(0x100回もすれば100%上手くいった)。
root権限を取るまでの流れだが、基本に忠実にcommit_creds(prepare_kernel_commit(0));を呼び出す。SMAPが無効なのでユーザ空間内にstackをpivotさせてROPを実行する。 (あとから考えるとSMEPも無効なのだから直接ユーザ空間内でもよかった)
参考URL
Blaze CTF 2018 blazeme writeup
はじめに
一人writeup advent calendarの1日目です。 1日1問分のwriteupを目標に頑張っていきます。 1日目の問題は、Blaze CTF 2018で出題された「blazeme」。 stack overflowを起点としたkernel exploit問題で初めての人にオススメです。
カーネルの情報 (セキュリティ機構など)
配布されたファイルは以下の通りで、blazeme.cが攻略対象のデバイスのソースコードである。 run.shが起動スクリプトになっている。 カーネルのバージョンは4.15で、smepやsmapは無効になっている。
% ls blazeme.c bzImage rootfs.ext2 run.sh % cat run.sh #!/bin/sh /usr/bin/qemu-system-x86_64 -kernel bzImage -smp 1 -hda rootfs.ext2 -boot c -m 64M -append "root=/dev/sda nokaslr rw ip=10.0.2.15:10.0.2.2:10.0.2.2 console=tty1 console=ttyAMA0" -net nic,model=ne2k_pci -net user -nographic
WARNING: Image format was not specified for 'rootfs.ext2' and probing guessed raw. Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted. Specify the 'raw' format explicitly to remove the restrictions. warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5] Welcome to Buildroot buildroot login: blazeme Password: login: can't change directory to '/home/blazeme' $ uname -a Linux buildroot 4.15.0 #1 SMP Wed Apr 18 01:12:17 PDT 2018 x86_64 GNU/Linux $ cat /proc/cpuinfo | grep flags flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx lm nopl cpuid pni cx16 hypervisor lahf_lm svm 3dnowprefetch retpoline rsb_ctxsw vmmcall
解析
blazeme_init()で、blazemeデバイスを作成し登録している。 blazeme_fopsはblazemeに対する操作を定義しており、read, write, open, releaseが登録されている。 これらはカーネル空間(カーネルモジュール)の関数なので、ユーザ空間から呼び出すには、
- blazeme_open() => open('/dev/blazeme', flag, mode);
- blazeme_close() => cloes(blazeme_fd);
- blazeme_read() => read(blazeme_fd, buf, size);
- blazeme_write() => write(blazeme_fd, buf, size);
といった感じになる。(blazeme_fdはopen()で得られたfd、他の引数については適切なものを設定)
blazeme_write()では、kmalloc(64)で取得した領域にユーザ領域からcopy_from_user()で書き込みを行っている。 その後、strncat()でカーネルのスタックへと書き込んでいる。
脆弱性はblazeme_write()のstrncat()でstack overflowとなることである。 今回のカーネルはSLUBアロケータを使用している。 SLUBアロケータでのチャンクはglibc mallocのチャンクでのサイズに相当するメタデータは存在しない。 そのため取得した領域を一杯まで埋めてstrlen()をすると、次のチャンクの内容も巻き込んで長さが求められる。 blazeme_write()でkmalloc()で取得されたポインタはエラーがなければkfree()されないので、複数回blazeme_write()を呼び出すことでチャンクを隣接して配置できる可能性がある。 上手くチャンクが隣接し、blazeme_write()のローカル変数として用意されたスタックのバッファのサイズを超えればstack overflowが発生する。
#include <linux/module.h> #include <linux/version.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/slab.h> #define DEVICE_NAME "blazeme" #define ERR_BLAZEME_OK (1) #define ERR_BLAZEME_MALLOC_FAIL (2) #define KBUF_LEN (64) dev_t dev = 0; static struct cdev cdev; static struct class *blazeme_class; ssize_t blazeme_read(struct file *file, char __user *buf, size_t count, loff_t *ppos); ssize_t blazeme_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos); int blazeme_open(struct inode *inode, struct file *file); int blazeme_close(struct inode *inode, struct file *file); char *kbuf; struct file_operations blazeme_fops = { .owner = THIS_MODULE, .read = blazeme_read, .write = blazeme_write, .open = blazeme_open, .release = blazeme_close, }; ssize_t blazeme_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int len = count; ssize_t ret = ERR_BLAZEME_OK; if (len > KBUF_LEN || kbuf == NULL) { ret = ERR_BLAZEME_OK; goto out; } if (copy_to_user(buf, kbuf, len)) { goto out; } return (ssize_t)len; out: return ret; } ssize_t blazeme_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char str[512] = "Hello "; ssize_t ret = ERR_BLAZEME_OK; if (buf == NULL) { printk(KERN_INFO "blazeme_write get a null ptr: buffer\n"); ret = ERR_BLAZEME_OK; goto out; } if (count > KBUF_LEN) { printk(KERN_INFO "blazeme_wrtie invaild paramter count (%zu)\n", count); ret = ERR_BLAZEME_OK; goto out; } kbuf = NULL; kbuf = kmalloc(KBUF_LEN, GFP_KERNEL); if (kbuf == NULL) { printk(KERN_INFO "blazeme_write malloc fail\n"); ret = ERR_BLAZEME_MALLOC_FAIL; goto out; } if (copy_from_user(kbuf, buf, count)) { kfree(kbuf); kbuf = NULL; goto out; } if (kbuf != NULL) { strncat(str, kbuf, strlen(kbuf)); printk(KERN_INFO "%s", str); } return (ssize_t)count; out: return ret; } int blazeme_open(struct inode *inode, struct file *file) { return 0; } int blazeme_close(struct inode *inode, struct file *file) { return 0; } int blazeme_init(void) { int ret = 0; ret = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME); if (ret) { printk("blazeme_init failed alloc: %d\n", ret); return ret; } memset(&cdev, 0, sizeof(struct cdev)); cdev_init(&cdev, &blazeme_fops); cdev.owner = THIS_MODULE; cdev.ops = &blazeme_fops; ret = cdev_add(&cdev, dev, 1); if (ret) { printk("blazeme_init, cdev_add fail\n"); return ret; } blazeme_class = class_create(THIS_MODULE, DEVICE_NAME); if (IS_ERR(blazeme_class)) { printk("blazeme_init, class create failed!\n"); return ret; } dev = device_create(blazeme_class, NULL, dev, NULL, DEVICE_NAME); if (IS_ERR(&cdev)) { ret = PTR_ERR(&cdev); printk("blazeme_init device create failed\n"); class_destroy(blazeme_class); cdev_del(&cdev); unregister_chrdev_region(&dev, 1); return ret; } return 0; } void blazeme_exit(void) { cdev_del(&cdev); class_destroy(blazeme_class); unregister_chrdev_region(&dev, 1); } module_init(blazeme_init); module_exit(blazeme_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("BLAZECTF 2018 crixer"); MODULE_DESCRIPTION("BLAZECTF CTF 2018 Challenge Kernel Module");
exoloit
今回はcanaryもないので、stack overflowでリターンアドレスを簡単に書き換えることができる。 kaslr、smap、smepが無効となっているので、ret2usrが容易にできる。 ただ今回は練習を兼ねて、stack pivotからのROPを行なった。 切り替え先の偽のスタックをユーザ空間に予め用意してROPガジェットを書き込んでおき、任意のROPに持ち込む。 ROPでは、commit_creds(prepare_kernel_cred(0));を呼んでroot権限を得てから、swapgsとiretqでユーザ空間へ戻っている。 stack pivotのガジェットのアドレスで埋めたバッファで、write()を無限に呼ぶことでstack overflowを起こした。
参考リンク
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