Break through C ++ virtual pointer - C ++ program buffer overflow attack

xiaoxiao2021-03-06  48

Backend Note: This article comes from the "Smashing C VPTRS" in Phrack56. As most foreign hackers, technical principles and applications are more detailed, but the source code provided seems to always have a problem. This may be because they feel that the readers should study and debug themselves to better master these technologies. Maybe I will do this later. ;)

test environment:

Operating system: Red Hat 6.1 (i386)

Nuclear version: kernel 2.2.14

Nuclear Patch: None Non-Executable Stack Patch (By Solar Design)

C compiler: GCC

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

So far, the buffer overflow program I have mastered is for C programming languages. Although C language programming is almost ubiquitous in UNIX systems, more and more C programs have begun. For most cases, C-language overflow technology is also applicable for C languages, but the object-oriented characteristics of C also results in new buffer overflow technologies. The following is analyzed in the X86 Linux system and the C GNU compiler.

--- [[Basis-Simple C Program] ------------------------------------ -

I don't want to waste time here to explain too much C language foundation. If you don't know anything about C or object-oriented programming technology, please find this book to see this. Before you continue to look down, please confirm that you have mastered or understand the following C terms:

1, Class (class)

2, Object (object)

3, Method (method)

4, Virtual (virtual)

5, inherit (inherited)

6, DeriVATIVE (derived)

Then, after reading the following two programs, confirm that you understand the meaning and role of each statement:

// bo1.cpp

// C basic procedure

#include

#include

Class myclass

{

Private:

Char buffer [32];

PUBLIC:

Void setBuffer (Char * String)

{

STRCPY (BUFFER, STRING);

}

Void PrintBuffer ()

{

Printf ("% s / n", buffer;

}

}

void main ()

{

Myclass object;

Object.setbuffer ("string");

Object.printbuffer ();

}

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

// bo2.cpp

/ / Common C program with buffer overflow vulnerabilities #include

#include

Class Baseclass

{

Private:

Char buffer [32];

PUBLIC:

Void setBuffer (Char * String)

{

STRCPY (Buffer, String); // There is a buffer overflow vulnerability

}

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 ();

}

The following is the resulting result after Bo2.cpp:

[Backend @ isbase test]> ./bo2

Myclass1: string1

Myclass2: String2

[Backend @ isbase test]>

Once again, when you continue to look down, you are sure that you read the above program, especially the object virtual method PrintBuffer (). Unlike the setBuffer () method, the PrintBuffer method must declare and implement in the base class baseclass derived type myclass1 and myclass2. This makes the setBuffer and PrintBuffer methods vary in runtime.

--- [[[C virtual pointer (Virtual Pointer, VPTR)] --------------------------------- -----

We know that a virtual method and a non-virtual method are that the call to the non-virtual method is determined at compile time (commonly referred to as "static binding"), but the call of the virtual method is determined when the program is determined ( Usually referred to as "dynamic binding"). The baseclass base class and its derived class in the above example are some explanations for dynamic binding mechanisms.

The compiler first checks the declaration of the BaseClass base class when compiling. In this case, the compiler first retains 32 bytes for private variables, followed by non-virtual method setBuffer () and specifies the corresponding call address (static binding process), and finally check the virtual method. When PrintBuffer (), dynamic binding processing will be done, ie, 4 bytes assigned in the class to store pointers for the virtual method. The structure is as follows:

Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv

Description: B variable buffer is occupied.

V Virtual method pointer is occupied.

This pointer is often referred to as "VPTR" (Virtual Pointer), which points to one of the function portions in a "vTable" structure. Every class has a vTable. As shown below: Object [0]: bbbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv

= ==

|

----------------------------

|

-> vtable_myclass1: iiiiiiiiiiiiipppp

Object [1]: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbwwwww

= ==

|

----------------------------

|

-> vtable_myclass2: iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiqqqq

Description: B variable buffer is occupied.

V Point VPTR pointer to vtable_myclass1.

W point to VPTR pointer to vtable_myclass2.

I other use data

P myclass1 object instance's address pointer.

Q Myclass2 object instance's address pointer of the printbuffer () method.

We can find that the VPTR is behind the Buffer variable in the process memory. That is, it is possible to overwrite the content of VPTR when calling the dangerous strcpy () function!

According to RIX research test, for Visual C 6.0 on the Windows platform, VPTR is located in the starting position of the object, so the technique mentioned here is unable to generate. This is very different from GNU C .

--- [[Analyzed VPTR] ----------------------------------------------------------------------------------------------------

It is also possible to use GDB under Linux:

[Backend @ isbase test]> gcc -o bo2 bo2.cpp

[Backend @ isbase test]> GDB BO2

GNU GDB 4.18

CopyRight 1998 Free Software Foundation, Inc.

GDB IS Free Software, Covered by the gnu general public license, and you are

Welcome to change IT and / or or distribute copies of it under certain conditions.

Type "Show Copying" to see the conditions.

There Is Absolutely No Warranty for GDB. Type "Show Warranty" for Details.

THIS GDB WAS Configured AS "i386-redhat-linux" ...

(GDB) Disassemble Main

Dump of assembler code for function main:

0x8049400

: push% EBP

0x8049401

: MOV% ESP,% EBP

0x8049403

: SUB $ 0x8,% ESP

0x8049406

: push% EDI

0x8049407

: push% ESI

0x8049408

: PUSH% EBX

0x8049409

: push $ 0x240x804940b
: Call 0x804b580 <__ builtin_new>

0x8049410

: Add $ 0x4,% ESP

0x8049413

: MOV% EAX,% EAX

0x8049415

: MOV% EAX,% EBX

0x8049417

: push% EBX

0x8049418

: Call 0x804c90c <__ 8myClass1>

0x804941D

: Add $ 0x4,% ESP

0x8049420

: MOV% EAX,% ESI

0x8049422

: JMP 0x8049430

0x8049424

: Call 0x8049c3c <__ throw>

0x8049429

: LEA 0x0 (% ESI, 1),% ESI

0x8049430

: MOV% ESI, 0xfffffff8 (% EBP)

0x8049433

: push $ 0x24

0x8049435

: Call 0x804b580 <__ builtin_new>

0x804943a

: Add $ 0x4,% ESP

0x804943D

: MOV% EAX,% EAX

0x804943f

: MOV% EAX,% ESI

0x8049441

: PUSH% ESI

0x8049442

: Call 0x804c8ec <__ 8myclass2>

0x8049447

: Add $ 0x4,% ESP

0x804944A

: MOV% EAX,% EDI

0x804944c

: jmp 0x8049455

0x804944e

: MOV% ESI,% ESI

0x8049450

: Call 0x8049c3c <__ throw>

0x8049455

: MOV% EDI, 0xfffffFFC (% EBP)

0x8049458

: push $ 0x804cda2

0x804945D

: MOV 0xfffffff8 (% EBP),% EAX

0x8049460

: push% EAX

0x8049461

: Call 0x804c930

0x8049466

: Add $ 0x8,% ESP

0x8049469

: Push $ 0x804cdaa --- type to continue, or q to quit ---

0x804946e

: MOV 0xfffffffc (% EBP),% EAX

0x8049471

: push% EAX

0x8049472

: Call 0x804c930

0x8049477

: Add $ 0x8,% ESP

0x804947a

: MOV 0xfffffff8 (% EBP),% EDX

0x804947D

: MOV 0x20 (% EDX),% EAX

0x8049480

: add $ 0x8,% EAX

0x8049483

: MOV 0xfffffff8 (% EBP),% EDX

0x8049486

: push% EDX

0x8049487

: MOV (% EAX),% EDI

0x8049489

: Call *% EDI

0x804948B

: Add $ 0x4,% ESP

0x804948e

: MOV 0xfffffffc (% EBP),% EDX

0x8049491

: MOV 0x20 (% EDX),% EAX

0x8049494

: add $ 0x8,% EAX

0x8049497

: MOV 0xffffffc (% EBP),% EDX

0x804949a

: push% edx

0x804949B

: MOV (% EAX),% EDI

0x804949d

: Call *% EDI

0x804949f

: Add $ 0x4,% ESP

0x80494a2

: xor% EAX,% EAX

0x80494A4

: jmp 0x80494d0

0x80494A6

: jmp 0x80494d0

0x80494A8

: push% EBX

0x80494A9

: Call 0x804b4f0 <__ builtin_delete>

0x80494ae

: Add $ 0x4,% ESP

0x80494B1

: jmp 0x8049424

0x80494B6

: push% ESI

0x80494B7

: CALL 0x804B4F0 <__ builtin_delete>

0x80494BC

: add $ 0x4,% ESP0X80494BF
: jmp 0x8049450

0x80494C1

: jmp 0x80494c8

0x80494c3

: Call 0x8049c3c <__ throw>

0x80494c8

: Call 0x8049fc0

0x80494cd

: Lea 0x0 (% ESI),% ESI

0x80494d0

: LEA 0xffffffec (% EBP),% ESP

0x80494d3

: POP% EBX

0x80494d4

: POP% ESI

0x80494d5

: POP% EDI

--- Type To Continue, Or Q To Quit ---

0x80494d6

: Leave

0x80494d7

: RET

0x80494d8

: NOP

0x80494d9

: NOP

0x80494DA

: NOP

0x80494db

: NOP

0x80494dc

: NOP

0x80494dd

: NOP

0x80494de

: NOP

0x80494df

: NOP

End of assembler dump.

(GDB)

The following is an explanation for assembly code for the program:

0x8049400

: push% EBP

0x8049401

: MOV% ESP,% EBP

0x8049403

: SUB $ 0x8,% ESP

0x8049406

: push% EDI

0x8049407

: push% ESI

0x8049408

: PUSH% EBX

Build a stack. To the Object [] array (ie, two 4-byte pointer addresses), the pointer of Object [0] is stored in 0xfffffff8 (% EBP), the pointer of Object [1] is stored at 0FFFFFFFC (% EBP). The register is then saved.

0x8049409

: push $ 0x24

0x804940B

: Call 0x804b580 <__ builtin_new>

0x8049410

: Add $ 0x4,% ESP

First call __builtin_new, assign 0x24 (36 bytes) to Object [0] in the heap (HEAP), and save its first address to the EAX register. The 32-byte of this 36 bytes is buffer variable, and the last 4 bytes are occupied by VPTR. 0x8049413

: MOV% EAX,% EAX

0x8049415

: MOV% EAX,% EBX

0x8049417

: push% EBX

0x8049418

: Call 0x804c90c <__ 8myClass1>

0x804941D

: Add $ 0x4,% ESP

Stack the first address of the object and call the __8myclass1 function. This is actually a constructor for MyClass1 objects.

(GDB) disassemble __8myclass1

Dump of assembler code for function __8myclass1:

0x804c90c <__ 8myclass1>: push% EBP

0x804c90d <__ 8myclass1 1>: MOV% ESP,% EBP

0x804c90f <__ 8myclass1 3>: push% EBX

0x804c910 <__ 8myclass1 4>: MOV 0x8 (% EBP),% EBX

Register EBX now stores a 36-byte pointer to allocated (in the C language, called "this" pointer).

0x804c913 <__8myclass1 7>: push% EBX

0x804c914 <__8myclass1 8>: Call 0x804c958 <__ 9baseclass>

0x804c919 <__8myclass1 13>: add $ 0x4,% ESP

First call the constructor of base class baseclass.

(GDB) Disassemble __9baseclass

Dump of assembler code for function __9baseclass:

0x804c958 <__ 9baseclass>: push% EBP

0x804c959 <__ 9baseclass 1>: MOV% ESP,% EBP

0x804c95b <__ 9baseclass 3>: MOV 0x8 (% EBP),% EDX

Register EDX now stores a 36-byte pointer to allocated ("this" pointer).

0x804c95e <__ 9baseclass 6>: MOVL $ 0x804e01c, 0x20 (% EDX)

Store 0x804e01c to EDX 0x20 (= EDX 32). Let's take a look at the 0x804E01C address memory data:

(GDB) x 0x804e01c

0x804e01c <__ vt_9baseclass>: 0x00000000

You can see that this address stored in EDX 0x20 (i.e., the VPTR position of the object) is the vTable address of the base class baseclass.

Now return to the constructor of myclass1 object:

0x804C91C <__ 8MYCLASS1 16>: MOVL $ 0x804e010, 0x20 (% EBX) stores 0x804e010 to EBX 0x20 (ie VPTR). Also let us see the 0x804e010 address memory data:

(GDB) x 0x804e010

0x804e010 <__ vt_8myclass1>: 0x00000000

Now, we know that VPTR has been rewritten, and then it is the VTABLE address of myclass1 object in its content. When returning to the main () function, the register EAX stores the pointer in the memory.

0x8049420

: MOV% EAX,% ESI

0x8049422

: JMP 0x8049430

0x8049424

: Call 0x8049c3c <__ throw>

0x8049429

: LEA 0x0 (% ESI, 1),% ESI

0x8049430

: MOV% ESI, 0xfffffff8 (% EBP)

Get the resulting address pointer to Object [0]. Then the program performs the same processing on Object [1], but the return of the return is different. After the above object initialization is initialized, the following instructions are executed:

0x8049458

: push $ 0x804cda2

0x804945D

: MOV 0xfffffff8 (% EBP),% EAX

0x8049460

: push% EAX

Stack 0x804cda2 and Object [0]. Observe the contents of 0x804cda2:

(GDB) x / s 0x804cda2

0x804cda2 <_io_stdin_used 30>: "String1"

It is understood that the address stores the setBuffer function that will pass the base class baseclass to the string "string1" "in the buffer.

0x8049461

: Call 0x804c930

0x8049466

: Add $ 0x8,% ESP

Call the setBuffer () method of the base class baseclass. Note that the call to this setBuffer method is "static binding" (because it is not a virtual method). The same is true for Object [1].

In order to verify that these two objects are properly initialized, we will set the following breakpoints:

0x8049410: Get the address of the first object.

0x804943A: Get the address of the second object.

0x804947A: Whether the initialization of the test object is correct.

(GDB) BREAK * 0x8049410

Breakpoint 1 at 0x8049410

(GDB) BREAK * 0x804943A

Breakpoint 2 AT 0x804943A

(GDB) BREAK * 0x804947A

Breakpoint 3 AT 0x804947A

Run this program now:

ST

转载请注明原文地址:https://www.9cbs.com/read-86857.html

New Post(0)