news 2026/10/29 7:04:27

汇编基础与逆向

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
汇编基础与逆向

引言:

本文介绍是宏汇编语言基础以及MASM宏汇编编译器基本使用方法,不同汇编语言的指令集都大差不差,主要是学会指令的语法及使用,会使用反汇编语言来分析目标程序就行,不必过于纠结不用汇编之间的差异。以下是本人的学习记录,如有问题,请各位博友指出。

1.汇编语言的介绍

汇编语言就是机器语言(0或1)的人类可读版本。它用简短的助记符(如MOV、ADD、JMP)来代替二进制指令,用标号来代替内存地址。

比如,同样是把数字5存入寄存器,机器语言是:

10110000 00000101

而汇编语言是:

MOV AL, 5

汇编语言是基于苦涩、难懂的机器语言而改进的便于人理解的语言,并且现在的很多硬件设备都是用汇编语言作为主要语言进行程序烧入的,汇编语言是计算机语言史上的一座里程碑。

那么问题来了:为什么不直接用高级语言?因为汇编语言离硬件最近——它和机器语言几乎是一一对应的关系。每一行汇编代码,都直接对应一条CPU指令。这意味着:你用汇编写的每一行代码,都在精确指挥硬件如何工作。这种精确控制,是任何高级语言都无法提供的。

2.汇编语言的“三足鼎立”

架构典型应用特点
x86(Intel语法)Windows、传统PC目标在前,源在后:MOV eax, 1
x86(AT&T语法)Unix/Linux/GCC内联汇编源在前,目标在后:movl $1, %eax
ARM手机、嵌入式、M系列Mac精简指令集,移动时代王者

本文介绍的是x86 Intel语法。

3. 基础知识

3.1 进制

我们平时常用的进制有二进制、八进制、十进制、十六进制,它们的表示如下表。

名称表示例子
二进制0、11010
八进制0~712
十进制0~910
十六进制0~9、A~FA

注意:十六进制的A~F对于10进制的10~15,后面进制转换会用到。

3.2 进制转换

3.2.1 位权

一个数从右向左,每一位的权值都是“进制的不同次方”。

例如十进制:

123

从右往左:

1 2 3 ↓ ↓ ↓ 10² 10¹ 10⁰

所以:

123 = 1×10² + 2×10¹ + 3×10⁰ = 100 + 20 + 3 = 123

3.2.2 任意进制转换为十进制

核心公式:

N = aₙ×bⁿ + aₙ₋₁×bⁿ⁻¹ + ... + a₁×b¹ + a₀×b⁰

其中:

  • b:目标数字的原进制
  • a:每一位上的数字
  • 从最右边开始,位权从b⁰开始

简单记忆:

从右往左编号 0、1、2、3……,每一位乘以“进制的对应次方”,最后全部相加。

例如:

二进制转十进制
110101₂

从右向左标记:

1 1 0 1 0 1 5 4 3 2 1 0

因此:

110101₂ = 1×2⁵ + 1×2⁴ + 0×2³ + 1×2² + 0×2¹ + 1×2⁰ = 32 + 16 + 0 + 4 + 0 + 1 = 53

所以:

110101₂ = 53₁₀
八进制转十进制
753₈

从右向左标记:

7 5 3 2 1 0

计算:

753₈ = 7×8² + 5×8¹ + 3×8⁰ = 7×64 + 5×8 + 3 = 448 + 40 + 3 = 491

所以:

753₈ = 491₁₀
十六进制转十进制
3F2₁₆

其中:

F = 15

从右向左标记:

3 F 2 2 1 0

计算:

3F2₁₆= 3×16² + 15×16¹ + 2×16⁰ = 3×256 + 15×16 + 2 = 768 + 240 + 2 = 1010

所以:

3F2₁₆ = 1010₁₀

3.2.3 十进制转其他进制

十进制转换为二进制、八进制、十六进制,需要使用“除基取余法”。

例如:

十进制转二进制

例如:

45₁₀

不断除以 2:

