前面的研究中提到了调用 ConOut 必须满足的2个条件,这次就研究一下其中的一个条件:

“实践发现在调用 SystemTable->ConOut 的时候会损坏堆栈,比如下面的调用方式(按道理2个参数的情况下是通过寄存器来进行参数传递的)

push rdx 
Call ConOut
pop rdx 

运行之后会发现 rdx 值发生了变化”

首先需要明确的是:堆栈生长方向是从高向低。比如:PUSH RAX执行之后,会将RSP=RSP-8, RAX值存放在 RSP 给出的位置,然后;POP 动作与之相反。

开始跟踪代码,首先在调用处停下来:

执行Call 命令之后,RSP=RSP-8, 然后将 call 后面的下一条指令地址存放在 [RSP ] 之中。此时堆栈如下(简单起见,认为下面是高地址,上面是低地址。当前的IP和堆栈IP 都记作2Bytes方便查看):

跳转到了我们的 ASM 代码中

继续运行 call qword ptr [rcx+8] 之后堆栈情况:

特别注意,这里有一个 mov qword ptr [rsp+8],rbx, 运行之后会将前面一个的返回地址覆盖掉(就是 call qword ptr [rbx+20h] 指令(RIP=989B)的下一条地址)。因此,无论后面如何,这里都不可能正常返回。再进一步,观察到每个函数进入之后都有 sub esp,xxx 这样的操作,咨询天杀他回复“64位的函数调用中,参数的传递,也是要通过栈传递的,call子函数前将数据放到栈中再调用。小于四个参数的情况,一般使用RCX、RDX、R8、R9传递,但是也需要在栈中保留相应的空间。”换句话说,小于4个参数的情况会直接使用寄存器传递参数,但是同样要在堆栈上预留出这几个参数的空间。经过研究在【参考1】有如下描述:

1.在调用函数之前,会申请参数预留空间.(rcx,rdx,r8,r9)

2.函数内部,会将寄存器传参的值(rcx,rdx,r8,r9)保存到我们申请的预留空间中.

上面这两步其实就相当于x86下的 push r9 push r8 push rdx,push rcx

3.调用约定是 __fastcall.传参有rcx rdx,平栈是按照c调用约定平栈. 也就是调用者平栈.

因此,我们之前编写调用  ConOut 的方法是错误的,必须按照规则在堆栈上预留2个参数的空间。 从前面可以看到,堆栈的生长方向是由高到低,因此用  sub rsp,16 即可预留2个8Bytes。

上面就是发生堆栈损坏的原因,本质是我们违反了编译器的约定导致的。

参考:

1. https://www.cnblogs.com/iBinary/p/10959444.html  x64汇编第三讲,64位调用约定与函数传参.

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>