x64函数调用规范以及setjmp/longjmp

580 阅读3分钟

x64系统在调用函数时,默认使用4寄存器的快速函数调用规范。被调用的函数(callee)使用调用栈空间保存这些寄存器的值。

在函数参数和寄存器之间有严格的一对一关系,整数类型的参数将被存放到RCX RDX R8 R9寄存器中。这几个寄存器和RAX R10 R11都被视为易失的,并且在函数调用时被视为已销毁,除非被分析为安全。

整型参数传递示例:

func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, f then e pushed on stack

该表格描述了寄存器在函数调用过程的作用

RegisterStatusUse
RAXVolatileReturn value register
RCXVolatileFirst integer argument
RDXVolatileSecond integer argument
R8VolatileThird integer argument
R9VolatileFourth integer argument
R10:R11VolatileMust be preserved as needed by caller; used in syscall/sysret instructions
R12:R15NonvolatileMust be preserved by callee
RDINonvolatileMust be preserved by callee
RSINonvolatileMust be preserved by callee
RBXNonvolatileMust be preserved by callee
RBPNonvolatileMay be used as a frame pointer; must be preserved by callee
RSPNonvolatileStack pointer

setjmp / longjmp

调用setjmp可以保存当前的栈指针和非易失寄存器的值。调用longjmp可以回到最近一次setjmp调用的地址,并且恢复栈指针和非易失寄存器的值。

以简单的例子开始,如下是a.c代码:

#include <setjmp.h>
#include <stdio.h>

int main() {
    int n = 0;
    jmp_buf buf;
    setjmp(buf);
    printf("Hello %d\n", n);
    longjmp(buf, n++);
}

使用gcc编译,gcc -g -fno-stack-protector a.c,然后使用gdb进行调试。

main汇编代码

   0x55555555518d <main+4>         push   %rbp       # 保存rbp的值                                                                                                        
   0x55555555518e <main+5>         mov    %rsp,%rbp                                                                                                         
   0x555555555191 <main+8>         sub    $0xd0,%rsp # 在栈上留出nbuf的保存空间                                                                                                       
   0x555555555198 <main+15>        movl   $0x0,-0x4(%rbp) # 将n的值放入栈中                                                                                                
   0x55555555519f <main+22>        lea    -0xd0(%rbp),%rax  # 将buf对应的地址放入rax0x5555555551a6 <main+29>        mov    %rax,%rdi   # 传递buf参数                                                                                                       
   0x5555555551a9 <main+32>        callq  0x555555555080 <_setjmp@plt>                                                                                      
   0x5555555551ae <main+37>        endbr64                                                                                                                  
   0x5555555551b2 <main+41>        mov    -0x4(%rbp),%eax                                                                                                   
   0x5555555551b5 <main+44>        mov    %eax,%esi   # 将n的值放入esi0x5555555551b7 <main+46>        lea    0xe46(%rip),%rdi  # 0x555555556004  Hello                                                                        
   0x5555555551be <main+53>        mov    $0x0,%eax   # 返回值设为0                                                                                                      
   0x5555555551c3 <main+58>        callq  0x555555555070 <printf@plt>                                                                                       
   0x5555555551c8 <main+63>        mov    -0x4(%rbp),%eax  # 将n的值让入eax0x5555555551cb <main+66>        lea    0x1(%rax),%edx	# 将n的值加1,放入edx0x5555555551ce <main+69>        mov    %edx,-0x4(%rbp)  # 将n+1后的值放回栈中                                                                                                 
   0x5555555551d1 <main+72>        lea    -0xd0(%rbp),%rdx  # 将buf对应的地址放入rdx0x5555555551d8 <main+79>        mov    %eax,%esi  # 传递参数n                                                                                                       
   0x5555555551da <main+81>        mov    %rdx,%rdi  # 传递参数buf                                                                                                      
   0x5555555551dd <main+84>        callq  0x555555555090 <longjmp@plt> 

setjmp的实现

