166 lines
6.0 KiB
C++
166 lines
6.0 KiB
C++
#include <windows.h>
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include <fstream>
|
|
#include <thread>
|
|
#include <string>
|
|
#include <span>
|
|
#include <shlobj.h>
|
|
|
|
#pragma comment(lib, "shell32.lib")
|
|
|
|
void CreateConsole() {
|
|
AllocConsole();
|
|
FILE* f;
|
|
freopen_s(&f, "CONOUT$", "w", stdout);
|
|
freopen_s(&f, "CONOUT$", "w", stderr);
|
|
SetConsoleTitleW(L"Endfield Runtime Metadata Dumper");
|
|
}
|
|
|
|
std::vector<int> ParsePattern(std::string_view combo) {
|
|
std::vector<int> pattern;
|
|
for (size_t i = 0; i < combo.size(); ++i) {
|
|
if (combo[i] == ' ') continue;
|
|
if (combo[i] == '?') {
|
|
pattern.push_back(-1);
|
|
if (i + 1 < combo.size() && combo[i + 1] == '?') i++;
|
|
}
|
|
else {
|
|
char buffer[3] = { combo[i], combo[i + 1], 0 };
|
|
pattern.push_back(std::strtoul(buffer, nullptr, 16));
|
|
i++;
|
|
}
|
|
}
|
|
return pattern;
|
|
}
|
|
|
|
uintptr_t ScanPattern(uintptr_t base, size_t size, std::string_view signature) {
|
|
auto pattern = ParsePattern(signature);
|
|
uint8_t* pData = reinterpret_cast<uint8_t*>(base);
|
|
for (size_t i = 0; i < size - pattern.size(); ++i) {
|
|
bool found = true;
|
|
for (size_t j = 0; j < pattern.size(); ++j) {
|
|
if (pattern[j] != -1 && pData[i + j] != pattern[j]) {
|
|
found = false;
|
|
break;
|
|
}
|
|
}
|
|
if (found) return base + i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uintptr_t ResolveRip(uintptr_t instructionAddr, int instructionLen, int offsetToRead) {
|
|
int32_t relativeOffset = *reinterpret_cast<int32_t*>(instructionAddr + offsetToRead);
|
|
return instructionAddr + instructionLen + relativeOffset;
|
|
}
|
|
|
|
void DumpThread(HMODULE hModule) {
|
|
CreateConsole();
|
|
|
|
std::cout << "\n[+] Initializing Dumper..." << std::endl;
|
|
|
|
uintptr_t modBase = 0;
|
|
while (!modBase) {
|
|
modBase = reinterpret_cast<uintptr_t>(GetModuleHandleW(L"GameAssembly.dll"));
|
|
if (!modBase) std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
std::cout << "[+] GameAssembly: 0x" << std::hex << modBase << std::endl;
|
|
|
|
constexpr std::string_view sig = "48 89 05 ? ? ? ? 48 85 C0 0F 84 ? ? ? ? 4C 89 05 ? ? ? ? 48 63 88";
|
|
uintptr_t sigAddr = ScanPattern(modBase, 0x8000000, sig);
|
|
|
|
if (!sigAddr) {
|
|
std::cout << "[-] Pattern not found." << std::endl;
|
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
|
FreeConsole(); FreeLibraryAndExitThread(hModule, 0);
|
|
return;
|
|
}
|
|
std::cout << "[+] Signature: 0x" << std::hex << sigAddr << std::endl;
|
|
|
|
uintptr_t globalMetadataVar = ResolveRip(sigAddr, 7, 3);
|
|
std::cout << "[+] Global Var: 0x" << std::hex << globalMetadataVar << std::endl;
|
|
|
|
uintptr_t metadataPtr = 0;
|
|
std::cout << "[*] Waiting for pointer..." << std::endl;
|
|
for (int i = 0; i < 200; i++) {
|
|
metadataPtr = *reinterpret_cast<uintptr_t*>(globalMetadataVar);
|
|
if (metadataPtr != 0) break;
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
|
|
if (!metadataPtr) {
|
|
std::cout << "[-] Timed out waiting for pointer." << std::endl;
|
|
}
|
|
else {
|
|
std::cout << "[+] Pointer: 0x" << std::hex << metadataPtr << std::endl;
|
|
|
|
uint32_t magic = *reinterpret_cast<uint32_t*>(metadataPtr);
|
|
std::cout << "[*] Header Magic: 0x" << std::hex << magic << std::endl;
|
|
|
|
if (magic != 0xFAB11BAF) {
|
|
std::cout << "[-] Magic Bytes mismatch! Expected 0xFAB11BAF." << std::endl;
|
|
std::cout << "[-] Aborting dump." << std::endl;
|
|
std::this_thread::sleep_for(std::chrono::seconds(3));
|
|
FreeConsole();
|
|
FreeLibraryAndExitThread(hModule, 0);
|
|
return;
|
|
}
|
|
|
|
std::cout << "[+] Magic Bytes verified!" << std::endl;
|
|
|
|
MEMORY_BASIC_INFORMATION mbi{};
|
|
if (VirtualQuery(reinterpret_cast<LPCVOID>(metadataPtr), &mbi, sizeof(mbi))) {
|
|
std::cout << "[+] Region Size: " << std::dec << mbi.RegionSize << " bytes" << std::endl;
|
|
DWORD oldProtect;
|
|
if (VirtualProtect(reinterpret_cast<LPVOID>(metadataPtr), mbi.RegionSize, PAGE_EXECUTE_READWRITE, &oldProtect)) {
|
|
std::cout << "[+] Permissions set to RWX." << std::endl;
|
|
}
|
|
else {
|
|
std::cout << "[!] VirtualProtect failed. Dumping might fail." << std::endl;
|
|
}
|
|
|
|
std::wstring fullPath = L"global-metadata-dump.dat";
|
|
FILE* f = nullptr;
|
|
_wfopen_s(&f, fullPath.c_str(), L"wb");
|
|
if (f) {
|
|
size_t totalWritten = 0;
|
|
size_t chunkSize = 4096;
|
|
uint8_t* pData = reinterpret_cast<uint8_t*>(metadataPtr);
|
|
|
|
for (size_t i = 0; i < mbi.RegionSize; i += chunkSize) {
|
|
size_t toWrite = (mbi.RegionSize - i < chunkSize) ? (mbi.RegionSize - i) : chunkSize;
|
|
size_t w = fwrite(pData + i, 1, toWrite, f);
|
|
totalWritten += w;
|
|
if (w != toWrite) break;
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
if (totalWritten > 0) {
|
|
std::wcout << L"[SUCCESS] Dumped " << totalWritten << L" bytes to: " << fullPath << std::endl;
|
|
}
|
|
else {
|
|
std::cout << "[-] Write failed completely. Zero bytes written." << std::endl;
|
|
}
|
|
}
|
|
else {
|
|
std::cout << "[-] Failed to open file. Error: " << errno << std::endl;
|
|
}
|
|
VirtualProtect(reinterpret_cast<LPVOID>(metadataPtr), mbi.RegionSize, oldProtect, &oldProtect);
|
|
}
|
|
}
|
|
|
|
std::cout << "Unloading in 5s..." << std::endl;
|
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
|
FreeConsole();
|
|
FreeLibraryAndExitThread(hModule, 0);
|
|
}
|
|
|
|
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
|
|
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
|
|
DisableThreadLibraryCalls(hModule);
|
|
CloseHandle(CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)DumpThread, hModule, 0, nullptr));
|
|
}
|
|
return TRUE;
|
|
} |