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

zhaozj2021-02-08  291

Author: rix (rix@securiweb.net)

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 Core Patch: None Non-Executable Stack Patch (By Solar Design) C Compiler: GCC --- [Preface]] - ------------------------------------- to the current, the buffer overflower 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, then read the following two programs, confirm that you understand the meaning and effect of each statement: // bo1.cpp // C foundation program #include #include Class myclass {private: char buffer [32]; public: void setbuffer {structure (buffer, string);} void printbuffer () {Printf ("% s / n", buffer;}}; void Main () {Myclass Object; Object.setBuffer ("string"); object.printbuffer ();} =========================== ============================================================================ 有 常 常 常 常 #include class baseclass {private: char buffer [32]; public: void setbuffer {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 pri NTBuffer () {Printf ("MyClass2:"); BaseClass :: PrintBuffer ();}}; void main () {baseclass * object [2]; object [0] = new myclass1; object [1] = new myclass2; oj Object [0] -> setBuffer ("string1"); object [1] -> setBuffer ("string2"); object [0] -> printbuffer ();

Object [1] -> PrintBuffer ();} The following is BO2.CPP compiled run results: [Backend @ isbase test]> ./bo2myclass1: string1myclass2: string2 [backnd @ isbase test]> Retreat again, continue When you look down, I am sure that you read the above procedures, 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 when compiling (commonly referred to as "static binding"), but the call of the virtual method is The program is determined (commonly 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 in the figure below: Object [0]: bbbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv = == | ---------------------------- | -> vtable_myclass1: iiiiiiiiiiiippppppobject [1]: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbwwww = == | ------------------------------ | -> vtable_myclass2: iiiiiiiiiiiiiiiiqqqq Description: B variable buffer occupied. V Point VPTR pointer to vtable_myclass1. W point to VPTR pointer to vtable_myclass2. I Other Use of data p myclass1 object instance's address pointer'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] -------------------------------------- under Linux Of course, use GDB to analyze: [Backend @ isbase test]> gcc -o bo2 bo2.cpp [Backend @ isbase test]> GDB bo2GNu gdb 4.18copyright 1998 Free Software Foundation, Inc.gdb is Free Software, Covered by the gnu General Public License, and you arewelcome to change it and / or distribute copies of it under certain conditions.Type "show copying" to see the conditions.There is absolutely no warranty for GDB. Type "show warranty" for details.This GDB was Configured as "i386-redhat-linux" ... (GDB) Disassemble MAINDUMP OF Assusser code for function main: 0x8049400

: push% EBP0X8049401
: MOV% ESP,% EBP0X8049403
: SUB $ 0x8,% ESP0X8049406
: push% EDI0X8049407
: push% esi0x8049408
: push% EBX0X8049409
: push $ 0x240x804940b
: call 0x804b580 <__ builtin_new> 0x8049410
: add $ 0x4,% ESP0X8049413
: MOV% EAX,% EAX0X8049415
: MOV% EAX,% EBX0X8049417
: P USH% EBX0X8049418
: Call 0x804c90c <__ 8myclass1> 0x804941d
: add $ 0x4,% ESP0X8049420
: MOV% Eax,% ESI0X8049422
: jmp 0x8049430
0x8049424
: Call 0x8049c3c <__ throw> 0x8049429
: LEA 0x0 (% ESI, 1),% ESI0X8049430
: MOV% ESI, 0xFffffff8 (% EBP) 0x8049433
: push $ 0x240x8049435
: Call 0x804b580 <__ builtin_new> 0x804943a
: add $ 0x4,% ESP0X804943D <

Main 61>: MOV% EAX,% EAX0X804943F

: MOV% EAX,% ESI0X8049441
: push% ESI0X8049442
: Call 0x804c8ec <__ 8myclass2> 0x8049447
: Add $ 0x4,% ESP0X804944A
: MOV% EAX,% EDI0X804944C
: jmp 0x8049455
0x804944e
: MOV% ESI,% ESI0x8049450
: call 0x8049c3c <__ throw> 0x8049455
: mov% edi, 0xfffffffc (% ebp) 0x8049458
: push $ 0x804cda20x804945d
: mov 0xfffffff8 (% ebp),% eax0x8049460
: push% eax0x8049461
: call 0x804c930 0x8049466
: add $ 0x8,% esp0x8049469
: push $ 0x804cdaa --- Type to continue, or q to quit --- 0x804946e
: mov 0xfffffffc (% ebp),% eax0x8049471
: push% eax0x8049472
: call 0x804c930 0x8049477
: Add $ 0x8,% ESP0X804947A
: MOV 0xfffffff8 (% EBP),% EDX0X804947D
: MOV 0x20 (% EDX),% EAX0X8049480
:: Add $ 0x8,% EAX0X8049483
: MOV 0xfffffff8 (% EBP),% EDX0X8049486
: PUSH% EDX0X8049487
: MOV (% EAX),% EDI0X8049489
: Call *% EDI0X804948B
: add $ 0x4,% ESP0X804948E
: MOV 0xfffffffc (% EBP),% EDX0X8049491
: MOV 0x20 (% EDX),% EAX0X8049494
: Add $ 0x8,% EAX0X8049497

