Find Pattern wird dafür genutzt, bestimmte Funktionsadressen aus einem Prozess auszulesen.
Dazu wird der Byte-OpCode einer EXE, DLL, BIN oder dergleichen durchlaufen und nach einem vordefiniertem Byte-Muster durchsucht.
Am Anfang etwas Theorie:
Da die Find Pattern Funktion einen bestimmten Suchbereich erwartet muss dieser zuvor ausgelesen werden. Normalerweise wird immer von der Modulbase bis zum Code-Ende alles durchsucht. Dazu braucht man den Anfangswert (Base) und den Endwert (SizeOfCode) , beides ist ganz einfach auslesbar. Der Anfang einer Windows Executable ist immer wie folgt aufgebaut (Tabelle unten).
An der Modulbasis der DOS-Header, dieser ist Standardisiert und sieht im Debugger so aus (Bild Unten).
Gleich darauf folgt der NT-Header (auch PE-Header). Im DOS-Header steht der Offset an dem letzterer zu finden ist (oben rot markiert). In diesem Fall das Offset 148 (Hexadezimal) also Beginnt dieser bei 400148, wenn mal die Modulbase draufrechnet (Im Bild unten nochmal durch die grüne Markierung verdeutlicht).
Interessant sind hierbei wie zuvor geschrieben der Start und Endwert. Der Basiswert wird durch die GetModuleHandle Funktion ermittelt, der Endwert wird aus dem PE-Header ausgelesen (Oben rot markiert). Der Codeabschnitt unten zeigt eine mögliche Umsetzung in C++.
DOS header |
NT header |
... |
... |
Gleich darauf folgt der NT-Header (auch PE-Header). Im DOS-Header steht der Offset an dem letzterer zu finden ist (oben rot markiert). In diesem Fall das Offset 148 (Hexadezimal) also Beginnt dieser bei 400148, wenn mal die Modulbase draufrechnet (Im Bild unten nochmal durch die grüne Markierung verdeutlicht).
Interessant sind hierbei wie zuvor geschrieben der Start und Endwert. Der Basiswert wird durch die GetModuleHandle Funktion ermittelt, der Endwert wird aus dem PE-Header ausgelesen (Oben rot markiert). Der Codeabschnitt unten zeigt eine mögliche Umsetzung in C++.
PHP Code:
DWORD Base = (DWORD)GetModuleHandleA("metin2client.bin");
DWORD PEOffset = *(DWORD*)(Base + 0x3C); //Anfang des PE-Headers auslesen
DWORD SizeOfCode = *(DWORD*)(Base + PEOffset + 0x1C) + Base; //SizeOfCode auslesen
Problemstellung:
Ziel ist es ein bestimmtes Muster zu finden, in diesem Beispiel muss der Metin2 Client herhalten. Es sollen die Funktionsadressen automatisch gesucht werden, im Beispiel die „DropItem“ Funktion. Diese sieht im Debugger folgendermaßen aus (im Bild unten grau unterlegt).
Aus den OpCode muss jetzt eine Suchmaske erstellt werden, dazu sucht man sich ein Codefragment aus welches die gewünschte Funktion beinhaltet, es kann ruhig etwas weiter nach oben bzw. unten gehen (wie im Bild oben rot markiert), damit es später besser gefunden werden kann. Anschließend wird dieser Code in ein BYTE-Array kopiert und die Bytes, die sich von Version zu Version oder durch Relozierung des Moduls ändern könnten (also z.B. Adressen oder Offsets) und daher durch die noch folgende String Maske ignoriert werden, zur besseren Lesbarkeit durch 0-Bytes ersetzt.
Das Byte Array speziell für diese Funktion sieht so aus wie im Bild unten, alle Unbekannten wurden durch 0-Bytes ersetzt.
Jetzt muss noch eine Prüfmaske erstellt werden, in dieser werden alle bekannten Bytes durch ein „x“ gekennzeichnet, alle Unbekannten durch ein „-„. Das Beispiel unten zeigt eine solche Maske (zur besseren Veranschaulichung mit Zeilenumbrüchen).
Nun ist alles Wichtige vorhanden um die Funktion aufzurufen.
Aus den OpCode muss jetzt eine Suchmaske erstellt werden, dazu sucht man sich ein Codefragment aus welches die gewünschte Funktion beinhaltet, es kann ruhig etwas weiter nach oben bzw. unten gehen (wie im Bild oben rot markiert), damit es später besser gefunden werden kann. Anschließend wird dieser Code in ein BYTE-Array kopiert und die Bytes, die sich von Version zu Version oder durch Relozierung des Moduls ändern könnten (also z.B. Adressen oder Offsets) und daher durch die noch folgende String Maske ignoriert werden, zur besseren Lesbarkeit durch 0-Bytes ersetzt.
Das Byte Array speziell für diese Funktion sieht so aus wie im Bild unten, alle Unbekannten wurden durch 0-Bytes ersetzt.
PHP Code:
BYTE Mask[] = {0xC3,
0x8B, 0x54, 0x24, 0x04,
0x8B, 0x44, 0x24, 0x08,
0x8B, 0x0D, 0x00, 0x00, 0x00, 0x00,
0x52,
0x6A, 0x00,
0x50,
0xE8, 0x00, 0x00, 0x00, 0x00,
0xE8, 0x00, 0x00, 0x00, 0x00,
0x5E};
PHP Code:
char mask[] = "x
xxxx
xxxx
xx---x
x
xx
x
x---x
x---x
x";
Die Find Pattern Funktion:
Diese Funktion besteht aus zwei Bestandteilen, einmal die Funktion als solches und eine Unterfunktion DataCompare welche dafür zuständig ist die Bytes aus der Suchmaske mit dem OpCode aus dem Prozess zu vergleichen. Die Originalfunktion stammt von dom1n1k (gamedeception.net), ich habe noch den Ignore Parameter hinzugefügt.
Parameter:
Rückgabe:
Zurückgegeben wird das Offset an dem die Funktion (die Suchmaske) gefunden wurde als DWORD Wert, in diesem Beispiel wäre das 0x41484E (auch auf dem Debugger-Codefragment im Bild oben ersichtlich, blau markiert).
Ein Aufruf könnte so aussehen
Als StartAddress wurde nicht direkt Base sondern Base + 1000(Hex) übergeben, dass ist weil der eigentliche Code erst ab Offset 1000(Hex) beginnt (und zwar immer). Der Bereich davor ist für die Headers reserviert.
Wie bereits geschrieben sollte die Funktion im Beispiel das Offset 0x41484E liefern. Mit diesem Offset lassen sich die benötigten Funktionsadressen ermitteln, wie das funktioniert wird nun erklärt.
PHP Code:
bool DataCompare(const BYTE* OpCodes, const BYTE* Mask, const char* StrMask)
{
//solange bis String zuende
while (*StrMask)
{
//wenn Byte ungleich --> false
if(*StrMask == 'x' && *OpCodes != *Mask )
return false;
++StrMask;
++OpCodes;
++Mask;
}
return true; //wenn alle Bytes gleich
}
DWORD FindPattern(DWORD StartAddress, DWORD CodeLen, BYTE* Mask, char* StrMask, unsigned short ignore)
{
unsigned short Ign = 0;
DWORD i = 0;
while (Ign <= ignore)
{
if(DataCompare((BYTE*)(StartAddress + i++), Mask,StrMask))
++Ign;
else if (i>=CodeLen)
return 0;
}
return StartAddress + i - 1;
}
Typ | Name | Beschreibung |
DWORD | StartAddress | Offset von welchem begonnen wird zu durchsuchen |
DWORD | CodeLen | Offset an dem die Suche gestoppt wird |
BYTE* | Mask | Byte-OpCode Maske |
char* | StrMask | String Maske, definiert welche Bytes bei der Suche beachtet werden(x) und welche ausgeschlossen werden(-). |
unsigned short | ignore | Anzahl der Funde die ignoriert werden, 0 = erster Fund wird zurückgegeben |
Zurückgegeben wird das Offset an dem die Funktion (die Suchmaske) gefunden wurde als DWORD Wert, in diesem Beispiel wäre das 0x41484E (auch auf dem Debugger-Codefragment im Bild oben ersichtlich, blau markiert).
Ein Aufruf könnte so aussehen
PHP Code:
DWORD Offset = FindPattern( Base + 0x1000, //BaseOfCode = Base + 1000(Hex)
SizeOfCode,
Mask,
"xxxxxxxxxxx---xxxxxx---xx---xx",
0);
Wie bereits geschrieben sollte die Funktion im Beispiel das Offset 0x41484E liefern. Mit diesem Offset lassen sich die benötigten Funktionsadressen ermitteln, wie das funktioniert wird nun erklärt.
Funktionsadressen berechnen:
Damit nicht ständig hochgescrollt werden muss hier nochmal das Codefragment der DropItem Funktion von vorhin.
Um eine Adresse im Stil von DWORD PTR DS:[??????] auszulesen muss man folgendes tun. Man zählt die Bytes ab Beginn der Suchmaske bis zu der Stelle an dem die benötigte Adresse steht. Im Beispiel soll die Adresse 61C028 (Bild oben grün markiert) ausgelesen werden, also beginnt man ab Start der Suchmaske, in unserem Beispiel war das C3 die Bytes (1 Byte = Zahlenpaar aus 2 Zahlen) bis zu der Adresse zu Zählen. So kommt man auf 11 Bytes. Die gezählten Bytes wurden im Bild zur Veranschaulichung gelb hinterlegt. Der Blau hinterlegte Abschnitt ist die gewünschte Adresse. Das Codebeispiel unten zeigt nun wie diese ausgelesen werden kann.
Um Andere Adressen wie z.B. 0041E1B0 auslesen zu können geht man ähnlich vor wie vorhin. Nur die Berechnung ändert sich da diese Adresse anders auf dem Speicher liegt. Man zählt wieder die Bytes vom Beginn unserer Maske (in diesem Fall C3) bis zur Adresse. Dieses Mal sind das 20 Bytes. Ausgelesen wird nun mit hilfe der Funktion unten.
Aufgerufen wird sie so:
Dies ist nötig weil die Adresse relativ zu dem Offset codiert ist. Man muss also erst den Wert auslesen der auf dem Offset liegt. In unserem Beispiel wäre das 994A, auf diesen Wert muss jetzt das Offset draufaddiert werden und dann noch die Länge der Adresse in Bytes also 4. Also 0x41E1B0 = 0x994A + (Offset +20) + 0x4.
Um eine Adresse im Stil von DWORD PTR DS:[??????] auszulesen muss man folgendes tun. Man zählt die Bytes ab Beginn der Suchmaske bis zu der Stelle an dem die benötigte Adresse steht. Im Beispiel soll die Adresse 61C028 (Bild oben grün markiert) ausgelesen werden, also beginnt man ab Start der Suchmaske, in unserem Beispiel war das C3 die Bytes (1 Byte = Zahlenpaar aus 2 Zahlen) bis zu der Adresse zu Zählen. So kommt man auf 11 Bytes. Die gezählten Bytes wurden im Bild zur Veranschaulichung gelb hinterlegt. Der Blau hinterlegte Abschnitt ist die gewünschte Adresse. Das Codebeispiel unten zeigt nun wie diese ausgelesen werden kann.
PHP Code:
DWORD Address1 = *(DWORD*)( Offset + 11);
PHP Code:
DWORD RelativeToAbsolute(DWORD Offset)
{
return *(DWORD*)(Offset) + Offset + 0x4;
}
PHP Code:
DWORD Address2 = RelativeToAbsolute(Offset + 20);
Alles in allem:
PHP Code:
//DataCompare--------------------------------------------------------
bool DataCompare(const BYTE* OpCodes, const BYTE* Mask, const char* StrMask)
{
//solange bis String zuende
while (*StrMask)
{
//wenn Byte ungleich --> false
if(*StrMask == 'x' && *OpCodes != *Mask )
return false;
++StrMask;
++OpCodes;
++Mask;
}
return true; //wenn alle Bytes gleich
}
//FindPattern------------------------------------------------------------------
DWORD FindPattern(DWORD StartAddress, DWORD CodeLen, BYTE* Mask, char* StrMask, unsigned short ignore)
{
unsigned short Ign = 0;
DWORD i = 0;
while (Ign <= ignore)
{
if(DataCompare((BYTE*)(StartAddress + i++), Mask, StrMask))
++Ign;
else if (i>=CodeLen)
return 0;
}
return StartAddress + i - 1;
}
//RelativeToAbsolute -------------------------------------------------------------
DWORD RelativeToAbsolute (DWORD Offset)
{
return *(DWORD*)(Offset) + Offset + 0x4;
}
// FindAddresses----------------------------------------------------------------
bool FindAddresses()
{
DWORD Base = (DWORD)GetModuleHandleA("metin2client.bin");
DWORD PEOffset = *(DWORD*)(Base + 0x3C); //Anfang des PE-Headers auslesen
DWORD SizeOfCode = *(DWORD*)(Base + PEOffset + 0x1C) + Base; //SizeOfCode auslesen
BYTE Mask[] = {0xC3,
0x8B, 0x54, 0x24, 0x04,
0x8B, 0x44, 0x24, 0x08,
0x8B, 0x0D, 0x00, 0x00, 0x00, 0x00,
0x52,
0x6A, 0x00,
0x50,
0xE8, 0x00, 0x00, 0x00, 0x00,
0xE8, 0x00, 0x00, 0x00, 0x00,
0x5E};
DWORD Offset = FindPattern( Base + 0x1000, //BaseOfCode = Base + 1000(Hex)
SizeOfCode,
Mask,
"xxxxxxxxxxx---xxxxxx---xx---xx",
0);
//Wenn Funktion nicht gefunden
if (!Offset)
return false;
DWORD Address1 = *(DWORD*)( Offset + 11);
DWORD Address2 = RelativeToAbsolute(Offset + 20);
return true;
}
Download als PDF: