Files
Sec-Interview/Chapter23/23-15.md
T
2025-09-25 04:02:19 +08:00

86 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### 在内存中已 Load 的程序如何快速找到其具有执行权限的段
**方法一:利用操作系统 API**
这是最常用、最稳定的方法。Windows 提供了强大的内存查询 API,可以快速遍历和检查一个进程的内存空间
1. **`VirtualQueryEx` 函数:** 这是最核心的 API。通过循环调用 `VirtualQueryEx`,你可以遍历整个进程的虚拟地址空间。这个函数会填充一个 `MEMORY_BASIC_INFORMATION` 结构体,其中包含了每个内存页面的信息,例如:
- `BaseAddress`:内存区域的起始地址
- `RegionSize`:内存区域的大小
- `State`:内存区域的状态(如已提交 `MEM_COMMIT`
- `Protect`:最重要的字段,描述内存区域的保护权限
2. **检查 `Protect` 字段:** `Protect` 字段是一个位掩码,你需要检查它是否包含代表**可执行**的标志。常见的可执行权限标志有:
- `PAGE_EXECUTE`
- `PAGE_EXECUTE_READ`
- `PAGE_EXECUTE_READWRITE`
- `PAGE_EXECUTE_WRITECOPY`
**示例代码(伪C++):**
```c++
#include <windows.h>
#include <iostream>
void FindExecutableRegions(HANDLE hProcess) {
MEMORY_BASIC_INFORMATION mbi;
LPVOID pBaseAddress = nullptr;
while (VirtualQueryEx(hProcess, pBaseAddress, &mbi, sizeof(mbi)) == sizeof(mbi)) {
// 检查内存状态,确保它已提交并有可执行权限
if (mbi.State == MEM_COMMIT &&
(mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))) {
// 找到了一个可执行的区域
std::cout << "Executable region found at: " << std::hex << mbi.BaseAddress
<< ", Size: " << mbi.RegionSize << " bytes" << std::endl;
}
// 移动到下一个内存区域
pBaseAddress = (LPVOID)((DWORD_PTR)mbi.BaseAddress + mbi.RegionSize);
}
}
int main() {
// 假设你已经获取了目标进程的句柄
// HANDLE hTargetProcess = OpenProcess(...);
// FindExecutableRegions(hTargetProcess);
return 0;
}
```
**方法二:利用 PEB(进程环境块)和 PE 结构**
对于已加载的 **PE 文件**,你可以直接解析其在内存中的结构来找到可执行段。这通常比遍历所有内存区域更快,但只适用于目标是主模块或已知的 DLL
1. **定位 PEB**
- 32 位:通过 `FS:[0x30]` 寄存器获取
- 64 位:通过 `GS:[0x60]` 寄存器获取
2. **获取 ImageBase** 从 PEB 中找到 `ImageBase` 字段,它存储了主模块在内存中的基址
3. **解析 PE 头部:**
- 从 `ImageBase` 开始,找到 `e_lfanew` 字段,它指向 **NT 头部(NT Header**
- 在 NT 头部中,找到**可选头部(Optional Header**
- 在可选头部中,找到**节表(Section Table**的偏移
4. **遍历节表:**
- 节表是一个 `IMAGE_SECTION_HEADER` 结构体数组
- 遍历这个数组,检查每个节的**特征(Characteristics**字段
- 寻找 `IMAGE_SCN_MEM_EXECUTE` 标志。如果这个标志被设置,那么这个节就是可执行的
5. **计算地址:** 找到可执行的节后,其在内存中的实际地址是 `ImageBase + VirtualAddress`
**方法三:利用调试器或反汇编器**
如果你在使用调试器(如 x64dbg 或 IDA Pro)或反汇编器(如 Ghidra),这个过程会变得非常直观
- **x64dbg**
- 打开内存视图(Memory Map),通常是快捷键 `Alt+M`
- 在这里,你会看到所有已加载的模块和内存区域的列表
- 列表会清楚地显示每个区域的权限(`R`、`W`、`X`),你可以直接找到所有带有 `X` 标志的区域
- **IDA Pro / Ghidra**
- 这些工具会自动解析 PE 文件并显示其所有节
- 你可以进入“段”(Segments)或“内存区域”(Memory Regions)视图
- 视图中会明确标记每个段的权限,通常`.text` 段会显示为 `EXECUTE` 权限
| 方法 | 优点 | 缺点 | 适用场景 |
| --------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -------------------------------------- |
| API调用 | 最通用、最稳定。能找到所有可执行内存区域,包括非PE结构(如JIT编译的代码) | 需要编写代码。性能相对较低,需要遍历整个虚拟内存空间 | 动态分析、恶意软件分析、注入器等 |
| PE结构解析 | 速度快,精确。能快速定位.text段和其它可执行的PE节 | 只能用于已加载的PE文件。如果程序有自解压或自修改行为,可能会失效 | 静态分析,当你知道目标是合法的PE文件时 |
| 调试器/反汇编器 | 最直观、最简单。提供可视化的界面,无需编写代码 | 依赖于外部工具。无法自动化(除非通过脚本) | 交互式调试、快速逆向工程 |