首页/新闻资讯/正文详情

CTF 内核利用视角下的 KPTI(Kernel Page Table Isolation)机制解析与绕过实战

发布时间:2026/9/25 23:26:07 来源:云帆数科 栏目:资讯中心
CTF 内核利用视角下的 KPTI(Kernel Page Table Isolation)机制解析与绕过实战
文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载导读本文以 CTF-Wikizh-tw中 KPTI 文档 为骨架系统讲解 Linux 内核页表隔离KPTI机制的设计动机、页表隔离模型、x86_64 下的内核态/用户态隔离语义以及在 CTF kernel pwn 场景中如何通过 qemu 命令行开启/关闭 KPTI、如何检查其状态并深入剖析绕过 KPTI 的三种主流思路修改页表权限、复用SWITCH_TO_USER_CR3_STACK切换页表后配合iret/sysret返回用户态以及利用用户态 signal handler 规避页表切换。阅读本文后你将能够在开启 KPTI 保护的题目环境中正确判断防护状态并构造出可实际运行的内核 ROP 返回用户态利用链。KPTI 是什么内核页表隔离KPTIKernel Page Table Isolation最初的主要设计目的有两个一是缓解对 KASLR 的绕过二是缓解 CPU 侧信道攻击尤其是 Meltdown 一类利用乱序执行/预测执行泄露内核数据的攻击。在开启 KPTI 之后内核态空间内存与用户态空间内存的隔离被进一步增强。其核心页表模型如下内核态页表包含用户空间内存的页表项 内核空间内存的页表项即完整的两部分映射用户态页表只包含用户空间内存的页表项以及必要的内核空间内存页表项——例如用于处理系统调用、中断等信息所需的少量内核内存trampoline 区域。也就是说当 CPU 处于用户态ring3时其页表CR3 指向的用户 PGD中并不存在完整的内核空间映射内核的代码、数据对于用户态而言是不可见的只有当陷入内核态ring0时才会切换到包含完整内核映射的内核页表。这一模型如下图所示图片来自仓库 defense/isolation/user-kernel/figure/476px-Kernel_page-table_isolation.svg.png仓库中 基础知識 一文也给出了同源的 KPTI 页表布局图figure/kpti.png并明确说明两张页表上都有对用户内存空间的完整映射但用户页表中只映射了少量内核代码如系统调用入口点、中断处理等只有内核页表中才有对内核内存空间的完整映射。这与本文所讲的模型完全一致。内核态视角KPTI 带来的类 SMEP效果在 x86_64 的 PTI 机制中内核态页表内属于用户空间内存的映射部分被全部标记为不可执行NX。这意味着对于原本不具有 SMEPSupervisor Mode Execution Protection特性的硬件一旦开启了 KPTI 保护就等效于拥有了类似 SMEP 的特性——内核态无法执行用户态代码SMAPSupervisor Mode Access Protection的模拟也可以以类似方式引入但截至目前尚未引入。因此在开启了 KPTI 保护的当前内核中如果没有开启 SMAP 保护内核仍然可以访问读/写用户态空间的内存只是不能跳转到用户态空间执行 Shellcode。这一语义差异是后续讨论如何绕过的出发点既然不能执行就只能想办法在保持执行权位于内核态的前提下完成提权并安全返回用户态。关于 SMEP/SMAP 的独立讨论可参考仓库 defense/isolation/user-kernel/user-code-execution.mdx86 SMEP、CR4 第 20 位与 user-data-access.mdx86 SMAP、CR4 第 21 位以及 defense/isolation/user-kernel/readme.md 中对四种隔离的概括默认的 ring 隔离、SMEP、SMAP、KPTI。发展历史与引入版本Linux 4.15 中正式引入了 KPTI 机制并且该机制被反向移植backport到了长期维护分支Linux 4.14.11Linux 4.9.75Linux 4.4.110也就是说即使不使用最新的内核只要位于上述或更新的版本KPTI 保护也是可能存在的。在 CTF 题目中需要以题目实际启动参数和内核版本为准来判断是否生效。原文档的發展歷史与實現小节标注为 TODO待补充本文以下内容将结合仓库内文档与 Linux 内核源码级代码arch/x86/entry/entry_64.S相关宏对实现细节进行补充讲解。开启与关闭 KPTIKPTI 是通过内核命令行参数控制的在 qemu 模拟环境下通过-append选项传给内核# 开启 KPTIqemu 启动内核时 qemu-system-x86_64 \ -kernel ./bzImage \ -initrd ./rootfs.cpio \ -append consolettyS0 root/dev/ram oopspanic panic1 kpti1 \ -nographic# 关闭 KPTI qemu-system-x86_64 \ -kernel ./bzImage \ -initrd ./rootfs.cpio \ -append consolettyS0 root/dev/ram oopspanic panic1 nopti \ -nographickpti1显式开启 KPTInopti显式关闭 KPTI。其他常见的内核命令行参数可参考仓库 environment/qemu-emulate.md例如nokaslr关闭内核地址随机化、consolettyS0与-nographic配合将内核输出重定向到当前终端等。注意 KPTI 与 KASLR 是相互独立的选项nokaslr并不会关闭 KPTI。另外需要留意一个细节KPTI 的生效还依赖内核配置CONFIG_PAGE_TABLE_ISOLATION。如果内核编译时未包含该配置则命令行参数也不会起作用反之若内核编译时默认开启了 KPTI则nopti可以将其关闭。以实际运行环境为准是 CTF 中最重要的判断原则。状态查看如何确认 KPTI 是否开启在目标系统qemu 启动的 rootfs中可以通过以下两种方式确认 KPTI 是否开启。方式一查看内核启动日志中的页表隔离状态/home/pwn # dmesg | grep page table [ 0.000000] Kernel/User page tables isolation: enabled若输出为enabled说明 KPTI 已开启。方式二查看 CPU 特性标志位/home/pwn # cat /proc/cpuinfo | grep pti fpu_exception : yes flags : ... pti smep smap若flags中出现了pti说明 KPTI 特性可用并已开启同时还可看到smep、smap标志位用于判断其他隔离是否开启。这与 bypass-smep.md 中通过grep smep /proc/cpuinfo检查 SMEP 的方式一脉相承——CPU 特性标志是内核防护状态最直接的反映。Attack KPTI为什么难以在运行时关闭与 SMAP、SMEP 不同KPTI 的实现与内核源码紧密结合涉及页表分配、上下文切换、trampoline 代码等核心路径似乎没有办法在运行时刻将其关闭。这意味着在开启 KPTI 的题目中攻击者必须正面处理内核页表对用户空间无执行权限这一约束。仓库 exploitation/rop/ret2usr.md 明确写道对于开启了 KPTI 的内核而言内核页表的用户地址空间无执行权限因此当内核尝试执行用户空间代码时由于对应页顶级表项没有设置可执行位会直接 panic——ret2usr 已经是过去式了。因此下面几种绕过思路才是开启 KPTI 后的主流方案。方案一修改页表权限开启 KPTI 后用户态空间的所有数据都被标记了 NX 权限。但是页表权限本身存放在内存中如果我们能够在劫持控制流后修改对应的页表项PTE权限使其重新获得可执行权限那么当内核没有开启 SMEP时我们就可以在修改页表权限后返回用户态执行用户态构造的代码Shellcode / 提权函数。这一思路的成立条件十分苛刻首先需要能够定位到当前进程用户空间地址对应的内核态 PTE通常需要结合direct map或struct page的地址计算其次需要内核未开启 SMEP。因此在现代题目中这一方案往往不如直接复用内核的返回路径来得通用但它是理解页表权限是内存数据、可被改写这一本质的最好例子。方案二复用 SWITCH_TO_USER_CR3_STACK 切换页表并返回用户态开启 KPTI 后用户态进入内核态时会进行页表切换用户 CR3 → 内核 CR3从内核态恢复用户态时也会进行页表切换内核 CR3 → 用户 CR3。如果攻击者能够控制内核在执行返回用户态路径时所执行的、用于切换页表的代码片段就可以在完成页表切换的同时正常返回到用户态。通过分析内核态到用户态切换的代码可以得知页表切换主要靠SWITCH_TO_USER_CR3_STACK汇编宏完成。因此我们只需要在 ROP 链中调用这部分代码即可。该宏位于内核源码arch/x86/entry/calling.h相关汇编可见仓库 kpti.md 原文 摘录核心逻辑如下.macro SWITCH_TO_USER_CR3_STACK scratch_reg:req pushq %rax SWITCH_TO_USER_CR3_NOSTACK scratch_reg\scratch_reg scratch_reg2%rax popq %rax .endm .macro SWITCH_TO_USER_CR3_NOSTACK scratch_reg:req scratch_reg2:req ALTERNATIVE jmp .Lend_\, , X86_FEATURE_PTI mov %cr3, \scratch_reg ALTERNATIVE jmp .Lwrcr3_\, , X86_FEATURE_PCID /* * Test if the ASID needs a flush. */ movq \scratch_reg, \scratch_reg2 andq $(0x7FF), \scratch_reg /* mask ASID */ bt \scratch_reg, THIS_CPU_user_pcid_flush_mask jnc .Lnoflush_\ /* Flush needed, clear the bit */ btr \scratch_reg, THIS_CPU_user_pcid_flush_mask movq \scratch_reg2, \scratch_reg jmp .Lwrcr3_pcid_\ .Lnoflush_\: movq \scratch_reg2, \scratch_reg SET_NOFLUSH_BIT \scratch_reg .Lwrcr3_pcid_\: /* Flip the ASID to the user version */ orq $(PTI_USER_PCID_MASK), \scratch_reg .Lwrcr3_\: /* Flip the PGD to the user version */ orq $(PTI_USER_PGTABLE_MASK), \scratch_reg mov \scratch_reg, %cr3 .Lend_\: .endm可以这样理解该宏的语义若 CPU 不支持 PTIX86_FEATURE_PTI未置位ALTERNATIVE直接跳到.Lend_\整个宏退化为空操作若支持 PTI则将当前%cr3读入暂存寄存器先按0x7FF掩码取出 ASID 并判断是否需要刷新 TLB利用 per-CPU 的THIS_CPU_user_pcid_flush_mask最后通过orq $(PTI_USER_PGTABLE_MASK)把 PGD 切换为 user 版本写入%cr3——这正是从内核页表切回用户页表的关键一步。事实上我们不仅希望切换页表还希望能够返回到用户态。因此还需要复用内核中返回用户态的代码。内核返回用户态主要有两种方式iret中断返回和sysret快速系统调用返回。下面分别介绍。iret 路径swapgs_restore_regs_and_return_to_usermode内核中swapgs_restore_regs_and_return_to_usermode位于arch/x86/entry/entry_64.S是经典的返回用户态代码段它依次完成恢复 pt_regs、切换到 trampoline 栈、拷贝 IRET frame、调用SWITCH_TO_USER_CR3_STACK切换页表、SWAPGS恢复 GS、最后INTERRUPT_RETURN即iretq。核心汇编如下SYM_INNER_LABEL(swapgs_restore_regs_and_return_to_usermode, SYM_L_GLOBAL) #ifdef CONFIG_DEBUG_ENTRY /* Assert that pt_regs indicates user mode. */ testb $3, CS(%rsp) jnz 1f ud2 1: #endif POP_REGS pop_rdi0 /* * The stack is now user RDI, orig_ax, RIP, CS, EFLAGS, RSP, SS. * Save old stack pointer and switch to trampoline stack. */ movq %rsp, %rdi movq PER_CPU_VAR(cpu_tss_rw TSS_sp0), %rsp UNWIND_HINT_EMPTY /* Copy the IRET frame to the trampoline stack. */ pushq 6*8(%rdi) /* SS */ pushq 5*8(%rdi) /* RSP */ pushq 4*8(%rdi) /* EFLAGS */ pushq 3*8(%rdi) /* CS */ pushq 2*8(%rdi) /* RIP */ /* Push user RDI on the trampoline stack. */ pushq (%rdi) /* * We are on the trampoline stack. All regs except RDI are live. * We can do future final exit work right here. */ STACKLEAK_ERASE_NOCLOBBER SWITCH_TO_USER_CR3_STACK scratch_reg%rdi /* Restore RDI. */ popq %rdi SWAPGS INTERRUPT_RETURN可以看到只要伪造如下的栈布局然后跳转到movq %rsp, %rdi即swapgs_restore_regs_and_return_to_usermode代码段中的相应偏移处就可以同时完成页表切换和返回用户态fake rax fake rdi RIP - 用户态待执行代码地址 CS - 用户态代码段__USER_CS EFLAGS RSP - 用户态栈 SS - 用户态栈段__USER_DS栈顶依次是fake rax、fake rdi用于对齐POP_REGS的弹出顺序随后是标准的 IRET frame。POP_REGS pop_rdi0会把fake rax弹入 RAX把fake rdi弹入 RDI随后代码从%rsp此时指向RIP处拷贝 5 个 8 字节构成 IRET frame 到 trampoline 栈再执行SWITCH_TO_USER_CR3_STACK scratch_reg%rdi完成 CR3 切换popq %rdi恢复用户态 RDISWAPGS后iretq直接进入用户态。在仓库的 FGKASLR 文档defense/randomization/fgkaslr.md中也提到swapgs_restore_regs_and_return_to_usermode位于不参与函数随机化的.text节区中是绕过 KPTI 防护时最常被复用的内核 gadget。而在 bypass-smep.md 的强网杯 2018 core 例题中ROP 链的收尾正是rop_chain[i] SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 22 offset;即从该标签偏移 22 字节处对应movq %rsp, %rdi附近进入配合getRootShell、user_cs、user_rflags、user_sp、user_ss构造出完整的返回用户态 IRET frame——这就是在开启 KPTI以及 SMEP/SMAP条件下返回用户态的标准姿势。sysret 路径entry_SYSCALL_64 收尾代码另一种返回用户态的方式是复用系统调用入口entry_SYSCALL_64的收尾代码通过sysretq返回。使用sysret时首先需要确保rcx与r11取如下值rcx, 保存返回用户态时要执行的代码地址rip r11, 保存 eflags然后构造如下的栈fake rdi rsp, 用户态栈地址最后跳转至entry_SYSCALL_64的如下代码即可返回用户态SWITCH_TO_USER_CR3_STACK scratch_reg%rdi popq %rdi popq %rsp swapgs sysretq这段代码同样先执行SWITCH_TO_USER_CR3_STACK完成页表切换然后popq %rdi、popq %rsp恢复用户态参数与栈swapgs后sysretq直接跳到rcx指向的用户态代码。相比iretq路径sysret路径要求攻击者精确控制rcx、r11、栈上两处数据构造略为精巧但同样有效。方案三用户态 signal handler我们也可以考虑在用户态注册 signal handler利用信号处理机制来执行位于用户态的代码。在这种方式下无需切换页表内核对信号的投递本身就会经过完整的保存上下文 → 返回用户态执行 handler → 恢复上下文流程而该流程是由内核合法地完成的。因此只要我们在劫持控制流后不直接跳去执行用户态代码而是修改被中断进程的上下文中RIP指向用户态 handler例如通过修改 pt_regs 或伪造信号帧就可以借助内核自身的信号返回机制规避 KPTI 的内核页表对用户空间不可执行限制。这是 KPTI 绕过中一种相对隐蔽的思路适合在内核代码无法提供合适 gadget 的场景下使用。实战要点小结开启 KPTI 后的利用流程综合仓库内相关文档basic-knowledge.md、ret2usr.md、bypass-smep.md、fgkaslr.md开启 KPTI 后的一套完整利用流程通常为判断防护启动后执行dmesg | grep page table与cat /proc/cpuinfo | grep pti确认 KPTI 状态同时通过grep smep /proc/cpuinfo、grep smap /proc/cpuinfo确认 SMEP/SMAP信息泄露通过内核漏洞如未初始化内存、越界读泄露 canary 与内核基址如commit_creds、prepare_kernel_cred地址可由/proc/kallsyms或__ksymtab相对偏移推算见 fgkaslr.md劫持控制流利用漏洞栈溢出 / UAF / 任意写在受害者内核栈上布置 ROP 链提权ROP 中调用commit_creds(prepare_kernel_cred(NULL))获取 root 权限函数原型与用法见 basic-knowledge.md返回用户态复用swapgs_restore_regs_and_return_to_usermodeiret 路径或entry_SYSCALL_64收尾代码sysret 路径完成 CR3 切换与模式切换跳回用户态执行system(/bin/sh)。其中第 5 步是 KPTI 特有的关键环节——必须由内核侧完成 CR3 切换后才能安全进入用户态否则会出现页表与特权级不匹配导致的 panic。参考与延伸阅读仓库内 KPTI 文档原文本文骨架来源basic-knowledge.mdKPTI 页表模型与 Meltdown 背景、commit_creds(prepare_kernel_cred(NULL))提权手法ret2usr.mdret2usr 与 KPTI 的关系说明为何 KPTI 令 ret2usr 成为过去式bypass-smep.md强网杯 2018 core 完整例题展示swapgs_restore_regs_and_return_to_usermode在实战中的用法fgkaslr.mdswapgs_restore_regs_and_return_to_usermode位于非随机化.text节区是绕过 KPTI 的关键 gadgetqemu-emulate.mdqemu 启动内核的完整参数说明赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐CTF 内核利用中的 KASLR原理、QEMU 开关实战与绕过思路ctf-wiki 内核防护篇CTF 内核利用中的 KASLR原理、QEMU 开关实战与绕过思路ctf wiki 内核防护篇 导读 KASLRKernel Address Space文档网络安全教程CTF-Wiki 内核堆利用堆喷射Heap Spray实战解析——以 RWCTF2023 Digging into kernel 3 为例CTF Wiki 内核堆利用堆喷射Heap Spray实战解析——以 RWCTF2023 Digging into kernel 3 为例 本文是 CTF文档网络安全教程ctf-wiki 内核 PwnSMEP/SMAP 防护原理与 ROP 绕过实战强网杯 2018 corectf wiki 内核 PwnSMEP/SMAP 防护原理与 ROP 绕过实战强网杯 2018 core 本文是 ctf wiki 内核 ROP 系列文章文档网络安全教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