45 ÷ 2 = 22 …… 1 22 ÷ 2 = 11 …… 0 11 ÷ 2 = 5 …… 1 5 ÷ 2 = 2 …… 1 2 ÷ 2 = 1 …… 0 1 ÷ 2 = 0 …… 1

余数从下往上读取:

101101

因此:

45₁₀ = 101101₂
十进制转八进制
83₁₀

不断除以 8:

83 ÷ 8 = 10 …… 3 10 ÷ 8 = 1 …… 2 1 ÷ 8 = 0 …… 1

从下往上读取:

123

因此:

83₁₀ = 123₈
十进制转十六进制
687₁₀

不断除以 16:

687 ÷ 16 = 42 …… 15 42 ÷ 16 = 2 …… 10 2 ÷ 16 = 0 …… 2

将余数转换成十六进制:

15 = F 10 = A

从下往上读取:

2AF

所以:

687₁₀ = 2AF₁₆

3.3 数据宽度及表示范围

单位所占位数字节数十六进制范围
字节(byte)810~FF
字(word)1620~FFFF
双字(dword)3240~FFFFFFFF
八字节(qword)6480-FFFFFFFFFFFFFFFF

3.4 大、小端字节序

字节序(Byte Order)是指多字节数据在内存中的存储顺序,主要有两种类型:大端字节序(Big-Endian)和小段字节序(Little-Endian)。

大端字节序

大端字节序是指数据的高位字节存储在内存的低地址端,而低位字节存储在内存的高地址端。大端字节序符合我们的阅读习惯,从左至右且符号位放在第一位,判断正负数的效率较高,但逻辑运行相对低。

小端字节序

小端字节序是指数据的低位字节存储在内存的低地址端,而高位字节存储在内存的高地址端。小端字节序在数据类型的转换时效率较高因为直接取低位数据值即可。

大端字节序和小端字节序的存储顺序示意图如下。

存储十六进制0x12345678

应用场景

  • 计算机内部处理:大多数现代计算机的内部处理使用小端字节序,因为这种方式能更有效地利用计算机电路。
  • 网络传输:在网络传输中,通常使用大端字节序。如TCP/IP协议使用大端字节序,因此在网络通信中需要考虑字节序的转换问题,以确保发送方和接受方对多字节数据的解释一致。

我们的计算机通常是小端字节序存储数据。

可通过一下程序来判断字节自己计算机的存储方式。

取值比较法

#include<windows.h> #include<stdio.h> int main(){ DWORD dwSmallNum=0x01020304; if(*(BYTE*)&dwSmallNum==0x04) printf("Small Sequence.\r\n"); else printf("Big Sequence.\r\n"); return 0; }

直接转换比较法

#include<stdio.h> #include<winsock2.h> #pragma comment(lib,"win2_32") int main(){ DWORD dwSmallNum=0x01020304; if(dwSmallNum==htonl(dwSmallNum)) printf("Big Sequence.\r\n"); else printf("Small Sequence.\r\n"); return 0; }

3.5 编译器安装

工欲善其事,必先利其器,本文用到的是32位的宏汇编编译器。工具链接:百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固,支持教育网加速,支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.baidu.com/s/1Nz-tZKrg5t_T1sJU8H-V1g?pwd=8tiv将压缩包解压后分别将编译器文件夹绝对路径和文件夹中的bin文件夹绝对路径添加到环境变量中即可在cmd中正常使用。

3.6 汇编语言程序格式

操作码与操作数

汇编语言程序本质上由一条条指令构成,而一条指令通常由操作码和操作数(地址码)组成。

操作码用于说明“要做什么操作”。

比如:

  • MOV:数据传送
  • ADD:加法
  • SUB:减法
  • JMP:无条件跳转
  • CMP:比较

操作数用于说明“对谁进行操作”或者“操作的数据在哪里”。

比如:

MOV AX BX
│ │ │
│ │ └── 源操作数
│ └──────── 目标操作数
└────────────── 操作码

表示将BX中的数据传送到AX中。

标识符

标识符包括标号(Label)和名字(Name)都是由程序员自定义,主要用于表示程序中某个存储位置的逻辑地址,但使用场景不同。

