汇编基础:逻辑运算、寄存器与堆栈
一、逻辑运算(位运算)
引入:计算机只认识二进制(0和1),它是如何进行复杂的加减乘除、加密解密等运算的呢?
答案就是通过逻辑运算(位运算) 来实现。这是CPU运算的基石。
1.1 基本逻辑运算
或运算(OR)
- 规则:两个操作位中,只要有一个为1,结果就为1。相当于电路中的“并联”开关,任一开关闭合,灯泡就会亮。
- C语言符号:
| - 汇编指令:
OR

1 | 1011 0001 0101 0101 |
与运算(AND)
- 规则:两个操作位中,都为1时,结果才为1。相当于电路中的“串联”开关,必须两个开关都闭合,灯泡才会亮。
- C语言符号:
& - 汇编指令:
AND

1 | 1011 0001 0101 0101 |
异或运算(XOR)
- 规则:两个操作位,不同(相异)时为1,相同(都为0或都为1)时为0。
- 核心特性:一个数A与另一个数B异或两次(
A XOR B XOR B),结果会还原为A本身。这一特性使其在加密算法中非常常用。 - C语言符号:
^ - 汇编指令:
XOR
1 | 1011 0001 0101 0101 |
非运算(NOT)
- 规则:单操作数运算,按位取反。1变成0,0变成1。
- 汇编指令:
NOT

