一、寄存器:

EAX:
累加寄存器(Accumulator),常用于算术运算、I/0 操作。Windows 的代码中通常用来存放结果(返回值)
EBX:
基址寄存器(Base),用于指向数组或数据结构的基址,
ECX:
计数寄存器(Count),常用于循环计数器或移位操作。在 Windows x86 调用约定(如 stdcall 和fastcall)中,ECX 通常用于传递函数的参数。具体来说,在 fastcall 调用约定中,ECX 存放函数的第一个参数。在 stdcall 调用约定中,参数通常是通过栈传递的,但 ECX 仍然可以用作 临时数据存储 或 循环计数器。
在面向对象的 C++编程中,this 指针指向当前对象的内存地址。当你调用类的成员函数时, this 指针会隐式地作为该成员函数的一个参数传递,在 C++中的成员函数 中, this 指针并没有一个固定的寄存器,而是由 编译器决定。对于 Windows x86 编译器(如 MSVC),通常情况下,this 指针会被传递给成员函数的 第一个参数,而这个参数的传递方式可能依赖于调用约定。具体来说,在 fastcall 调用约定中,第一个参数(通常是 this 指针)会存放在 ECX 寄存器中。
EDX:
数据寄存器(Data),用于存储乘法、除法的结果,或与 //O 操作相关。ESI:源索引寄存器(Source Index),用于指向源操作数,尤其是在字符串操作中
ESI:
原索引寄存器,指向源操作数,尤其是在字符串操作中。
EDI:
目的索引寄存器(Destination Index),用于指向目标操作数,尤其是在字符串操作中。
EBP:
基指针寄存器,保存栈帧的基地址(调用函数),也叫栈底
ESP:
堆栈指针寄存器,指向栈顶,栈的push、pop都依赖于它,也叫栈顶
EIP:
指令指针寄存器,指向CPU下一条指令的地址,64位中位rip。只存在于CPU中,没有汇编指令可以直接更改,通过异常处理机制可以修改EIP。VT技术中对EIP有虚化,所以可以对虚化的EIP寄存器直接修改。
在64位架构中,寄存器名称略有变化,但是功能类似:
RAX,RBX,RCX,RDX,RSI,RDI,RBP,RSP——这些是64位扩展版本,此外R8,R15也是64位架构中新增的通用寄存器。
二、汇编指令的基本结构:
1.指令:每条指令通常由操作码(Opcode)和操作数(Operand)组成。
操作码:告诉CPU执行什么操作,,如mov(数据传送)、add(加法)、sub(减法)等。
操作数:操作数可以是寄存器、内存地址、立即数(常数)等,指定操作对象。
如mov eax,5(从右向左):
mov为操作码,寄存器和常数是操作数,将常数(立即数)5存入寄存器eax。
mov eax,[0x41000000]:注意方括号之能用于内存地址,用来提取地址里的值。
扩展:假如ebx=0x00401000,那么mov eax,[ebx]也是合理的。
2.操作符(Operat):
汇编语言中执行运算的符号。
常见的算术操作符:
add:加法 | sub:减法 | mul;乘法 | div:除法。
常见的位操作符:
and:按位与 | or:按位或 | xor:按位异或 | shl:逻辑左移 | shr:逻辑右移。
如add eax,5:表示将eax的值加5,结果存入eax。
三、栈结构
1.栈的基本操作:
push(入栈):将数据压入栈顶
pop(出栈):将数据移移除并返回
2.站的变化须知:
如果不断push数据,数据向上堆积,栈地址不断变小

反之不断pop数据,栈地址会变大,堆积下沉
四、数据传输指令
用于在寄存器、内存、立即数之间传递数据

lea(load effective address):加载有效地址
五、FS与TEB
FS:
在 Windows 操作系统中,fs是一个段寄存器(Segment Register),它用于指向线程的线程环境块(TEB,Thread Environment Block)。 fs 寄存器主要用于访问当前线程的局部存储区域,而不是全局数据。这使得线程能够高效地存储和访问其专有的信息,而不需要锁定整个进程的数据。
在 32 位 Windows 操作系统中, fs 寄存器的默认值是指向当前线程的 TEB 的地址。具体来说:
fs:[0] 存储的是当前线程的 TEB 的基地址,通常被用来获取该线程的相关信息
TEB:
TEB(Thread Environment Block)是为每个线程分配的一个数据结构,用来存储线程的相关信息。每个线程都有一个独立的 TEB,它被用来存储多种信息,包括线程的本地存储、异常处理、TLS(线程局部存储)、栈指针等。每个线程都会通过 fs:[0]获取指向其 TEB 的指针。
进程是外壳,内部是由线程构成的
TEB结构详细说明:
32位Windows中,TEB结构大概如下:

