1. 程式人生 > >突破C++的虛擬指標--C++程式的緩衝區溢位攻擊

突破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 Linux系統和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編譯後的執行結果:

[

[email protected] test]> ./bo2
MyClass1: string1
MyClass2: string2
[[email protected] test]>

  再一次提醒,在繼續往下看時,確信你讀懂了上面的程式,特別是物件虛擬(virtual)方法PrintBuffer()。與SetBuffer()方法不同,PrintBuffer方法必須在基類BaseClass的派生類MyClass1和MyClass2中宣告並實現。這使得SetBuffer與PrintBuffer方法在執行時的處理會有所不同。


---[[ C++的虛擬指標(Virtual PoinTeR,VPTR)]]--------------------------------------

  我們知道,虛擬方法與非虛擬方法的一個不同之處是,非虛擬方法的呼叫是在編譯時確定(通常稱為“靜態繫結”),而虛擬方法的呼叫卻是在程式時確定的(通常稱為“動態繫結”)。下面以上例中的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: IIIIIIIIIIIIQQQQ

說明: 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 ]]--------------------------------------

  在Linux下當然是使用GDB來分析了:

[
[email protected]
test]> gcc -o bo2 bo2.cpp
[[email protected] 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

相關推薦

突破C++的虛擬指標C++程式緩衝區溢位攻擊

backend注:本文來自Phrack56期的《SMASHING C++ VPTRS》。正如大多數國外黑客的文章,技術原理及應用都講得比較詳細,但所提供的原始碼似乎總是會存在不大不小的問題。這也許是因為他們覺得應該讓讀者自己去研究和除錯,以更好地掌握這些技術。或許以後我也會

WebViewJavascriptBridgeObj-C和JavaScript互通消息的橋梁

ref 靜態 反饋 new mar ati port ces bridge 本文翻譯自Marcus Westin的開源框架WebViewJavascriptBridge的readme,英文原文鏈接https://github.com/marcuswestin/WebV

C語言的sleep函數linux

store targe userinfo pop tor tar use fan mar 76sM壤笛17屹靠仔畝7http://shufang.docin.com/mnmx84230 徘Y6躺BR渤誓39http://jz.docin.com/kedhn520 f15

C語言那年踩過的坑區域性變數,靜態變數,全域性變數在記憶體中存放的位置

  先看幾個概念: 1、bss是英文block started by symbol的簡稱,通常是指用來存放程式中未初始化的全域性變數的一塊記憶體區域,在程式載入時由核心清0。bss段屬於靜態記憶體分配。它的初始值也是由使用者自己定義的連線定位檔案所確定,使用者應該將它定義在可讀寫的ram區內,源程式中使用m

corelooper有望解決這一難題:C++的軟肋缺少統一的框架

感覺說的很有道理,原文如下: C++的軟肋-缺少統一的框架 C++語言誕生二十年有餘,曾經輝煌,在Java大出風頭時被頻頻指責,再到如今不死不活。語言是表達思想的工具,作為一門程式設計語言應該為開發人員提供有力的工具來解放開發人員。 C++語言支援結構化程式設計、面向物

c語言尋找指定字串的程式程式利用指標完成封裝)

一定要注意註釋了*的位置  我除錯了好久才發現自己沒有分配內訓 指標定義完一定要記得三選一 1.去null了 2.分配記憶體 3.指向特定的位置 還有就是宕機基本本質上都跟錯誤的操作記憶體有關 還有就是關於字串的輸出問題 首先下面的是正確的但是可以發現這樣寫編譯器

iOS開發Object-C獲取手機裝置資訊(UIDevice)

一、獲取UiDevice裝置資訊 // 獲取裝置名稱 NSString *name = [[UIDevice currentDevice] name]; // 獲取裝置系統名稱 NSString *systemName = [[UIDevice currentDevice] systemName

C++ 併發程式設計》 第1章 你好,C++的併發世界

本文是《C++ 併發程式設計》的第一章,感謝人民郵電出版社授權併發程式設計網發表此文,版權所有,請勿轉載。該書將於近期上市。 本章主要內容 何謂併發和多執行緒  為什麼要在應用程式中使用併發和多執行緒  C++併發支援的發展歷程  一個簡單的C++多執行緒程式是什麼樣的 這是C++使用者