1 | NOT 1011 0001 0101 0101 |
左移运算(SHL)
- 规则:将二进制位整体向左移动指定位数。高位丢弃,低位补0。每左移一位,相当于乘以2。
- 汇编指令:
SHL
1 | 1011 0001 0101 0101 |
1.2 逻辑运算的应用
1. CPU如何做加法?(以 2+3 为例)
CPU只会做逻辑运算,所有算术运算(加、减、乘、除)都必须分解成逻辑运算和移位来实现。以下是 2+3 的计算过程:
2的二进制:00103的二进制:0011
第一步:不考虑进位的加法(异或)
1 | 0010 |
第二步:计算进位(与运算 + 左移)
1 | 0010 |
第三步:判断运算是否结束
判断第二步得到的进位值(0100)是否为 0。若不为 0,则需将第一步的结果(0001)和这个进位值(0100)作为新的加数,重复上述过程。
新一轮运算:
- 新加数X:
0001 - 新加数Y:
0100
重复第一步:
1 | 0001 |
重复第二步:
1 | 0001 |
此时,新进位值为 0,循环结束。最终结果就是 R 中的值 0101,即十进制的 5。
2. 一个简单的加密/解密算法(异或加密)
利用异或的特性,可以设计一个最简单的加密方案。
- 客户端(发送方)与服务器端(接收方)需要事先约定一个密钥。
- 加密过程:
密文 = 明文 XOR 密钥 - 解密过程:
明文 = 密文 XOR 密钥
示例:加密“20”
假设我们要发送的明文是 20,密钥是 26。
- 明文
20的二进制:0010 0100 - 密钥
26的二进制:0001 1010 - 加密(XOR):
1 | 0010 0100 (明文: 20) |
客户端将加密后的 14 发送给服务器。服务器收到后,用同样的密钥 26 进行解密:
- 解密(XOR):
1 | 0000 1110 (密文: 14) |
二、寄存器与内存
引入:我们知道了CPU怎么算,但计算的数据和结果该保存在哪里呢?
计算机中有两个核心的存储部件:CPU内部的寄存器和主板上的内存。它们本质都是存储0和1的“容器”,没有区别。
- 寄存器:集成在CPU内部,读写速度极快但容量小、价格昂贵。
- 内存:独立于CPU,读写速度相对慢但容量大、价格便宜。
2.1 32位通用寄存器
x86架构CPU提供了8个32位的通用寄存器,用于临时存储数据。它们是:
| 寄存器名 | 约定用途(了解即可,无需硬背) |
|---|---|
| EAX | 累加器,常用于存放函数返回值 |
| ECX | 计数器,常用于循环 |
| EDX | 数据寄存器 |
| EBX | 基址寄存器 |
| ESP | 堆栈指针,始终指向栈顶 |
| EBP | 基址指针,常用于访问堆栈内的参数和局部变量 |
| ESI | 源变址寄存器 |
| EDI | 目的变址寄存器 |
“32位”意味着每个寄存器都是一个能存放32个二进制位(即4字节)的容器。
2.2 如何使用寄存器(基础汇编指令)
1 | ; 1. 将立即数存入寄存器 |
三、内存
当数据量远超8个寄存器的容量时,就必须使用内存。
3.1 什么是32位/64位计算机?
这个位数的核心指的是CPU的寻址宽度,即它能访问的内存地址的最大范围。
- 32位CPU:地址总线宽度为32位,能表示的地址编号范围是
0x00000000到0xFFFFFFFF。每个地址指向一个字节,因此最大能直接管理的物理内存是2^32字节,即 4GB。 - 64位CPU:同理,能管理的理论内存上限远超4GB。
理解误区:不是因为“寄存器是32位的”才叫32位计算机,因为有些寄存器(如段寄存器)位数远超32位。
3.2 内存读写(寻址公式)
要读写内存,必须知道它的“门牌号”,即内存地址。以下是五种获取内存地址和读写数据的寻址方式。
说明:在编写汇编指令时,段寄存器前缀规则如下:
- 地址如果是直接的数字(立即数):用
DS。 - 地址中包含了
ESP或EBP:用SS。 - 地址中包含了
EDI:用ES。
(和16汇编有不同,在32位平坦模式下,段寄存器的作用主要是标识内存属性,与寻址本身关系不大,初学时可先按此规则套用。)
寻址公式一:[立即数]
1 | ; 读取内存的值(将地址0x213456里的4字节内容放到EAX中) |
寻址公式二:[reg]
1 | ; reg可以是任意通用寄存器 |
寻址公式三:[reg + 立即数]
1 | MOV ECX, 0x13FFD0 |
寻址公式四:[reg + reg * {1,2,4,8}]
注意:乘数只能是
1, 2, 4, 8。
1 | MOV EAX, 0x13FFD0 |
寻址公式五:[reg + reg * {1,2,4,8} + 立即数]
1 | MOV EAX, 0x13FFD0 |
四、堆栈
堆栈的本质就是一块被特殊管理的内存区域,用于存放函数调用过程中的临时变量、参数和返回地址等。
- 关键特性:堆栈的操作遵循 后进先出 的原则。
- 核心寄存器:
- ESP(堆栈指针):始终指向栈顶(低地址)。
- EBP(基址指针):常用来作为基准点,访问当前函数栈帧内的参数和局部变量。
- 重要备注:对于正向开发,堆栈细节常被编译器“透明化”;但对于逆向分析,堆栈是必须攻克的核心高地。
这里放堆栈图
4.1 基本堆栈操作
- PUSH(压栈/入栈):将数据放入堆栈。
ESP = ESP - 4(栈顶向低地址移动4字节)。- 将数据存入
[ESP]指向的新位置。
- POP(弹栈/出栈):从堆栈取出数据。
- 将
[ESP]当前指向的数据取出。 ESP = ESP + 4(栈顶向高地址回退4字节)。
- 将
1 | ; 观察下面的操作对ESP和EBP的影响 |
这里放执行图
4.2 STOS与REP指令组合
- STOS指令:将
EAX的值拷贝到EDI寄存器所指向的内存地址,并自动修改EDI。STOS DWORD PTR ES:[EDI]->[EDI] = EAX;EDI自动加/减4。
- REP指令:重复执行紧跟的字符串指令(如STOS),重复次数由
ECX决定。
1 | ; 组合用法示例:将EDI指向的内存区域,连续ECX次填充为EAX的值 |
这里放执行图
4.3 变形的艺术
逆向分析者通常会重点监控 PUSH、POP 等标志性指令。指令变形的核心思想是:使用其他功能等效的指令组合来代替这些敏感指令,增加分析难度,这是动态变形壳的基础技术
分析 PUSH EAX 到底做了什么?
ESP的值减4(栈顶提升)。- 将
EAX的值存入新的栈顶地址。
1 | ; 传统的 PUSH EAX |
同理,推导 POP ECX 的等效变形:
1 | ; 传统的 POP ECX |
挑战:
push esp和pop esp怎么变形
五、课后作业
逐步画出以下代码执行时,每一行指令结束后,堆栈区域的变化图。需清晰标注出 ESP 和 EBP 的位置,以及内存中被修改的部分。
1 | ; 假设执行前:ESP = 0x12FFC4, EBP = 0x12FFF0 |
提示:这不是一个可直接调试的程序,这是一个独立的手绘练习。请将这段代码“放入”你画的内存图中,一步步模拟执行,这能帮你深入理解函数调用的标准过程(__stdcall)以及栈帧的建立和销毁。这个练习是后续学习缓冲区溢出和函数调用约定的绝对基础。
汇编基础:逻辑运算、寄存器与堆栈