[C++]Hooks

03/21/2010 20:22 Tyrar#1
so... ich denke mal, dass ich euch eine der wichtigsten teile in sachen gamehacking hier erklären kann, oder zumindest versuchen zu erklären:p

fangen wir mal damit an, was ich benutze:
-Visual Studio 2008 Professional + Visual Assist X
-IDA Pro Free
-ein game

los gehts:
erstellt euch eine struktur, wodrin ihr informationen über einen hook speichert.
bei mir sieht das so aus:
Code:
[...]
typedef struct
{
BYTE* pBackup;
LPVOID* lpOriginal;
LPVOID* lpHook;
LPVOID* lpSub;
LPVOID* lpReturn;
}SHOOKINFO,*LPSHOOKINFO;
[...]
pBackup ist die kopie von dem überschriebenen speicher (später mehr)
lpOriginal ist die original funktions adresse
lpHook ist die funktion, die wir anspringen wollen!
lpSub ist eine zwischenfunktion, die lpHook ausführt, und auf lpReturn zurück springt.
lpReturn ist eine backup funktion, die den überschriebenen speicher ausführt, und zu der adresse von originalen+überschriebene byte länge springt.
als nächstes sollte ein globale variable erstellt werden, in der wir ALLE hook informationen speichern (auch mehrere). dazu nehme ich std::vector<typename>. das sieht so aus:
Code:
#include <vector>
[...]
std::vector<SHOOKINFO> hooks;
[...]
jetzt kommen wir zum interessanten teil, dem eigentlichen hook!
ein hook funktioniert so:
er überschreibt die ersten bytes einer funktion, mit den bytes die dafür sorgen dass die funktion als erstes an eine andere funktions adresse springt.
von dieser funktionsadresse KANN man die überschriebenen bytes ausführen, und an die original adresse + überschriebene byte länge springen, um die originale funktion aufzurufen.
da ich noch eine subfunktion habe die das alles für mich übernimmt (an neue adresse springen, und hinterher das backup ausführen), wird der letztere teil eher unnötig!
die subfunktion sieht bei mir so aus:
Code:
__declspec(naked) void hkSub()
{
__asm
{
pushfd //register backup
pushad //register backup
call 0xDEADBEEF //platzhalter für die neue funktion
popad //register backup laden
popfd //register backup laden
jmp 0xDEADBEEF //platzhalter für die backup funktion
}
}
die platzhalter werden später durch die "richtigen" adressen ersetzt.
Code:
[...]
#define ASM_JMP 0xE9 //byte-op-code für den JMP befehl!
[...]
SHOOKINFO AttachHook(LPVOID lpOriginal,LPVOID lpNew)
{
DWORD dwOldProtect;
SHOOKINFO info;
info.lpSub = VirtualAlloc(NULL,sizeof(&hkSub),MEM_COMMIT,PAGE_EXECUTE_READWRITE);
info.lpReturn = VirtualAlloc(NULL,10,MEM_COMMIT,PAGE_EXECUTE_READWRITE); //wie ich auf 10 komme? überschriebene bytes(5)+jmp(1)+DWORD(4)
VirtualProtect(lpOriginal,5,PAGE_EXECUTE_READWRITE,&dwOldProtect); //wie ich auf 5 komme? jmp(1)+DWORD(4)
info.pBackup = new BYTE[5];
memcpy(&info.pBackup[0],lpOriginal,5);
memcpy(lpOriginal,(void*)ASM_JMP,1); //jmp an den anfang schreiben
memcpy((void*)((DWORD)lpOriginal+1),info.lpSub,4); //adresse hinterher schreiben!
VirtualProtect(lpOriginal,5,dwOldProtect,0);
memcpy(info.lpSub,&hkSub,sizeof(&hkSub));
memcpy((void*)((DWORD)info.lpSub+3),lpNew,4); //das erste 0xDEADBEEF durch lpNew ersetzen!
memcpy((void*)((DWORD)info.lpSub+10),info.lpReturn,4); //das zweite 0xDEADBEEF durch info.lpReturn ersetzen!
memcpy(info.lpReturn,&info.pBuffer[0],5); //die überschriebenen bytes in die backup funktion schreiben
memcpy((void*)((DWORD)info.lpReturn+5),(void*)ASM_JMP,1); //jmp schreiben
memcpy((void*)((DWORD)info.lpReturn+6),(void*)((DWORD)lpOriginal+5),4); //die adresse hinterher schreiben, wo der jmp befehl auf die sub funktion zuende ist!
return info;
}
startet jetzt IDA, und analysiert das spiel! (könnte etwas dauern)
wenn IDA fertig ist, guckt mal in die register karte "Functions", und sucht nach einer funktion die interessant sein könnte. Ob die funktion euch interessiert, könnt ihr am namen lesen (wenn sie einen hat).
nehmen wir mal an, die funktion liegt an adresse 0x00618942.
dann muss euer code so aussehen:
Code:
__declspec(naked) void hkNothing()
{
//do something
}
[...]
AttachHook((void*)0x00618942,&hkNothing);
ich hoffe da sind keine fehler drin, und dass es hilfreich war:D
03/21/2010 23:50 flo8464#2
Der Code ist leserlich wie Spaghetti und du gibst einen Scheiß auf 64bit-Kompatibilität, aber trotzdem mal was brauchbares, netter Beitrag ;)

