如何在运行时检测KASLR是否已启用或禁用?

如何在运行时检测KASLR是否启用或禁用?
4个回答

回答这个问题:
$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.14.27-041427-generic root=UUID=f3f8e7bc-b337-4194-88b8-3a513f6be55b ro quiet splash loglevel=0 vga=current udev.log-priority=3 fastboot kaslr acpiphp.disable=1 crashkernel=384M-2G:128M,2G-:256M vt.handoff=7

我对之前的答案不满意,因为它们没有真正深入验证KASLR的本质 - 尤其是嵌入式或自定义内核,它们没有实现相同的内核命令行接口(例如,ARM64在内核引导时需要一个名为kaslr-seed的设备树选择节点。这通常由引导加载程序使用安全随机数生成器来实现)。
在运行时,我知道有两件事情可以检查:
  1. 通过查看/proc/kallsyms文件,可以查看虚拟内存地址空间中的符号地址。
  2. 通过查看虚拟内存地址空间中的内核模块地址,可以使用lsmod命令。 注意:在某些机器上,lsmod可能不显示地址。在这种情况下,请尝试以root身份使用cat /proc/modules命令。如果不使用root,则地址可能全部为零(出于安全原因而清除)。~~感谢用户@crass的评论!~~
1和2都是类似的检查,但取决于系统上可用的内容,您可能需要使用其中之一。

1. /proc/kallsyms

要这样做,只需查看/proc/kallsyms的前几行即可:
root@device:~# head -n 3 /proc/kallsyms
ffffff8008080000 t _head
ffffff8008080000 T _text
ffffff8008080800 T do_undefinstr

请注意,例如 _head 的地址是 ffff ff80 0808 0000
现在重新启动您的机器并再次检查。
root@device:~# head -n 3 /proc/kallsyms
ffffff9fc8c80000 t _head
ffffff9fc8c80000 T _text
ffffff9fc8c80800 T do_undefinstr

请注意,例如_head的地址现在是ffff ff9f c8c8 0000
比较高位字节并发现ffffff80080 != 0xffffff9fc8c,因此地址在重新启动时发生了变化 => KASLR已启用。

2. lsmod

与上述/proc/kallsyms方法类似:检查lsmod,重新启动,再次检查lsmod,并比较地址。
root@device:~# lsmod
iptable_filter 16384 0 - Live 0xffffffa1c49b9000
ip_tables 28672 1 iptable_filter, Live 0xffffffa1c49ad000

请注意,例如 iptable_filter 的地址是 ffff ffa1 c49b 9000
现在重新启动您的机器并再次检查。
root@device:~# lsmod
iptable_filter 16384 0 - Live 0xffffff2100716000
ip_tables 28672 1 iptable_filter, Live 0xffffff210070a000

请注意,例如iptable_filter的地址现在是ffff ff21 0071 6000。 比较高位字节并发现ffffff2100716 != 0xffffffa1c49b9,因此地址在重新启动时被修改了 => KASLR已启用。
您可以迭代执行这些测试来确定随机性的质量。重新启动时地址有多大不同?是否存在明显的模式?KASLR的安全性益处与随机性或熵的质量成正比。
参考资料: 使用KASLR调试Linux内核 Linux内核驱动程序数据库RANDOMIZE_BASE

1在我的机器上,lsmod命令不会显示模块的地址。但是,如果以root权限使用cat /proc/modules命令,则可以看到这些地址。如果没有使用root权限,这些地址可能会全部显示为零(出于安全考虑而被清除)。 - crass

检查你的内核命令行。(以Debian 8为例)
$ cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-`uname -r` root=/dev/mapper/`hostname`-root ro quiet

kASLR在Ubuntu 14.10版本开始可用,但默认情况下未启用。在内核命令行上指定"kaslr"选项以使用kASLR。
注意:启用kASLR将禁用进入休眠模式的功能。
来源:https://wiki.ubuntu.com/Security/Features

现在Ubuntu默认启用了kASLR。所以这个检查不起作用了。请参考@bhass1或者我的回答,了解更好的kASLR检测方法。 - crass

我喜欢@bhass1在这里的答案,但是我不想重新启动我的电脑来进行测试。所以如果你有虚拟机的话,你可以执行基本上相同的测试。我正在使用qemu和-kernel以及-initrd选项,下面是我的操作步骤:
首先,通过查看cat /proc/cmdline来检查你正在运行的内核。
$ cat /proc/cmdline 
BOOT_IMAGE=/boot/vmlinuz-5.2.0-42-generic root=/dev/sda1 ro vt.handoff=7

所以我正在使用内核vmlinuz-5.2.0-42-generic。
我们需要运行带有initrd的内核,只是为了拥有一个最小的环境来检查地址。我使用与我的内核对应的initrd,使模拟更加真实,但实际上这并不重要。我还使用正在运行的内核的内核命令行,以尽可能接近正在运行的内核,并在内核命令行中添加break=top,以尽快进入shell。
接下来,我运行qemu虚拟机:
sudo qemu-system-x86_64 -m 1024 -kernel /boot/vmlinuz-5.2.0-42-generic \
  -append "$(cat /proc/cmdline) break=top" -initrd /boot/initrd.img-5.2.0-42-generic

当我进入shell时,我会得到加载的模块地址:
(initramfs) cat /proc/modules
usbhid 57344 0 - Live 0xffffffffc0269000
hid 131072 1 usbhid, Live 0xffffffffc0248000

现在我将检查运行系统,看看一个模块的模块地址是否不同:
sudo grep hid /proc/modules 
hid_generic 16384 0 - Live 0xffffffffc20ac000
usbhid 57344 0 - Live 0xffffffffc1a09000
hid 131072 2 hid_generic,usbhid, Live 0xffffffffc1c96000

所以我们可以看到这里的hid模块的地址是不同的(运行时:0xffffffffc1c96000和虚拟:0xffffffffc0248000)。所以我的内核正在运行kASLR,尽管它没有在内核命令行上(现在某些发行版默认开启)。
您可以在另一个qemu实例中再次检查模块地址,并验证每次运行qemu时模块地址的变化。此外,您可以将nokaslr添加到内核命令行(-append qemu选项),并运行qemu几次以验证模块地址不会改变。