1. 课程导入与学习路径
做逆向分析,第一件事是什么?
我个人的答案是:搞懂CPU在干什么。你连寄存器都认不全,怎么跟反汇编代码打交道?
ARM64,或者说AArch64,现在几乎统治了移动端、服务器端,甚至苹果的桌面端。咱们这一章就把地基打牢。别急,我不会给你念手册,咱们聊点实在的。
1.1 ARM64发展史:为什么是它?
ARM架构最早是1985年Acorn电脑搞出来的,当时叫Acorn RISC Machine。后来改名叫Advanced RISC Machine。早期是ARMv4、ARMv5,那时候还是32位。
真正的大转折是2011年。ARM发布了ARMv8架构,第一次引入了64位指令集。这就是咱们说的ARM64。苹果是第一个吃螃蟹的,2013年的A7芯片就上了ARM64。我当时还在做嵌入式,看到这个消息就知道——时代变了。
| 架构版本 | 发布时间 | 位宽 | 关键特性 |
|---|---|---|---|
| ARMv7 | 2005 | 32位 | Cortex-A系列,Thumb-2 |
| ARMv8-A | 2011 | 32/64位 | AArch64执行状态,新指令集 |
| ARMv8.1-8.5 | 2014-2018 | 64位 | 原子指令,RAS扩展,预测性分支 |
| ARMv9 | 2021 | 64位 | CCA机密计算,SVE2 |
关键认知:ARM64不是ARM32的简单升级。它是全新的执行状态,指令编码、寻址模式、异常模型全都重写了。你拿ARM32的经验直接套,会踩坑。
1.2 ARM32 vs ARM64:核心差异
很多刚转过来的朋友会问:不就是32位变64位吗?地址宽了,寄存器多了,还有啥?
嗯,远不止这些。我列几个你在逆向时一定会碰到的差异:
- 指令长度固定:ARM32有ARM(4字节)和Thumb(2字节)两种模式,切换起来很烦。ARM64统一4字节,解码简单多了。
- 寄存器翻倍:通用寄存器从16个(ARM32)变成31个(ARM64)。PC和SP不再是通用寄存器。
- 条件执行砍掉了:ARM32几乎每条指令都能加条件后缀(EQ/NE/GT...)。ARM64只有少数分支指令支持条件执行。为什么?为了简化流水线。
- 寻址模式更规整:ARM64的寻址模式比ARM32少,但更一致。写反汇编脚本时你会感谢这个设计。
- 异常模型重构:ARM64的异常等级(EL0-EL3)比ARM32的处理器模式清晰得多。
避坑指南:我曾经在逆向一个iOS应用时,看到函数开头是 STP X29, X30, [SP,#-0x10]!,下意识以为是ARM32的PUSH。不对。ARM64没有PUSH/POP指令,栈操作全靠STP/LDP。这个思维转换很重要。
1.3 AArch64执行状态
ARMv8架构支持两种执行状态:AArch64和AArch32。咱们这门课只关注AArch64。
AArch64有几个特点:
- 地址空间:支持64位虚拟地址,但实际实现通常只用48位或52位。高16位必须是符号扩展,否则CPU会抛异常。
- 异常等级(Exception Levels):EL0(用户态)、EL1(内核态)、EL2(虚拟化)、EL3(安全监控)。每个等级有自己的页表、栈指针和系统寄存器。
- 执行状态切换:AArch64和AArch32可以在异常等级之间切换。比如EL1跑64位内核,EL0跑32位应用。但同一个异常等级内不能混用。
调试技巧:看异常等级最简单的方法是检查 CurrentEL 系统寄存器。我在做内核调试时经常用它确认当前在哪个特权级。
1.4 寄存器模型
寄存器是逆向分析的第一手资料。你看到的每条指令都在操作它们。咱们一个一个来。
1.4.1 通用寄存器
AArch64有31个通用寄存器,编号X0到X30。每个都是64位宽。访问低32位时用W0到W30。
| 寄存器 | 别名/用途 | 调用约定(AAPCS64) |
|---|---|---|
| X0-X7 | 参数寄存器 | 传递参数,X0也用于返回值 |
| X8 | 间接结果寄存器 | 返回大结构体时用 |
| X9-X15 | 临时寄存器 | 调用者保存 |
| X16-X17 | IP0, IP1 | 过程内调用临时寄存器 |
| X18 | 平台寄存器 | OS保留,应用层别碰 |
| X19-X28 | 被调用者保存 | 函数内必须恢复原值 |
| X29 | FP(帧指针) | 指向当前栈帧底部 |
| X30 | LR(链接寄存器) | 保存函数返回地址 |
| SP | 栈指针 | 独立寄存器,有专用指令操作 |
| PC | 程序计数器 | 不能直接读写,只能通过分支指令修改 |
实战要点:逆向时看到 MOV X0, XZR,就是把0写入X0。XZR是零寄存器,读出来永远是0,写进去被丢弃。ARM32没有这个,很多人第一次看到会懵。
1.4.2 SIMD与浮点寄存器
ARM64有32个128位的SIMD寄存器,叫V0到V31。它们可以当作不同宽度的向量用:
- B:8位(字节)
- H:16位(半字)
- S:32位(单精度浮点)
- D:64位(双精度浮点)
- Q:128位(四字)
举个例子:ADD V0.4S, V1.4S, V2.4S 意思是把V1和V2的四个32位整数分别相加,结果存到V0。
个人经验:做多媒体逆向时,SIMD寄存器是重点。我曾经分析过一个视频编解码库,核心算法全是用NEON指令写的。看不懂V寄存器,就等于没看。
1.4.3 系统寄存器
系统寄存器是ARM64的“控制面板”。它们控制MMU、中断、调试、性能监控等核心功能。访问系统寄存器只能用专用的 MRS(读)和 MSR(写)指令。
常用的系统寄存器:
| 寄存器 | 用途 | 访问等级 |
|---|---|---|
| CurrentEL | 当前异常等级 | EL1及以上 |
| DAIF | 中断屏蔽位 | EL1及以上 |
| TPIDR_EL0 | 线程指针 | EL0可读 |
| VBAR_EL1 | 异常向量表基址 | EL1 |
| PAR_EL1 | 物理地址寄存器 | EL1 |
| PMCCNTR_EL0 | 性能计数器 | EL0 |
注意:系统寄存器的命名有规律:寄存器名_ELx 表示最低访问等级。比如 TPIDR_EL0 在EL0就能读,但 VBAR_EL1 必须在EL1才能写。逆向内核代码时,看到 MSR VBAR_EL1, X0,就知道这是在设置异常向量表。
1.5 知识结构总览
下面这张图把本章的核心知识点串起来了。你可以把它当作后续调试时的快速参考。
1.6 本章小结
ARM64不是ARM32的简单升级。它是全新的架构,有自己的一套规则。做逆向分析,你得先熟悉这些规则:
- 知道寄存器怎么用,才能看懂反汇编代码
- 知道异常等级,才能理解内核和应用的分界
- 知道系统寄存器,才能调试底层问题
我见过太多人一上来就追着反汇编代码看,结果连X29是帧指针都不知道。嗯,先把基础打牢,后面咱们再深入异常处理、性能调优和调试技巧。