type
Post
status
Published
date
Jul 10, 2026
slug
summary
tags
安卓
逆向
安全
unicorn
category
日志记录
icon
password
1. 执行层级与运行身份不一致
1.1. 简介
普通 Android native 代码运行在 EL0:
这两个寄存器都要求至少 EL1 权限。正常情况下:
- CPU 执行
MRS。
- 产生未定义指令异常。
- Linux 检查是否属于允许向用户态模拟的系统寄存器。
CurrentEL、SCTLR_EL1不在允许的用户态 ID 寄存器范围中。
- 最终进程收到
SIGILL。
Linux 确实会为部分 ID 寄存器模拟
MRS,例如 MIDR_EL1、ID_AA64ISAR0_EL1,所以“某个 EL1 命名的寄存器可读”不能直接判定异常;但未开放的系统寄存器会导致 SIGILL。Linux ARM64 CPU feature registers因此要特别选择:
而不能仅仅选择
MIDR_EL1。1.2. 原理
本地我使用的是 Unicorn
2.1.4,对应提交: 8028ec436f2d9376525352dd38ed9ed6b9f6be10Unicorn 创建 ARM64 CPU 时,如果没有主动设置 CPU model,会选择 A72:
cpu64.c
unicorn-engine
随后 Unicorn 又直接把 PSTATE 设置成:
cpu64.c
unicorn-engine
PSTATE_MODE_EL1h 的编码是:计算当前 EL 时,Unicorn 提取 PSTATE 的
[3:2]:cpu.h
unicorn-engine
1.3. 检测代码
探测覆盖这几个点:
- CurrentEL 指令居然成功返回
- CurrentEL 表示 EL1/EL2/EL3
- SCTLR_EL1 也能成功读取
实际探测结果:
2. CPU 静态元组指纹
核心: 一次读取多个描述 CPU 身份、指令集、内存系统和缓存结构的寄存器
2.1. Unicorn 2.1.4 的硬编码元组
模型 | MIDR_EL1 | ISAR0_EL1 | MMFR0_EL1 | CTR_EL0 |
A53 | 0x410fd034 | 0x00011120 | 0x00001122 | 0x84448004 |
A57 | 0x411fd070 | 0x00011120 | 0x00001124 | 0x8444c004 |
A72 | 0x410fd083 | 0x00011120 | 0x00001124 | 0x8444c004 |
2.2.MIDR_EL1 怎么解码
拆解结果:
所以单看 MIDR,它确实自称一颗 Cortex-A72。
问题是:真实 Cortex-A72 设备也可能返回这个 MIDR。因此
MIDR_EL1 == 0x410fd083 单独只能算弱证据。3. CPU 与 Android/Linux 跨层一致性
Unicorn 可以模拟 ARM64 指令,也可以让程序读取一组看起来真实的 CPU 寄存器。但一个 Android 进程看到的运行环境不只有 CPU,还包括 Linux 内核提供的用户态能力信息。
因此,我们可以询问同一个问题两次:
这台 CPU 支持哪些指令?第一次询问 CPU 特征寄存器,第二次询问 Linux。若答案互相矛盾,说明 CPU 层和操作系统层很可能不是由同一套真实环境产生的。
3.1. CPU 给出的答案
ARM64 使用
ID_AA64ISAR0_EL1 描述部分指令集能力。Unicorn 的默认 Cortex-A72 模型将它固定为:这个值表示 CPU 支持:
如果只看 CPU 层,这是一颗能力正常的 Cortex-A72。
3.2. Linux 给出的答案
Linux 不要求普通应用自己判断每颗 CPU。它会在启动过程中汇总系统里所有 CPU 的能力,然后生成一个适合用户态使用的能力位图,称为 HWCAP。
其中:
所以前面五种能力对应:
当 Linux 创建一个新进程时,会把 HWCAP 放进进程的 auxiliary vector,简称 auxv:
auxv 是 Linux 在
execve() 阶段交给新进程的一张启动信息表。页面大小、程序入口、随机数地址和 CPU 能力都保存在这里。同一份 auxv 还会保存在进程的:
因此,读取:
得到的就是 Linux 创建当前进程时生成的启动信息。
3.3. 源码索引
base.c
torvalds
4. 独占监视行为异常
前面的检测主要观察寄存器值。来一点巧思, 不再检查静态数据,而是观察一段指令跨越系统调用之后,CPU 内部状态是否发生了正确变化。检测序列是:
它可以判断
SVC 是否真的经历了 ARM 异常进入和异常返回。4.1. 独占监视器是什么
ARM64 使用
LDXR/STXR 实现原子操作。执行
LDXR 时,CPU不仅从 [x0] 读取数据,还会在内部记录:这个内部状态称为 Local Exclusive Monitor,局部独占监视器。随后执行
STXR:最简单的情况:
中间没有发生任何事情,通常得到:
如果主动清除独占状态:
则必然得到:
这组
CLREX 序列可以作为对照实验。4.2. 系统调用为什么会影响独占状态
Android 应用运行在 EL0。执行:
会产生同步异常,CPU进入 EL1,Linux 处理系统调用,最后执行
ERET 返回 EL0。完整路径是:ARM 异常返回会清除局部独占监视器。因此:
在真实 Android/Linux 上,
STXR 应该失败:Linux 返回用户态的最终指令确实是
ERET:entry.S
torvalds
4.3. Unicorn 哪里出了差异
nicorn 对
LDXR 的实现,会把独占地址保存到:核心代码:
translate-a64.c
unicorn-engine
执行
STXR 时,Unicorn先比较当前地址和保存的独占地址:成功返回
0,失败返回 1:问题出现在
SVC。Unicorn 将 SVC 转换为:源码:SVC 翻译
随后调用使用者注册的:
典型 Python 模拟器会在 hook 里:
- 读取
x8,取得系统调用号。
- 计算返回值。
- 把返回值写入
x0。
- 从
SVC后面继续执行。
Unicorn 的处理代码在 hook 返回后直接清除异常编号:
这里没有真正进入 EL1,没有运行 Linux 异常处理程序,也没有执行
ERET。因此独占状态可能仍然存在:
4.4. 为什么问题在异常返回?
Unicorn/QEMU 本身其实知道
ERET 应该清除独占状态。它实现异常返回时会调用:helper-a64.c
unicorn-engine
清除操作本身就是:
internals.h
unicorn-engine
所以问题不是 Unicorn 不会处理独占监视器,而是简单的 syscall hook 绕过了完整的异常返回路径。
4.5. 检测代码
这里选择无副作用的
getpid:在 Unicorn 2.1.4 的 A53、A57、A72 和 max 模型中均得到:
