versionsunabhängige Game-Trainer bauen
Verfasst: 30.06.2008 18:58
In letzter Zeit gabs ja mal Fragen zu Trainern und Speicherzugriff, etc.
Hier will ich mal was weiterführendes zum Thema Trainer ansprechen: Versionsunabhängigkeit oder zumindest Versionstolleranz. ^^
Es gibt nicht viele Trainer, die dieses Feature haben. Ich kenn eigentlich sogar garkeinen, aber möglich ist das.
Ok, die Sache ist nicht ganz einfach. Das geht weit über normale simple Trainer, die Instruktionen statisch auf fixen Adressen patchen hinaus. Und ich hab das vor kurzem erst selbst zum ersten mal gemacht. Drumm ist das hier kein Tutorial und ich würde mich über eine Diskussion zu dem Thema freuen, falls hier genügend Leute mit Intresse an Trainern sind. Es gibt sicherlich noch einiges zu verbessern an meiner Methode.
Um das ganze zu verstehen sind folgende Kenntnisse von Nöten:
Damit ihr euch eine Vorstellung machen könnt, beschreibe ich euch erstmal wofür ich das in meinem Projekt brauche und wie ich das im groben umgesetzt habe. Details zu dem Projekt darf ich leider keine nennen. Vieleicht zu einem späteren Zeitpunkt. Was ich sagen darf ist, das es sich um ein Hackingtool handelt, welches dazu designt ist dabei zu unterstützen ein bestimmtes Spiel auf Cheat-Schwachstellen während der Betaphase zu testen.
Ok, los gehts: Mein Tool hookt die ingame-Konsole des Spiels um dort seine eigenen Kommandos zu implementieren. Das ganze läuft natürlich über einen Inline-Hook. Das heisst mit jedem neuen Build des Spiels ändert sich die Adresse und in diesem speziellen Fall auch Pointeradressen, welche mein Tool benötigt um Text in der Konsole auszugeben. Das ist ziemlich nervig da aktuell wöchentlich eine neue Beta-Version des Spiels gebaut wird.
So kam die Idee das ganze etwas dynamischer zu machen.
In diesem Fall können wir davon ausgehen das sich der Zielcode selbst nicht ändert, da es sehr unwarscheinlich ist, das große Änderungen am Konsolencode nötig sind. Allerdings sorgen die massiven Codeänderungen bei jedem Build dafür das sich Codeadressen und Pointeradressen verschieben. Lösung ist es nun sich den Code anzuschauen und nun nach seinem binären Muster zu suchen. Ist dieses gefunden kann man daran die Adresse zum Codepatchen ermitteln und die Pointeradressen aus dem Code auslesen.
Damit das ganze sehr schnell über die Bühne geht sollte man zum einen mit DLL-Injection arbeiten um direkte Operationen auf den virtuellen Speicher ausführen zu können und nicht den Umweg über ReadProcessMemory gehen zu müssen. Zweitens sollte man sich die Speicherregionen mit Ausführengsrechten rauspicken und nur in dehnen suchen. Um es nochmal zu optimieren kann man dann auch nur auf das jeweilige Modul begrenzen. Aber wenn man alleine schon die ganzen Datenregionen ausschließt läuft die binäre Suche sowieso im Sekundenbruchteil ab.
Hier mal ein Beispielcode zum raussuchen der Coderegionen im Prozessspeicher:
PureBasic 4.20 Code
Nun die binäre Mustersuche. Ich hab dafür keine schöne Prozedur, wenn da jemand was hat, wäre auf jeden Fall für nen Code dankbar. Ich hab das einfach im Augenblick direkt in Assembler geschrieben.
Nehmen wir ein simples Beispiel.
Das ist eine Codestelle im Spiel, welche Text auf der Konsole ausgibt. Mein Tool soll nun die Adresse der Ausgabeprozedur herausfinden, welche dort aufgerufen wird und ausserdem noch eine Pointeradresse, von der ich keine Ahnung habe wozu die gut ist aber ich muss den Pointer als Parameter für die Textausgabe verwenden. Solange es funktioniert is mir erstmal wurscht wozu der Pointer gut ist.
PUSH 005E208C Übergabe des Textes als Parameter (Adresse des ASCII-Strings)
PUSH DWORD PTR DS:[669924] Übergabe eines unbekannten Pointers
CALL 005165B3 aufruf der Prozedur zur Textausgabe
POP ECX Stack aufräumen
POP ECX Stack aufräumen
Daraus ergibt sich folgendes Binärmuster. Die Fragezeichen symbolisieren Bytes, die sich ändern können.
Wenn wir nach diesem Muster suchen, sollten wir die Adresse des Codes bekommen. Anhand der Adresse des Codes können wir dann die Parameter von "DWORD PTR DS:[669924]" und "CALL 005165B3" auslesen um damit unsere eigene Konsolentextausgabe dynamisch zur Laufzeit zu bauen, bzw. als Parameter für eine entsprechend dynamisch angelegte Prozedur verwenden.
PureBasic v4.20 Code
Das ganze funktioniert nun schon die letzten 2 Builds und ich hoffe das ich da auch für nächsten Builds nichts updaten brauche.
Was haltet ihr davon? Wo seht ihr Probleme, Verbesserungsmöglichkeiten, Schwachstellen?
Vorallem für ne Prozedur mit der man binär den Speicher durchsuchen kann und die Wildcards unterstützt, wäre ich dankbar.
Hier will ich mal was weiterführendes zum Thema Trainer ansprechen: Versionsunabhängigkeit oder zumindest Versionstolleranz. ^^
Es gibt nicht viele Trainer, die dieses Feature haben. Ich kenn eigentlich sogar garkeinen, aber möglich ist das.
Ok, die Sache ist nicht ganz einfach. Das geht weit über normale simple Trainer, die Instruktionen statisch auf fixen Adressen patchen hinaus. Und ich hab das vor kurzem erst selbst zum ersten mal gemacht. Drumm ist das hier kein Tutorial und ich würde mich über eine Diskussion zu dem Thema freuen, falls hier genügend Leute mit Intresse an Trainern sind. Es gibt sicherlich noch einiges zu verbessern an meiner Methode.
Um das ganze zu verstehen sind folgende Kenntnisse von Nöten:
- Grundkenntnisse in 32 Bit x86 Assembler
- Kenntnisse über das Speichermodell unter Win32/64
- Kenntnisse über die generelle Funktionsweise von Game-Trainern
- Wissen was man unter DLL-Injection versteht
Damit ihr euch eine Vorstellung machen könnt, beschreibe ich euch erstmal wofür ich das in meinem Projekt brauche und wie ich das im groben umgesetzt habe. Details zu dem Projekt darf ich leider keine nennen. Vieleicht zu einem späteren Zeitpunkt. Was ich sagen darf ist, das es sich um ein Hackingtool handelt, welches dazu designt ist dabei zu unterstützen ein bestimmtes Spiel auf Cheat-Schwachstellen während der Betaphase zu testen.
Ok, los gehts: Mein Tool hookt die ingame-Konsole des Spiels um dort seine eigenen Kommandos zu implementieren. Das ganze läuft natürlich über einen Inline-Hook. Das heisst mit jedem neuen Build des Spiels ändert sich die Adresse und in diesem speziellen Fall auch Pointeradressen, welche mein Tool benötigt um Text in der Konsole auszugeben. Das ist ziemlich nervig da aktuell wöchentlich eine neue Beta-Version des Spiels gebaut wird.
So kam die Idee das ganze etwas dynamischer zu machen.
In diesem Fall können wir davon ausgehen das sich der Zielcode selbst nicht ändert, da es sehr unwarscheinlich ist, das große Änderungen am Konsolencode nötig sind. Allerdings sorgen die massiven Codeänderungen bei jedem Build dafür das sich Codeadressen und Pointeradressen verschieben. Lösung ist es nun sich den Code anzuschauen und nun nach seinem binären Muster zu suchen. Ist dieses gefunden kann man daran die Adresse zum Codepatchen ermitteln und die Pointeradressen aus dem Code auslesen.
Damit das ganze sehr schnell über die Bühne geht sollte man zum einen mit DLL-Injection arbeiten um direkte Operationen auf den virtuellen Speicher ausführen zu können und nicht den Umweg über ReadProcessMemory gehen zu müssen. Zweitens sollte man sich die Speicherregionen mit Ausführengsrechten rauspicken und nur in dehnen suchen. Um es nochmal zu optimieren kann man dann auch nur auf das jeweilige Modul begrenzen. Aber wenn man alleine schon die ganzen Datenregionen ausschließt läuft die binäre Suche sowieso im Sekundenbruchteil ab.
Hier mal ein Beispielcode zum raussuchen der Coderegionen im Prozessspeicher:
PureBasic 4.20 Code
Code: Alles auswählen
Structure Region
BaseAddr.l
Size.l
EndStructure
Procedure.l GetExecutableMemRegions(RegionList.Region(1))
Define.l Addr,RetVal,RegionCnt
Define.MEMORY_BASIC_INFORMATION MemoryInfo
RegionCnt = -1
Repeat
Addr = Addr + MemoryInfo\RegionSize
RetVal = VirtualQuery_(Addr,MemoryInfo,SizeOf(MEMORY_BASIC_INFORMATION))
If RetVal > 0
If MemoryInfo\State = #MEM_COMMIT
If MemoryInfo\Protect = #PAGE_EXECUTE Or MemoryInfo\Protect = #PAGE_EXECUTE_READ Or MemoryInfo\Protect = #PAGE_EXECUTE_READWRITE Or MemoryInfo\Protect = #PAGE_EXECUTE_WRITECOPY
RegionCnt = RegionCnt + 1
ReDim RegionList.Region(RegionCnt)
RegionList(RegionCnt)\BaseAddr = MemoryInfo\BaseAddress
RegionList(RegionCnt)\Size = MemoryInfo\RegionSize
EndIf
EndIf
EndIf
Until RetVal < 1
ProcedureReturn RegionCnt
EndProcedure
Nehmen wir ein simples Beispiel.
Das ist eine Codestelle im Spiel, welche Text auf der Konsole ausgibt. Mein Tool soll nun die Adresse der Ausgabeprozedur herausfinden, welche dort aufgerufen wird und ausserdem noch eine Pointeradresse, von der ich keine Ahnung habe wozu die gut ist aber ich muss den Pointer als Parameter für die Textausgabe verwenden. Solange es funktioniert is mir erstmal wurscht wozu der Pointer gut ist.

