Virtuelle Methode einer Klasse in vTable austauschen?
-
hustbaer schrieb:
und solltest idealerweise dafür sorgen dass der Instruction-Cache geflushed wird.
Wo ich nochmal drüber nachdenke... das wird hier vermutlich nichtmal nötig sein. Weil ja nicht direkt in den vTable reingesprungen wird, sondern nur ein Zeiger als "Datum" aus dem vTable geladen, "über" den die Funktion dann angesprungen wird.
Und der Datencache sieht ja sowieso was abgeht.
-
moin,
vielen Dank für euere Antworten!
Also zu 1.)
Da lagt ihr richtig ... Habe das Initial Object, um an die vTable zu kommen nun dynamisch erzeugt, und siehe da, die DummyFunktion wir auch dort sofort verwendet.Zu 2.)
Ja der Source Code, dann könntet ihr auch direkt Code Posten für die Lösungsidee. Da steht noch viel Testmüll drinn, daher hatte ich den auch nur verkürzt gepostet. folgt aber, sobald ich den "geputzt" habe.Vielen Dank soweit,
mathi
-
Also hier nun der Source.
Einmal komplett als VC++10 sln ... Edit:link entferntund zum direkt lesen, aber trotzdem ohne den Standard WIN 32 kram.
(Ist "10mal" so viel code, als der code, um den es geht.) Wenn der
trotzdem sein muss, sagts bitte einfach nochmal.Meine Klasse:
// DrawObject.h #pragma once class CDrawObject { public: CDrawObject(void); virtual ~CDrawObject(void); virtual void draw(HDC hdc, PAINTSTRUCT* ps); }; // DrawObject.cpp #include "StdAfx.h" #include "DrawObject.h" CDrawObject::CDrawObject(void) { } CDrawObject::~CDrawObject(void) { } void CDrawObject::draw(HDC hdc, PAINTSTRUCT* ps) { HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, CreateSolidBrush(RGB(10,120,220))); RoundRect(hdc, 100, 100, 200, 200, 30, 30); DeleteObject(SelectObject(hdc, hOldBrush)); }// VTableSample.cpp : Definiert den Einstiegspunkt für die Anwendung. // #include "stdafx.h" #include "VTableSample.h" #include "DrawObject.h" // [...] CDrawObject* pZeichnen; typedef void (*pZEICHENFUNC)(HDC hdc, PAINTSTRUCT* ps); pZEICHENFUNC pOriginalFunktion = NULL; void DummyFunktion(HDC hdc, PAINTSTRUCT* ps); int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // [... Fenster Klasse registrieren ...] // Austauschen der Funktion pZeichnen = new CDrawObject(); int* vTablePtr = (int*)((int*)pZeichnen)[0]; // Sichern der Adresse der Original Funktion pOriginalFunktion = (pZEICHENFUNC)vTablePtr[1]; // Kopieren der Adresse der DummyFunktion an die Stelle von 'draw' DWORD dwOldProt = 0; VirtualProtect(&vTablePtr[1], sizeof(int), PAGE_EXECUTE_READWRITE, &dwOldProt); vTablePtr[1] = (int)DummyFunktion; VirtualProtect(&vTablePtr[1], sizeof(int), dwOldProt, NULL); // Anwendung initialisieren, Fenster erstellen // Hauptnachrichtenschleife: // Nachrichtenschleife beendet, Fenster zerstört delete pZeichnen; return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // [...] case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: Hier den Zeichnungscode hinzufügen. { pZeichnen->draw(hdc, &ps); } EndPaint(hWnd, &ps); break; // [...] } void DummyFunktion(HDC hdc, PAINTSTRUCT* ps) { HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, CreateSolidBrush(RGB(200,0,0))); RoundRect(hdc, 250, 100, 350, 200, 30, 30); DeleteObject(SelectObject(hdc, hOldBrush)); // vorm return stoppen, zum prüfen, was gezeichnet wurde ::MessageBoxA(NULL, "Rotes Rect = DummyFunktion\nBlaues Rect = OriginalFunktion", "Warten ...", MB_OK); }
-
mathi schrieb:
und zum direkt lesen, aber trotzdem ohne den Standard WIN 32 kram.
(Ist "10mal" so viel code, als der code, um den es geht.) Wenn der
trotzdem sein muss, sagts bitte einfach nochmal.Hihi.
Ich dachte du willst "nur mal testen".
Will uns hier denn jeder nur mehr verarschen?
Schreib doch einfach dass du irgend ein Programm hacken/patchen/... willst.ps: Das ist immer noch kein lauffähiges Beispiel, die Funktion wird da ja nirgends aufgerufen. Ausserdem fehlt die stdafx.h. Und es ist nicht nur nicht genug da, sondern gleichzeitig auch viel zu viel.
Dass da in dem Programm wo du das anwenden willst mit Fenstern rumgemacht wird, interessiert ja für die Frage "wie tu ich vtable patchen" nicht.
Das kann man mit einem viel einfacheren Beispiel erschlagen.
-
nabend hustbaer,
hustbaer schrieb:
Hihi.
Ich dachte du willst "nur mal testen".
Will uns hier denn jeder nur mehr verarschen?Warum sollte das kein Test sein? Da wo ich hin will, steht doch (noch) garnicht zur Diskussion. Die vTable zu manipulieren ist meine Absicht und der Code hier ist mein Test dazu. Mit dem Verarschen tust du mir Unrecht. Ich habe mein aktuelles Problem genau auf den Punkt reduziert, an dem ich fest stecke und hier ohne schnörkel, Verzierungen und Sonstigem Overhead formuliert. Falls du dich daher fragst was ich vor habe und neugierig geworden bist, dann kann das doch nur positiv sein. Dann beschäftigen sich offensichtlich Leute mit diesem Thread und ich habe höhere Chancen, mein Problem zu lösen. Aber mit verarschen hat das nichts zu tun.
hustbaer schrieb:
Schreib doch einfach dass du irgend ein Programm hacken/patchen/... willst.
Naja, im Prinzip stehts doch schon im Titel. Wenn ich jemanden Frage, wie ich ein Auto fahre, dann laber ich ihn doch nicht gleich damit zu, wo ich überall hinfahren möchte.
Wieder zum Thema:
hustbaer schrieb:
ps: Das ist immer noch kein lauffähiges Beispiel, die Funktion wird da ja nirgends aufgerufen. Ausserdem fehlt die stdafx.h.
Hast du da evtl. den Link oben zum kompletten Project/Source übersehen?
Die Funktion wird auch aufgerufen. Den Teil der WndProc mit der WM_PAINT Behandlung habe ich gepostet.
Die stdafx.h habe ich tatsächlich vergessen. Hier als Nachtrag:// stdafx.h #pragma once #include "targetver.h" #define WIN32_LEAN_AND_MEAN // Selten verwendete Teile der Windows-Header nicht einbinden. // Windows-Headerdateien: #include <windows.h> // C RunTime-Headerdateien #include <stdlib.h> #include <malloc.h> #include <memory.h> #include <tchar.h> /////////////////////////////////////////////// // targetver.h #pragma once #include <SDKDDKVer.h>Einen schönen Abend euch allen!
mathiAuch noch ein PS.:
hustbaer schrieb:
Und es ist nicht nur nicht genug da, sondern gleichzeitig auch viel zu viel. Dass da in dem Programm wo du das anwenden willst mit Fenstern rumgemacht wird, interessiert ja für die Frage "wie tu ich vtable patchen" nicht.
Zu viel, zu wenig? Du hast um den Source gebeten. Der Fenster kram ist raus, das Framework ist zu erkennen. Komplett Projekt ist verlinkt ... Was mach ich Falsch? Oder anders gefragt, womit ecke ich bei dir an?
-
Ne guck mal Freund, du schreibst "Ich möchte nur Verstehen Lernen, wie das Ganze so funktionieren kann.".
Und dann ein paar Beiträge weiter was von Code der 10x so lange ist.
Zum Rumprobieren und Verstehen brauchst du keinen Code der 10x so lange ist.
Also hast du uns im ersten Beitrag angeschummelt. Oder aber dein Test-Code ist ca. 20x so lang und umständlich wie er sein müsste.
So einfach ist das.Hast du da evtl. den Link oben zum kompletten Project/Source übersehen?
Nein, es interessiert mich bloss überhaupt nicht ne Solution runterzuladen. Wieso sollte ich das tun? Ist doch nur umständlich.
mathi schrieb:
Auch noch ein PS.:
hustbaer schrieb:
Und es ist nicht nur nicht genug da, sondern gleichzeitig auch viel zu viel. Dass da in dem Programm wo du das anwenden willst mit Fenstern rumgemacht wird, interessiert ja für die Frage "wie tu ich vtable patchen" nicht.
Zu viel, zu wenig? Du hast um den Source gebeten. Der Fenster kram ist raus, das Framework ist zu erkennen. Komplett Projekt ist verlinkt ... Was mach ich Falsch? Oder anders gefragt, womit ecke ich bei dir an?
Ja, der Fensterkram ist raus. Fast. Dafür auch der Aufruf der Funktion. Was soll man also damit? Das was du hier gepostet hast (direkt, vergiss die verlinkte Solution!) kann nicht funktionieren. Wieso sollte sich den Code dann jmd. angucken? Du verschwendest bloss unsere Zeit. Kommentare wie "hier passiert jetzt das und das" (wo der Code dazu aber fehlt) sind uninteressant. Wer sagt denn dass der Fehler nicht evtl. genau DA zu finden ist?
*seufz*
Guck mal, SO sieht ein Beispiel aus. Das lässt sich 1:1 bauen, ist nicht sinnlos in 15 Files aufgeteilt. Und es funktioniert sogar.
#define _WIN32_WINNT 0x0500 #include <windows.h> #include <string> #include <iostream> // Die originalen Klassen class Base { public: virtual ~Base() {} // Slot 0 virtual void Slot1() {} virtual void Slot2() {} virtual int Foo(int param1, char const* param2, std::string param3) // // Slot 3 { std::cout << "Base: " << param1 << " " << param2 << " " << param3 << "\n"; val = 11; return 1; } int val; }; class Derived : public Base { public: virtual int Foo(int param1, char const* param2, std::string param3) { std::cout << "Derived: " << param1 << " " << param2 << " " << param3 << "\n"; val = 22; return 2; } }; // Die Funktion die wir reinpatchen wollen int __fastcall ThePatchedFoo(Base* that, int /*EDX*/, int param1, char const* param2, std::string param3) { std::cout << "PATCH: " << param1 << " " << param2 << " " << param3 << "\n"; that->val = 99; return 9; } // Die Hilfsfunktion zum Patchen typedef void(*SomeFunctionPtr)(); template <class T> void OptimizationBarrier(T const& t) { char dummy[sizeof(T) + 1]; dummy[0] = 0; memcpy(&dummy[1], &t, sizeof(T)); ::OutputDebugStringA(dummy); } template <class T, class F> SomeFunctionPtr PatchItBaby(T* instance, int slotIndex, F* function) { OptimizationBarrier(instance); // Vermutlich in diesem Fall nicht nötig - funktioniert zumindest auch im Release ohne den Aufruf SomeFunctionPtr* vTable = *reinterpret_cast<SomeFunctionPtr**>(instance); SomeFunctionPtr& vTableSlot = vTable[slotIndex]; DWORD oldPageFalgs = 0; VirtualProtect(&vTableSlot, sizeof(SomeFunctionPtr), PAGE_EXECUTE_READWRITE, &oldPageFalgs); SomeFunctionPtr oldValue = vTableSlot; vTableSlot = reinterpret_cast<SomeFunctionPtr>(function); VirtualProtect(&vTableSlot, sizeof(SomeFunctionPtr), oldPageFalgs, NULL); OptimizationBarrier(instance); // Vermutlich nicht nötig, siehe oben return oldValue; } // Der Testcode Base* g_base; void Test() { int rv = g_base->Foo(42, "p2", "the_mighty_param_3"); std::cout << "Foo returned: " << rv << "\n"; std::cout << "val = " << g_base->val << "\n"; std::cout << "\n"; } int main() { g_base = new Derived(); // Mal mit dem Original Test(); // Patchen und probieren SomeFunctionPtr original = PatchItBaby(g_base, 3, &ThePatchedFoo); Test(); // Und wieder zurückpatchen und nochmal probieren PatchItBaby(g_base, 3, original); Test(); }Ergebnis:
Derived: 42 p2 the_mighty_param_3 Foo returned: 2 val = 22 PATCH: 42 p2 the_mighty_param_3 Foo returned: 9 val = 99 Derived: 42 p2 the_mighty_param_3 Foo returned: 2 val = 22In diesem Fall das erwünschte.
Hätte ich aber eine Frage, dann wäre diese jetzt für jeden klar nachzuvollziehen.
Es wäre weiters anzunehmen dass ich den Code nicht fürs Forum verunstaltet habe, und damit vermutlich den eigentlichen Fehler entfernt, dafür 5 neue eingefügt. Sondern dass der 1:1 so compilierbar ist und 1:1 genau das Ergebnis ausspuckt was ich gepostet habe.
-
Wundervoller Beitrag
Wenn du es nicht verstehst, hilft dir bei VTables kein Beispiel was.
Das Verständnis von VTables ergibt sich nicht aus der Anwendung oder dem Debuggen.
Das Verständnis von VTables ergibt sich über das Verständnis von Assembler und den Abläufen in die C++ den Code umsetzt. VTable Manipulation ist eine Direkte Manipulation in den unterliegenden Verwaltungsstrukturen der Klassen.Googel VTable Hook und such dir was passendes für dein Verständnis raus.
-
Just Additional schrieb:
Das Verständnis von VTables ergibt sich nicht aus der Anwendung oder dem Debuggen.
Das ist ohne Zweifel korrekt. Allerdings können solche Beispiele das theorethische Wissen oder sagen wir mal lieber in meinem Fall, die wage Ahnung von dem, was da passiert, stützen. Ich muss ja nicht alles wissen und vollkommen verstehen

