x64汇编
掌握汇编语言对于恶意软件开发有着很大的作用,例如可以编写自定义 Shellcode、在木马加载器中插入汇编代码以实现混淆以及底层的指令操作等。
基本概念
汇编语言是我们可以用来为给定 CPU 编写程序的最底层的编程语言,汇编可以被翻译为 CPU 操作码,即 CPU 可以直接执行的机器码。 通常,汇编指令与操作码具有 1:1 的关系,但在 C 等高级语言中情况并非如此,它们有多种方法将书面代码编译或转换为机器代码。接下来,我们分别来讨论汇编中设计的名词与概念。
字节顺序
字节顺序指的是数据在计算机内存中的存储方式。大端是指数据的最高有效字节 (最左端) 存储在低的内存地址中,最低有效位存储在高的内存地址中。字节顺序只适用于字节,而非位。
如上图所示,0x11223344 的 4 个字节 0x11,0x22,0x33,0x44 分别存储在由低往高的内存地址中。
小端则正好相反,如下图所示,0x11223344 的 4 个字节 0x11,0x22,0x33,0x44 分别存储在由高往低的内存地址中。
因为小段运用更多,请尝试理解下图:
有符号与无符号数字
如果存储一个无符号的数,那么我们不需要指定其正负符号,那么该数的范围为 0 到 2^64 -1 。但如果要存储一个有符号的数,因为要额外留出一位存储正负符号,那么该数的范围为 -2^63 到 2^63-1。
计算一个数的负数形式,有 2 个步骤:翻转所有位,再加上 1。以 42 为例,过程如下:
42: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 1010
翻转: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1101 0101
加1: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1101 0110
CPU 寄存器
由于访问内存 (RAM) 对于 CPU 来说通常是一个缓慢的过程,因此处理器内总是包含许多寄存器,这些寄存器是处理器内部的小型存储位置,可以非常快速地访问数据。在 64 位 x64 处理器上,寄存器可以保存 64 位或 8 字节。 让我们分别查看一下如下的常用寄存器:
寄存器名称 |
作用 |
备注 |
RIP |
指令寄存器,指向要被执行的下一条指令的地址 | 只读,不能拆分 |
RAX |
累加寄存器,用于算术运算,I/O 操作,存储函数返回值等 |
通用寄存器。在 Windows syscall 中保存 SSN |
RBX |
基址寄存器,用作数据指针 |
通用寄存器 |
RCX |
计数寄存器,常用于循环中的计数器 |
通用寄存器 |
RDX |
数据寄存器,用于算数运算和 I/O 操作,以及扩展精度结果 |
通用寄存器 |
RSI |
源索引,通常用作字符串操作中的输入字符串的指针 |
通用寄存器 |
RDI |
目标索引,通常用作字符串操作中的输出字符串的指针 |
通用寄存器 |
RBP |
基址指针,指向栈帧的基址,配合偏移定位变量 |
通用寄存器 |
RSP |
栈指针,指向栈的顶部 |
通用寄存器 |
R8-R15 |
额外的通用寄存器 |
通用寄存器 |
RFLAGS |
FLAG 寄存器,存储着一系列标志位,揭示操作的结果,例如两数的数值大小比较。 |
在 x64 处理器中,每个寄存器是 8 字节,但可以被分为更小的部分以及被直接饮用。例如,RAX 的后 4 字节为 EAX,EAX 的后 2 字节为 AX 等,如下图所示。AH 与 AL 中的 A 与 H 分别表示 High 和 Low。RIP 不可被拆分。
寄存器 R8 也可以被类似的方式细分:
当我们使用后 32 位的时候,例如 EAX,那么前面的 32 位被全部填充了 0。但当我们使用后 8 或者 16 位的时候,却不是这样。
对于通用寄存器,各个部分的名称如下表所示:
64 位寄存器 | 低 32 位 |
低 16 位 |
低 8 位 |
---|---|---|---|
rax | eax | ax | al |
rbx | ebx | bx | bl |
rcx | ecx | cx | cl |
rdx | edx | dx | dl |
rsi | esi | si | sil |
rdi | edi | di | dil |
rbp | ebp | bp | bpl |
rsp | esp | sp | spl |
r8 | r8d | r8w | r8b |
r9 | r9d | r9w | r9b |
r10 | r10d | r10w | r10b |
r11 | r11d | r11w | r11b |
r12 | r12d | r12w | r12b |
r13 | r13d | r13w | r13b |
r14 | r14d | r14w | r14b |
r15 | r15d | r15w | r15b |
以及讨论一下 RFLAGS 寄存器,该寄存器包含了一系列的标志位,每个标志位都有特定的含义,用于反映操作的结果,例如两数大小的比较。以下是一些最常见和重要的位:
位 |
标签 |
描述 |
0 |
CF |
进位标志 |
2 |
PF |
奇偶校验标志 |
6 |
ZF |
零标志 |
7 |
SF |
符号标志 |
11 |
OF |
溢出标志 |
栈
数据尺寸
我们会接触到的常见数据类型以及所占字节数如下所示:
名称 |
字节数 |
BYTE |
1 |
WORD |
2 |
DWORD |
4 |
QWORD |
8 |
常见汇编指令
值操作
mov
lodsb:从 RSI 中取下一个字节传递给 AL
cdq:清空 RDX 为 NULL
比较
test
cmp
jxx
栈操作
push:保存所有寄存器
pushad
pop
popad:恢复寄存器
取址
lea
基本运算
inc
dec
add
sub
mul
div
neg
位操作
and
or
xor
not
sal
sar
shi
sjr
rol
ror
跳转
jmp
jxx
jexcz: 如果 RAX 为 0,那么跳转到最后。
函数调用与返回
call
ret
其他
stosx
int3
nop
repe scasd:重复对比 RAX 与 RDI
调用约定
参数类型 |
参数 1 |
参数 2 |
参数 3 |
参数 4 |
参数 5 + |
RCX |
RDX |
R8 |
R9 |
栈 |
|
其他 |
XMM0 |
XMM1 |
XMM2 |
XMM3 |
栈 |