Add files via upload
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
### 在内存中已 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文件时 |
|
||||
| 调试器/反汇编器 | 最直观、最简单。提供可视化的界面,无需编写代码 | 依赖于外部工具。无法自动化(除非通过脚本) | 交互式调试、快速逆向工程 |
|
||||
Reference in New Issue
Block a user