博客> 逆向4-汇编1
逆向4-汇编1
2019-12-07 17:48 评论:0 阅读:362 BellaWong

常用汇编指令集

读写指令:

  • str(store register)指令:将数据从寄存器中读出来,存到内存中
  • ldr(load register)指令:将数据从内存中读出来,存到寄存器中
  • stp :str的变种指令,可以同时操作两个寄存器
  • ldp : ldr的变种指令,可以同时操作两个寄存器
  • stur : 无符号变种指令,可当做str来看

    stur 是str的变种指令(用于负数),当成str来用就行。 stur:stur wzr, [x29, #-0x4] str : str x1, [sp, #0x10]

示例:32个字节空间作为这段程序的栈空间,然后利用栈将x0和x1的值进行交换。

 <font color=gray>每个64位的寄存器(x)占8个字节,32位(w)的占4个字节</font>
  sub    sp, sp, #0x20;   <font color=gray>//拉伸栈空间32个字节</font>
  stp    x0, x1, [sp, #0x10]; //sp往上加 16个字节,存放x0 和 x1
  ldp    x1, x0, [sp, #0x10];  //将sp偏移16个字节的值取出来,放入x1 和 x0

tips:数据的读/写都是往高地址读/写

跳转指令

  • bl标号:

    将下一条指令的地址放入lr(x30)寄存器 到标号处执行指令

  • ret : 默认使用lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址!(在函数嵌套调用的时候,需要将x30入栈!保护回家的路)

当函数跳转时,使用bl指令,将函数执行完毕之后要执行的指令存放在x30(lr)寄存器里,以便函数执行完毕,跳转至lr保存的地址继续执行。

tips:自己使用汇编编写嵌套函数的时候,在bl指令执行之前没有对lr寄存器进行入栈保护,再次跳转的时候会导致lr被覆盖,形成死循环。

其他指令

  • orr: 表示或

    任何一个数或上一个0等于它自己 orr w8,wzr,#0x1 相当于w8 = 0x1

  • adrp :基地址+偏移 找到全局变量或者常量 adrp x0,1; 会将1的值左移12位(左移位数由CPU决定的)
    1. 将1的值左移12位1 0000 0000 0000 == 0x1000
    2. 将PC寄存器的低12位清零,如:0x1002e6874 ==>0x1002e6000(假如此时PC寄存器的值为0x1002e6874)
    3. 将1和2的结果相加给x0寄存器
    4. 加上下一句汇编指令的偏移地址
    5. 找到全局变量或者常量

tips:由于得到的结果低12bit为0,那么就有4KB的偏移量,这个偏移地址是在编译的时候得到的 adrp 计算指定的数据地址,到当前PC值的相对偏移,这是一个物理地址

找到一个全局Block

函数的参数和返回值

ARM64下

  • 函数的参数:存放在X0到X7(W0到W7)这8个寄存器里面的。如果超过8个参数,就会入栈。
  • 函数的返回值:放在X0寄存器里面的。
  • 但是如果返回到的是一个结构体,那么返回的就不是x0,因为放不下。

函数的局部变量

  • 函数的局部变量: 放在栈里面

tips:如果函数里不再调用函数,称为叶子函数,叶子函数没必要再开辟栈空间

简写指令:

  • [sp, #-0x10]! : 0x1021c2898 <+0>: stp x29, x30, [sp, #-0x10]! //[sp, #-0x10]! 是将sp-0x10,赋值给sp

  • stp: stp x29,x30,[sp,#-0x10]! 等价于下面两句 sub sp,sp,#0x10
    stp x29,x30,[sp]

    ldr x30,[sp],#0x10 等价于下面两句 ldr x30,[sp] add sp, sp,#0x10 //ldp x29,x30,[sp],#0x10 同理

示例和总结

1、汇编编写嵌套函数导致死循环

 .text
 .global _A,_B
 _A:
     mov x0,#0xaaaa
     bl _B
     mov x0,#0xcccc
     ret
 _B:
     mov x0,#0xbbbb
     ret

死循环原因:lr寄存器内容被覆盖(具体原因ret指令处有说明) 可做如下修改

.text
.global _A,_B
 _A:
     mov x0,#0xaaaa
     str x30,[sp,#-0x10]!
     bl _B
     ldr x30,[sp],#0x10
     mov x0,#0xcccc
     ret
 _B:
     mov x0,#0xbbbb
     ret

程序中,编译器一定是将x29、x30一起入栈的。这里自己编写,只将x30入栈了

2、如下a、b 为int型数据

 return a + b;对应汇编代码为 0x100d668bc <+20>: add    w0, w0, w1;  //用w0,w1而不用x0,x1的原因是因为int数据,占4个字节。w就够了

3、查看汇编代码,跳转函数,总不能停留在第一行??

进入函数时,control + step into让断点停留在汇编第一行  4-1.png

4、内存地址

0x10212898 <+0>:  stp x29, x30, [sp, #-0x10]!  //前面的地址0x1021c2898:因为代码时放在本地磁盘里,运行之后需要装载到内存中,所以会有一个对应的内存地址

5、arm64 拉升栈空间是16字节对齐的,也就是说要是16字节的倍数

str x30,[sp,#-0x10]!  //假如只需要8个字节,不能为了解决内存,改为str x30,[sp,#-0x8]! 会导致奔溃。#-0x18也不行

 4-2.png

6、函数的返回值放在x0寄存器

函数的返回值放在x0寄存器,这是由系统编译器决定的,如果自己写的汇编,把函数返回结果放在了x1中,那么用高级语言所调用的函数拿到的x0就是x0原来的值,而不是函数返回结果了。

.text
.global _A,_suma;
_suma:
    add x1,x0,x1
    ret

使用高级语言在main.m中编写:

int suma(int a,int b);
int b = suma(10,20);
//这样打印的b其实是之前存在x0寄存器的10,而不是相加之后的30

7、str w10,[sp] 为什么不是sp,而是[sp]?

因为[sp]指向的是sp指针所指向的区域,这句话代表把w10存到sp所指向的区域

8、程序中,一定是把x29、x30一起入栈

如果不是叶子函数,还嵌套调用了别的函数,那么需要保存x29,x30寄存器,需要多加16个字节,所以拉伸的栈空间会增加0x10

9、sp根据什么来决定拉伸多少空间?

int sum(int a, int b,int c){
     int d = 4;
     return a+b+c;
}
a、b、c、d四个int型数据 4 * 4 = 16 <= 16.拉伸16个字节就行了sub sp, sp, #0x10

int sum(int a, int b,int c){
     int d = 4;
     int f = 0;
     return a+b+c;
}
a、b、c、d四个int型数据 5 * 4 = 20 > 16 && 20 &lt; 32

以上为叶子函数,如果函数嵌套调用呢?
int sum(int a, int b,int c){
    int d = 4;
    printf("%d",d);
    return a+b+c;
}
如上,因为嵌套调用需要保护x29、x30寄存器。就不只拉伸0x10.

10、函数嵌套调用,保护W0

int sum(int a, int b){
    int d = 4;
    return a+b;
}
int funcA(int a, int b){
    int d = sum(a, b);
    int e = sum(a, b);
    return d;
 }
funcA中调用了sum函数:执行int d = sum(a, b)之后,w0寄存器中存入的必然是d的结果,但是下一步int e = sum(a, b)又需要取出w0和w1,为了避免取出来的a被d覆盖,就应该一开始的时候将a和b保存起来(入栈)。

11、函数调用时会开辟一段空间(栈空间)

保存函数的局部变量,参数,寄存器的保护

12、函数嵌套调用会保存嵌套函数的地址吗?

函数嵌套调用的时候,会堆lr(x30)寄存器进行保护,保护回家的路

13、函数的重复调用

A函数(开辟空间)--> B函数(开辟空间)--> A函数(开辟空间) 代码只有一份,内存不只一份,汇编没有所谓的复用

收藏
0
sina weixin mail 回到顶部