我们知道在Android逆向中,最后的实质都会回归到对于so文件的分析无论是静态还是动态分析都会看到一堆的ARM返汇编的指令,今天就专门对这个里面遇到的一些不好理解的点进行总结,一来帮助需要之人,二来加深自己的理解,从而夯实基础。
这些东西很枯燥,我不会讲那些长篇大论,每个点要结合实际例子来展开,这样便于理解。
一、 ARM中的重要指令:
1.跳转指令:
在 ARM 中有两种方式可以实现程序的跳转:
一种是使用跳转指令直接跳转;跳转指令有跳转指令 B,带链接的跳转指令 BL 带状态切换的跳转指令 BX。
一种则是直接向 PC 寄存器赋值实现跳转。
B lable:满足条件跳转到lable地址处。
实际的操作是把lable的地址给到PC,PC寄存器作为程序计数器。
BLlable:带链接的跳转。 首先将当前指令的下一条指令地址保存在LR寄存器,然后跳转到lable。通常用于调用子程序。
实际的操作是PC-4给了LR,然后把lable的地址给到PC。
这块有个问题:为什么是PC-4给了LR,其实这个跟ARM的流水线结构是有关系的,实际上PC总是指向pc+8处,因此当前指令要在这个基础上减4。
BXRm: 带状态切换的跳转。最低位为1时,切换到Thumb指令执行,为0时,解释为ARM指令执行。
BLXlable: 带链接和状态切换的跳转,结合了BX与BL功能。
接下来我们用一个例子来说明一下,就选用最后一个功能最为多的BLX lable:
LR寄存器观察前后值的变化:
F7进去如下:
由于这个BLX此时的HEX为EF 1A F7 FF,可以看到最低位为1,因此会切换到Thumb处。
2.LDR和STR
ARM中很重要的指令就是内存与寄存器之间的一个交互,加载/存储字和无符号字节指令。使用单一数据传送指令(STR 和LDR)来装载和存储单一字节或字的数据从/到内存。LDR 指令用 于从内存中读取数据放入寄存器中;STR 指令用于将寄存器中的数据保存到内存。
比如下面这个图片就是这个:
LDR.W LR, [R7,#arg_8],把R7+ arg_8地址处的数据读出来,存到LR寄存器中。
STMFD SP!,{R0-R7,LR};现场保存,将R0~R7、LR入栈
LDMFD SP!,{R0-R7,PC};恢复现场,异常处理返回
二.ARM中函数调用问题:
要想理解汇编的意思,在我们逆向中最为注重的就是逻辑,函数的参数如何传递以及函数调用完的返回结果是如何的等等。
规定如下:
1. 函数参数传递:
r0 – r3:存储传递给函数的参数值,多余的参数通过压栈传递!
比如这里我简单的写一个调用有6个参数的函数调用,
在IDA中去调用加法的时候明显可以看到这样的结果:
前四个参数是通过R0---R3寄存器来存储的,而多余的是通过栈的形式保存的。
r4 – r11:存储函数的局部变量,Thumb模式不会使用r8以后的寄存器
r12:是内部过程调用暂时寄存器(intra-procedure-callscratch register)。
r13:存储栈指针(sp)。 这个寄存器保存着栈顶的指针。
这个指针比较重要,因为寄存器和栈之间的交互很多时候是要通过sp指针起着一个连接的作用。
r14:链接寄存器(link register)。存储着当被调用函数返回时,将要执行的下一条指令的地址。
这个寄存器也很重要,这是ARM中特有的寄存器,就像上面所说的额BL lable,先保存着当前指令的下一条指令,类似于中断的理解,多适用于子函数的调用,当子函数执行完的时候能够接着执行。
r15:用作程序计数器(program counter)。存储着当前执行指令的地址。每条执行被执行后,该计数器会进行自增(+1)。
2. 关于函数调用返回的问题:
ARM中的函数执行完的返回结果都会给到R0寄存器,这也就是在脱壳过程中始终把握R0寄存器的原因。
LR寄存器,包含函数返回时要装载的地址。在逆向脱壳中我们也可以通过观察这个寄存器来看所要执行的逻辑。
三、ARM与THUMB的区别:
我们知道在ARM指令集分为ARM与THUMB,主要区别如下:1.ARM指令集:
指令固定4字节
包含条件码,有条件执行
能访问所有寄存器
2.THUMB指令集
T1指令2字节,T2指令4字节
仅分支指令存在条件执行
仅能访问R0-R7,SP,LR,PC
PC寄存器值 = 当前地址 + 1
具体的写个jni自己编译成两种模式瞧瞧,这里不多说了。
为毛要提出来个这种指令集,还是为了节省系统存储空间,搞这么复杂让我们在基于二进制做保护时或者hook时好生困难啊,没办法。。。艳艳夏热,无心科研新东西,嚼点老东西,加深理解。