Arm32
- 基本寄存器:
- R0-R3 作为 参数寄存器
- R4-R11 作为 局部变量寄存器
- R12 作为 内部调用暂时寄存器 (ip)
- R13 作为 栈指针 (sp)
- R14 作为 链接寄存器 (lr)
- R15 作为 程序计数器 (pc)
- Tips:
- 中断产生时编译器会自动保存R4-R11
- 程序返回时需恢复使用过的局部变量寄存器
- Thumb16模式下,只能使用R0-R7,R13,R14,R15这几个寄存器
- Arm下R11默认FP,Thumb下R7默认为FP
- LR在遇到BL/BLX/CALL时候自动保存下一条指令
举例七个参数的参数传递情况
- 这里看着参数不匹配是由于是C++写法,封装了一成调用,看FindClass()函数原型:
jclass (FindClass)(JNIEnv, const char*); -
LDR R2, [R0,#0x18]
至于这条指令的偏移为什么是0x18,我们可以看jni.h中定义的:
for while switch
func_circulation(JNIEnv *env, jobject thiz,int c) {
printf("----- for -----");
for (int i = 0; i < 5; ++i) {
printf("count %d",i);
}
printf("----- while -----");
int s = 3;
while(s>0){
printf("count run %d",s--);
}
printf("----- switch -----");
switch (c){
case 1:
printf("count run 1");
break;
case 2:
printf("count run 2");
break;
case 10:
printf("count run 10");
break;
default:
printf("count run default");
break;
}
}![DF$2{RX[9N$YP[PH7S8`0L5.png](https://upload-images.jianshu.io/upload_images/9548468-b91e01ec2d39c85d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
***当然还有其他版本的swith汇编代码
常用的汇编指令
------------------- ARM 32 -------------------
return;
bx lr
1E FF 2F E1
return true;
MOV R0, #1
bx lr
01 00 A0 E3 1E FF 2F E1
return false;
MOV R0, #0
bx lr
00 00 A0 E3 1E FF 2F E1
return 99;
E7 03 00 E3 1E FF 2F E1
return 999f;
7A 04 04 E3 1E FF 2F E1
空指令
NOP
00 00 A0 E1
------------------- ARM 64 -------------------
nop 1F2003D5
ret C0035FD6
Arm64
ARM64 有34个寄存器,包括31个通用寄存器、SP、PC、CPSR
x0-x30 通用寄存器(w0-w30用作32位/x0-x30用作64位)
x0-x7 用于子程序调用时的参数传递,X0还用于返回值传递
x8 用于保存子程序返回地址
X9~X15 临时寄存器,使用时不需要保存
X16~X17 子程序内部调用寄存器,使用时不需要保存
X18 平台寄存器
X19~X28 临时寄存器,使用时必须保存
FP(x29) 保存栈帧地址(栈底指针)
LR(x30) 链接寄存器LR
SP/ZXR(X31) 堆栈指针寄存器SP或零寄存器ZXR
SP 保存栈指针,使用 SP/WSP来进行对SP寄存器的访问
PC 程序计数器,俗称PC指针,总是指向即将要执行的下一条指令,在arm64中,软件是不能改写PC寄存器的
CPSR 状态寄存器
NZCV是状态寄存器的条件标志位,分别代表运算过程中产生的状态,其中:
N, negative condition flag,一般代表运算结果是负数
Z, zero condition flag, 指令结果为0时Z=1,否则Z=0;
C, carry condition flag, 无符号运算有溢出时,C=1。
V, oVerflow condition flag 有符号运算有溢出时,V=1
MOV 指令只能用于寄存器之间传值,寄存器和内存之间传值通过 LDR 和 STR
MOVZ 、MOVK 和MOVN
MOVZ 赋值一个16位的立即数到寄存器中,该寄存器中除了立即数占用到的位之外的其他位都设为0,立即数可以设置向左移位0、16、32或者48(lsl:向左移位)
instruction value of x0
movz x0, #0x1234 | 0x1234
movz x0, #0x1234, lsl #16 | 0x12340000
MOVK 赋值一个立即数到寄存器,保持立即数没用到的位保持不变
instruction value of x0
mov x0, xzr | 0x0000000000000000
movk x0, #0x0123, lsl #48 | 0x0123000000000000
movk x0, #0x4567, lsl #32 | 0x0123456700000000
movk x0, #0x89ab, lsl #16 | 0x0123456789ab0000
movk x0, #0xcdef | 0x0123456789abcdef
MOVN 用于赋值立即数的位掩码,例如想要将0xffffffff0000ffff赋值给x0,只需要使用MOVN将向左移位16的0xffff赋值位寄存器就可以实现,会自动求移位后的立即数位掩码然后赋值
instruction value of x0
MOVN x0, 0xFFFF, lsl 16 | 0xffffffff0000ffff
ARM32 为 STR LDR
STP x29, x30, [sp, #0x10] ;入栈指令
LDP x29, x30, [sp, #0x10] ;出栈指令
CBZ 比较(Compare),如果结果为零(Zero)就转移(只能跳到后面的指令)
CBNZ 比较,如果结果非零(Non Zero)就转移(只能跳到后面的指令)
CMP 比较指令,相当于SUBS,影响程序状态寄存器CPSR
B/BL 绝对跳转#imm, 返回地址保存到LR(X30)
RET 子程序返回指令,返回地址默认保存在LR(X30)
Arm 汇编层面理解inlinehook (arm32)
//dobby 64
- 明显看到了替换了前面4条thumb指令,共计8字节
- DF F8 00 F0 这条指令翻译过来就是 ldr.w pc, [pc, #0]
(三级流水线,也就是说ADD R2, PC指令在取码的时候,刚好我们准备的跳转指令正在译码,而最终执行的代码是我们译码的代码,译码这个时刻我们的pc就是当前跳转的指令位置,具体可以结合下面字符串寻址的思路理解0xCE010E62) - 前面被跳转指令替换的四条thrumb指令会被写到我们的自定义新函数开始
- 在thumb指令集中R7一般用来作为FP
- 怎么进入新函数
- 函数调用
- 新函数源码在此
jstring new_Func1(JNIEnv *env, jobject *jobject) {
LOGD("Called new_Func1");
jstring ret = (jstring) old_fuc_1(env, jobject);
const char *str_stc = env->GetStringUTFChars(ret, NULL);
LOGD("修改前的ret = %s", str_stc);
jclass jMainAct=env->FindClass("com/lzy/inlinehook/MainActivity");
jmethodID jm_makeText=env->GetStaticMethodID(jMainAct,"show","()V");
env->CallStaticVoidMethod(jMainAct,jm_makeText,NULL);
return env->NewStringUTF("Hook this return value");
}
-
C函数调用其实和正常的没啥区别,着重看他调用原函数
(这里我就直接定位0xCE3CE448)
- 接着进去R2看他做了什么事情
(简单来说就是修改了进入之前已经压栈的LR,这样老函数退出的时候才能返回新函数调用老函数的下一条指令)
这里涉及到的静态值是inlinehook onhook时候做的事情,这个debug区域都是动态生成的,这里这涉及已经形成的代码逻辑分析,怎么算的还请看源码