1. 前置知识
1.1 大、小端序存储
小端序(Little-endian)和大端序(Big-endian)是计算机存储数据时两种不同的字节序排列方式。这两种方式定义了在内存中多字节数据(如整数、浮点数)的字节顺序。
- 大端序(Big-endian):
- 在大端序存储中,数据的高字节(即最重要的字节)存储在低地址处,而低字节(即最不重要的字节)存储在高地址处。
- 例如,假设有一个32位的整数
0x12345678
,其字节排列在内存中的顺序是:
地址 数据 0x00 0x12 0x01 0x34 0x02 0x56 0x03 0x78
- 大端序的优点是它符合人类阅读的自然顺序(从左到右)。在网络协议中,通常采用大端序来标准化数据格式,称为网络字节序。
- 小端序(Little-endian):
- 在小端序存储中,数据的低字节(即最不重要的字节)存储在低地址处,而高字节(即最重要的字节)存储在高地址处。
- 例如,对于同样的32位整数
0x12345678
,其字节排列在内存中的顺序是:
地址 数据 0x00 0x78 0x01 0x56 0x02 0x34 0x03 0x12
- 小端序的优点是对较小的数据(如一个字节)进行读写操作时,无需重新排列字节,从而可以提高效率。许多现代的计算机体系结构(如Intel x86系列处理器)使用小端序。
具体区别可以参见下图:
总结:
- 大端序:高字节在低地址,符合人类的自然阅读顺序。
- 小端序:低字节在低地址,常被认为对处理器更高效,也是编译器比较常用的。
1.2 汇编风格
Intel汇编风格和AT&T汇编风格是两种不同的汇编语言语法格式,它们在语法和操作数顺序上有所不同。
1.2.1 Intel汇编风格
- 操作数顺序:指令格式为 操作码 源操作数, 目标操作数。即先写源操作数,再写目标操作数。
- 示例:MOV AX, BX 表示将 BX 的值移动到 AX 寄存器中。
- 寄存器名:使用简短的寄存器名,如 AX, BX, CX 等。
- 符号:使用 [] 表示内存地址,如 MOV AX, [1234h] 表示从内存地址 1234h 读取数据到 AX 寄存器。
1.2.2 AT&T汇编风格
- 操作数顺序:指令格式为
操作码 源操作数, 目标操作数
。即先写源操作数,再写目标操作数,但操作数的顺序是反向的。 - 示例:
MOV %bx, %ax
表示将bx
的值移动到ax
寄存器中。 - 寄存器名:寄存器名前加
%
符号,如%ax
,%bx
,%cx
等。
- 符号:使用
()
表示内存地址,如MOV %ax, 1234(%bx)
表示将1234
加上bx
寄存器的值所指向的内存地址中的数据移动到ax
寄存器中。
总结
- Intel风格:操作数顺序为源在前,目标在后;寄存器名简短,无符号标记。
- AT&T风格:操作数顺序为源在前,目标在后;寄存器名带有
%
符号,内存地址使用()
符号。
显然,对于示例编译得到的exe程序,windows采用了小端序存储格式和Intel汇编风格。
1.3 .bss段与.data段
在程序内存布局中,.bss
段和.data
段都是存储全局变量和静态变量的重要区域。它们在程序编译和运行时的行为有很大不同。
1.3.1 .bss
段
功能:
.bss
(Block Started by Symbol)段用于存储那些在程序开始运行前未被初始化的全局变量和静态变量。- 在程序启动时,
.bss
段中的数据会被自动初始化为零。
特点:
- 未初始化数据:这些变量在源代码中没有被显式初始化,例如
int globalVar;
。它们默认值为零。 - 节省空间:由于
.bss
段的数据在程序文件中不占用实际的空间(只在内存中存在),这可以减少程序的文件大小。 - 内存分配:虽然
.bss
段在程序的二进制文件中不占用空间,但在程序加载到内存时,操作系统会为这些变量分配足够的内存空间,并将其初始化为零。
例子:
int globalVar; // 一个.bss段中的变量,因为它没有初始化赋值
1.3.2 .data
段
功能:
.data
段用于存储已初始化的全局变量和静态变量。这些变量在程序启动前已经有了确定的值。
特点:
- 已初始化数据:这些变量在源代码中有明确的初始值,例如 int initializedVar = 10;。
- 占用空间:.data段中的数据在程序文件中是有实际大小的,因为它们的初始值被存储在程序的二进制文件中。
- 内存分配:当程序被加载到内存中,操作系统会将这些变量的初始值从程序文件中读取到内存中。
例子:
int initializedVar = 10; // 一个.data段中的变量,具有有一个初始值
总结
.bss
段:存储未初始化的全局变量和静态变量;在程序启动时被初始化为零;节省程序文件空间,但在内存中占用空间。.data
段:存储已初始化的全局变量和静态变量;程序文件中有实际的初始值;在内存中保留这些初始值。
2. 在内存中存储数据
若想在内存中存储常量,则需要将数据存放在.data段
首先打开程序内存布局,双击.data段,将.data段数据转储到内存1或2的地方。
2.1 存储纯数字常数
- Step1:在转存的.data段选中某地址部分数据【以四字节为例】,右键进行编辑内存数据。
- 假设需要将0x12345678存入指定地址0x7FF7D380B170,由于程序采用小端序存储模式,需要将数据编写为 \x78\x56\x34\x12如下图所示:
- Step 2:接着将写入数据的地址内存复制给rax寄存器,改写汇编指令如下:
- Step 3:最后步过运行,即可在寄存器窗口查看到指定的数据已经被成功传递给rax寄存器。
Step 4 :最后尝试将rax寄存器的数据存储到另外一个地址0x7FF7D38B00,修改汇编指令最后查得该地址数据成功被改写。
最后,如果需要保存修改,导出补丁文件即可。之后将该补丁导入初始程序可以直接看到 “手动修改过的“ 数据已经固化在那个地址。【以上步骤中只有0x7FF7D380B170处数据满足,其他通过代码修改的数据则需要运行才会变化】
2.2 存储字符串常量
- Step 1:编辑某个地址数据,修改为字符串”hello world“
tips:字符串hex编码末尾需要加\x00作为标识符。
- Step 2:修改汇编代码将字符串所在地址复值给rax寄存器
- Step 3:查看寄存器窗口,rax寄存器值为存储字符串的地址,并且自动将字符串”hello world“解析出来了
总结
- 由于 .data段数据是固定的常量,因此如果手动修改了某个.data地址的数据,那么程序再次启动后该地址数据依然保存着被修改过的数据。
- 相对的,.bss段数据保存的是变量,每次程序启动都会进行刷新,即修改后的数据再次启动会被清除。
3. 在内存中存储变量
若想在内存中设置,则需要将数据存放在.bss段
- Step 1:内存布局中双击.bss段,将数据转储到内存框区域,接着选定地址进行更改数据。
- Step 2:修改.bss地址数据,编写指令将数据赋值给rax寄存器
Step 3:接着尝试将rax寄存器的数据复制到另一个地址,成功修改.bss地址数据。