Age of Empires 2 - External Entity Hack

Ich glaube annährend jeder Besucher dieses Blogs hat früher, oder sogar in letzter Zeit als HD Remake, Age of Empires 2 gespielt. Dieses Kultspiel wird hier exemplarisch herrangezogen, um die Schritte beim Programmieren eines Hacks nachvollziehbarer zu machen. Genaue Offsets und Klassen gibt es nicht, wohl aber die Möglichkeit die Schritte nachzumachen. Damit stösst man unweigerlich auch auf die Offsets und lernt den Umgang mit IDA Pro und dem Debugger. Es ist definitiv nicht meine Intention, die Grundlage für einen Hack zu schaffen. Vielmehr hab ich gemerkt, wie schön man das Finden der Entites als Beispiel nutzen und abstrahieren kann.

Am Ende des Blogposts werden wir die Positionen aller Entites und aller Spieler auf eine Grafik plotten können. Natürlich wären weitere Features möglich, aber es geht nur um den Weg bis zu diesem Punkt ;)

Fangen wir mit etwas allgemeinen Informationen an. AoE2 ist wie eigentlich jedes ältere Spiel in C++ geschrieben. Konkret heißt das für uns, dass wir eine Entity-Basisklasse haben, von denen alle Einheiten und Gebäude erben. Im Speicher befindet sich also immer die Basisklasse, gefolgt von den speziellen Attributen je nach Klassentyp. Damit wissen wir auch, dass der erste Eintrag in diesen Entity-Klassen die VTable ist. Das Konzept dahinter wurde schon bei Siedler3 und Interfaces erklärt: Um bei einem Aufruf von “Building->Update()” und “Unit->Update()” bei unterschiedlichen Funktionen zu landen, wird der Update()-Funktionszeiger überschrieben, der sich an einer fixen Stelle innerhalb der VTable befindet. Das hat den Vorteil, das wenn wir ein riesiges Array von Entites haben, einfach alle durchlaufen und die Update()-Funktion aufrufen können. Die VTable sorgt dann schon dafür, dass die richtige spezielle Methode aufgerufen wird und wir müssen uns nicht darum kümmern.

Soviel zur gleich gebrauchten Theorie, los gehts in der Praxis mit Memory Scanning. Wie brauchen nämlich eine Variable, die sicher in der Entity-Klasse steht. Optional sollte sie noch leicht zu verändern und eindeutig sein. Was gibt es da passenderes wie das Leben der Entity? Starten wir Cheat Engine und stellen den Typ auf Float. Wir markieren eine Einheit, lesen das Leben ab und verändern es, indemaok1 wir die Einheit einen Gegner angreifen lassen. Zum Glück können wir über F3 das Spiel pausieren, so ist entspanntes Suchen nach der richtigen Variable möglich ohne in die Trickkiste greifen zu müssen. Übrig bleiben sollten zwei Variablen die jeweils das aktuelle Leben anzeigen. Mit der Adresse der Variable selbst fangen wir nichts an, denn spätenstens beim nächsten Spielstart sieht diese ganz anders ist. Spannender ist aber die Funktion von Cheat Engine, über den Debugger herraus zu finden welche Instruktionen auf die Variable zugreifen. Das ist natürlich auch im normalen Debugger über einen Memory-Breakpoint möglich, allerdings bekommen wir bei den doch vielen Zugriffen eine schöne Liste von CheatEngine.


Die Floating-Point Instruktionen muss man nicht verstehn, und wer doch so masochistisch ist darf sich mein KeygenMe anschaun. Wichtig ist vielmehr die Adresse, von der gelesen wird. In jedem Fall ist das ein Register+0×30. Das heißt, dass ohne den Offset von 0×30 Bytes drauf zu addieren, wir die Adresse an welcher die Struktur beginnt haben. In diesem Fall ist das der Wert in ECX 0×18631890. Damit haben wir auch unsere erste Information über die Entity Struktur: an 0×30 ist bei jeder Entity das aktuelle Leben gespeichert.

