news 2026/9/25 14:49:42

CTF Linux 内核 Pwn:SMEP/SMAP 与用户代码不可执行防护的攻防全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CTF Linux 内核 Pwn:SMEP/SMAP 与用户代码不可执行防护的攻防全解析
  • 文档
  • 网络安全
  • 教程

【免费下载链接】ctf-wiki

Come and join us, we need you!

项目地址:https://gitcode.com/gh_mirrors/ct/ctf-wiki
点击查看免费下载

本篇文章聚焦 CTF Linux 内核 Pwn 中最基础也最关键的防御机制——SMEP(Supervisor 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 的攻击流程为:

  1. 攻击者先在内核态找到一个漏洞(如栈溢出),劫持内核控制流;
  2. 将返回地址直接指向用户空间预先构造好的提权函数(典型为调用commit_creds(prepare_kernel_cred(NULL))获得 root);
  3. 用户空间函数执行完毕后,通过手工构造的swapgs; iretq汇编代码回到用户态并启动 shell。

与"常规 kernel ROP"(在内核空间用大量 gadget 完成提权并利用内核中已有的swapgs; iretq返回用户态)相比,ret2usr 的显著优势正如 ret2usr.md 总结的:我们只需要劫持内核执行流,而无需在内核空间构造复杂的 ROP 链条——在用户空间写一段提权代码,显然比在难以调试的内核空间堆 gadget 简单得多。

正是为了封堵这条"捷径",CPU 厂商与内核研究者提出:当 CPU 处于内核态(ring0)时,禁止执行用户态的代码。在 Linux 内核中,这一防御措施的实现与指令集架构强相关——x86 下对应 SMEP,ARM 下对应 PXN。

x86 下的实现:SMEP 与 CR4 寄存器的第 20 位

SMEP(Supervisor 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 寄存器位图直观展示该布局:

与之配套的还有 SMAP(Supervisor Mode Access Protection,管理模式访问保护),对应 user-data-access.md 的主题——CR4 第 21 位,用于阻止内核态直接访问用户态的数据。两者通常同时开启,正如 basic-knowledge.md 所概括的隔离矩阵:

  • 默认:用户态不可直接访问内核态的数据、执行内核态的代码;
  • SMEP:内核态不可执行用户态的代码(CR4 bit 20);
  • SMAP:内核态不可访问用户态的数据(CR4 bit 21);
  • KPTI:用户态不可看到内核态的页表,且内核态不可执行用户态的代码(模拟)。

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 console=ttyS0 oops=panic panic=1 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中,向如下两行的"..."内追加nosmep:

GRUB_CMDLINE_LINUX_DEFAULT="quiet" GRUB_CMDLINE_LINUX="initrd=/install/initrd.gz"

然后运行update-grub并重启系统,即可关闭 SMEP:

sudo 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 20(SMEP)与 bit 21(SMAP)清零,从而一次性关闭两项保护。

仓库中 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 的有效性还取决于另一个保护——KPTI(Kernel 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 内核题启动与核对流程:

  1. 查看启动脚本,确认保护配置。典型开启 SMEP/SMAP 的启动参数:
qemu-system-x86_64 \ -m 128M \ -cpu qemu64-v1,+smep,+smap \ -kernel ./bzImage \ -initrd ./rootfs.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
  1. 进入内核后核对状态:
grep smep /proc/cpuinfo # 有 smep 字符串则开启 grep smap /proc/cpuinfo # 有 smap 字符串则开启 cat /proc/cpuinfo | grep -i smep
  1. 判断攻击路径:若-append中无nopti/pti=off或内核较新,默认 KPTI 开启,则 ret2usr 不可行,需走完整的内核空间 ROP;若题目显式关闭了 KPTI 且仅开启 SMEP(如部分老题),则可先用 ROP 将 CR4 置为0x6f0关闭 SMEP/SMAP,再 ret2usr 提权(强网杯 2018 core 即此路线)。

  2. 关闭保护以便本地调试:修改/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.md
  • SMAP(用户数据不可访问)专题:user-data-access.md
  • KPTI 专题:kpti.md
  • ret2usr 完整原理与 2018 强网杯 core 原始 exp:ret2usr.md
  • SMEP-bypass 完整分析(含 CR4 gadget 链与完整 exp):bypass-smep.md
  • ret2dir(不修改 CR4 的 SMEP 绕过思路):ret2dir.md
  • Linux 内核 Pwn 基础知识(ring 模型、cred 提权、KASLR/SMAP/SMEP/KPTI 综述):basic-knowledge.md
  • 文档
  • 网络安全
  • 教程

【免费下载链接】ctf-wiki

Come and join us, we need you!

项目地址:https://gitcode.com/gh_mirrors/ct/ctf-wiki
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 14:44:37

ng-zorro-antd Button 加载中状态(nzLoading)完整实战指南

UI组件前端 【免费下载链接】ng-zorro-antd Angular UI Component Library based on Ant Design 项目地址: https://gitcode.com/gh_mirrors/ng/ng-zorro-antd 点击查看 免费下载 导读 在 Angular 应用中使用 ng-zorro-antd 的 Button 组件时,nzLoadin…

作者头像 李华
网站建设 2026/9/25 14:43:22

Atlas 300V 24G推理加速卡部署YOLOv5:从模型转换到性能调优实战

1. 先搞清楚:Atlas 300V 24G到底是不是运算加速卡最近不少做视觉算法落地的朋友在问Atlas,特别是Atlas 300V 24G这张卡。问题集中在两个:这玩意儿到底算不算运算加速卡,以及它能不能拿来部署YOLO。我今天就把这块卡从头到尾聊透—…

作者头像 李华
网站建设 2026/9/25 14:38:29

多智能体协同工程化实战:角色拆分、通信契约与上下文管理

1. 从"一个模型打天下"到"一支队伍打硬仗":多智能体协同到底在解决什么如果你最近半年在关注 AI 研发的工程化落地,大概率会反复撞见一个词——多智能体协同。但真正让我决定动手写这篇总结的,不是概念本身有多热&#x…

作者头像 李华
网站建设 2026/9/25 14:34:53

通信型CRM如何重塑客户跟进流程:坐席工作台与客户时间线实践复盘

做客服团队管理这几年,我一直有个执念:客户跟进的上下文绝对不能断。2024年下半年,我们整个客服和销售运营从“微信Excel传统呼叫平台”的混合方案,迁移到了DeskcommCRM,到现在跑了快九个月。整个过程从选型到落地&…

作者头像 李华