_setjmp汇编代码,在这段代码中,setjmp将非易失性寄存器的值都保存到buf中

   0x7ffff7e06be4 <__sigsetjmp+4>  mov    %rbx,(%rdi) # 将rbx存入buf[0]                                                                                                      
   0x7ffff7e06be7 <__sigsetjmp+7>  mov    %rbp,%rax   # 以下两行对rbp寄存器的值进行加密操作                                                                                                      
   0x7ffff7e06bea <__sigsetjmp+10> xor    %fs:0x30,%rax                                                                                                     
   0x7ffff7e06bf3 <__sigsetjmp+19> rol    $0x11,%rax                                                                                                        
   0x7ffff7e06bf7 <__sigsetjmp+23> mov    %rax,0x8(%rdi) # 将加密后的rbp值存入buf[0]                                                                                                  
   0x7ffff7e06bfb <__sigsetjmp+27> mov    %r12,0x10(%rdi)                                                                                                   
   0x7ffff7e06bff <__sigsetjmp+31> mov    %r13,0x18(%rdi)                                                                                                   
   0x7ffff7e06c03 <__sigsetjmp+35> mov    %r14,0x20(%rdi)                                                                                                   
   0x7ffff7e06c07 <__sigsetjmp+39> mov    %r15,0x28(%rdi)                                                                                                   
   0x7ffff7e06c0b <__sigsetjmp+43> lea    0x8(%rsp),%rdx  # rsp+8对应main的rsp,将该值加密后保存                                                                                                  
   0x7ffff7e06c10 <__sigsetjmp+48> xor    %fs:0x30,%rdx                                                                                                     
   0x7ffff7e06c19 <__sigsetjmp+57> rol    $0x11,%rdx                                                                                                        
   0x7ffff7e06c1d <__sigsetjmp+61> mov    %rdx,0x30(%rdi)                                                                                                   
   0x7ffff7e06c21 <__sigsetjmp+65> mov    (%rsp),%rax   # 将rsp值加密后存入buf[7]                                                                                                    
   0x7ffff7e06c25 <__sigsetjmp+69> nop                                                                                                                      
   0x7ffff7e06c26 <__sigsetjmp+70> xor    %fs:0x30,%rax 
   0x7ffff7e06c2f <__sigsetjmp+79>         rol    $0x11,%rax                                                                                                
   0x7ffff7e06c33 <__sigsetjmp+83>         mov    %rax,0x38(%rdi)                                                                                           
   0x7ffff7e06c37 <__sigsetjmp+87>         testl  $0x2,%fs:0x48                                                                                             
   0x7ffff7e06c43 <__sigsetjmp+99>         je     0x7ffff7e06c4e <__sigsetjmp+110>                                                                          
   0x7ffff7e06c45 <__sigsetjmp+101>        rdsspq %rax                                                                                                      
   0x7ffff7e06c4a <__sigsetjmp+106>        mov    %rax,0x58(%rdi)                                                                                           
   0x7ffff7e06c4e <__sigsetjmp+110>        jmpq   0x7ffff7e06c60 <__sigjmp_save>                                                                            
                                                                                                 
   0x7ffff7e06c60 <__sigjmp_save>          endbr64                                                                                                          
   0x7ffff7e06c64 <__sigjmp_save+4>        push   %rbx                                                                                                      
   0x7ffff7e06c65 <__sigjmp_save+5>        mov    %rdi,%rbx                                                                                                 
   0x7ffff7e06c68 <__sigjmp_save+8>        test   %esi,%esi                                                                                                 
   0x7ffff7e06c6a <__sigjmp_save+10>       jne    0x7ffff7e06c78 <__sigjmp_save+24> 
	 0x7ffff7e06c6c <__sigjmp_save+12>       mov    %esi,0x40(%rbx)                                                                                           
   0x7ffff7e06c6f <__sigjmp_save+15>       xor    %eax,%eax                                                                                                 
   0x7ffff7e06c71 <__sigjmp_save+17>       pop    %rbx                                                                                                      
   0x7ffff7e06c72 <__sigjmp_save+18>       retq    # 返回   

longjmp的实现

longjmp汇编代码如下,这段代码读取buf中的值,并将这些值复原到非易失性寄存器中

  0x7ffff7e06d64 <__longjmp+4>    mov    0x30(%rdi),%r8   # 对保存的加密数值进行解密                                                                                                 
  0x7ffff7e06d68 <__longjmp+8>    mov    0x8(%rdi),%r9                                                                                                     
  0x7ffff7e06d6c <__longjmp+12>   mov    0x38(%rdi),%rdx                                                                                                   
  0x7ffff7e06d70 <__longjmp+16>   ror    $0x11,%r8                                                                                                         
  0x7ffff7e06d74 <__longjmp+20>   xor    %fs:0x30,%r8                                                                                                      
  0x7ffff7e06d7d <__longjmp+29>   ror    $0x11,%r9                                                                                                         
  0x7ffff7e06d81 <__longjmp+33>   xor    %fs:0x30,%r9                                                                                                      
  0x7ffff7e06d8a <__longjmp+42>   ror    $0x11,%rdx              # 0x555555556004                                                                          
  0x7ffff7e06d8e <__longjmp+46>   xor    %fs:0x30,%rdx                                                                                                     
  0x7ffff7e06d97 <__longjmp+55>   testl  $0x2,%fs:0x48                                                                                                     
  0x7ffff7e06da3 <__longjmp+67>   je     0x7ffff7e06dd1 <__longjmp+113>                                                                                    
  0x7ffff7e06da5 <__longjmp+69>   rdsspq %rax                                                                                                              
  0x7ffff7e06daa <__longjmp+74>   sub    0x58(%rdi),%rax                                                                                                   
  0x7ffff7e06dae <__longjmp+78>   je     0x7ffff7e06dd1 <__longjmp+113>                                                                                    
  0x7ffff7e06db0 <__longjmp+80>   neg    %rax                                                                                                              
  0x7ffff7e06db3 <__longjmp+83>   shr    $0x3,%rax
  
   0x7ffff7e06dd1 <__longjmp+113>  nop                                                                                                                      
   0x7ffff7e06dd2 <__longjmp+114>  mov    (%rdi),%rbx             # 0x555555556004                                                                          
   0x7ffff7e06dd5 <__longjmp+117>  mov    0x10(%rdi),%r12    # 复原非易失性寄存器值                                                                                               
   0x7ffff7e06dd9 <__longjmp+121>  mov    0x18(%rdi),%r13                                                                                                   
   0x7ffff7e06ddd <__longjmp+125>  mov    0x20(%rdi),%r14                                                                                                   
   0x7ffff7e06de1 <__longjmp+129>  mov    0x28(%rdi),%r15                                                                                                   
   0x7ffff7e06de5 <__longjmp+133>  mov    %esi,%eax                                                                                                         
   0x7ffff7e06de7 <__longjmp+135>  mov    %r8,%rsp                                                                                                          
   0x7ffff7e06dea <__longjmp+138>  mov    %r9,%rbp