C++ 虛擬函式表指標以及虛擬函式指標的確定

【摘要】 很多教材上都有介紹到虛指標、虛擬函式與虛擬函式表,有的說類物件共享一個虛擬函式表,有的說,一個類物件擁有一個虛擬函式表;還有的說,無論使用者聲明瞭多少個類物件,但是,這個VTABLE虛擬函式表只有一個;也有的在說,每個具有虛擬函式的類的物件裡面都有一

2016藍橋杯省賽java C組第十題 密碼脫落

暑假以來刷藍橋杯題,實在被暴力所折服,終於碰到一眼前一亮的題~~~ 第一篇線上的部落格,開寫~~~ 密碼脫落 X星球的考古學家發現了一批古代留下來的密碼。 這些密碼是由A、B、C、D 四種植物的種子串成的序列。 仔細分析發現,這些密碼串當初應該是前後對稱的(也就是我們說的映

C語言大數運算乘除法篇

前言: 這是第三篇部落格,也是一次介紹二個計算的部落格,可能難度會比前兩篇部落格大一點,所以建議對於初學者來說一定要看完我的前兩篇部落格再來看本篇部落格,關於本次實驗的環境,和思想在第一篇部落格已經簡單介紹過了,所以不再贅述,我會先介紹大數的乘法載介紹大數的除

[C++] 虛指標,虛表,虛擬函式地址列印

#include <iostream> using namespace std; class Base { public : int base_data; Base() { base_data = 1; } virtual void func1()

Linux C程式設計之IO檔案拷貝

Linux C程式設計:IO 1.1檔案拷貝 本次檔案拷貝需要使用到如下三個函式原型: 開啟檔案 FILE * fopen(const char * path,const char * mode); 相關函式:open,fclose,fopen_

EditLog類的使用C++標準輸出cout重定向到edit控制元件

原文地址:https://blog.csdn.net/vrix/article/details/1808421原文中的下載地址已失效,重新給出csdn的下載:https://download.csdn.net/download/augusdi/4012663要解決的問題:我曾

C++ 虛擬函式 6-- 三種呼叫虛擬函式的方式比較->通過物件、指標、引用

#include <iostream> #include <string> using namespace std; /*--------------------------------- 13-9 三種呼叫虛擬函式的方式比較 --------

C/C++面試之算法系列約瑟夫環:每隔兩個迴圈刪除陣列元素,求最後刪除者的下標問題

對於只讀陣列,普通的標誌法都不能用了,將高位置1遍歷完後清除的方法借鑑意義最高;時間和空間效率最均衡;連結串列法可以處理只讀陣列的問題;迴圈佇列法此時無法實現;當然對於標誌法,可以額外申請空間儲存標誌,也可以處理只讀問題,但空間效率下來了 (adsbygoogle = window

ios開發Object-C可變引數函式

簡介 一個可變引數函式是指一個函式擁有不定的引數,即為一個函式可接收多個引數。有時我們會遇到一些算術問題需要用到,比如是計算傳入引數的總和,字串的連線或是其它操作過程,我們在 OC 裡也經常使用,最經典的就是 NSLog( C 為 printf ) ,它可以指定格式的輸出,格式化輸出的內容。 現在我們

C++學習筆記模板

The general form of a template function definition is shown here: 通用的模板函式定義如下所示: template<class

黑馬程式設計師---c語言 指標

——Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! ——- 指標變數的型別說明 對指標變數的型別說明包括三個內容: (1)指標型別說明,即定義變數為一個指標變數; (2)指標變數名; (3

開發實戰中的C語言基礎篇-周兆熊-專題視訊課程

開發實戰中的C語言--基礎篇—5796人已學習 課程介紹        本課程基於實際開發中的編碼規範及研發流程,對開發實戰中的C語言進行了詳細的講解,課程內容主要包括:C語言概述、學校到職場、程式的樣式 、變數和函式、記憶體操作、檔案、指標和結構體、演算法和協議、程式重構、