文章目录
如果硬编码指令 48:8B05 01FB0C00
是固定的,可以通过解析该指令中的操作数来动态获取内存地址。48:8B05 01FB0C00
这条指令的含义是 mov rax, [rip + 0xC0FB01]
,其中 0xC0FB01
是相对地址偏移量。
你可以解析可执行文件中的指令,计算出 rip
相对于该指令的偏移量,然后读取这个内存地址的值。下面是如何在 C++ 中实现这个过程的一个示例:
- 查找目标进程的模块基址。
- 解析指令,计算目标地址。
- 使用
ReadProcessMemory
读取目标地址的值。
#include <windows.h>
#include <iostream>
#include <TlHelp32.h>
DWORD GetProcessIdByName(const std::wstring& processName) {
DWORD processId = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
if (Process32First(hSnapshot, &pe)) {
do {
if (processName == pe.szExeFile) {
processId = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
}
return processId;
}
uintptr_t GetModuleBaseAddress(DWORD processId, const std::wstring& moduleName) {
uintptr_t baseAddress = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, processId);
if (hSnapshot != INVALID_HANDLE_VALUE) {
MODULEENTRY32 me;
me.dwSize = sizeof(me);
if (Module32First(hSnapshot, &me)) {
do {
if (moduleName == me.szModule) {
baseAddress = reinterpret_cast<uintptr_t>(me.modBaseAddr);
break;
}
} while (Module32Next(hSnapshot, &me));
}
CloseHandle(hSnapshot);
}
return baseAddress;
}
int main() {
// 目标进程的名称和模块名称
std::wstring targetProcessName = L"v.exe";
std::wstring moduleName = L"v.exe";
// 获取目标进程的PID
DWORD processId = GetProcessIdByName(targetProcessName);
if (processId == 0) {
std::wcerr << L"Failed to find process: " << targetProcessName << std::endl;
return 1;
}
// 获取模块基址
uintptr_t moduleBase = GetModuleBaseAddress(processId, moduleName);
if (moduleBase == 0) {
std::cerr << "Failed to get module base address" << std::endl;
return 1;
}
// 硬编码指令的偏移量(例如假设它在模块基址的某个偏移处)
uintptr_t instructionOffset = 0x1000; // 需要根据实际情况调整
uintptr_t instructionAddress = moduleBase + instructionOffset;
// 读取内存中的指令
BYTE instruction[7];
SIZE_T bytesRead;
HANDLE hProcess = OpenProcess(PROCESS_VM_READ, FALSE, processId);
if (!hProcess) {
std::cerr << "Failed to open process" << std::endl;
return 1;
}
if (!ReadProcessMemory(hProcess, (LPCVOID)instructionAddress, instruction, sizeof(instruction), &bytesRead)) {
std::cerr << "Failed to read memory at instruction address" << std::endl;
CloseHandle(hProcess);
return 1;
}
// 解析指令以获取相对偏移量
uintptr_t relativeOffset = *reinterpret_cast<uint32_t*>(instruction + 3);
uintptr_t targetAddress = instructionAddress + 7 + relativeOffset;
// 读取目标地址的值
uint64_t value = 0;
if (ReadProcessMemory(hProcess, (LPCVOID)targetAddress, &value, sizeof(value), &bytesRead)) {
std::cout << "Value at address " << std::hex << targetAddress << ": " << std::hex << value << std::endl;
} else {
std::cerr << "Failed to read memory at target address" << std::endl;
}
// 关闭句柄
CloseHandle(hProcess);
return 0;
}
这个示例代码做了以下几件事:
- 使用
CreateToolhelp32Snapshot
和Process32First
/Process32Next
获取目标进程的 PID。 - 使用
CreateToolhelp32Snapshot
和Module32First
/Module32Next
获取目标模块的基址。 - 计算硬编码指令的实际地址。
- 读取指令并解析相对偏移量。
- 计算目标地址并读取其内存值。
请注意,instructionOffset
需要根据实际情况调整,这取决于硬编码指令在目标模块中的偏移位置。你需要确保这个偏移是正确的,以便正确解析指令和读取内存。
详细解释一下这两句代码的含义:
解析指令以获取相对偏移量
uintptr_t relativeOffset = *reinterpret_cast<uint32_t*>(instruction + 3);
这行代码从读取的指令字节中提取相对偏移量。具体步骤如下:
-
读取的指令: 我们读取了硬编码指令
48 8B 05 01 FB 0C 00
,这是7字节的指令。 -
指令结构:
48 8B 05
是指令的操作码部分(opcode),表示mov rax, [rip + offset]
。01 FB 0C 00
是32位(4字节)的相对偏移量部分(offset)。
-
指令偏移量: 我们需要从指令字节数组中提取偏移量部分。偏移量从第4个字节(
instruction[3]
)开始,占用4个字节。 -
解释代码:
instruction + 3
指向指令字节数组的第4个字节,即偏移量部分的起始位置。*reinterpret_cast<uint32_t*>(instruction + 3)
将这4个字节解释为一个32位无符号整数(uint32_t
),并将其值存储在relativeOffset
中。
计算目标地址
uintptr_t targetAddress = instructionAddress + 7 + relativeOffset;
这行代码计算目标地址。具体步骤如下:
-
指令地址:
instructionAddress
是硬编码指令在目标进程中的地址。 -
指令长度: 这条指令的总长度是7字节(
48 8B 05 01 FB 0C 00
),所以我们需要加上7。 -
相对偏移量:
relativeOffset
是我们从指令中提取出来的偏移量。这个偏移量是相对于下一条指令的地址的。 -
解释代码:
instructionAddress + 7
计算下一条指令的地址(即当前指令结束的位置)。instructionAddress + 7 + relativeOffset
将相对偏移量添加到下一条指令的地址上,得到最终的目标地址(targetAddress
)。
总结一下,这两句代码的作用是从硬编码指令中提取相对偏移量,并将其添加到指令结束位置,以计算目标地址。在计算目标地址后,可以使用 ReadProcessMemory
函数读取该地址的值。
以下是详细示例:
假设我们读取到的指令是 48 8B 05 01 FB 0C 00
,其含义为 mov rax, [rip + 0xC0FB01]
。这条指令表示从当前指令位置(rip
)加上 0xC0FB01
处读取数据并存储到 rax
寄存器。
假设指令的起始地址是 0x1000
,则计算过程如下:
- 指令长度:7字节
- 下一条指令地址:
0x1000 + 7 = 0x1007
- 相对偏移量:
0xC0FB01
- 目标地址:
0x1007 + 0xC0FB01 = 0xC0FB08
因此,目标地址是 0xC0FB08
。