CTF Linux 内核 Pwn:SMEP/SMAP 与用户代码不可执行防护的攻防全解析
发布时间:2026/9/25 14:49:43来源:尧图网络
文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载本篇文章聚焦 CTF Linux 内核 Pwn 中最基础也最关键的防御机制——SMEPSupervisor Mode Execution Protection管理模式执行保护即内核态不可执行用户态代码。文章以仓库 user-code-execution.md 为主体结合仓库中 bypass-smep.md、ret2usr.md 与 basic-knowledge.md 的源码级佐证系统讲解 SMEP 的原理、开启/关闭方式、状态检测、绕过思路以及它与 ret2usr、KPTI 的攻防演进关系。读完本文你将能看懂 CTF 内核题中 boot 脚本的smep参数含义、用grep smep /proc/cpuinfo判断保护状态、理解 CR4 寄存器第 20 位的攻防意义并能复现强网杯 2018 core 一题中通过 ROP 关闭 SMEP 的完整提权流程。攻击起源ret2usr 与内核态执行用户代码在引入 SMEP 之前内核态代码可以直接执行用户态的代码。正如 user-code-execution.md 所述如果攻击者控制了内核中的执行流就可以转而执行处于用户态的代码。由于用户态代码是攻击者完全可控的攻击的构造与调试成本远低于在内核态构造复杂的 ROP 链因此这种手法在当时十分流行。仓库 ret2usr.md 对这种攻击做了精确定义在【未】开启 SMAP/SMEP 保护的情况下用户空间无法访问内核空间的数据但是内核空间可以访问/执行用户空间的数据因此ret2usr这种攻击手法应运而生——通过 kernel ROP 以内核的 ring 0 权限执行用户空间的代码以完成提权。具体来说ret2usr 的攻击流程为攻击者先在内核态找到一个漏洞如栈溢出劫持内核控制流将返回地址直接指向用户空间预先构造好的提权函数典型为调用commit_creds(prepare_kernel_cred(NULL))获得 root用户空间函数执行完毕后通过手工构造的swapgs; iretq汇编代码回到用户态并启动 shell。与常规 kernel ROP在内核空间用大量 gadget 完成提权并利用内核中已有的swapgs; iretq返回用户态相比ret2usr 的显著优势正如 ret2usr.md 总结的我们只需要劫持内核执行流而无需在内核空间构造复杂的 ROP 链条——在用户空间写一段提权代码显然比在难以调试的内核空间堆 gadget 简单得多。正是为了封堵这条捷径CPU 厂商与内核研究者提出当 CPU 处于内核态ring0时禁止执行用户态的代码。在 Linux 内核中这一防御措施的实现与指令集架构强相关——x86 下对应 SMEPARM 下对应 PXN。x86 下的实现SMEP 与 CR4 寄存器的第 20 位SMEPSupervisor Mode Execution Protection是 Intel 在 Ivy Bridge 一代处理器开始引入的硬件特性。其核心语义为当 CPU 处于 ring0管理模式/supervisor mode时尝试取指执行用户空间ring3页面上的代码会触发页错误page fault从而直接终结内核态跳去执行用户态代码这类攻击。在 x86 架构中SMEP 的开关由控制寄存器 CR4 决定。CR4 用于控制 CPU 的各种特性其中**第 20 位bit 20**标记 SMEP 是否开启置 1 表示开启保护置 0 表示关闭保护。仓库 user-code-execution.md 使用了 CR4 寄存器位图直观展示该布局与之配套的还有 SMAPSupervisor Mode Access Protection管理模式访问保护对应 user-data-access.md 的主题——CR4 第 21 位用于阻止内核态直接访问用户态的数据。两者通常同时开启正如 basic-knowledge.md 所概括的隔离矩阵默认用户态不可直接访问内核态的数据、执行内核态的代码SMEP内核态不可执行用户态的代码CR4 bit 20SMAP内核态不可访问用户态的数据CR4 bit 21KPTI用户态不可看到内核态的页表且内核态不可执行用户态的代码模拟。bypass-smep.md 中给出了一个可验证的 CR4 位解析示例当$CR4 0x1407f0 000 1 0100 0000 0111 1111 0000时SMEP 保护开启而 CR4 是可通过mov指令修改的因此只要执行mov cr4, 0x1407e0即 0x1407e0 对应 bit 20 为 0即可关闭 SMEP。用 GDB 调试内核时可用info registers cr4查看当前值及其启用的标志位例如(gdb) info registers cr4 cr4 0x3006f0 [ SMAP SMEP OSXMMEXCPT OSFXSR PGE MCE PAE PSE ]开启与关闭QEMU 参数与 GRUB 配置原文档给出了两种应用场景下的开关方法这里结合仓库其他文档中的实际用法做完整展开。默认状态开启SMEP 保护默认开启现代 x86_64 内核编译时默认启用CONFIG_X86_SMAP/CONFIG_X86_SMEP相关支持且硬件支持时默认使能。开启QEMU-append参数使用 QEMU 启动内核进行 CTF 调试时可在内核命令行-append选项中添加smep来开启qemu-system-x86_64 \ -cpu kvm64,smep,smap \ -kernel ./bzImage \ -initrd ./rootfs.cpio \ -append root/dev/ram rw consolettyS0 oopspanic panic1 quiet kaslr需要说明的是仓库 bypass-smep.md 与 ret2usr.md 中展示的典型写法是-cpu参数加smep如-cpu kvm64,smep,smap而 user-code-execution.md 写的是-append选项加smep。两者本质都是通过内核命令行/CPU 特性开关控制保护位实操中以前者-cpu显式声明 CPU 特性更为常见后者等价于在启动参数层面要求内核启用该特性。实际调试时无论用哪种写法都以/proc/cpuinfo与 GDB 中的 CR4 值为准进行确认。关闭GRUB 引导参数nosmep在真实机器上非 QEMU关闭 SMEP需要修改 GRUB 配置。在/etc/default/grub中向如下两行的...内追加nosmepGRUB_CMDLINE_LINUX_DEFAULTquiet GRUB_CMDLINE_LINUXinitrd/install/initrd.gz然后运行update-grub并重启系统即可关闭 SMEPsudo update-grub sudo reboot同理若需关闭 SMAP则追加nosmap参见 user-data-access.md。关闭QEMU-append/-cpu参数使用 QEMU 启动的内核可以在-append选项中添加nosmep来关闭 SMEP更常见的做法是在-cpu参数中使用负号前缀显式关闭如 ret2usr.md 中的写法#!/bin/sh qemu-system-x86_64 \ -enable-kvm \ -cpu host,-smep,-smap \ # ...状态查看/proc/cpuinfo 与 GDB通过 /proc/cpuinfo 检测在目标内核内执行如下命令若输出中发现smep字符串则说明 SMEP 已开启否则未开启grep smep /proc/cpuinfo其输出形如flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse ... smep smap ...在 QEMU 场景下也可以直接在宿主机上检查启动脚本确认保护是否开启例如 bypass-smep.md 中给出的排查方式$ cat run.sh | grep -i smep -cpu kvm64,smep,smap \通过 GDB 查看 CR4在调试内核时可用info registers cr4查看 CR4 当前值与启用的标志位直接确认 SMEP/SMAP 是否生效参见上文示例。Attack SMEP内核态修改 CR4 绕过虽然 SMEP 能拦住直接跳去执行用户代码但它并非不可绕过。原文档给出的核心思路是把 CR4 寄存器的第 20 位置 0从而重新允许内核态执行用户态代码。结合仓库 bypass-smep.md 的实战记录绕过分为两条技术路径。路径一ROP gadget 修改 CR4既然 CR4 可由mov指令写入那么在劫持控制流后攻击者可以在内核空间中搜索并串联可修改 CR4 的 gadget用 ROP 链完成关闭。由于 SMAP 与 SMEP 通常同时开启一般会直接给 CR4 赋值为0x6f0——该值同时将 bit 20SMEP与 bit 21SMAP清零从而一次性关闭两项保护。仓库中 user-code-execution.md 指出内核中修改 cr4 的代码最终会调用到native_write_cr4从另一个维度看内核中也存在固定的修改 cr4 代码片段例如refresh_pce、set_tsc_mode等函数中都有可利用的mov cr4, ...指令序列。这意味着若采用调用函数思路可尝试在内核中定位native_write_cr4并为之构造参数即 0x6f0若采用纯 gadget思路可搜索mov cr4, rax; ...; ret这类指令片段配合pop rdi; ret、and rax, rdi; ret等 gadget先读出当前 CR4 再用掩码清零目标位后写回。bypass-smep.md 给出的 2018 强网杯 core 一题解法正是后者先用mov rax, cr4; ...读出 CR4再用and rax, rdi与掩码0xffffffffffcfffff将 bit 20/21 清掉最后用mov cr4, rax; ...写回随后即可 ret2usr。其关键 ROP 片段如下注意地址已随 KASLR 加上偏移rop_chain[i] MOV_RAX_CR4_ADD_RSP_8_POP_RBP_RET kernel_offset; rop_chain[i] *(size_t*) arttnba3; /* 占位数据 */ rop_chain[i] *(size_t*) arttnba3; rop_chain[i] POP_RDI_RET kernel_offset; rop_chain[i] 0xffffffffffcfffff; /* 清除 SMEP(bit20)/SMAP(bit21) 的掩码 */ rop_chain[i] AND_RAX_RDI_RET kernel_offset; rop_chain[i] MOV_CR4_RAX_PUSH_RCX_POPFQ_RET kernel_offset; rop_chain[i] (size_t) ret2usr_attack; /* 关闭保护后直接跳到用户态提权函数 */当 SMEP 生效而攻击者未做绕过时直接 ret2usr 会触发内核报错并 panic。仓库 bypass-smep.md 中保留了实际运行失败时的截图证据报错信息明确提示无法执行用户态代码疑似 SMEP上述完整 exp含 canary 泄露、符号地址解析、ROP 链构造可在 bypass-smep.md 中查阅此处不再整段重复。路径二ret2dir —— 从地址映射维度绕过除了改 CR4还有另一种不碰寄存器的绕过思路——ret2dir。basic-knowledge.md 对其做了概括利用内核线性映射区对物理地址空间的完整映射找到用户空间对应页框的内核空间地址利用该内核地址完成对用户空间的访问即一个内核空间地址与一个用户空间地址映射到了同一个页框上。也就是说不再跳转去执行用户空间地址上的代码而是跳到同一物理页在内核线性映射区中的别名地址执行。由于该地址属于内核地址空间SMEP 无法识别它其实对应的是用户数据页从而绕过了检查。相关更深入的分析可参考仓库 ret2dir.md。补充访问用户数据与 copy_from/to_user顺带说明SMAP 负责的是访问而非执行用户态数据。仓库 user-data-access.md 指出即使开启 SMAP内核中也存在合法的用户数据访问通道copy_from_user与copy_to_user这两个函数会在拷贝期间临时清空禁止访问用户态内存的标志位这也是驱动与用户程序交换数据的标准途径。在绕过 SMAP 时同样可以借助这两个函数读写用户态内存。攻防演进KPTI 让 ret2usr 与 SMEP-bypass 成为过去式SMEP-bypass 的有效性还取决于另一个保护——KPTIKernel Page-Table Isolation。basic-knowledge.md 对 KPTI 与 ret2usr 的关系给出了明确的结论KPTI 同时还令内核页表中属于用户地址空间的部分不再拥有执行权限这使得 ret2usr 彻底成为过去式。其原理是开启 KPTI 后内核页表中的用户地址空间不再设置可执行位NX因此即使攻击者通过 ROP 成功把 CR4 的 SMEP 位清零内核去执行用户空间代码时仍会因对应页顶级表项没有执行权限而直接 panic。仓库 bypass-smep.md 结尾对此有同样清晰的判断对于开启了 KPTI 的内核而言内核页表的用户地址空间无执行权限因此当内核尝试执行用户空间代码时由于对应页顶级表项没有设置可执行位因此会直接 panic这意味着 ret2usr 已经是过去式了相应地与之伴生的 smep bypass 也便成为了过去式。因此面对现代 CTF 内核题目通常同时开启 KASLR、SMEP、SMAP、KPTI、FGKASLR 等正确的攻击路径是在内核空间中完成提权ROP 调用commit_creds(prepare_kernel_cred(NULL))再利用内核中已有的swapgs; iretq片段回到用户态而不是尝试跳去执行用户空间代码。仓库 kpti.md 与 kpti-bypass.md 详细讨论了该场景下的对抗手法可作延伸阅读。实战速查QEMU 启动与保护状态核对综合原文档与仓库其他实战文档给出一个可直接套用的 CTF 内核题启动与核对流程查看启动脚本确认保护配置。典型开启 SMEP/SMAP 的启动参数qemu-system-x86_64 \ -m 128M \ -cpu qemu64-v1,smep,smap \ -kernel ./bzImage \ -initrd ./rootfs.cpio \ -append root/dev/ram rw consolettyS0 oopspanic panic1 quiet kaslr \ -s \ -netdev user,idt0, -device e1000,netdevt0,idnic0 \ -nographic进入内核后核对状态grep smep /proc/cpuinfo # 有 smep 字符串则开启 grep smap /proc/cpuinfo # 有 smap 字符串则开启 cat /proc/cpuinfo | grep -i smep判断攻击路径若-append中无nopti/ptioff或内核较新默认 KPTI 开启则 ret2usr 不可行需走完整的内核空间 ROP若题目显式关闭了 KPTI 且仅开启 SMEP如部分老题则可先用 ROP 将 CR4 置为0x6f0关闭 SMEP/SMAP再 ret2usr 提权强网杯 2018 core 即此路线。关闭保护以便本地调试修改/etc/default/grub的GRUB_CMDLINE_LINUX_DEFAULT与GRUB_CMDLINE_LINUX追加nosmep nosmap执行update-grub后重启QEMU 场景则用-cpu host,-smep,-smap或-append ... nosmep nosmap。延伸阅读隔离机制总览默认、SMEP、SMAP、KPTI 的对照readme.mdSMAP用户数据不可访问专题user-data-access.mdKPTI 专题kpti.mdret2usr 完整原理与 2018 强网杯 core 原始 expret2usr.mdSMEP-bypass 完整分析含 CR4 gadget 链与完整 expbypass-smep.mdret2dir不修改 CR4 的 SMEP 绕过思路ret2dir.mdLinux 内核 Pwn 基础知识ring 模型、cred 提权、KASLR/SMAP/SMEP/KPTI 综述basic-knowledge.md赞分享文档网络安全教程【免费下载链接】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 内核 pwn 防护系列Linux 内核 SMAPSupervisor Mode Access Protection原理、开关配置与绕过手法详解CTF 内核 pwn 防护系列Linux 内核 SMAPSupervisor Mode Access Protection原理、开关配置与绕过手法详解 在文档网络安全教程Linux 内核防御机制__ro_after_init 只读数据保护与 mmap_min_addr 空指针防护解析CTF-Wiki 内核 Pwn 篇Linux 内核防御机制 __ro_after_init 只读数据保护与 mmap_min_addr 空指针防护解析CTF Wiki 内核 Pwn 篇 导文档网络安全教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网