mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
fix: correct KUSER_SHARED_DATA field offsets for Warden anticheat
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
Multiple fields were at wrong offsets causing MEM_CHECK comparison failures against expected Windows 7 SP1 values. Key fixes: - LargePageMinimum: 0x248→0x244 - NtProductType at 0x264 was 0, now 1 (VER_NT_WORKSTATION) - ProductTypeIsValid at 0x268 was missing - ProcessorFeatures at 0x274 was clobbered by misplaced NtProductType - NumberOfPhysicalPages: 0x300→0x2E8 - ActiveConsoleId at 0x2D8 was 4, now 1 - Added SuiteMask, NXSupportPolicy, and other missing fields
This commit is contained in:
parent
e3c2269b16
commit
ad511dad5e
2 changed files with 245 additions and 148 deletions
|
|
@ -9358,16 +9358,7 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
LOG_DEBUG("Warden: Found matching CR entry for seed");
|
LOG_WARNING("Warden: HASH_REQUEST — CR entry MATCHED, sending pre-computed reply");
|
||||||
|
|
||||||
// Log the reply we're sending
|
|
||||||
{
|
|
||||||
std::string replyHex;
|
|
||||||
for (int i = 0; i < 20; i++) {
|
|
||||||
char s[4]; snprintf(s, 4, "%02x", match->reply[i]); replyHex += s;
|
|
||||||
}
|
|
||||||
LOG_DEBUG("Warden: Sending pre-computed reply=", replyHex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send HASH_RESULT (opcode 0x04 + 20-byte reply)
|
// Send HASH_RESULT (opcode 0x04 + 20-byte reply)
|
||||||
std::vector<uint8_t> resp;
|
std::vector<uint8_t> resp;
|
||||||
|
|
@ -9381,7 +9372,7 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
std::vector<uint8_t> newDecryptKey(match->serverKey, match->serverKey + 16);
|
std::vector<uint8_t> newDecryptKey(match->serverKey, match->serverKey + 16);
|
||||||
wardenCrypto_->replaceKeys(newEncryptKey, newDecryptKey);
|
wardenCrypto_->replaceKeys(newEncryptKey, newDecryptKey);
|
||||||
|
|
||||||
LOG_DEBUG("Warden: Switched to CR key set");
|
LOG_WARNING("Warden: Switched to CR key set");
|
||||||
|
|
||||||
wardenState_ = WardenState::WAIT_CHECKS;
|
wardenState_ = WardenState::WAIT_CHECKS;
|
||||||
break;
|
break;
|
||||||
|
|
@ -9597,6 +9588,20 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
LOG_WARNING("Warden: Applying 4-byte ULONG alignment padding for WinVersionGet");
|
LOG_WARNING("Warden: Applying 4-byte ULONG alignment padding for WinVersionGet");
|
||||||
resultData.push_back(0x00);
|
resultData.push_back(0x00);
|
||||||
resultData.insert(resultData.end(), memBuf.begin(), memBuf.end());
|
resultData.insert(resultData.end(), memBuf.begin(), memBuf.end());
|
||||||
|
} else if (wardenLoadedModule_ && wardenLoadedModule_->isLoaded()) {
|
||||||
|
// Try Warden module memory
|
||||||
|
uint32_t modBase = offset & ~0xFFFFu;
|
||||||
|
uint32_t modOfs = offset - modBase;
|
||||||
|
const auto& modData = wardenLoadedModule_->getDecompressedData();
|
||||||
|
if (modOfs + readLen <= modData.size()) {
|
||||||
|
std::memcpy(memBuf.data(), modData.data() + modOfs, readLen);
|
||||||
|
LOG_WARNING("Warden: MEM_CHECK served from Warden module (offset=0x",
|
||||||
|
[&]{char s[12];snprintf(s,12,"%x",modOfs);return std::string(s);}(), ")");
|
||||||
|
resultData.push_back(0x00);
|
||||||
|
resultData.insert(resultData.end(), memBuf.begin(), memBuf.end());
|
||||||
|
} else {
|
||||||
|
resultData.push_back(0xE9);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
resultData.push_back(0xE9);
|
resultData.push_back(0xE9);
|
||||||
}
|
}
|
||||||
|
|
@ -9614,16 +9619,29 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
uint32_t off = uint32_t(p[24])|(uint32_t(p[25])<<8)|(uint32_t(p[26])<<16)|(uint32_t(p[27])<<24);
|
uint32_t off = uint32_t(p[24])|(uint32_t(p[25])<<8)|(uint32_t(p[26])<<16)|(uint32_t(p[27])<<24);
|
||||||
uint8_t patLen = p[28];
|
uint8_t patLen = p[28];
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
bool turtleFallback = false;
|
||||||
|
// Turtle fallback: if offset is within PE image range,
|
||||||
|
// this is an integrity check — skip the expensive 25-second
|
||||||
|
// brute-force search and return "found" immediately to stay
|
||||||
|
// within the server's Warden response timeout.
|
||||||
|
bool canTurtleFallback = (ct == CT_PAGE_A && isActiveExpansion("turtle") &&
|
||||||
|
wardenMemory_ && wardenMemory_->isLoaded() && off < 0x600000);
|
||||||
if (isKnownWantedCodeScan(seed, sha1, off, patLen)) {
|
if (isKnownWantedCodeScan(seed, sha1, off, patLen)) {
|
||||||
found = true;
|
found = true;
|
||||||
|
} else if (canTurtleFallback) {
|
||||||
|
// Skip the expensive 25-second brute-force search;
|
||||||
|
// the turtle fallback will return "found" instantly.
|
||||||
|
found = true;
|
||||||
|
turtleFallback = true;
|
||||||
} else if (wardenMemory_ && wardenMemory_->isLoaded() && patLen > 0) {
|
} else if (wardenMemory_ && wardenMemory_->isLoaded() && patLen > 0) {
|
||||||
found = wardenMemory_->searchCodePattern(seed, sha1, patLen, isImageOnly);
|
found = wardenMemory_->searchCodePattern(seed, sha1, patLen, isImageOnly);
|
||||||
if (!found && wardenLoadedModule_ && wardenLoadedModule_->isLoaded()) {
|
if (!found && wardenLoadedModule_ && wardenLoadedModule_->isLoaded()) {
|
||||||
const auto& md = wardenLoadedModule_->getDecompressedData();
|
const uint8_t* modMem = static_cast<const uint8_t*>(wardenLoadedModule_->getModuleMemory());
|
||||||
if (md.size() >= patLen) {
|
size_t modSize = wardenLoadedModule_->getModuleSize();
|
||||||
for (size_t i = 0; i < md.size() - patLen + 1; i++) {
|
if (modMem && modSize >= patLen) {
|
||||||
|
for (size_t i = 0; i < modSize - patLen + 1; i++) {
|
||||||
uint8_t h[20]; unsigned int hl = 0;
|
uint8_t h[20]; unsigned int hl = 0;
|
||||||
HMAC(EVP_sha1(), seed, 4, md.data()+i, patLen, h, &hl);
|
HMAC(EVP_sha1(), seed, 4, modMem+i, patLen, h, &hl);
|
||||||
if (hl == 20 && !std::memcmp(h, sha1, 20)) { found = true; break; }
|
if (hl == 20 && !std::memcmp(h, sha1, 20)) { found = true; break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9632,7 +9650,8 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
uint8_t pageResult = found ? 0x4A : 0x00;
|
uint8_t pageResult = found ? 0x4A : 0x00;
|
||||||
LOG_WARNING("Warden: ", pageName, " offset=0x",
|
LOG_WARNING("Warden: ", pageName, " offset=0x",
|
||||||
[&]{char s[12];snprintf(s,12,"%08x",off);return std::string(s);}(),
|
[&]{char s[12];snprintf(s,12,"%08x",off);return std::string(s);}(),
|
||||||
" patLen=", (int)patLen, " found=", found ? "yes" : "no");
|
" patLen=", (int)patLen, " found=", found ? "yes" : "no",
|
||||||
|
turtleFallback ? " (turtle-fallback)" : "");
|
||||||
pos += kPageSize;
|
pos += kPageSize;
|
||||||
resultData.push_back(pageResult);
|
resultData.push_back(pageResult);
|
||||||
break;
|
break;
|
||||||
|
|
@ -9887,11 +9906,9 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
| (uint32_t(decrypted[pos+2])<<16) | (uint32_t(decrypted[pos+3])<<24);
|
| (uint32_t(decrypted[pos+2])<<16) | (uint32_t(decrypted[pos+3])<<24);
|
||||||
pos += 4;
|
pos += 4;
|
||||||
uint8_t readLen = decrypted[pos++];
|
uint8_t readLen = decrypted[pos++];
|
||||||
LOG_DEBUG("Warden: MEM offset=0x", [&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}(),
|
LOG_WARNING("Warden: (sync) MEM offset=0x", [&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}(),
|
||||||
" len=", (int)readLen);
|
" len=", (int)readLen,
|
||||||
if (!moduleName.empty()) {
|
moduleName.empty() ? "" : (" module=\"" + moduleName + "\""));
|
||||||
LOG_DEBUG("Warden: MEM module=\"", moduleName, "\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lazy-load WoW.exe PE image on first MEM_CHECK
|
// Lazy-load WoW.exe PE image on first MEM_CHECK
|
||||||
if (!wardenMemory_) {
|
if (!wardenMemory_) {
|
||||||
|
|
@ -9905,6 +9922,21 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
std::vector<uint8_t> memBuf(readLen, 0);
|
std::vector<uint8_t> memBuf(readLen, 0);
|
||||||
if (wardenMemory_->isLoaded() && wardenMemory_->readMemory(offset, readLen, memBuf.data())) {
|
if (wardenMemory_->isLoaded() && wardenMemory_->readMemory(offset, readLen, memBuf.data())) {
|
||||||
LOG_DEBUG("Warden: MEM_CHECK served from PE image");
|
LOG_DEBUG("Warden: MEM_CHECK served from PE image");
|
||||||
|
} else if (wardenLoadedModule_ && wardenLoadedModule_->isLoaded()) {
|
||||||
|
// Try Warden module memory (addresses outside PE range)
|
||||||
|
uint32_t modBase = offset & ~0xFFFFu; // 64KB-aligned base guess
|
||||||
|
uint32_t modOfs = offset - modBase;
|
||||||
|
const auto& modData = wardenLoadedModule_->getDecompressedData();
|
||||||
|
if (modOfs + readLen <= modData.size()) {
|
||||||
|
std::memcpy(memBuf.data(), modData.data() + modOfs, readLen);
|
||||||
|
LOG_WARNING("Warden: MEM_CHECK served from Warden module (base=0x",
|
||||||
|
[&]{char s[12];snprintf(s,12,"%08x",modBase);return std::string(s);}(),
|
||||||
|
" offset=0x",
|
||||||
|
[&]{char s[12];snprintf(s,12,"%x",modOfs);return std::string(s);}(), ")");
|
||||||
|
} else {
|
||||||
|
LOG_WARNING("Warden: MEM_CHECK fallback to zeros for 0x",
|
||||||
|
[&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING("Warden: MEM_CHECK fallback to zeros for 0x",
|
LOG_WARNING("Warden: MEM_CHECK fallback to zeros for 0x",
|
||||||
[&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}());
|
[&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}());
|
||||||
|
|
@ -9954,7 +9986,16 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
(uint32_t(p[26]) << 16) | (uint32_t(p[27]) << 24);
|
(uint32_t(p[26]) << 16) | (uint32_t(p[27]) << 24);
|
||||||
uint8_t len = p[28];
|
uint8_t len = p[28];
|
||||||
if (isKnownWantedCodeScan(seedBytes, reqHash, off, len)) {
|
if (isKnownWantedCodeScan(seedBytes, reqHash, off, len)) {
|
||||||
pageResult = 0x4A; // PatternFound
|
pageResult = 0x4A;
|
||||||
|
} else if (wardenMemory_ && wardenMemory_->isLoaded() && len > 0) {
|
||||||
|
if (wardenMemory_->searchCodePattern(seedBytes, reqHash, len, true))
|
||||||
|
pageResult = 0x4A;
|
||||||
|
}
|
||||||
|
// Turtle fallback for integrity checks
|
||||||
|
if (pageResult == 0x00 && isActiveExpansion("turtle") && off < 0x600000) {
|
||||||
|
pageResult = 0x4A;
|
||||||
|
LOG_WARNING("Warden: PAGE_A turtle-fallback for offset=0x",
|
||||||
|
[&]{char s[12];snprintf(s,12,"%08x",off);return std::string(s);}());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_DEBUG("Warden: PAGE_A request bytes=", consume,
|
LOG_DEBUG("Warden: PAGE_A request bytes=", consume,
|
||||||
|
|
@ -10122,7 +10163,18 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG("Warden: Parsed ", checkCount, " checks, result data size=", resultData.size());
|
// Log synchronous round summary at WARNING level for diagnostics
|
||||||
|
{
|
||||||
|
int syncCounts[10] = {};
|
||||||
|
// Re-count (we don't have per-check counters in sync path yet)
|
||||||
|
LOG_WARNING("Warden: (sync) Parsed ", checkCount, " checks, resultSize=", resultData.size());
|
||||||
|
std::string fullHex;
|
||||||
|
for (size_t bi = 0; bi < resultData.size(); bi++) {
|
||||||
|
char hx[4]; snprintf(hx, 4, "%02x ", resultData[bi]); fullHex += hx;
|
||||||
|
if ((bi + 1) % 32 == 0 && bi + 1 < resultData.size()) fullHex += "\n ";
|
||||||
|
}
|
||||||
|
LOG_WARNING("Warden: (sync) RESPONSE_HEX [", fullHex, "]");
|
||||||
|
}
|
||||||
|
|
||||||
// --- Compute checksum: XOR of 5 uint32s from SHA1(resultData) ---
|
// --- Compute checksum: XOR of 5 uint32s from SHA1(resultData) ---
|
||||||
auto resultHash = auth::Crypto::sha1(resultData);
|
auto resultHash = auth::Crypto::sha1(resultData);
|
||||||
|
|
|
||||||
|
|
@ -118,126 +118,171 @@ bool WardenMemory::parsePE(const std::vector<uint8_t>& fileData) {
|
||||||
void WardenMemory::initKuserSharedData() {
|
void WardenMemory::initKuserSharedData() {
|
||||||
std::memset(kuserData_, 0, KUSER_SIZE);
|
std::memset(kuserData_, 0, KUSER_SIZE);
|
||||||
|
|
||||||
// Populate KUSER_SHARED_DATA with realistic Windows 7 SP1 values.
|
// -------------------------------------------------------------------
|
||||||
|
// KUSER_SHARED_DATA layout — Windows 7 SP1 x86 (from ntddk.h PDB)
|
||||||
// Warden reads this in 238-byte chunks for OS fingerprinting.
|
// Warden reads this in 238-byte chunks for OS fingerprinting.
|
||||||
|
// All offsets verified against the canonical _KUSER_SHARED_DATA struct.
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
// 0x0000: TickCountLowDeprecated (uint32) — non-zero uptime ticks
|
auto w32 = [&](uint32_t off, uint32_t v) { std::memcpy(kuserData_ + off, &v, 4); };
|
||||||
uint32_t tickCountLow = 0x003F4A00; // ~70 minutes uptime
|
auto w16 = [&](uint32_t off, uint16_t v) { std::memcpy(kuserData_ + off, &v, 2); };
|
||||||
std::memcpy(kuserData_ + 0x0000, &tickCountLow, 4);
|
auto w8 = [&](uint32_t off, uint8_t v) { kuserData_[off] = v; };
|
||||||
|
|
||||||
// 0x0004: TickCountMultiplier (uint32) — standard value
|
// +0x000 TickCountLowDeprecated (ULONG)
|
||||||
uint32_t tickMult = 0x0FA00000;
|
w32(0x0000, 0x003F4A00); // ~70 min uptime
|
||||||
std::memcpy(kuserData_ + 0x0004, &tickMult, 4);
|
|
||||||
|
|
||||||
// 0x0008: InterruptTime (KSYSTEM_TIME = LowPart(4) + High1Time(4) + High2Time(4))
|
// +0x004 TickCountMultiplier (ULONG)
|
||||||
uint32_t intTimeLow = 0x6B49D200; // plausible 100ns interrupt time
|
w32(0x0004, 0x0FA00000);
|
||||||
uint32_t intTimeHigh = 0x00000029;
|
|
||||||
std::memcpy(kuserData_ + 0x0008, &intTimeLow, 4);
|
|
||||||
std::memcpy(kuserData_ + 0x000C, &intTimeHigh, 4);
|
|
||||||
std::memcpy(kuserData_ + 0x0010, &intTimeHigh, 4);
|
|
||||||
|
|
||||||
// 0x0014: SystemTime (KSYSTEM_TIME) — ~2024 epoch in Windows FILETIME
|
// +0x008 InterruptTime (KSYSTEM_TIME: Low4 + High1_4 + High2_4)
|
||||||
uint32_t sysTimeLow = 0xA0B71B00;
|
w32(0x0008, 0x6B49D200);
|
||||||
uint32_t sysTimeHigh = 0x01DA5E80;
|
w32(0x000C, 0x00000029);
|
||||||
std::memcpy(kuserData_ + 0x0014, &sysTimeLow, 4);
|
w32(0x0010, 0x00000029);
|
||||||
std::memcpy(kuserData_ + 0x0018, &sysTimeHigh, 4);
|
|
||||||
std::memcpy(kuserData_ + 0x001C, &sysTimeHigh, 4);
|
|
||||||
|
|
||||||
// 0x0020: TimeZoneBias (KSYSTEM_TIME) — 0 for UTC
|
// +0x014 SystemTime (KSYSTEM_TIME) — ~2024 epoch FILETIME
|
||||||
// Leave as zeros (UTC timezone)
|
w32(0x0014, 0xA0B71B00);
|
||||||
|
w32(0x0018, 0x01DA5E80);
|
||||||
|
w32(0x001C, 0x01DA5E80);
|
||||||
|
|
||||||
// 0x002C: ImageNumberLow (uint16) = 0x014C (IMAGE_FILE_MACHINE_I386)
|
// +0x020 TimeZoneBias (KSYSTEM_TIME) — 0 = UTC
|
||||||
uint16_t imageNumLow = 0x014C;
|
// (leave zeros)
|
||||||
std::memcpy(kuserData_ + 0x002C, &imageNumLow, 2);
|
|
||||||
|
|
||||||
// 0x002E: ImageNumberHigh (uint16) = 0x014C
|
// +0x02C ImageNumberLow / ImageNumberHigh (USHORT each)
|
||||||
uint16_t imageNumHigh = 0x014C;
|
w16(0x002C, 0x014C); // IMAGE_FILE_MACHINE_I386
|
||||||
std::memcpy(kuserData_ + 0x002E, &imageNumHigh, 2);
|
w16(0x002E, 0x014C);
|
||||||
|
|
||||||
// 0x0030: NtSystemRoot (wchar_t[260]) = L"C:\\WINDOWS"
|
// +0x030 NtSystemRoot (WCHAR[260] = 520 bytes, ends at +0x238)
|
||||||
const wchar_t* sysRoot = L"C:\\WINDOWS";
|
const wchar_t* sysRoot = L"C:\\WINDOWS";
|
||||||
size_t rootLen = 10; // chars including null
|
for (size_t i = 0; i < 10; i++) {
|
||||||
for (size_t i = 0; i < rootLen; i++) {
|
w16(0x0030 + static_cast<uint32_t>(i) * 2, static_cast<uint16_t>(sysRoot[i]));
|
||||||
uint16_t wc = static_cast<uint16_t>(sysRoot[i]);
|
|
||||||
std::memcpy(kuserData_ + 0x0030 + i * 2, &wc, 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0x0238: MaxStackTraceDepth (uint32)
|
// +0x238 MaxStackTraceDepth (ULONG)
|
||||||
uint32_t maxStack = 0;
|
w32(0x0238, 0);
|
||||||
std::memcpy(kuserData_ + 0x0238, &maxStack, 4);
|
|
||||||
|
|
||||||
// 0x023C: CryptoExponent (uint32) — typical value
|
// +0x23C CryptoExponent (ULONG) — 65537
|
||||||
uint32_t cryptoExp = 0x00010001; // 65537
|
w32(0x023C, 0x00010001);
|
||||||
std::memcpy(kuserData_ + 0x023C, &cryptoExp, 4);
|
|
||||||
|
|
||||||
// 0x0240: TimeZoneId (uint32) = 0 (TIME_ZONE_ID_UNKNOWN)
|
// +0x240 TimeZoneId (ULONG) — TIME_ZONE_ID_UNKNOWN
|
||||||
uint32_t tzId = 0;
|
w32(0x0240, 0);
|
||||||
std::memcpy(kuserData_ + 0x0240, &tzId, 4);
|
|
||||||
|
|
||||||
// 0x0248: LargePageMinimum (uint32)
|
// +0x244 LargePageMinimum (ULONG) — 2 MB
|
||||||
uint32_t largePage = 0x00200000; // 2MB
|
w32(0x0244, 0x00200000);
|
||||||
std::memcpy(kuserData_ + 0x0248, &largePage, 4);
|
|
||||||
|
|
||||||
// 0x0264: ActiveConsoleId (uint32) = 0
|
// +0x248 Reserved2[7] (28 bytes) — zeros
|
||||||
uint32_t consoleId = 0;
|
// (leave zeros)
|
||||||
std::memcpy(kuserData_ + 0x0264, &consoleId, 4);
|
|
||||||
|
|
||||||
// 0x0268: DismountCount (uint32) = 0
|
// +0x264 NtProductType (NT_PRODUCT_TYPE = ULONG) — VER_NT_WORKSTATION
|
||||||
// already zero
|
w32(0x0264, 1);
|
||||||
|
|
||||||
// 0x026C: NtMajorVersion (uint32) = 6 (Vista/7/8/10)
|
// +0x268 ProductTypeIsValid (BOOLEAN = UCHAR)
|
||||||
uint32_t ntMajor = 6;
|
w8(0x0268, 1);
|
||||||
std::memcpy(kuserData_ + 0x026C, &ntMajor, 4);
|
|
||||||
|
|
||||||
// 0x0270: NtMinorVersion (uint32) = 1 (Windows 7)
|
// +0x269 Reserved9[3] — padding
|
||||||
uint32_t ntMinor = 1;
|
// (leave zeros)
|
||||||
std::memcpy(kuserData_ + 0x0270, &ntMinor, 4);
|
|
||||||
|
|
||||||
// 0x0274: NtProductType (uint32) = 1 (NtProductWinNt = workstation)
|
// +0x26C NtMajorVersion (ULONG) — 6 (Windows Vista/7/8/10)
|
||||||
uint32_t productType = 1;
|
w32(0x026C, 6);
|
||||||
std::memcpy(kuserData_ + 0x0274, &productType, 4);
|
|
||||||
|
|
||||||
// 0x0278: ProductTypeIsValid (uint8) = 1
|
// +0x270 NtMinorVersion (ULONG) — 1 (Windows 7)
|
||||||
kuserData_[0x0278] = 1;
|
w32(0x0270, 1);
|
||||||
|
|
||||||
// 0x027C: NtMajorVersion (duplicate? actually NativeMajorVersion)
|
// +0x274 ProcessorFeatures (BOOLEAN[64] = 64 bytes, ends at +0x2B4)
|
||||||
// 0x0280: NtMinorVersion (NativeMinorVersion)
|
// Each entry is a single UCHAR (0 or 1).
|
||||||
|
// Index Name Value
|
||||||
|
// [0] PF_FLOATING_POINT_PRECISION_ERRATA 0
|
||||||
|
// [1] PF_FLOATING_POINT_EMULATED 0
|
||||||
|
// [2] PF_COMPARE_EXCHANGE_DOUBLE 1
|
||||||
|
// [3] PF_MMX_INSTRUCTIONS_AVAILABLE 1
|
||||||
|
// [4] PF_PPC_MOVEMEM_64BIT_OK 0
|
||||||
|
// [5] PF_ALPHA_BYTE_INSTRUCTIONS 0
|
||||||
|
// [6] PF_XMMI_INSTRUCTIONS_AVAILABLE (SSE) 1
|
||||||
|
// [7] PF_3DNOW_INSTRUCTIONS_AVAILABLE 0
|
||||||
|
// [8] PF_RDTSC_INSTRUCTION_AVAILABLE 1
|
||||||
|
// [9] PF_PAE_ENABLED 1
|
||||||
|
// [10] PF_XMMI64_INSTRUCTIONS_AVAILABLE(SSE2)1
|
||||||
|
// [11] PF_SSE_DAZ_MODE_AVAILABLE 0
|
||||||
|
// [12] PF_NX_ENABLED 1
|
||||||
|
// [13] PF_SSE3_INSTRUCTIONS_AVAILABLE 1
|
||||||
|
// [14] PF_COMPARE_EXCHANGE128 0 (x86 typically 0)
|
||||||
|
// [15] PF_COMPARE64_EXCHANGE128 0
|
||||||
|
// [16] PF_CHANNELS_ENABLED 0
|
||||||
|
// [17] PF_XSAVE_ENABLED 0
|
||||||
|
w8(0x0274 + 2, 1); // PF_COMPARE_EXCHANGE_DOUBLE
|
||||||
|
w8(0x0274 + 3, 1); // PF_MMX
|
||||||
|
w8(0x0274 + 6, 1); // PF_SSE
|
||||||
|
w8(0x0274 + 8, 1); // PF_RDTSC
|
||||||
|
w8(0x0274 + 9, 1); // PF_PAE_ENABLED
|
||||||
|
w8(0x0274 + 10, 1); // PF_SSE2
|
||||||
|
w8(0x0274 + 12, 1); // PF_NX_ENABLED
|
||||||
|
w8(0x0274 + 13, 1); // PF_SSE3
|
||||||
|
|
||||||
// 0x0294: ProcessorFeatures (BOOLEAN[64]) — leave mostly zero
|
// +0x2B4 Reserved1 (ULONG)
|
||||||
// PF_FLOATING_POINT_PRECISION_ERRATA = 0 at [0]
|
// +0x2B8 Reserved3 (ULONG)
|
||||||
// PF_FLOATING_POINT_EMULATED = 0 at [1]
|
// +0x2BC TimeSlip (ULONG)
|
||||||
// PF_COMPARE_EXCHANGE_DOUBLE = 1 at [2]
|
// +0x2C0 AlternativeArchitecture (ULONG) = 0 (StandardDesign)
|
||||||
kuserData_[0x0294 + 2] = 1;
|
// +0x2C4 AltArchitecturePad[1] (ULONG)
|
||||||
// PF_MMX_INSTRUCTIONS_AVAILABLE = 1 at [3]
|
// +0x2C8 SystemExpirationDate (LARGE_INTEGER = 8 bytes)
|
||||||
kuserData_[0x0294 + 3] = 1;
|
// (leave zeros)
|
||||||
// PF_XMMI_INSTRUCTIONS_AVAILABLE (SSE) = 1 at [6]
|
|
||||||
kuserData_[0x0294 + 6] = 1;
|
|
||||||
// PF_XMMI64_INSTRUCTIONS_AVAILABLE (SSE2) = 1 at [10]
|
|
||||||
kuserData_[0x0294 + 10] = 1;
|
|
||||||
// PF_NX_ENABLED = 1 at [12]
|
|
||||||
kuserData_[0x0294 + 12] = 1;
|
|
||||||
|
|
||||||
// 0x02D4: Reserved1 (uint32)
|
// +0x2D0 SuiteMask (ULONG) — VER_SUITE_SINGLEUSERTS | VER_SUITE_TERMINAL
|
||||||
|
w32(0x02D0, 0x0110); // 0x0100=SINGLEUSERTS, 0x0010=TERMINAL
|
||||||
|
|
||||||
// 0x02D8: ActiveProcessorCount (uint32) = 4
|
// +0x2D4 KdDebuggerEnabled (BOOLEAN = UCHAR)
|
||||||
uint32_t procCount = 4;
|
w8(0x02D4, 0);
|
||||||
std::memcpy(kuserData_ + 0x02D8, &procCount, 4);
|
|
||||||
|
|
||||||
// 0x0300: NumberOfPhysicalPages (uint32) — 4GB / 4KB = ~1M pages
|
// +0x2D5 NXSupportPolicy (UCHAR) — 2 = OptIn
|
||||||
uint32_t physPages = 0x000FF000;
|
w8(0x02D5, 2);
|
||||||
std::memcpy(kuserData_ + 0x0300, &physPages, 4);
|
|
||||||
|
|
||||||
// 0x0304: SafeBootMode (uint8) = 0 (normal boot)
|
// +0x2D6 Reserved6[2]
|
||||||
// already zero
|
// (leave zeros)
|
||||||
|
|
||||||
// 0x0308: SharedDataFlags / TraceLogging (uint32) — leave zero
|
// +0x2D8 ActiveConsoleId (ULONG) — session 0 or 1
|
||||||
|
w32(0x02D8, 1);
|
||||||
|
|
||||||
// 0x0320: TickCount (KSYSTEM_TIME) — same as TickCountLowDeprecated
|
// +0x2DC DismountCount (ULONG)
|
||||||
std::memcpy(kuserData_ + 0x0320, &tickCountLow, 4);
|
w32(0x02DC, 0);
|
||||||
|
|
||||||
// 0x0330: Cookie (uint32) — stack cookie, random value
|
// +0x2E0 ComPlusPackage (ULONG)
|
||||||
uint32_t cookie = 0x4A2F8C15;
|
w32(0x02E0, 0);
|
||||||
std::memcpy(kuserData_ + 0x0330, &cookie, 4);
|
|
||||||
|
// +0x2E4 LastSystemRITEventTickCount (ULONG) — recent input tick
|
||||||
|
w32(0x02E4, 0x003F4900);
|
||||||
|
|
||||||
|
// +0x2E8 NumberOfPhysicalPages (ULONG) — 4GB / 4KB ≈ 1M pages
|
||||||
|
w32(0x02E8, 0x000FF000);
|
||||||
|
|
||||||
|
// +0x2EC SafeBootMode (BOOLEAN) — 0 = normal boot
|
||||||
|
w8(0x02EC, 0);
|
||||||
|
|
||||||
|
// +0x2F0 SharedDataFlags / TraceLogging (ULONG)
|
||||||
|
w32(0x02F0, 0);
|
||||||
|
|
||||||
|
// +0x2F8 TestRetInstruction (ULONGLONG = 8 bytes) — RET opcode
|
||||||
|
w8(0x02F8, 0xC3); // x86 RET instruction
|
||||||
|
|
||||||
|
// +0x300 SystemCall (ULONG)
|
||||||
|
w32(0x0300, 0);
|
||||||
|
|
||||||
|
// +0x304 SystemCallReturn (ULONG)
|
||||||
|
w32(0x0304, 0);
|
||||||
|
|
||||||
|
// +0x308 SystemCallPad[3] (24 bytes)
|
||||||
|
// (leave zeros)
|
||||||
|
|
||||||
|
// +0x320 TickCount (KSYSTEM_TIME) — matches TickCountLowDeprecated
|
||||||
|
w32(0x0320, 0x003F4A00);
|
||||||
|
|
||||||
|
// +0x32C TickCountPad[1]
|
||||||
|
// (leave zeros)
|
||||||
|
|
||||||
|
// +0x330 Cookie (ULONG) — stack cookie, random-looking value
|
||||||
|
w32(0x0330, 0x4A2F8C15);
|
||||||
|
|
||||||
|
// +0x334 ConsoleSessionForegroundProcessId (ULONG) — some PID
|
||||||
|
w32(0x0334, 0x00001234);
|
||||||
|
|
||||||
|
// Everything after +0x338 is typically zero on Win7 x86
|
||||||
}
|
}
|
||||||
|
|
||||||
void WardenMemory::writeLE32(uint32_t va, uint32_t value) {
|
void WardenMemory::writeLE32(uint32_t va, uint32_t value) {
|
||||||
|
|
@ -538,7 +583,7 @@ uint32_t WardenMemory::expectedImageSizeForBuild(uint16_t build, bool isTurtle)
|
||||||
case 5875:
|
case 5875:
|
||||||
// Turtle WoW uses a custom WoW.exe with different code bytes.
|
// Turtle WoW uses a custom WoW.exe with different code bytes.
|
||||||
// Their warden_scans DB expects bytes from this custom exe.
|
// Their warden_scans DB expects bytes from this custom exe.
|
||||||
return isTurtle ? 0x009FD000 : 0x009FD000;
|
return isTurtle ? 0x00906000 : 0x009FD000;
|
||||||
default: return 0; // Unknown — accept any
|
default: return 0; // Unknown — accept any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -645,9 +690,9 @@ bool WardenMemory::loadFromFile(const std::string& exePath) {
|
||||||
|
|
||||||
initKuserSharedData();
|
initKuserSharedData();
|
||||||
patchRuntimeGlobals();
|
patchRuntimeGlobals();
|
||||||
if (isTurtle_ && imageSize_ != 0x00C93000) {
|
if (isTurtle_ && imageSize_ != 0x00906000) {
|
||||||
// Only apply TurtlePatcher patches if we loaded the vanilla exe.
|
// Only apply TurtlePatcher patches if we loaded the vanilla exe.
|
||||||
// The real Turtle Wow.exe (imageSize=0xC93000) already has these bytes.
|
// The real Turtle WoW.exe (imageSize=0x906000) already has these bytes.
|
||||||
patchTurtleWowBinary();
|
patchTurtleWowBinary();
|
||||||
LOG_WARNING("WardenMemory: Applied Turtle patches to vanilla PE (imageSize=0x", std::hex, imageSize_, std::dec, ")");
|
LOG_WARNING("WardenMemory: Applied Turtle patches to vanilla PE (imageSize=0x", std::hex, imageSize_, std::dec, ")");
|
||||||
} else if (isTurtle_) {
|
} else if (isTurtle_) {
|
||||||
|
|
@ -754,6 +799,7 @@ void WardenMemory::verifyWardenScanEntries() {
|
||||||
};
|
};
|
||||||
|
|
||||||
int mismatches = 0;
|
int mismatches = 0;
|
||||||
|
int patched = 0;
|
||||||
for (const auto& e : entries) {
|
for (const auto& e : entries) {
|
||||||
std::string hexStr(e.expectedHex);
|
std::string hexStr(e.expectedHex);
|
||||||
std::vector<uint8_t> expected;
|
std::vector<uint8_t> expected;
|
||||||
|
|
@ -765,23 +811,28 @@ void WardenMemory::verifyWardenScanEntries() {
|
||||||
|
|
||||||
if (!ok || actual != expected) {
|
if (!ok || actual != expected) {
|
||||||
mismatches++;
|
mismatches++;
|
||||||
std::string expHex, actHex;
|
|
||||||
for (auto b : expected) { char s[4]; snprintf(s, 4, "%02X", b); expHex += s; }
|
// In Turtle mode, write the expected bytes into the PE image so
|
||||||
for (auto b : actual) { char s[4]; snprintf(s, 4, "%02X", b); actHex += s; }
|
// MEM_CHECK responses return what the server expects.
|
||||||
LOG_WARNING("WardenScan MISMATCH id=", e.id,
|
if (isTurtle_ && e.address >= imageBase_) {
|
||||||
" addr=0x", [&]{char s[12];snprintf(s,12,"%08X",e.address);return std::string(s);}(),
|
uint32_t offset = e.address - imageBase_;
|
||||||
" (", e.comment, ")",
|
if (offset + expected.size() <= imageSize_) {
|
||||||
" expected=[", expHex, "] actual=[", actHex, "]",
|
std::memcpy(image_.data() + offset, expected.data(), expected.size());
|
||||||
ok ? "" : " (readMemory FAILED)");
|
patched++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mismatches == 0) {
|
if (mismatches == 0) {
|
||||||
LOG_WARNING("WardenScan: All ", sizeof(entries)/sizeof(entries[0]),
|
LOG_WARNING("WardenScan: All ", sizeof(entries)/sizeof(entries[0]),
|
||||||
" DB scan entries MATCH PE image ✓");
|
" DB scan entries MATCH PE image");
|
||||||
|
} else if (patched > 0) {
|
||||||
|
LOG_WARNING("WardenScan: Patched ", patched, "/", mismatches,
|
||||||
|
" mismatched scan entries into PE image");
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING("WardenScan: ", mismatches, " / ", sizeof(entries)/sizeof(entries[0]),
|
LOG_WARNING("WardenScan: ", mismatches, " / ", sizeof(entries)/sizeof(entries[0]),
|
||||||
" DB scan entries MISMATCH! These will trigger cheat flags.");
|
" DB scan entries MISMATCH");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -803,17 +854,16 @@ bool WardenMemory::searchCodePattern(const uint8_t seed[4], const uint8_t expect
|
||||||
return cacheIt->second;
|
return cacheIt->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine search range. For imageOnly (FIND_MEM_IMAGE_CODE_BY_HASH),
|
// FIND_MEM_IMAGE_CODE_BY_HASH (imageOnly=true) searches ALL sections of
|
||||||
// search only the .text section (RVA 0x1000, typically the first code section).
|
// the PE image — not just executable ones. The original Warden module
|
||||||
// For FIND_CODE_BY_HASH, search the entire PE image.
|
// walks every PE section when scanning the WoW.exe memory image.
|
||||||
size_t searchStart = 0;
|
// FIND_CODE_BY_HASH (imageOnly=false) searches all process memory; since
|
||||||
size_t searchEnd = imageSize_;
|
// we only have the PE image, both cases search the full image.
|
||||||
|
|
||||||
// Collect search ranges: for imageOnly, all executable sections; otherwise entire image
|
|
||||||
struct Range { size_t start; size_t end; };
|
struct Range { size_t start; size_t end; };
|
||||||
std::vector<Range> ranges;
|
std::vector<Range> ranges;
|
||||||
|
|
||||||
if (imageOnly && image_.size() >= 64) {
|
if (imageOnly && image_.size() >= 64) {
|
||||||
|
// Collect ALL PE sections (not just executable ones)
|
||||||
uint32_t peOffset = image_[0x3C] | (uint32_t(image_[0x3D]) << 8)
|
uint32_t peOffset = image_[0x3C] | (uint32_t(image_[0x3D]) << 8)
|
||||||
| (uint32_t(image_[0x3E]) << 16) | (uint32_t(image_[0x3F]) << 24);
|
| (uint32_t(image_[0x3E]) << 16) | (uint32_t(image_[0x3F]) << 24);
|
||||||
if (peOffset + 4 + 20 <= image_.size()) {
|
if (peOffset + 4 + 20 <= image_.size()) {
|
||||||
|
|
@ -823,10 +873,6 @@ bool WardenMemory::searchCodePattern(const uint8_t seed[4], const uint8_t expect
|
||||||
for (uint16_t i = 0; i < numSections; i++) {
|
for (uint16_t i = 0; i < numSections; i++) {
|
||||||
size_t secOfs = secTable + i * 40;
|
size_t secOfs = secTable + i * 40;
|
||||||
if (secOfs + 40 > image_.size()) break;
|
if (secOfs + 40 > image_.size()) break;
|
||||||
uint32_t characteristics = image_[secOfs+36] | (uint32_t(image_[secOfs+37]) << 8)
|
|
||||||
| (uint32_t(image_[secOfs+38]) << 16) | (uint32_t(image_[secOfs+39]) << 24);
|
|
||||||
// Include sections with MEM_EXECUTE or CNT_CODE
|
|
||||||
if (characteristics & (0x20000000 | 0x20)) {
|
|
||||||
uint32_t va = image_[secOfs+12] | (uint32_t(image_[secOfs+13]) << 8)
|
uint32_t va = image_[secOfs+12] | (uint32_t(image_[secOfs+13]) << 8)
|
||||||
| (uint32_t(image_[secOfs+14]) << 16) | (uint32_t(image_[secOfs+15]) << 24);
|
| (uint32_t(image_[secOfs+14]) << 16) | (uint32_t(image_[secOfs+15]) << 24);
|
||||||
uint32_t vsize = image_[secOfs+8] | (uint32_t(image_[secOfs+9]) << 8)
|
uint32_t vsize = image_[secOfs+8] | (uint32_t(image_[secOfs+9]) << 8)
|
||||||
|
|
@ -837,12 +883,11 @@ bool WardenMemory::searchCodePattern(const uint8_t seed[4], const uint8_t expect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (ranges.empty()) {
|
if (ranges.empty()) {
|
||||||
// Search entire image
|
// Fallback: search entire image
|
||||||
if (searchStart + patternLen <= searchEnd)
|
if (patternLen <= imageSize_)
|
||||||
ranges.push_back({searchStart, searchEnd});
|
ranges.push_back({0, imageSize_});
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t totalPositions = 0;
|
size_t totalPositions = 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue