如何找到vtable的位置?

逆向工程 调试器 C++ 虚表
2021-06-12 06:49:37

实际上,我正在尝试了解一些有关 vtable 溢出的知识。所以,我的学习文件说明如下:

要实现的要点是,每当我们声明一个带有虚方法的 C++ 类时,它所在的内存池(堆、堆栈等)现在都包含一个指向函数指针表的指针,该指针表最终将用于调用功能。如果发生溢出,我们可以覆盖这个指针值,以便下次调用虚拟方法时调用我们的代码。

所以,我的问题是,如何找到 vtable 指针的位置?

我的意思是,我是否必须像尝试从某些模块中查找基址一样搜索 PEB。或者,这是针对每种情况的吗?

1个回答

这是依赖于编译器的——编译器可以将 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 在你的程序启动时被预先初始化,并且(通常)永远不会改变。要编写漏洞利用程序,您可以:

  1. 使用缓冲区溢出来修改 vtable 中的函数指针,因此下次调用类方法时,将执行您的代码
  2. 使用缓冲区溢出来修改类实例的 vtable 指针,因此下次此类实例执行一个方法时,将使用您的 vtable 而不是另一个。

由于 vtables 通常不会改变,甚至可能被编译器放置在只读内存段中,因此您的正常漏洞利用会忽略 1. 并使用 2。