记录第一个ARM汇编还原案例
案例APK:https://github.com/lyousan/pic-host/raw/main/apks/ctf1.apk
目标:将核心算法由
ARM32
还原为C
代码并正确运行。前置要求:假定已经会一点C语言了,至少能看懂一级指针。
0x0 定位关键函数
使用jadx
反编译apk,类很少,关键方法很容易就能找到,最终方法的实现是在so层,使用IDA Pro
打开对应的so文件,为了简化分析过程,这里选择32位的so进行还原。so层函数采用的静态注册,在导出表中可以直接搜索到目标函数。
经过IDA反编译之后可以明显看出关键函数就是j_TestDec
。
TestDec
函数如下,这就是我们待会儿要还原成C语言的汇编代码。
0x1 初步分析
这次的主要目标是还原核心算法,整个so层函数的逻辑就大致分析一下,确定一下关键函数的入参即可。
1 | int __fastcall Java_com_testjava_jack_pingan2_cyberpeace_CheckString(JNIEnv *a1, int a2, int a3) // a3 就是java层传入的字符串 |
经过初步分析后,我们可以得知关键函数的入参是一个char*
的指针。
0x2 ARM指令前置知识
这里只简单描述一下本次还原中会涉及到的几个ARM指令,遇到不会的指令可以再去问问GPT。
指令 | 含义 |
---|---|
MOV | 移动,相当于赋值,比如MOV R0, #1 可以理解为R0=1 |
ADD | 加法,比如ADD R0, R1, R2 可以理解为R0=R1+R2 |
BL | 带返回的跳转,多用在函数调用上,其中B就是跳转的意思,单个B相当于是单程的船票,BL相当于是往返的船票 |
CMP | 比较,比如CMP R0, #2 ,其本质是做减法,通常还会紧跟其他指令一起使用,这个指令会改变标志位中的数据,然后其他指令就可以根据标志位来处理不同的情况,根据标志位的不同 可以表示出大于、小于、等逻辑运算符。 |
BCC | 当标志位处于小于的情况时就跳转到指定位置去执行,其中B跳转,CC是小于的意思,通常和CMP连用。结合上一条CMP指令,可以理解为当R0<2的时候就跳转到某个地方去执行。 |
LDR | 从内存中加载数据到寄存器中,比如LDR R0,[R1] ,此时R1中存放的是一个地址,[R1] 就是取R1中存放的地址 在内存中所指向的值,可以理解为R0=*R1 |
STR | 将寄存器中的数据保存到内存中,STR R0,[R1] ,此时R1中存放的是一个地址,将R0的值写入到[R1] 这块内存里,可以理解为*R1=R0 |
0x3 算法还原
1 | PUSH {R4,R5,R7,LR} ;准备所需的栈空间 跟我们这次还原关系不大,暂时不懂也没事 |
还原成C的参考代码:
1 |
|
0x4 总结
初学ARM汇编,熟悉一下这些常用的寄存器,可以先从ARM32入手,寄存器少一点。先对这些汇编指令有个大概的印象,囫囵吞枣的看都行,先大概知道这些指令是干什么的,看不懂的时候再去问GPT。建议把汇编和c语言对照着看,这样理解起来更快,然后就可以开始尝试把汇编翻译成伪代码,再逐步完善成C代码。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 有三!