本篇文章是Android逆向系列的第九篇,承接上一篇的IDA动态调试前的准备工作,上一篇介绍了调试前的启动步骤,具体的调试分析内容将在本文及之后的文章中展开。先是介绍ARM汇编指令,主要目标就是能看懂简单常见的汇编指令,其次结合IDA动态调试分析so文件,逐行详解,加深对ARM汇编指令的印象,为后期更大难度的源码调试分析打好基础。
一、ARM指令集介绍
ARM架构是一个精简指令集(RISC)处理器架构家族,其广泛地使用在许多嵌入式系统设计(绝大部分安卓手机都是基于ARM)。ARM架构的指令为定长32位(Thumb指令集支持变长的指令集,提供对32位和16位指令集的支持)
1、ARM指令格式介绍
ARM指令基本格式:
<opcode>{<cond>}{S} <Rd>,<Rn>{,<opcode2}
opcode 指令助记符,如LDR, STR等
cond执行条件,如EQ, NE等
S 是否影响CPSR寄存器的值,书写时影响CPSR,否则不影响
Rd 目标寄存器
Rn 第一个操作数的寄存器
opcode2 第二个操作数
- 中括号
<>
内的项是必须的 - 大括号
{}
内的项是可选的
2、条件码
条件码对应于ARM指令格式中的执行条件cond
二、ARM寻址方式
1、寄存器寻址
操作数的值在寄存器中,指令执行时直接取出寄存器值操作。
MOV R2, R1
2、立即寻址
立即寻址指令中的操作码字段后面的地址码部分就是操作数本身
MOV R0, #0XFF00
3、寄存器偏移寻址
将第二个寄存器的值进行移位操作后赋予第一个寄存器中
MOV R0, R1 LSL #3
将R1寄存器中的值左移3位放置到R0寄存器中。
移位操作之前介绍
4、寄存器间接寻址
间接寻址的地址存在第二个寄存器指定地址的存储单元中,使用[]
表示其中的值作为地址进行寻址。
LDR R1, [R2]
读取R2寄存器中的地址对应的存储单元中的值至R1寄存器中
三、常见ARM指令简介
这里使用之前编译的so文件反编译学习ARM指令
先使用IDA随便打开个so文件,如下
1、跳转指令
1)B
B(Branch)为直接跳转指令
B 0x11223344
B R3
跳转到0x11223344地址/R3处
2)BL
带链接的跳转,即跳转前会将下一条指令的地址拷贝到LR寄存器中,保存好后才会执行跳转,便于找到返回地址。
F3B00058 BL R3
F3B0005C CMPR0, #0
跳转后可以看到LR中的值为F3B0005C
3)BX
带状态切换的跳转,若跳转地址的位[0]为1,将标志T置为1,目标代码解释为Thumb代码;若跳转地址的位[0]为0,将标志位置为0,目标代码解释为ARM代码。
**小拓展:**Thumb 指令可以看作是 ARM 指令压缩形式的子集,是针对代码密度的问题而提出的
BX R3
跳转到R3中的地址处
4)BLX
带状态切换和带链接的跳转
2、存储器访问指令
1)LDR和STR
对存储器的访问需要通过加载和存储指令实现(LDR、STR),详细可分为字/半字,有符号/无符号等操作。
LDR:从内存单元中加载数据到寄存器(从右到左)
LDR R8,[R9,#4]
加载R9+0x4(作为地址)所指向的内容存入R8中
STR:将寄存器中的数据存储到所指向的存储单元中(也就是左边的值放到右边)
STR R8,[R9,#4]
存储R9+0x4所指向的内容存储至R8中
实例:
LDR R3, [R0,#0x4C]
将R0+0x4指向的内存单元内容读取到R3寄存器中
2)LDM和STM
批量加载/存储指令用于在一组寄存器和一块连续的内存单元之间的数据传输。主要作用为现场保护、数据传送。其中,后缀!表示最后
LDM:将存储器的数据加载到一个寄存器列表
LDM R0!, {R1-R3}
将R0指向的存储单元的数据依次加载到R1,R2,R3寄存器
STM:将一个寄存器列表的数据存储到指定的存储器
STM R0!, {R1-R3}
将R1-R3的数据存储到R0指向的地址上
实例:
STMFD SP!, {R3,LR}
STM加上FD表示堆栈操作,保护现场,将R3和LR寄存器的值保存到堆栈内,并更新SP值
3)SWP
SWP指令用于将一个内存单元中的值读取到寄存器中,同时将另一个寄存器中的值写入该内存单元中。可以用于信号量的操作
SWP R1, R2 [R0]
读取R0指向的内容到R1中,并将R2的内容写入到该内存单元中
3、数据传送指令
1)MOV
MOV为数据传送指令,用于传输数据
MOV R0, R1
将R1中的数值传送至R0寄存器
实例:
MOV R1, R4
MOV R0, #9
4、逻辑运算指令
1)ADD、SUB
ADD、SUB等的逻辑运算指令用于寄存器中值的运算,举一反三即可
ADD R1, R2, R3
SUB R1, R2, R3
将R2和R3的值相加存至R1中
2)AND、ORR
AND R1, R2, R3
ORR R1, R2, R3
将R2和R3进行按位与/或操作,结果保存至R1中
5、比较指令
1)CMP
CMP为比较指令,通过减法操作,再根据结果修改标志位,一般修改CF、OF、SF、ZF,尤其是Z标志位。
CMP R1, R2
R1的值减去R2的值,得到相应标志位的改变
实例:
CMP R7, #6
将R7中的值和6进行比较,若相等ZF置为1,不相等ZF置为0
结合下面的BNE指令分析,当R7不等于6时进行跳转,即ZF值为0时跳转。
2)TST
TST位测试指令,将两值按位作逻辑与操作,根据结果得新的标志位信息,用于后续的条件判断
TST R1, #0X01
TST R2, #0x0F
判断R1寄存器中的最低位是否为和0x01相等,判断R2寄存器中的低4位是否和0x0F相等。TST指令一般和EQ、NE等条件码配合使用,当所有测试位均为0时,EQ有效,只要有一个测试不为0,则NE有效
实例:
TST R3, #4
BNE loc_17B4
测试R3中的值和4进行按位与操作,如果结果不等于0(表示R3值不为4),执行BNE指令。
6、移位指令
1)逻辑移位LSL、LSR
LSL是逻辑左移,低位补0
LSR是逻辑右移,高位补0
2)算术移位ASL、ASR
ASL算术左移和LSL类似,主要是ASR有点细微区别
ASR算术右移中保持符号位不变,正数的话最高位补0,负数最高位补1
四、IDA动态分析源码
这部分承接上篇文章的第六部分,前边介绍了ARM汇编,稍微懂得了一些常见简单的ARM汇编指令
1、IDA动态调试载入so文件
这部分可以参考上篇文章中的普通调试部分,按照其中的步骤可以找到so文件中的JNI_OnLoad函数
2、逐行汇编指令详解
在第一行下了断点后(F2),直接步进到此处(F9),左边蓝色的PC标志表示当前指令位于此。
1)第一行汇编指令
第一行汇编代码如下,含义为将R0值指向的内存单元的值存入R3中,我们注意图中寄存器信息页面的两个框,分别是R0和R3。
LDR R3, [R0]
先查看R0所指向内存单元的值,值为0xF4DB3D20(ARM使用小端序法进行存储数据)
那么根据上面的分析,这条汇编指令执行完成后R3的值变为0XF4DB3D20
小技巧:点击PC的小箭头可以返回到指令位置
单击F7单步执行,可以看到和我们的分析完全一致
2)第二行汇编指令
第二行指令为MOV R2, #4
,意思是将立即数4赋予到R2中。
F7单步执行完该MOV指令后R2中的值将会被覆盖为4
3)第三行汇编指令
第三行指令为STR LR, [SP, #VAR_4]!
,表示将LR中的值存储至内存单元某处。
这里的#VAR_4
是一个变量,指令上面有给出是-4。SP寄存器保存栈顶指针,所以本指令的内存单元为SP值减4的位置,也就是栈顶指针减4的位置。并且最后的!
表示SP值加上偏移量值后会写回到SP寄存器中,我们先查看执行前的各值。
F7单步执行后为
**拓展:**可以查看hex信息窗口,在栈地址中选定栈顶地址,右键Follow in hex dump
可以跳转到指定地址的hex页面, 也可以查看到该值
4)第四行汇编指令
第四行指令为SUB SP, SP, #0xC
,意思就是将SP值减4写到SP中(注意这里是直接SP的值,而不是SP值指向的值)
这里SP的值为0xFF952CAC减去0xC就是0xFF952CA0,F7单步执行看看
5)第五行汇编指令
第五行指令为ADD R1, SP, R2
,含义是将SP和R2的值相加写到R1中,(没有加中括号[]
都是指寄存器中的值),SP值为0xFF952CA0,R2值为4,相加为0xFF952CA4,F7单步执行:
6)第六行汇编指令
第六行指令为LDR R3, [R3, #0x18]
,将R3值加上0x18值作为地址指向的值写到R3中。R3值为0xF4DB3D20,加上0x18得到0xF4DB3D38,可以查看到该内存单元的值为0xF470111
直接F7单步执行
7)第七行汇编指令
第七行指令为MOVT R2, #1
,MOV后面加了T表示只操作高16位,即将1赋值到R2的高16位。这里的R2值为0x00000004,操作后应该为0x00010004,F7单步执行
8)第八行汇编指令
第八行指令为BLX R3
,是一个带状态切换和带链接的跳转,跳转到R3中保存的地址中,即0XF4A70111
。
执行前的信息:
R3: 0XF4A70111 跳转的地址
0xF3B00058 下一个指令的地址(之后会保存至LR寄存器中)
T:0 标志位
F7单步执行后,可以看到跳转到了指定地址并且LR寄存器中保存了相应的返回地址,标志位T置为1表示目标代码解释为Thumb代码。
小技巧:
- 按
ESC
回到跳转前的指令处(这里的回指的是视角的回去,但是指令执行顺序是不会回去的) - 找到当前执行指令的位置,按PC寄存器旁边的箭头即可跳转找到
- 按F4代码运行到当前光标处
将光标移到函数的末尾,按F4后代码直接跳转到此处
接着按F7跳出该函数,回到原先指令的下一条指令的地址处
9)第九行汇编指令
第九行指令为CMP R0, #0
,将R0的值和0做减法比较,根据结果修改标志寄存器中的标志位(一般看Z标志位),R0值0和立即数0相等,Z标志位置为1
10)第十行汇编指令
第十行指令为BNE loc_F3B000B4
,根据状态位进行跳转,BNE看Z标志位,Z=1跳转,Z=0不跳转。
五、总结
对于刚入门安卓逆向,能看懂一些常见的、简单的ARM汇编就是我们的目标,不用追求理解ARM汇编指令中的所有内容。不同学习阶段有不同的任务,之后深入学习下去遇到不会的ARM汇编指令再查就是了。所以,对于一段ARM汇编程序代码,能看懂大部分即可。
- 本文作者: xigua
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/707
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!