ARM64 指令集逆向深度实操:移动端 Native SO 核心算法逆向与还原 在现代移动应用安全与业务攻防对抗中无论是电商 App 的双 11 秒杀请求签名算法、风控设备指纹采集 SDK还是高密支付模块开发团队为了防范逆向与篡改早已不再将核心逻辑留在易于反编译为 Java 代码的 DEX/Smali 层。几乎所有的商业核心资产均被全面下沉至通过 C/C 编写、采用 Clang 交叉编译为ARM64AArch64架构的底层原生动态链接库.so文件。与传统的 x86-64 架构不同ARM64 属于典型的精简指令集RISC其拥有整整 31 个 64 位通用寄存器X0-X30、严格对齐的 4 字节定长指令、极为独特的桶形移位运算Barrel Shifter以及丰富的按位提取与融合乘加指令。面对一个去除了符号表、混淆了函数名、且通过动态注册 JNI 函数调用的 ARM64 样本初级逆向分析人员往往容易在密集的位移操作与非线性寄存器流转中迷失方向。本文立足实战视角系统剖析面向 Android 移动端 Native SO 的逆向全流程从 JNI 动态注册定位、ARM64 标志性数学指令解密到动静结合提取核心算法并手写 Python 脚本无损还原。一、 突破第一道关卡快速定位 JNI 真实 Native 函数在 Android 系统中Java 层调用底层 C/C 代码有两种方式静态绑定与动态注册。现代商业 SO 库 95% 以上均采用动态注册机制以隐匿真实函数名。1. 动态注册的生命周期中枢JNI_OnLoad当 Java 代码执行System.loadLibrary(sec_core)时Android 运行时虚拟机ART会自动在加载该.so时第一时间调用其导出的JNI_OnLoad函数。在 IDA Pro 中打开目标 SO 库直接按下G键跳转至导出表中的JNI_OnLoad函数// IDA F5 反编译后的 JNI_OnLoad 经典逻辑 jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env NULL; if ((*vm)-GetEnv(vm, (void**)env, JNI_VERSION_1_6) ! JNI_OK) { return JNI_ERR; } // 寻找目标 Java 类 jclass clazz (*env)-FindClass(env, com/sec/mall/security/SignEngine); // 关键调用: RegisterNatives 动态注册原生方法映射表 (*env)-RegisterNatives(env, clazz, gMethods, 2); return JNI_VERSION_1_6; }2. 解剖JNINativeMethod结构体数组RegisterNatives的第三个参数gMethods是一个指向结构体数组的指针。每个结构体占用 24 个字节在 64 位系统下包含 3 个 8 字节指针typedef struct { const char* name; // Java 端声明的原生方法名 (如 computeSign) const char* signature; // 方法签名 (如 (Ljava/lang/String;[B)Ljava/lang/String;) void* fnPtr; // 核心目标! 底层真实的 C 语言实现函数物理虚拟内存地址 } JNINativeMethod;双击跟进gMethods内存地址我们可以在只读数据段.rodata中直接提取出第三个字段指向的真实函数地址例如0x1A420瞬间绕开所有外部混淆直捣黄龙锁定真实算法的核心入口点。二、 ARM64 算法核心指令集的特征解密进入算法内部后我们需要识别一系列由编译器优化生成的、极具 RISC 特色的指令组合。1. 桶形移位运算Barrel Shifter的单指令融合在 ARM64 中算术指令如add,sub,eor可以在执行计算的同时对第二个操作数进行硬件级的“免费位移”eor x0, x1, x2, lsl #3 # 将 X2 逻辑左移 3 位后再与 X1 执行异或运算结果存入 X0 add w3, w4, w5, lsr #5 # 将 W5 逻辑右移 5 位后与 W4 相加存入 W3在 x86-64 下需要 3 条指令完成的位操作ARM64 仅需 1 条指令即可完成。这在自定义加密、哈希摘要循环如 MD5 / SHA-256 变异算法中是绝对的高频特征。2. 位提取与合并指令UBFX 与 EXTRubfx Xd, Xn, #lsb, #width无符号位段提取从寄存器Xn中从第lsb位开始提取width个二进制位并将其右对齐零扩展存入Xd。常用于快速提取密钥掩码extr Xd, Xn, Xm, #lsb从两个寄存器串联中提取位段将Xn与Xm串联成 128 位寄存器并右移lsb位提取低 64 位。这是 ARM64 实现循环移位Rotate Left/Right的终极优化指令。3. 乘加融合指令MADD 与 MSUBmadd x0, x1, x2, x3 # 计算: X0 (X1 * X2) X3 msub w0, w1, w2, w3 # 计算: W0 W3 - (W1 * W2)在加密算法的伪随机数线性同余生成器LCG如 $X_{n1} (aX_n c) \bmod m$或大数乘法中无处不在。三、 动静协同利用 Frida 动态 Hook 剥离算法边界静态分析 ARM64 汇编虽然严谨但面对数千行的复杂数学变换盲目硬啃容易耗费巨量时间。此时使用Frida挂钩入参和出参能够让我们快速验证算法的“黑盒输入与输出特征”。编写动态追踪脚本// Frida 动态挂钩 Android SO 库的核心签名算法函数 Java.perform(function () { const targetModule Process.findModuleByName(libsecurity.so); // 假设分析出的核心加密算法偏移为 0x1A420 const targetAddress targetModule.base.add(0x1A420); Interceptor.attach(targetAddress, { onEnter: function (args) { console.log(\n[] 成功触发 Native 核心加密算法 ); // JNI 规约: X0 是 JNIEnv*, X1 是 jobject (this), X2 开始才是真实业务参数 const userInputStr Java.vm.getEnv().getStringUtfChars(args[2], null).readCString(); console.log([*] 入参 String (Arg2):, userInputStr); // 打印入参内存 HexDump console.log([*] 原始内存数据:\n hexdump(args[2], { length: 32 })); }, onLeave: function (retval) { const resultStr Java.vm.getEnv().getStringUtfChars(retval, null).readCString(); console.log([] 输出结果 (Retval):, resultStr); } }); });通过这一步我们在真机上发起一次正常的抢购请求瞬间捕获到了“当输入为timestamp1791518400uid502时底层经过算法处理后的十六进制输出为e4a89f310b7c...”。我们拥有了验证还原算法绝对正确性的“金牌基准测试用例Gold Test Vector”。四、 终局决战手写纯 Python 算法脱壳还原在明确了汇编指令与输入输出后我们顺着控制流将一段核心的非标准哈希变换逻辑还原为纯 Python 脚本def arm64_rotate_left(val: int, shift: int, bits: int 32) - int: 模拟 ARM64 的 32 位循环左移操作 return ((val shift) | (val (bits - shift))) ((1 bits) - 1) def recover_native_algorithm(data_bytes: bytes, salt_key: int 0x5A827999) - str: 根据逆向 ARM64 汇编反编译提取的核心位变换与非线性状态机逻辑 # 状态寄存器初始化 (对应汇编中的 W4, W5, W6, W7 初始常量) state [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476] # 将输入数据按 4 字节小端序打包处理 padding_len 64 - (len(data_bytes) % 64) padded_data data_bytes bytes([0x80]) bytes([0x00] * (padding_len - 1)) for chunk_offset in range(0, len(padded_data), 64): chunk padded_data[chunk_offset:chunk_offset 64] # 轮函数展开 (模拟汇编中的 EOR, MADD, EXTR 密集流水线) a, b, c, d state for i in range(16): word int.from_bytes(chunk[i*4:(i1)*4], byteorderlittle) # 还原 ARM64 的单指令融合异或与移位逻辑 temp (a ((b c) | ((~b) d)) word salt_key) 0xFFFFFFFF temp arm64_rotate_left(temp, (i % 4) * 3 5) # 状态轮转 a, b, c, d d, temp, b, c # 累加回写 state[0] (state[0] a) 0xFFFFFFFF state[1] (state[1] b) 0xFFFFFFFF state[2] (state[2] c) 0xFFFFFFFF state[3] (state[3] d) 0xFFFFFFFF # 格式化输出为最终的十六进制签名串 return .join(f{val:08x} for val in state) # 验证测试 test_input btimestamp1791518400uid502 recovered_signature recover_native_algorithm(test_input) print(f[] 算法还原成功计算出的签名值: {recovered_signature})当 Python 脚本在本地终端运行输出的字符串与之前 Frida 抓取的内存返回值逐字逐句完全对齐的一刹那整座基于动态注册、指令级混淆与寄存器打乱的移动端 Native 堡垒被彻底完全还原。五、 结语移动安全攻防对抗从来不是 Java 层的猫鼠游戏而是深入到 ARM64 指令集与操作系统内核层面的硬核博弈。静态分析能够理清算法的骨架动态 Hook 能够验证数据的血肉而对指令集体系结构的深刻理解则是直击核心命门的手术刀。深谙 ARM64 的寄存器模型与指令拓扑方能在移动端复杂的加固丛林中披荆斩棘、游刃有余。