查看上一章节内容《逆向工程核心原理》第一~二章知识整理
对应《逆向工程核心原理》第三章到第五章内容
小端序标记法
- 字节序
-
- 多字节数据在计算机内存中存放的字节顺序
- 分为小端序和大端序两大类
- 大端序与小端序
BYTE b = 0x12;
WORD w = 0x1234;
DWORD dw = 0x12345678;
char str[] = "abcde";
TYPE | Name | 字节 | 大端序 | 小段序 |
BYTE | b | 1 | [12] | [12] |
WORD | w | 2 | [12][34] | [12][34] |
DWORD | dw | 4 | [12][34][56][78] | [78][56][34][12] |
char[] | str | 6 | [61][62][63][64][65][00] | [61][62][63][64][65][00] |
- 1 字节,在二进制是 8 位,在十六进制是 2 位
- 内存地址:从低到高增长(如
0x1000
→0x1001
→0x1002
)。 - 数据的高/低位:数值的权重高低。例如,
0x12345678
中:
-
- 高位字节是
0x12
(权重最大,像数字的“千位”), - 低位字节是
0x78
(权重最小,像数字的“个位”)
- 高位字节是
- 大端序:内存的低地址存储数据的高位字节
-
- 人类视角:从左到右(地址递增)阅读,数值顺序不变,符合直觉。
- 网络传输(网络字节序)、某些文件格式(如JPEG)
- 保存多字节数据很直观
- 小端序:低位字节在低地址,高位字节在高地址
-
- 硬件视角:低地址存低位字节,方便逐字节处理(如加法从低位开始)
- x86/x64架构CPU,内存中直接处理低位数据更高效
- 字符串:最后是以 NUll 结尾,a 的 ASCII 码十六进制是 0x61
-
- 字符串被保存在一个字符数组中,字符数组在内存中是连续的,无论采用大端序还是小端序,存储顺序都相同
在 OllyDbg 中查看 LittleEndian.exe 文件
#include "windows.h"
BYTE b = 0x12;
WORD w = 0x1234;
DWORD dw = 0x12345678;
char str[] = "abcde";int main(int argc, char *argv[])
{byte lb = b;WORD lw = w;DWORD ldw = dw;char *lstr = str;return 0;
}
直接给出 main() 函数的地址,按 Ctrl+G 跳转到 401000 地址处
可以看到我们的定义的变量 0x12 地址应该是 40AC40
可以发现,这些数据使用小端序存储
IA-32 寄存器
寄存器
- 寄存器是 CPU 内部用来存储数据的一些小型存储区域,有非常高的读写速度
- 与 RAM(随机存储器、内存)略有不同,CPU 访问 RAM 中的数据时要经过较长的物理路径,花费时间长
学习程序调试技术,需要学习调试器解析出的汇编指令
IA-32 提供了庞大的汇编指令,指令可以翻看 Intel 提供的用户手册了解,大部分汇编指令用于操作寄存器或检查其中的数据
IA-32 寄存器
IA-32 是英特尔推出的 32 位元架构,属于复杂的指令集架构,支持多种寄存器
基本程序运行寄存器是 IA-32 寄存器的一种
基本程序运行寄存器
E 表示扩展,表示该寄存器在 16 位 CPU(IA-16)时已存在,并且其大小在 IA-32 下由原 16 位扩展为 32 位
通用寄存器
- 4 字节 32 位,保存常量与地址
- 传送和暂存数据,参与算术逻辑运算,保存运算结果
- 各寄存器
-
- EAX:累加器(针对操作数和结果数据)
-
-
- 用在函数返回值中,所有 Win32 API 函数都会先把返回值保存到 EAX 再返回
-
-
- EBX:基址寄存器(DS 段中的数据指针)
- ECX:计数器(字符串和循环操作)
-
-
- 也可以用在循环命令(LOOP)中循环计数,每执行一次循环,ECX 减 1
-
-
- EDX:数据寄存器(I/O 指针)
以上 4 个寄存器主要用在算法运算(ADD、SUB、XOR、OR)指令中,常用来保存常量与变量的值
某些汇编指令(MUL、DIV、LODS)直接操作特定寄存器,执行这些命令,仅改变特定寄存器的值
-
- EBP:扩展基址指针寄存器(SS 段中栈内数据指针)
-
-
- 表示栈区域的基地址,函数被调用时保存 ESP 的值,函数返回时再把值返回 ESP,保证栈不会崩溃(即栈帧技术)
-
-
- ESI:源变址寄存器(字符串操作源指针)
- EDI:目的变址寄存器(字符串操作目标指针)
- ESP:栈指针寄存器(SS 段中栈指针)
-
-
- 指示栈区域的栈顶地址,某些指令(PUSH、POP、CALL、PET)可以直接用来操作 ESP
-
后四个寄存器主要用作保存内存地址的指针
- 对低 16 位兼容
-
- EAX:(0~31)32 位
- AX:(0~15)EAX 的低 16 位
- AH:(8~15)AX 的高 8 位
- AL:(0~7)AX 的低 8 位
段寄存器
- 段是一种内存保护技术。它把内存划分为多个区段,并为每个区段赋予起始地址。范围、访问权限等,以保护内存,
- 此外段还同分页技术一起用于将虚拟内存变更为实际物理内存。
- 段内存记录在 SDT(段描述符表)中,而段寄存器就持有这些 SDT 的索引
- 段寄存器由 6 中寄存器组成,每个寄存器大小为 2 字节 16 位,存放相应所在段的段基址
-
- CS:代码段寄存器
- SS:栈段寄存器
- DS:数据段寄存器
- ES:附加(数据)段寄存器
- FS:数据段寄存器
-
-
- 用于计算 SEH(结构化异常处理机制)、TEB(线程环境块)、PEB(进程环境块)等地址
-
-
- GS:数据段寄存器
- 每个段寄存器指向的段描述符与虚拟内存结合,形成一个线性地址,借助分页技术,线性地址最终被转换为实际的物理地址
-
- 不使用分页技术的操作系统中,线性地址直接变为物理地址
程序状态与控制寄存器
- EFLAGS:标志寄存器
-
- 大小为 4 字节 32 位,由原来 16 为 FLAGS 寄存器扩展而来
- 每位的值为 1 或 0
- ZF:零标志,若运算结果为 0,则值为 1,否则为 0
- OF:溢出标志,有符号整数溢出时,OF 值被设置为 1。MSB(最高有效位)改变时,其值也被设为 1
- CF:进位标志,无符号整数溢出时,其值也被设置为 1
指令指针寄存器
- EIP:指令指针寄存器
-
- 指令指针寄存器保存着 CPU 要执行的指令地址,其大小为 4 字节 32 位,由原 16 位 IP 寄存器扩展而来。
- 程序执行时,CPU 会读取 EIP 中一条指令的地址,传送指令到指令缓冲区后,EIP 寄存器的值自动增加,增加的大小即是读取指令的字节大小
- 这样,CPU 每次执行完一条指令,就会通过 EIP 寄存器读取并执行下一条指令
- 与通用寄存器不同,不能直接修改 EIP 的值,只能通过其他指令间接修改,比如 JMP,Jcc,CALL,RET,还可以通过中断或异常修改 EIP 的值
栈
栈内存在进程中的作用如下:
- 暂时保存函数内的局部变量
- 调用函数时传递参数
- 保存函数返回后的地址
栈是一种数据结构,按照 FILO(后进先出)的原则存储数据
栈的特征
一个进程中,栈顶指针(ESP)初始状态指向栈底端。
执行 PUSH 命令将数据压入栈时,栈顶指针就会上移到栈顶端。
执行 POP 命令从栈中弹出数据时,若栈为空,则栈顶指针重新移动到栈底端
栈是一种由高地址向低地址扩展的数据结构,栈是由下往上扩展的,逆向扩展
栈操作示例
打开 Stack.exe(调试过程中,寄存器的初始值与栈的初址会随运行环境的不同而不同)
可以看到栈顶指针的值为 19FF74,观察右下角的栈窗口,可以看到 ESP 指向的地址及其值
在代码窗口按 F7,执行 401000 地址处的 PUSH 100 命令
ESP 的值变为了 19FF70,比原来减少了 4 个字节,并且当前栈顶指针指向了 19FF20 地址,该地址保存的值就是 100
也就是说,执行 PUSH 命令时,数值 100 被压入栈,ESP 随之向上移动,值减少 4 个字节
再次按 F7,执行 401005 地址的 POP EAX 命令
可以发现,ESP 的值增加了 4 个字节,变为了 19FF74,变为了一开始的状态
也就是说,从栈中弹出数据后,ESP 随之向下移动
总结:栈顶指针在初始状态指向栈底。向栈压入数据时,栈顶指针减小,向低地址移动;从栈中弹出数据时,栈顶指针增加,向高地址移动。
- 总结
第 3 到 5 章初看时可能感觉很多都是记忆类的知识,不太容易理解,尤其是寄存器相关,可以大概先留个印象,初步学习时,知道这个术语在这里出现过就可以了。
关键的点是栈的理解,可以结合实例去弄明白栈的特征