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.

BackGround

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

7 people like this post.
    • hoschi111
    • 18. Mai. 2014 1:41pm

    Geil! Ich freue mich schon auf mehr. Nieder mit Apex, du schaffst das!

  1. Noch keine TrackBacks.