逆向思路

寄存器种类

X64汇编语言寄存器结构及其与X86架构编程区别 新手福利——x64逆向基础

通用寄存器 (general register)

通用寄存器是平时运行程序会使用到的寄存器,也是最多接触的寄存器

64-bit32-bit16-bit8-bit (low)
RAXEAXAXAL
RBXEBXBXBL
RCXECXCXCL
RDXEDXDXDL
RSIESISISIL
RDIEDIDIDIL
RBPEBPBPBPL
RSPESPSPSPL
R8R8DR8WR8B
R9R9DR9WR9B
R10R10DR10WR10B
R11R11DR11WR11B
R12R12DR12WR12B
R13R13DR13WR13B
R14R14DR14WR14B
R15R15DR15WR15B

其中16位的寄存器中,可以访问其高8位的数据 十六位再拆

16-bit8-bit (high)
AXAH
BXBH
CXCH
DXDH

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位描述
raxeaxaxal累加器
rbxebxbxbl基地址
rcxecxcxcl循环记数器
rdxedxdxdl数据寄存器,通常扩展A寄存器
rsiesisisil字符串操作的源索引
rdiedididil字符串操作的目的索引
rbpebpbpbpl栈指针
rspespspspl基地址指针(栈帧基地址)
r8r8dr8wr8b新增通用寄存器
r9r9dr9wr9b新增通用寄存器
r10r10dr10wr10b新增通用寄存器
r11r11dr11wr11b新增通用寄存器
r12r12dr12wr12b新增通用寄存器
r13r13dr13wr13b新增通用寄存器
r14r14dr14wr14b新增通用寄存器
r15r15dr15wr15b新增通用寄存器

栈指针 rsp : 栈顶指针,指向栈的顶部 rbp : 栈底指针 指向栈的底部, 在x64下rbp已经很少使用了.

累加寄存器 eax/rax 累加寄存器 eax/rax被称为累加寄存器,它除了被用作一些加法和乘法指令的默认寄存器,还有几个默认用法:

  1. 若函数有返回值,它默认用来存放返回值。当然,eax/rax在函数中是可以使用的,只是在返回时(若有返回值)要存放着返回值。
  2. 在x86中,当要把一个数从32位扩展成64位,则eax存放低32位,edx存放高64位。
  3. 在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位标志寄存器增加的标志位

  1. 重启动标志RF 重启动标志RF用来控制是否接受调试故障。

RF=0时,表示“接受”调试故障,否则拒绝之。在成功执行完一条指令后,处理机把RF置为0,当接受到一个非调试故障时,处理机就把它置为1。

  1. 嵌套任务标志NT 嵌套任务标志NT用来控制中断返回指令IRET的执行。

当NT=0,用堆栈中保存的值恢复EFLAGS、CS和EIP,执行常规的中断返回操作;当NT=1,通过任务转换实现中断返回。

  1. I/O特权标志IOPL I/O特权标志用两位二进制位来表示,也称为I/O特权级字段。该字段指定了要求执行I/O指令的特权级。

如果当前的特权级别在数值上小于等于IOPL的值,那么,该I/O指令可执行,否则将发生一个保护异常。

  1. 虚拟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位 颜色指针本质上包含了地址映射的概念