: MOV 0xffffffc (% EBP),% EDX0X804949A

: push% EDX0X804949B
: MOV (% ED),% EDI0X804949D
: Call *% EDI0X804949F
: add $ 0X4,% ESP0X80494A2
: xor% EAX,% EAX0X80494A4
: jmp 0x80494d0
0x80494a6
: jmp 0x80494d0
0x80494a8
: push% ebx0x80494a9
: call 0x804b4f0 <__ builtin_delete> 0x80494ae
: add $ 0x4,% esp0x80494b1
: jmp 0x8049424
0x80494b6
: push% esi0x80494b7
: call 0x804b4f0 <__ builtin_delete> 0x80494bc
: add $ 0x4,% esp0x80494bf
: jmp 0x8049450
0x80494c1
: jmp 0x80494c8
0x80494c3
: call 0x8049c3c <__ throw> 0x80494c8
: call 0x8049fc0 0x80494cd
: lea 0x0 (% esi),% esi0x80494d0
: lea 0xffffffec (% EBP),% ESP0X80494D3
: POP% EBX0X80494D4
: pop% ESI0X80494D5
: 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% EBP0X8049401
: MOV% ESP,% EBP0X8049403
: SUB $ 0x8, % ESP0X8049406
: push% EDI0X8049407
: push% esi0x8049408
: PUSH% EBX build 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 $ 0x240x804940b
: Call 0x804b580 <__ builtin_new> 0x8049410
: add $ 0x4,% ESP first call __builtin_new, assign 0x24 in heap (HEAP) (36 words Section) Give Object [0] 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,% EAX0X8049415
: MOV% EAX,% EBX0X8049417
: push% EBX0X8049418
: Call 0x804c90c <__ 8myclass1> 0x804941d
: Add $ 0x4,% ESP puts the first address of the object, and then calls the __8myclass1 function. This is actually a constructor for MyClass1 objects. (Gdb) disassemble __8MyClass1Dump of assembler code for function __8MyClass1: 0x804c90c <__ 8MyClass1>: push% ebp0x804c90d <__ 8MyClass1 1>: mov% esp,% ebp0x804c90f <__ 8MyClass1 3>: push% ebx0x804c910 <__ 8MyClass1 4>: mov 0x8 ( % EBP),% EBX register EBX now stores a 36-byte pointer that is allocated (in the C language, called "this" pointer). 0x804c913 <__8myclass1 7>: push% EBX0X804C914 <__ 8myClass1 8>: Call 0x804c958 <__ 9baseclass> 0x804c919 <__ 8myClass1 13>: add $ 0x4,% ESP first call the base class baseclass constructor.

(Gdb) disassemble __9BaseClassDump of assembler code for function __9BaseClass: 0x804c958 <__ 9BaseClass>: push% ebp0x804c959 <__ 9BaseClass 1>: mov% esp,% ebp0x804c95b <__ 9BaseClass 3>: mov 0x8 (% ebp),% edx register EDX now Sticker points to allocated 36 bytes of a pointer ("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 0x804e01c0x804e01c <__ vt_9baseclass>: 0x00000000 You can see the address stored to EDX 0x20 (ie the VPTR position of the object) is a vTable address of base class baseclass. Now return to the constructor of MyClass1 object: 0x804c91c <__ 8myclass1 16>: MOVL $ 0x804e010, 0x20 (% EBX) stores the 0x804e010 to EBX 0x20 (ie VPTR). Similarly let's take a look at the 0x804E010 address memory data: (GDB) x 0x804e0100x804e010 <__ vt_8myclass1>: 0x00000000 Now we know that VPTR is rewritten, then its content is a VTable address of MyClass1 object. When returning to the main () function, the register EAX stores the pointer in the memory. 0x8049420

: MOV% EAX,% ESI0X8049422
: jmp 0x8049430
0x8049424
: Call 0x8049c3c <__ throw> 0x8049429
: lea 0x0 (% ESI , 1),% ESI0X8049430
: 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 is initialized, the following instructions will be executed: 0x8049458
: push $ 0x804cda20x804945d
: MOV 0xfffffffff8 (% EBP),% EAX0X8049460
: push% EAX will 0x804cda2 and The value of Object [0]. Observe the contents of 0x804cda2: (GDB) X / S 0x804cda20x804cda2 <_io_stdin_used 30>: "String1" can beware that the address is stored in a string "string1" that will be copied in the buffer through the base class baseclass.

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

New Post(0)