Greetings,
I'm currently implementing the only known module of Warden into TrinityCore's 4.3.4 fork. I have a couple of questions for the people that extensively know how the inner workings of Warden behaves.
* MPQ Scans appear to downright crash the client because of an invalid pointer dereference as seen here:
Code:
char __userpurge sub_1020CC0@<al>(Warden$Module *module@<ecx>, SHA1Digest *a2@<esi>, Warden$FileSystemPointers *pointers@<edx>, const char *filePath)
{
char result; // al
Warden$MPQCheck context; // [esp+8h] [ebp-74h]
context.ppFnProcess = &off_10252A4;
context.m_sha1.N[0] = 0;
context.m_sha1.N[1] = 0;
context.m_sha1.h[0] = 0x67452301;
context.m_sha1.h[1] = 0xEFCDAB89;
context.m_sha1.h[2] = 0x98BADCFE;
context.m_sha1.h[3] = 0x10325476;
context.m_sha1.h[4] = 0xC3D2E1F0;
result = Warden::ServerModule::HandleMPQScan(pointers, (unsigned int)filePath, &context, module);
if ( result )
{
SHA1::Finish(&context.m_sha1, a2);
result = 1;
}
return result;
}
void __fastcall SHA1::Finish(SHA1 *a1, SHA1Digest *a2)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v3 = a1->N[0];
v4 = a1->N[1];
a2a[0] = v3;
v5 = __PAIR__(v4, v3) >> 8;
v4 >>= 8;
a2a[6] = v5;
v6 = __PAIR__(v4, v5) >> 8;
v4 >>= 8;
a2a[5] = v6;
v7 = __PAIR__(v4, v6) >> 8;
v4 >>= 8;
a2a[4] = v7;
v8 = __PAIR__(v4, v7) >> 8;
v4 >>= 8;
a2a[3] = v8;
v9 = __PAIR__(v4, v8) >> 8;
a2a[2] = v9;
v10 = __PAIR__(v4 >> 8, v9) >> 8;
a2a[1] = v10;
v11 = a1->N[0];
a2a[0] = BYTE1(v10);
v12 = a2; // <--------------------------
SHA1::Process(a1, byte_1025250, ((-9 - (unsigned __int8)(v11 >> 3)) & 0x3F) + 1);
SHA1::Process(a1, a2a, 8);
v13 = &v12->Digest[1];
v14 = a1->h;
v15 = 5;
do
{
v16 = *v14;
v13[2] = *v14;
v16 >>= 8;
v13[1] = v16;
v16 >>= 8;
*v13 = v16;
*(v13 - 1) = BYTE1(v16);
++v14;
v13 += 4;
--v15;
}
while ( v15 );
}
For reasons unbeknownst to me, during the execution of SHA1::Finish, v12 becomes nullptr.
Let's step back and take a look at the assembly.
Code:
.srvwrdn:0101E030 ; void __fastcall SHA1::Finish(SHA1 *a1, SHA1Digest *a2)
.srvwrdn:0101E030 SHA1::Finish(unsigned char (&)[20]) proc near
.srvwrdn:0101E030 ; CODE XREF: HMACFinalize+1E↓p
.srvwrdn:0101E030 ; HMACFinalize+4A↓p ...
.srvwrdn:0101E030
.srvwrdn:0101E030 a2 = byte ptr -0Ch
.srvwrdn:0101E030 var_4 = dword ptr -4
.srvwrdn:0101E030
.srvwrdn:0101E030 000 55 push ebp
.srvwrdn:0101E031 004 8B EC mov ebp, esp
.srvwrdn:0101E033 004 83 EC 10 sub esp, 10h
.srvwrdn:0101E036 014 A1 00 60 02 01 mov eax, ds:dword_1026000
.srvwrdn:0101E03B 014 33 C5 xor eax, ebp
.srvwrdn:0101E03D 014 89 45 FC mov [ebp+var_4], eax
.srvwrdn:0101E040 014 56 push esi
.srvwrdn:0101E041 018 57 push edi
.srvwrdn:0101E042 01C 8B F9 mov edi, ecx ; a1
.srvwrdn:0101E044 01C 8B 0F mov ecx, [edi]
.srvwrdn:0101E046 01C 8B 47 04 mov eax, [edi+4]
.srvwrdn:0101E049 01C 88 4D FB mov [ebp+a2+7], cl
.srvwrdn:0101E04C 01C 0F AC C1 08 shrd ecx, eax, 8
.srvwrdn:0101E050 01C C1 E8 08 shr eax, 8
.srvwrdn:0101E053 01C 88 4D FA mov [ebp+a2+6], cl
.srvwrdn:0101E056 01C 0F AC C1 08 shrd ecx, eax, 8
.srvwrdn:0101E05A 01C C1 E8 08 shr eax, 8
.srvwrdn:0101E05D 01C 88 4D F9 mov [ebp+a2+5], cl
.srvwrdn:0101E060 01C 0F AC C1 08 shrd ecx, eax, 8
.srvwrdn:0101E064 01C C1 E8 08 shr eax, 8
.srvwrdn:0101E067 01C 88 4D F8 mov [ebp+a2+4], cl
.srvwrdn:0101E06A 01C 0F AC C1 08 shrd ecx, eax, 8
.srvwrdn:0101E06E 01C C1 E8 08 shr eax, 8
.srvwrdn:0101E071 01C 88 4D F7 mov [ebp+a2+3], cl
.srvwrdn:0101E074 01C 0F AC C1 08 shrd ecx, eax, 8
.srvwrdn:0101E078 01C C1 E8 08 shr eax, 8
.srvwrdn:0101E07B 01C 88 4D F6 mov [ebp+a2+2], cl
.srvwrdn:0101E07E 01C 0F AC C1 08 shrd ecx, eax, 8
.srvwrdn:0101E082 01C C1 E8 08 shr eax, 8
.srvwrdn:0101E085 01C 88 4D F5 mov [ebp+a2+1], cl
.srvwrdn:0101E088 01C 0F AC C1 08 shrd ecx, eax, 8
.srvwrdn:0101E08C 01C C1 E8 08 shr eax, 8
.srvwrdn:0101E08F 01C 8B 07 mov eax, [edi]
.srvwrdn:0101E091 01C 88 4D F4 mov [ebp+a2], cl
.srvwrdn:0101E094 01C C1 E8 03 shr eax, 3
.srvwrdn:0101E097 01C B9 F7 FF FF FF mov ecx, 0FFFFFFF7h
.srvwrdn:0101E09C 01C 2B C8 sub ecx, eax
.srvwrdn:0101E09E 01C 83 E1 3F and ecx, 3Fh
.srvwrdn:0101E0A1 01C 83 C1 01 add ecx, 1
.srvwrdn:0101E0A4 01C 51 push ecx ; a3
.srvwrdn:0101E0A5 020 68 50 52 02 01 push offset byte_1025250 ; a2
.srvwrdn:0101E0AA 024 8B F2 mov esi, edx
.srvwrdn:0101E0AC 024 E8 BF 5F 00 00 call SHA1::Process(char const*)
.srvwrdn:0101E0B1 01C 6A 08 push 8 ; a3
.srvwrdn:0101E0B3 020 8D 55 F4 lea edx, [ebp+a2]
.srvwrdn:0101E0B6 020 52 push edx ; a2
.srvwrdn:0101E0B7 024 E8 B4 5F 00 00 call SHA1::Process(char const*)
.srvwrdn:0101E0BC 01C 8D 4E 01 lea ecx, [esi+1]
.srvwrdn:0101E0BF 01C 8D 57 08 lea edx, [edi+8]
.srvwrdn:0101E0C2 01C BE 05 00 00 00 mov esi, 5
.srvwrdn:0101E0C7
.srvwrdn:0101E0C7 loc_101E0C7: ; CODE XREF: SHA1::Finish(uchar (&)[20])+B6↓j
.srvwrdn:0101E0C7 01C 8B 02 mov eax, [edx]
.srvwrdn:0101E0C9 01C 88 41 02 mov [ecx+2], al ; <---------------
.srvwrdn:0101E0CC 01C C1 E8 08 shr eax, 8
.srvwrdn:0101E0CF 01C 88 41 01 mov [ecx+1], al
.srvwrdn:0101E0D2 01C C1 E8 08 shr eax, 8
.srvwrdn:0101E0D5 01C 88 01 mov [ecx], al
.srvwrdn:0101E0D7 01C C1 E8 08 shr eax, 8
.srvwrdn:0101E0DA 01C 88 41 FF mov [ecx-1], al
.srvwrdn:0101E0DD 01C 83 C2 04 add edx, 4
.srvwrdn:0101E0E0 01C 83 C1 04 add ecx, 4
.srvwrdn:0101E0E3 01C 83 EE 01 sub esi, 1
.srvwrdn:0101E0E6 01C 75 DF jnz short loc_101E0C7
.srvwrdn:0101E0E8 01C 8B 4D FC mov ecx, [ebp+var_4]
.srvwrdn:0101E0EB 01C 5F pop edi
.srvwrdn:0101E0EC 018 33 CD xor ecx, ebp
.srvwrdn:0101E0EE 018 5E pop esi
.srvwrdn:0101E0EF 014 E8 2A 2B 00 00 call sub_1020C1E
.srvwrdn:0101E0F4 014 8B E5 mov esp, ebp
.srvwrdn:0101E0F6 004 5D pop ebp
.srvwrdn:0101E0F7 000 C3 retn
.srvwrdn:0101E0F7 SHA1::Finish(unsigned char (&)[20]) endp
Execution fails on the highlighted line, because ECX=1. We can see ECX is loaded from the value at [ESI+1]. ESI itself loads from EDX. EDX is passed as an argument to the function, so let's look at the caller.
Code:
.srvwrdn:01020CC0 ; char __userpurge sub_1020CC0@<al>(Warden$Module *module@<ecx>, SHA1Digest *a2@<esi>, Warden$FileSystemPointers *pointers@<edx>, const char *filePath)
.srvwrdn:01020CC0 sub_1020CC0 proc near ; CODE XREF: sub_101FC30+5D2↑p
.srvwrdn:01020CC0
.srvwrdn:01020CC0 context = Warden$MPQCheck ptr -74h
.srvwrdn:01020CC0 var_8 = dword ptr -8
.srvwrdn:01020CC0 filePath = dword ptr 8
.srvwrdn:01020CC0
.srvwrdn:01020CC0 000 55 push ebp
.srvwrdn:01020CC1 004 8B EC mov ebp, esp
.srvwrdn:01020CC3 004 83 EC 78 sub esp, 78h
.srvwrdn:01020CC6 07C A1 00 60 02 01 mov eax, ds:dword_1026000
.srvwrdn:01020CCB 07C 33 C5 xor eax, ebp
.srvwrdn:01020CCD 07C 89 45 F8 mov [ebp+var_8], eax
.srvwrdn:01020CD0 07C 8B 45 08 mov eax, [ebp+filePath]
.srvwrdn:01020CD3 07C 57 push edi
.srvwrdn:01020CD4 080 51 push ecx ; a4
.srvwrdn:01020CD5 084 8D 4D 8C lea ecx, [ebp+context]
.srvwrdn:01020CD8 084 8B FA mov edi, edx ; fptrs
.srvwrdn:01020CDA 084 33 D2 xor edx, edx
.srvwrdn:01020CDC 084 51 push ecx ; a3
.srvwrdn:01020CDD 088 50 push eax ; fileSizeHiPart
.srvwrdn:01020CDE 08C C7 45 8C A4 52 02 01 mov [ebp+context.ppFnProcess], offset off_10252A4
.srvwrdn:01020CE5 08C 89 55 94 mov [ebp+context.m_sha1.N], edx
.srvwrdn:01020CE8 08C 89 55 98 mov [ebp+context.m_sha1.N+4], edx
.srvwrdn:01020CEB 08C C7 45 9C 01 23 45 67 mov [ebp+context.m_sha1.h], 67452301h
.srvwrdn:01020CF2 08C C7 45 A0 89 AB CD EF mov [ebp+context.m_sha1.h+4], 0EFCDAB89h
.srvwrdn:01020CF9 08C C7 45 A4 FE DC BA 98 mov [ebp+context.m_sha1.h+8], 98BADCFEh
.srvwrdn:01020D00 08C C7 45 A8 76 54 32 10 mov [ebp+context.m_sha1.h+0Ch], 10325476h
.srvwrdn:01020D07 08C C7 45 AC F0 E1 D2 C3 mov [ebp+context.m_sha1.h+10h], 0C3D2E1F0h
.srvwrdn:01020D0E 08C E8 3D EC FF FF call Warden::ServerModule::HandleMPQScan
.srvwrdn:01020D13 080 84 C0 test al, al
.srvwrdn:01020D15 080 75 11 jnz short loc_1020D28
.srvwrdn:01020D17 080 5F pop edi
.srvwrdn:01020D18 07C 8B 4D F8 mov ecx, [ebp+var_8]
.srvwrdn:01020D1B 07C 33 CD xor ecx, ebp
.srvwrdn:01020D1D 07C E8 FC FE FF FF call sub_1020C1E
.srvwrdn:01020D22 07C 8B E5 mov esp, ebp
.srvwrdn:01020D24 004 5D pop ebp
.srvwrdn:01020D25 000 C2 04 00 retn 4
.srvwrdn:01020D28 ; ---------------------------------------------------------------------------
.srvwrdn:01020D28
.srvwrdn:01020D28 loc_1020D28: ; CODE XREF: sub_1020CC0+55↑j
.srvwrdn:01020D28 080 8B D6 mov edx, esi ; a2
.srvwrdn:01020D2A 080 8D 4D 94 lea ecx, [ebp+context.m_sha1] ; a1
.srvwrdn:01020D2D 080 E8 FE D2 FF FF call SHA1::Finish(uchar (&)[20])
.srvwrdn:01020D32 080 8B 4D F8 mov ecx, [ebp+var_8]
.srvwrdn:01020D35 080 33 CD xor ecx, ebp
.srvwrdn:01020D37 080 B0 01 mov al, 1
.srvwrdn:01020D39 080 5F pop edi
.srvwrdn:01020D3A 07C E8 DF FE FF FF call sub_1020C1E
.srvwrdn:01020D3F 07C 8B E5 mov esp, ebp
.srvwrdn:01020D41 004 5D pop ebp
.srvwrdn:01020D42 000 C2 04 00 retn 4
.srvwrdn:01020D42 sub_1020CC0 endp
We see that EDX is ... loaded from ESI, which is, once again, an argument to us. This function is what I call Warden::Process, and is in charge of processing the received scans that have been stored internally. Because it's huge (it contains a jump table and handles most scans), I'm only going to paste the bit related to MPQ scans.
Code:
.srvwrdn:0102019E loc_102019E: ; CODE XREF: sub_101FC30+F6↑j
.srvwrdn:0102019E ; DATA XREF: .srvwrdn:off_1020990↓o
.srvwrdn:0102019E 26C 8B 46 08 mov eax, [esi+8] ; jumptable 0101FD26 case 2
.srvwrdn:010201A1 26C 8B 7E 04 mov edi, [esi+4]
.srvwrdn:010201A4 26C 3B C7 cmp eax, edi
.srvwrdn:010201A6 26C 77 09 ja short loc_10201B1
.srvwrdn:010201A8 26C 8B CF mov ecx, edi
.srvwrdn:010201AA 26C 2B C8 sub ecx, eax
.srvwrdn:010201AC 26C 83 F9 01 cmp ecx, 1
.srvwrdn:010201AF 26C 73 0E jnb short loc_10201BF
.srvwrdn:010201B1
.srvwrdn:010201B1 loc_10201B1: ; CODE XREF: sub_101FC30+576↑j
.srvwrdn:010201B1 26C 8A 8D E3 FD FF FF mov cl, [ebp+anonymous_1]
.srvwrdn:010201B7 26C 8D 57 01 lea edx, [edi+1]
.srvwrdn:010201BA 26C 89 56 08 mov [esi+8], edx
.srvwrdn:010201BD 26C EB 0B jmp short loc_10201CA
.srvwrdn:010201BF ; ---------------------------------------------------------------------------
.srvwrdn:010201BF
.srvwrdn:010201BF loc_10201BF: ; CODE XREF: sub_101FC30+57F↑j
.srvwrdn:010201BF 26C 8B 0E mov ecx, [esi]
.srvwrdn:010201C1 26C 8A 0C 08 mov cl, [eax+ecx]
.srvwrdn:010201C4 26C 83 C0 01 add eax, 1
.srvwrdn:010201C7 26C 89 46 08 mov [esi+8], eax
.srvwrdn:010201CA
.srvwrdn:010201CA loc_10201CA: ; CODE XREF: sub_101FC30+58D↑j
.srvwrdn:010201CA 26C 39 7E 08 cmp [esi+8], edi
.srvwrdn:010201CD 26C 0F 87 54 FC FF FF ja loc_101FE27
.srvwrdn:010201D3 26C 0F B6 C1 movzx eax, cl ; stringIndex
.srvwrdn:010201D6 26C 8D 95 F8 FE FF FF lea edx, [ebp+textIdentifier]
.srvwrdn:010201DC 26C 52 push edx ; str
.srvwrdn:010201DD 270 8B CB mov ecx, ebx ; ecx0
.srvwrdn:010201DF 270 E8 DC 0D 00 00 call WardenPacket__ExtractStringByIndex
.srvwrdn:010201E4 26C 84 C0 test al, al
.srvwrdn:010201E6 26C 0F 84 EA FC FF FF jz loc_101FED6
.srvwrdn:010201EC 26C 8D 85 F8 FE FF FF lea eax, [ebp+textIdentifier]
.srvwrdn:010201F2 26C 8D 4B 14 lea ecx, [ebx+arg_C] ; ecx0
.srvwrdn:010201F5 26C 50 push eax ; filePath
.srvwrdn:010201F6 270 8D 93 C4 07 00 00 lea edx, [ebx+arg_7BC] ; edx0
.srvwrdn:010201FC 270 8D B5 E4 FD FF FF lea esi, [ebp+a2] ; a2
.srvwrdn:01020202 270 E8 B9 0A 00 00 call sub_1020CC0
.srvwrdn:01020207 26C 84 C0 test al, al
.srvwrdn:01020209 26C 0F 84 34 FD FF FF jz loc_101FF43
.srvwrdn:0102020F 26C 8B BD D8 FD FF FF mov edi, [ebp+scanResult]
.srvwrdn:01020215 26C 8B 47 0C mov eax, [edi+0Ch]
.srvwrdn:01020218 26C 8B 50 08 mov edx, [eax+8]
.srvwrdn:0102021B 26C 8B 70 04 mov esi, [eax+4]
.srvwrdn:0102021E 26C 3B D6 cmp edx, esi
.srvwrdn:01020220 26C 77 09 ja short loc_102022B
.srvwrdn:01020222 26C 8B CE mov ecx, esi
.srvwrdn:01020224 26C 2B CA sub ecx, edx
.srvwrdn:01020226 26C 83 F9 01 cmp ecx, 1
.srvwrdn:01020229 26C 73 08 jnb short loc_1020233
We see here that ESI is loaded as the address of [EBP+a2] ([EBP-21Ch]). This is ... a variable on the stack.
So wait. How can a pointer to the stack become nullptr? The only possible explanation i have is that somehow Warden::ServerModule::HandleMPQScan is corrupting ESI, maybe with mismatched pop/push instructions. A quick analysis shows that indeed some code paths don't result in the same amount of pops and pushes to/from ESI, but the code being full of calls into function pointers makes it quite hard to trace what's going on.
TL;DR: Has someone already played around with MPQ hash scans as of 15595 and run into this issue? I'm fairly certain the way i request MPQ scans is correct:
Code:
uint8 command (SMSG_WARDEN_CHEAT_CHECKS_REQUEST);
uint8 stringLength;
char fileName[stringLength];
uint8 { 0 };
uint8 { check id ^ client key[0] }
uint8 { string index };
uint8 { client key[0] };
Is this scan straight up bugged? Did I miss something while looking at assembler? Has someone debugged this and found the fix ? I'm unforutnately using IDA 7.0, which has a bug preventing it from interfacing with WinDBG; plus i'd need to trace the VirtualAlloc call that creates the code range for Warden modules and set breakpoints/tracepoints before the check gets sent. I can definitely try if no one has run into this, but at this point I'm just trying to save a bit of time.
* The second question is quite a lot simpler: are there known WDB cache backups of 2012 era containing 64-bits Warden modules for Windows, as well as OSX modules?
* And finally, the third question: has anyone figured out the cryptographic algorithm behind the key exchange part of warden once function pointers have been sent to the module? For now, I'm generating random seed/key pairs like so:
Code:
uint8 mainShellCode[] = { asm bytes from module };
struct TransformSeedT
{
TransformSeedT()
{
_address = VirtualAlloc(nullptr, sizeof(mainShellCode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
DWORD bytesWritten = 0;
WriteProcessMemory(GetCurrentProcess(), _address, mainShellCode, sizeof(mainShellCode), &bytesWritten);
FlushInstructionCache(GetCurrentProcess(), _address, sizeof(mainShellCode));
}
~TransformSeedT()
{
VirtualFree(_address, 0, MEM_RELEASE);
}
std::array<uint8_t, 16> operator () (std::array<uint8_t, 16> const& seed)
{
using transform_seed_t = void(__fastcall*)(uint8_t* data);
std::array<uint8_t, 16> alteredSeed = seed;
(*reinterpret_cast<transform_seed_t*>(&_address))(alteredSeed.data());
return alteredSeed;
}
private:
LPVOID _address;
};
I appreciate any help,
Thanks