Yea nothing much changed code wise, detection wise they might have added some extra signatures. I'll post the whole code once i'm done shouldn't take longer than 1H now. Gotta head to work soon.
Yea nothing much changed code wise, detection wise they might have added some extra signatures. I'll post the whole code once i'm done shouldn't take longer than 1H now. Gotta head to work soon.
Ok idaq really sucks with local windows struct but i figured out one extra detection method :
PROCESSENTRY32 structure (Windows) tells you that the exe name is in position 0x24Code:int __stdcall CheckForExeFileName(int a1) { HANDLE v1; // edi@1 char *v2; // eax@2 char v3; // cl@3 int v4; // eax@4 char *v5; // esi@4 int v6; // edx@4 int v7; // ecx@5 int result; // eax@9 int v9; // esi@10 int v10; // [sp+10h] [bp-128h]@1 char v11; // [sp+34h] [bp-104h]@2 _BYTE v12[3]; // [sp+35h] [bp-103h]@4 v1 = CreateToolhelp32Snapshot(2u, 0); v10 = 296; if ( lProcess32First(v1, &v10) ) { while ( 1 ) { v2 = &v11; do v3 = *v2++; while ( v3 ); v4 = v2 - v12; v5 = &v11; v6 = 5381; if ( v4 ) { v7 = v4; do { v6 = 33 * v6 + *v5++; --v7; } while ( v7 ); } if ( v6 == a1 ) break; if ( !lProcess32Next(v1, &v10) ) goto LABEL_9; } v9 = GetFlags(v4, &v11); lCloseHandle(v1); result = v9; } else { LABEL_9: lCloseHandle(v1); result = 0; } return result; }
In Ida C decompiled code you can see
int v10; // [sp+10h] [bp-128h]@1
char v11; // [sp+34h] [bp-104h]@2
0x34 - 0x10 = 0x24 and v10 is used at the out of Process32First so v10 is actually a struct tagPROCESSENTRY32 but ida doesn't know that which is why sometimes it has ectra variables.
Anyway it takes the exe name of every opened process, makes a hash and compare it, if the hash is detected, you're flagged.
Then there's another detection to ensure there's no false positive :
it scans for hack pattern for external processes for suspicious exe filenameCode:int __userpurge CheckExternalApplicationMemory<eax>(int eax0<eax>, int a1) { int v2; // eax@1 int v3; // ebx@2 int v4; // ebp@4 int v5; // edi@4 int v6; // eax@6 unsigned int v7; // esi@6 int v8; // eax@6 int v10; // [sp+4Ch] [bp-1Ch]@1 __int64 v11; // [sp+50h] [bp-18h]@1 _DWORD v12[2]; // [sp+58h] [bp-10h]@1 __int64 v13; // [sp+60h] [bp-8h]@1 v10 = 0; _mm_storel_epi64(&v11, 0); _mm_storel_epi64(v12, 0); _mm_storel_epi64(&v13, 0); v2 = GetSuspiciousProcessHandle(eax0); if ( !v2 || (v3 = lOpenProcess(1040, 0, v2)) == 0 || !lVirtualQueryEx(v3, 0, &v10, 28) ) return 0; while ( 1 ) { v4 = v10; v5 = v12[0]; if ( !(v12[1] & 0x1000) || !(BYTE4(v11) & 0xD0) ) goto LABEL_8; v6 = lVirtualAlloc(0, v12[0], 4096, 4); v7 = v6; v8 = lReadProcessMemory(v3, v4, v6, v5, 0); if ( PatternScanner(v8, v7, v5, a1) ) return v4; lVirtualFree(v7, 0, 32768); LABEL_8: if ( !lVirtualQueryEx(v3, v4 + v5, &v10, 28) ) return 0; } }
Here is the suspicious exe detector code :
That's +0x08 of the processentry which is DWORD th32ProcessID;Code:int __usercall GetSuspiciousProcessHandle<eax>(int a1<edi>) { void *v1; // esi@1 char *v2; // eax@2 char v3; // cl@3 int v4; // eax@4 char *v5; // edx@4 int i; // ecx@4 int result; // eax@8 int v8; // [sp+10h] [bp-12Ch]@1 int v9; // [sp+18h] [bp-124h]@9 char v10; // [sp+34h] [bp-108h]@2 _BYTE v11[3]; // [sp+35h] [bp-107h]@4 v1 = lSnapShop32(2, 0); v8 = 296; if ( lProcess32First(v1, &v8) ) { while ( 1 ) { v2 = &v10; do v3 = *v2++; while ( v3 ); v4 = v2 - v11; v5 = &v10; for ( i = 5381; v4; --v4 ) i = 33 * i + *v5++; if ( i == a1 ) break; if ( !lProcess32Next(v1, &v8) ) goto LABEL_8; } result = v9; } else { LABEL_8: lCloseHandle(v1); result = 0; } return result; }
Every function of the anti cheat, patching it shouldn't be hardCode:check7 .text 009C60B0 0000003D R . . . . T . LaunchAndCommunicateWithACThread .text 009C60F0 00000084 R . . . . T . CheckForDebuggerThread .text 009C6180 0000009D R . . . . . . CheckForCheatThread .text 009C6250 000000DD R . . . . T . LoadAC .text 009C63C0 00000052 R . . . . . . LoadCryptedModulename .text 009C6420 00000078 R . . . . . . loadModules .text 009C64A0 00000263 R . . . . . . SomeHashFunction .text 009C6710 00000022 R . . . . T . GetFlags .text 009C6740 00000034 R . . . . . . CheckThreadEvent .text 009C6780 00000079 R . . . . . . CheckExceptionEvent .text 009C6800 00000061 R . . . . . . getDecryptedModuleName .text 009C6870 0000004D R . . . . T . GetPoeHandle .text 009C68C0 0000005E R . . . . . . GetSuspiciousProcessHandle .text 009C6920 00000093 R . . . . . . randomBetween1And15 .text 009C69C0 0000004A R . . . . . . decrypt .text 009C6A10 00000061 R . . . . . . GetFlagForAction .text 009C6A80 000001A1 R . . . . T . check1 .text 009C6C30 00000082 R . . . . T . check2 .text 009C6CC0 0000004D R . . . . . . check3 .text 009C6D10 0000004D R . . . . . . check4 .text 009C6D60 00000052 R . . . . . . check5 .text 009C6DC0 0000004C R . . . . . . check6 .text 009C6E10 00000050 R . . . . . . CheckModule .text 009C6E60 000000CD R . . . . . . CheckForExeFileName .text 009C6F30 000000B3 R . . . . . . PatternScanner .text 009C6FF0 000000A9 R . . . B . . CheckForModifiedMemory .text 009C70A0 00000065 R . . . . . . CheckExternalApplicationMemory .text 009C7110 000000DD R . . . . . . CheckForWindowText .text 009C71F0 000000C3 R . . . . . . CheckForUnmappedDLL .text 009C72C0 000000AB R . . . . . .
I guess GGG knows about my VirtualGirl.exe now huh? :P
Is it still only looking for the maphack pattern or is there more now?
I didn't do any "run" debugging, just interpretation from the ASM.
Someone needs to run it inside a debugger to know which pattern are being scanned. I'll post the C file + idb file if you guys are interested. (for idaq 6.1)
The reason GGG states that their AC isn't a spyware is because they don't send the list of your running processes/window name to GGG server, they hash it and compare it to a local database, that's all.
Let's say poehud.exe hash is 54871257, if you rename it it'll become something else so that detection is already bypassed.
Then it checks for the window name, in our case it's ExileHUD (random hash : 28392843945, if that hash is detected, it's also flagged.
To bypass that you'll just need to change the window name in the source of poehud.
Then memory changes in poe itself you'll need to patch poe.exe for that (or have a hardware hook to 'trick' the AC into thinking the memory wasn't modified)
The checkmodule is probably looking for dll name inside of poe, i'll post about that in a bit, didn't check yet, but then again renaming the dll should do it.(unless it's using the base dll address)
checkforunmappeddll is actually misnamed, it's checking for memory region that weren't created by poe itself (virtualalloc from external source etc)
i think that's about all detection implemented so far.
Last edited by Ouariasse; 01-16-2015 at 01:18 AM.
CheckModule checks for the hash of LPMODULEENTRY32[+0x20] which is szModule (http://msdn.microsoft.com/en-us/libr...v=vs.85).aspx)Code:int __stdcall CheckModule(int a1) { int v1; // eax@1 int v2; // edi@1 int result; // eax@2 char *v4; // eax@3 char v5; // cl@4 int v6; // eax@5 char *v7; // esi@5 int v8; // edx@5 int v9; // ecx@6 int v10; // esi@11 int v11; // [sp+10h] [bp-224h]@1 char v12; // [sp+30h] [bp-204h]@3 _BYTE v13[3]; // [sp+31h] [bp-203h]@5 v1 = lGetCurrentProcessId(); v2 = lSnapShop32(8, v1); v11 = 548; if ( lModule32First(v2, &v11) ) { while ( 1 ) { v4 = &v12; do v5 = *v4++; while ( v5 ); v6 = v4 - v13; v7 = &v12; v8 = 5381; if ( v6 ) { v9 = v6; do { v8 = 33 * v8 + *v7++; --v9; } while ( v9 ); } if ( v8 == a1 ) break; if ( !lModule32Next(v2, &v11) ) { lCloseHandle(v2); return 0; } } v10 = GetFlags(v6, &v12); lCloseHandle(v2); result = v10; } else { lCloseHandle(v2); result = 0; } return result; }
So that's probably the dll name? idk but yea it checks for injected dll.
well the lastest cracked hexray decompiler is 1.5, even if i get 6.6 i still would have the old plugin. The new hexraydecompiler is like 1.9 or even 2.0 but the licensing fee for it is like $500. It would make reversing games 100x easier though for thousands of people if it were leaked.
Complete AC reversed :
http://www.privatepaste.com/aeb4877e02
And not sure what this function is but it's called after every check :
signed __int32 __cdecl SomeFunc1(void *a1, int a2)
{
signed __int32 result; // eax@1
dword_C8EE08 = a1;
dword_C8EE04 = a2;
result = _InterlockedExchange(&a1, a1);
dword_C905F4 = 1;
return result;
}
Last edited by Ouariasse; 01-16-2015 at 01:50 AM.
Now the fun part.. figuring out what it all means.
Really impressive work digging through the code, Ouariasse. In your opinion, would you say it's best to steer clear of simple memory editing now that they have these countermeasures in place?
You can hardpatch the poe exe to render the AC useless, just need to find the signature table and alter it to detect stuff that doesn't exist
Signature code is :
v8 = 5381;
if ( v6 )
{
v9 = v6;
do
{
v8 = 33 * v8 + *v7++;
--v9;
}
while ( v9 );
}
v6 being the char* to hash, here's a pseudo "readable" code
Code:char * = "PoeHUD.exe"; int lenstringtocheck = strlen(stringtocheck); int hashsignature = 5381; if (char *stringtocheck) { char* currentPtr = stringtocheck; do { hashsignature = 33 * hashsignature + *currentPtr++; lenstringtocheck--; } while (lenstringtocheck); if (hashsignature == MALICIOUSHASH) { //OMG YOU ARE CHEATING } }
Last edited by Ouariasse; 01-16-2015 at 02:12 AM.
Ouariasse: check your PMs![]()
Thanks bro <3
I am very thankful for you guys and your great work, and very happy you took up the gauntlet they threw. I will support you even more in future, because you're working on things this game needs so much to be perfect, and they refuse to add them (itemlevel, loot alert for colorblind, disabling particles for slow computers ect.).
But do you think GGG does not know the URL of this forum and they don't lurk here already, preparing themselves for things you're making against them?
Is it correct to share your great knowledge in this open forum?
Cheers.