寄存器种类
X64汇编语言寄存器结构及其与X86架构编程区别 新手福利——x64逆向基础
通用寄存器 (general register)
通用寄存器是平时运行程序会使用到的寄存器,也是最多接触的寄存器
| 64-bit | 32-bit | 16-bit | 8-bit (low) |
|---|---|---|---|
| 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 |
其中16位的寄存器中,可以访问其高8位的数据 十六位再拆
| 16-bit | 8-bit (high) |
|---|---|
| AX | AH |
| BX | BH |
| CX | CH |
| DX | DH |
32位
esp:栈顶指针,指向栈的顶部, 该指针永远指向系统栈最上面一个栈帧的栈顶 ebp:栈底指针,指向栈的底部,通常用ebp+偏移量的形式来定位函数存放在栈中的局部变量, 该指针永远指向系统栈最上面一个栈帧的底部 eax:通常用来执行加法;(通常用于存储函数调用返回值) ebx:数据存取 ecx:通常用作计数器,比如for循环 edx:用于保存乘法形成的部分结果,或者除法之前部分被除数 edi:字符串操作时,用于存放数据源的地址 esi:字符串操作时,用于存放目的地址的,和edi两个经常搭配一起使用,执行字符串的复制等操作
”栈底指针” 技巧 (32)
在调用栈 定位到 ebp/rbp(栈底指针) 在当前函数内: 往上减就是 本函数的局部变量 往下加就是 本函数的参数(stdcall)
例如 push esp 8; //先提高栈 push (esp地址其实是减);类似 sub esp, 8h mov [esp-4] 1; // 给第一个栈局部变量 赋值 1 mov [esp-8] 1; // 给第二个栈局部变量 赋值 1
此技巧仅适用32位 (stdcall)
64位
X64将X86的 8个通用寄存器扩展为64位,增加了8个新的通用64位寄存器是32位汇编的两倍(共16个)。64位寄存器的名称都以字母“r”开头(当然,寄存器名称不区分大小写,也可以是“R”开头),例如,eax的64位扩展寄存器命名为rax,新增加的寄存器命名从r8到r15。
每个寄存器的低32位,低16位,低8位可在操作数中直接寻址。这包括像esi这样的寄存器,其低8位在以前是不能直接寻址的。下面的列表指出了64位通用寄存器的低位名称。
| 64位 | 低32位 | 低16位 | 低8位 | 描述 |
|---|---|---|---|---|
| rax | eax | ax | al | 累加器 |
| rbx | ebx | bx | bl | 基地址 |
| rcx | ecx | cx | cl | 循环记数器 |
| rdx | edx | dx | dl | 数据寄存器,通常扩展A寄存器 |
| 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 | 新增通用寄存器 |
栈指针 rsp : 栈顶指针,指向栈的顶部 rbp : 栈底指针 指向栈的底部, 在x64下rbp已经很少使用了.
累加寄存器 eax/rax 累加寄存器 eax/rax被称为累加寄存器,它除了被用作一些加法和乘法指令的默认寄存器,还有几个默认用法:
- 若函数有返回值,它默认用来存放返回值。当然,eax/rax在函数中是可以使用的,只是在返回时(若有返回值)要存放着返回值。
- 在x86中,当要把一个数从32位扩展成64位,则eax存放低32位,edx存放高64位。
- 在int 80的中断类型的系统调用中,eax又默认被用来存放系统调用号。
fastcall 和 stdcall
在Windows平台上,32位通常使用 stdcall的调用约定,调用者将参数依次压入栈中,然后调用函数。被调用函数从栈上读取参数,完成相应的操作。
64位默认使用 fastcall 调用使用寄存器来传递函数的一部分参数。通常,前几(4)个参数会被放在特定的寄存器中,而后续的参数仍然通过栈传递。这样做的目的是为了减少内存访问,提高函数调用的性能。
in short 换句话说, 用到了几个固定的寄存器用来传递 call 参数, 而不是stdcall的 push 压栈的形式
调用约定使用以下寄存器来传递函数的前几个参数: RCX:用于传递第一个参数。 RDX:用于传递第二个参数。 R8:用于传递第三个参数。 R9:用于传递第四个参数。
其余的参数如果还需要传递,则会使用栈来传递。需要注意的是,传递参数的顺序是从左往右,依次使用 RCX、RDX、R8 和 R9 寄存器。
在x64上,在函数调用时如果发现函数会使用rsp, rbp, rbx, r12, r13, r14和r15,需要保存。其他寄存器可以自由使用。 x86和x64通用寄存器在汇编中的特殊默认用法
浮点数寄存器
x64处理器也提供了几套浮点寄存器:
8个80位x87 寄存器。 8个64位MMX寄存器。(这些与x87 寄存器重叠) 原来的8个128位SSE寄存器增加到16个。
另外通常操作浮点型还有一套叫 FPU 的汇编特殊指令
IP 指令指针寄存器
EIP(32)/RIP(64): CPU下次次执行的代码地址
FR 标志寄存器
64位 eflags 寄存器升级为64bit的rflags,不过其高32位并没有新增什么功能,保留为将来使用。
FR是16位寄存器,其中有9位有效位用来存放状态标志和控制标志。 状态标志共6位,CF、PF、AF、ZF、SF、OF
1.进位标志位CF
进位标志CF主要用来反映运算是否产生进位或借位。
运算结果的最高位产生了一个进位或借位,那么,CF=1,否则CF=0。
2.零标志ZF
零标志ZF用来反映运算结果是否为0。
运算结果为0,那么,ZF=1,否则ZF=0。
3.符号标志SF
符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。
运算结果为正数时,SF=0,否则SF=1。
4.溢出标志OF
溢出标志OF用于反映有符号数加减运算所得结果是否溢出。
如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF=1,否则,OF=0。
5.奇偶标志PF
奇偶标志PF用于反映运算结果中“1”的个数的奇偶性。
如果“1”的个数为偶数,则PF=1,否则PF=0。
6.辅助进位标志AF
在字操作时发生低字节向高字节进位或借位时 或者 在字节操作时发生低4位向高4位进位或借位时,AF=1,否则AF=0。
32位标志寄存器增加的标志位
- 重启动标志RF 重启动标志RF用来控制是否接受调试故障。
RF=0时,表示“接受”调试故障,否则拒绝之。在成功执行完一条指令后,处理机把RF置为0,当接受到一个非调试故障时,处理机就把它置为1。
- 嵌套任务标志NT 嵌套任务标志NT用来控制中断返回指令IRET的执行。
当NT=0,用堆栈中保存的值恢复EFLAGS、CS和EIP,执行常规的中断返回操作;当NT=1,通过任务转换实现中断返回。
- I/O特权标志IOPL I/O特权标志用两位二进制位来表示,也称为I/O特权级字段。该字段指定了要求执行I/O指令的特权级。
如果当前的特权级别在数值上小于等于IOPL的值,那么,该I/O指令可执行,否则将发生一个保护异常。
- 虚拟8086方式标志VM 如果该标志的值为1,则表示处理机处于虚拟的8086方式下的工作状态,否则,处理机处于一般保护方式下的工作状态。
段寄存器 (Segment registers)
在x64架构下,段寄存器被废弃,不再起到分段的作用。因此,在64位模式下,没有实际使用的段寄存器。x64架构直接使用了平坦模型(Flat Model),其中所有的线性地址(虚拟地址)直接映射到物理地址,不再需要分段的操作。
CS (Code Segment Register) - 代码段寄存器 DS (Data Segment Register) - 数据段寄存器 SS (Stack Segment Register) - 栈段寄存器 ES (Extra Segment Register) - 附加段寄存器 FS (Segment Register) - 段寄存器 GS (Segment Register) - 段寄存器
但在x64架构下,这些段寄存器不再用于控制内存访问,而是变成了通用寄存器,可以用于一般性的数据处理,类似于RAX、RBX等。因此,在64位模式下,CS、DS、SS、ES、FS和GS等段寄存器不再有实际用途,它们被当作通用寄存器使用。
in short 应该是64位的寻址能力, 完全够用了
常用指令
数据传送指令
MOV:传送字或字节。 MOVSX:先符号扩展,再传送。 MOVZX:先零扩展,再传送。 PUSH:压入堆栈。 将操作数的值压栈, 相应的esp(栈顶指针)的值会改变 POP: 弹出堆栈。 将栈顶的值赋值操作数, 相应的esp(栈顶指针)的值会改变 PUSHA: 把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈。 POPA: 把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈。 PUSHAD: 把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈。 POPAD: 把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈。 BSWAP: 交换32位寄存器里字节的顺序。 XCHG: 交换字或字节。( 至少有一个操作数为寄存器,段寄存器不可作为操作数) CMPXCHG: 比较并交换操作数。(第二个操作数必须为累加器AL/AX/EAX) XADD: 先交换再累加。( 结果在第一个操作数里 )
mov ax,word ptr [bx]; 等于是内存地址 “BX寄存器的值” 的地方所存放的数据,赋予ax。 (中括号指取地址表达式)
算术运算
ADD: 加法。 ADC: 带进位加法。 INC: 加 1。 AAA: 加法的ASCII码调整。 DAA: 加法的十进制调整。 SUB: 减法。 SBB: 带借位减法。 DEC: 减 1。 NEG: 取补。 CMP: 比较。(两操作数作减法,仅修改标志位,不回送结果) AAS: 减法的ASCII码调整。 DAS: 减法的十进制调整。 MUL: 无符号乘法。结果回送AH和AL(字节运算),或DX和AX(字运算) IMUL: 整数乘法。结果回送AH和AL(字节运算),或DX和AX(字运算) AAM: 乘法的ASCII码调整。 DIV: 无符号除法:商回送AL,余数回送AH,(字节运算);或商回送AX,余数回送DX(字运算) IDIV: 整数除法:商回送AL,余数回送AH,(字节运算);或商回送AX,余数回送DX(字运算) AAD: 除法的ASCII码调整。 CBW: 字节转换为字。(把AL中字节的符号扩展到AH中去) CWD: 字转换为双字。(把AX中的字的符号扩展到DX中去) CWDE: 字转换为双字。 (把AX中的字符号扩展到EAX中去) CDQ: 双字扩展。(把EAX中的字的符号扩展到EDX中去)
逻辑运算
AND // 与运算 /& 运算的两个位都为 1 时, 结果才为 1, 否则为 0; or // 或运算 /| 两个二进制位有一个为 1 时, 结果就为 1, 两个都为 0 时结果才为 0; XOR // 异或运算 /^ 参与运算两个二进制位不同时, 结果为 1, 相同时结果为 0; NOT // 取反 /~ 二进制表示的所有位数取反,例,二进制 0001
移位指令
SHL: 逻辑左移。 SAL: 算术左移。(=SHL) SHR: 逻辑右移。(每位右移,低位进 CF,高位补 0) SAR: 算术右移。(每位右移, 低位进 CF,高位不变) ROL: 循环左移。 ROR: 循环右移。 RCL: 通过进位的循环左移。 RCR: 通过进位的循环右移。
test,cmp 指令
test: 命令将两个操作数进行**逻辑与(and)运算,该命令的两个操作数不会被改变。运算结果在设置 ZF标志位 cmp: 命令将两个操作数进行减法(sub)**运算,该命令的两个操作数不会被改变。运算结果在设置 ZF标志位
jmp, nop 指令
jmp : 无条件跳转 nop : 空指令, 无任何操作只会改变 IP寄存器
jcc 指令
条件跳转
JE, JZ 结果为零则跳转(相等时跳转) ZF=1 JNE, JNZ 结果不为零则跳转(不相等时跳转) ZF=0 JS 结果为负则跳转 SF=1 JNS 结果为非负则跳转 SF=0 JP, JPE 结果中1的个数为偶数则跳转 PF=1 JNP, JPO 结果中1的个数为偶数则跳转 PF=0 JO 结果溢出了则跳转 OF=1 JNO 结果没有溢出则跳转 OF=0 JB, JNAE 小于则跳转 (无符号数) CF=1 JNB, JAE 大于等于则跳转 (无符号数) CF=0 JBE, JNA 小于等于则跳转 (无符号数) CF=1 or ZF=1 JNBE, JA 大于则跳转(无符号数) CF=0 and ZF=0 JL, JNGE 小于则跳转 (有符号数) SF≠ OF JNL, JGE 大于等于则跳转 (有符号数) SF=OF JLE, JNG 小于等于则跳转 (有符号数) ZF=1 or SF≠ OF JNLE, JG 大于则跳转(有符号数) ZF=0 and SF=OF
call, retn 指令
call: 跳转到函数地址 并且 push call指令的 下一行指令的地址 retn: 将栈顶的值赋值给 EIP 寄存器; 相当于 pop eip(出栈,将栈顶的值赋值给 EIP )
寄存器数据保存指令
pushad: 将所有的32位通用寄存器压入堆栈 pusha: 将所有的16位通用寄存器压入堆栈
pushfd: 然后将32位标志寄存器EFLAGS压入堆栈 pushf: 将的16位标志寄存器EFLAGS压入堆栈
popad: 将所有的32位通用寄存器取出堆栈 popa: 将所有的16位通用寄存器取出堆栈
popfd: 将32位标志寄存器EFLAGS取出堆栈 popf: 将16位标志寄存器EFLAGS取出堆栈
CPU如何区分内存时间是 立即数 or 一条指令
cpu总线内部分为:数据总线 地址总线 控制总线, 如何区分取决于内存数据是从哪条逻辑总线过来的
地址总线目前(大部分主板):48位 颜色指针本质上包含了地址映射的概念