偏移0x20存储了进程ID,偏移0x24线程的ID,偏移0x30指向进程环境块(PEB)的指针
PEB(ProcessEnvironmentBlock):指向进程环境块(PEB)的指针。PEB 存储了与当前进程相关的信息,例如进程的启动参数、模块信息、进程内存布局等。
例:
mov eax,dword ptr fs:[0x20] ;将进程ID读进eax
dword ptr fs:[0x20] ;指定操作双字,4字节,TEB中偏移0x20处的4字节
六、了解TEB、PEB、与反调试
PEB(进程环境块):
每个进程在Windows中都有的一个数据结构,包含与进程相关的重要信息。Windows内核将其保存在进程的虚拟地址空间中,通常位于进程的基址附近,并使用偏移量的形式列出各个字段。
结构:


PEB结构中主要根据这两个检测程序是否被调试分析(反调试技术)
重要字段:
BeingDebugged 等标志字段控制进程的不同状态,
InheretAddressSpaceLdr 指向进程加载器数据,记录进程已加载模块的列表(DLL、EXE 文件等)
ProcessParameters 指向进程的启动参数,包含了命令行、环境变量、启动路径等信息。
等字段包含内核与进程间的调用数
KernelCallbackTable、EventLogSection等字段包含内核与进程的调用数据。
七、算术运算指令
基本的数学运算指令:

