C++逆向?qū)W習(xí)(四) 類

測(cè)試代碼

基類?base?,派生類?derived?,分別有成員變量、成員函數(shù)、虛函數(shù)

#include<stdio.h>#include<stdlib.h>classbase {public:inta;doubleb;? ? base() {this->a =1;this->b =2.3;printf("base constructor\n");? ? }voidfunc(){printf("%d? %lf\n", a, b);? ? }virtualvoidv_func(){printf("base v_func()\n");? ? }? ? ~base() {printf("base destructor\n");? ? }};classderived :publicbase {public:? ? derived() {printf("derived constructor\n");? ? }virtualvoidv_func(){printf("derived v_func()");? ? }? ? ~derived() {printf("derived destructor\n");? ? }};intmain(intargc,char** argv){? ? base a;? ? a.func();? ? a.v_func();? ? base* b = (base*)newderived();? ? b->func();? ? b->v_func();return0;}

編譯:?g++ test.cpp -o test

IDA視角

IDA打開,如下:

this指針

可以看到,?base::?的每個(gè)函數(shù)都傳入了一個(gè)參數(shù)?(base*)&v5?,正是類實(shí)例的?this指針

以下是普通成員函數(shù)?func()?的調(diào)用過(guò)程

rdi?作為第一個(gè)參數(shù),存放?this?指針,而?windows?下是寄存器?rcx

this?指針是識(shí)別類成員函數(shù)的一個(gè)關(guān)鍵

如果看到C++生成的exe文件中,如果?rcx?寄存器還沒(méi)有被初始化就直接使用,很可能是類的成員函數(shù)

構(gòu)造、析構(gòu)

考慮構(gòu)造函數(shù)時(shí)的過(guò)程

其中?*this = off_400C18?,即先把類的虛表地址賦值給類實(shí)例的首字段

補(bǔ)充一些

注意虛表前還有一個(gè)?typeinfo?,在?g++?的實(shí)現(xiàn)中,真正的?typeinfo信息在虛表之后,虛表的前一個(gè)字段存放了?typeinfo?的地址

typeinfo?是編譯器生成的特殊類型信息,包括對(duì)象繼承關(guān)系、對(duì)象本身的描述等

Aclass* ptra=new Bclass;int ** ptrvf=(int**)(ptra);RTTICompleteObjectLocator str=*((RTTICompleteObjectLocator*)(*((int*)ptrvf[0]-1)));? //vptr-1

這段獲取對(duì)象RTTI信息相關(guān)的代碼也顯示了這一點(diǎn)

回到構(gòu)造和析構(gòu)函數(shù)

在構(gòu)造函數(shù)調(diào)用中,顯然需要將虛表的地址賦值給類實(shí)例的虛表指針,從代碼上來(lái)看也是這樣

但是,我們觀察base類的析構(gòu)函數(shù)

析構(gòu)時(shí)也首先重新賦值了虛表指針,看起來(lái)可能有點(diǎn)多此一舉

但如果析構(gòu)函數(shù)中調(diào)用了虛函數(shù),此行為可以保證正確;至于如果不重新賦值會(huì)有錯(cuò)誤行為的情況就不展開了

虛表指針的賦值是識(shí)別的一個(gè)關(guān)鍵,排除開發(fā)者故意偽造編譯器生成的代碼來(lái)誤導(dǎo)分析,基本可以確定是?構(gòu)造函數(shù)?或者?析構(gòu)函數(shù)

同樣的,找到了虛表,也就可以根據(jù)IDA的交叉引用,找到對(duì)應(yīng)的?構(gòu)造函數(shù)?和?析構(gòu)函數(shù)

構(gòu)造、析構(gòu)代理函數(shù)

全局對(duì)象和靜態(tài)對(duì)象的構(gòu)造時(shí)機(jī)相同,可以說(shuō)是被隱藏了起來(lái),在main函數(shù)之前由構(gòu)造代理函數(shù)統(tǒng)一構(gòu)造

