Problem mit IDirect3DDevice9 VTable Hooking

03/23/2014 17:57 snow#1
Hallo,

ich habe hier ein Problem mit der VTable, bei dem ich mir nicht ganz sicher bin, ob ich nicht einfach etwas übersehe.

Ich erstelle mit IDirect3D9::CreateDevice() ein IDirect3DDevice9 und initialisiere damit meinen Pointer auf die VTable:
Code:
m_device = CreateDevice();
m_vtable = *reinterpret_cast<SD3DDeviceVTable **>(m_device);
SD3DDeviceVTable ist ein einfaches Struct, in dem die ganzen Memberfunktionen sind:
Code:
struct SD3DDeviceVTable
{
	CD3DDeviceFunction& operator[](DWORD index)
	{
		return reinterpret_cast<CD3DDeviceFunction *>(this)[index];
	}

#pragma pack(push, 1)
	union
	{
		CD3DDeviceFunction *QueryInterface_Raw; // 0
		HRESULT(__stdcall *QueryInterface)(LPDIRECT3DDEVICE9, REFIID, void**);
	};
        //usw...
};
Wenn ich jetzt mit DetourFunction eine Funktion hooke, funktioniert alles ohne Probleme:

Code:
nd::CD3DDevice d3d;
auto &present = d3d[PRESENT_INDEX];
present_orig = reinterpret_cast<Present_t>(DetourFunction(present, reinterpret_cast<PBYTE>(&present_hk)));
Wenn ich jetzt allerdings die Adresse direkt in der VTable verändern möchte, klappt das zwar auch, allerdings ruft das Zielprogramm meinen Hook nie auf. Wenn ich mit dem Debugger den Aufruf einer Funktion abfange & die VTable des Device Pointers anschaue, hat die eine ganz andere Adresse als die meines Device Pointers - somit schreibe ich zwar in gültigen Speicher, allerdings halt in welchen, der von keinem anderen Device verwendet wird.

Die VTable im Konstruktor von CD3DBase wird zwar gesetzt, allerdings später scheinbar nochmals ersetzt, somit hat es keinen Nutzen, einfach die VTable, die das Pattern zurückgibt, zu verwenden (bereits getestet).

Hat jemand eine Idee, weshalb es für das Interface mehrere VTables gibt, obwohl die Aufrufe von CreateDevice fast identisch sind?

Das Zielprogramm ist übrigens momentan ein von mir selbst geschriebenes, soll später natürlich aber auch bei anderen Programmen funktionieren.
03/23/2014 19:00 Tasiro#2
Quote:
Originally Posted by snow911 View Post
Die VTable im Konstruktor von CD3DBase wird zwar gesetzt, allerdings später scheinbar nochmals ersetzt, somit hat es keinen Nutzen, einfach die VTable, die das Pattern zurückgibt, zu verwenden (bereits getestet).
Speziell zu deinem Problem kann ich leider nichts sagen, aber normalerweise wird Vererbung mit virtuellen Funktionen von C++ - Compilern so realisiert:
1. In der Basisklasse wird der Zeiger auf die Tabelle virtueller Funktionen der Basisklasse gesetzt. Ich könnte ja im Konstruktor der Basisklasse eine solche virtuelle Funktion aufrufen.
2. In der abgeleiteten Klasse wird dieser Zeiger überschrieben. Es soll schließlich später die richtige virtuelle Funktion aufgerufen werden.
Der Destruktor arbeitet ähnlich, nur umgekehrt.
Ist CD3DBase eine Basisklasse?
03/23/2014 19:24 snow#3
Quote:
Originally Posted by Tasiro View Post
Speziell zu deinem Problem kann ich leider nichts sagen, aber normalerweise wird Vererbung mit virtuellen Funktionen von C++ - Compilern so realisiert:
1. In der Basisklasse wird der Zeiger auf die Tabelle virtueller Funktionen der Basisklasse gesetzt. Ich könnte ja im Konstruktor der Basisklasse eine solche virtuelle Funktion aufrufen.
2. In der abgeleiteten Klasse wird dieser Zeiger überschrieben. Es soll schließlich später die richtige virtuelle Funktion aufgerufen werden.
Der Destruktor arbeitet ähnlich, nur umgekehrt.
Ist CD3DBase eine Basisklasse?
Dass der Zeiger auf die Tabelle überschrieben wird, ist mir bewusst (und auch sinnvoll), mich wundert nur, dass das jetzt auf einmal der Fall ist, vor allem weil der "neue" Zeiger immer dynamisch allokiert wird & dann mit dem Inhalt der bestehenden Tabelle gefüllt wird:

Code:
67463FF8   . 8B0B           MOV ECX,DWORD PTR DS:[EBX]
67463FFA   . 898B 982D0000  MOV DWORD PTR DS:[EBX+0x2D98],ECX
67464000   . 8BB3 982D0000  MOV ESI,DWORD PTR DS:[EBX+0x2D98]
67464006   . 8DBB 9C2D0000  LEA EDI,DWORD PTR DS:[EBX+0x2D9C]
6746400C   . 893B           MOV DWORD PTR DS:[EBX],EDI
6746400E   . B9 AF000000    MOV ECX,0xAF
67464013   . F3:A5          REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
Das kann ja soweit durchaus Sinn machen, wenn die beiden Zeiger verschiedene Klassen hinter der Schnittstelle haben, aber dann frage ich mich, warum das so ist? Selbst wenn ich den Code zur Erzeugung eines Fensters für beide Devices verwende, erhalte ich verschiedene Adressen..

EDIT:
Okay, habe noch ein paar Dinge herausgefunden:
- die VTable wird in IDirect3DDevice9::BeginStateBlock zurückgesetzt, das liegt daran, dass ich innerhalb meines Hooks GetFVF / SetFVF verwende
- beide Devices (sowohl vom Zielprogramm als auch von der DLL) werden mit dem VTable-Zeiger in der Funktion CD3DHal::CD3DHal gesetzt
- die Zeiger werden in der Funktion CD3DBase::InitDevice überschrieben

Ich verstehe ehrlich gesagt immer noch nicht, warum das nicht funktioniert..
03/25/2014 17:19 Master674b#4
BeginStateBlock funktioniert so als Hook, schiebt sich zwischen alle Set* Methoden um einen StateBlock zu generieren mit den Werten die du setzen willst und lässt den Aufruf "nichts" tun. In EndStateBlock sollten die Hooks wieder entfernt werden.

DirectX einfach nicht über die VTable hooken und gut.