- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 247
- Reaction score
- 7
Решил выкатить небольшой дамп для тех, кто ковыряет The Division 2. Тут классика работы с памятью через ReadProcessMemory и WriteProcessMemory. Если вам нужно было понимание того, как резолвить цепочки указателей в этом движке, этот пример как раз для вас.
Техническая часть:
Данный код считывает базовый адрес модуля и проходит по цепочке офсетов для доступа к нужному значению. В примере реализован цикл, который постоянно перезаписывает значение на "999" (своего рода фикс, чтобы не проседало).
Кто пробовал это на актуальных версиях? Есть ли сейчас триггеры на WPM в TD2 или игра пока еще «прощает» такие внешние манипуляции с памятью? Делитесь результатами тестов, если кто-то уже пытался внедрять этот код в свой лоадер.
Техническая часть:
Данный код считывает базовый адрес модуля и проходит по цепочке офсетов для доступа к нужному значению. В примере реализован цикл, который постоянно перезаписывает значение на "999" (своего рода фикс, чтобы не проседало).
Code:
Reading
#include <windows.h>
#include <iostream>
#include <vector>
#include <TlHelp32.h>
#include <Psapi.h>
// Get process ID by name
DWORD GetProcessIdByName(const wchar_t* processName) {
DWORD processId = 0;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);
if (Process32FirstW(snapshot, &entry)) {
do {
if (wcscmp(entry.szExeFile, processName) == 0) {
processId = entry.th32ProcessID;
break;
}
} while (Process32NextW(snapshot, &entry));
}
CloseHandle(snapshot);
}
return processId;
}
// Follow a pointer chain with multiple offsets
uintptr_t ResolvePointerChain(HANDLE hProcess, uintptr_t baseAddr, std::vector<uintptr_t> offsets) {
uintptr_t addr = baseAddr;
for (size_t i = 0; i < offsets.size(); ++i) {
if (!ReadProcessMemory(hProcess, (LPCVOID)addr, &addr, sizeof(addr), nullptr)) {
std::cerr << "Failed to read memory.\n";
return 0;
}
addr += offsets[i];
}
return addr;
}
int main() {
const wchar_t* processName = L"TheDivision2.exe";
DWORD pid = GetProcessIdByName(processName);
if (pid == 0) {
std::cerr << "Process not found.\n";
return 1;
}
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!hProcess) {
std::cerr << "Failed to open process.\n";
return 1;
}
// Base address from Cheat Engine pointer (you may need to get actual module base)
uintptr_t baseAddress = 0x06DE73D0;
// Get actual module base of the process
HMODULE hMods[1024];
DWORD cbNeeded;
uintptr_t moduleBase = 0;
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
moduleBase = (uintptr_t)hMods[0]; // Assuming first module is the main EXE
}
uintptr_t pointerBase = moduleBase + baseAddress;
std::vector<uintptr_t> offsets = { 0x18, 0x48, 0x8, 0x10, 0x240, 0x5C8, 0x148 };
uintptr_t finalAddr = ResolvePointerChain(hProcess, pointerBase, offsets);
int value = 0;
if (ReadProcessMemory(hProcess, (LPCVOID)finalAddr, &value, sizeof(value), nullptr)) {
std::cout << "Final value: " << value << std::endl;
} else {
std::cerr << "Failed to read final value.\n";
}
CloseHandle(hProcess);
return 0;
}
Write 999
#include <windows.h>
#include <iostream>
#include <vector>
#include <TlHelp32.h>
#include <Psapi.h>
DWORD GetProcessIdByName(const wchar_t* processName) {
DWORD processId = 0;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);
if (Process32FirstW(snapshot, &entry)) {
do {
if (wcscmp(entry.szExeFile, processName) == 0) {
processId = entry.th32ProcessID;
break;
}
} while (Process32NextW(snapshot, &entry));
}
CloseHandle(snapshot);
}
return processId;
}
uintptr_t ResolvePointerChain(HANDLE hProcess, uintptr_t baseAddr, std::vector<uintptr_t> offsets) {
uintptr_t addr = baseAddr;
for (size_t i = 0; i < offsets.size(); ++i) {
if (!ReadProcessMemory(hProcess, (LPCVOID)addr, &addr, sizeof(addr), nullptr)) {
std::cerr << "Failed to read memory.\n";
return 0;
}
addr += offsets[i];
}
return addr;
}
int main() {
const wchar_t* processName = L"TheDivision2.exe";
DWORD pid = GetProcessIdByName(processName);
if (pid == 0) {
std::cerr << "Process not found.\n";
return 1;
}
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!hProcess) {
std::cerr << "Failed to open process.\n";
return 1;
}
uintptr_t baseAddress = 0x06DE73D0;
HMODULE hMods[1024];
DWORD cbNeeded;
uintptr_t moduleBase = 0;
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
moduleBase = (uintptr_t)hMods[0]; // First module is usually the .exe
}
uintptr_t pointerBase = moduleBase + baseAddress;
std::vector<uintptr_t> offsets = { 0x18, 0x48, 0x8, 0x10, 0x240, 0x5C8, 0x148 };
uintptr_t finalAddr = ResolvePointerChain(hProcess, pointerBase, offsets);
int value = 999;
// Optionally: infinite loop to freeze the value
while (true) {
if (WriteProcessMemory(hProcess, (LPVOID)finalAddr, &value, sizeof(value), nullptr)) {
std::cout << "Value 999 written successfully." << std::endl;
} else {
std::cerr << "Failed to write value." << std::endl;
}
Sleep(100); // Sleep 100ms to avoid CPU overload
}
CloseHandle(hProcess);
return 0;
}
/* cut here */
//Write 999
#include <windows.h>
#include <iostream>
#include <vector>
#include <TlHelp32.h>
DWORD GetProcessIdByName(const wchar_t* processName) {
DWORD processId = 0;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);
if (Process32FirstW(snapshot, &entry)) {
do {
if (wcscmp(entry.szExeFile, processName) == 0) {
processId = entry.th32ProcessID;
break;
}
} while (Process32NextW(snapshot, &entry));
}
CloseHandle(snapshot);
}
return processId;
}
uintptr_t ResolvePointerChain(HANDLE hProcess, uintptr_t baseAddr, std::vector<uintptr_t> offsets) {
uintptr_t addr = baseAddr;
for (size_t i = 0; i < offsets.size(); ++i) {
if (!ReadProcessMemory(hProcess, (LPCVOID)addr, &addr, sizeof(addr), nullptr)) {
std::cerr << "Failed to read memory.\n";
return 0;
}
addr += offsets[i];
}
return addr;
}
int main() {
const wchar_t* processName = L"TheDivision2.exe";
DWORD pid = GetProcessIdByName(processName);
if (pid == 0) {
std::cerr << "Process not found.\n";
return 1;
}
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!hProcess) {
std::cerr << "Failed to open process.\n";
return 1;
}
uintptr_t baseAddress = 0x06DE73D0;
HMODULE hMods[1024];
DWORD cbNeeded;
uintptr_t moduleBase = 0;
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
moduleBase = (uintptr_t)hMods[0]; // First module is usually the .exe
}
uintptr_t pointerBase = moduleBase + baseAddress;
std::vector<uintptr_t> offsets = { 0x18, 0x48, 0x8, 0x10, 0x240, 0x5C8, 0x148 };
uintptr_t finalAddr = ResolvePointerChain(hProcess, pointerBase, offsets);
int value = 999;
// Optionally: infinite loop to freeze the value
while (true) {
if (WriteProcessMemory(hProcess, (LPVOID)finalAddr, &value, sizeof(value), nullptr)) {
std::cout << "Value 999 written successfully." << std::endl;
} else {
std::cerr << "Failed to write value." << std::endl;
}
Sleep(100); // Sleep 100ms to avoid CPU overload
}
CloseHandle(hProcess);
return 0;
}
[*] Убедитесь, что запускаете студию от имени администратора, иначе OpenProcess вернет ноль.
[*] Офсеты могут улетать после обновлений игры, мониторьте client_dll или основной экзешник.
[*] Использование бесконечного цикла с Sleep(100) — это самый простой способ держать значение, но не забывайте про риск обнаружения примитивных методов записи.
[*] Офсеты могут улетать после обновлений игры, мониторьте client_dll или основной экзешник.
[*] Использование бесконечного цикла с Sleep(100) — это самый простой способ держать значение, но не забывайте про риск обнаружения примитивных методов записи.
Кто пробовал это на актуальных версиях? Есть ли сейчас триггеры на WPM в TD2 или игра пока еще «прощает» такие внешние манипуляции с памятью? Делитесь результатами тестов, если кто-то уже пытался внедрять этот код в свой лоадер.