ARM汇编逆向基础:看懂这几条指令就能读so库
逆向到 native 层,看不懂 ARM 汇编就等于看天书。这篇把最核心的指令和概念讲清楚。一、为什么是 ARM
手机和平板几乎全是 ARM 架构。你在 IDA 里打开一个 so 库,看到的就是 ARM 汇编。ARM 有两种模式:ARM(32 位,4 字节定长指令)和 Thumb(16/32 位混合,省空间)。ARM64(AArch64)是现在主流,全是 4 字节定长指令。
二、寄存器
ARM64 有 31 个通用寄存器(X0-X30),每个 64 位:
[*]X0-X7:函数参数和返回值。X0 是第一个参数,也是返回值
[*]X8-X18:临时寄存器,函数内随便用
[*]X19-X28:被调用者保存寄存器,函数用之前必须压栈保存
[*]X29:帧指针(FP),指向当前栈帧
[*]X30:链接寄存器(LR),存函数返回地址
[*]SP(X31):栈指针,指向栈顶
三、核心指令
[*]MOV:数据搬运
MOV X0, #42 ; X0 = 42
MOV X1, X0 ; X1 = X0
[*]LDR / STR:内存读写
LDR X0, ; 从X1指向的地址读8字节到X0
LDR X0, ; 从X1+8的地址读
STR X0, ; 把X0写到X1指向的地址
[*]BL / RET:函数调用和返回
BL func_name; 跳转到func_name,返回地址存LR
RET ; 跳回LR指向的地址(函数返回)
[*]CMP + B.cond:比较和条件跳转
CMP X0, #0 ; 比较X0和0
BEQ label ; 如果相等(X0==0)跳转到label
BNE label ; 如果不等跳转
BLT label ; 小于跳转
BGT label ; 大于跳转
[*]ADD / SUB:加减
ADD X0, X1, X2 ; X0 = X1 + X2
ADD X0, X1, #10; X0 = X1 + 10
SUB X0, X1, X2 ; X0 = X1 - X2
[*]EOR:异或(加密常见)
EOR X0, X1, X2; X0 = X1 ^ X2
四、函数调用约定
ARM64 调用约定:
[*]参数 1-8 放 X0-X7,超过 8 个压栈
[*]返回值放 X0
[*]X19-X28 被调用者负责保存恢复
[*]SP 必须 16 字节对齐
五、实战:看懂一个加密函数
IDA 反编译可能不完整,直接看汇编:
; JNI函数入口,env=X0, thiz=X1, input=X2
SUB SP, SP, #32 ; 开辟栈空间
STP X29, X30, ; 保存FP和LR
MOV X29, SP ; 设置帧指针
; 获取字符串
LDR X3, ; X3 = *env (JNIEnv函数表)
LDR X4, ; X4 = GetStringUTFChars函数指针
BLR X4 ; 调用,input的C字符串返回到X0
MOV X5, X0 ; X5 = 字符串指针
; 加密:逐字符异或0x37
MOV X6, #0 ; i = 0
loop:
LDRB W7, ; W7 = str
CMP W7, #0 ; 字符串结尾?
BEQ done
EOR W7, W7, #0x37 ; W7 ^= 0x37
STRB W7, ; str = W7
ADD X6, X6, #1 ; i++
B loop
done:
MOV X0, X5 ; 返回加密后的字符串
LDP X29, X30, ; 恢复FP和LR
ADD SP, SP, #32 ; 释放栈空间
RET
看懂了这个你就知道:加密逻辑就是逐字符异或 0x37。用 Python 复现:
decrypted = ''.join(chr(ord(c) ^ 0x37) for c in encrypted)
https://www.metk.cn/img/posts/arm_asm.jpg
ARM 汇编的核心就这些。不需要全背下来,记住 LDR/STR(读写内存)、BL/RET(调用返回)、CMP/B.cond(条件跳转)、MOV(赋值)这四组就够看懂大部分逻辑了。遇到看不懂的指令查 ARM 官方手册就行。 有个细节,日志记得清掉,不然很容易被发现。 感谢,正需要这个,之前找的资料都太老了。 感谢分享,之前一直卡在这个点,看完豁然开朗。 照着试了一下,第一遍没成功,发现是权限问题,建议楼主补充个提示。 同意楼主的判断,现在加固手段越来越多,纯手工成本太高了。 有个坑提醒,不同厂商ROM差异很大,通用性没有想象中那么强。 这个流程我在真机上验证过,确实可行,注意备份就行。 试了下你说的第二种方法,成功率更高,推荐。 收藏了,最近正好要搞这块,省了不少翻文档的时间。