[TOC]
回顾-函数
指令:bl、ret
指令:pc、lr、sp
栈:函数开辟
存放局部变量、参数,寄存器保护;
函数嵌套:
funcA -> funcB -> funcA:每次进入一个函数都会开辟一片栈空间;第二次进入funcA函数依然会开辟新的栈空间,即高级代码只有一份,可以复用,但内存不能复用,需要重新开辟;
死递归 ==》内存溢出
参数:存放位置、大小,是否入栈
状态寄存器(标记寄存器)
cpsr : current program status register,按位起作用,记录特定信息。
cpsr对if的作用:可以改变z为来控制if的比较结果;
cpsr是32位的寄存器,包含4个字节,高4位低8位有特定含义,其余中间位为预留位。
其中低8位(包括I、F、T和M[4:0],称为控制位)由系统控制,除非cpu运行在特权模式下。
高4位(31-28)组成CPSR,条件码标记位,这4位可以由算数或逻辑运算结果所改变,可以决定某条指令是否执行,意义重大!。
内嵌(对外联)汇编代码:
// N位
void funcB() {
asm (
"mov w0, #0xffffffff\n"
"adds w0, w0, #0x0\n" // 需要改变cpsr寄存器需要在运算指令后面加上s,如adds
// 用0x7fffffff替代0x0fffffff,然后add 0x0后N位还为0,因为7f在有符号数据中依然为正数
//"adds w0, w0, w0\n" // w0=w0+w0, ==> w0=w0*2 ==> w0=w0<<1,即进行左移
);
}
CPSR
N(Negative)
第31位,符号标志位,记录指令执行后,得到的结果是否为负数,是负数置为1,否则置为0;
Z(Zero)
第30位,0标记位,标记相关指令执行后其结果是否为0 ,为0标记位1,否则标记位0;
C(Carry)
第29位,进位标志位,一般情况下,进行无符号数的运算。
- 加法时,有进位时C=1,否则C=0;
- 减法时(含CMP,compare),有借位时C=0,否则C=1;
<u>Notes:不进不借,C位不变,保存上一次,运算时不区分有无符号,结果再进行区分</u>
加法:重复进行0xaaaaaaaa + 0xaaaaaaaa
时, 0xa
可转为二进制时1010
,所以在相加时(此时可视为左移)
时会有进位的现象,C位在1和0之间进行切换。如:
mov w0,#0xaaaaaaaa;0xa 的二进制是 1010
adds w0,w0,w0; 执行后 相当于 1010 << 1 进位1(无符号溢出) 所以C标记 为 1
adds w0,w0,w0; 执行后 相当于 0101 << 1 进位0(无符号没溢出) 所以C标记 为 0
adds w0,w0,w0; 重复上面操作
adds w0,w0,w0
减法:做减法时0x00000000 - 0x000000ff
,产生借位,借位后,相当于计算0x100000000 - 0x000000ff=0xffffff01
。由于借了一位,所以C位 用来标记借位。如:
mov w0,#0x0
subs w0,w0,#0xff ;
subs w0,w0,#0xff
subs w0,w0,#0xff
负数计算公式
负数 :将无符号数转换成有符号数。计算机表示有符号数的正负时,数的二进制都采用相同表示,如1111 1111
,无符号时=255,有符号时-1。运算过程:将1111 1111
取反+1
。而0x80
=-128/128
计算公式:取反+1。包括10进制负数---->二进制,二进制——>10进制负数
2到10,包括最高位(符号)一起取反,然后取反后+1
10到2,先求绝对值部分的2进制,对结果取反,再+1;
负数的补码就是对反码加1,而正数不变,正数的原码反码补码是一样的
如:-42(10进制) = d6(16)
42 = 0010 1010 -----> 1101 0101 -- +1 --> = 1101 0110 = d6(16)
如:0xa8(16) = -58(16) = -88(10)
a8 = 1010 1000 -----> 0101 0111 -- +1 --> = 0101 1000 = -58(16)
将a8转成-58的含义?为了做加法?,内存不认识-58的负号
V(Overflow)
第28位是V,溢出标志位。在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出。
- 正数 + 正数 为负数 溢出
- 负数 + 负数 为正数 溢出
- 正数 + 负数 不可能溢出
内存分区
分区:
代码区:可读可写可执行
栈区域:放参数和局部变量
堆区域: 动态申请,可读可写
全局: 可读可写(在传递时,传递的是地址)
常量: 只读(有编译器规定)
adrp
adrp = address page
寻找常量和全局变量的指令,如:
adrp x0, 2 // 2可以为其他数N
add x0, x0, #0xd10 // 0xd10 可以为其他任意值0x***
解释如下:
adrp x0, 2
1.将2的值,左移12位 2 0000 0000 0000 == 0x1000
2.将PC寄存器的低12位清零 0x1000ba89c ==> 0x1000ba000
3.将将1 和 2 的结果相加 给 X0 寄存器!!
4. 1. + 2. => 0x1000bcd10
快捷方式:倒数4位加上N,后三位替换为0x***
地址0x1000bcd10即为所要找的常量或全局变量的地址
正向开发时,此时的0x1000bcd10是指向一个int的正整数的全局变量,使用p *(int *)0x1000bcd10
可以将其存放的值打印出来,使用*(int *)
将地址转换为指定的类型;如果是char类型,使用char *
。参见视频26’’。
左移12位,是寻找偏移地址,12位的寻址能力为212=22^10 = 4KB的范围,4KB(12位)为编译器决定的*
这些的偏移量都是在编译阶段确定的,游戏外挂等有可能采用这种方法。
还原高级代码
IDA操作
- IDA在加载Mach-O时
Ready按钮
变绿色时,表示加载分析完成。 - 窗口左侧时所有方法的列表;
- 选择某个方法双击进入方法窗口;
- 右击进入的方法窗口选择
Text View
进入全屏显示; - 第4可以用点击后按空格替换;
- var_4、var_8为IDA提供的方便变量;
- 汇编中可以使用双击进行方法的跳转;
还原代码
如何判断是全局还是常量?暂时将全局视为带有“_”;
函数是否有返回值:如果有返回值,在调用ret指令会对x0寄存器进行操作;否则不会。
传入参数类型:如果有参数传入,函数进入时会对x0、x1…进行数据保护,进行入栈或者,直接对x0,x1赋予立即数。另外在调用前的上一级函数也会对x0,x1进行操作。
在优化高级代码时,从后到前,反向优化;
在验证环节,可以将还原回来的高级代码重新生成mach-o文件然后在反汇编,得到汇编代码进行对比。