Was machen wir nun mit dieser ersten Base-Adresse? Wir könnten einen Pointer-Scan laufen lassen und hoffen damit eine Reihe nach statischen Zeigern zu finden… Nein, auf solche Tools sind wir nicht angewiesen. Generell ist es jetzt klug, sich erstmal ein grobes Bild von der Entity-Struktur zu machen. Das geht mit Cheat-Engine und der Memory View, aber eleganter über das Tool ReClass 2011. Damit kann man sich Strukturen bauen und diese später als C++-Header exportieren lassen. Und jede Adresse wird endweder als Zeiger auf anderen Speicher/Text, Float oder DWORD erkannt und dargestellt. Laden wir doch mal unsere Entity-Adresse rein:

Viele komische Werte? In der Tat, aber wenn man den Durchblick hat wird man das Tool lieben. Fangen wir mit der Brücke zum Theorieteil an. In C++ hat jede vererbte Klasse als ersten Eintrag einen Zeiger auf die VTable. Und wenn wir uns den Zeiger an Stelle 0×00 anschaun und daraus eine “Sub-Klasse” bilden, sehen wir wiederrum viele Adressen die wohl auf Code Verweisen. Das erkannt man daran, dass es ein Zeiger in die .code-Section ist und das Module davor steht. Anders als z.B. bei den Einträgen ab Offset 0×8. Damit haben wir die VTable gefunden, aber die bringt uns ja noch nicht wirklich was. Also schauen wir uns die weiteren Werte an… Es macht Sinn die Entity-Klasse mit Zeigern auf andere Manager auszustatten, damit diese nicht immer beim Funktionsaufruf übergeben werden müssen. Diese finden sich ab 0×08, ebenfalls als Zeiger auf weitere Strukturen. Die Stelle 0×30 habe ich als Float-Datentyp festgelegt und den Namen fHealth verpasst. Und ab Offset 0×38 scheint die Position zu stehen! Darauf wollten wir hinaus. Tiefer in die Entity-Klasse dringen wir garnicht vor, wir haben wir Werte die wir brauchen. Wir wollen jetzt “nur” noch an die weiteren Entites und vorallem an den statischen Base-Pointer. So, holt euch am besten hier einen Kaffe, denn jetzt wirds nochmal bisschen wild.

In C++ gibt es die Calling-Convention Thiscall, die speziell für Aufrufe aus Klassenmethoden geeignet ist. Wir können im allgemeinen davon ausgehen, dass jede VTable-Funktion diese Convention besitzt. Damit wissen wir, dass sich der This-Pointer (also der Zeiger auf das Objekt, was die Methode besitzt) immer im Register ECX befindet. Und Überraschung, der Base-Pointer von Entity vorhin war ebenfalls in ECX. Der Fluch ist allerdings auch Segen zugleich, gerade wenn wir versuchen die Methode zu finden die zum ersten Mal eine VTable-Funktion aufruft. Denn dieser VTable-Offset wird dynamisch berechnet und kann daher nicht über “Find References To Function” (XRefs in IDA) gefunden werden. Denn irgendwann ist die Refenz zu der Methode nur noch ein Eintrag, überraschung, in die statische VTable. Aber von irgendwo her muss der Call doch kommen! Genau, und zwar dynamisch aus einem berechneten VTable Eintrag. Alles was dynamisch ist, mache ich mit meinem neuen Lieblingsdebugger x64Dbg. Laufen wir den Stack doch so lange zurück, bis wir keinen direkten Aufruf “Call XXX” mehr sehen, sondern irgendwas mit Registern und CALL [EAX+X]. Also hoch im Stack, und weiter und weiter. Irgendwann stoßen wir auf diese Instructions und genau hier fließt nochmal alle Theorie zusammen:

