2014-02-06 23 views
2

我只是讀一個新的C++的挑戰: http://blogs.msdn.com/b/vcblog/archive/2014/02/04/challenge-vulnerable-code.aspxC++鑄造基地和「覆蓋」的vptr問題

提供的代碼,如果充滿了問題,一些明顯對任何人有良好的編程習慣,有的可見只有C++ :-)

它在註釋中描述當地人,即一個特定的線路(37)是特別危險:

ImageFactory::DebugPrintDimensions((ImageFactory::CustomImage*)image); 

然後函數調用CustomImage(定義的第一TI的虛擬方法我在CustomImage)。

據稱這引起CustomImage的第一個成員被當作實例(這是一個unique_ptr實際上)的vptr的和使由它指向的二進制文件視爲可執行文件(也許是惡意的)代碼..

雖然我可以理解這一點,我想知道爲什麼這真的有用。

CustomImage是一個虛擬的類,所以(可能)它的前4個字節(只是假設X86)是vptr,並且unique_ptr成員是next ..並且由於演員似乎沒有移動任何東西......

...如何執行unique_ptr所保存的數據?

+0

http://stackoverflow.com/questions/14914940/is-it-undefined-behavior-to-cast-from-base-class-to-derived – user2485710

回答

1

我採取的(我更樂意被糾正):

這裏,CustomImage是一個多態類(用的vptr作爲Windows下的ABI的第一個「成員」),但Image不。定義的順序意味着ImageFactory函數知道CustomImageImage,但main()沒有。

所以當工廠做:

Image* ImageFactory::LoadFromDisk(const char* imageName) 
{ 
    return new (std::nothrow) CustomImage(imageName); 
} 

CustomImage*指針轉換爲Image*,一切都很好。因爲Image不是多態的,所以指針被調整爲指向CustomImage內部的(POD)Image實例 - 但是從本質上講,這是 vptr之後的,因爲它始終在MS ABI中的多態類中處於第一位(I承擔)。

然而,當我們到達

ImageFactory::DebugPrintDimensions((ImageFactory::CustomImage*)image); 

編譯器看到從一個類C風格的投它一無所知到另一個。它所做的只是把它的地址,並假裝它是CustomImage*。該地址實際上指向自定義圖像中的Image;但由於Image爲空,並且大概空基類優化生效,它最終指向CustomImage內的第一個成員,即unique_ptr

現在,ImageFactory::DebugPrintDimensions()假設它已經交給了一個完全完整的CustomImage的指針,所以地址等於vptr的地址。但它沒有 - 它已經交給了unique_ptr的地址,因爲在被調用的地方,編譯器不知道更好。所以現在它取消了它認爲是vptr(我們控制的數據)的真實含義,尋找虛擬函數的偏移並盲目地執行 - 現在我們遇到了麻煩。

有幾件事可以幫助緩解這種情況。首先,因爲我們通過基類指針操縱派生類,所以Image應該有一個虛擬析構函數。這會使得多態性成爲Image,並且很可能我們不會有問題(並且我們也不會泄漏內存)。其次,因爲我們是從基地派生的,所以應該使用dynamic_cast而不是C風格演員,這將涉及運行時檢查和正確的指針調整。最後,如果編譯器在編譯main()時掌握了所有的信息,它可能已經能夠警告我們(或正確地執行了演員表,調整CustomImage的多態屬性)。因此,建議將類別定義移至main()以上。

+0

好吧,這是一個實現細節,但是:執行由'unique_ptr'保存的二進制文件將在這裏工作** only **如果'unique_ptr' **是指向二進制文件的指針..是這樣嗎?我的意思是 - 這是執行unique_ptr的方式 - 它的前N個字節是指向所持資源的指針? – Parobay

+0

我不知道MS的實現,但不需要'unique_ptr '有任何數據成員而不是'T *',或者它是多態的,所以我認爲這很可能是地址'unique_ptr'與其中的數據相同。 –

+0

事實上,'T *'類型的'pointer'成員是(至少某些)unique_ptr'實現 – Parobay

1

據推測,存儲器佈局,使得的vptr進入基地子對象之前,例如:

class CustomImage { 
    void * __vptr; 
    Image __base; // empty 
    unique_ptr<whatever> evil; 
}; 

這意味着,有效轉換從Image*CustomImage*需要來自指針減去幾個字節。然而,你發佈的邪惡陣容來自類定義之前,所以它不知道如何正確調整指針。相反,它的行爲如reinterpret_cast,只是假裝指針指向CustomImage而未調整其值。

現在,由於基類是空的,unique_ptr中的指針將被誤解爲vptr。這指向另一個指針,它將被誤解爲vtable指向第一個虛擬成員函數的指針。這又指向從該文件加載的數據,當該虛擬函數被調用時,該數據將作爲代碼執行。作爲錦上添花,內存保護標誌從文件中加載,而不是爲了防止執行而進行調整。

這裏的一些經驗教訓:

  • 可以避免C-風格的轉換,特別是指針或引用類型。如果轉換無效,它們會回落到reinterpret_cast,導致未定義行爲的雷區。 (爲了使邪惡複雜化,它們的語法也是不可取代的,如果你不仔細閱讀代碼,很容易錯過。)
  • 避免非多態基類。正如我們在這裏所看到的,除了在概念上令人懷疑,並使刪除更加尷尬和容易出錯,它可以對內存佈局做出令人驚訝的事情。如果基類是多態的,我們可以使用dynamic_cast(或者通過提供合適的虛擬函數來完全避免投射),並且不可能有無效轉換。
  • 避免不必要的間接級別 - 沒有特別需要m_imageData作爲指針。
  • 切勿將用戶數據放入可執行內存中。
+0

的第一個成員,你顯然不會選擇Empty Base Opt。考慮到? – Parobay

+0

@Parobay:是的,我喜歡。如果基礎對象沒有優化出來,那麼轉換會給出一個指向它的指針,而不是'unique_ptr',它可能會導致崩潰而不是利用漏洞。 –

+0

你的回答非常好,但我只能接受一個..對不起,這是一個公平的硬幣折騰.. – Parobay