
ist knapp 11 Seiten lang geworden.
WarRock C&P Hacks mal wirklich verstehen
Hey ho, ich habe mich in der letzten Zeit ein wenig mit dem Gamehacking beschäftigt und mich vor allem mit WarRock auseinandergesetzt. Es gibt sehr viele Hacks für dieses Spiel und alle sind verdächtig ähnlich aufgebaut. Als wäre das nicht genug findet man auch immer mehr Beiträge von Usern die sich bescheren, dass der kopierte Code nicht(mehr) funktioniert, weil sie nicht wissen wie sie den Code anpassen müssen, geschweige denn wissen, wie der kopierte Code überhaupt funktioniert. An solche User richtet sich dieser Text, ich werde versuchen alle Funktionen, die man in den üblichen Sourcecodes findet zu erläutern, wie ich sie mir vor einigen Wochen angeeignet habe. Das hilft euch hoffentlich zu einem erweiterten Verständnis und mir hoffentlich den Stoff zu verinnerlichen.
WarRock ist wohl eins der besten Spiele, um ins Gamehacking einzusteigen, weil es sehr clientseitig programmiert wurde. Das heißt, dass ein Großteil der Berechnungen auf dem eigenen Computer getätigt werden und kaum vom Gameserver überprüft werden. Diese Berechnungen kann man ganz einfach zu eigenen Gunsten manipulieren. So ist es relativ einfach einen Teleporter zu schreiben, der in Spielen wie Counter-Strike und Metin2 wohl nicht so leicht realisierbar wäre.
Wir werden uns in diesem Text mit den einfachen NonMenü WarRock Hacks beschäftigen, da ich erst seit einigen Wochen in C++ programmiere und wenig Erfahrung mit der 3D3-Programmierung habe.
Speicheradressen und deren Werte
Jede hier verwendete Adresse ist wahrscheinlich nicht mehr aktuell!
Fangen wir mal ganz von vorne an. Ein Programm (auch WarRock) besteht aus mehreren Segmenten (genau genommen 5 Stück: Text-,Daten-,BSS- und Heap- und Stacksegment). Jedes Segment hat eine andere Aufgabe und ist in Adressen unterteilt. An jeder Adresse befindet sich ein Wert. Im Textsegment steht z.B der Programmcode, der auch als Opcode bezeichnet wird. Im BSS- und Datensegment befinden sich z.B. globale Variablen des Programmes. Betrachtet man eine bestimmte Adresse, die sich im Textsegment befindet, so erhält man einen Ausschnitt der Assemblerinstruktionen des Programmes. Betrachtet man jedoch eine Adresse innerhalb des Datensegments, so erhält man irgend einen Wert, den das Programm gerade gespeichert hat, z.B. die Anzahl der Lebenspunkte, oder der Name eines Spielers.
Wichtig für uns zu wissen ist, dass eine Adresse immer einen Wert hat. Dieser Wert kann ein Buchstabe, ein Text oder eine Zahl sein. Später dazu mehr.
Die DLL Injection
Viele WarRock Hacks verwenden eine DLL Injection, um das Spiel zu beeinflussen. Eine DLL Injection macht die Speichermanipulation um einiges leichter, da man sozusagen in den Spielprozess rein programmieren kann. Der Sourcecode wird vom Compiler in Maschinensprache übersetzt und in der DLL gespeichert. Anschließend wird die DLL mit einem Injector oder einem Loader in den Spielprozess injiziert und dieser wird gezwungen die DLL auszuführen. Komischerweise werden fast alle NonMenü WarRock Hacks in DLLs verpackt, obwohl man das ganze auch einfach extern mit Writeprocessmemory o.ä. gestalten könnte, somit wäre eine Manipulation auch ohne DLL Injectior/Loader möglich.
Der C&P Code
Gut, (fast)jede DLL Injection baut auf einer bestimmten Funktion auf, der DllMain() Funktion. Diese Funktion wird immer aufgerufen, wenn die DLL geladen wird. Eventuell kennt ihr ja die main() Funktion aus C/C++ oder Java Programmen. Diese main() Funktion wird immer als erstes beim Programmstart ausgeführt und ähnlich verhält es sich mit der DllMain Funktion in DLL Dateien. Diese Funktion findet man in fast jedem WarRock Hack und so sieht sie meistens aus:
Code:
BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpvReserved)
{
if(dwReason == DLL_PROCESS_ATTACH)
{
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)HackThread, 0, 0, 0);
}
return TRUE;
}
BOOL das heißt einfach, dass die Funktion einen boolischen Wert zurück gibt. Ein boolischer Wert kann nur zwei Zustände annehmen: Wahr (true) oder Falsch (false).
Wie wir sehen wird in der vorletzten Zeile mit dem return Befehl einfach der Wert TRUE zurück gegeben. Was das WINAPI macht weiß ich selber garnicht so genau. Eventuell gibt es auf anderen Systemen solche DllMain() Funktionen nicht und es ist ein Hinweiß für den Compiler, ich weiß es ehrlich gesagt nicht.
So, nun kommt der Name der Funktion, DllMain(). Die Funktion muss einfach so heißen, damit sie beim Start ausgeführt wird, würden wie sie anders nennen, würde das ganze nicht funktionieren. Vlt. kann man die DllMain Funktion bei einigen Compiler umbenennen, im Normalfall bleibt man jedoch bei diesem Namen.
Nach dem Funktionsnamen werden 3 Parameter übergeben. Ähnliches kennt man aus der main() Funktion in C/C++ und Java, dort werden die Kommandozeilenargumente übergeben (int argc und char *argv[]). Die DllMain() Funktion bekommt also 3 Parameter. Der erste Parameter heißt hModule und ist vom Datentype HINSTANCE. Dieser Parameter beinhaltet das Handle der DLL, für den Hack nicht wichtig.
Der zweige Parameter heißt dwReason und ist vom Datentype DWORD. Dieser Parameter ist schon interessanter. Er sagt uns, wie die DLL geladen wurde. Was der dritte Parameter für eine Funktion hat weiß ich nicht so genau, das ist für den Hack aber auch nicht relevant.
So, das war soweit der sogenannte Funktionskopf, nun folgt in geschweiften Klammern der Funktionsrumpf, also das, was ausgeführt werden soll, wenn die Funktion aufgerufen wird.
Die Funktion beginnt mit einer If-Abfrage. Hier wird überprüft, ob der Parameter dwReason den Wert DLL_PROCESS_ATTACH besitzt. DLL_PROCESS_ATTACH ist eine Konstante, die den Wert 1 hat. Konstanten werden in C++ meistens komplett groß geschrieben, das dient einfach der Übersicht. Da diese Konstante für den Wert 1 steht könnte man die If-Abfrage auch einfach so verwirklichen:
Code:
if(dwReason == 1)
Einige Hacks verwenden hier auch die Switsh-Case Operation, finde ich eigendlich total schwachsinnig. Die If-Abfrage überprüft also einfach, ob die DLL von einem DLL Injector/Loader in einen Prozess injiziert wurde.
So, nun wird mit der CreateThread() Funktion ein neuer Thread erstellt, aber warum ?
Threading
Jedes Programm hat, wenn es gestartet wird, einen Mainthread, einen Hauptthread, dort beginnt das Programm. Zum Glück gibt es das sogenannte Multithreading, es erlaubt einem Programm mehrere Operationen gleichzeitig auszuführen. Wenn man die Operationen eines Programmes auf 10 Threads verteilt, wird das Programm fast 10 mal so schnell.
Also, wenn wir die DLL in einen Prozess injizieren, dann wird diese in einen Thread des Prozesses injiziert. Wenn wir durch unseren Hack den Verlauf dieses Threads stören würden, dann könnte es zu großen Großen Problemen kommen. Deshalb erstellen wir sobald die DLL Injiziert wurde einen neuen Thread, der unseren Opferprozess nicht weiter stört.
Zurück zum Hack
Zurück zum C&P Code. Die CreateThread() Funktion hat 6 Parameter. Ich weiß nicht genau, welche Parameter welche Funktion sie erfüllen. Einige dienen der Überwachung des erstellten Threads, andere übergeben Parameter an den Thread. In unserem Falle soll die HackThread Funktion in einem Thread gestartet werden. Dies wird im 3. Parameter festgelegt. Da CreateThread allerdings einen Pointer auf die auszuführende Funktion erwartet, müssen wir den übergebenen Wert noch in den richtigen Type casten, das macht man in C/C++ in dem man den Datentype, in den man casten möchte in Klammern davor schreibt, das macht also das (LPTHREAD_START_ROUTINE). So, das return True hatten wir ja bereits diskutiert. Ich denke man kann auch False zurückgeben. Bei einigen Compiler muss man auch keinen Rückgabewert definieren, andere wiederum verlangen ihn.
Gut, unsere Funktion mit dem Namen HackThread() wird also in eine Thread ausgeführt und interessiert den Prozess (WarRock) nicht die Bohne. Also betrachten wir mal die HackThread() Funktion:
Code:
void HackThread()
{
for(;; )
{
if(*ingame)
{
PlayerHacks();
}
if(*outgame)
{
ServerHacks();
}
}
Sleep( 200 ); // Prevent for overloading CPU!
}
Im Funktionsbauch sehen wir eine For-Schleife. Zu beachten ist, dass der Kopf der For-Schleife leer ist. Normalerweise sieht der Kopf einer For-Schleife in C++ so aus:
Code:
for(int i = 0;i<20;i++)
Lässt man alle diese Instruktionen und Anweisungen im Funktionskopf weg, so erhält man eine Endlosschleife, also eine Schleife, die unendlich durchläuft. Es wäre wohl schöner gewesen, eine While-True Schleife zu verwenden. Eventuell war der Programmierer ein Feind der While-Schleife, oder er war so ein Scriptkiddy, dass er um deren Existenz nicht wusste.
Eine While-True Schleife sieht wie folgt aus und macht genau das selbe:
Code:
while (true){
Anweisungen...
}
Sleep( 200 ); // Prevent for overloading CPU!
Die Sleep Funktion lässt das Programm einfach warten, in diesem Falle 200 Millisekunden, also 0,2 Sekunden. Das macht man, um die CPU in einer Endlosschleife zu entlasten.
Der englische Kommentar zeigt auch hier wieder, das der Code einfach kopiert wurde.
Die Includs, Addys und Pointer in C++
Um die Addys zu verstehen, braucht es ein Weilchen.
Dazu betrachten wir uns mal alle Sachen, die Global deklariert wurden:
Code:
#include <Windows.h> #include <stdio.h> #define Addr_Playerpointer 0xC62388 #define Addr_Serverpointer 0xB5D0F8 #define Ofs_Z 0x102D8 #define Ofs_NoFallDamage 0x103A4 DWORD *ingame = (DWORD*)Addr_Playerpointer; DWORD *outgame = (DWORD*)Addr_Serverpointer;
Man fügt dem Programm sogenannte Header-Dateien an, um mehr Funktionen zu erhalten. Die Windows Header-Datei (Windows.h) stellt , wie der Name schon sagt, viele Windowsfunktionen zur Verfügung, darunter die WindowsAPI Funktionen (z.B. CreateThread()).
Die stdio.h stellt uns ältere C-Funktionen zur Verfügung. Da diese Funktionen im Hack nicht gebraucht werden, hat der Programmierer dies wohl übersehen, ein erneutes Zeichen dafür, dass er wenig Ahnung von dem hat, was er da eigentlich macht.
Unter den Includes werden die Addys und Offsets per #define Makro definiert. Ich weiß nicht, warum alle die Addys immer so ins Programm integrieren. Man könnte sie auch einfach als Variablen oder Konstanten definieren. Eventuell hat die #define Methode bestimmte Vorteile denen ich mir nicht bewusst bin, vlt. hat der Programmierer den Code auch einfach kopiert und sich keinen Kopf drum gemacht. Wir stellen uns also vor es wären Variablen und sie wären anstatt des #defind durch:
Code:
DWORD Addr_Playerpointer = 0xc62388; DWORD Addr_Serverpointer = 0xb5d0f;
Was machen diese Addys überhaupt ?
Diese Adressen sind Speicheradressen im Speicher. Erinnert ihr euch noch an den Anfrang ? Jede Speicheradresse hat einen Wert. In der Adresse 0xc62388 befindet sich also im WarRock Prozess und beinhaltet einen Wert. Nun wird es ein wenig kompliziert.
Der Wert dieser Adresse ist ebenfalls eine Adresse. Man nennt so was Pointer(Zeiger). Also der PlayerPointer befindet sich an der Adresse 0xc62388 im Speicher und hat einen Wert, dieser Wert ist selbst eine Adresse. An dieser Adresse befindet sich die Player-Struktur oder das Playerobjekt eurer WarRock Spielfigur. Gleiches gilt für die Adresse des Serverpointers.
kleiner Exkurs C++ Pointerarithmetik
In C++ werden Pointer oft verwendet und es ist wichtig den Prozess zu verstehen, ein einfaches Beispiel:
Code:
int i=1337; int *IchZeigeAuf_i = &i;
Code:
*IchZeigeAuf_i = 6;
Der Programmierer des Codes macht dies ebenfalls:
Code:
DWORD *ingame = (DWORD*)Addr_Playerpointer; DWORD *outgame = (DWORD*)Addr_Serverpointer;
Um nun an die Werte zu kommen, die in den Adressen gespeichert sind, verwendet man die Pointer wie folgt:
Code:
DWORD InhaltDesPlayerPointers = *ingame; DWORD InhaltDesServerPointers = *outgame;
Back to the Hack
Gut, dann schauen wir mal weiter den Sourcecode an. Wir sind immer noch in der HackThread() Funktion, bzw. in deren For-Schleife. Wir sehen, dass in einer If-Abfrage der Inhalt der Adressen abgefragt wird, auf die die jeweiligen ingame und outgame Pointer zeigen.
Code:
if(*ingame)
Code:
if(*outgame)
Hinzu kommt, dass jeder Wert, der nicht 0 ist immer für wahr steht. Diese Eigenschaft hat C++ geerbt. Das bedeutet also, wenn einer der beiden Pointer einen Wert zurückgibt, der nicht 0 ist, so wird der Bauch/Rumpf der If-Abfrage ausgeführt.
Ob der ingame Pointer immer dann auf eine Adresse zeigt, deren Wert 0 ist, wenn man gerade nicht im Spiel ist und der outgame Pointer immer dann auf eine Adresse zeigt, deren Wert 0 ist, wenn man im Spiel ist, sei mal dahin gestellt. Ich habe es auch nicht getestet, es scheint aber sehr fehleranfällig.
Nun gut, diese beiden If-Abfragen haben wir hinter uns gelassen. Wenden wir uns der ersten Funktion zu, die ausgeführt wird, wenn der ingame Pointer einen Wert zurückgibt, der nicht 0 ist:
Code:
void PlayerHacks()
{
DWORD dwPlayerPtr = *(DWORD*)Addr_Playerpointer;
if(dwPlayerPtr != 0)
{
//Super Jump
{
if(GetAsyncKeyState(VK_CONTROL) &1)
{
*(float*)(dwPlayerPtr + Ofs_Z) = 2000;
}
}
//No Fall Damage
{
*(float*)(dwPlayerPtr + Ofs_NoFallDamage) = -20000;
}
}
}
Code:
*(DWORD*)Addr_Playerpointer;
Ist euch was aufgefallen ? Das ganze ist doppeltgemoppelt. Der Programmierer hat doch bereits den *ingame Pointer erstellt, der auf den Playerpointer zeigt, es wäre wohl sinnvoller den erwähnten Code so zu schreiben:
Code:
DWORD dwPlayerPtr = *ingame;
Okay nun geht es weiter. Der Programmierer überprüft, ob der Inhalt der dwPlayerPtr Variablen ungleich 0 ist.
Code:
if(dwPlayerPtr != 0)
Code:
if(*ingame)
Als nächstes schauen wir uns mal folgenden Codeabschnitt an:
Code:
if(GetAsyncKeyState(VK_CONTROL) &1)
Das &1 ist ein wenig komplizierter, ich bin mir auch nicht so ganz sicher. Es legt wohl fest, dass noch eine andere Taste zusätzlich gedrückt werden muss. Dazu sollte man sich mit dem Binärsystem und dessen Gesetzen auskennen:
(Und-Gesetz)
Code:
0 und 0 = 0 1 und 0 = 0 0 und 1 = 0 1 und 1 = 1
Code:
if(GetAsyncKeyState(VK_UP))
Player Hacks
Der Superjump
Wie funktioniert der Superjump eigentlich ? Das ist ganz einfach. Irgendwo im Speicher sind die Koordinaten der Spielfigur gespeichert. Diese Koordinaten bestehen mindestens aus 3 Werten:
X,Y und Z. Genau wie in einem 3D Koordinatensystem (Vektorrechnung FTW!).
Nun jede Koordinate steht für eine Dimension. Die Z Koordinate gibt an, auf welcher Höhe sich die Spielfigur befindet. Da der Gameserver das nicht überprüft können wir diese Koordinate nach belieben verändern und somit auch die Höhe der Spielfigur verändern, denn genau das macht der Superjump. Die Spielfigur springt nicht, sondern ihre Position in der Höhe wird einfach verändert.
Gut, folgende Pointerverkettung soll den Superjump auslösen:
Code:
*(float*)(dwPlayerPtr + Ofs_Z) = 2000;
Ihr wisse ja noch, dass in dwPlayerPtr die Adresse ist, an der die Player-Struktur/Objekt anfängt. Stellen wir uns mal vor die Adresse wäre 0x01.
Dann könnte der Speicher der Player-Struktur ca. so aussehen:
Code:
0x01 0 0x02 0 0x03 K 0x04 R 0x05 U 0x06 S 0x07 T 0x08 Y 0x09 0 0x0A 1 0x0B 3 0x0C 3 0x0D 7 0x0E 0 0x0F 0
Code:
0x01 + 0x02 = 0x03
Gut:
Code:
dwPlayerPtr + Ofs_Z
Float ist ein Datentype, genauso wie Int oder DWORD. Float verwendet ebenfalls 4 Bytes, desshalb kann man diese Datentypen auch so gut hin und her casten. Float kann, im Gegensatz zu den anderen beiden Datentypen, Fließkommazahlen darstellen. Die Koordinaten werden für gewöhnlich immer in float Variablen abgelegt, sie ermöglichen eine genaue Lokalisierung der Spielobjekte im Spiel mit Nachkommastellen.
Der Programmabschnitt
Code:
*(float*)(dwPlayerPtr + Ofs_Z) = 2000;
NoFallDamage
Kommen wir zum NoFallDamage. Es läuft nach dem gleichen Prinzip ab. Irgendwo im Speicher steht, wie viel Schaden die Spielfigur bekommt. Wenn sie von einem hohen Punkt auf den Boden springt. Um an diese Stelle im Speicher zu gelangen, muss das Offset 0x103A4 zur Startadresse der Player-Struktur/Objekt addiert werden.
Mit:
Code:
*(float*)(dwPlayerPtr + Ofs_NoFallDamage) = -20000;
Server Hacks
So, mit den Playerhacks sind wir fertig, nun folgen die Serverhacks. Der Name Serverhack ist auch hier irreführend, da wir keinen Server hacken, geschweige denn etwas am Server ändern. Alle Manipulationen laufen auf unserem Computer ab. Der Programmierer, dessen Code wir einfach kopiert haben ruft in der HackThread() Funktion durch eine If-Abfrage eine weitere Funktion auf:
Code:
if(*outgame)
{
ServerHacks();
}
Code:
void ServerHacks()
{
DWORD dwSrvrPtr = *(DWORD*)Addr_Serverpointer;
if(dwSrvrPtr != 0)
{
//Slot Stuff
//5 Slot
{
*(long*)(dwSrvrPtr + Ofs_5Slot) = 1;
}
//6 Slot
{
*(long*)(dwSrvrPtr + Ofs_6Slot) = 1;
}
//7 Slot
{
*(long*)(dwSrvrPtr + Ofs_7Slot) = 1;
}
//8 Slot
{
*(long*)(dwSrvrPtr + Ofs_8Slot) = 1;
}
}
}
Auch hier wird eine DWORD Variable mit dem Namen dwSrvrPtr erstellt, die die Startadresse der Server-Struktur beinhaltet. Als erstes wird diese wieder auf „ungleich null“ überprüft, ebenfalls eine überflüssige Abfrage. Auch hier hätte man wieder den globalen *outgame Pointer verwenden können, dem Programmierer war dies wohl nicht bewusst. Diese Abfrage soll sicherstellen, dass wir uns momentan nicht im Spiel befinden. Unterhalb der If-Abfrage sehen wir mehrere Blöcke, die mit einer geschweiften Klammer eingeleitet und beendet werden, z.B.:
Code:
//5 Slot
{
*(long*)(dwSrvrPtr + Ofs_5Slot) = 1;
}
Ich denke die Funktionsweise der anderen Slots sollte klar sein.
Der Teleporter
Der Programmierer des Hacks stellt zusätzlich noch weitere Funktionen zur Verfügung, unter anderem eine Teleport Funktion.
Die Teleport Funktion ähnelt dem Superjump. Wir verändern einfach die Koordinaten der Spielfigur und setzen sie so an einen anderen Punk im Spiel. Dazu benötigen wir die Offsets der jeweiligen X und Y Koordinate, die der Z Koordinate haben wir ja bereits vom Superjump her. Beim Teleportieren erstellt man zwei Funktionen: eine, um einen bestimmten Punkt zu speichern und eine zweite Funktion, um sich später wieder zu diesem Punkt zu teleportieren. Um dies zu verwirklichen, verwendet der Programmierer folgenden Code:
Code:
//Teleport
{
if(GetAsyncKeyState(VK_F2))
{
CoordX = *(float*)(dwPlayerPtr+Ofs_X);
CoordY = *(float*)(dwPlayerPtr+Ofs_Y);
CoordZ = *(float*)(dwPlayerPtr+Ofs_Z);
}
if (GetAsyncKeyState(VK_F3))
{
*(float*)(dwPlayerPtr + Ofs_X) = CoordX;
*(float*)(dwPlayerPtr + Ofs_Y) = CoordY;
*(float*)(dwPlayerPtr + Ofs_Z) = CoordZ;
}
}
Anschließend folgt eine If-Abfrage, die überprüft, ob die F2 Taste gedrückt wird. Wenn das der Fall ist, dann werden die Koordinaten der Spielfigur aus der Player-Struktur/Objekt ausgelesen, dazu werden wieder die jeweiligen Offsets addiert. Diese Koordinaten werden in den Variablen CoordX,CoordY und CoordZ gespeichert. Diese Variablen müssen noch global als float deklariert werden
Code:
float CoordX,CoordY,CoordZ;
Code:
#define Ofs_X 0x00102D4 #define Ofs_Y 0x00102DC #define Ofs_Z 0x00102D8 //gibts ja bereis von Superjump
Code:
DWORD Ofs_X = 0x00102D4; DWORD Ofs_Y = 0x00102DC; DWORD Ofs_Z = 0x00102D8; //gibts ja bereis von Superjump
Code:
if (GetAsyncKeyState(VK_F3))
Ende
Wie ihr gesehen habt, war der programmierte Code nicht gerade sauber und ordentlich. Jeder sollte sich ab und zu mal einen kleinen Teil bei anderen abschauen, um ein Verständnis zu entwickeln. Jemand anderem die komplette Arbeit zu stehlen und dann zu behaupten es wäre die eigene ist jedoch verantwortungslos und vor allem beschämend, wenn man mit dem Diebesgut nicht umgehen kann.
Mit freundlichen Grüßen Krusty.