对比标号名字
使用语句执行性语句说明性语句
分隔方式通常用冒号:通常用空格或制表符
主要表示指令在主存中的逻辑地址变量、段、子程序等的逻辑地址
主要作用指明分支、循环等的目的地址标识变量、段、子程序等
示例LOOP:DATA DB 10

如:

LOOP: ADD AX, BX JMP LOOP 其中 LOOP 是标号。 可以理解为: LOOP: ↓ 指向 ADD AX,BX 所在的逻辑地址 执行JMP LOOP就是跳转到 LOOP 对应的地址。
DATA DB 10 DATA → 名字 DB → 说明性语句中的操作 10 → 数据 DATA 表示这段数据所在的逻辑地址。

标识符最多由31个字母、数字以及规定的特殊符号(如_、$、?、@)组成,且不能由数字开头,也不能是汇编程序采用的保留字。保留字是编程语言本身需要使用的各种具有特殊含义的标识符,也称关键字,关键字详细可见下表。

类型示例作用
硬指令助记符MOV、ADD、SUB、JMP表示 CPU 执行的指令
伪指令助记符DB、DW、SEGMENT、ENDS指示汇编器如何处理程序
寄存器名AX、BX、CX、DX表示 CPU 寄存器
其他特殊符号/关键字PROC、ENDP、ASSUME等具有特定语法含义

执行性语句:

标号: 硬指令助记符 目标操作数,源操作数 ;注释

LOOP: MOV AX, BX ; 将BX的内容送入AX

说明性语句:

名字 伪指令助记符 参数,参数,....... ;注释

msg db"hello, assembly!",13,10,0 ;定义一个字节类型的字符串变量,变量名为msg(message的缩写)。13相当于回车符\r,10相当于换行符\n,0相当于字符串结束符(NULL终止符)

包含伪指令include

将常用的常量定义、过程说明、共享的子程序库等内容进行声明(相当于c语言中包含头文件的作用)。

