在这篇文章中,我将讨论 Samuel Groß 在他的 Remote iPhone Exploitation Part 2: Bringing Light into the Darkness -- a Remote ASLR Bypass 中描述的技术在 Linux x86_64 上绕过 ASLR 的应用。
为了展示这一点,我将解决来自 Buckeye CTF 的一个 pwnable 挑战,即 guess_god。
我会尽量让内容对初学者友好,如果你觉得自己已经足够自信,或者只想直接看漏洞利用,可以随意跳过任何部分。

我没有参加这个 CTF,但在 CTF 结束前大约 2 小时,由于 Guray00 在 fibonhack 的 discord 上请求帮助解决一些加密相关的把戏,我因此对这个挑战产生了兴趣。
我帮不上他的忙,但我看了一下 pwnable 挑战,觉得弄懂 P0 博客文章会是一件好事,并希望能拿到那份赏金。
地址空间布局随机化(ASLR)是一种计算机安全技术,它涉及在进程的地址空间中随机定位可执行文件的基础地址以及库、堆和栈的位置。
在 Linux 上,你可以通过读取 /proc/<pid>/maps 文件,借助 procfs 来检查给定 pid 进程的内存映射。
如果你是一个进程,并且想了解自己的内存映射,可以读取 /proc/self/maps。
例如,你可以尝试用 cat 读取 /proc/self/maps:```
root@088ec31b2ce9:/home/ctf/challenge# cat /proc/self/maps
55faeb01c000-55faeb01e000 r--p 00000000 fe:01 2497233 /usr/bin/cat
55faeb01e000-55faeb023000 r-xp 00002000 fe:01 2497233 /usr/bin/cat
55faeb023000-55faeb026000 r--p 00007000 fe:01 2497233 /usr/bin/cat
55faeb026000-55faeb027000 r--p 00009000 fe:01 2497233 /usr/bin/cat
55faeb027000-55faeb028000 rw-p 0000a000 fe:01 2497233 /usr/bin/cat
55faeb115000-55faeb136000 rw-p 00000000 00:00 0 [heap]
7fe15dfb1000-7fe15dfd5000 rw-p 00000000 00:00 0
7fe15dfd5000-7fe15dffb000 r--p 00000000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so
7fe15dffb000-7fe15e166000 r-xp 00026000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so
7fe15e166000-7fe15e1b2000 r--p 00191000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so
7fe15e1b2000-7fe15e1b5000 r--p 001dc000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so
7fe15e1b5000-7fe15e1b8000 rw-p 001df000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so
7fe15e1b8000-7fe15e1c3000 rw-p 00000000 00:00 0
7fe15e1c7000-7fe15e1c8000 r--p 00000000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so
7fe15e1c8000-7fe15e1ef000 r-xp 00001000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so
7fe15e1ef000-7fe15e1f9000 r--p 00028000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so
7fe15e1f9000-7fe15e1fb000 r--p 00031000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so
7fe15e1fb000-7fe15e1fd000 rw-p 00033000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so
7fff4388f000-7fff438b0000 rw-p 00000000 00:00 0 [stack]
7fff43989000-7fff4398d000 r--p 00000000 00:00 0 [vvar]
7fff4398d000-7fff4398f000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
root@088ec31b2ce9:/home/ctf/challenge# cat /proc/self/maps 55ffc0b1b000-55ffc0b1d000 r--p 00000000 fe:01 2497233 /usr/bin/cat 55ffc0b1d000-55ffc0b22000 r-xp 00002000 fe:01 2497233 /usr/bin/cat 55ffc0b22000-55ffc0b25000 r--p 00007000 fe:01 2497233 /usr/bin/cat 55ffc0b25000-55ffc0b26000 r--p 00009000 fe:01 2497233 /usr/bin/cat 55ffc0b26000-55ffc0b27000 rw-p 0000a000 fe:01 2497233 /usr/bin/cat 55ffc2108000-55ffc2129000 rw-p 00000000 00:00 0 [heap] 7f1ec6e0f000-7f1ec6e33000 rw-p 00000000 00:00 0 7f1ec6e33000-7f1ec6e59000 r--p 00000000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec6e59000-7f1ec6fc4000 r-xp 00026000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec6fc4000-7f1ec7010000 r--p 00191000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7010000-7f1ec7013000 r--p 001dc000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7013000-7f1ec7016000 rw-p 001df000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7016000-7f1ec7021000 rw-p 00000000 00:00 0 7f1ec7025000-7f1ec7026000 r--p 00000000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7026000-7f1ec704d000 r-xp 00001000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec704d000-7f1ec7057000 r--p 00028000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7057000-7f1ec7059000 r--p 00031000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7059000-7f1ec705b000 rw-p 00033000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7ffc72fa4000-7ffc72fc5000 rw-p 00000000 00:00 0 [stack] 7ffc72fe7000-7ffc72feb000 r--p 00000000 00:00 0 [vvar] 7ffc72feb000-7ffc72fed000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
### 内存映射模式
如果你重复操作几次,就能推断出:
* 二进制文件的 PIE 基地址应该位于 0x00005500_00000000-0x00005700_00000000 范围内,这意味着有 2TB 个可能的地址。
* 堆位于二进制文件附近。
* 库位于 0x00007f00_00000000 - 0x00007fff_ffffffff 范围内,有 1TB 个可能的地址。
* 栈 \(大部分情况下\) 位于 0x00007ffc_00000000 - 0x00007fff_ffffffff 范围内,有 16gb 个可能的地址。
* 范围 0xffffffffff600000 - 0xffffffffff601000 总是被映射,如果你对它是什么感到好奇,可以阅读[这篇文章](http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2019/02/13/vsyscall-and-vdso)。
## 1.3 如何在没有信息泄露的情况下绕过 ASLR
让我们讨论一下,当无法进行信息泄露时,你可以用什么方法来绕过 ASLR。
以下是我尝试对阅读 Saelo 博客文章后的收获所做的总结。
要绕过 ASLR,你需要:
* 一种内存喷射技术,它允许你在给定的地址范围内映射指定大小的连续内存。
正如他所说,有两种方式可以做到:
1. 滥用内存泄漏(不是信息泄露!)——一种内存块被“遗忘”而从未释放的 bug——并多次触发它,直到泄漏出所需数量的内存。
2. 寻找并滥用“amplification gadget”(放大小工具):一段获取现有数据块并复制它(可能多次)的代码,从而使攻击者只需发送相对较少的字节就能喷射大量内存。
* 一个 `isAddressMapped` oracle,给定一个地址,它会告诉你该地址是否已被映射。
### Linux 上绕过 ASLR 的 PoC
让我们尝试复现 saelo 的 PoC,以在 Linux 上彻底打破 aslr。
<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/93fcc6aff8ffd2e8395f34b35bc2f5d244e74a53b2fac39c3dae35c802227339.png" > <i> saelo 的 poc</i> <p/> <br/>
在 Linux 上这并不容易,只有当你能够分配 16TB 内存时,才有可能完全打破 ASLR。```C
#include <stdio.h>
#include <stdlib.h>
int main()
{
// 64gb
size_t size = 0x1000000000;
// 16TB allocations
for (int i = 0; i < 256; i++) {
void *mem = malloc(size); // this ends up calling mmap
if (!mem) {
puts("Failed");
return 1;
}
printf("%p\n", mem);
}
unsigned int *mem = (void*)0x7f0000000000ULL;
*mem = 0x41414141;
printf("R/W to %p: %x\n", mem, *mem);
return 0;
}
摘自 man malloc 的说明:
因此 void *mem = malloc(size) 最终会调用 mmap(size + malloc_metadata_size, ...)
由于库是通过 ld 以 mmap 方式映射进进程的,这些分配最终会落在库附近。
如果你查看 malloc 返回的地址,就能更好地理解发生了什么。小贴士:关注最高有效字节。
| mem | 跨越 16TB 边界? |
|---|---|
| 0x7fb03b55e010 | 否 |
| 0x7fa03b55d010 | 否 |
| 0x7f903b55c010 | 否 |
| 0x7f803b55b010 | 否 |
| 0x7f703b55a010 | 否 |
| 0x7f603b559010 | 否 |
| 0x7f503b558010 | 否 |
| 0x7f403b557010 | 否 |
| 0x7f303b556010 | 否 |
| 0x7f203b555010 | 否 |
| 0x7f103b554010 | 否 |
| 0x7f003b553010 | 否 |
| 0x7ef03b552010 | 是 |
| 0x7ee03b551010 | 是 |
| 0x7ed03b550010 | 是 |
| 0x7ec03b54f010 | 是 |