汇编常用指令
- sub 拉伸栈空间
sub sp, sp, #0x20
sp 是栈顶,由于栈是由高地址到低地址,
一次性拉伸32个字节就需要将栈顶地址减去0x20(栈是16字节对齐的)
- str (store register) 将数据从寄存器中读出来,存到内存中.
- stp 是str的延伸,可以同时存储两个
str x0,[sp,#0x10]
将x0的值存在以sp+0x10为地址的存储器中(栈空间内存)
stp x29,x30,[sp,#0x20]
sp往上加32(20 = 2 * 16)个字节,存放x29 和 x30
ldr(load register)读取指令
将数据从内存中读取出来, 存到寄存器中,LDR:通常都是作加载指令的,但是它也可以作伪指令ldp是ldr的衍生, 可以同时读两个寄存器
LDR r0,[r1] //将R1中的值存到r0中
LDR r1,[r2,#16] //将(r2+16)地址中的内容存到r1中
LDR r1,[r2],#4 //将r2地址中的内容存到r1中,同时r2=r2+4
sub sp, sp, #0x20 ; 拉伸栈空间32(20 = 2*16)个字节
stp x0 , x1, [sp, #0x10] ; sp往上加16(10 = 1 * 16)个字节,存放x0 和 x1
ldp x1 , x0, [sp, #0x10] ; 将sp偏移16个字节的值取出来,放入x1 和 x0
- adrp 通过基地址 + 偏移 获得一个字符串(全局变量)
原理 adrp操作步骤adrp x0, 1
1. 将1的值,左移12位 1 0000 0000 0000 == 0x1000
2.将PC寄存器的低12位清零 0x1045228b0 ==> 0x104522000
3.将将1 和 2 的结果相加 给 X0 寄存器!! ==>0x104523000
4.add x1,x0,#0xfd8 即将第三布得出的结果加上0xfd8 ==>0x104523fd8 就可以得出全局变量或者常量所在的地址
注:地址为pc寄存器左边的地址,adrp是找出要获取参数的地址范围,然后下个pc寄存器执行的代码会定位到准确的物理地址。
- cbz :【cbz 寄存器,地址 】指令的意思是 如果寄存器的值==0,则跳转到地址,如果不等于0,则继续执行代码
- subs : subs其实就是sub指令,但是这个指令操作结束后,会影响到标志寄存器
cmp(Compare)比较指令
CMP 把一个寄存器的内容和另一个寄存器的内容或立即数进行比较。但不存储结果,只是正确的更改标志。
一般CMP做完判断后会进行跳转,后面通常会跟上b指令!
- bl 标号:跳转到标号处执行
- b.gt 标号:比较结果是大于(greater than),执行标号,否则不跳转
- b.ge 标号:比较结果是大于等于(greater than or equal to),执行标号,否则不跳转
- b.eq 标号:比较结果是等于,执行标号,否则不跳转
- b.hi 标号:比较结果是无符号大于,执行标号,否则不跳转
- b.le 标号:判断上面cmp的值是小于等于 执行标号,否则直接往下走
- b.lt 判断上面cmp的值是 小于 执行后面的地址中的方法 否则直接往下走
CPY 把一个寄存器的值拷贝(COPY)到另一个寄存器中
EOR 近位异或
LSL 逻辑左移(Logic Shift Left)
LSR 逻辑右移(Logic Shift Right)
MOV 寄存器加载数据,既能用于寄存器间的传输,也能用于加载立即数
MUL 乘法(Multiplication)
MVN 加载一个数的 NOT值(取到逻辑反的值)
NEG 取二进制补码
ORR 按位或
ROR 循环右移
SBC 带借位的减法
SUB 减法(Subtraction)
TST 测试(Test,执行按位与操作,并且根据结果更新Z)
REV 在一个32位寄存器中反转(Reverse)字节序
REVH 把一个32位寄存器分成两个(Half)16位数,在每个16位数中反转字节序
REVSH 把一个32位寄存器的低16位半字进行字节反转,然后带符号扩展到32位
SXTB 带符号(Signed)扩展一个字节(Byte)到 32位
SXTH 带符号(Signed)扩展一个半字(Half)到 32位
UXTB 无符号(Unsigned)扩展一个字节(Byte)到 32位
UXTH 无符号(Unsigned)扩展一个半字(Half)到 32位
Switch
1、假设switch语句的分支比较少的时候(例如3,少于4的时候没有意义)没有必要使用此结构,相当于if。
2、各个分支常量的差值较大的时候,编译器会在效率还是内存进行取舍,这个时候编译器还是会编译成类似于if,else的结构。
3、在分支比较多的时候:在编译的时候会生成一个表(跳转表每个地址四个字节)。
void __switch_1__(){
int value = 5;
switch (value) {
case 0:
printf("1");
break;
case 1:
printf("2");
break;
case 2:
printf("3");
break;
default:
printf("else");
break;
}
}
汇编代码如下:
01-汇编-IF-SWITCH`__switch_1__:
0x100c96644 <+0>: sub sp, sp, #0x30 ; =0x30
0x100c96648 <+4>: stp x29, x30, [sp, #0x20]
0x100c9664c <+8>: add x29, sp, #0x20 ; =0x20
-> 0x100c96650 <+12>: mov w8, #0x5 ; w8 = 5
0x100c96654 <+16>: stur w8, [x29, #-0x4]
0x100c96658 <+20>: ldur w8, [x29, #-0x4] ; value = w8
0x100c9665c <+24>: mov x9, x8
0x100c96660 <+28>: stur w9, [x29, #-0x8]
0x100c96664 <+32>: cbz w8, 0x100c96694 ; cbz 指令的意思是 如果w8==0,则跳转到 0x100c96694,如果不等于0,则继续执行代码
0x100c96668 <+36>: b 0x100c9666c ; <+40> at main.m:26
0x100c9666c <+40>: ldur w8, [x29, #-0x8]
0x100c96670 <+44>: subs w9, w8, #0x1 ; w9=w8-1
0x100c96674 <+48>: stur w9, [x29, #-0xc]
0x100c96678 <+52>: b.eq 0x100c966a8 ; 如果w9==0,则跳转到 0x100c966a8
0x100c9667c <+56>: b 0x100c96680 ; <+60> at main.m:26
0x100c96680 <+60>: ldur w8, [x29, #-0x8]
0x100c96684 <+64>: subs w9, w8, #0x2 ; w9=w8-2
0x100c96688 <+68>: str w9, [sp, #0x10]
0x100c9668c <+72>: b.eq 0x100c966bc ; 如果w9==0,则跳转到 0x100c966bc
0x100c96690 <+76>: b 0x100c966d0 ; 如果都不满足即default,则跳转到 0x100c966d0
0x100c96694 <+80>: adrp x0, 1
0x100c96698 <+84>: add x0, x0, #0xf29 ; =0xf29
0x100c9669c <+88>: bl 0x100c96c04 ; printf("1");
0x100c966a0 <+92>: str w0, [sp, #0xc]
0x100c966a4 <+96>: b 0x100c966e0 ; <+156> at main.m:40
0x100c966a8 <+100>: adrp x0, 1
0x100c966ac <+104>: add x0, x0, #0xf2b ; =0xf2b
0x100c966b0 <+108>: bl 0x100c96c04 ; printf("2");
0x100c966b4 <+112>: str w0, [sp, #0x8]
0x100c966b8 <+116>: b 0x100c966e0 ; <+156> at main.m:40
0x100c966bc <+120>: adrp x0, 1
0x100c966c0 <+124>: add x0, x0, #0xf2d ; =0xf2d
0x100c966c4 <+128>: bl 0x100c96c04 ; printf("3");
0x100c966c8 <+132>: str w0, [sp, #0x4]
0x100c966cc <+136>: b 0x100c966e0 ; <+156> at main.m:40
0x100c966d0 <+140>: adrp x0, 1
0x100c966d4 <+144>: add x0, x0, #0xf24 ; =0xf24
0x100c966d8 <+148>: bl 0x100c96c04 ; printf("else");
0x100c966dc <+152>: str w0, [sp]
0x100c966e0 <+156>: ldp x29, x30, [sp, #0x20]
0x100c966e4 <+160>: add sp, sp, #0x30 ; =0x30
0x100c966e8 <+164>: ret
由上可见,当case为3个时,汇编执行的代码为if else语句 而当增加到4个case时,汇编代码发生了相关变化
0x100be66f8 <+48>: adrp x8, 0
0x100be66fc <+52>: add x8, x8, #0x790 ; x8 = 0x0000000100be6790
0x100be6700 <+56>: ldur x9, [x29, #-0x10] ; x9 就是前面存进来的值 也就是3
0x100be6704 <+60>: ldrsw x10, [x8, x9, lsl #2] ; 取出[0x0000000100be6790 + 03 << 2],给x10
0x100be6708 <+64>: add x8, x10, x8 ; 计算出要调转的位置
0x100be670c <+68>: br x8 ; 跳转
ldrsw ldrsw x10, [x8, x9, lsl #2] lsl就是将后面的值左移 []里面的意思是将x9+ 左移后的值 赋值给x8,然后通过[]取出x8寄存器所对应的值赋值给x10
br (brew register) 意思与b ’地址‘ 一样,就是跳转后面的地址
这时分析汇编代码就可得知:当超过3个case并且case值里面尽可能的连续时,编译器会通过表来存储相关的偏移值(由于本身编译的时候会增加ASLR一个随机值,故而通过pc寄存器与偏移值之间的加减就可以直接得出相关值存在的地址),通过br指令跳转对应的地址执行下面的相关代码。