您的位置:首页 > 编程语言 > C语言/C++

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

2007-10-27 07:46 393 查看
2000年12月21日 15:43:00

作者:rix (rix@securiweb.net)

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]-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

  现在运行这个程序:

St

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=3278
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: