首先配置IDA与安卓联动
IDA动态调用手机apk,请参考:安卓逆向-从环境搭建到动态调试apk IDA部分https://www.freebuf.com/articles/mobile/285861.html
1)加载server
2)端口转发+执行app(javandk1这个测试app)
adb install E:\IDA7.0\test\javandk1.apk #安装app
adb shell am start -D -n com.example.javandk1/.MainActivity #启动app
adb forward tcp:23946 tcp:23946 #端口转发
3)打开启动DDMS
DDMS
4)打开IDA 32并调试ndk运行
5)并勾选三项调试
6)F9启动+执行jdb调用DDMS
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8600
此时可以看到加载的不是so,而是/arm/base.odex文件,那么此时怎么加载我们需要的so库,分析JNI_onload呢?
问题:解决方法无libjavandk1.so库
1)在Modules查询java:
2)选择libjavacore.so库-在搜索JNI_load选择
那么此时就进入了libjavacore.so的JNI_onload了
3)接下来下一个断点:点击或F2
4)F9运行后跳转到断点截断处,此时回到Modules继续搜索java:
此时就出现需要调试的so库文件:libjavandk1.so库文件
5)那么继续选择进入搜索JNI即可
此时问题就解决了
6)继续来到JNI_onload下断点,开始分析判断传参
这里BLX传入了几个参数?
这里BLX中R3需要跳转,那么R3也是有规定给地址的,所以我们这里的有4个参数R0-R3
R0-R3:4个寄存器->参数寄存器
R0-R3:用于函数参数及返回值的传递
R4-R6,R8,R10-R11:普通的通用寄存器
R7:栈帧指针(Frame Pointer),指向前一个保存的栈帧(stack frame)和链接寄存器(link register,lr)在栈上的地址。
R9:操作系统保留
R12:IP(intra-procedure scratch)
R13:SP(stack pointer) 是栈顶指针
R14:LR(link register) 存放函数的返回地址
R15:PC (program counter),指向当前指令地址
如果R3作为一个地址的存放,当你把一个函数地址存放在R3里面,根据规定R3已经作为地址存放了,如果这个函数要传参,只能从R0-R2,这个三个寄存器里面进行传参。
注:如果想判断有几个参数,看这个BLX(指令)后面的值(是不是寄存器,如果是,Rn小于4,参数个数就是当前减1;大于等于4,参数寄存器就是R0-R3)
此时回到图中,BLX R3;三个参数,用了R0-R2后,只会依次使用下一个寄存器存放跳转的地址;
BLX R4(三个参数:R0,R1,R2,R3)
现在打开堆栈看看
在BLX R3处打桩,F9运行到下一个断点处
记住此时的栈顶是00000001,这时候SP:FF9ABBE0指向栈顶
这时候单步F7
这时候查看,SP的值还是FF9ABBE0是没有变化的
静态调试SO库,按F5查看伪代码
可以查看到伪c代码(int a1,int a2)就是查看参数个数
我们随便找的几个BLX指令的地方。
BLX R3此时传入几个参数 ?三个
那么BLX R12传入几个参数?四个
按F5进入伪C代码
静态注册参数怎么修改呢?选择你要修改的参数,按Y
如何修改寄存器的值呢
这时候就可以修改寄存器的值了
例如:cmp R0,0,那么就执行BEN,意思就是修改了条件为0后,就不执行改条件,反调试会更深入演示
Y键修改C代码(退出C代码按ESC)
R0-R3:用于函数参数及返回值的传递
R4-R6,R8,R10-R11:普通的通用寄存器
R7:栈帧指针(Frame Pointer),指向前一个保存的栈帧(stack frame)和链接寄存器(link register,lr)在栈上的地址。
R9:操作系统保留
R12:IP(intra-procedure scratch)
R13:SP(stack pointer) 是栈顶指针
R14:LR(link register) 存放函数的返回地址
R15:PC (program counter),指向当前指令地址
如果我要改这条指令
根据三级流水线,需要在前三个代码断点
不想让程序执行怎么办?直接同步下PC寄存器
现在你想在HEX处找的PC的指令,当鼠标放在PC指令处,hex自动选择
然后在Hex View-1处快捷键F2操作,修改,
修改为00 00 00 00后,然后在快捷键F2保存下
这时候指令就没了
或者直接设置PC(只适合调试时候测试使用)
例如此刻需求是,跳过大红框内容,直接执行MOV R0, #0x10004这条指令,此刻PC在F42AA08C
只需要在General registers处修改PC值为MOV处的指令值即可,那么此时MOV出指令值为:
F42AA0A4
开始修改,双击General registers-PC处,修改为MOV的值:
直接跳转mov处
在PC窗口处配置出堆栈指针和Hex View对应的十六进制的值
CPSR标志位详解
一边情况下标志位情况
断点后的情况
用最简单的理解,这些到底有什么用
通过图很好了解,例如BLX进行运算是正数还是负数,经过运算后值是不是为零,可以理解为条件标志位,帮我们记录一些状态! 标志位的结果内容可被算数或逻辑运算的结果所改变,并且可以决定某条指令是否被执行。 最重要的是N、Z、C、V、Q、T,那么T是什么意思? T标志位︰该为反应处理器的运行状态。当该位为1时,程序运行于THUMB状态,否则运行于ARM状态。该信号反映在外部引脚TBIT上。在程序中不得修改CPSR中的TBIT位,否则处理器工作状态不能确定。
ARM状态
arm处理器工作于32位指令的状态,所有指令均为32位
thumb状态
arm执行16位指令的状态,即16位状态
前缀
ARM7处理器采用3级流水线来增加处理器指令流的速度,能提供0.9MIPS/MHz的指令处理速度。
MIPS(Million Instruction Per Second)表示每秒多少百万条指令。比如0.9MIPS,表示每秒九十万条指令。
MIPS/MHz表示CPU在每MHz的运行速度下可以执行多少个MIPS,如0.9MIPS/MHz则表示如果CPU运行在1MHz的频率下,每秒可执行90万条指令。
三级流水线使用3个阶段,因此指令分为3个阶段执行
1)取指从存储器装载一条指令
2)译码识别将要被执行的指令
3)执行处理指令并将结果写会寄存器
但是处理实际是这样的:ARM正在执行第一条指令的同时对第二条指令进行译码,并将第三条指令从存储器中取出
所以,ARM7流水线只能在取第4条指令时,第1条指令才算完成执行
无论处理器处于何种状态,程序计数器R15(PC)总是指向”正在取指“的指令,而不是指向”正在执行“的指令或者正在”译码“的指令。
人们一边会习惯性的将正在执行的指令作为参考点,即当前第一条指令,所以,pc总是指向第三条指令
或者说PC总是指向当前正在执行的指令在加2条指令的地址
处理器处于ARM状态是,每条指令为4个字节,所以PC指令为正在执行的指令地址加8个字节,即是:
PC值=当前程序执行位置+8字节
处理器处于Thumb状态时,每条指令为2字节,所以PC值为正在执行的指令地址加4字节,即是:
PC值=当前程序执行位置+4字节
下面一个列子很好的说明了这个问题
libjavandk1.so:F42AA090 010 00 C0 90 E5 LDR R12, [R0] #正在被执行的指令
libjavandk1.so:F42AA094 010 5C C3 9C E5 LDR R12, [R12,#0x35C] #正在被编码的指令
libjavandk1.so:F42AA098 010 3C FF 2F E1 BLX R12 #正在被取指的指令 PC=F42AA098
libjavandk1.so:F42AA09C 010 00 00 50 E3 CMP R0, #0 #PC+4=F42AA09C
另外补充说明就是根据以上描述,流水线只有被指令填满时才能发挥最大的效能,既每时钟周期完成一条指令的指向(仅单周期指令)
如果程序发送跳转,流水线会被清空,这将需要几个时钟才能使流水线被再次填满。因此,尽量地少使用跳转指令可以提高程序的指令效率
PC代表程序计数器,流水线使用三个阶段,因此指令为分为三个阶段执行:
1、取指(从存储器装载一条指令)
2、译码(识别将要被执行的指令)
3、执行(处理指令并将结果写回寄存器)
而R15(PC)总是指向”正在取指“的指令,而不是指向”正在执行“的指令或者正在”译码“的指令。一般来说,人习惯性约定将”正在执行“的指令作为参考点,称之为第一条指令,因此PC总是指向第三条指令,当ARM状态时,每条指令为4字节长,所以PC始终指向改指令地址加8字节的地址,既:PC值=当前程序执行位置+8;
ARM指令是三级流水线,取指、译指、执行是同时执行的,现在PC指向的是正在取值的地址,那么CPU正在译指的指令地址是PC-4(假设ARM状态下,一个指令占4个字节),cpu正在执行的指令地址是cpu-8,也就是说PC所指向的地址和现在所执行的指令地址相差8。
当突然发生中断的时候,保存的是PC的地址
这样你就知道了,如果返回的时候返回PC,那么中间就有一个指令没有执行,所以用SUB pc lr-irq#4。
这个需要参考《ARM指令》来学习
下面我们将一个ARM指令转换为机器码试试
00001BD0 BEQ lc04
BEQ lc04;跳转指令,执行条件EQ,即相等跳转到lc04
来计算这条指令
1)首先ARM指令是32位的,因为这里是BEQ的B的跳转指令,32位可以拆分为格式如下
2)前四位:31-28就是cond,这里的意思就是条件码,例如EQ、NE
0000
3)紧接着3位:27-25(这里由于B指令是101固定的)
0000 101
4)在往下24位:(1或者0,具体判断:带有连接的如果是BL指 令对应的二进制操作数就是1 无条件跳转B指令就是0。)
0000 1010
5)0-23位:偏移地址:目标地址与该指令的相对偏移
偏移的计算公式:
(目标跳转的地址-(当前这条指令的地址+8))/4
(1c04-(1bd0+8))/4=1011
1c04:要跳转的地址
1bd0:此指令所在的地址
+8:arm指令有3级流水线的原因,如果执行1c04地址指令,需要加8
/4:因为要做对 其处理
我们拼接一下计算出来的二进制ARM指令机器码,因为0-23位算出来的是1011,
1011对应0-3位置
4-23用0补齐
结果
0000 1010 0000 0000 0000 0000 0000 1011
转换为16进制就是
0A 00 00 0B
因为是小端模式,需要换一下位置结果就是
0B 00 00 0A
我们在IDA中测试一下
将IDA中hex中随便一个地方改为0B 00 00 0A
我们看到指令变为了BEQ loc_F42AA0D4
我们计算利用偏移公式计算F42AA0D4(目标地址)和F42AA0A0(当前地址)结果是不是1011
(A0D4-(A0A0+8))/4=1011
我们这里就简单了解到了ARM指令如何计算为ARM指令机器码
此章节我们详细学习了使用IDA在静态注册和动态注册下分析和修改参数,学习修改返回值、函数,对IDA堆栈功能模块和标志位功能模块进行深入解析,同时了解了ARM中三级流水线并利用案例熟悉ARM指令转换为机器码的案例。