mov eax,dword ptr ds:[ebx+4]        | ;Mov Entity Array Start into EAX
mov edi,dword ptr ds:[eax+esi*4]    | ;Calculate Offset for current Entity i
mov ecx,edi                         | ;Prepare for THISCALL
mov eax,dword ptr ds:[edi]          | ;Get VTable Pointer
mov eax,dword ptr ds:[eax+24]       | ;Get Offset 0x24 in VTable
call eax                            | ;Call VTable Entry
inc esi                             | ;i = i + 1
cmp esi,dword ptr ds:[ebx+8]        | ;Is i == Entity Count ? => Loop Finished
jl aok hd.CA5380                    | ;Repeat Loop

Während erfahrene Leute direkt sehen, dass es sich hierbei um eine For-Schleife handelt, entwirre ich die ganzen Zeilen nochmal im Detail: Wir brauchen einen Zeiger für den Array Anfang, der wohl in EBX+4 gespeichert ist. Im Anschluss berechnen wir in diesem Array den i-ten Eintrag, wobei i in ESI gespeichert ist. Nun schieben wir diesen Eintrag in ECX (Thiscall Convention) und dereferenzieren noch den Offset an 0×00 der Entity Struktur. Damit landen wir in der VTable, von welcher wir nochmal den Eintrag an Offset 0×24 abrufen. Dieser Wert wird im Anschluss gecalled. Nach dem Aufruf wird i erhöht und vergleichen, ob i bereits EBX+8 entspricht. Das heißt wohl, dass in EBX+8 die Anzahl der Array-Einträge gespeichert ist. Wenn i noch nicht identisch ist, wird wieder an die erste Instruktion gesprungen. Schauen wir uns dochmal EBX an, um unsere Theorie zu verifizieren:

Zunächst einmal wieder der Offset an 0×00. Auch hier scheint es sich um eine VTable zu handeln. Dem Code nach müssten sich die Array-Einträge hinter 0×04 verbergen, und die Anzahl der Einträge an der Stelle 0×08. In diesem Fall haben wir recht, 88 Einträge sind es an der Zahl. Breaken wir nochmal an dieser Stelle, so finden wir plötzlich die gleiche Struktur mit einer anderen Anzahl an Einträgen und anderen Arrays. Es scheint also mehrere Arrays für Entites zu geben, die werden wir ja wohl auch noch finden. In dem Fall hilft die IDA XRef Suche:

 do
{
  v62 = *(_DWORD *)(v20 + 0x17C) + 8 * v78;// Loop All Managers ?
  if ( CheckArgIsNotZeroWrapper(v62) )
    LoopEntitesInternal(*(void **)(*(_DWORD *)v62 + 0x80));// Loop Entities
  ++v78;
}
while ( v78 < (*(_DWORD *)(v20 + 0x180) - *(_DWORD *)(v20 + 0x17C)) >> 3 );

Wir landen bei diesem Code. Das gewohnte Spiel, eine Schleife die so lange läuft wie der Counter < (Offset 0×180 - Offset 0x17C) >> 3 ist. Das Right-Shifting sieht zwar im Decompiler hässlig aus, aber ist nichts anderes als diesen Wert durch 8 zu teilen. Denn durch dreifaches Verschieben der Bits nach Rechts, werden 3 Bits verworfen und 2^3 sind 8 ;) D.h. die Struktur über die Iteriert wird ist 8 Byte-Groß. Und genau 8 Byte werden auch jeweils in der dritten Zeile hinzugefügt. Von diesem Wert befindet sich nun an 0×80 die gefundene Array-Struktur. Wie man an v20 kommt ist hier nicht erwähnt, aber es ist zieeemlich nahe an einem statischen Pointer …

Wir stellen Fest dass diese Schleife immer SPIELERANZAHL+1 mal durchlaufen wird. Wem gehören also die Entites für den fehlenden Spieler? Wir haben es hier mit einer Basisklasse zu tun und in AoE2 ist so ziemlich ALLES eine Entity. Plotten wir doch mal die Entites für den unbekannten Spieler.

 

