ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深入解析 Linux FGKASLR:函数级内核地址随机化的实现、代价与攻防视角

深入解析 Linux FGKASLR:函数级内核地址随机化的实现、代价与攻防视角 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载FGKASLRFunction Granular KASLR是对传统 KASLR 的强化它在内核基地址整体随机化的基础上于解压加载阶段以函数粒度重新排布内核代码使攻击者在泄露单个内核地址后无法直接推导整个代码段布局。本篇指南以 ctf-wiki 仓库中 docs/zh-tw/docs/pwn/linux/kernel-mode/defense/randomization/fgkaslr.md 为主体结合仓库中内核安全相关文档系统讲解 FGKASLR 的编译期实现、加载期布局算法、筛选逻辑、性能/内存/体积开销、开关方法以及它在 CTF 内核利用视角下可利用的弱点如__ksymtab固定偏移与符号地址推导。读完本文你将理解 FGKASLR 的内部工作机理并能用它分析真实内核镜像中的符号布局与随机化边界。从 KASLR 到 FGKASLR背景与动机在介绍 FGKASLR 之前需要先明确传统 KASLR 的能力边界。仓库中的 KASLR 文档 指出开启 KASLR 后内核代码段基地址等地址会整体偏移而 基础知识点文档 进一步说明KASLR 与用户态 ASLR 类似——在内核镜像映射到实际地址空间时加上一个偏移值但内核内部的相对偏移仍然不变。未开启 KASLR 时内核代码段基址为0xffffffff81000000direct mapping area 基址为0xffff888000000000。KASLR 的不足由此暴露若攻击者通过任意信息泄露漏洞拿到内核中的某一个地址即可立即反推出整个内核的加载偏移进而掌握全部内核地址布局。正是针对这一缺陷研究者实现了 FGKASLR在 KASLR 基地址随机化的基础上在加载时刻以函数粒度重新排布内核代码。也就是说即使攻击者知道内核镜像的加载基址函数之间的相对位置也不再固定。FGKASLR 的实现相对简单主要修改集中在两个部分编译阶段与加载阶段。目前 FGKASLR 只支持 x86_64 架构。编译阶段借助-ffunction-sections拆分函数节区FGKASLR 利用 gcc 编译选项-ffunction-sections把内核中不同的函数放到不同的 section 中。具体规则如下任何使用 C 语言编写的函数以及不在特殊输入节的函数都会单独作为一个节区使用汇编编写的代码会位于一个统一的节区中例如系统调用入口等汇编代码集中在.text合并节。编译后的vmlinux保留所有的节区头Section Headers以便内核在加载阶段知道每个函数的地址范围同时FGKASLR 还带有一个重定位地址的扩展表。通过这两组信息内核在解压后就可以乱序排列函数。最终生成的二进制镜像其第一个段segment包含一个合并节由若干个函数合并而成即.text若干其它单独构成一个节的函数.text.*形式如.text.prepare_kernel_cred。加载阶段解压后对.text.*节区重新布局在内核解压之后FGKASLR 的加载逻辑会首先检查保留的符号信息寻找需要随机化的.text.*节区跳过第一个合并的节区.text它不会被随机化对其余节区进行地址随机化但随机化后的地址仍然与.text节区相邻修改已有的用于更新重定位地址的代码不仅考虑相对于加载地址的偏移还考虑函数节区要被移动到的位置。此外为了隐藏新的内存布局/proc/kallsyms中的符号使用随机的顺序排列。在 FGKASLR v4 版本之前该文件中的符号按照字母序排列。随机化节区的筛选逻辑layout_randomized_image通过分析源码可以确认在layout_randomized_image函数中计算了最终会被随机化的节区并存储在sections数组中。核心遍历逻辑如下/* * now we need to walk through the section headers and collect the * sizes of the .text sections to be randomized. */ for (i 0; i shnum; i) { s sechdrs[i]; sname secstrings s-sh_name; if (s-sh_type SHT_SYMTAB) { /* only one symtab per image */ if (symtab) error(Unexpected duplicate symtab); symtab malloc(s-sh_size); if (!symtab) error(Failed to allocate space for symtab); memcpy(symtab, output s-sh_offset, s-sh_size); num_syms s-sh_size / sizeof(*symtab); continue; } if (s-sh_type SHT_STRTAB i ! ehdr-e_shstrndx) { if (strtab) error(Unexpected duplicate strtab); strtab malloc(s-sh_size); if (!strtab) error(Failed to allocate space for strtab); memcpy(strtab, output s-sh_offset, s-sh_size); } if (!strcmp(sname, .text)) { if (text) error(Unexpected duplicate .text section); text s; continue; } if (!strcmp(sname, .data..percpu)) { /* get start addr for later */ percpu s; continue; } if (!(s-sh_flags SHF_ALLOC) || !(s-sh_flags SHF_EXECINSTR) || !(strstarts(sname, .text))) continue; sections[num_sections] s; num_sections; } sections[num_sections] NULL; sections_size num_sections;从这段代码可以提炼出节区参与随机化的三个必要条件条件含义节区名以.text开头strstarts(sname, .text)只有代码函数节区候选section flags 中包含SHF_ALLOC该节区在内存中被分配section flags 中包含SHF_EXECINSTR该节区包含可执行指令注意两个特判分支名为.text的合并节被单独记录后continue不进入随机化列表.data..percpu仅记录起始地址供后续使用同样不参与随机化。用 readelf 实证哪些节区不参与随机化基于上述筛选条件可以立刻推断出两个关键结论__ksymtab不会参与随机化它不是可执行节区.data不会参与随机化没有SHF_EXECINSTR。在真实 vmlinux 上可用如下命令验证-vE .text|AX用于过滤掉参与随机化的节区 readelf --section-headers -W vmlinux| grep -vE .text|AX ... [36106] .rodata PROGBITS ffffffff81c00000 e1e000 382241 00 WA 0 0 4096 [36107] .pci_fixup PROGBITS ffffffff81f82250 11a0250 002ed0 00 A 0 0 16 [36108] .tracedata PROGBITS ffffffff81f85120 11a3120 000078 00 A 0 0 1 [36109] __ksymtab PROGBITS ffffffff81f85198 11a3198 00b424 00 A 0 0 4 [36110] __ksymtab_gpl PROGBITS ffffffff81f905bc 11ae5bc 00dab8 00 A 0 0 4 [36111] __ksymtab_strings PROGBITS ffffffff81f9e074 11bc074 027a82 01 AMS 0 0 1 [36112] __init_rodata PROGBITS ffffffff81fc5b00 11e3b00 000230 00 A 0 0 32 [36113] __param PROGBITS ffffffff81fc5d30 11e3d30 002990 00 A 0 0 8 [36114] __modver PROGBITS ffffffff81fc86c0 11e66c0 000078 00 A 0 0 8 [36115] __ex_table PROGBITS ffffffff81fc8740 11e6738 001c50 00 A 0 0 4 [36116] .notes NOTE ffffffff81fca390 11e8388 0001ec 00 A 0 0 4 [36117] .data PROGBITS ffffffff82000000 11ea000 215d80 00 WA 0 0 8192 [36118] __bug_table PROGBITS ffffffff82215d80 13ffd80 01134c 00 WA 0 0 1 [36119] .vvar PROGBITS ffffffff82228000 14110d0 001000 00 WA 0 0 16 [36120] .data..percpu PROGBITS 0000000000000000 1413000 02e000 00 WA 0 0 4096 [36122] .rela.init.text RELA 0000000000000000 149eec0 000180 18 I 36137 36121 8 [36124] .init.data PROGBITS ffffffff822b6000 14a0000 18d1a0 00 WA 0 0 8192 [36125] .x86_cpu_dev.init PROGBITS ffffffff824431a0 162d1a0 000028 00 A 0 0 8 [36126] .parainstructions PROGBITS ffffffff824431c8 162d1c8 01e04c 00 A 0 0 8 [36127] .altinstructions PROGBITS ffffffff82461218 164b214 003a9a 00 A 0 0 1 [36129] .iommu_table PROGBITS ffffffff82465bb0 164fbb0 0000a0 00 A 0 0 8 [36130] .apicdrivers PROGBITS ffffffff82465c50 164fc50 000038 00 WA 0 0 8 [36132] .smp_locks PROGBITS ffffffff82468000 1651610 007000 00 A 0 0 4 [36133] .data_nosave PROGBITS ffffffff8246f000 1658610 001000 00 WA 0 0 4 [36134] .bss NOBITS ffffffff82470000 165a000 590000 00 WA 0 0 4096 [36135] .brk NOBITS ffffffff82a00000 1659610 02c000 00 WA 0 0 1 [36136] .init.scratch PROGBITS ffffffff82c00000 1659620 400000 00 WA 0 0 32 [36137] .symtab SYMTAB 0000000000000000 1a59620 30abd8 18 36138 111196 8 [36138] .strtab STRTAB 0000000000000000 1d641f8 219a29 00 0 0 1 [36139] .shstrtab STRTAB 0000000000000000 1f7dc21 0ed17b 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), l (large), p (processor specific)可以看到__ksymtab、.data、.rodata、__bug_table、.bss等节区要么只有W可写要么只有A可分配标志均不满足SHF_EXECINSTR因此都不会参与函数级随机化。这一实证结果直接支撑了后文FGKASLR 可利用弱点的分析。性能开销分析FGKASLR 对性能的影响主要来自两个阶段启动阶段与运行阶段。启动阶段在启动阶段FGKASLR 需要完成以下额外工作解析内核的 ELF 文件获取需要随机化的节区调用随机数生成器确定每个节区需要存储的地址并进行布局将原有解压的内核拷贝到另外一个地方以避免内存破坏增加内核需要重定位的次数检查每一个需要重定位的地址是否位于随机化的节区如果是则调整一个新的偏移重新排列那些需要按照地址排序的数据表。综合以上开销在一个现代化系统上启动一个测试 VM 大概会花费约 1 秒的额外时间。运行阶段运行阶段的额外开销主要取决于具体负载。由于原先相邻的函数可能被随机化到不同的地址指令缓存I-cache局部性变差因此整体性能相对而言会有所降低。内存开销与程序体积影响内存方面在启动阶段FGKASLR 需要较多的堆内存如上述代码中大量malloc分配符号表、字符串表等因此 FGKASLR 可能不适用于内存较小的系统。这些临时内存会在内核解压后被释放。程序体积方面FGKASLR 会引入额外的节区头部信息因此会增加vmlinux文件的大小。在标准配置下vmlinux的大小会增加约3%压缩后的镜像大小大概会增加约15%。开启与关闭 FGKASLR开启若想开启内核的 FGKASLR需要在内核配置中开启CONFIG_FG_KASLRy选项。FGKASLR 还支持模块的随机化尽管 FGKASLR 只支持 x86_64 架构下的内核但该特性可以支持其它架构下的模块通过CONFIG_MODULE_FG_KASLRy开启。关闭通过在命令行使用nokaslr关闭 KASLR会同时关闭 FGKASLR也可以单独使用nofgkaslr关闭 FGKASLR。这与仓库中 KASLR 文档 及 QEMU 模拟环境文档 的描述一致使用 qemu 启动内核时可以在-append选项中添加kaslr开启、nokaslr关闭 KASLR。在本地调试内核漏洞时若希望稳定地调试关闭随机化以固定地址可以在 qemu 启动脚本中追加nokaslrqemu-system-x86_64 \ -m 64M \ -nographic \ -kernel ./bzImage \ -initrd ./rootfs.img \ -append root/dev/ram rw consolettyS0 oopspanic panic1 nokaslr \ -smp cores2,threads1 \ -cpu kvm64FGKASLR 的弱点与利用思路尽管 FGKASLR 提升了随机化粒度但根据其实现特点仍然存在以下可以利用的缺陷函数粒度随机化的固有局限如果函数内部的某个地址已知那么函数内部各指令间的相对地址也就全部可知。.text合并节不参与随机化一旦知道其中某个地址就可以获取该节区所有地址。有意思的是系统调用的入口代码都在该节区内——这些代码大多是汇编代码集中在统一的合并节中。该节区还包含一些对内核利用很有价值的 gadgetswapgs_restore_regs_and_return_to_usermode可帮助绕过 KPTI 防护相关背景可参考 KPTI 文档memcpy内存拷贝 gadgetsync_regs可以把 RAX 放到 RDI 中。__ksymtab相对偏移固定__ksymtab相对于内核镜像的偏移是固定的因此如果能够泄露数据就可以泄露其它符号地址如prepare_kernel_cred、commit_creds。具体推导步骤如下基于内核镜像地址获取__ksymtab地址基于__ksymtab获取对应符号记录项的地址根据符号记录项中的具体内容获取对应符号的地址。data 节区偏移固定data 节区相对于内核镜像的偏移也是固定的因此在获取内核镜像基地址后就可以计算出数据区数据的地址。该节区有一个值得重点关注的全局数据modprobe_path——改写它可以实现以 root 权限执行任意程序的内核提权。__ksymtab 格式与符号地址计算__ksymtab中每个记录项的名字格式为__ksymtab_func_name。以prepare_kernel_cred为例对应记录项名为__ksymtab_prepare_kernel_cred可以直接通过该名字在 IDA 中找到对应位置__ksymtab:FFFFFFFF81F8D4FC __ksymtab_prepare_kernel_cred dd 0FF5392F4h __ksymtab:FFFFFFFF81F8D500 dd 134B2h __ksymtab:FFFFFFFF81F8D504 dd 1783Eh__ksymtab每一项的结构为struct kernel_symbol { int value_offset; int name_offset; int namespace_offset; };第一个表项value_offset记录了符号值相对于当前地址的偏移。那么prepare_kernel_cred的地址应为0xFFFFFFFF81F8D4FC - (2**32 - 0xFF5392F4) 0xffffffff814c67f0实际验证确实如此.text.prepare_kernel_cred:FFFFFFFF814C67F0 public prepare_kernel_cred .text.prepare_kernel_cred:FFFFFFFF814C67F0 prepare_kernel_cred proc near ; CODE XREF: sub_FFFFFFFF814A5ED552↑p这一计算关系意味着攻击者只需要知道内核镜像基址或任一能定位到__ksymtab的地址即可逐一恢复prepare_kernel_cred、commit_creds等关键符号的精确地址从而在 ROP 链中精准调用。在 CTF 内核利用场景中的意义综合来看FGKASLR 让单点地址泄露 → 全局布局已知的传统 KASLR 绕过思路失效但由于.text合并节、__ksymtab、data 区偏移固定等残留弱点攻击者依然可以借助.text合并节中的固定 gadget如swapgs_restore_regs_and_return_to_usermode、memcpy、sync_regs完成 KPTI 绕过与数据搬移借助__ksymtab相对偏移固定将任意数据泄露转化为commit_creds(prepare_kernel_cred(0))提权链借助 data 区偏移固定改写modprobe_path实现稳定提权。实际调试内核 pwn 题目时可先在 QEMU 模拟环境 中以nokaslr启动内核完成漏洞利用原型的开发与验证再切换到开启 FGKASLR 的真实环境利用上述弱点构造不依赖函数级随机化的通用利用链。关于内核漏洞利用的更多基础如权限提升、信息泄露等手法可继续阅读 aim 目录 与 exploitation 目录 下的相关文档。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐Linux内核引导过程第六部分内核地址空间随机化详解Linux内核引导过程第六部分内核地址空间随机化详解 前言 本文是深入解析Linux内核引导过程的第六部分我们将重点探讨内核地址空间随机化 KASLR 的实文档教程操作系统Linux内核引导过程第六部分内核地址空间随机化KASLRLinux内核引导过程第六部分内核地址空间随机化KASLR 前言 本文是深入解析Linux内核引导过程的第六部分我们将重点探讨内核地址空间随机化Ker文档教程操作系统上一篇RTSPtoWebRTC终极指南10分钟实现RTSP到WebRTC的无缝转换下一篇Docker环境下部署investment_data高效管理量化研究数据创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表