x86_64

Stack Introduction

栈是一种基础数据结构,具有后进先出的特点,主要操作分别是压栈(PUSH)和弹栈(POP)两个。

image.png

二进制程序在运行的时候使用了这个结构,常见的局部变量,函数调用的时候的返回地址,甚至是寄存器会存储在这上面,当产生栈溢出漏洞的时候,会对整个程序执行造成巨大的影响。

函数调用栈约定

一些前置知识:

call 这个asm会做两个行为:

  1. 将函数首地址写入EIP寄存器中
  2. 将函数的返回地址push进栈中

ret 这个asm会做一个行为:

将栈顶地址pop给eip

leave 这个asm会做两个行为:

  1. mov rbp, rsp
  2. pop rbp

在x86下,也就是32位程序下:

会将函数调用的参数从右往左压入栈中

x86下C语言分为了三种调用方式:

IDA里是可以看到函数调用的方式的,在函数声明处

  1. C语言默认调用CDECL

    特点:

    例如:

    push 2
    push 1
    call function
    add esp, 8  ; 注意, 这里调用者再恢复堆栈
    
  2. STDCALL,这个是C++的标准调用方式

    特点:

    调用函数:可以看到这里没有add esp, 8 这个汇编,这里堆栈平衡由函数自己清理

    push 2 ; 第二个参数入栈
    push 1 ; 第一个参数入栈
    call function ; 调用函数, 注意此时自动把 cs:eip 入栈
    

    函数:

    push ebp ; 保存 ebp 寄存器, 该寄存器将用来保存堆栈的栈顶指针, 可以在函数退出时恢复
    mov ebp, esp ; 保存栈顶指针
    mov eax, [ebp + 8H] ;  堆栈中 ebp 指向位置之前依次保存有 ebp, cs:eip, a, b, ebp + 8 指向 a
    add eax, [ebp + 0CH] ; 堆栈中 ebp + 12 处保存了 b
    mov esp, ebp ; 恢复 esp
    pop ebp
    ret 8
    
    
  3. FASTCALL,编译器指定的快速调用方式

    特点:

    这里只需要它的特点就是会通过两个寄存器来传递一点参数就行了

在x64模式下,也就是64位程序:

函数参数按照RDI /RSI /RDX/RCX /R8 /R9 中,其它多余的参数保存在栈上

Arm