(Das ist unabhängig der vorherigen Beiträge zu lesen)
Du _musst_ ASM können. Es kommt mir vor, du würdest nicht wissen was du da versuchst oder wie das ursprünglich in C/C++ aussah. Ich habe für dich ein kleines Beispiel erstellt:
Code:
#include <windows.h>
struct UNIT_PLAYER {
char name[20];
int currentHealth;
int maxHealth;
};
UNIT_PLAYER *PlayerList = (UNIT_PLAYER*)malloc(sizeof(UNIT_PLAYER) * 2); // 2 units
bool main()
{
memset(PlayerList, 0, sizeof(PlayerList));
// dummy werte für unit 0
strcpy(PlayerList[0].name, "foo");
PlayerList[0].currentHealth = 100;
PlayerList[0].maxHealth = 100;
// dummy werte für unit 1
strcpy(PlayerList[1].name, "foo 2");
PlayerList[1].currentHealth = 200;
PlayerList[1].currentHealth = 200;
return false;
}
In dem Fall bildet die struct eine einzige Unit. Doch wie greifen Spiele auf diese Units zu? Irgendwie muss das Spiel alle Units erreichen. Oft wird dafür eine Linked List verwendet und der Pointer auf die erste Unit (ob Spieler oder Monster ist egal) wird global in einer Variable gespeichert. Der Offset darauf ist konstant (egal ob es später vielleicht ein Pointer auf ein Pointer wird ...).
Kompilieren wir uns den oberen Code und schauen ihn in VS05 in der Disassembly Sicht an (Wärend dem debuggen Rechte Maustaste drücken -> Go to Disassembly).
Das ganze sieht bei mir so aus:
Code:
bool main()
{
(...)
memset(PlayerList, 0, sizeof(PlayerList));
004113BE push 4
004113C0 push 0
004113C2 mov eax,dword ptr [PlayerList (418178h)]
004113C7 push eax
004113C8 call @ILT+120(_memset) (41107Dh)
004113CD add esp,0Ch
// dummy werte für unit 0
strcpy(PlayerList[0].name, "foo");
004113D0 push offset string "foo" (416744h)
004113D5 mov eax,dword ptr [PlayerList (418178h)]
004113DA push eax
004113DB call @ILT+170(_strcpy) (4110AFh)
004113E0 add esp,8
PlayerList[0].currentHealth = 100;
004113E3 mov eax,dword ptr [PlayerList (418178h)]
004113E8 mov dword ptr [eax+14h],64h
PlayerList[0].maxHealth = 100;
004113EF mov eax,dword ptr [PlayerList (418178h)]
004113F4 mov dword ptr [eax+18h],64h
// dummy werte für unit 1
strcpy(PlayerList[1].name, "foo 2");
004113FB push offset string "foo 2" (41673Ch)
00411400 mov eax,dword ptr [PlayerList (418178h)]
00411405 add eax,1Ch
00411408 push eax
00411409 call @ILT+170(_strcpy) (4110AFh)
0041140E add esp,8
PlayerList[1].currentHealth = 200;
00411411 mov eax,dword ptr [PlayerList (418178h)]
00411416 mov dword ptr [eax+30h],0C8h
PlayerList[1].currentHealth = 200;
0041141D mov eax,dword ptr [PlayerList (418178h)]
00411422 mov dword ptr [eax+30h],0C8h
return false;
00411429 xor al,al
}
Du siehst erstmal den Befehl in C++ und dann den dafür erzeugten ASM-Code. Schau dir besonders die Zuweisungen an. Eigentlich müssten Dinge wie "mov dword ptr [eax+14h], 64h" dir recht bekannt vorkommen (64h wird da wohl ein Register oder lokale Variable sein). Schau dir besonders die Unterschiede zwischen den beiden Zuweisungen an:
Code:
// Für Unit 0
004113EF mov eax,dword ptr [PlayerList (418178h)]
004113F4 mov dword ptr [eax+18h],64h
Code:
// Für Unit 1
0041141D mov eax,dword ptr [PlayerList (418178h)]
00411422 mov dword ptr [eax+30h],0C8h
eax hält in beiden Fällen einen Pointer auf den _anfang_ des Arrays. Im ersten Fall addiert er bei der Zuweisung zu eax 18h, das wäre dezimal 24. Wenn wir uns die struct anschaun, belegt der Name 20 und wenn wir den dazuaddieren, wären wir bei currentHealth. Ein int hat auf meiner Maschine eine Länge von 4 byte, also 24.
Unit 1 liegt im Speicher direkt nach Unit 0 und Unit 0 hat insgesamt eine Länge von 28 (Name=20, maxHealth=4, currentHealth=4). Um auf den Anfang der Unit 1 zuzugreifen (dem Namen) müssten wir immer 1C addieren (was du bei der Zuweisung des Namens oben sehen kannst).
Bevor die Zuweisung + Adressierung kommt, wird eax der Pointer auf den Anfang der ersten Unit zugewiesen. Diese steht in dem Fall _immer_ (!) an der Position 418178h, den ich für einen "Hack" nur auslesen müsste ohne irgendwas am Code zu ändern. Es ändert trotzdem nichts daran, dass ich Wissen muss wie lang die struct ist, sonst komme ich nicht an Unit 1.
Bei Spielen hast du es oft, das es für jede Aktion eine Funktion gibt z. B. SetCurrentHealth und an die wird direkt ein Pointer als Parameter übergeben. Der Funktionsprototyp würde so aussehen: SetCurrentHealth(UNIT_PLAYER *p, int newHealth).
Vor dem Aufruf würde das bei meinem Code vielleicht so aussehen:
Code:
push edx // newHealth
mov eax,dword ptr [PlayerList (418178h)]
add eax,1Ch
push eax // Unit 1 Ptr
Um also an den Pointer für Unit 0 zu kommen (um 1 zu erreichen), kann es sicherlich nicht schaden den Code anzuschauen der deine Funktion aufruft. Bei dem Beispiel würde es ereichen wenn ich die Operation sehe "mov dword ptr [eax+30h],0C8h" den Inhalt von eax zu nehmen und die Datei im Speicher nach diesem Wert zu durchsuchen. Der Suchvorgang wäre eine einmalige Aktion, da ich bei einem Fund einfach von 418178h lesen brauche.
Wenn du das erstmal verstanden hast, wirst du es auch ohne Probleme und mit ein bisschen Verwendung deines Kopfes sicherlich auch auf ähnliche Varianten (z. B. Linked Lists) anwenden können.
Für den Fall das du nicht weisst wie der ASM-Code bei der Verwendung von Linked Lists aussehen könnte, würde ich dir empfehlen selbst ein Testszenario zu erstellen und dir dann deinen eigenen ASM-Code anzuschaun den der Compiler erzeugt.
Manchmal ist diese Methode nicht so einfach anwendbar (__declspec(thread)). Hier wäre mehr Aufwand zu betreiben.
Viele überschreiben aber paar byte und speichern den Inhalt von eax an eine "konstente Adresse" (jmp-hooks). Das ist aber nichts zwangsläufig eine gute Lösung. Siehe SetCurrentHealth-Funktion und die Parameter, die für jede Unit geeignet wäre ... auf der anderen Seite bei Double-Linked-Lists annehmbar ;)
Dafür gibt es aber genug Tutorials, die du mit google finden kannst.