cuDF 字符串与定点数互转指南:pylibcudf `strings.convert.convert_fixed_point` 模块详解
cuDF 字符串与定点数互转指南:pylibcudf `strings.convert.convert_fixed_point` 模块详解

数据分析数据工程机器学习 【免费下载链接】cudf cuDF - GPU DataFrame Library 项目地址: https://gitcode.com/gh_mirrors/cu/cudf 点击查看 免费下载 本指南以 pylibcudf 文档 convert_fixed_point.rst 为骨架,系统讲解 pylibcudf.strings.convert.… · 2026/9/25 23:26:00

风电齿轮箱故障诊断工业数据集:ResNet18端到端实战指南
风电齿轮箱故障诊断工业数据集:ResNet18端到端实战指南

简介:本资源为面向机械故障诊断与智能运维领域的齿轮箱多模态故障数据集,适用于高校研究生、工业算法工程师及设备预测性维护实践者,支撑振动分析、声学诊断、温度建模等典型故障识别任务。压缩包共15个文件,含6张故障频谱图&… · 2026/9/25 23:25:41

离线安装Docker与Docker-Compose:安装包、踩坑与版本对齐
离线安装Docker与Docker-Compose:安装包、踩坑与版本对齐

简介:面向开发、测试与运维场景的Docker及Docker Compose安装包,适合在离线或内网环境中快速搭建容器平台。压缩包共4个文件,主要包括Docker 27.3.1官方二进制tgz包、用于将Docker注册为系统服务的docker.service文件、一键执行环境配置与安装… · 2026/9/25 23:25:34

