我一直在使用与Microsoft Detours相同的方法练习弯路(用jmp和地址替换前五个字节)。最近我通过修改虚拟表来阅读有关绕行的内容。如果有人能够通过提及与前面提到的方法相比这种方法的一些优点和缺点,我将不胜感激。[/ p>
我还想询问有关补丁的vtable和堆栈上的对象。请考虑以下情况:
// Class definition
struct Foo
{
virtual void Call(void) { std::cout << "FooCall\n"; }
};
// If it's GCC, 'this' is passed as the first parameter
void MyCall(Foo * object)
{
std::cout << "MyCall\n";
}
// In some function
Foo * foo = new Foo; // Allocated on the heap
Foo foo2; // Created on the stack
// Arguments: void ** vtable, uint offset, void * replacement
PatchVTable(*reinterpret_cast<void***>(foo), 0, MyCall);
// Call the methods
foo->Call(); // Outputs: 'MyCall'
foo2.Call(); // Outputs: 'FooCall'
在这种情况下,foo->Call()
会在MyCall(Foo * object)
调用原始函数(即foo2.Call()
方法)时调用Foo::Call(void)
。这是因为如果可能的话,编译器将尝试在编译期间决定任何虚拟调用(如果我错了,请纠正我)。这是否意味着如果您修补虚拟表是否无关紧要,只要您在堆栈上使用对象(而不是堆分配)?
答案 0 :(得分:2)
堆栈与堆无关紧要 - 重要的是编译器在编译时知道对象的类型。除非优化器非常聪明,否则以下内容可能会生成相同的结果:
Foo foo2; // Created on the stack
Foo * foo = &foo2; // Also on the stack, in fact the same object
由于已知foo2
的类型,编译器可以直接调用该函数而无需查看任何vtable。它不能对foo
执行相同的操作,因为指针也可以指向派生对象。
答案 1 :(得分:0)
当你使用foo2.call()时,编译器不应该使用vtable来确定你要调用哪个函数,它在类中调用函数而不调用vtable。