嵌入式學習資源——突破C++的虛擬指標-C++程式的緩衝區溢位攻擊
backend
注:本文來自
Phrack56
期的《
SMASHING C++ VPTRS
》。正如大多數國外
的文章,技術原理及應用都講得比較詳細,但所提供的原始碼似乎總是會存在不大不小的問題。這也許是因為他們覺得應該讓讀者自己去研究和除錯,以更好地掌握這些技術。或許以後我也會這樣做。;)
測試環境:
:
Red Hat 6.1 (i386)
核心版本:
Kernel 2.2.14
核心補丁:
None
Non-executable stack patch (by Solar Design)
C++
編譯器:
gcc
---[[
前言
]]--------------------------------------
到目前為止,我所掌握的緩衝區溢位程式都是針對
C
程式設計
語言的。雖然
C
語言程式設計在
UNIX
系統中幾乎無處不在,但越來越多的
C++
程式也開始出現了。對於大多數情況,
C
語言的溢位技術對於
C++
語言也是適用的,但
C++
的物件導向的特性也導致了新的緩衝區溢位技術。下面以
x86
系統和
C++ GNU
編譯器為平臺進行分析。
---[[
基礎--簡單的
C++
程式
]]--------------------------------------
我不願在這裡浪費時間講解太多的
C++
語言基礎。如果你對
C++
或物件導向程式設計技術一無所知,請先找本這方面的書籍看看。在繼續往下看之前,請確認你已經掌握或瞭解以下
C++
術語:
1
、
Class
(類)
2
、
Object
(物件)
3
、
Method
(方法)
4
、
Virtual
(虛擬)
5
、
Inherit
(繼承)
6
、
Derivative
(派生)
接著,把下面的兩個程式看完,確認你瞭解每條語句的含義和作用:
// bo1.cpp
// C++
基礎程式
#include <stdio.h>
#include <string.h>
class MyClass
{
private:
char Buffer[32];
public:
void SetBuffer(char *String)
{
strcpy(Buffer, String);
}
void PrintBuffer()
{
printf("%s\n", Buffer);
}
};
void main()
{
MyClass Object;
Object.SetBuffer("string");
Object.PrintBuffer();
}
===========================================================
// bo2.cpp
//
有緩衝區溢位漏洞的常見
C++
程式
#include <stdio.h>
#include <string.h>
class BaseClass
{
private:
char Buffer[32];
public:
void SetBuffer(char *String)
{
strcpy(Buffer,String);
//
存在緩衝區溢位漏洞
}
virtual void PrintBuffer()
{
printf("%s\n",Buffer);
}
};
class MyClass1:public BaseClass
{
public:
void PrintBuffer()
{
printf("MyClass1: ");
BaseClass::PrintBuffer();
}
};
class MyClass2:public BaseClass
{
public:
void PrintBuffer()
{
printf("MyClass2: ");
BaseClass::PrintBuffer();
}
};
void main()
{
BaseClass *Object[2];
Object[0] = new MyClass1;
Object[1] = new MyClass2;
Object[0]->SetBuffer("string1");
Object[1]->SetBuffer("string2");
Object[0]->PrintBuffer();
Object[1]->PrintBuffer();
}
以下是
bo2.cpp
編譯後的執行結果:
[backend@isbase test]> ./bo2
MyClass1: string1
MyClass2: string2
[backend@isbase test]>
再一次提醒,在繼續往下看時,確信你讀懂了上面的程式,特別是物件虛擬(
virtual
)方法
PrintBuffer()
。與
SetBuffer()
方法不同,
PrintBuffer
方法必須在基類
BaseClass
的派生類
MyClass1
和
MyClass2
中宣告並實現。這使得
SetBuffer
與
PrintBuffer
方法在執行時的處理會有所不同。
我們知道,虛擬方法與非虛擬方法的一個不同之處是,非虛擬方法的呼叫是在編譯時確定(通常稱為
“
靜態繫結
”
),而虛擬方法的呼叫卻是在程式時確定的(通常稱為
“
動態繫結
”
)。下面以上例中的
BaseClass
基類及其派生類為例,對動態繫結的機制做一些解釋。
編譯器在編譯時首先檢查
BaseClass
基類的宣告。在本例,編譯器首先為私有變數
Buffer
(字串型)保留
32
個位元組,接著為非虛擬方法
SetBuffer()
計算並指定相應的呼叫地址(靜態繫結處理),最後在檢查到虛擬方法
PrintBuffer()
時,將做動態繫結處理,即在類中分配
4
個位元組用以存放該虛擬方法的指標。結構如下:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV
說明:
B
變數
Buffer
佔用。
V
虛擬方法指標佔用。
這個指標通常被稱為
“VPTR”
(
Virtual Pointer
),它指向一個
“VTABLE”
結構中的函式入口之一。每一個類都有一個
VTABLE
。如下圖所示:
Object[0]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV
=+==
|
+------------------------------+
|
+--> VTABLE_MyClass1: IIIIIIIIIIIIPPPP
Object[1]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBWWWW
=+==
|
+------------------------------+
|
+--> VTABLE_MyClass2: IIIIIIIIIIII
QQ
說明:
B
變數
Buffer
佔用。
V
指向
VTABLE_MyClass1
的
VPTR
指標佔用。
W
指向
VTABLE_MyClass2
的
VPTR
指標佔用。
I
其它用途的資料
P MyClass1
物件例項的
PrintBuffer()
方法的地址指標。
Q MyClass2
物件例項的
PrintBuffer()
方法的地址指標。
我們可以發現,
VPTR
位於程式
中
Buffer
變數之後。即當呼叫危險的
strcpy()
函式時有可能覆蓋
VPTR
的內容!
根據
rix
的研究測試,對於
Windows
平臺上的
Visual C++
6.0
,
VPTR
位於物件的起始位置,因此這裡提到的技術無法產生作用。這點與
GNU
C++
有很大的不同。
---[[
剖析
VPTR ]]--------------------------------------
在
下當然是使用
GDB
來分析了:
[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 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 <main>:
push
%ebp
0x8049401 <main+1>:
mov
%esp,%ebp
0x8049403 <main+3>:
sub
$0x8,%esp
0x8049406 <main+6>:
push
%edi
0x8049407 <main+7>:
push
%esi
0x8049408 <main+8>:
push
%ebx
0x8049409 <main+9>:
push
$0x24
0x804940b <main+11>:
call
0x804b580 <__builtin_new>
0x8049410 <main+16>:
add
$0x4,%esp
0x8049413 <main+19>:
mov
%eax,%eax
0x8049415 <main+21>:
mov
%eax,%ebx
0x8049417 <main+23>:
push
%ebx
0x8049418 <main+24>:
call
0x804c90c <__8MyClass1>
0x804941d <main+29>:
add
$0x4,%esp
0x8049420 <main+32>:
mov
%eax,%esi
0x8049422 <main+34>:
jmp
0x8049430 <main+48>
0x8049424 <main+36>:
call
0x8049c3c <__throw>
0x8049429 <main+41>:
lea
0x0(%esi,1),%esi
0x8049430 <main+48>:
mov
%esi,0xfffffff8(%ebp)
0x8049433 <main+51>:
push
$0x24
0x8049435 <main+53>:
call
0x804b580 <__builtin_new>
0x804943a <main+58>:
add
$0x4,%esp
0x804943d <main+61>:
mov
%eax,%eax
0x804943f <main+63>:
mov
%eax,%esi
0x8049441 <main+65>:
push
%esi
0x8049442 <main+66>:
call
0x804c8ec <__8MyClass2>
0x8049447 <main+71>:
add
$0x4,%esp
0x804944a <main+74>:
mov
%eax,%edi
0x804944c <main+76>:
jmp
0x8049455 <main+85>
0x804944e <main+78>:
mov
%esi,%esi
0x8049450 <main+80>:
call
0x8049c3c <__throw>
0x8049455 <main+85>:
mov
%edi,0xfffffffc(%ebp)
0x8049458 <main+88>:
push
$0x804cda2
0x804945d <main+93>:
mov
0xfffffff8(%ebp),%eax
0x8049460 <main+96>:
push
%eax
0x8049461 <main+97>:
call
0x804c930 <SetBuffer__9BaseClassPc>
0x8049466 <main+102>:
add
$0x8,%esp
0x8049469 <main+105>:
push
$0x804cdaa
---Type <return> to continue, or q <return> to quit---
0x804946e <main+110>:
mov
0xfffffffc(%ebp),%eax
0x8049471 <main+113>:
push
%eax
0x8049472 <main+114>:
call
0x804c930 <SetBuffer__9BaseClassPc>
0x8049477 <main+119>:
add
$0x8,%esp
0x804947a <main+122>:
mov
0xfffffff8(%ebp),%edx
0x804947d <main+125>:
mov
0x20(%edx),%eax
0x8049480 <main+128>:
add
$0x8,%eax
0x8049483 <main+131>:
mov
0xfffffff8(%ebp),%edx
0x8049486 <main+134>:
push
%edx
0x8049487 <main+135>:
mov
(%eax),%edi
0x8049489 <main+137>:
call
*%edi
0x804948b <main+139>:
add
$0x4,%esp
0x804948e <main+142>:
mov
0xfffffffc(%ebp),%edx
0x8049491 <main+145>:
mov
0x20(%edx),%eax
0x8049494 <main+148>:
add
$0x8,%eax
0x8049497 <main+151>:
mov
0xfffffffc(%ebp),%edx
0x804949a <main+154>:
push
%edx
0x804949b <main+155>:
mov
(%eax),%edi
0x804949d <main+157>:
call
*%edi
0x804949f <main+159>:
add
$0x4,%esp
0x80494a2 <main+162>:
xor
%eax,%eax
0x80494a4 <main+164>:
jmp
0x80494d0 <main+208>
0x80494a6 <main+166>:
jmp
0x80494d0 <main+208>
0x80494a8 <main+168>:
push
%ebx
0x80494a9 <main+169>:
call
0x804b4f0 <__builtin_delete>
0x80494ae <main+174>:
add
$0x4,%esp
0x80494b1 <main+177>:
jmp
0x8049424 <main+36>
0x80494b6 <main+182>:
push
%esi
0x80494b7 <main+183>:
call
0x804b4f0 <__builtin_delete>
0x80494bc <main+188>:
add
$0x4,%esp
0x80494bf <main+191>:
jmp
0x8049450 <main+80>
0x80494c1 <main+193>:
jmp
0x80494c8 <main+200>
0x80494c3 <main+195>:
call
0x8049c3c <__throw>
0x80494c8 <main+200>:
call
0x8049fc0 <terminate__Fv>
0x80494cd <main+205>:
lea
0x0(%esi),%esi
0x80494d0 <main+208>:
lea
0xffffffec(%ebp),%esp
0x80494d3 <main+211>:
pop
%ebx
0x80494d4 <main+212>:
pop
%esi
0x80494d5 <main+213>:
pop
%edi
---Type <return> to continue, or q <return> to quit---
0x80494d6 <main+214>:
leave
0x80494d7 <main+215>:
ret
0x80494d8 <main+216>:
nop
0x80494d9 <main+217>:
nop
0x80494da <main+218>:
nop
0x80494db <main+219>:
nop
0x80494dc <main+220>:
nop
0x80494dd <main+221>:
nop
0x80494de <main+222>:
nop
0x80494df <main+223>:
nop
End of assembler dump.
(gdb)
以下是對該程式
彙編
程式碼的解釋:
0x8049400 <main>:
push
%ebp
0x8049401 <main+1>:
mov
%esp,%ebp
0x8049403 <main+3>:
sub
$0x8,%esp
0x8049406 <main+6>:
push
%edi
0x8049407 <main+7>:
push
%esi
0x8049408 <main+8>:
push
%ebx
構建堆疊。為
Object[]
陣列保留
8
個位元組(即兩個
4
位元組指標地址),則
Object[0]
的指標存放在
0xfffffff8(%ebp)
,
Object[1]
的指標存放在
0fffffffc(%ebp)
。接著儲存暫存器。
0x8049409 <main+9>:
push
$0x24
0x804940b <main+11>:
call
0x804b580 <__builtin_new>
0x8049410 <main+16>:
add
$0x4,%esp
首先呼叫
__builtin_new
,在堆(
heap
)中分配
0x24
(
36
位元組)給
Object[0]
,並將其首地址儲存到
EAX
暫存器中。這
36
位元組中前
32
位元組是
Buffer
變數的,後
4
位元組由
VPTR
佔用。
0x8049413 <main+19>:
mov
%eax,%eax
0x8049415 <main+21>:
mov
%eax,%ebx
0x8049417 <main+23>:
push
%ebx
0x8049418 <main+24>:
call
0x804c90c <__8MyClass1>
0x804941d <main+29>:
add
$0x4,%esp
將物件的首地址壓棧,然後呼叫
__8MyClass1
函式。這其實是
MyClass1
物件的建構函式(
constructor
)。
(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
暫存器
EBX
現在存放著指向分配的
36
個位元組的指標(在
C++
語言中,稱之為
"This"
指標)。
0x804c913 <__8MyClass1+7>:
push
%ebx
0x804c914 <__8MyClass1+8>:
call
0x804c958 <__9BaseClass>
0x804c919 <__8MyClass1+13>:
add
$0x4,%esp
首先呼叫基類
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
暫存器
EDX
現在存放著指向分配的
36
個位元組的指標(
"This"
指標)。
0x804c95e <__9BaseClass+6>:
movl
$0x804e01c,0x20(%edx)
將
0x804e01c
存放到
EDX+0x20
(
=EDX+32
)。讓我們看看該
0x804e01c
地址
資料:
(gdb) x 0x804e01c
0x804e01c <__vt_9BaseClass>:
0x00000000
可以看到這個存放到
EDX+0x20
(即該物件的
VPTR
位置)的地址是基類
BaseClass
的
VTABLE
地址。
現在回到
MyClass1
物件的建構函式:
0x804c91c <__8MyClass1+16>:
movl
$0x804e010,0x20(%ebx)
將
0x804e010
存放到
EBX+0x20
(即
VPTR
)。同樣讓我們看看該
0x804e010
地址記憶體資料:
(gdb) x 0x804e010
0x804e010 <__vt_8MyClass1>:
0x00000000
現在,我們知道
VPTR
被改寫了,再在它的內容是
MyClass1
物件的
VTABLE
地址。當返回到
main()
函式時暫存器
EAX
中存放著該物件在記憶體中的指標。
0x8049420 <main+32>:
mov
%eax,%esi
0x8049422 <main+34>:
jmp
0x8049430 <main+48>
0x8049424 <main+36>:
call
0x8049c3c <__throw>
0x8049429 <main+41>:
lea
0x0(%esi,1),%esi
0x8049430 <main+48>:
mov
%esi,0xfffffff8(%ebp)
將得到的地址指標賦予
Object[0]
。然後程式對
Object[1]
進行同樣的處理,只不過返回的地址不同罷了。在經過以上物件初始化處理後,將執行以下指令:
0x8049458 <main+88>:
push
$0x804cda2
0x804945d <main+93>:
mov
0xfffffff8(%ebp),%eax
0x8049460 <main+96>:
push
%eax
將
0x804cda2
和
Object[0]
的值壓棧。觀察一下
0x804cda2
的內容:
(gdb) x/s 0x804cda2
0x804cda2 <_IO_stdin_used+30>:
"string1"
可知該地址存放了將要透過基類
BaseClass
的
SetBuffer
函式複製到
Buffer
中的字串
"string1"
。
0x8049461 <main+97>:
call
0x804c930 <SetBuffer__9BaseClassPc>
0x8049466 <main+102>:
add
$0x8,%esp
呼叫基類
BaseClass
的
SetBuffer()
方法。注意到這種
SetBuffer
方法的呼叫是
“
靜態繫結
”
(因為它不是虛擬方法)。對
Object[1]
的處理也是一樣的。
為了驗證這兩個物件在執行時都被正確地初始化,我們將要設定如下斷點:
0x8049410:
獲得第一個物件的地址。
0x804943a:
獲得第二個物件的地址。
0x804947a:
檢驗物件的初始化是否正確。
(gdb) break *0x8049410
Breakpoint 1 at 0x8049410
(gdb) break *0x804943a
Breakpoint 2 at 0x804943a
(gdb) break *0x804947a
Breakpoint 3 at 0x804947a
現在執行這個程式:
St
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69914734/viewspace-2653535/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 駭客中級技術--緩衝區溢位攻擊(轉)
- 緩衝區溢位攻擊初學者手冊(更新版)
- 緩衝區溢位小程式分析
- ASLR 是如何保護 Linux 系統免受緩衝區溢位攻擊的Linux
- 緩衝區溢位實驗
- 緩衝區溢位漏洞的原理及其利用實戰
- 一個緩衝區溢位漏洞的簡易教程
- 淺談C#緩衝區溢位的祕密C#
- CMD.EXE中dir超長字串緩衝區溢位原理學習字串
- Redis緩衝區溢位及解決方案Redis
- IMail SMTP 緩衝區溢位漏洞 (APP,缺陷) (轉)AIAPP
- pwntools緩衝區溢位與棧沒對齊
- 【轉】strcpy溢位的攻擊示例
- 緩衝區溢位漏洞那些事:C -gets函式函式
- C++指標與引用的區別C++指標
- C 標準庫IO緩衝區和核心緩衝區的區別
- C++物件導向總結——虛指標與虛擬函式表C++物件指標函式
- C++虛擬函式學習總結C++函式
- C++中的this指標C++指標
- MikroTik RouterOS 中發現了可遠端利用的緩衝區溢位漏洞ROS
- Nio再學習之NIO的buffer緩衝區
- C++ this 指標C++指標
- C++ 指標C++指標
- C++的成員指標C++指標
- C++智慧指標學習——小談引用計數C++指標
- CVE-2010-2883-CoolType.dll緩衝區溢位漏洞分析
- Python經典棧緩衝區溢位獲取root許可權Python
- Java NIO 緩衝區學習筆記Java筆記
- 再學C/C++ 之 指標常量 和 常量指標C++指標
- 【C++系列】指標物件和物件指標的區別C++指標物件
- C++智慧指標模板類複習C++指標
- C++指標理解C++指標
- 【c++】智慧指標C++指標
- C++智慧指標C++指標
- C++的未來和指標C++指標
- C++中的指標與引用C++指標
- C++ 中的虛擬函式C++函式
- C++學習day18之 指向類成員的指標C++指標