nginx-1.25.2已编译包部署避坑指南:从依赖检查到平滑升级
nginx-1.25.2已编译包部署避坑指南:从依赖检查到平滑升级

简介:本资源为 Linux 环境下已编译完成的 Nginx 1.25.2 版本安装包,面向需要在服务器上快速部署 Web 服务或反向代理的运维与后端开发人员,解压后即可直接运行,省去源码编译环节。压缩包共 569 个文件,约 4.26MB&#… · 2026/9/25 23:53:24

RAR for Linux 原生命令行工具深度指南
RAR for Linux 原生命令行工具深度指南

简介:本资源是Linux平台专用的64位RAR命令行工具v6.1.b1测试版,面向Linux系统管理员、运维工程师及需要处理RAR格式文件的开发者,解决在Ubuntu、Fedora等主流发行版中缺乏原生RAR支持的问题。压缩包共11个文件,含核心可执行文件&a… · 2026/9/25 23:53:24

仿抖音上下滑动切换视频:手势、滚动容器与播放器生命周期全链路
仿抖音上下滑动切换视频:手势、滚动容器与播放器生命周期全链路

简介:这是一份面向Android开发者的「仿抖音上下滑动切换视频」完整工程源码,适合具备一定Android基础、希望掌握短视频列表交互与播放器集成的中高级开发者。资源围绕RecyclerView、SnapHelper与自定义LayoutManager三大核心组件展开,解决视频… · 2026/9/25 23:53:18

