第一章:异架构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

不同架构上跑的堆管理器也不一样。我整理了一张图,帮你快速理解它们的核心设计:

三大堆管理器核心设计对比 三大堆管理器核心设计对比 dlmalloc 经典设计,glibc前身 • 隐式空闲链表 • 边界标记 (footers) • 首次适应/最佳适应 • 常见于嵌入式/旧系统 漏洞利用: 溢出覆盖边界标记 jemalloc 现代高性能设计 • 多arena + 线程缓存 • 大小类 (size classes) • 红黑树管理空闲块 • 常见于FreeBSD/Android 漏洞利用: tcache/metadata攻击 musl 轻量级,静态链接友好 • 简单bins + 大块mmap • 无tcache • 空闲链表单向 • 常见于Alpine Linux 漏洞利用: 链表指针篡改 关键洞察 不同堆管理器的元数据布局、空闲链表结构、分配策略差异巨大 同一个漏洞在不同管理器上的利用手法完全不同 ARM: 常用dlmalloc/jemalloc MIPS: 常用dlmalloc RISC-V: 常用musl/jemalloc

注意:不要以为学会了glibc的堆利用就能直接套用到异架构上。dlmalloc虽然和glibc早期版本类似,但chunk结构、bin管理、合并策略都有细微差别。jemalloc更是完全不同的设计哲学——它用红黑树管理空闲块,没有fastbin的概念。musl则极其简单,简单到有些利用手法反而失效了。

1.4 交叉编译环境搭建

好了,理论说完了,我们来点实际的。没有环境,一切都是纸上谈兵。我个人习惯用Docker来管理交叉编译环境,干净、可复现、不污染宿主机。

下面是我常用的一个环境搭建流程:

  1. 安装Docker(如果你还没装的话)
  2. 拉取基础镜像:docker pull ubuntu:22.04
  3. 安装交叉编译工具链:
# 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高一些。但只要你把底层差异吃透,把堆管理器的数据结构摸清楚,剩下的就是经验积累和耐心调试了。我在后续章节会逐一深入每个架构的堆管理器实现,并配合实际漏洞案例讲解利用手法。

最后提醒:不要跳过环境搭建这一步。我见过太多人直接看理论,结果自己动手时连编译都过不了。花半小时把环境配好,后面会省很多时间。