Anschließen (hot patching) Klassen-member-Funktionen. Ändern vtable-Einträge
Ich habe 2 Fragen, nach einer ziemlich langen Präambel.
Durch die suchen-Funktion Zeiger auf void*
ich bin in der Lage zu ändern, seine ersten Anweisungen, verwandeln Sie sich in eine jmp
(entweder 32-bit relativ-oder 64-bit-absolut -, über r11
je auf x86/x86-64). Ich glaube, dass der Blick auf die Funktion von code als Daten ist illegal in C und C++, aber es irgendwie zu funktionieren scheint, in einer nicht unterstützten Methode, sowohl in Microsoft Visual C ++ (Win32) und GCC (OS X). Es gibt mehrere Orte im Internet, die sagen, dass das casting von Funktionszeigern zu void*
illegal ist.
Man nicht einfach einen Zeiger auf eine Klasse. Ich meine, der compiler direkt wirft Fehler bei der Erstellung, wenn Sie versuchen zu schauen wie ein Zeiger in der gleichen Weise, wie ich Aussehen würde bei einer void *
-, Praxis -, die scheint zu funktionieren gut für nicht-member-Funktionen.
Glücklicherweise, um die Haken Direct3D9, ich arbeite mit Sachen wie IDirect3DDevice9
die eine vtable
. Mit pDev
Typ IDirect3DDevice9*
reicht es aus, schaue ich mir pDev
als bei einem PVOID*
. Dann, der erste Wert bei pDev
ist die Adresse eines Arrays von Zeigern auf Funktionen (die vtable
):
//IDirect3DDevice9::Present()
typedef HRESULT (CALLBACK *PRESENT_PROC)(
LPDIRECT3DDEVICE9, const RECT*,
const RECT*,
HWND,
const RGNDATA*
);
PVOID (*vPtr)[] = reinterpret_cast<PVOID (*)[]>(
*reinterpret_cast<PVOID*>(pDev)
);
PRESENT_PROC pDevicePresent = reinterpret_cast<PRESENT_PROC>(
(*vPtr)[17]
);
da ist der 18-Eintrag.
Die erste Antwort von hier gibt eine elegantere, höher-level-Methode, beginnend mit der Definition CINTERFACE
. Ich habe es noch nicht getestet, aber laut ihm kann ich Dinge tun wie
reinterpret_cast<PVOID>(pDev->lpVtbl->Present)
ohne Fehler.
Erste problem. Bin ich nicht eine tolle C++ Programmierer; wie bekomme ich einen Zeiger, der im Allgemeinen an eine member-Funktion, so dass ich die überschreiben die ausführbare bytes der Funktion. Für ein nicht-Mitglied ich:
#include <windows.h>
#include <cstdio>
using namespace std;
const unsigned char OP_JMP = 0xE9; //32 bit relative jmp
const SIZE_T SIZE_PATCH = 5; //jmp dword ptr distance; 1 byte + 4 bytes
typedef void (*MyProc)();
void SimpleFunction1()
{
printf("foo\n");
}
void SimpleFunction2()
{
printf("bar\n");
}
int main()
{
PBYTE foo = reinterpret_cast<PBYTE>(SimpleFunction1);
PBYTE bar = reinterpret_cast<PBYTE>(SimpleFunction2);
DWORD oldProtection;
//make sure the bytes of the function are writable
//by default they are only readable and executable
BOOL res = VirtualProtect(
foo,
SIZE_PATCH,
PAGE_EXECUTE_READWRITE,
&oldProtection
);
if (!res) return 1;
//be mindful of pointer arithmetic
//works with PBYTE, won't with PDWORD
DWORD distanceToNewFoo = bar - foo - SIZE_PATCH;
*foo = OP_JMP;
*reinterpret_cast<PDWORD>(foo + 1) = distanceToNewFoo;
//called though the pointer instead of foo()
//to make sure the compiler won't inline or do some other stupid stuff
reinterpret_cast<MyProc>(foo)(); //will print "bar\n"
return 0;
}
und etwas in der gleichen Ader für die x86-64. Für einen virtuellen member eines Objekts, bekomme ich die foo
Zeiger von der vtable
sich, wie ich oben gezeigt:
reinterpret_cast<FUNC_TYPE>(
*(reinterpret_cast<void**>(
*reinterpret_cast<void**>(objptr)) + n
)
)
Zweite problem. Kann ich nicht erhalten, indem Sie einfach ändern Sie die Einträge aus der vtable
für mein Objekt? Hier ist ein Beispiel, es ist unnötig zu sagen, es funktioniert nicht für die pDev
Objekt, als käme Sie direkt aus Direct3D, aber Taksi scheint um diese Methode zu verwenden:
#include <cstdio>
using namespace std;
class BaseClass
{
public:
BaseClass(int a = 0, int b = 0);
int GetA();
int GetB();
virtual void Test();
private:
int _a;
int _b;
};
BaseClass::BaseClass(int a, int b) :
_a(a),
_b(b)
{
}
int BaseClass::GetA()
{
return _a;
}
int BaseClass::GetB()
{
return _b;
}
void BaseClass::Test()
{
printf("test %d; %d\n", _a, _b);
}
void TheNewFunction(BaseClass *bc)
{
printf("I am an intruder\n");
}
typedef void (*PROC_TYPE)(BaseClass *);
int main()
{
BaseClass foo(5, 56);
PROC_TYPE proc = 0;
proc = reinterpret_cast<PROC_TYPE>(
*reinterpret_cast<void**>(
*reinterpret_cast<void**>(&foo)
)
);
proc(&foo);
reinterpret_cast<void**>(
*reinterpret_cast<void**>(&foo)
)[0] = reinterpret_cast<void*>(TheNewFunction);
foo.Test(); //runs same old Test(); maybe due to compiler optimization?
proc = reinterpret_cast<PROC_TYPE>(
*reinterpret_cast<void**>(
*reinterpret_cast<void**>(&foo)
)
);
proc(&foo); //runs TheNewFunction
BaseClass *goo = &foo;
goo->Test(); //runs TheNewFunction
return 0;
}
Du musst angemeldet sein, um einen Kommentar abzugeben.
Wie gesagt-aus, Ihre Methode ist nicht "tragbar", aber es funktioniert tatsächlich in Ihrem speziellen Fall. Und es ist kein problem IMHO.
Erste Antwort:
Dies ist die syntax für die Arbeit mit member-Funktion-Zeiger:
Zweite Antwort:
Können Sie änderungen für die Mitglieder der
vtable
direkt, aber Sie sollten wissen, dass Sie eine Unterklasse alle die Objekte dieser Klasse (und möglicherweise einige abgeleitete Klassen).Wenn Sie möchten, um eine Unterklasse nur von einer bestimmten obj erstellen Sie eine weitere Funktion Tabelle, und überschreiben des Objekts
vtable
auf die neue Funktion Tabelle.SomeFunc
und ich bin glücklich und zufrieden; so konvertierenpfn
zuvoid*
- das kann ich nicht. Die zweite Antwort wirft ein Licht; genau das, was ich will - ändern der in der Klasse Verhalten.Den schnellsten Weg zu dieser hässlichen Darsteller (Mitglied-Funktion zum
void *
), wäre die berüchtigteunion_cast<>
:Verwenden, wie diese:
Nun, ich habe Ihnen eine geladene Waffe mit der Sicherheit aus. Bitte verwenden Sie mit Vorsicht...
Gibt es keinen portablen Weg, dies zu tun in C++ als Sprache, die Spezifikation sagt, dass jede Art von Gießen, die Sie vielleicht nicht von einer Zeiger-auf-member-Funktion oder Zeiger-auf-Funktion ein void* Ergebnisse zu undefiniertem Verhalten. Wenn Sie wollen, um diese Art von dynamischen code-rewriting, müssen Sie konsultieren Sie die compiler-Dokumentation für die entsprechende Plattform Sie arbeiten.
Wenn Sie mit einem compiler erzeugt vtables Sie sollten in der Lage sein zu ändern, wenn die Funktion code befindet, durch das Strahlen der vtable, vorausgesetzt, dass die vtable ist veränderlich. Ich glaube, die meisten Compiler drop die vtable in einem nur-lese-segment, so kann man nicht d diese versehentlich (oder absichtlich), obwohl. Sie würde sich auch sorgen machen über die compiler-Optimierungen inline rufen, da gibt es nichts hindert den compiler von der Erkenntnis, das Ziel eines Methodenaufrufs in einigen Fällen und nur hartzucodieren die call.
goo->Test()
nennt sich die neue Funktion, währendfoo.Test()
noch ruft der alte. In meinem Windows-app, merke ich, dass beim Strahlen meinIDirect3DDevice9
Objektvtable
um nicht scheinen, um den job zu erledigen, während plain old Injektion (mit derE9
opcodejmp
) tut es. Ich denke, dass die Spiele Ihre eigenen Exemplare der Direct3D-Objekte, die Laufleistung variiert je nach Anwendung.Sind Sie sicherlich richtig, dass in einigen Fällen-wie diesem -
den compiler können leicht erkennen, die Art von
foo
und nicht brauchen, um eine vtable an alle. Es weiß, welche Funktion rufen sowieso.Beim Aufruf über einen Zeiger auf das Objekt, den compiler-Designer nicht die Mühe gemacht zu überprüfen, ob es immer der gleiche Typ. In Ihrem code, den Sie sicherlich könnte das sehen, aber vielleicht ist eine solche Optimierung ist nicht nützlich genug, im echten code? Oder Sie können hinzufügen, es in der nächsten Version des Compilers. 🙂
Und wir sollten nicht vergessen, dass vtables, Ihre Existenz und die möglichen layout sind alle details zur Implementierung, der Sprache standard sagt nichts darüber aus.
Fast tragbare Lösung für das erste problem -
va_list
in eine helper-Funktion:valdo genagelt, die zweite.