前言
ASLR(Address Space Layout Randomization,地址空间随机化)是一种内存攻击缓解技术,是一种操作系统用来抵御缓冲区溢出攻击的内存保护机制。这种技术使得系统上运行的进程的内存地址无法被预测,使得与这些进程有关的漏洞变得更加难以利用。
它最早于 2001 年出现在 PaX 项目中,于 2005 年正式成为 Linux 的一部分,如今已经广泛使用在各类操作系统中。
在 Linux 上,ASLR 的全局配置 /proc/sys/kernel/randomize_va_space
有三种情况:
0 表示关闭 ASLR;
1 表示部分开启(将 mmap 的基址、stack 和 vdso 页面随机化);
2 表示完全开启(在部分开启的基础上增加 heap 的随机化)
实例
环境:ARM Linux 32bit,qemu
test.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main()
{
int stack;
int *heap;
void *libc;
heap = malloc(sizeof(int));
libc = dlopen("libc.so.6", RTLD_NOW | RTLD_GLOBAL);
printf("executable : %p\n", &main);
printf("system@plt : %p\n", &system);
printf("heap : %p\n", heap);
printf("stack : %p\n", &stack);
printf("1ibc : %p\n", libc);
free(heap);
return 0;
}
Makefile
TARGET=test
CC=/home/liyongjun/project/board/buildroot/Vexpress_2/host/bin/arm-linux-gcc
all:
${CC} ${TARGET}.c -o ${TARGET}.out -Wall -no-pie
cp:
cp ${TARGET}.out ~/tftp/
clean:
rm *.out
注意,编译时使用 -no-pie
选项关闭了 PIE,因为开启 PIE 会影响 heap 随机化(原因未追查)。
测试
从 host 机下载 test.out 到 ARM Linux 系统,然后针对 ASLR 不同配置,进行测试对比
一、关闭 ASLR
# tftp -gr test.out 192.168.31.223
# echo 0 > /proc/sys/kernel/randomize_va_space
# ./test.out
executable : 0x105ac
system@plt : 0x10484
heap : 0x13190
stack : 0x7efffcf0
1ibc : 0x76fd7380
# ./test.out
executable : 0x105ac
system@plt : 0x10484
heap : 0x13190
stack : 0x7efffcf0
1ibc : 0x76fd7380
发现两次执行 test.out,程序执行时所有地址都是不变化的
二、部分开启 ASLR
# echo 1 > /proc/sys/kernel/randomize_va_space
# ./test.out
executable : 0x105ac
system@plt : 0x10484
heap : 0x13190
stack : 0x7ed35cf0
1ibc : 0x76f68380
# ./test.out
executable : 0x105ac
system@plt : 0x10484
heap : 0x13190
stack : 0x7e81ccf0
1ibc : 0x76ee1380
两次执行 test.out,只有 stack、动态库地址随机化
三、完全开启 ASLR
# echo 2 > /proc/sys/kernel/randomize_va_space
# ./test.out
executable : 0x105ac
system@plt : 0x10484
heap : 0x1828190
stack : 0x7ec97cf0
1ibc : 0x76f4d380
# ./test.out
executable : 0x105ac
system@plt : 0x10484
heap : 0x1724190
stack : 0x7ec41cf0
1ibc : 0x76efe380
增加了 heap 地址随机化
PIE
由于 ASLR 是一种操作系统层面的技术,而二进制程序本身是不支持地址随机化加载的,便出现了一些绕过方式,例如 ret2plt、GOT 劫持、地址爆破等。于是,在 2003 年引入了位置无关可执行文件(Position-Independent Executable, PIE),它在应用层的编译器上实现,通过将程序编译为位置无关代码(Position-Independent Code, PIC),使程序可以被加载到任意位置运行,就像是一个特殊的共享库。
在 PIE 和 ASLR 同时开启的情况下,攻击者将对程序的内存布局一无所知,大大增加了利用难度。当然凡事有利也有弊,在增加安全性的同时,PIE 也会一定程度上影响性能,因此在大多数操作系统上 PIE 仅用于一些对安全性要求比较高的程序。
gcc 支持的 PIE 选项如下所示,-fpie 是代码生成选项,其生成的位置无关代码可以被 -pie 选项链接到可执行文件中。
- -fpic,为共享库生成位置无关代码
- -pie,生成动态链接的位置无关可执行文件,通常需要同时指定 -fpie
- -no-pie,不生成动态链接的位置无关可执行文件
- -fpie,类似于 -fpic,但生成的位置无关代码只能用于可执行文件,通常同时指定 -pie
- -fno-pie,不生成位置无关代码
修改 Makefile 文件
TARGET=test
CC=/home/liyongjun/project/board/buildroot/Vexpress_2/host/bin/arm-linux-gcc
all:
${CC} ${TARGET}.c -o ${TARGET}.out -Wall -pie -fpie
cp:
cp ${TARGET}.out ~/tftp/
clean:
rm *.out
重新编译、下载、执行
# tftp -gr test.out 192.168.31.223
# ./test.out
executable : 0x49c6dc
system@plt : 0x76e8fc40
heap : 0xb7f190
stack : 0x7ee06ce8
1ibc : 0x76f90380
# ./test.out
executable : 0x4436dc
system@plt : 0x76de8c40
heap : 0x17db190
stack : 0x7ea81ce8
1ibc : 0x76ee9380
发现程序运行的所有地址都随机化了
EXEC & DYN
如何查看一个二进制程序是 pie 的,还是 no-pie 的?使用 readelf 工具查看 ELF 文件类型
no-pie:
$ readelf -h test.out | grep Type
Type: EXEC (Executable file)
pie:
$ readelf -h test.out | grep Type
Type: DYN (Shared object file)
关闭 ASLR 的几种方法
在调试程序时,开启 ASLR 会增加调试难度,下面是几种临时关闭 ASLR 的方法
一、修改 /proc/sys/kernel/randomize_va_space
# echo 0 > /proc/sys/kernel/randomize_va_space
# ./test.out
executable : 0x4006dc
system@plt : 0x76ed6c40
heap : 0x403190
stack : 0x7efffce8
1ibc : 0x76fd7380
# ./test.out
executable : 0x4006dc
system@plt : 0x76ed6c40
heap : 0x403190
stack : 0x7efffce8
1ibc : 0x76fd7380
二、使用 setarch 命令
-R 选项即为关闭地址空间随机化
# setarch --help
BusyBox v1.36.1 (2024-01-04 00:19:02 CST) multi-call binary.
Usage: setarch PERSONALITY [-R] PROG ARGS
PERSONALITY may be:
linux32 Set 32bit uname emulation
linux64 Set 64bit uname emulation
-R Disable address space randomization
# setarch linux32 -R ./test.out
executable : 0x4006dc
system@plt : 0x76ed6c40
heap : 0x403190
stack : 0x7efffce8
1ibc : 0x76fd7380
# setarch linux32 -R ./test.out
executable : 0x4006dc
system@plt : 0x76ed6c40
heap : 0x403190
stack : 0x7efffce8
1ibc : 0x76fd7380
三、gdb
在 gdb 场景下,使用 set disable-randomization on
命令关闭地址随机化
不过,很明显,在我的测试环境中,该方案没起作用。(有知道内幕的小伙伴请在评论区告知)
# gdb test.out
GNU gdb (GDB) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "arm-buildroot-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from test.out...
(No debugging symbols found in test.out)
(gdb) set disable-randomization on
(gdb) show disable-randomization
Disabling randomization of debuggee's virtual address space is on.
(gdb) run
Starting program: /root/test.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/libthread_db.so.1".
executable : 0x4c46dc
system@plt : 0x76e9fc40
heap : 0x8e5190
stack : 0x7eee0cc8
1ibc : 0x76fa0380
[Inferior 1 (process 254) exited normally]
(gdb) run
Starting program: /root/test.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/libthread_db.so.1".
executable : 0x4066dc
system@plt : 0x76e9cc40
heap : 0x12aa190
stack : 0x7e8eecc8
1ibc : 0x76f9d380
[Inferior 1 (process 256) exited normally]
(gdb)
总结
ASLR 不负责代码段以及数据段的随机化工作,这项工作由 PIE 负责。但是只有在开启 ASLR 之后,PIE 才会生效。
无论是 ASLR 还是 PIE,由于颗粒度问题,被随机化的都只是某个对象的起始地址,而在该对象的内部依然保持原来的结构,也就是说相对偏移是不会变的。
附录
Documentation/admin-guide/sysctl/kernel.rst
randomize_va_space
==================
This option can be used to select the type of process address
space randomization that is used in the system, for architectures
that support this feature.
== ===========================================================================
0 Turn the process address space randomization off. This is the
default for architectures that do not support this feature anyways,
and kernels that are booted with the "norandmaps" parameter.
1 Make the addresses of mmap base, stack and VDSO page randomized.
This, among other things, implies that shared libraries will be
loaded to random addresses. Also for PIE-linked binaries, the
location of code start is randomized. This is the default if the
``CONFIG_COMPAT_BRK`` option is enabled.
2 Additionally enable heap randomization. This is the default if
``CONFIG_COMPAT_BRK`` is disabled.
There are a few legacy applications out there (such as some ancient
versions of libc.so.5 from 1996) that assume that brk area starts
just after the end of the code+bss. These applications break when
start of the brk area is randomized. There are however no known
non-legacy applications that would be broken this way, so for most
systems it is safe to choose full randomization.
Systems with ancient and/or broken binaries should be configured
with ``CONFIG_COMPAT_BRK`` enabled, which excludes the heap from process
address space randomization.
== ===========================================================================