Zu dem Thema vTable Hook ect. Habe ich bereits ne große Linksammlung. Vieles davon hat sich allerdings um das einbringen von asm jmp direktiven, also soweit ich das verstanden habe, nicht um das eigentliche ersetzen einer Funktion, sondern um das "Hinspringen" zu einer anderen Funktion gedreht. Das war mir dann doch zu viel. Oder es wurde eher oberflächlich mit psydo source erklärt und dann auf bestehende libs verwiesen.@hustbaer:
Ich kann mich Just Additional da nur anschließen. Wirklich elegant gelöst. Vielen lieben Dank für deine Mühe. Die generische Umsetzung der Problematik ist wirklich schön zu lesen und erschlägt so automatisch ne ganze Menge Folgefragen.Eine habe ich da allerdings noch: Die Funktion OptimizationBarrier macht doch in dem Beispiel nichts weiter, als den übergebenen Parameter in ein lokales array zu kopieren (mit einem offset von 1). Die sollte doch bestimmt was wichtiges(?) bewirken? Kannst du dazu nochmal was sagen?
-
mathi schrieb:
Die Funktion OptimizationBarrier macht doch in dem Beispiel nichts weiter, als den übergebenen Parameter in ein lokales array zu kopieren (mit einem offset von 1).
Ne, die macht mehr. Sie ruft dann nämlich
OutputDebugStringAmit einem Zeiger auf dieses Array auf. (Dazu ist auch das Nullbyte am Anfang gut: so erkenntOutputDebugStringAhier nen leeren String und tut im Endeffekt nix.)
Der Knackpunkt ist dassOutputDebugStringAne Kernelfunktion ist. Und ich gehe mal davon aus, dass der Compiler nicht weiss was die tut.D.h. der Compiler sieht dass hier eine Funktion, von der total unbekannt ist was sie machen könnte, mit einem Zeiger auf ein Array gefüttert wurde, wo was vorher was reinkopiert wurde.
D.h. der Compiler muss annehmen, dass der Wert der da reinkopiert wurde jedem denkbaren Programmteil bekannt sein könnte, und dass auch alles was über diesen Wert erreichbar ist (wenn der Wert z.B. ein Zeiger ist), von
OutputDebugStringAverändert hätte werden können.Kurzum: Wenn du schreibst
Foo* f = new Foo(); f->VirtualFunction();Dann kann der Compiler eigentlich sehen dass hier wirklich ein Foo erstellt wird, und VirtualFunction direkt aufrufen, ohne den Umweg über den vTable.
Hier dagegen:
Foo* f = new Foo(); OptimizationBarrier(&f); f->VirtualFunction();kann er das nimmer.
Weil er nicht sicher sein kann obOptimizationBarrier(&f)nicht vielleicht den Wert von f geändert haben könnte.---
Wobei... vermutlich wäre es besser die Adresse des übergebenen Teils zu kopieren statt den Inhalt.
Dann könnte man das & weglassen, also nurOptimizationBarrier(f)schreiben.
-
Ok, Danke nochmals!
Einen schönen Abend,
mathi