Skip to content

导引与随机源:KRandom

ASLR(Address Space Layout Randomization)的思路直白:每次运行把用户态的内存布局随机挪一挪,攻击者就没法预先知道「栈在哪、堆在哪、映射在哪」,也就没法照着固定地址写攻击。这一章给内核加这套随机化——但随机化的是栈顶、mmap 起点、brk 起点三个位置,不是代码段。为什么代码段(ELF base)没随机化,是这一章最值得讲的一件事:它不是「忘了做」,是当前的用户程序编译形态(non-PIE + small model)把全局变量和字符串的地址烤死成了 32 位绝对立即数,代码段一挪整个程序就崩——这条路是死的,得绕。做 ASLR 之前还得先有个内核随机源(KRandom),所以这一章从它讲起。验证靠一个抽样测试(随机偏移页对齐 + 落在区间内 + 每次不同),不靠「触发一次攻击看它迷不迷路」。

一个诚实的边界先说在前头:这套只随机化了栈/mmap/brk,代码段没动——所以栈溢出、堆溢出、mmap 那几类攻击的地址被搅乱了,但「在代码段里找 ROP gadget」这类依赖固定 text 地址的攻击仍然防不住。堵 text 那个口子要先把用户程序改成 PIE,是后面的事。

这章咱们要点亮什么

  1. ASLR 随机化哪三个位置:栈顶、mmap 起点、brk 起点——各随机一个页对齐的偏移,窗口大小不同(栈几 MiB、mmap 上 GiB、brk 十几 MiB)。
  2. 为什么代码段不能直接随机化:用户程序是 non-PIE + small model,全局/字符串地址被编成绝对立即数,挪 base 就全错位——这是调研出来的死路,不是偷懒。
  3. KRandom 这个随机源:xoshiro256** 流,启动时用 rdrand / TSC / PIT tick / 内核镜像地址混熵;它是「够用的内核 PRNG」,不是认证过的 CSPRNG。
  4. 页对齐为什么必须保住:随机偏移得是整页,否则会破坏栈的 16 字节对齐 ABI 约定。

先得有个随机源:KRandom

ASLR 要随机数,内核得先有个随机源。Cinux 加了一个 KRandom(kernel/lib/random.hpp),底层是 xoshiro256**——Blackman/Vigna 那个 PRNG,快、质量高、周期 2^256,是「不是密码学级但够用」的典型选择。它不是 CSPRNG——后面会讲为什么这个定位对 hobby 内核够。

随机源最怕的是「每次启动拿到同一个序列」(那 ASLR 就形同虚设)。所以 KRandom 在启动时(PIT::init 之后)做一次熵混合播种,把四个来源搅在一起:

cpp
// 熵来源(随机.cpp):能用的都用上,互相兜底
// - rdrand(CPUID.01H:ECX[30] 检测;有则用,是其中最强的)
// - TSC(rdtsc,时间戳,每次不同)
// - PIT::get_ticks()(启动到现在 tick 数)
// - 内核镜像自己的加载地址(&g_random)

random.cpp 的熵混合,has_rdrand():33CPUID.01H:ECX[30] 检测。)四个来源里 rdrand 最强(硬件随机);TSC/PIT/地址是兜底——单看任何一个都能被猜(比如 TSC 有规律),但混在一起、再经 splitmix64 扩成 256 位状态,就够挡住「照着固定地址写攻击」这种威胁了。这个定位要清楚:它是给 ASLR 搅乱地址用的「够用熵」,不是给加密密钥用的 CSPRNG;真要密码学强度,得走 rdrand 的专门路径 + 更严的熵审计,那是另一回事。

懒初始化的小心思。 next32/next64 调用时,如果还没 init,会自己先 init 一遍。这是防漏调:生产路径在 main.cpp 调了 g_random.init(),但测试环境可能不走 main,懒初始化保证「就算漏调也能用」——不至于因为播种没跑而崩或返回定值。

035_multi_terminal-45-gf25de18 · f25de18 · 2026-08-04