mul(无符号乘法):
默认eax寄存器作为被乘数,算术结果低32位存储在eax,高32位存储在edx
mul ecx 或 mul eax ecx
imul(有符号乘法):
支持单、双、三操作数形式:
单操作数:默认被乘数在eax
双操作数:imul dest,scr,ecx(目标寄存器)
三操作数:imul dest,scr,imm,将scr * imm作为结果存入dest
div(无符号除法):
默认被除数是eax、edx组成的64位整数,商存储在eax,余数存储在edx
注意:确保edx已经清零
mov edx,0
idiv:
确保清零,并且用cdq指令开启edx符号:
符号扩展指令:cdq
mov eax,-15 ;低位被除数
cdq ;扩展符号位到edx
mov ecx,3 ;除数
idiv ecx
八、位操作指令
and(按位与):
and eax,ebx ;每一位相同为1,不相同为0
or(按位或):
or eax,ebx ;每一位有1则为1,否则为0
xor(按位异或):
xor eax,ebx ;对应的每一位相同为0,不相同为1
场景:
xor eax,0x5A ;用密钥0x5A为密钥进行加密
xor eax,0x1 ;反转状态
xor eax,eax ;清零
not(按位取反):
not eax ;每一位取反,生成补码
shl(无符号左移):
shl eax,1 ;左移一位,最左边舍去,左移相当于乘以2的幂
sal(有符号左移):
sal eax,1 同无符号,符号位会变化
sar(有符号右移):
sar eax,1 最右边舍弃,左边符号位不变,相当于有符号除以2的幂
rol(循环左移):
rol eax,1 左移,最左边到最右边
ror(循环右移):
ror eax,1
九、逻辑比较命令
cmp(Compare):
cmp eax,ebx
将eax与ebx相减,并根据结果更新标志寄存器(...FLAGS,存储执行命令后的状态信息),通常与条件跳转指令(如JE、JNE、JG、JL等)配合使用,以决定程序的执行流程
常见用途:
cmp eax,ebx ;比较两个值是否相等
JE equal_label ;如果eax==ebx,跳转到equal_label
JG equal_label ;如果eax>ebx,跳转到equal_label
相等则ZF(Zero Flag,0标志寄存器)被置为1,可以通过JE或者JZ执行相应跳转
test:
对两个操作数进行按位与运算,并根据结果更新标志寄存器
test eax,eax ;检查eax是否为0
JZ zero_label ;若为0跳转到zero_label
test eax,0x01 ;检查eax最低位是否为0
JZ zero_label ;若为0跳转
区别总结:
cmp:用于比较操作数之间的关系,会影响多个标志位(如ZF、SF、OF)
test:用于检查特定标志位
十、控制转移指令
操作符功能示例jmp无条件跳转jmp 0x42200000call调用子程序call 0x44400002ret返回到调用点ret 0x25250000je/jz若相等(zero flag置位)跳转je 0x20000000jne/jnz若不相等(zero flag未置位)jne 0x22000000jg若大于跳转jg 0x22220000jl若小于跳转jl 0x12340000jge大于等于跳转jge 0x22003300jle小于等于跳转jle 0x22001100loop循环跳转loop 0x23232200int调用中断int 0x25874500iret从中断返回iret 0x22110000
重点:
call的本质:
-
push返回的地址:实际上就是call执行完的下一行的地址(返回地址)
-
进入到call的内部执行(相当于jmp到子程序)
-
执行完根据push的返回地址,返回call下一行的执行代码(ret,相当于简单的jmp到返回地址)
dbg中F7步入子程序
cmp可能影响的标志位:
CF(carry flag):
设置条件:发生借位(第一个操作数小于第二个操作数)
ZF(zero flag):
设置条件:两个操作数相等(即结果为0)
SF(sign flag):
设置条件:结果为负(即最高有效位(符号位)为1)
OF(oveflow flag):
设置条件:有符号比较时发生溢出(即正数减负数得到负数或者负数减正数得到正数)
PF(parity flag):
设置条件:结果的最低字节中,1的位数为偶数
AF(auxiliary carry flag):
设置条件:在低四位操作中发生借位
总结:cmp影响的标志寄存器有CF、ZF、SF、OF、PF、ZF、AF
十一、栈操作指令
指令功能栈指针变化push将寄存器值、立即数或内存操作数压入栈顶esp-=4pop将栈顶的值弹出并存入寄存器或者内存操作数esp+-4pushad将通用寄存器(eax、ecx、edx、ebx、esp、ebp、esi、edi)的值按顺序压入栈esp-=32popad将(edi、esi、ebp、esp、ebx、edx、ecx、eax)的值按顺序从栈中弹出esp+=32pushfd将32位标志寄存器(eflags)压入栈顶esp-=4popfd从栈中弹出值并恢复到标志寄存器中esp+=4call调用子程序,将当前指令的返回地址压入栈顶,然后跳转到目标地址esp-=4ret从栈中弹出返回地址并跳转esp+=4enter设置栈帧:将当前ebp压入栈,然后设置新的ebp并为全局变量分配空间esp-=局部变量大小leave回复栈帧:将ebp的值恢复到esp,然后弹出原来的ebp的值esp+=局部变量大小int imm8软中断:将标志寄存器、代码段(CS)和指令指针(EIP)压入栈,然后跳转到中断向量表中的处理程序地址esp-=12iret中断返回:从栈中弹出标志寄存器、代码段(CS)和指令指针,恢复中断前的状态esp+=12
leave的本质:
x86汇编中的指令,常用于函数返回之前进行栈帧清理
总体流程图
leave
retn ;相当于pop rdi
等价于完整展开版:
mov rsp, rbp ;让rsp返回栈帧起点,相当于 “一口气释放所有局部变量空间”(sub rsp, 10h 的逆操作)
pop rbp ;弹出rbp(push rbp的逆操作)
此时:
rbp 回到 调用 main 的函数(如 _start)的栈帧
rsp 指向 返回地址
ret
十二、字符串操作指令
mov:
**作用:**字符串赋值给寄存器
mov eax,offset str_hello ;加载hello到eax
mov [edi],eax ;把eax的地址存入edi指向的位置
rep(无条件重复执行):
作用:重复执行指令
rep movsb ;重复批量复制字符串数据
rep 是重复指令前缀,结合movsb一次复制一个字节
ecx指定复制字节数,esi指定复制源地址,edi指定目标地址
mov ecx,length ;要复制的字节数
mov esi,sourse ;源字符串地址
mov edi,destination ;目标地址
rep movsb
repe等价于repz(equal或zero执行):
当zero flag(ZF)为1,即相等且ecx不为0时重复执行
常用于字符串比较指令,表示当相等时继续比较
repne等价于repnz(不相等或不为zero):
当zero flag(ZF)为0,即不相等且ecx不为0时重复执行
表示当不相等时继续比较
cmp(compare):
检测字符串是否匹配,本质为相减是否为0,然后更改标志寄存器而不保存计算结果,用于实现strcmp、strncmp等
cmpsb:
比较esi指向的字节与edi指向的字节
mov edi,offset str_password ;目标字符串
mov esi, offset str_userinput ;用户输入
mov ecx,lenth ;比较的字节长度
cld ;清除方向标志
repe cmpsb ;比较连段内存的字符串,相等则重复比较直到ecx清0
je match ;相等则跳转到match
scasb:
扫描字符串中的特定字符,扫描方向由DF(direction flag)决定,cld清楚DF从低地址扫描,std设置DF从高地址扫描
用于实现字符串查找,如strchr
mov edi, offset str_buffer ;指向要扫描的目标地址
mov al,'A' ;查找字符A
mov ecx,length ;设置查找长度
repne scasb ;扫描直到A结束
jnz not_found ;未扫描到则跳转到not_found
stosb:
向目标地址填充数据,填充0则为清空
mov edi,offset buffer ;目标地址
mov al,0 ;填充数据为0
mov ecx,lengh ;填充长度
cld ;设置方向标为递增
rep syosb ;填充
loadsb:
加载一个字节到al,并使esi自增1
mov esi,0x456544 ;加载源地址
mov edi,0x252525 ;目标地址
mov ecx,0x6 ;加载长度
cld ;从低字节开始
lodsb ;加载到al,相当于mov al,char
stosb ;向目标地址填充al中的数据
push、pop:
push offset str_hello ;将hello地址压栈
call printf ;调用printf打印
add esp,4 ;平衡堆栈
十三、浮点运算指令
1.FPU(Floating Point Unit,浮点运算单元):
CPU中负责浮点运算的模块
组成:
寄存器堆栈:
8个80位宽的浮点寄存器(ST0-ST7)以堆栈形式组织
通过pop和push模式操作寄存器
状态寄存器:
用于保护堆栈状态(如堆栈深度)和计算标志(C0-C3)
控制寄存器:
配置FPU的工作模式(精度舍入模式等)
2.x87指令集:
专门为FPU设计的一套指令集,操作对象是堆栈寄存器
常见指令:
Fld/Fst:加载/存储浮点数
FADD/FSUB:浮点数加减
FMUL/FDIV:乘除
FCOM/FUCOM:比较浮点数
fld dword ptr ss:[ebp-0x4] ;加载ebp-0x4的数据到ST(0)
fadd qword ptr ss:[ebp-0x8] ;ebp-0x8的数据与ST(1)相加
fstp qword ptr ss:[ebp-0x14] ;ST(0)的数据加载到ebp-0x14,并且弹出ST(0)
fld qword ptr ss:[ebp-0x14] ;加载
fst qword ptr ss:[ebp-0x14] ;加载到ebp-0x14,不弹出
dword和qword分别代表4字节和8字节,8字节会自动存储入两个相邻地址
3.SSE(Streaming SIMD Extensions)
Intel在x86处理器上引入的一套SIMD(单指令多数据)扩展指令集,独立于FPU:
采用xmm寄存器(每个128位宽)
支持并行处理多个浮点数
指令风格是xxxPS(处理单精度矢量)、xxxSD(处理多精度矢量)
优势:
无需堆栈操作,更快的浮点计算
并行处理提高性能
十四、函数
代码逻辑的最小单元,封装可复用代码,随时调用
汇编的样子:
call xxxx ;跳转到xxxx地址执行函数
注意:call会先把自己下一个地址压入栈作为函数最后的ret地址
C++:
//加法函数
int add(int a,int b){
return a+b;
}
//主函数
int main{
int result=add(1,3);
return 0;
}
汇编:
;main函数
push ebp
mov ebp,esp ;常见函数头
sub esp,10h ;分配栈空间
mov dword ptr [esp+8],1
mov dword ptr [esp+4],3 ;函数参数
call add ;跳转到add
mov [esp-4],eax ;返回值一般存储在eax,赋值到返回地址
mov esp,ebp
pop ebp ;这两行相当于leave
ret
;add函数
push ebp
mov ebp,esp
mov eax,[ebp+8] ;参数a
add eax,[ebp+12] ;a+b
leave
ret ;返回到call下一行命令(call自动压栈)
栈结构的作用:
传递参数、为局部变量分配空间
返回值一般存储在eax
十五、函数与栈结构
- 函数是程序的基本单位
2.函数的结构全部是栈结构
空函数的结构:
;函数开始
push ebp ;压入栈底
mov ebp,esp ;把esp赋值给ebp,用ebp寻址
sub esp,10 ;为局部变量分配空间
;函数结束
mov esp,ebp ;回收栈空间,再次回到扁平的空间
pop ebp ;弹出ebp,扔掉最初的底部(push后必须pop)
ret ;返回(哪来的回哪去)
嵌套函数的结构:
push ebp
mov ebp,esp
sub esp,0x10
push 3 ;函数的第二个参数
push 2 ;函数的第一个参数
call 加法函数.401041 ;函数中嵌套了一个add函数
mov esp,ebp
pop enp
ret
执行call后esp是返回地址,esp+4就是第一个参数,esp+8就是第二个参数
一般在mov ebp,esp之后第一个参数变为了esp+8,第二个参数为esp+c,此时就可以开始用ebp寻址,ebp+xxx就是函数参数,ebp-xxx就是局部变量

栈(栈帧)的作用:
保存返回地址
存储函数参数
存储局部变量
每次调用函数都会创建一个新的栈结构
思考:如果没有ebp,如何用esp寻址回溯参数?
十六、函数嵌套
函数嵌套堆栈图
----低地址----
0x0018FF00 [局部变量2] <- ebp-8
0x0018FF04 [局部变量1] <- ebp-4
0x0018FF08 [保存的旧EBP] <- EBP(当前栈帧基地址)
0x0018FF0C [返回地址] <- ebp+4
0x0018FF10 [函数参数1] <- ebp+8
0x0018FF14 [函数参数2] <- ebp+0xC
----高地址----
栈帧时当前函数执行的临时工作区,存储函数参数、返回地址、函数参数和局部变量
十七、函数调用约定
在x86调用约定中不同的调用约定(calling convention)决定了函数参数如何传递、栈如何管理以及局部变量如何分配
1.cdecl(C Declaration) - Windows 下都是外平栈(清空栈帧在外部操作)
特点:
- 参数传递:依次push压栈
- 栈清理(堆栈平衡):调用者(caller)外部清理(add esp,0x**)
- 返回值:存储在eax寄存器
- 常见场景:C语言默认约定,可变参函数(如printf)
push 3
push 2
push 1
call func
add esp,0xC ;堆栈平衡(外平栈),参数数量决定
2.stdcall(Standard call) - Windows下都是内平栈
特点:
- 参数传递:push依次压栈
- 堆栈平衡:函数内部(callee)清理栈
- 返回值:eax寄存器
- 常见场景:Windows API(如MessageBoxA)
push 3
push 2
push 1
call func
func:
push ebp ;保存旧栈基地址
mov ebp,esp ;建立新栈帧
sub esp,0x8 ;分配栈帧空间
mov dword ptr [ebp-4],10 ;局部变量a=10
mov dword ptr [ebp-8],20 ;局部变量b=20
;访问参数
mov eax,[ebp+8] ;第一个参数
mov ebx,[ebp+12]
mov ecx,[ebp+16]
mov esp,ebp ;恢复栈指针
pop ebp ;恢复旧的栈基地址
retn 0xC ;callee返回并清理栈空间
3.fastcall - Windows下都是内平栈
特点:
- 参数传递:前两个参数通过寄存器(ecx、edx)传输,后序参数通过push压栈传递
- 堆栈平衡:被调用者清理
- 返回值:eax存储
- 常见场景:高性能函数(如编译器优化后的代码)
mov ecx,1 ;第一个参数
mov edx,2 ;第二个参数
push 3 ;第三个参数压栈
call func
func:
push ebp
mov ebp,esp
sub esp,0x8
...
retn 0x4
4.thiscall(C++成员函数调用) - 可内可外
特点:
- 参数传递:this指针通过ecx传递,其余参数从右到左依次压栈
- 堆栈平衡:调用者或编译器决定
- 返回值:eax寄存器
- 常用场景:C++类成员函数
mov ecx,obj_ptr ;this指针存入ecx
push 2
push 1
call obj.method
add esp,8
5.系统调用约定(syscall)
特点:
- 参数传递:通过寄存器,eax为系统调用号,ebx、ecx等传递参数
- 堆栈平衡:内核处理无需手动操作
- 常见用法:linux系统调用
mov dword ptr ss:[ebp-c],eax
push 0
push 0
push 0
push 80000301
push 0
push 40
push 80000004
push 0
mov eax,dword otr ss:[ebp-C]
test eax,eax
jne cm第四弹.4028F5
mov eax,cm第四弹.403801
add esp,28
