基于内核版本5.4.0与6.17.0,仅讨论x86架构。
内核Image生成 #
要了解KASLR的细节,首先需要了解内核镜像的构建过程。根据6.17.0中的Makefile文件中描述的构建规则可以整理出如下步骤:
- 在内核根目录下构建出 vmlinux 与 vmlinux.unstripped;
- 在 arch/x86/boot/compressed 中根据根目录下的 vmlinux 获得 vmlinux.bin(对vmlinux进行objcopy) 与 vmlinux.relocs(提取vmlinux中的R_X86_64_32、R_X86_64_32S、R_X86_64_64)
- 利用 arch/x86/boot/compressed 中的 vmlinux.bin和 vmlinux.relocs 从而获得压缩文件 vmlinux.bin.(gz|bz2|lzma|…)
- 使用 mkpiggy 把压缩后的文件转为 piggy.S,进而放入 piggy.o。
- vmlinux-objs-y 和 vmlinux-libs-y 会包含一堆文件列表:
# arch/x86/boot/compressed/Makefile
$(obj)/vmlinux: $(vmlinux-objs-y) $(vmlinux-libs-y) FORCE
$(call if_changed,ld)
- 在 arch/x86/boot 目录内会利用 arch/x86/boot/compressed 目录内的 vmlinux 使用 objcopy 获得 vmlinux.bin,最后就是生成 bzImage:
# arch/x86/boot/Makefile
$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin FORCE
$(call if_changed,image)
@$(kecho) 'Kernel: $@ is ready' ' (#'$(or $(KBUILD_BUILD_VERSION),`cat .version`)')'
KASLR随机化机制 #
构建时保存重定位信息 #
根据构建规则,创建 vmlinux.relocs 就是把根目录下的 vmlinux 文件中的绝对地址跳转指令所在的虚拟地址保存下来。vmlinux.relocs 中的数据为二进制格式的32位无符号整型,首先是一个0作为起始标志,紧跟着的是64位的绝对地址跳转指令中目的地址所处位置(随机化之前)。下面又是一个0,然后是32位的绝对地址跳转指令。 使用hexdump输出为如下:
// R_X86_64_64
00000000 00 00 00 00 30 d6 22 81 a3 fe 28 81 7d a6 2d 81
00000010 b3 00 32 81 83 ac 32 81 42 38 37 81 9a 71 3c 81
00000020 42 a0 3c 81 40 a2 3c 81 c8 a2 3c 81 dc a3 3c 81
......
// R_X86_64_32 and R_X86_64_32S
00109310 00 00 00 00 80 14 00 81 14 16 00 81 70 17 00 81
00109320 7e 17 00 81 c1 17 00 81 a9 00 20 81 fe 00 20 81
00109330 4c 01 20 81 5a 01 20 81 61 01 20 81 6d 01 20 81
因此80 14 00 81对应的0xffffffff81001480这个地址处是一个32位的绝对地址,需要进行KASLR处理。
30 d6 22 81对应的0xffffffff8122d630这个地址处是一个64位的绝对地址,同样需要进行KASLR处理。
在 objdump 得到的反汇编代码中这两个地址的内容为:
ffffffff81001476: 48 25 ff 0f 00 00 and $0xfff,%rax
// 0x83036100 + 0x7cfc9f00 = 0x100000000
ffffffff8100147c: 48 8b 04 c5 00 61 03 83 mov -0x7cfc9f00(,%rax,8),%rax
ffffffff81001484: f3 48 0f ae d8 wrgsbase %rax
ffffffff8122d629: e8 b2 ac 37 00 callq ffffffff815a82e0 <wait_for_kprobe_optimizer_locked>
ffffffff8122d62e: 48 b8 df 87 a0 83 ff movabs $0xffffffff83a087df,%rax
ffffffff8122d635: ff ff ff
这两处都是需要随机化的。第一处看起来是与 %gs 寄存器有关,第二处则显然。
启动时应用重定位 #
内核启动时,会读取 vmlinux.relocs 中的重定位信息,并对其中记录的虚拟地址处的绝对地址进行重定位处理。
具体表现就是从后向前依次遍历 vmlinux.relocs 的32位、64位重定位位置,使整体虚拟地址空间偏移。
// arch/x86/boot/compressed/misc.c
asmlinkage __visible void *extract_kernel(void *rmode, unsigned char *output)
{
....
// 生成随机Offset
choose_random_location((unsigned long)input_data, input_len,
(unsigned long *)&output, needed_size, &virt_addr);
....
// 解压内核代码同时直接随机化
entry_offset = decompress_kernel(output, virt_addr, error);
....
}
// arch/x86/boot/compressed/kaslr.c
void choose_random_location(unsigned long input,
unsigned long input_size,
unsigned long *output,
unsigned long output_size,
unsigned long *virt_addr)
{
....
/* Pick random virtual address starting from LOAD_PHYSICAL_ADDR. */
if (IS_ENABLED(CONFIG_X86_64))
random_addr = find_random_virt_addr(LOAD_PHYSICAL_ADDR, output_size);
*virt_addr = random_addr; // 随机的Offset
}