Hooking: Originalfunktion aufrufen lässt Zielprozess crashen



  • Hi,
    um in DirectX 9 Anwendungen Overlays zu erzeugen will ich die EndScene Funktion in d3d9.dll hooken.
    Dazu habe ich dieses Tutorial http://www.elitepvpers.com/forum/warrock-guides-tutorials-modifications/839557-howto-d3d-hack-selber-machen.html mal durchgelesen und nachgecodet.
    Leider stürzt mein Test-Zielprocess ( http://www.theolith.com/ ) nach dem Aufruf meiner Detourfunktion ab und zwar beim aufrufen der Originalfunktion.

    Der Code der Dll die in den Prozess Injected wird:

    #pragma once
    
    #pragma comment(lib, "d3d9.lib")
    //#pragma comment(lib, "d3dx9.lib")
    
    #include <windows.h>
    #include <cstdio>
    #include <d3d9.h>
    //#include <d3dx9.h>
    #include <fstream>
    #include "FindPattern.h"
    
    typedef HRESULT(__stdcall* EndScene_t)(LPDIRECT3DDEVICE9);
    EndScene_t endSceneOrig;
    
    HRESULT __stdcall endSceneDetour(LPDIRECT3DDEVICE9 pDevice)
    {
    	return endSceneOrig(pDevice);
    
            (das ganze als ASM)
    
            5CF71EFE  mov         esi,esp  
            5CF71F00  mov         eax,dword ptr [pDevice]  
            5CF71F03  push        eax  
            5CF71F04  call        dword ptr [endSceneOrig (5CF78138h)] [b] <-- hier ensteht der Fehler[/b]
            5CF71F0A  cmp         esi,esp  
            5CF71F0C  call        @ILT+375(__RTC_CheckEsp) (5CF7117Ch)  
    }
    
    void *DetourFunc(BYTE *src, const BYTE *dst, const int len) 
    {
    	BYTE *jmp = (BYTE*)malloc(len+5);
    	DWORD dwback;
    
    	VirtualProtect(src, len, PAGE_READWRITE, &dwback);
    	memcpy(jmp, src, len); 
    	jmp += len;
    
    	jmp[0] = 0xE9;
    	*(DWORD*)(jmp+1) = (DWORD)(src+len - jmp) - 5; [b] <--- Falsch?[/b]
    
    	src[0] = 0xE9;
    	*(DWORD*)(src+1) = (DWORD)(dst - src) - 5;
    
    	VirtualProtect(src, len, dwback, &dwback);
    
    	return (jmp-len);    [b]<--- Falsch?[/b]
    }
    
    void initHook()
    {
    	HMODULE hModule = NULL;
    
    	while(!hModule)
    	{
    		hModule = GetModuleHandleA("d3d9.dll");
    		Sleep(100);
    	}
    
    	DWORD* VTableStart = 0;
    	DWORD FoundByGordon = dwFindPattern((DWORD)hModule, 0x128000, (PBYTE)"\xC7\x06\x00\x00\x00\x00\x89\x86\x00\x00\x00\x00\x89\x86", "xx????xx????xx");
    	memcpy(&VTableStart, (void*)(FoundByGordon+2), 4);
    
    	DWORD dwEndScene = VTableStart[42];
    	endSceneOrig = (EndScene_t) DetourFunc((BYTE*)dwEndScene, (BYTE*)endSceneDetour, 5);
    }
    
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  reason,
                           LPVOID lpReserved
    					 )
    {
    	switch(reason)
    	{
    		case DLL_PROCESS_ATTACH:
    
    		CreateThread(0, 0, (LPTHREAD_START_ROUTINE) initHook, 0, 0, 0);
    
    		break;
    
    	}
    
    	return true;
    }
    

    Es ist mir leider ein Rätsel warum das passiert. Ich vermute allerdings das der jmp von Detour zur Originalfunktion falsch berechnet wird und ich damit ins Nirvana jmpe.
    Wäre wirklich super wenn mich Jemand aufklären könnte.

    War mir jetzt nicht sicher in welches Forum die Frage genau gehört aber ein Problem mit DirectX schließ ich (naiv) mal aus.



  • 5CF71F04  call        dword ptr [endSceneOrig (5CF78138h)]
    

    Hast du mal geschaut was an der Adresse 5CF78138h steht?

    Überprüf auch mal mit einem Debugger ob dein JMP richtig injected wird.

    Ich würde es wohl so machen:

    void *DetourFunc(BYTE *src, const BYTE *dst ) 
    {
    DWORD dwback;
    VirtualProtect( src, 5, PAGE_EXECUTE_READWRITE, &dwback);
    *( (DWORD*)(src+1) )=  (DWORD)dst - (DWORD)src - 5; 
    
    VirtualProtect( src, 5, dwback, &dwback);
    }
    

    Überprüf auch ob wirklich GENAU das im Prozess steht was du haben willst:
    Eventuell

    HRESULT __declspec(naked) endSceneDetour(LPDIRECT3DDEVICE9 pDevice)
    

    verwenden den __stdcall fügt einen Epilog und einen Prolog in den Code ein.

    HRESULT __stdcall endSceneDetour(LPDIRECT3DDEVICE9 pDevice)
    {
        return endSceneOrig(pDevice);
    
            (das ganze als ASM)
    
            5CF71EFE  mov         esi,esp  
            5CF71F00  mov         eax,dword ptr [pDevice]  
            5CF71F03  push        eax  
            5CF71F04  call        dword ptr [endSceneOrig (5CF78138h)]  <-- hier ensteht der Fehler
            5CF71F0A  cmp         esi,esp  
            5CF71F0C  call        @ILT+375(__RTC_CheckEsp) (5CF7117Ch)  
    }
    

    Beispiel:

    HRESULT __stdcall endSceneDetour()
    {
     _asm
      {
       nop
       nop
      }
    }
    

    Erzeugt folgendes:

    00401168 >/$ 55             PUSH EBP
    00401169  |. 8BEC           MOV EBP,ESP
    0040116B  |. 90             NOP
    0040116C  |. 90             NOP
    0040116D  |. 5D             POP EBP
    0040116E  \. C3             RETN
    

    Bei HRESULT _declspec(naked) endSceneDetour();
    Hast du in der Funktion wirklich nur das was du auch schreibst.

    Falls du vorher noch nie was mit Hooks gemacht hast bau dir selber eine kleine Anwendung und versuch darin irgendwas zu Hooken:

    Und les mal hier: http://www.vivid-abstractions.net/category/programming/tutorials/

    Dort wird das Function Hooking gut erklärt.



  • Wie soll das funktionieren? Die ersten 5 Byte der originalen Funktion werden mit einem Sprung auf den Hook überschrieben. Von diesem aus wird dann aber wieder die original Funktion aufgerufen - Das ist unendliche Schleife die dazu führt, dass der Stack überläuft. Außerdem ist der Funktionszeiger endSceneOrig noch ungültig, wenn die Hook Bytes bereits geschrieben sind.



  • Wie wird den Sichergestellt, dass kein Befehl beim kopieren/überschreiben "zerschnitten" wird?



  • Da hab ich wohl die hälfte vom ASM vergessen.

    HRESULT __stdcall endSceneDetour(LPDIRECT3DDEVICE9 pDevice)
    {
    5CF71EE0  push        ebp  
    5CF71EE1  mov         ebp,esp  
    5CF71EE3  sub         esp,0C0h  
    5CF71EE9  push        ebx  
    5CF71EEA  push        esi  
    5CF71EEB  push        edi  
    5CF71EEC  lea         edi,[ebp-0C0h]  
    5CF71EF2  mov         ecx,30h  
    5CF71EF7  mov         eax,0CCCCCCCCh  
    5CF71EFC  rep stos    dword ptr es:[edi]
    
    	return endSceneOrig(pDevice);
    
    5CF71EFE  mov         esi,esp  
    5CF71F00  mov         eax,dword ptr [pDevice]  
    5CF71F03  push        eax  
    5CF71F04  call        dword ptr [endSceneOrig (5CF78138h)]  
    5CF71F0A  cmp         esi,esp  
    5CF71F0C  call        @ILT+375(__RTC_CheckEsp) (5CF7117Ch)  
    }
    

    __stdcall sollte stimmen ( wenn ich STDMETHOD(EndScene)(THIS) PURE; aus der d3d9.h richtig interpretiere ). Hab naked mal getestet: compiler mekert das naked Funktionen nichts returnen dürfen. Ich werd mir nochmal alles in ruhe im Speicher anschauen.

    mhhh schrieb:

    Wie soll das funktionieren? Die ersten 5 Byte der originalen Funktion werden mit einem Sprung auf den Hook überschrieben. Von diesem aus wird dann aber wieder die original Funktion aufgerufen - Das ist unendliche Schleife die dazu führt, dass der Stack überläuft. Außerdem ist der Funktionszeiger endSceneOrig noch ungültig, wenn die Hook Bytes bereits geschrieben sind.

    ..
    
    memcpy(jmp, src, len);
    jmp += len;  //(len = 5)
    
    ..
    
    return (jmd-5);
    

    Der returnte Wert sollte den ungehookten Aufruf darstellen und mich somit auch zur Originalfunktion bringen oder? Das endSceneOrig ungültig ist könnte natürlich das Problem sein. Ich werd ma schnell nen Launcher schreiben der dafür sorgt das der Prozess Suspendet ist bis gehookt wurde.



  • Ich gebe mal ein Beispiel wie ich es bei meinem Guild Wars 2 Speedhack mache: (bzw. der Speedhack is noch nicht ganz fertig ein jmp im allokierten Speicherbereich wird noch nicht injectet. Aber das mach ich gleich noch.^^ [ok fertig funktioniert alles :D] )

    (btw. ich bin gegen das Cheaten in irgendwelchen Online Ligen z.b. ESL,WCG,W3CL !)

    00ABCB34 - D9 86 70010000             - fld dword ptr [esi+00000170]
    00ABCB3A - D9 9F D8000000             - fstp dword ptr [edi+000000D8]
    

    Zuerst sichere ich mir die Opcodes die an Adresse 00ABCB34 stehen.

    fld dword ptr [esi+00000170] überschreibe ich mit:
    jmp AdresseMeinesReserviertenSpeichers.

    (Den Speicher reserviere ich mit VirtualAllocEx.)

    Anschließend injecte ich an AdresseMeinesReserviertenSpeichers folgendes:
    mov [esi+00000170],41C80000

    Dann injecte ich: fld dword ptr [esi+00000170]

    Am Ende injecte ich noch einen jmp um wieder aus meinem reservierten Speicherbereich rauszukommen.

    (Ich mache das ohne DLL direkt als Code Injection.)

    Alles was man überschreibt muss man natürlich auch sichern und wiederherstellen.

    Chrises schrieb:

    Hab naked mal getestet: compiler mekert das naked Funktionen nichts returnen dürfen.

    Den return musst du selber bauen, also anstatt z.n. return xy schreibst du_ jmp xy



  • Also ich habs jetzt mit dem Lauchner probiert und das funktioniert leider auch nicht.

    proc.suspendResumeProc(true);  <-- schlafen
    
    	proc.injectDll(DLLPATH);       <-- injection und hooking
    
    	Sleep(10000);                  <-- 10sek sollten wohl dicke ausreichen
    
    	proc.suspendResumeProc(false); <-- aufwachen
    

    Morgen geh ich mal Schritt für Schritt mim debugger durch und schau mir alle Addressen und Offsets genau an. Kann ja nicht sein das das nicht geht.
    Ma ne Nacht drüber schlafen hilft vielleicht auch mal 🙂



  • Ich hab mir jetzt mal alles im debugger angeschaut:

    Ungehookte EndScene Funktion:

    MOV EDI, EDI
    PUSH EBP
    MOV EBP, ESP
    PUSH -1
    
    ...
    

    Gehookte EndScene Funktion:

    JMP endSceneDetour
    PUSH -1
    
    ...
    

    Meine Funktion hatte ich zuerst so angepasst:

    __declspec(naked) int endSceneDetour(LPDIRECT3DDEVICE9 pDevice)
    {
    	__asm
    	{
    		CALL endSceneOrig
    		RETN 4
    	}
    }
    

    Der call hat hier hin geführt (BYTE* jmp aus DetourFunc):

    STD
    STD
    STD
    MOV EDI, EDI      <-- endSceneOrig
    PUSH EBP
    MOV EBP, ESP
    JMP (EndScene + 5)  // jmp nach push -1
    STD
    STD
    STD
    

    Beim Befehl MOV EDI, EDI crasht der Prozess leider.
    Wenn meine Funktion so aussieht:

    __declspec(naked) int endSceneDetour(LPDIRECT3DDEVICE9 pDevice)
    {
    	__asm
    	{
    		MOV EDI, EDI
    		PUSH EBP
    		MOV EBP, ESP
    		CALL (EndScene + 5) // PUSH -1
    		RETN 4
    
    	}
    }
    

    Funktioniert alles. Kann es sein das die gespeicherten 5 Bytes nicht ausführbar sind?



  • Chrises schrieb:

    Kann es sein das die gespeicherten 5 Bytes nicht ausführbar sind?

    VirtualAlloc mit PAGE_EXECUTE_READWRITE? Ansonsten könnte das NX Bit stören (DEP).
    Du verlässt dich drauf, das der Code der Funktion auf allen Systemen gleich ist!? Normalerweise hat man einen Disassembler, mit dem man ermittelt wie viele Instruktionen gesichert werden müssen.



  • Original EndSence

    MOV EDI, EDI
    PUSH EBP
    MOV EBP, ESP
    PUSH -1
    

    Wo jetzt mov edi,edi steht setzt du deinem JMP der überschreibt:

    PUSH EBP
    MOV EBP, ESP
    

    So das du nur noch hast:

    JMP endSceneDetour
    PUSH -1
    

    Dein DLL Injector sollte so denn Speicher reservieren:

    void *addr = VirtualAllocEx(ProcessHandle, 0, strlen(DLL_NAME.c_str() ) , MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    Eventuell versuch dich erst mal an einem anderen Spiel um 100% Sicherzustellen das an deinem Code alles ok ist.

    Mir fällt jetzt auch so nix ein außer das es ein Problem mit VirtualAlloc gibt. (Im Bezug auf die Parameter von VirtualAlloc).



  • Ich denke mal das es nicht unbedingt nötig ist das zur Laufzeit zu Disassambeln. Hab gelesen das genau diese 5 bytes die der JMP benötigt der Funktions prolog sind und der sollte mindestens auf allen Windows Systemen gleich sein. Korrigiert mich bitte wenn ich da falsch liege.

    VirtualAlloc teste ich nachher wenn ich am Pc bin.

    So wies immoment ist funktioniert das Hooken beim Testprogramm und Guild Wars 2. Allerdings das modifizieren der D3D Device nur beim Tesprogramm was aber wieder ein ganz anderes Thema ist. Wir im Tutorial beschrieben kann ich beim Testprogramm einen Bereich mit Farbe überdecken.



  • Falls es jemanden Interesiert:

    damit der Code läuft habe ich jetzt einfach die

    BYTE *jmp = (BYTE*)malloc(len+5);
    

    aus DetourFunc durch

    BYTE *jmp = (BYTE*)VirtualAllocEx(GetCurrentProcess(), 0, len + 5 , MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    

    ersetzt und die endSceneDetour in ihren Originalzustand gebracht

    HRESULT __stdcall endSceneDetour(LPDIRECT3DDEVICE9 pDevice)
    {
    	return endSceneOrig(pDevice);
    }
    

    Vielen dank für die ganzen Antworten 😉

    Edit:

    Um GW2 zu Overlayen muss der State der D3DDevice gesichert werden:

    HRESULT __stdcall endSceneDetour(LPDIRECT3DDEVICE9 pDevice)
    {
    	DWORD dwback;
    	int savelen = sizeof(*pDevice);
    
    	VirtualProtect(pDevice, sizeof(*pDevice), PAGE_READWRITE, &dwback);
    
    	BYTE* deviceCache = new BYTE[savelen];
    	memcpy(deviceCache, pDevice, savelen);
    
    	DrawRect(pDevice, 10, 10, 200, 200, 0xFFFFFFFF);
    
    	HRESULT ret = endSceneOrig(pDevice);
    
    	memcpy(pDevice, deviceCache, savelen);
    	delete [] deviceCache;
    
    	return ret;
    }
    

    viel Spaß^^



  • Bitte nicht Call und Jmp so vermischen!

    Die DetourFunc alloziert ein Gateway mit dem originalen Code und berechnet ein zeiger darauf als Rückgabewert. Der originale Code führt die Opcodes aus und springt dann zu [Original+length]. Das heißt, dass du in deiner eigenen Funktion ein jmp auf das Gateway programmieren musst:

    __declspec(naked) void endSceneDetour(void)
    {
        __asm {
            //argument an: [esp+4] LPDIRECT3DDEVICE9 pDevice
    
            // __ CODE __
    
            jmp endSceneHook_Gateway
        }
    }
    

    D.H. Programmfluß:
    Originale Funk. -> Neue Funk. -> Gateway -> Originale Funk. + Länge

    Der Sinn des Gateway ist, dass dynamisch anhand der Länge der originale Code kopiert und dann ausgeführt wird und dann zurück + Länge gesprungen wird. Du selbst musst nur noch die Länge des zu überschreibenden Codes wissen. Bei einer Größe > 5 solltest du, falls du nicht eine andere Stelle suchen willst, Nops einfügen. Meine eigene Detour Funktion macht dies.
    Z.B: Länge: 7 = {jmp 0xADRESSE; nop; nop; }

    In deinem Fall führst du den originalen Code doppelt aus, des Weiteren wird durch dein Call und Ret eine Menge korruptet. Immerhin hast du durch deinen Call noch mal 4 Bytes auf den Stack geschoben. Schau dir die DetourFunc noch mal genau an, bzw. was sie genau macht!

    Ausserdem:

    BYTE *jmp = (BYTE*)VirtualAllocEx(GetCurrentProcess(), 0, len + 5 , MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    

    Geht gar nicht!^^

    malloc, bzw new! oder eben VirtualAlloc. In deinem Fall wird unnötige CPU Zeit verbraucht.
    Wenn du die Zugriffsrechte auf Speicher im Prozessraum deines Targets modifizieren willst, dann benutz bitte VirtualProtect(). Speicher aber den originalen Zugriffsstatus und stelle ihn nach dem patchen wieder her.

    PS: auf www.gamedeception.net gibt es anschaulichen Code + Erklärungen (Englisch)

    Falls ich etwas vergessen habe: Bin gerade in einer Word 2010 Lehrstunde und muss also gerade Multitasken. :p



  • Der originale Code führt die Opcodes aus und springt

    Ich meinte natürlich "Das Gateway"

    und:

    Mit VirtualProtect die Rechte des Zielcodes ändern, speichern und dann nach der Operation wiederherstellen.

    So, bin dann mal weg.

    ps: @Bassmaster: Biste auch auf gd? :p



  • gamehack0r schrieb:

    ps: @Bassmaster: Biste auch auf gd?

    Nein, ich kenne die Seite gar nicht.
    Werd ich mir aber mal anschauen, aber ich bin eigentlich nicht so der Gamehacker.

    Außerdem regt es mich auf wenn die ganzen Leute ihre Programme fertig Compiliert rausrücken, den dann kommen wieder die ganzen Bengels raus und meinen damit Schabernack treiben zu müssen ... Wenn schon public machen dann nur den Source Code und ein paar "Anti copy paster Maßnahmen" einbauen.^^

    z.b.

    char v[2]; for(int i=0;<1000;i++) {v[i]='A';}
    

    Dann gibts ja schon mal einen Programmabsturtzt und die Bengels wüssen nicht wo der Klabautermann aufm Schiffsdeck sein Unwesen treibt. 😃 (Vereinfacht^^: Die Bengels wüssen nicht wieso ihr Programm immer abstürtzt weil sie nicht Programmieren können und einfach alles copy pasten.)

    Ich hab mich nur mal etwas mit dem Gamehacking Thema auseinander gesetzt, das tollste was ich in dem Bereich gemacht hab war wohl mein Speed und Flyhack für Guild Wars 2 und ein cvars Speed und Wallhack für CSS und CS:Go. Naja und ein Wallhack für eine OpenGL Anwendung von der Seite NeHe Gamedev. xD

    Für weitere Sachen fehlt mir da irgendwie die Motivation.^^



  • Bassmaster schrieb:

    gamehack0r schrieb:

    ps: @Bassmaster: Biste auch auf gd?

    Nein, ich kenne die Seite gar nicht.
    Werd ich mir aber mal anschauen, aber ich bin eigentlich nicht so der Gamehacker.

    Außerdem regt es mich auf wenn die ganzen Leute ihre Programme fertig Compiliert rausrücken, den dann kommen wieder die ganzen Bengels raus und meinen damit Schabernack treiben zu müssen ... Wenn schon public machen dann nur den Source Code und ein paar "Anti copy paster Maßnahmen" einbauen.^^

    z.b.

    char v[2]; for(int i=0;<1000;i++) {v[i]='A';}
    

    Dann gibts ja schon mal einen Programmabsturtzt und die Bengels wüssen nicht wo der Klabautermann aufm Schiffsdeck sein Unwesen treibt. 😃 (Vereinfacht^^: Die Bengels wüssen nicht wieso ihr Programm immer abstürtzt weil sie nicht Programmieren können und einfach alles copy pasten.)

    Ich hab mich nur mal etwas mit dem Gamehacking Thema auseinander gesetzt, das tollste was ich in dem Bereich gemacht hab war wohl mein Speed und Flyhack für Guild Wars 2 und ein cvars Speed und Wallhack für CSS und CS:Go. Naja und ein Wallhack für eine OpenGL Anwendung von der Seite NeHe Gamedev. xD

    Für weitere Sachen fehlt mir da irgendwie die Motivation.^^

    Ich halte das für sinnlos. Es gibt so viele anderen Code der wirklich mit Copy & Paste funktioniert. Außerdem gibt es extrem viele fertig Compilierte Programme.

    Wenn ein Spiel (Anwendung) eine Schwachstelle hat die Cheating erlaubt sind hierfür meistens mehrere Programme vorhanden. (Und dann liegt das Problem beim Spiel) Da bring es überhaupt nichts extra Fehler einzubauen, denn dadurch werden hauptsächlich Personen betraft, die solche Programme eher aus (wissenschaftlicher) Neugier als aus Cheating-Absichten betrachten.

    Im Gegenteil werden solche Schwachstellen in Spielen meistens erst dann von den (Spiele) Herstellern geschlossen, wenn sie massenhaft ausgenutzt werden.



  • Leute die Programmieren können gehen mit nem Debugger durch und finden den Fehler sofort ...

    Andreas XXL schrieb:

    dadurch werden hauptsächlich Personen betraft, die solche Programme eher aus (wissenschaftlicher) Neugier als aus Cheating-Absichten betrachten.

    Die Leute sollten aber in der Lage sein den Fehler schnell zu finden.^^
    Ich meine sowas:

    char v[2]; for(int i=0;<1000;i++) {v[i]='A';}
    

    Ist ja wohl doch recht auffällig und mit einem Debugger auch leicht zu finden.

    Andreas XXL schrieb:

    Im Gegenteil werden solche Schwachstellen in Spielen meistens erst dann von den (Spiele) Herstellern geschlossen, wenn sie massenhaft ausgenutzt werden.

    Wie alt ist nochmal CS 1.6 und CSS ?^^ Da gibts doch so einige Cheater.



  • @AndreasXXL: Naja, wenn ein Hackcoder in einen veröffentlichen Src c&p fehler reinhaut, dann kann ein noob, je nach dem wie der c&p error "implementiert" ist, eh nicht viel damit anfangen. Und jemand der was damit anfangen kann, wird sich an so etwas wohl kaum stören, da er den kern des sourcecodes versteht und etwas eigenes implementiert. Die ärgerlichsten (die mag keiner in der "richtigen" szene) sind die, die mit dem geklauten Scheiß auch noch Kohle machen und dann sowieso kein Support geben können (Hack Selling). Trotzdem hast du natürlich recht, gerade bei 1.6 bzw auch generell so Quake engine basierten Sachen gibts viel fertigen Kram und viele public Hacks waren oft auch einfach c&p kram. Es wurden sogar schon Ripps von public Hacks bei Ebay verkauft. 😕

    @Bassmaster: Auf GD gibts nur Sourcecodes und keine Binaries. Ist ein reines Development Board und Leute, die C&P Code wollen, werden gleich gebannt xD (soll jetzt aber keine Werbung sein)

    Na und das Cheat/Anticheat Battle ist halt auch so ein hin und her. Aber das ist halt alles individuell und technisch bedingt. Gut bei CS 1.6 wird wohl auch nichts mehr passieren, das Spiel bringt eh nicht mehr viel Geld ein (wenn überhaupt noch). Wie es bei den ganzen Battlefields und Call of Duty's aussieht weiß ich nicht, da ich das gar nicht habe. Interessant wird es vllt bei CS:GO, aber das habe ich auch noch nicht leider, kann dazu also auch nichts sagen. Hacks gibt es natürlich schon (Client side und server side).

    PS: Ich cheate selbst auch nicht, aber das Development macht schon spass. 🙂


Log in to reply