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
该表格描述了寄存器在函数调用过程的作用
| Register | Status | Use |
|---|---|---|
| RAX | Volatile | Return value register |
| RCX | Volatile | First integer argument |
| RDX | Volatile | Second integer argument |
| R8 | Volatile | Third integer argument |
| R9 | Volatile | Fourth integer argument |
| R10:R11 | Volatile | Must be preserved as needed by caller; used in syscall/sysret instructions |
| R12:R15 | Nonvolatile | Must be preserved by callee |
| RDI | Nonvolatile | Must be preserved by callee |
| RSI | Nonvolatile | Must be preserved by callee |
| RBX | Nonvolatile | Must be preserved by callee |
| RBP | Nonvolatile | May be used as a frame pointer; must be preserved by callee |
| RSP | Nonvolatile | Stack 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 # 在栈上留出n和buf的保存空间
0x555555555198 <main+15> movl $0x0,-0x4(%rbp) # 将n的值放入栈中
0x55555555519f <main+22> lea -0xd0(%rbp),%rax # 将buf对应的地址放入rax中
0x5555555551a6 <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的值放入esi中
0x5555555551b7 <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的值让入eax中
0x5555555551cb <main+66> lea 0x1(%rax),%edx # 将n的值加1,放入edx中
0x5555555551ce <main+69> mov %edx,-0x4(%rbp) # 将n+1后的值放回栈中
0x5555555551d1 <main+72> lea -0xd0(%rbp),%rdx # 将buf对应的地址放入rdx中
0x5555555551d8 <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