必要性

  • 纯汇编语言开发
  • 高级语言内联汇编
  • Windows内核编程
  • 逆向分析

汇编语言种类

  • x86汇编语言
  • x64汇编语言
  • 伪指令
  • 高级数据表示
  • win32汇编入门
  • 内联汇编

硬编码

程序是由数据和指令构成,在底层都是二进制(通常以十六进制查看),本质上并无太大区别。CPU 仅通过指令指针(如 EIP/RIP)指向的内存地址来将其解析为指令还是数据。

  • 概念定义:在逆向分析中,“硬编码”通常指代机器码(Machine Code)操作码(Opcode)。它是 CPU 硬件能够直接识别、解码并执行的底层指令字节。
  • 与汇编的关系:汇编语言是硬编码的“助记符”。人读汇编,机器读硬编码。两者基本是一一对应的关系(经过汇编器编译)。
    • 例如:汇编指令 NOP 对应的硬编码是 90INT 3(断点)对应的硬编码是 CC
  • 在逆向工程中的应用
    • Patch(打补丁/爆破):通过修改可执行文件中的硬编码来改变程序逻辑。例如将条件跳转 JZ(常对应硬编码 74)修改为强制跳转 JMPEB)或用 NOP90)填充掉某段验证代码。
    • 特征码搜索:杀毒软件、游戏反作弊或外挂开发中,常通过定位一段特定的硬编码序列(Signature)来寻找关键函数或恶意代码。
    • Shellcode 开发:在漏洞利用(Pwn)时,通常需要手动构造一段去除了坏字符(如 00)的硬编码字节流注入到内存中让 CPU 执行。

指令编码的结构

根据 x86/x64 架构标准,一条完整的机器指令由多个字段组成,长度是可变的,但最大总长度不超过 15 Bytes。参考 image_beb032.png 中的示意图,其从左到右的标准结构拆解如下:

  1. Prefixes (指令前缀)
    • 可选,0~4 Bytes
    • 用于修改指令的默认执行行为。
    • Legacy Prefix: 包括段覆盖(如 FS:)、操作数大小覆盖(如 66)、地址大小覆盖(如 67)、LOCK前缀(F0)或重复前缀(REP 系列)。
    • REX Prefix: 仅在 64 位 (x64) 模式下存在,用于访问新增的 8 个通用寄存器 (R8-R15) 或是强制使用 64 位操作数大小。
  2. Opcode (操作码) 必有,1~3 Bytes
    • 指令的核心,决定了 CPU 具体要执行什么动作(例如 MOVADDJMP)。
  3. ModR/M (寻址方式/寄存器)
    • 可选,1 Byte
    • 如果指令需要用到内存寻址或涉及寄存器操作,通常会有这个字节。它由三部分按位(Bit)组成:
      • Mod (2 bits) + Reg/Opcode (3 bits) + R/M (3 bits)
  4. SIB (比例-变址-基址)
    • 可选,1 Byt
    • 当 ModR/M 字段不足以表达复杂的内存寻址时(例如数组寻址 [Base + Index * Scale + Disp]),会引入 SIB 字节。它也由三部分组成:
      • Scale (比例,如 1,2,4,8,占 2 bits) + Index (变址寄存器,占 3 bits) + Base (基址寄存器,占 3 bits)
  5. Displacement (位移/偏移量)
    • 可选,1, 2, 4 或 8 Bytes
    • 内存寻址时所用到的地址偏移值。
  6. Immediate (立即数)
    • 可选,1, 2, 4 或 8 Bytes
    • 直接硬编码在指令中的常量数据(例如指令 MOV EAX, 0x12345678 中的 0x12345678 就是立即数)。

指令前缀