Wenn wir langweilig ist, werde ich deinen Code mal in schöneres C++ mitsamt x64-Unterstützung portieren, mal sehen.
03/22/2010 04:06 P-a-i-n#3
Quote:
Originally Posted by flo8464 View Post
Der Code ist leserlich wie Spaghetti und du gibst einen Scheiß auf 64bit-Kompatibilität, aber trotzdem mal was brauchbares, netter Beitrag ;)

Wenn wir langweilig ist, werde ich deinen Code mal in schöneres C++ mitsamt x64-Unterstützung portieren, mal sehen.
so ein quatsch das wird sogar unter 64 bit unterstützt arbeite genauso wie er und hab nie probleme

das ist kein hook d3 hook wo sich die adressen ändern dies aber auch im system32 nimmt man den devicepointer von spielen so ändert der sich nicht ;)

also läuft auf 32bit so wie 64bit
wenn man weiß wie und wo man es anwenden muss ist der source klasse :)
03/22/2010 06:43 flo8464#4
Quote:
Originally Posted by P-a-i-n View Post
so ein quatsch das wird sogar unter 64 bit unterstützt arbeite genauso wie er und hab nie probleme

das ist kein hook d3 hook wo sich die adressen ändern dies aber auch im system32 nimmt man den devicepointer von spielen so ändert der sich nicht ;)

also läuft auf 32bit so wie 64bit
wenn man weiß wie und wo man es anwenden muss ist der source klasse :)
Viel Spaß mit pushad/popad unter 64bit ;)

Unde der Rest deines Postings macht irgendwie keinen Sinn.
03/22/2010 08:13 Tyrar#5
Quote:
Originally Posted by flo8464 View Post
Viel Spaß mit pushad/popad unter 64bit ;)

Unde der Rest deines Postings macht irgendwie keinen Sinn.
argh >.<
is eben für 32 bit XD

naja ich wusste schon immer dass ich sowas nich kann XD

egal versuch war es wert
03/22/2010 12:32 P-a-i-n#6
das man das nicht 1 zu 1 verwenden kann ist klar
aber aus der erklärung kann man sich vieles ableiten
ich arbeite eh anders wenn ich es schreibe dann würd es so aussehen
und das geht ganz sicher unter 64bit
nur als bsp

DWORD funktion= 0x0XXXXXX;

_asm
{
XOR EAX,EAX;
PUSH Ent;
CALL funktion;
add esp, 4
MOV Ret, AL;
}
03/22/2010 13:21 flo8464#7
Quote:
DWORD funktion= 0x0XXXXXX;

_asm
{
XOR EAX,EAX;
PUSH Ent;
CALL funktion;
add esp, 4
MOV Ret, AL;
}
Das ist gleich mehrfach falsch :p

1. Passen 64bit-Addressen nicht in ein DWORD (4byte unsigned long). Hier würde sich DWORD_PTR anbieten, das ist ein typedef auf unsigned long bei 32bit und ein unsigned long long bei 64bit.

2. Das du mit den 32bit Registern bei 64bit Programmen recht blöd dastehst sollte auch klar sein ;)