Code: Alles auswählen
00401960 68 8C205E00 PUSH 005E208C
00401965 FF35 24996600 PUSH DWORD PTR DS:[669924]
0040196B E8 434C1100 CALL 005165B3
00401970 59 POP ECX
00401971 59 POP ECX
PUSH DWORD PTR DS:[669924] Übergabe eines unbekannten Pointers
CALL 005165B3 aufruf der Prozedur zur Textausgabe
POP ECX Stack aufräumen
POP ECX Stack aufräumen
Daraus ergibt sich folgendes Binärmuster. Die Fragezeichen symbolisieren Bytes, die sich ändern können.
Code: Alles auswählen
68 ?? ?? ?? ?? FF 35 ?? ?? ?? ?? E8 ?? ?? ?? ?? 59 59
PureBasic v4.20 Code
Code: Alles auswählen
Define.l i,RegionCnt,RegionStart,RegionSize,FoundAddr
Dim RegionList.Region(0)
RegionCnt = GetExecutableMemRegions(RegionList())
For i = 0 To RegionCnt
RegionStart = RegionList(i)\BaseAddr
RegionSize = RegionList(i)\Size - 20
FoundAddr = 0
!mov edi,[p.v_RegionStart]
!mov ecx,[p.v_RegionSize]
!mov esi,edi
!add esi,ecx
!GCP_LoopStart:
!mov al,$68
!repne scasb
!mov ax,[edi+4]
!cmp ax,$35FF
!jne GCP_NotFound
!mov al,[edi+10]
!cmp al,$E8
!jne GCP_NotFound
!mov ax,[edi+15]
!cmp ax,$5959
!jne GCP_NotFound
!mov [p.v_FoundAddr],edi
!jmp GCP_Found
!GCP_NotFound:
!cmp edi,esi
!jl GCP_LoopStart
!GCP_Found:
If FoundAddr <> 0
ConsolePointer = PeekL(FoundAddr+6)
ConsoleOutputProcAddr = PeekL(FoundAddr+11) + FoundAddr + 15
Break
EndIf
Next
Was haltet ihr davon? Wo seht ihr Probleme, Verbesserungsmöglichkeiten, Schwachstellen?
Vorallem für ne Prozedur mit der man binär den Speicher durchsuchen kann und die Wildcards unterstützt, wäre ich dankbar.