YuuJ13rのhome
首页项目归档照片墙音乐灵境说说杂谈友链关于
封面

汇编基础

写作时间:2026-09-27 03:57:54

一、寄存器:


‍

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的本质:

  1. push返回的地址:实际上就是call执行完的下一行的地址(返回地址)

  2. 进入到call的内部执行(相当于jmp到子程序)

  3. 执行完根据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

‍

十五、函数与栈结构

  1. 函数是程序的基本单位

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

‍

avatar

YuuJ13r

这个人很懒

RECOMMENDED

栈溢出防护与绕过技术

2026-09-27 23:33:57

Table of Contents