測(cè)試代碼:

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<iostream>usingnamespacestd;classt {public:char* str;? ? t() {cout<<"constructor"<str =newchar[16];memcpy(this->str,"hello",12);? ? }? ? ~t() {cout<str <

編譯:?visual studio 2019 x64 release

IDA打開,根據(jù)輸出,下斷點(diǎn)后發(fā)現(xiàn)t類全局變量構(gòu)造函數(shù)輸出信息調(diào)用于?initterm_0?函數(shù)

一段?initterm_0?的代碼實(shí)現(xiàn)如下:

while(pfbegin < pfend) {//pfbegin == __xc_a , pfend == __xc_zif(*pfbegin !=NULL) {? ? ? ? (**pfbegin)();//調(diào)用每一個(gè)初始化或構(gòu)造代理函數(shù)++pfbegin();? ? }}

執(zhí)行?(**pfbegin)()?后并不會(huì)進(jìn)入全局對(duì)象的構(gòu)造函數(shù)中,而是進(jìn)入編譯器提供的?構(gòu)造代理函數(shù)

最簡(jiǎn)單的找到全局對(duì)象構(gòu)造函數(shù)的方法:因?yàn)闃?gòu)造代理函數(shù)中會(huì)?注冊(cè)析構(gòu)函數(shù)?,其注冊(cè)方式是使用?atexit?,我們對(duì)?atexit?下斷點(diǎn),調(diào)試過(guò)程中很容易在附近找到全局對(duì)象構(gòu)造的構(gòu)造函數(shù)

如圖所示,?10?即為對(duì)象數(shù)組的大小,并且最后一個(gè)參數(shù)傳入了構(gòu)造函數(shù)指針?t::t()

析構(gòu)代理函數(shù)比較類似,就不多分析了,同樣以?atexit?為切入點(diǎn)

t::_t?即為?t?類的析構(gòu)函數(shù)

虛函數(shù)調(diào)用

代碼中我們用?base*?指針指向了?new derived()?,在IDA里如下

v3作為derived類實(shí)例的地址,存放的正好是虛表指針,而?v_func()?正好在虛表的第一個(gè)位置,參數(shù)?v3?則是例行傳入?this?指針

已經(jīng)有很多文章講過(guò)虛函數(shù)調(diào)用過(guò)程了,這里就只是簡(jiǎn)單說(shuō)一下

虛基類繼承

主要分析一下?菱形繼承?的內(nèi)存布局,代碼如下:

#include<stdio.h>#include<stdlib.h>//間接基類classA {public:virtualvoidfunction(){printf("A virtual function\n");? ? }inta;};//直接基類classB :virtualpublicA {//虛繼承public:virtualvoidfunc(){printf("B virtual func()\n");? ? }intb;};//直接基類classC :virtualpublicA {//虛繼承public:virtualvoidfunc(){printf("C virtual func()");? ? }intc;};//派生類classD :publicB,publicC {public:virtualvoidfunction(){printf("D virtual function()");? ? }intd;};intmain(intargc,char** argv){? ? A* A_ptr = (A*)newD();? ? A_ptr->function();return0;}

編譯:?visual studio 2019 x64 release

B、C類都虛繼承了A類,然后D類多重繼承于B、C類

布局如圖:

具體實(shí)現(xiàn)是在B、C類里不再保存A類的內(nèi)容,而是保存一份?偏移地址?,然后將A類的數(shù)據(jù)保存在一個(gè)公共位置處,降低數(shù)據(jù)冗余

為方便說(shuō)明,使用?g++?編譯并用IDA打開

main函數(shù)比較清晰,跟進(jìn)D類的構(gòu)造函數(shù)

虛表占8字節(jié),int占4字節(jié),考慮字節(jié)對(duì)齊,實(shí)際B、C類都占了16字節(jié)

接著用gdb跟進(jìn)一下,斷在?(**func)(func)?上

已經(jīng)分析過(guò),D類的首字段即存放了B類的虛表,也就是?RBX==0x614c20是D類實(shí)例地址

IDA可以看到?0x400A90==A::vtable?,也就是先找到A類的虛表

而A類虛表實(shí)際存放的函數(shù)指針值,由于虛函數(shù)機(jī)制被?D::function()覆蓋,會(huì)實(shí)際調(diào)用到D類對(duì)應(yīng)的函數(shù)

補(bǔ)充

關(guān)于如何讓IDA里的分析更清晰,添加結(jié)構(gòu)體、類的信息來(lái)幫助IDA的內(nèi)容,網(wǎng)上已經(jīng)有很多,這里不再多說(shuō)了

推薦一本書《深度探索C++對(duì)象模型》,里面有很多類布局的歷史實(shí)現(xiàn),以及這些布局設(shè)計(jì)時(shí)對(duì)空間、時(shí)間效率的權(quán)衡

看我主頁(yè)簡(jiǎn)介免費(fèi)C++學(xué)習(xí)資源,視頻教程、職業(yè)規(guī)劃、面試詳解、學(xué)習(xí)路線、開發(fā)工具

每晚8點(diǎn)直播講解C++編程技術(shù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容