1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
| /* VTableInfo.h */
#ifndef __VTableInfo_h
#define __VTableInfo_h
//
// The function GetVTableInfo returns, for a given virtual method of a class,
// in which vtable it can be found, and at which index. The OLE function
// CreateDispTypeInfo requires this index in order to expose a dispatch
// method.
//
// Note that this is a serious *hack*, which is intended to function only
// with Microsoft Visual C++ version 13.00.9466 (shipped with MS Visual
// Studio.NET, use cl /? to get this version number). It relies heavily on
// the assembly code generated for pointers to virtual functions, and may
// therefore not work with other versions of this compiler.
//
// In short, whenever a pointer to a virtual method is taken, it will not
// refer to that method's address, but rather to a piece of generated code
// that is known as 'vcall'. For each virtual method pointer, such a 'vcall'
// is generated.
// Hence, when this method pointer is used to execute that method, it will
// call the 'vcall' code. This 'vcall' takes an object's instance pointer,
// and uses this object's vtable to call the method's implementation. A
// 'vcall' consists of 3 assembly instructions (two MOV's and one JMP) to
// accomplish this.
//
// GetVTableInfo works by using such a virtual method pointer to locate its
// related 'vcall' code, and extracting the vtable nr from this pointer and
// the vtable index used directly from the assembly code.
//
// Leon Bouquiet.
//
namespace VTableTools{
/**
* Defines in which vtable, and at which index in this vtable a virtual
* method can be found.
*/
struct VTableInfoType{
/** The vtable nr. used (usually 0, can be higher when multiple
* inheritance is used. */
int m_iSelector;
/** The index within this vtable which holds the virtual method's address
*/
int m_iIndex;
};
/**
* Possible error codes for GetVTableInfo(), see docs for background info.
*/
enum VTableErrorType{
/** No errors, the vtable info was successfully extracted. */
VTE_NO_ERROR,
/** The supplied function pointer is NULL.*/
VTE_INVALID_FUNCPTR,
/** Couldn't locate the JMP instruction to the 'vcall' code (returned in
* debug builds only).*/
VTE_NO_LONGJMP_INSTR,
/** The 1st instruction in the 'vcall' code didn't match. */
VTE_NO_GET_INSTANCE_INSTR,
/** The 2nd instruction in the 'vcall' code didn't match. */
VTE_NO_GET_VTABLE_INSTR,
/** The 3rd instruction in the 'vcall' code didn't match. */
VTE_NO_VTABLE_CALL_INSTR
};
/**
* Convenience macro that requires only the class and method name to extract
* the vtable info. For MyClass::MyVirtMethod, use it like this:
* VTableErrorType err = GET_VTABLE_INFO(MyClass, MyVirtMethod, &vtableInfo)
*/
#define GET_VTABLE_INFO(ClassName, MethodName, pInfo) \
GetVTableInfo<ClassName>( (void (__stdcall ClassName::*)())ClassName::MethodName, \
pInfo)
/**
* Templated function that fills a VTableInfoType struct with the given
* virtual method's vtable position.
*
* @param pfn Pointer to a virtual method (must be __stdcall)
* @param pInfo On return, holds the method's vtable number and index.
* @return VTE_NO_ERROR, or an VTableErrorType code on failure.
*/
template<class C>
VTableErrorType GetVTableInfo(void (__stdcall C::*pfn)(), VTableInfoType *pInfo){
//For a given C *pInstance, the passed method would be called as:
//((*pInstance).*(pfn))();
//Clear the returned VTablePosType struct and verify the passed function pointer
pInfo->m_iSelector = 0;
pInfo->m_iIndex = -1;
if(pfn == NULL)
return VTE_INVALID_FUNCPTR;
//Step 1. Get the address of pfn (i.e. &pfn) as an int *, so we can work
//with it. Since the compiler doesn't allow us to cast these types, pass
//pfn through the eax register, so the compiler can't type-check it.
unsigned int *piFn = NULL;
__asm{
push eax;
lea eax, pfn;
mov dword ptr[piFn], eax;
pop eax;
};
//If the passed method pointer is 4 bytes in size, it only stores a
//pointer. However, when a method pointer of a class with multiple base
//classes is passed, it will be 8 bytes; the last 4 bytes then hold the
//vtable selector.
if(sizeof(pfn) == 8){
//Extract the vtable selector (stored as an offset, so divide by 4)
pInfo->m_iSelector = piFn[1] >> 2;
}
//Find the generated 'vcall' code. When compiled in debug mode, piFn[0]
//points to a long jump that jumps to the vcall code, while in release
//mode, piFn[0] points _directly_ to the 'vcall' code.
unsigned char *pVCall;
#ifndef NDEBUG //Debug mode
//Step 2a: piFn[0] points to a 5-byte opcode, representing a long jump.
//Verify that this is indeed a long jmp (0xE9), and get its offset
//(last 4 bytes)
unsigned char *pbJmp = (unsigned char *)piFn[0];
if(pbJmp[0] != 0xE9)
return VTE_NO_LONGJMP_INSTR;
int iJmpOffset = *((int *)(pbJmp + 1));
//Step 2b: locate the generated 'vcall' code, by adding the iJmpOffset to
//the address of the JMP instruction, plus the instruction length (5 bytes)
pVCall = &((pbJmp)[5 + iJmpOffset]);
#else //Release mode
pVCall = *((unsigned char **)piFn);
#endif
//Step 3: verify the 'vcall' opcodes at *pVCall
//First instruction should get object instance pointer from stack, coded as
//"mov eax, dword ptr[esp, 4]" (opcode: 8B 44 24 04)
if(!((pVCall[0] == 0x8B) && (pVCall[1] == 0x44) && (pVCall[2] == 0x24) && (pVCall[3] == 0x04)))
return VTE_NO_GET_INSTANCE_INSTR;
//Second instruction dereferences this instance pointer with an added
//selector value to select the proper vtable. This is coded as:
//"mov eax, dword ptr[eax + selector]" (opcode: 8B selector)
if(pVCall[4] != 0x8B)
return VTE_NO_GET_VTABLE_INSTR;
//Third instruction simulates the virtual function call through a jmp. It
//can be coded either with or without offset, i.e.
// "jmp dword ptr[eax + vtbl_index]" (opcodes: FF 60 vtbl_index) or
// "jmp dword ptr[eax]" (opcodes: FF 20, implies offset=0)
if(!((pVCall[6] == 0xFF) && ((pVCall[7] == 0x60) || (pVCall[7] == 0x20))))
return VTE_NO_VTABLE_CALL_INSTR;
//Step 4: extract the index, if present. Because this value is extracted
//as a direct offset rather than an index, divide by 4
if(pVCall[7] == 0x60)
pInfo->m_iIndex = (pVCall[8] >> 2);
else
pInfo->m_iIndex = 0;
return VTE_NO_ERROR;
}
}; //End namespace VTableTools
#endif //__VTableInfo_h |