English translation:
Vorwort
Im Folgenden geht es darum, am Beispiel von Metin2 Funktionen im Speicher zu reversen(ich zeige das am Beispiel von SG). Was bringt einem das? Nunja, man kann damit zum Beispiel Bots/Hacks programmieren.
Eingeleitet wird das ganze durch ein paar Basics, welchen man sich bewusst sein sollte, bevor man sich Assemblercode anschaut.
Ich setze hier einige Dinge vorraus. Ums kurz zu halten sollte man schon einmal irgendetwas programmiert haben und sich evtl mit CheatEngine auseinander gesetzte haben. Ist natürlich aber nicht zwingend notwendig. Verbesserungsvorschläge oder Ergänzungen, welche zu einem besseren Verständnis verhelfen, sind gerne gesehen. Ich werde als Beispiel die SendUseItemPacketFunktion nehmen. Wenn mehr gewünscht ist werde ich mit anderen Funktionen weitere Beispiele bringen.(z.B. NetzwerkStream Reader für den Client mit inline Hooking etc..)
1.) Umgebung einrichten
Zuerst einmal müssen wir die nötigen Programme herunterladen und diese konfigurieren.
Benötigte Programme:
CheatEngine
Visual Studio 2015
Irgendein Metin2Client
Zu CheatEngine:
Um Abstürze des Spiels zu vermeiden, hat es sich bei mir bewährt den VEH Debugger zu aktivieren.
2.) Basics
[How To]Eine Funktion reversen(SendItemUse)
Da es bereits Tutorials über PickUp oder ähnliche Funktionen gibt, dachte ich mir ich nehme die SendItemUsePacket, welche es möglich macht, Items automatisch auszurüsten bzw. zu benutzen.
Der Client kommuniziert mit dem Server über Pakete und die SendItemUsePaket teilt dem Server mit, welches Item benutzt werden soll. Diese sieht im Sourcecode folgendermaßen aus:
[Only registered and activated users can see links. Click Here To Register...]
In dem Bild/Source sind nun Zeichenketten/Strings zu sehen.
Diese wären:
Nach diesen Strings können wir den Speicher des Prozesses nun untersuchen.
Zuerst einmal müssen jedoch folgende Schritte durchgeführt werden:
1.) Starte Metin2 und CheatEngine
2.) MemoryViewer in CheatEngine anklicken
3.) Tools -> Dissect Code( oder STRG+J) und Start drücken, um nach Strings zu suchen
4.) Wenn die Stringsuche fertig ist, in der Menüleiste auf View->Referenced Strings( oder STRG-ALT+R) und dann mit STRG+F nach String suchen: "SendItemUsePacket Error"
Man könnte auch nach den anderen oben genannten Strings suchen und würde im Endeffekt auch auf die selbe Lösung kommen.
5.) Klickt man auf den String erscheint rechts eine Adresse. Diese mit Doppelklick anklicken.
[Only registered and activated users can see links. Click Here To Register...]
Nun zum eigentlichen Part.
Die Funktion finden
Die Stelle an welche wir nun gesprungen sind zeigt:
(Natürlich wird die Adresse sehr wahrscheinlich anders bei euch aussehen!)
012C50AC ist eine Adresse im Speicher, an der ein String liegt. Um zu sehen, ob dies auch wirklich stimmt, kann man unten im MemoryViewer bei der Hexansicht einen Rechtsklick machen und mit "Got to Adress" zu dieser Adresse springen.
[Only registered and activated users can see links. Click Here To Register...]
Es wird also die Adresse, an der die Errormeldung steht, auf den Stack gepusht.
Legen wir nun einen Breakpoint(Um einen Breakpoint zu setzen Rechtsklick auf die Zeile und Toggle Breakpoint auswählen) auf diese Zeile des push Befehls und benutzen ein Item in Metin2, werden wir feststellen, dass dieser nicht auslöst. Warum? Ganz einfach: Weil das Senden des Paketes erfolgreich war und dieser Codeabschnitt deswegen übersprungen wurde.Um ihn auszulösen müssen wir den Breakpoint auf die Zeile dadrüber legen:
[Only registered and activated users can see links. Click Here To Register...]
Das Spiel ist nun eingefrohren und wir haben die Möglichkeit Schritt für Schritt durch den Code zu steppen(F8), um zu sehen wie sich die Funktion verhält und was für Werte auf dem Stack liegen, bzw in den Registern. Das ist an dieser Stelle jedoch erstmal nicht von Relevanz. Um das Spiel weiterlaufen zu lassen muss F9 gedrückt werden.
Wir haben also die Funktion fürs Erste gefunden.
Wie im Sourcecode zu sehen ist(Bild1), benötigt die Funktion einen Parameter. Und zwar die Position des Items.
[Only registered and activated users can see links. Click Here To Register...]
Die Datenstruktur des Typs TItemPos sieht wie folgt aus:
Um die Funktion also benutzen zu können, müssen wir vorher auch angeben, welches Item/bzw Zelle wir benutzen möchten. Dazu muss man wissen wie die Parameterübergabe geschieht:
Bevor eine Funktion aufgerufen wird, werden die Parameter die an die Funktion übergeben werden sollen, auf den Stack gepusht. Wie kriegen wir nun raus, was wir später auf den Stack pushen müssen, um die Funktion zu benutzen? Wir setzen zunächst einen Breakpoint auf den Anfang der Funktion. Diese beginnt in diesem Beispiel bei 0099A740.
Rüsten wir uns nun mit einem Item aus, so freezt das Spiel wieder und wir sehen die Registerwerte. Uns interessiert hierbei nun aber der Stack. Dieser befindet sich im MemoryViewer unten rechts. Um diesen übersichtlicher darzustellen kann man per Rechtsklick in dem Stackfeld "Full Stack" auswählen.
Das sollte nun folgendermaßen aussehen:
[Only registered and activated users can see links. Click Here To Register...]
Erklärung zum Stackview
Was ihr dort seht ist nun der aktuelle Stack, bzw die Stelle an dem der Stackpointer(ESP Register) momentan steht.
Address(Spalte 1) ist die Addresse, an der ein Wert(Spalte 2) steht. Dieser Wert kann nun ein Pointer auf eine Datenstruktur sein, oder ein 1-4 Byte Wert.
Ende Erklärung Stackv
Der erste Wert ist eine Addresse, und zwar nicht irgendeine Addresse sondern die Returnadresse, welche uns nach dem Durchlauf der Funktion wieder zum eigentlichen Programmablauf bringt.
(Um zu sehen von wo aus diese Funktion also gecalled wird, könnten wir dort hinspringen und evtl weitere interessante Dinge herrausfinden.)
Vorerst noch: Das Item welches ich angezogen habe liegt im Inventar I oben links.
Schauen wir 4Bytes weiter, also die nächste Zeile, sehen wir, dass dort der ein Wert gepusht wurde(Siehe Bild oben).
Das sieht doch schonmal interessant aus.
Gucken wir was passiert, wenn ich das Item in den 2. Platz von links im Inventar platziere, und den Breakpoint erneut auslösen.
Der gepushte Wert, der eben bei 00000001 lag, liegt nun bei 00000101. Lege ich das Item daneben und ziehe es an wird es 00000201 sein, lege ich es in den 5. Slot und benutze es, wird es 00000401 sein... und so weiter. Ihr könnt ja ein wenig testen.
Auffällig ist auf jeden Fall, dass das Byte 01 am Ende immer gleich zu sein scheint und das andere Byte(bzw WORD) die Zelle im Inventar angibt, wobei bei 0 angefangen wird zu zählen(Zelle 1 = Wert 0, Zelle 2 = Wert 1 usw..).
Blicken wir im Tutorial nach oben steht dort die Struktur des Parameters. window_type vom Typ BYTE und cell vom Typ WORD. Word ist ein Datentyp mit 2Bytes.
Alles was wir also vor dem Funktionsaufruf machen müssen, ist die Datenstruktur auf den Stack zu pushen.
Wollen wir also das Item des Slots 10 benutzen, so muss der Wert wie folgt aussehen:
Vom Prinzip sieht unser Code nun folgendermaßen aus:
Das ist jedoch leider noch nicht alles.
Der Klassenpointer
Die Funktion SendItemUsePacket(TItemPos pos) befindet sich in einer Klasse. Und zwar der CPythonNetworkStream class. Der Pointer(Zeiger) auf diese Klasse muss in diesem Fall ebenfalls bekannt sein. Wenn Funktionen aus anderen Klassen aufgerufen werden, wird der Pointer zu dieser Klasse mit angegeben. Dazu ist in der Regel das ECX Register.
Kurz gesagt: Vor dem Aufruf der Funktion SendUseItemPacket wird der Klasspointer in ECX gemoved. Dies müssen wir ebenfalls tun.(Das ist fast immer so!)
Um zu wissen wie der Pointer lautet müssen wir nur wieder einen Breakpoint auf den Anfang! der Funktion legen und ein Item benutzen. Der Wert, welcher sich nun im ECX Register befindet, ist unser gesuchter Klassenpointer. Dies ist jedoch kein statischer Pointer, heißt er wird nur für diesen Prozess funktionieren. Startet ihr das Spiel neu, ist der Pointer ebenfalls neu und euer Code wird nächstes mal nicht mehr hinhauen.
Wie findet man also den statischen Pointer?
Ganz einfach: Man reversed das Ganze ein wenig mehr.Wir wollen also nun herrausfinden, wo der Wert des ECX Registers herkommt. Dazu müssen wir allerdings vor den Aufruf der Funktion. Ich hatte vorher ja schon davon gesprochen, dass sich auf dem Stack, wenn der Breakpoint auslöst, die Rücksprungadresse befindet. Dort machen wir einfach einen Doppelklick drauf und schon landen wir bei dem Funktionsaufruf(call).
[Only registered and activated users can see links. Click Here To Register...]
Scrollen wir nun etwas hoch sieht man in meinem Beispiel folgendes:
Durch Breakpoints setzen(über dem call) kann man nun herrausfinden, wo das ECX Register geladen wird, wenn es nicht vorher schon offensichtlich ist.
Es kann vorkommen, dass vor dem Funktionsaufruf direkt folgendes steht:
Dann habt ihr den statischen Pointer direkt gefunden.
Da das hier nicht offensichtlich ist, müssen wir ein wenig rumprobieren. Wir wissen also das ECX 06CA9ED0 sein muss, also setzen wir Schritt für Schritt Breakpoints(über dem eigentlichen Funktionscall) und warten bis unsere Addresse in den Registern auftaucht.
Ich habe mal auf die Addresse 0098C49B (siehe Bild oben) einen Breakpoint gesetzt und gemerkt, dass das EAX Register nach diesem call den gesuchten Wert hält.(EAX ist das return Register, wo nach dem Funktionsaufruf (meistens) das Ergebnis einer Funktion landet)
[Only registered and activated users can see links. Click Here To Register...]
Nachfolgender Code(damit ist der Code nach dem call auf 0090BD70 gemeint) schiebt den EAX Wert dann nur noch in das ECX Register. Der call auf 0090BD70 scheint uns also den gesuchten Klassenpointer zu liefern.
[Only registered and activated users can see links. Click Here To Register...]
Schauen wir uns diese nun genauer an (Rechtsklick auf call 0090BD70 und Follow).
[Only registered and activated users can see links. Click Here To Register...]
Ahh! Perfekt. Wir sehen einen mov ins EAX Register. Und zwar mov eax, [01482190]. 01482190 ist hierbei eine Adresse im Speicher, an welcher unsere gesuchte Adresse steht.
Sucht man nach der Adresse unten wieder im HexViewer, so findet man an der Stelle 06CA9ED0(bzw D09ECA06-> Im Speicher steht es verkehrt herrum).
4.) Programmierung
Ich werde dazu demnächst mehr schreiben.
Programmcode dazu:
Bei Fragen Skype: marcel.metin2
Vorwort
Im Folgenden geht es darum, am Beispiel von Metin2 Funktionen im Speicher zu reversen(ich zeige das am Beispiel von SG). Was bringt einem das? Nunja, man kann damit zum Beispiel Bots/Hacks programmieren.
Eingeleitet wird das ganze durch ein paar Basics, welchen man sich bewusst sein sollte, bevor man sich Assemblercode anschaut.
Ich setze hier einige Dinge vorraus. Ums kurz zu halten sollte man schon einmal irgendetwas programmiert haben und sich evtl mit CheatEngine auseinander gesetzte haben. Ist natürlich aber nicht zwingend notwendig. Verbesserungsvorschläge oder Ergänzungen, welche zu einem besseren Verständnis verhelfen, sind gerne gesehen. Ich werde als Beispiel die SendUseItemPacketFunktion nehmen. Wenn mehr gewünscht ist werde ich mit anderen Funktionen weitere Beispiele bringen.(z.B. NetzwerkStream Reader für den Client mit inline Hooking etc..)
1.) Umgebung einrichten
Zuerst einmal müssen wir die nötigen Programme herunterladen und diese konfigurieren.
Benötigte Programme:
CheatEngine
Visual Studio 2015
Irgendein Metin2Client
Zu CheatEngine:
Um Abstürze des Spiels zu vermeiden, hat es sich bei mir bewährt den VEH Debugger zu aktivieren.
2.) Basics
[How To]Eine Funktion reversen(SendItemUse)
Da es bereits Tutorials über PickUp oder ähnliche Funktionen gibt, dachte ich mir ich nehme die SendItemUsePacket, welche es möglich macht, Items automatisch auszurüsten bzw. zu benutzen.
Der Client kommuniziert mit dem Server über Pakete und die SendItemUsePaket teilt dem Server mit, welches Item benutzt werden soll. Diese sieht im Sourcecode folgendermaßen aus:
[Only registered and activated users can see links. Click Here To Register...]
In dem Bild/Source sind nun Zeichenketten/Strings zu sehen.
Diese wären:
PHP Code:
BINARY_AppendNotifyMessage
CANNOT_EQUIP_EXCHANGE
BINARY_AppendNotifyMessage
CANNOT_EQUIP_SHOP
SendItemUsePacket Error
Zuerst einmal müssen jedoch folgende Schritte durchgeführt werden:
1.) Starte Metin2 und CheatEngine
2.) MemoryViewer in CheatEngine anklicken
3.) Tools -> Dissect Code( oder STRG+J) und Start drücken, um nach Strings zu suchen
4.) Wenn die Stringsuche fertig ist, in der Menüleiste auf View->Referenced Strings( oder STRG-ALT+R) und dann mit STRG+F nach String suchen: "SendItemUsePacket Error"
Man könnte auch nach den anderen oben genannten Strings suchen und würde im Endeffekt auch auf die selbe Lösung kommen.
5.) Klickt man auf den String erscheint rechts eine Adresse. Diese mit Doppelklick anklicken.
[Only registered and activated users can see links. Click Here To Register...]
Nun zum eigentlichen Part.
Die Funktion finden
Die Stelle an welche wir nun gesprungen sind zeigt:
PHP Code:
"push 012C50AC" Comment: SendItemUsePacket Error
012C50AC ist eine Adresse im Speicher, an der ein String liegt. Um zu sehen, ob dies auch wirklich stimmt, kann man unten im MemoryViewer bei der Hexansicht einen Rechtsklick machen und mit "Got to Adress" zu dieser Adresse springen.
[Only registered and activated users can see links. Click Here To Register...]
Es wird also die Adresse, an der die Errormeldung steht, auf den Stack gepusht.
Legen wir nun einen Breakpoint(Um einen Breakpoint zu setzen Rechtsklick auf die Zeile und Toggle Breakpoint auswählen) auf diese Zeile des push Befehls und benutzen ein Item in Metin2, werden wir feststellen, dass dieser nicht auslöst. Warum? Ganz einfach: Weil das Senden des Paketes erfolgreich war und dieser Codeabschnitt deswegen übersprungen wurde.Um ihn auszulösen müssen wir den Breakpoint auf die Zeile dadrüber legen:
[Only registered and activated users can see links. Click Here To Register...]
Das Spiel ist nun eingefrohren und wir haben die Möglichkeit Schritt für Schritt durch den Code zu steppen(F8), um zu sehen wie sich die Funktion verhält und was für Werte auf dem Stack liegen, bzw in den Registern. Das ist an dieser Stelle jedoch erstmal nicht von Relevanz. Um das Spiel weiterlaufen zu lassen muss F9 gedrückt werden.
Wir haben also die Funktion fürs Erste gefunden.
Wie im Sourcecode zu sehen ist(Bild1), benötigt die Funktion einen Parameter. Und zwar die Position des Items.
[Only registered and activated users can see links. Click Here To Register...]
Die Datenstruktur des Typs TItemPos sieht wie folgt aus:
PHP Code:
struct TItemPos{
BYTE window_type;
WORD cell;
};
Bevor eine Funktion aufgerufen wird, werden die Parameter die an die Funktion übergeben werden sollen, auf den Stack gepusht. Wie kriegen wir nun raus, was wir später auf den Stack pushen müssen, um die Funktion zu benutzen? Wir setzen zunächst einen Breakpoint auf den Anfang der Funktion. Diese beginnt in diesem Beispiel bei 0099A740.
Rüsten wir uns nun mit einem Item aus, so freezt das Spiel wieder und wir sehen die Registerwerte. Uns interessiert hierbei nun aber der Stack. Dieser befindet sich im MemoryViewer unten rechts. Um diesen übersichtlicher darzustellen kann man per Rechtsklick in dem Stackfeld "Full Stack" auswählen.
Das sollte nun folgendermaßen aussehen:
[Only registered and activated users can see links. Click Here To Register...]
Erklärung zum Stackview
Was ihr dort seht ist nun der aktuelle Stack, bzw die Stelle an dem der Stackpointer(ESP Register) momentan steht.
Address(Spalte 1) ist die Addresse, an der ein Wert(Spalte 2) steht. Dieser Wert kann nun ein Pointer auf eine Datenstruktur sein, oder ein 1-4 Byte Wert.
Ende Erklärung Stackv
Der erste Wert ist eine Addresse, und zwar nicht irgendeine Addresse sondern die Returnadresse, welche uns nach dem Durchlauf der Funktion wieder zum eigentlichen Programmablauf bringt.
(Um zu sehen von wo aus diese Funktion also gecalled wird, könnten wir dort hinspringen und evtl weitere interessante Dinge herrausfinden.)
Vorerst noch: Das Item welches ich angezogen habe liegt im Inventar I oben links.
Schauen wir 4Bytes weiter, also die nächste Zeile, sehen wir, dass dort der ein Wert gepusht wurde(Siehe Bild oben).
PHP Code:
00000001
Gucken wir was passiert, wenn ich das Item in den 2. Platz von links im Inventar platziere, und den Breakpoint erneut auslösen.
Der gepushte Wert, der eben bei 00000001 lag, liegt nun bei 00000101. Lege ich das Item daneben und ziehe es an wird es 00000201 sein, lege ich es in den 5. Slot und benutze es, wird es 00000401 sein... und so weiter. Ihr könnt ja ein wenig testen.
Auffällig ist auf jeden Fall, dass das Byte 01 am Ende immer gleich zu sein scheint und das andere Byte(bzw WORD) die Zelle im Inventar angibt, wobei bei 0 angefangen wird zu zählen(Zelle 1 = Wert 0, Zelle 2 = Wert 1 usw..).
Blicken wir im Tutorial nach oben steht dort die Struktur des Parameters. window_type vom Typ BYTE und cell vom Typ WORD. Word ist ein Datentyp mit 2Bytes.
Alles was wir also vor dem Funktionsaufruf machen müssen, ist die Datenstruktur auf den Stack zu pushen.
Wollen wir also das Item des Slots 10 benutzen, so muss der Wert wie folgt aussehen:
PHP Code:
0x00000A01. (A ist Hexadezimal für 10)
PHP Code:
push 00000A01
call 0099A740
Der Klassenpointer
Die Funktion SendItemUsePacket(TItemPos pos) befindet sich in einer Klasse. Und zwar der CPythonNetworkStream class. Der Pointer(Zeiger) auf diese Klasse muss in diesem Fall ebenfalls bekannt sein. Wenn Funktionen aus anderen Klassen aufgerufen werden, wird der Pointer zu dieser Klasse mit angegeben. Dazu ist in der Regel das ECX Register.
Kurz gesagt: Vor dem Aufruf der Funktion SendUseItemPacket wird der Klasspointer in ECX gemoved. Dies müssen wir ebenfalls tun.(Das ist fast immer so!)
Um zu wissen wie der Pointer lautet müssen wir nur wieder einen Breakpoint auf den Anfang! der Funktion legen und ein Item benutzen. Der Wert, welcher sich nun im ECX Register befindet, ist unser gesuchter Klassenpointer. Dies ist jedoch kein statischer Pointer, heißt er wird nur für diesen Prozess funktionieren. Startet ihr das Spiel neu, ist der Pointer ebenfalls neu und euer Code wird nächstes mal nicht mehr hinhauen.
Wie findet man also den statischen Pointer?
Ganz einfach: Man reversed das Ganze ein wenig mehr.Wir wollen also nun herrausfinden, wo der Wert des ECX Registers herkommt. Dazu müssen wir allerdings vor den Aufruf der Funktion. Ich hatte vorher ja schon davon gesprochen, dass sich auf dem Stack, wenn der Breakpoint auslöst, die Rücksprungadresse befindet. Dort machen wir einfach einen Doppelklick drauf und schon landen wir bei dem Funktionsaufruf(call).
[Only registered and activated users can see links. Click Here To Register...]
Scrollen wir nun etwas hoch sieht man in meinem Beispiel folgendes:
Durch Breakpoints setzen(über dem call) kann man nun herrausfinden, wo das ECX Register geladen wird, wenn es nicht vorher schon offensichtlich ist.
Es kann vorkommen, dass vor dem Funktionsaufruf direkt folgendes steht:
PHP Code:
mov ECX, [0035BD78]
Da das hier nicht offensichtlich ist, müssen wir ein wenig rumprobieren. Wir wissen also das ECX 06CA9ED0 sein muss, also setzen wir Schritt für Schritt Breakpoints(über dem eigentlichen Funktionscall) und warten bis unsere Addresse in den Registern auftaucht.
Ich habe mal auf die Addresse 0098C49B (siehe Bild oben) einen Breakpoint gesetzt und gemerkt, dass das EAX Register nach diesem call den gesuchten Wert hält.(EAX ist das return Register, wo nach dem Funktionsaufruf (meistens) das Ergebnis einer Funktion landet)
[Only registered and activated users can see links. Click Here To Register...]
Nachfolgender Code(damit ist der Code nach dem call auf 0090BD70 gemeint) schiebt den EAX Wert dann nur noch in das ECX Register. Der call auf 0090BD70 scheint uns also den gesuchten Klassenpointer zu liefern.
[Only registered and activated users can see links. Click Here To Register...]
Schauen wir uns diese nun genauer an (Rechtsklick auf call 0090BD70 und Follow).
[Only registered and activated users can see links. Click Here To Register...]
Ahh! Perfekt. Wir sehen einen mov ins EAX Register. Und zwar mov eax, [01482190]. 01482190 ist hierbei eine Adresse im Speicher, an welcher unsere gesuchte Adresse steht.
Sucht man nach der Adresse unten wieder im HexViewer, so findet man an der Stelle 06CA9ED0(bzw D09ECA06-> Im Speicher steht es verkehrt herrum).
4.) Programmierung
Ich werde dazu demnächst mehr schreiben.
Programmcode dazu:
PHP Code:
void UseItem()
{
DWORD callToUseItem = 0x099A740;
_asm{
MOV ECX, DWORD PTR DS : [0x01482190]
PUSH 0x0A01
CALL callToUseItem
}
}