对于局部变量_64位函数参数与局部变量

  • Post author:
  • Post category:其他


在之前的文章中,我们已经简单的了解了64位的一些参数的知识,下面我们从不同的情况对64位的堆栈与函数做详细的讲解。

首先,对于64位的函数,他的参数并不都是以push压栈的方式进行传递的,他的前4个参数分别通过RCX,RDX,R8,R9这几个寄存器进行传递(如图)

c7c671f9f3d8fcd000ca14ef046e8d93.png

从图中的函数可以发现,64位的函数是不会对参数进行push的,而是将前4个参数传递给了RCX-R9这4个寄存器。从参数窗口,可以轻易的看到这些参数的数值。

那么在这种情况下如何分辨具体用到了哪些参数呢?

首先在函数调用处进行观察,一般来说,函数的参数在调用处附近会传递给RCX-R9这4个寄存器,并在函数内部进行调用。所以观察在函数调用前,看哪些寄存器被进行赋值。

其次在被调用函数头部进行观察,看4个寄存器中的哪些在函数中对其他内存进行赋值(如图)

40a6314ef0446267c4745dace8078e4f.png

图中的RDX,R8,R9都在函数头部被使用,而RCX则在被使用前被其他地址进行了赋值,所以说这个函数只需要传递RDX-R9三个参数即可,而RCX并不会影响该函数的调用。当然,这种情况也是非常少见的,通常RCX的使用率会远高于其他的三个寄存器。

以上是在函数参数不多于4个的情况下,函数的参数传递情况。那么当函数的参数超过4个以后,参数又如何进行传递呢?(如图)

10582ff34bebc4175b58b2ea93fd91e7.png

图中是某寻路函数,在函数被调用前,我们可以看到出了RCX-R9寄存器意外,还有rsp+20,rsp+28,……,rsp+40被赋值,这些被赋值的堆栈地址,就是函数第四个参数以后的其他的参数。

在rsp+20前面还有20字节,是作为预留的4个8字节存在的,有时候会在函数内部用来传递RCX-R9这4个参数,而在调用函数时通常是无需在意这20字节的。

当然,64位的函数在单步进入函数内部时也需要push一个8字节的返回地址,这一点和32位相同(如图)

a38c884330131f27acc97079cac71c7d.png

这时的第五个参数则变为了rsp+28,后面的参数地址依次+8,而前面的4个参数不变,依然为RCX-R9。

可以将64为函数的参数传递总结为下图(如图)

fe4a79b5386d9e9fcb47c6f814feab05.png

以上为64为函数的参数与堆栈的关系,那么在函数执行的过程中如果遇到了堆栈地址应该如何分析呢?这里也分为几种情况。

参数的传递

参数的传递相对局部变量会简单很多,通常我们只需要将堆栈地址计算到函数头部,就可以得出具体是由那个参数传递的(如图)

420092332d86577a237d95de1ee4ebdb.png

022E1E88处的r14d来源于[rbp+148],而rbp+148计算到头部为rsp+40,在头部到022E1E88处没有任何代码对这个地址赋值,也就是说明这个地址里的值是来源于上一层传进来的参数(如图)

468e076a99d97bf90e4ad76138bbae05.png

执行到返回后我们可以看到22F1F5B处[rsp+38]被r15b赋值,这也验证了我们的之前的推测。

局部变量的传递

局部变量的传递也分为很多种,第一种是通过rbp+N的这种方式进行传递的,这里的N可能为正值也可能为负值(如图)(如图)

a0e86a0b4680bef62cee06339e16a6a6.png

eee51f8858f8948f681757f20d78fc1f.png

图中的rax来源于[RBP+37],而RBP在头部来源于rax-5F,经过计算,rbp+37的地址在函数头部为rsp-28,这说明rbp+37其实是一个局部变量。于是我们在函数内部进行分析,并得到1E5C57D处的mov qword ptr [rbp + 0x37], rsi

这是最简单的,也是最常见的局部变量传递。

第二种是局部变量以参数的形式传递到函数中,并在函数中被赋值(如图)(如图)

2e6fc9da32c597c6f6e847150bb31bf4.png

88915a0bd3922dfd62160d9db992a201.png

图中1A497B1处的rcx来源于[rsp+70],而rsp+70计算到头部为rsp+8,看似是来源于第一个参数的,但是并没有参数向这个地址中传递数值,而在头部下断发现rsp+8里也的确没有被写入任何数值。这说明[rsp+70]其实是一个局部变量。

在1A49797处我们发现rsp+70的地址传给了rdx,而经过下面这个函数后被赋值,这说明rsp+70做为结构体参数被传入到了函数中,并且在函数内部被进行赋值。以下是赋值处的代码(如图)

8708b4b76e09d691505cd5a111a2818c.png

以上就是参数和局部变量常见的传递方式。在64位代码中,局部变量和参数分析起来非常简单,因为大部分的函数都不会对堆栈造成影响。但是参数的分析则麻烦一些,因为参数的传递一般不会用到push,而好处则是当参数个数较少的时候,我们不会因为忽视堆栈的变化导致堆栈不平衡,进而让程序崩溃。



版权声明:本文为weixin_34514218原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。