第一章:异架构Pwn概述——ARM/MIPS/RISC-V的内存与堆管理差异
各位好,我是老周。做二进制安全有些年头了,从x86/x64平台一路摸爬滚打过来。几年前我第一次接触ARM架构的堆漏洞时,说实话,有点懵。malloc的行为、chunk的结构、甚至内存对齐方式,都跟x86上那套不太一样。后来做MIPS路由器的漏洞利用,又踩了不少坑。嗯,今天这第一节课,我就把这几年的经验梳理一下,帮你快速建立起异架构堆利用的知识框架。
核心观点:异架构Pwn的核心挑战不在于漏洞本身,而在于理解目标架构的内存管理模型和堆管理器实现。你只有摸清了这些底层差异,才能写出稳定、通用的利用代码。
1.1 为什么异架构堆利用这么“别扭”?
说白了,堆利用的本质是操纵内存分配器的元数据或空闲链表,实现任意地址写或代码执行。但不同架构对内存的“看法”不一样:
- 地址空间布局:ARM64的用户空间地址范围、MIPS的kseg0/kseg1映射、RISC-V的S-mode与U-mode隔离,这些直接影响你的payload布局。
- 字节序:ARM和RISC-V默认小端,MIPS大端居多。你写一个堆溢出payload,字节序错了,整个利用链就断了。
- 对齐要求:ARM要求4字节对齐,MIPS要求更严格(某些指令要求8字节对齐),RISC-V相对宽松但也不建议乱来。
我的经验:有一次我在MIPS路由器上做fastbin attack,payload里一个地址没对齐,结果malloc直接返回NULL,调试了一整天才发现是地址末尾多了个0x1。从那以后,我写任何异架构利用代码,第一件事就是检查对齐。
1.2 三大架构的内存管理差异
我们直接看一张对比表,把关键差异列出来:
| 特性 | ARM (AArch64) | MIPS (32/64) | RISC-V (64) |
|---|---|---|---|
| 页大小 | 4KB / 64KB | 4KB / 16KB | 4KB (常见) |
| 用户空间范围 | 0x0000_0000_0000_0000 ~ 0x0000_FFFF_FFFF_FFFF | 0x0000_0000 ~ 0x7FFF_FFFF (32位) | 0x0000_0000_0000_0000 ~ 0x0000_7FFF_FFFF_FFFF |
| 字节序 | 小端 (可切换) | 大端 (常见) | 小端 |
| 对齐要求 | 4字节 (严格) | 4/8字节 (严格) | 2/4/8字节 (建议) |
| 缓存一致性 | 硬件维护 | 软件维护 (cache指令) | 硬件维护 (FENCE) |
你想想看,MIPS的缓存一致性需要软件手动刷cache,这在堆利用里是个大坑。我曾经在MIPS上做堆溢出,写进去的shellcode死活不执行,最后发现是icache没刷新。嗯,这个后面讲MIPS章节时会细说。
1.3 堆管理器设计差异:dlmalloc、jemalloc、musl
不同架构上跑的堆管理器也不一样。我整理了一张图,帮你快速理解它们的核心设计:
注意:不要以为学会了glibc的堆利用就能直接套用到异架构上。dlmalloc虽然和glibc早期版本类似,但chunk结构、bin管理、合并策略都有细微差别。jemalloc更是完全不同的设计哲学——它用红黑树管理空闲块,没有fastbin的概念。musl则极其简单,简单到有些利用手法反而失效了。
1.4 交叉编译环境搭建
好了,理论说完了,我们来点实际的。没有环境,一切都是纸上谈兵。我个人习惯用Docker来管理交叉编译环境,干净、可复现、不污染宿主机。
下面是我常用的一个环境搭建流程:
- 安装Docker(如果你还没装的话)
- 拉取基础镜像:
docker pull ubuntu:22.04 - 安装交叉编译工具链:
# ARM64 (aarch64)
apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
# MIPS (大端)
apt install gcc-mips-linux-gnu g++-mips-linux-gnu
# RISC-V (64位)
apt install gcc-riscv64-linux-gnu g++-riscv64-linux-gnu
我的建议:不要一股脑全装。先确定你要研究哪个架构,只装对应的工具链。我刚开始时把三个都装了,结果编译时经常搞混目标架构,链接器报错半天才发现是用了错误的gcc。
验证环境是否正常:
# 编译一个简单的测试程序
aarch64-linux-gnu-gcc -o test_arm test.c
file test_arm
# 输出: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV)
mips-linux-gnu-gcc -o test_mips test.c
file test_mips
# 输出: ELF 32-bit MSB executable, MIPS, MIPS32 version 1 (SYSV)
riscv64-linux-gnu-gcc -o test_riscv test.c
file test_riscv
# 输出: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV)
看到这些输出,说明环境搭好了。嗯,这里要注意:MIPS默认是大端,如果你需要小端MIPS,要用 mipsel-linux-gnu-gcc。
1.5 第一个异架构堆利用练习
光说不练假把式。我准备了一个简单的ARM64堆溢出示例,你可以先感受一下:
// vuln.c - ARM64堆溢出示例
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main() {
char *a = malloc(64);
char *b = malloc(64);
// 堆溢出:写入超过64字节
memset(a, 'A', 128);
// 检查b是否被破坏
printf("b = %s\n", b);
free(a);
free(b);
return 0;
}
用交叉编译器编译:
aarch64-linux-gnu-gcc -o vuln_arm vuln.c
然后在QEMU用户模式下运行:
qemu-aarch64 ./vuln_arm
思考题:在ARM64的dlmalloc实现中,chunk的size字段和prev_size字段分别占多少字节?溢出后,b的chunk头会被如何覆盖?这个问题的答案,将直接影响你后续的利用手法选择。
1.6 本章小结
这一章我们主要做了三件事:
- 理解了ARM/MIPS/RISC-V在内存管理上的核心差异(页大小、地址空间、字节序、对齐)
- 对比了dlmalloc、jemalloc、musl三种堆管理器的设计哲学和利用切入点
- 搭建了交叉编译环境,并跑通了第一个ARM64堆溢出示例
说实话,异架构堆利用的门槛确实比x86高一些。但只要你把底层差异吃透,把堆管理器的数据结构摸清楚,剩下的就是经验积累和耐心调试了。我在后续章节会逐一深入每个架构的堆管理器实现,并配合实际漏洞案例讲解利用手法。
最后提醒:不要跳过环境搭建这一步。我见过太多人直接看理论,结果自己动手时连编译都过不了。花半小时把环境配好,后面会省很多时间。