include io32.inc ;包含I/O库文件,该库包含dismsg,dispint,readmsg,exit等函数(整个语句类似于从语言的# include <stdio.h>)

段的简化定义

段是把程序按照不同用途划分出来的一块逻辑区域。

一个汇编程序通常会划分为不同的段,例如:

汇编程序 │ ├── 代码段(CODE) │ └── 存放程序指令 │ ├── 数据段(DATA) │ └── 存放已经初始化的数据 │ ├── 未初始化数据段(DATA?) │ └── 存放未初始化的数据 │ └── 堆栈段(STACK) └── 存放堆栈数据

“段的简化定义”指的是使用.DATA、.CODE、.STACK等简化段定义伪指令来定义程序中的各个段,不需要像完整段定义那样写SEGMENT和ENDS。

数据段定义伪指令.DATA创建一个数据段,定义可读可写的变量等

代码段定义伪指令.CODE创建一个代码段,保留需要执行的可执行性语句

堆栈段定义伪指令.STACK创建一个堆栈段。

更多详情可参考汇编语言教程 | 汇编语言从入门到精通 | w3schools 中文网

3.7 编译器的简单使用

这是本次的示例代码:

;eg01.asm in windows console for MASM include io32.inc ;包含I/O库文件,该库包含dismsg,dispint,readmsg,exit等函数(整个语句类似于从语言的# include <stdio.h>) .data ;数据段开始标志 msg db"hello, assembly!",13,10,0 ;定义一个字节类型的字符串变量,变量名为msg(message的缩写)。13相当于回车符\r,10相当于换行符\n,0相当于字符串结束符(NULL终止符) .code ;代码段开始标志 start: ;程序起始执行位置 mov eax,offset msg ;使用offset这个操作数计算msg的偏移量,并将值传递给eax call dispmsg ;调用dispmsg这个库函数来显示eax中的字符串 exit 0 ;主程序结束位置 end start ;汇编代码结束位置

Notepad2.exe是MASM目录中的asm代码编辑器,可使用该编辑器直接编写汇编代码。

快速开发方法

  • 进入MASM目录,可从cmd窗口切换,也可直接双击WIN32.bat。

  • 生成可执行文件
make32 文件名 //也就说汇编代码.asm的前面部分

自定义编译

先生成目标模块文件(.obj)

ml /c /coff 汇编文件 #/c 只编译,不链接 #/coff 生成 COFF 格式 .obj

生成可执行文件(.exe)

link32 /subsystem:console 目标文件 /libpath:bin #/subsystem:console,指定子系统为控制台,生成控制台程序 #/libpath:bin,指定库文件搜索路径,告诉链接器到 bin 目录寻找 .lib 库文件

调试程序

指令:

ml /c /coff /Fl /Zi 汇编文件 #/Fl 生成 汇编列表文件 .lst,.lst 列表文件通常可以看到源代码、地址、机器码等汇编信息。 #/Zi 生成调试信息,用于调试程序,让调试器能够把机器指令和源代码对应起来,便于单步执行、查看变量等。

link32 /subsystem:console eg01.obj /debug /libpath:bin #/debug 调试信息,在生成的程序中保留/生成调试信息,便于调试

3.8 指令集

CPU 的指令集架构(ISA)可以按照指令设计思想分为两种典型类型:

  • CISC(Complex Instruction Set Computer):复杂指令集计算机
  • RISC(Reduced Instruction Set Computer):精简指令集计算机

二者的主要区别在于:

CISC 强调让单条指令完成更多功能,让软件轻松,硬件受累;RISC 强调使用简单、规整的指令,通过多条指令组合完成复杂任务,让硬件轻松,软件多写几条。

CISC

架构代表厂商/产品特点
x86/x86-64Intel、AMD、海光、兆芯

性能强、兼容性好、软件生态丰富;但功耗较高、硬件复杂

x86的32位版本,现已逐步被64位x86-64取代

IA--32Intel早期32位处理器(如Pentium)x86的32位版本,现已逐步被64位x86-64取代

RISC

架构代表厂商/产品特点
ARM苹果M系列、高通骁龙、华为鲲鹏、飞腾低功耗、高效能、移动设备霸主;服务器领域也在快速扩展
RISC-V阿里平头哥、国芯科技、SiFive完全开源、模块化、无版权费;生态正在快速建设中
MIPS龙芯(早期)简洁、优化方便;但生态逐渐萎缩,龙芯已转向自研LoongArch
Alpha申威高性能,主要用于超算和特定领域

3.9 计算机的基本结构

微型计算机系统的基本组成

  • 硬件:物理设备
  • 软件:程序和文档

现代计算机基本组成结构

汇编语言程序员将硬件抽象为:寄存器、存储器地址和输入输出地址。

3.10 寄存器

3.10.1 通用寄存器

通用寄存器(General Purpose Registers)是最常用的寄存器,用于存放运算数据和临时结果。

x86提供了8个常用的x86通用寄存器

32 位16 位高 8 位低 8 位主要用途
EAXAXAHAL累加器,存放函数返回值、算术运算结果
EBXBXBHBL基址寄存器,常用于存放内存基地址
ECXCXCHCL计数器,常用于循环计数和移位
EDXDXDHDL数据寄存器,存放乘除法的高位结果
ESISI——源变址寄存器,字符串操作的源地址
EDIDI——目的变址寄存器,常用于数组和字符串操作
EBPBP——基址指针,常用于定位栈帧
ESPSP——栈指针,指向当前栈顶

3.10.2 段寄存器

段寄存器用于指定当前使用的内存段。

常见的16位段寄存器有6 个:

段寄存器名称主要用途
CSCode Segment(代码段寄存器)指向当前正在执行的代码
DSData Segment(数据段寄存器)指向程序的数据
SSStack Segment(栈段寄存器)指向栈空间
ESExtra Segment(附加段寄存器)可用于额外的数据段
FSExtra Segment(附加段寄存器)现代系统中具有特殊用途
GSExtra Segment(附加段寄存器)现代系统中具有特殊用途

3.10.3 指针与变址寄存器

这些32位寄存器主要用于访问内存,存放内存地址:

寄存器名称主要作用
EIPInstruction Pointer(指令指针寄存器)指向下一条要执行的指令
ESPStack Pointer(栈指针寄存器)指向当前栈顶
EBPBase Pointer(基址指针寄存器)定位当前函数的栈帧
ESISource Index(源变址寄存器)保存源数据的地址或索引
EDIDestination Index(目的变址寄存器)保存目标数据的地址或索引

3.10.4 标志寄存器

标志寄存器(EFLAGS)是一个 32 位寄存器,每个比特位代表一个状态标志(Flag)。

你不能直接读写整个 EFLAGS,但 CPU 会根据运算结果自动更新这些标志位,条件跳转指令则根据标志位决定是否跳转。

常见标志位:

标志位名称作用
CFCarry Flag(进位标志)无符号运算产生进位或借位时置 1
PFParity Flag(奇偶标志)结果低 8 位中 1 的个数为偶数时置 1
AFAuxiliary Carry Flag(辅助进位标志)低 4 位向高 4 位产生进位或借位时置 1
ZFZero Flag(零标志)运算结果为 0 时置 1
SFSign Flag(符号标志)运算结果最高位为 1 时置 1
OFOverflow Flag(溢出标志)有符号运算发生溢出时置 1
DFDirection Flag(方向标志)控制字符串操作的地址增减方向
IFInterrupt Enable Flag(中断允许标志)控制是否允许可屏蔽中断
TFTrap Flag(陷阱标志)用于单步调试

参考:汇编语言 – 寄存器 | 菜鸟教程

待续...

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 23:19:11

mebdlts3.4.1安全启动全流程

为了让你彻底掌握 mebdlts 3.4.1 下的启动全貌&#xff0c;我将这次分析分为四个部分&#xff1a;物理内存地图与地址详解&#xff08;地基&#xff09;。结构与二进制深度剖析&#xff08;砖块&#xff09;。正常启动全流程代码与打印&#xff08;盖楼&#xff09;。验签失败案…

作者头像 李华
网站建设 2026/10/23 0:27:36

从AT条到核心回路:轨迹系列战斗框架的设计演进

《轨迹》系列从空之轨迹FC一路走到黎轨&#xff0c;表面上看是回合制&#xff0c;背地里却藏着一套非常讲究的“战力框架”。我说的战力框架不只是角色攻击力有多高&#xff0c;而是把行动顺序、技能资源、装备成长、敌人设计这些捆在一起的那套底层逻辑。很多玩家玩完一部作品…

作者头像 李华
网站建设 2026/10/20 6:07:56

OpenShell 使用指南:在 Windows 11 上自定义经典开始菜单

Windows 11 的开始菜单摆在屏幕正中间&#xff0c;还塞了一堆推荐内容&#xff0c;每次点开都觉得别扭。如果你也怀恋 Win7 时代那个干净利落的开始菜单&#xff0c;那 OpenShell 这名字你应该听过。它老牌&#xff0c;但绝对不过时&#xff0c;能直接把系统默认的开始菜单换掉…

作者头像 李华
网站建设 2026/10/28 22:17:20

开源终端环境OpenShell:统一Shell配置与性能优化实践

如果你也经历过这种场景——有两台电脑&#xff0c;一台是Mac&#xff0c;一台是公司的Linux工作站&#xff0c;偶尔还要ssh到客户的服务器上查日志——那你大概率体会过“同一个终端&#xff0c;完全不同的体验”的别扭。我原先的终端环境就是典型的“能用就行”&#xff1a;M…

作者头像 李华
网站建设 2026/10/19 13:41:56

.NET与Selenium浏览器自动化实战:从WebDriver到稳定运行的完整指南

我最初入坑浏览器自动化&#xff0c;完全是被逼的&#xff1a;运营那边需要把一批线下数据通过后台表单逐条录入&#xff0c;人工点了一天还没完成。我当时懒得再写一遍重复操作&#xff0c;就用.NET搭了个控制台程序&#xff0c;配合Selenium开着Chrome自动填表&#xff0c;一…

作者头像 李华