指令前缀是可选的,最多可包含 4 个字节(每组最多选一个),主要用于修改指令的默认执行行为。按功能分组如下:

  1. LOCK 和 REPEAT 前缀 (LOCK & REPEAT)
    • F0LOCK。锁总线前缀。用于多核环境下确保接下来的内存操作是“原子操作”,防止被其他线程打断(常见于并发同步汇编指令,如 lock xadd)。
    • F2REPNE / REPNZ。当不相等或非零时,重复执行后面的字符串指令。
    • F3REP / REPZ。当相等或为零时,重复执行后面的字符串指令(常配合 movsdstosb 等指令用于内存拷贝或初始化)。
  2. 段前缀指令 (Segment Override)
    • 用于覆盖指令默认使用的段寄存器。在现代操作系统(如 Windows)的平坦内存模型中,CS/SS/DS/ES 的前缀基本不再起实际地址隔离作用,但 FS 和 GS 依然极其重要
    • 2E:CS (代码段)
    • 36:SS (堆栈段
    • 3E:DS (数据段)
    • 26:ES (附加数据段)
    • 64FS在 32 位逆向和免杀中非常关键。 32 位 Windows 中,FS 寄存器指向 TEB (线程环境块)。恶意代码常通过 FS:[0x30] 获取 PEB,进而动态遍历导出表寻找 API 地址,实现无导入表运行(特征隐藏)。
    • 65GS。在 64 位 Windows 环境中接替了 FS 的作用(例如 GS:[0x60] 指向 64 位的 PEB)。
  3. 操作数宽度前缀指令 (Operand-Size Override)
    • 66:用于切换默认的操作数大小。例如,在 32 位模式下,寄存器默认使用 32 位(如 EAX)。如果指令机器码前加了 66 前缀,CPU 就会将其降级作为 16 位操作数处理(如 AX)。
  4. 地址宽度前缀指令 (Address-Size Override)
    • 67:用于切换默认的内存寻址大小。例如,在 32 位环境下强制使用 16 位地址模式寻址,或者在 64 位环境下强制使用 32 位地址。

环境配置

VCwin32应用程序向导(选择空项目)属性高级入口点(main)生成依赖项(选择masm)添加汇编程(asm后缀)

扩展:asmdude

代码基本套路

.586  ;指令集
.MODEL flat,stdcall ;内存模式、语言模式、其他模式
includelib user32.lib ;调用静态库
includelib kernel32.lib 
ExitProcess PROTO,dwExitCode:DWORD ;伪指令
MessageBoxA PROTO hWnd:DWORD,lpText:BYTE,lpCaption:BYTE,uType:DWORD
;option
;option casemap:none,是否对大小写敏感
.data
;.data:数据,定义初始值
;.data?:未初始化数据段
;.const:常量数据段
;.code:代码段
;.stack:堆栈段,现在不需要,系统会自动调用
Number DWORD 0
text db "shellcode",0
.code
main proc
	mov eax,5
	mov ebx,6
	add eax,Number
	push 0
	push offset text
	push offset text
	push 0
	call MessageBoxA
	add esp,16
	call ExitProcess
main ENDP
END main.586  ;指令集
.MODEL flat,stdcall ;内存模式、语言模式、其他模式
includelib user32.lib ;调用静态库
includelib kernel32.lib 
ExitProcess PROTO,dwExitCode:DWORD ;伪指令
MessageBoxA PROTO hWnd:DWORD,lpText:BYTE,lpCaption:BYTE,uType:DWORD
;option
;option casemap:none,是否对大小写敏感
.data
;.data:数据,定义初始值
;.data?:未初始化数据段
;.const:常量数据段
;.code:代码段
;.stack:堆栈段,现在不需要,系统会自动调用
Number DWORD 0
text db "shellcode",0
.code
main proc
	mov eax,5
	mov ebx,6
	add eax,Number
	push 0
	push offset text
	push offset text
	push 0
	call MessageBoxA
	add esp,16
	call ExitProcess
main ENDP
END main

汇编使用:注释

汇编函数没有main,需要指定主函数

硬件知识

CPU的内部组成

  • 运算器进行信息处理
  • 寄存器进行信息存储
  • 控制器控制各种器件进行工作
  • 内部总线连接各种器件,在它们之间进行数据的传送

寄存器是程序员可以用指令读写的部件。程序员通过改变各种寄存器的内容实现对CPU的控制

image.png

通用寄存器

8086/8088 CPU寄存器组

包括4个16位数据寄存器,两个16位指针寄存器,,两个16位变址寄存器,一个16位指令执政,4个16位段寄存器,,1个16位标志寄存器,一共十四个16位寄存器 image.png

image.png

通用寄存器

数据寄存器

4个16位数据寄存器:AX、BX、CX、DX;主要用来保存操作数或运算结果等信息,它们的存在节省了为读取操作数所需占用总线和访问存储器的时间

控制寄存器

标志寄存器

image.png 8086/8088 CPU中有一个16位的标志寄存器,包含了9个标志,主要用于反映处理器的状态和运算结果的某些特征。各标志在标志寄存器中的位置如下所示:

image.png

有些指令的执行会影响部分标志,而有些指令的执行不会影响标志,反过来,有些指令的执行后某些标志的影响,有些指令不受标志的影响。编写程序时,需要充分注意指令与标志的关系。有些指令的执行会影响部分标志,而有些指令的执行不会影响标志,反过来,有些指令的执行后某些标志的影响,有些指令不受标志的影响。编写程序时,需要充分注意指令与标志的关系。

相关基础知识

度量单位

基本数据度量单位: 1BYTE=8BIT WORD=2BYTE=16BIT DWORD=4BYPE=32BIT QWORD=8BYPE=64BIT

数据存储度量单位 1KB=1024BYPE=8192BIT 1MB=1024KB 1GB=1024MB 1TB=1024GB

取值范围 BYPE=有符号:-128至127|无符号:0至255 WORD=有符号:-32768至32767|无符号:0至65535 DWORD=有符号:-2147483648至2147483647|无符号:0-4294967295 QWORD=有符号:-9223372036854775808至9223372036854775808

X86处理架构基本概念

基本微机设计

1d12bf9c76e3429e957c04113e948ce6.png

时钟周期

v2-464417834617a4920095c2ec93e5533d_1440w.jpg 时钟周期是处理器执行动作的最小时间单位。处理器的主频越高,其时钟周期就越短,执行操作的速度就越快。但是主频的提高也会带来其它问题,比如功耗和散热,因此现代CPU会权衡性能和功耗这两方面的影响;同时主频也做不到无限提高,因为电流的传输也是需要点时间的。

在计算机中,时钟周期提供了一个统一的时间基准,使得不同的硬件组件和指令能够按照同样的时间节奏进行操作。各个组件和指令可以根据时钟信号的跳变来确定何时开始和结束各自的操作,从而实现同步和协调。

加载执行程序

  1. 磁盘寻找文件
  2. 访问文件信息
  3. 加载程序至内存
  4. 执行指令,分配Process ID
  5. 进程自动运行
  6. 结束后从内存移除

操作模式

保护模式

保护模式是Intel 80286及后续x86系列处理器的核心运行模式,随80286处理器首次引入。该模式通过段级保护与特权级检查机制实现内存保护功能,支持分页系统和硬件虚拟内存管理,被Linux、FreeBSD及Windows 2.0之后版本等操作系统采用。处理器启动时默认进入实模式,需通过程序切换至保护模式

虚拟8086模式

8086 CPU 中寄存器总共为 14 个,且均为 16 位 。即 AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES 共 14 个。而这 14 个寄存器按照一定方式又分为了通用寄存器,控制寄存器和段寄存器。

实地址模式

实模式:(即实地址访问模式)它是Intel公司80286及以后的x86(80386,80486和80586等)兼容处理器(CPU)的一种操作模式。实模式被特殊定义为20位地址内存可访问空间上,这就意味着它的容量是2的20次幂(1M)的可访问内存空间(物理内存和BIOS-ROM),软件可通过这些地址直接访问BIOS程序和外围硬件。实模式下处理器没有硬件级的内存保护概念和多道任务的工作模式。但是为了向下兼容,所以80286及以后的x86系列兼容处理器仍然是开机启动时工作在实模式下。80186和早期的处理器仅有一种操作模式,就是后来我们所定义的实模式。实模式虽然能访问到1M的地址空间,但是由于BIOS的映射作用(即BIOS占用了部分空间地址资源),所以真正能使用的物理内存空间(内存条),也就是在640k到924k之间。1M地址空间组成是由16位的段地址和16位的段内偏移地址组成的。用公式表示为:物理地址=左移4位的段地址+偏移地址。

实模式寻址采用和8086相同的16位段和偏移量,最大寻址空间1MB,最大分段64KB。可以使用32位指令。32位的x86 CPU用做高速的8086。

系统管理模式

系统管理模式(SMM)是Intel处理器架构中的一种特殊模式,用于执行系统底层功能,如电源管理和安全防护。