3. Deine Return-Value ist sogar unter 32bit falsch, die gängigen Calling Conventions nutzen prinzipiell bei 32bit den ganzen EAX-Register zur Rückgabe, nur AL zu nutzen ist nicht gerade geschickt.
03/22/2010 14:21 MrSm!th#8
Quote:
Originally Posted by HeavyHacker View Post
ganz nett in der theorie, aber du hast etwa vergessen.
nämlich wenn am anfang der funktion nur eine instruction mit 1 byte ist und du damit auch eine zweite zum teil überschreibst.
oder wenn die instruction größer als 5 bytes ist.
dann wird das in die hose gehen, da dadurch entweder keine gültigen oder einfach die falschen instructions entstehen.
klar, bei api funktionen gibts normalerweise am anfang eine sinnlose instruction wie mov edi,edi aber bei anderen funktionen, wo sofort am anfang push ebp steht, haste ein problem.
da klappt das so nicht, denn in diesem falle müsste man die opcodes parsen und dann die richtige länge an bytes sichern. d.h. du nimmst entweder eine fertige disassembler lib, wie die von Olly oder BeaEngine oder du schreibst deine eigene ;) Wenn du wirklich nur vor hast, den Hook am Anfang zu platzieren, kannste die meisten opcodes vernachlässigen und nur nach den gängigsten am anfang einer funktion suchen (unwahrscheinlich dass direkt am anfang zb. gecalled wird). aber wie gesagt, ganz ohne gehts nur bei api funktionen
03/22/2010 15:29 P-a-i-n#9
Quote:
Originally Posted by flo8464 View Post
Das ist gleich mehrfach falsch :p

1. Passen 64bit-Addressen nicht in ein DWORD (4byte unsigned long). Hier würde sich DWORD_PTR anbieten, das ist ein typedef auf unsigned long bei 32bit und ein unsigned long long bei 64bit.

2. Das du mit den 32bit Registern bei 64bit Programmen recht blöd dastehst sollte auch klar sein ;)

3. Deine Return-Value ist sogar unter 32bit falsch, die gängigen Calling Conventions nutzen prinzipiell bei 32bit den ganzen EAX-Register zur Rückgabe, nur AL zu nutzen ist nicht gerade geschickt.
geht es zu 100% außerdem ist es nur ein teil von einer weit größeren funktion vergess nicht das ist nicht das 1x1
beim programmieren führen viele wege nach oben ich hab dir nur gezeigt wie ich es aufrufe die komplette funktion hab ich nicht kopiert es ist nur ein kleiner teil von weit mehr
das ist von CoD4 checkt ob ein gegener unsichtbar ist und färbt dann das boxesp um damit kannst dur die denken was da noch alles nötig ist das es klappt
da hängt noch eine riesige struct dran und es funktioniert ohne probleme ;)

nur weil du ein wegfährst muss ich den nicht auch fahren wenn es noch andere gibt

und ruf es so auf
BOOL funktion( CEntity* Ent )
und nicht mit

__declspec

das macht schon unterschiede so nu genug diskutiert jeder macht es anderes wichtig ist das es geht
03/22/2010 15:38 flo8464#10
COD 4 gibt es doch gar nicht als 64bit Binary?

Und nein, die physikalischen Gesetze verbieten es, "andere Wege" zu gehen. ;)
03/22/2010 16:47 P-a-i-n#11
wir machen es so du hast recht und ich meine ruhe sonst endet das hier nie
03/24/2010 16:26 MrSm!th#12
Mein erwähntes Problem am Source kümmert wohl niemanden >.<
03/25/2010 09:33 Tyrar#13
Quote:
Originally Posted by MrSm!th View Post
ganz nett in der theorie, aber du hast etwa vergessen.
nämlich wenn am anfang der funktion nur eine instruction mit 1 byte ist und du damit auch eine zweite zum teil überschreibst.
oder wenn die instruction größer als 5 bytes ist.
dann wird das in die hose gehen, da dadurch entweder keine gültigen oder einfach die falschen instructions entstehen.
klar, bei api funktionen gibts normalerweise am anfang eine sinnlose instruction wie mov edi,edi aber bei anderen funktionen, wo sofort am anfang push ebp steht, haste ein problem.
da klappt das so nicht, denn in diesem falle müsste man die opcodes parsen und dann die richtige länge an bytes sichern. d.h. du nimmst entweder eine fertige disassembler lib, wie die von Olly oder BeaEngine oder du schreibst deine eigene ;) Wenn du wirklich nur vor hast, den Hook am Anfang zu platzieren, kannste die meisten opcodes vernachlässigen und nur nach den gängigsten am anfang einer funktion suchen (unwahrscheinlich dass direkt am anfang zb. gecalled wird). aber wie gesagt, ganz ohne gehts nur bei api funktionen
ich habe dabei ziemlich allgemein gedacht ;)
natürlich wenn man ganz am anfang z.b.
Code:
mov edi, edi
call 0x00000001
kommt am ende nur scheisse raus!

das sollte eigendlich nur eine grundanleitung sein ;)