Wer findet den gesuchten Kartenauschnitt und vorallem was es ist? Netterweise ist die Karte schon richtig hingedreht (45° rotiert), und der im Spiel gezeigte Ausschnitt passt auf die beiden Punktansammlungen mittig oben. Es handelt sich bei Spieler 0 also um die Natur bzw Rohstoffvorkommen. Die vielen Punkte in der Mitte und Rechts sind Fische. Damit wissen wir, welchem Spieler die Arrays jeweils gehören und können so eine ganze Karte plotten. Dieses mal, zur besseren Ansicht, mit dem “marco polo” Cheat.

Die Minimap wird verzogen dargestellt, wenn man sie rotiert und auf ein Quadrat staucht kommt man auf das selbe raus ;) Hier im Bild zu sehen der eigentlich Versteckte Sir Wallace, der uns später zur Hilfe eilt. Aber schon auf unserer Map geplottet wird …

Ich hoffe ich konnte mit dem Beitrag ein allgemeine Vorgehen demonstrieren, wie ich an solche Spiele herrangeh. Vieles ist natürlich ausprobieren und auch nicht immer landet man direkt bei den richtigen Funktionen. Umso mehr Spass macht es dann, wenn es funktioniert! Bei Fragen jeglicher Art einfach kommentieren!

9 people like this post.
    • Pain
    • 4. Mrz. 2016 7:00pm

    geil, wenn du das gleiche nochmal mit rose online machen könntest spendier ich dir nen Kaffee :D. Ich habe in Roseonline versucht das Inventar auszulesen, das hat auch einigermaßen geklappt, allerdings komme ich nicht an die Strings ran. Ich finde nur die Amount, gelooped wird durch das Array auch via i counter und gefühlten 37 offsets. Falls du tatsächlich mal Interesse haben solltest - einen Account kann ich dir gerne geben. Nen paar € fürs nächste Game sollten auch drin sein, sofern daraus nochmal ein Blog eintrag wird. Das Spiel ist übrigens recht alt ;).

    • Pain
    • 5. Mrz. 2016 1:19am

    Ich habe mal versucht dir zu folgen, scheitere jedoch bei dem Punkt bei den es dann in x32dbg/x64dbg geht. “Aber von irgendwo her muss der Call doch kommen! Genau, und zwar dynamisch aus einem berechneten VTable Eintrag. Alles was dynamisch ist, mache ich mit meinem neuen Lieblingsdebugger x64Dbg. Laufen wir den Stack doch so lange zurück, bis wir keinen direkten Aufruf “Call XXX” mehr sehen, sondern irgendwas mit Registern und CALL [EAX+X]. ” leider finde ich da nichts, bzw nicht die besagte Stelle an der die beschrieben Schleife zu finden wäre. Es wäre außerdem schön wenn du uns mitteilen würdest mit welcher Version von AOE 2 HD du spielst denn es scheint als hätte ich eine andere.

      • Pain
      • 5. Mrz. 2016 2:33pm

      Mein Fehler:Ö Ich habe die falsche Addresse untersucht, nämlich die, in der die HP der angeklickten Entity sind, nicht die des allgemeinen Arrays. Danach konnte ich nach hochlaufen des Call Stacks auch die Schleifen finden. Die erste für die Entities eines Spielers, die Zweite für die Spieler. Den Schritt “Es scheint also mehrere Arrays für Entites zu geben, die werden wir ja wohl auch noch finden. In dem Fall hilft die IDA XRef Suche:” habe ich leider nicht verstanden, weshalb ich einfach solange gesucht habe bis ich die nächst höhere Schleife gefunden habe.

        • Easysurfer
        • 6. Mrz. 2016 6:18pm

        Gut dass Dus von selbst gefunden hast. Die XRefs Suche findet einfach alle Funktionen, die die aktuell ausgewählte Funktion aufrufen. In diesem Fall siehe Screenshot: http://www0.xup.in/exec/ximg.php?fid=14062232
        Ein XRef is nur darstellbar wenn es kein VTable call ist .

        P.S Ich nutze die normale Steam Version Age of Empires 2 HD

  1. Noch keine TrackBacks.