仿抖音上下滑动切换视频:手势冲突与播放器复用实战
仿抖音上下滑动切换视频:手势冲突与播放器复用实战

简介:这是一份面向Android开发者的「仿抖音上下滑动切换视频」完整工程源码,适合已掌握RecyclerView基础、希望进阶学习短视频交互实现的中级开发者。资源围绕RecyclerView、SnapHelper与自定义LayoutManager三大核心组件展开,解决视频列表整… · 2026/9/25 23:53:18

仿抖音上下滑动切换视频:手势识别、预加载与播放器生命周期完整实现
仿抖音上下滑动切换视频:手势识别、预加载与播放器生命周期完整实现

简介:这是一份面向Android开发者的「仿抖音上下滑动切换视频」完整工程源码,适合具备一定Android基础、希望深入理解短视频列表交互实现原理的中高级开发者。资源围绕RecyclerView、SnapHelper与自定义LayoutManager三大核心组件展开,涵盖视频… · 2026/9/25 23:53:11

WinForms多选下拉控件:继承ComboBox并自绘复选框
WinForms多选下拉控件:继承ComboBox并自绘复选框

简介:一套面向 Windows 桌面开发者的自定义控件源码,实现将复选框(Checkbox)整合进组合框(Combobox)的“CheckComboBox”类;它解决了普通下拉列表只能单选、无法直观显示多选状态的问题&#xf… · 2026/9/25 23:52:45

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码