Apex AntiCheat - Erste Schicht einer großen Zwiebel
Da hat das Reverse Engineering Fieber wieder zugeschlagen und mir dieses mal einen sehr hartnäckigen Gegner entgegen geworfen: Ein Anti-Cheat System Namens Apex (Avital), welches u.a. von den Spielen Wolfteam und StarGateZ verwendet wird. Im konkreten wird es in dieser Serie um das (hoffentliche erfolgreiche) Bypassen des AntiCheats für Wolfteam gehen.
Fangen wir mit ein paar Basics an und analysieren, wo wir Apex vermuten: Im Ordner von Wolfteam gibt es einen Unterordner “avital” mit verschiedensten Dateien drin:
axxalog, TrayDlg.bin, ApexProgress.da_, LoadSysControl.da_, ApexProgress.dat, LoadSysControl.dat, SysDownLoad.dll, SysLoad.dll, TrayDlg.dll, ApexProgress.exe, LoadSysControl.exe, Config.ini, vssver2.scc, update.u, apexprogress.xoe, bootthunker.xoe, childprocboot.xoe, childprocfuncloader.xoe, chk.xoe, initgamer.xoe, loaddrv.xoe, packmodboot.xoe, parentprocboot.xoe, parentprocfuncloader.xoe, showerrorinfo.xoe, thegameonlymod.xoe, trdlg.xoe, usedrv.xoe, webd.xoe, AXCMN_DRVMAIN.XSE, axpack.xse
Die .xoe Dateien sind lediglich unbenannte DLL Dateien, welche jeweils 1-5 Funktionen exportieren. Diese werden per GetProcAddr geladen und anschließend aufgerufen. Damit können wir bisher wenig anfangen, denn alle Dateien einzeln zu analysieren wäre stundenlange Arbeit.
Der nächste Ansatz ist der Launcher, welcher Spieleupdates herrunterläd und Wolfteam im Anschluss Startet. Irgendwo hier muss auch Apex geladen werden! Die Vermutung liegt nahe, dass der Launcher das eigentliche Wolfteam-Spiel per CreateProcess startet, also direkt mal OllyDbg angeschmissen und reindebugged:
Address Value ASCII Comments 0018D108 /0018D388 ˆÓ ; |ApplicationName = "C:\Program Files (x86)\Wolfteam\WolfTeam.bin" 0018D10C |0018D490 Ô ; |CommandLine = ""C:\Program Files (x86)\Wolfteam\WolfTeam.bin" easysurfer 6c65696465725f6e696368745f6d65696e5f7077 72" 0018D110 |00000000 ; |pProcessSecurity = NULL 0018D114 |00000000 ; |pThreadSecurity = NULL 0018D118 |00000000 ; |InheritHandles = FALSE 0018D11C |00080000 ; |CreationFlags = 80000 0018D120 |00000000 ; |pEnvironment = NULL 0018D124 |00000000 ; |CurrentDirectory = NULL 0018D128 |0018D168 hÑ ; |pStartupInfo = 0018D168 -> STARTUPINFOA {Size=72., Reserved1=NULL, Desktop=NULL, Title=NULL, X=0, Y=0, Width=0, Height=0, XCountChars=0, YCountChars=0, FillAttribute=0, Flags=0, ShowWindow=SW_HIDE, Reserved2=0, Reserved3=NULL, hStdInput=NULL, hStdOutput= 0018D12C |0018D230 0Ò ; \pProcessInformation = 0018D230 -> PROCESS_INFORMATION {hProcess=NULL, hThread=NULL, ProcessID=0 (0.), ThreadID=0}
Damit können wir doch schon mehr anfangen! Anscheinend startet der Launcher die Wolfteam.bin und übergibt als Argumente den Usernamen, das Passwort (Hex Codiert) und die Nummer 72, die vieleicht für das Patchlevel steht. In Zukunft brauchen wir den Launcher also nicht mehr, um Wolfteam zu starten! Wir Übergeben also Olly unsere Argumente und starten Wolfteam erneut, aber diesesmal direkt. Das Laden/Updaten des Anticheats blinkt kurz auf und schon ist unser Spiel im Fullscreen gestartet. Der Anticheat scheint also schnell geladen zu werden! In OllyDbg die Option setzen, dass er bei jedem neuen Module “anhalten” soll schafft Abhilfe.
Allerdings ist diese Methode auch nicht mit Erfolg gekrönt worden. Denn nach dem Laden von ein paar Modulen startet Wolfteam zwar, allerdings in einem neuen Prozess! Und so schnell wie dieser Prozess startet können wir keinen Debugger attachen… Wir schauen uns also genauer an, welche Module geladen werden.
Chk.xoe ist das erste Modul das geladen wird. Ein Backtrace im Stack zeigt auch, was Wolfteam mit dem Laden vorhat:
CPU Disasm 007862D9 |. 52 PUSH EDX ; /Arg1 = Chk_xoe. 007862DA |. FF15 40F07900 CALL DWORD PTR DS:[KERNEL32.LoadLibraryW] ; 007862E0 |. 8BF8 MOV EDI,EAX 007862E2 |. 85FF TEST EDI,EDI 007862E4 |. 75 05 JNZ SHORT 007862EB 007862E6 |. 8D70 01 LEA ESI,[EAX+1] 007862E9 |. EB 2B JMP SHORT 00786316 007862EB |. 68 9C0F7A00 PUSH 007A0F9C ; /Arg2 = ASCII "DoChk" 007862F0 |. 57 PUSH EDI ; |Arg1 007862F1 |. FF15 3CF07900 CALL DWORD PTR DS:[KERNEL32.GetProcAddr]; 007862F7 |. 85C0 TEST EAX,EAX 007862F9 |. 75 05 JNZ SHORT 00786300 007862FB |. 8D70 02 LEA ESI,[EAX+2] 007862FE |. EB 0F JMP SHORT 0078630F 00786300 |. FFD0 CALL EAX
Wenn das mal nicht leicht verständlicher ASM Code ist! Zunächst wird “Chk.xoe” auf den Stack geladen und mit einem Aufruf von LoadLibraryW die DLL geladen. Nun wird geprüft ob das Resultat (in EAX, dann in EDI) nicht 0 war. Wenn die Library geladen wurde, wird per GetProcAddr die Adresse der Methode “DoChk” aufgerufen. Da Chk.xoe eine DLL von Apex ist, sind wir wohl nich auf dem ganz falschen Weg. Die DLL führt allerdings nur ein paar HashChecks aus, also ist nicht sehr zielführend. Nach dem Laden von ein paar weiteren Apex Modulen stossen wir auf den BootThunker.xoe und damit beginnt der Spass.
Es wird die Methode “BootThunkerStart” gestartet welche nach ein paar Init-Calls die CommandLine (also die übergebenen Args) abruft:
v1 = GetCommandLineW(); v3 = v1; if ( v1 ) { wcsstr(v1, L"-axchild"); v5 = wcsstr(v3, L"-axparent"); |
Es wird gecheckt, ob -axchild bzw -axparent in den CommandLine Args vorkommt? Von diesen beiden Parametern haben wir bisher nichts gehört! Wenn dieses in den Argumenten vorkommt, so wird die ChildProcBoot-DLL geladen.
v15 = sub_10001490(L"avital", L"ChildProcBoot.xoe"); // Pfad zur DLL erzeugen LibFileName = sub_10001DD0((const char *)L"%s\\%s\\%s", v15); v13 = LoadLibraryW(&LibFileName); // Load Lib v16 = GetProcAddress(v13, "ChildProcBootStart"); // Get ChildProcBootStart // call Stuff |
Ihr ratet was folgt: In Falle dass die Argumente nicht auftreten wird der so ziemlich selbe Code mit der ParentProcBoot ausgeführt. Zeit für einen erneuten Breakpoint auf CreateProcess, sehen wir doch mal wo wir den ChildProzess herzaubern:
CPU Stack Address Value ASCII Comments 0018F6BC [00313A71 q:1 ; /RETURN from KERNEL32.CreateProcessW to ParentProcBoot_xoe.00313A71 0018F6C0 /0018F810 ø ; |ApplicationName = "C:\Program Files (x86)\Wolfteam\avital\ApexProgress.xoe" 0018F6C4 |0018F748 H÷ ; |CommandLine = ""ProgressFlag" "3656"" 0018F6C8 |00000000 ; |pProcessSecurity = NULL
Hier scheint wohl zuerst ApexProgress aufgerufen zu werden, welches ein Update von Apex durchführt und die nette Grafik anzeigt, dass Wolfteam diese AntiCheat-Engine besitzt. Der nächste Aufruf ist schon besser:
CPU Stack Address Value ASCII Comments 0018EED4 [0031154F O1 ; /RETURN from KERNEL32.CreateProcessW to ParentProcBoot_xoe.0031154F 0018EED8 /0018F01C ð ; |ApplicationName = "C:\Program Files (x86)\Wolfteam\Wolfteam_orig.bin" 0018EEDC |0018F224 $ò ; |CommandLine = ""C:\Program Files (x86)\Wolfteam\Wolfteam_orig.bin" -axchild -axparent=3656" 0018EEE0 |00000000 ; |pProcessSecurity = NULL
Und diese Argumente sind uns bereits über den Weg gelaufen. Anscheinend wird Apex nur direkt in den ChildProcess geladen, über den es dann auch volle Kontrolle hat (da selbst erzeugt). In der ChildProcessStart wird übrigens auch ein eigener Treiber mitgeladen, der Funktionen wie OpenProcess und WriteProcessMemory hooked.
Nun lautet die Preisfrage: Wie Apex nicht laden, den richtigen Prozess aber schon? Nun, Apex macht es uns in diesem Falle leicht. Wir unterbinden, dass die ParentProcBootStart gestartet wird. Damit werden keine ChildProzesse gestartet und auch sonst stört sich Wolfteam wenig daran. Es wird in diesem Falle einfach der bereits erstellte Prozess weiter gestartet. So ersetzen wir den Aufruf von ParentProcBootStart einfach durch ein XOR EAX, EAX und durch den Rückgabewert 0 wird praktischerweiße gleich noch die DLL entladen und keine weiteren Funktionen mehr (ParentProcBootEnd) gecalled.
Doch sobald wir wieder wild mit CheatEngine im Speicher von Wolfteam rumwühlen oder gar einen Debugger attachen, so fliegt uns ein Apex Kick um die Ohren.
Ist Apex also noch nicht richtig entladen? Nunja, die Zwiebel hat, wie schon angedeutet, viele Schichten. Der nächsten Schicht widmen wir uns nächstes mal. Dabei geht es zunächst einmal drum, das Patchen der ParentProcBootStart zu automatisieren und einen Windowed Mode für gescheites Debugging zu erreichen.
So Long
Easy
Geil! Ich freue mich schon auf mehr. Nieder mit Apex, du schaffst das!