作者: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]->SetBuffer("string1"); Object[1]->SetBuffer("string2"); Object[0]->PrintBuffer(); Object[1]->PrintBuffer();} 以下是bo2.cpp编译后的运行结果:[backend@isbase test]> ./bo2MyClass1: string1MyClass2: 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: IIIIIIIIIIIIPPPPObject[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 bo2GNU gdb 4.18Copyright 1998 Free Software Foundation, Inc.GDB is free software, covered by the GNU General Public License, and you arewelcome 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 mainDump of assembler code for function main:0x8049400 <main>: push