实际上,我正在尝试了解一些有关 vtable 溢出的知识。所以,我的学习文件说明如下:
要实现的要点是,每当我们声明一个带有虚方法的 C++ 类时,它所在的内存池(堆、堆栈等)现在都包含一个指向函数指针表的指针,该指针表最终将用于调用功能。如果发生溢出,我们可以覆盖这个指针值,以便下次调用虚拟方法时调用我们的代码。
所以,我的问题是,如何找到 vtable 指针的位置?
我的意思是,我是否必须像尝试从某些模块中查找基址一样搜索 PEB。或者,这是针对每种情况的吗?
实际上,我正在尝试了解一些有关 vtable 溢出的知识。所以,我的学习文件说明如下:
要实现的要点是,每当我们声明一个带有虚方法的 C++ 类时,它所在的内存池(堆、堆栈等)现在都包含一个指向函数指针表的指针,该指针表最终将用于调用功能。如果发生溢出,我们可以覆盖这个指针值,以便下次调用虚拟方法时调用我们的代码。
所以,我的问题是,如何找到 vtable 指针的位置?
我的意思是,我是否必须像尝试从某些模块中查找基址一样搜索 PEB。或者,这是针对每种情况的吗?
这是依赖于编译器的——编译器可以将 vtable 放置在它想要的任何地方,只要它始终如一地执行。但是,在大多数情况下,vtable 指针是生成结构的第一个元素(偏移量为 0)。
class test {
int a;
int b;
test() { ...; }
~test() { ...; }
void somefunc() { ...; }
int c;
}
将为该类使用此内存布局:
+----------------+ +--------------+
| vtable | ------------> | test |
+----------------+ +--------------+
| a | | ~test |
+----------------+ +--------------+
| b | | somefunc |
+----------------+ +--------------+
| c |
+----------------+
所以(假设指针和整数都是 4 个字节),vtable 的偏移量为 0,a 位于 4,b 位于 8,c 位于 12。
请注意,并非所有编译器都使用此约定。例如,Watcom C++ 386 编译器根本不使用 vtable,而是将函数指针与数据混合在一起。(我知道这个案例是因为我曾经反汇编了一个 20 年前用 Watcom 编译的游戏。并不是说我希望你在现代编译器中看到这种布局,只是为了提供一个例子,它可以是不同的):
+----------------+
| test |
+----------------+
| ~test |
+----------------+
| a |
+----------------+
| b |
+----------------+
| somefunc |
+----------------+
| c |
+----------------+
偏移量 0 和 4 处的条目(再次假设 4 字节整数/指针)是类的无参数构造函数和析构函数,其余部分是变量和方法的混合,按照它们在类定义中出现的顺序。当然,这是非常低效的,因为每当一个对象被实例化时,编译器必须初始化每个类方法,而不是仅仅设置一个指向 vtable 的指针。
TL;DR:在大多数情况下,vtable 指针是类结构的第一个元素,但您确实需要知道使用了哪个编译器以及该编译器具有哪些约定。
另一件事-您在原始帖子中谈到了“vtable 溢出”。您的“正常”利用不会溢出 vtable;vtables 在你的程序启动时被预先初始化,并且(通常)永远不会改变。要编写漏洞利用程序,您可以:
由于 vtables 通常不会改变,甚至可能被编译器放置在只读内存段中,因此您的正常漏洞利用会忽略 1. 并使用 2。