本文意在說(shuō)明Android NDK 在實(shí)現(xiàn)C++ RTTI時(shí)的相關(guān)數(shù)據(jù)結(jié)構(gòu),并從匯編角度分析其內(nèi)存布局渴丸,以幫助理解RTTI的實(shí)現(xiàn)原理,同時(shí)戒幔,分析在逆向過(guò)程中如何利用RTTI恢復(fù)C++類(lèi)名信息诗茎。
用ndk-build編譯C++代碼時(shí),默認(rèn)的C++運(yùn)行時(shí)庫(kù)(libstdc++)是不支持RTTI的栅组, 需要在Application.mk與Android.mk中進(jìn)行配置枢析。其它可以選擇的C++運(yùn)行時(shí)庫(kù)有GAbi++醒叁、STLport、GNU STL把沼、LLVM libc++, 各種庫(kù)又分靜態(tài)鏈接庫(kù)與動(dòng)態(tài)鏈接庫(kù)饮睬。其中中STLport的RTTI是借用了GAbi++中的實(shí)現(xiàn),另外GNU STL割去、LLVM libc++的實(shí)現(xiàn)也與GAbi++非常相似(相關(guān)數(shù)據(jù)結(jié)構(gòu)的命名昼丑、結(jié)構(gòu)都相似, 可能是因?yàn)槎际腔贗tanium C++ ABI。
所以本文將選擇STLPort為C++運(yùn)行時(shí)庫(kù)咖城, 在Application.mk中配置:
APP_STL := stlport_static
在Android.mk中配置:
LOCAL_CPP_FEATURES := rtti
另外宜雀,本文使用 Android NDK 10c編譯控妻,編譯abi為armeabi揭绑,編譯32位代碼時(shí)其默認(rèn)使用GCC 4.8。若使用其它版本NDK或者其它編譯器菇存,可能與本文分析結(jié)果有差異。
一亥至、C++ RTTI 簡(jiǎn)介
RTTI是Runtime Type Identification的縮寫(xiě)贱迟,即運(yùn)行時(shí)類(lèi)型識(shí)別衣吠。程序能夠借此使用基類(lèi)的指針或引用,來(lái)檢查這些指針或引用所指的對(duì)象的實(shí)際派生類(lèi)型惊搏。C++通過(guò)typeid與dynamic_cast來(lái)提供RTTI忧换。typeid返回一個(gè)typeinfo對(duì)象的引用亚茬,它記錄了與類(lèi)型相關(guān)的信息,后文將詳細(xì)分析這個(gè)結(jié)構(gòu)刹缝;dynamic_cast用于安全而有效地進(jìn)行向下轉(zhuǎn)型(down_cast)赞草,即安全地將一個(gè)基類(lèi)指針轉(zhuǎn)換為一個(gè)派生類(lèi)指針。
它們的基本使用方法如下:
classes.h文件:
classBase
{
public:
Base();
virtual ~Base();
virtualvoidFunc();
private:
intmMember;
};
classDeriver1 :publicBase
{
public:
Deriver1();
virtual ~Deriver1();
virtualvoidFunc();
private:
intmDeriver1Member;
};
classDeriver2 :publicBase
{
public:
Deriver2();
virtual ~Deriver2();
virtualvoidFunc();
private:
intmDeriver2Member;
};
main.cpp文件:
intmain()
{
Base base;
Deriver1 deriver1;
Deriver2 deriver2;
cout<
cout<
cout<
Base *pBase = &deriver1;
cout<
cout<
cout << pBase << endl;
Driver1 *pDeriver1 = dynamic_cast(pBase);
cout << pDeriver1 << endl;
Driver2 *pDeriver2 = dynamic_cast(pBase);//正確,返回NULL
cout << pDeriver2 << endl;
pDeriver2 = (Deriver2*)pBase;//錯(cuò)誤
cout << pDeriver2 << endl;
pDeriver2 = static_cast(pBase);//錯(cuò)誤
cout << pDeriver2 << endl;return 0;
}
編譯成可執(zhí)行文件沾凄,push到android 手機(jī)上運(yùn)行撒蟀,輸出:
i <------- typeid(int).name(), 變量類(lèi)型
4Base <------- typeid(Base).name(), 類(lèi)名
4Base <------- typeid(base).name()手负, 變量
P4Base <------- typeid(pBase).name(), Base的指針類(lèi)型
8Deriver1 <------- typeid(*pBase).name(), pBase實(shí)際指向一個(gè)Deriver1
0xbec87a20
0xbec87a20 <----- 正確的轉(zhuǎn)換,指向deriver1的基類(lèi)指針可以轉(zhuǎn)換為Deriver1類(lèi)型指針
0x00000000 <----- 正確的轉(zhuǎn)換蝠猬,因?yàn)橹赶騞eriver1的基類(lèi)指針并不能轉(zhuǎn)換為Deriver2類(lèi)型指針
0xbec87a20 <----- 錯(cuò)誤统捶,若繼續(xù)使用喘鸟,可能會(huì)導(dǎo)致內(nèi)存訪(fǎng)問(wèn)出錯(cuò),即將Dervier1當(dāng)Deriver2用
0xbec87a20 <----- 錯(cuò)誤犬绒,若繼續(xù)使用兑凿,可能會(huì)導(dǎo)致內(nèi)存訪(fǎng)問(wèn)出錯(cuò)
P.S. 上面看到顯示的類(lèi)名與我們定義的不完全一樣,是因?yàn)闉榱吮WC每個(gè)類(lèi)名稱(chēng)在程序中的唯一性咐鹤,編譯器會(huì)通過(guò)一定的規(guī)則對(duì)原始類(lèi)名進(jìn)行改寫(xiě)圣絮,如想了解這一規(guī)則扮匠,可以以name mangling為關(guān)鍵詞進(jìn)行搜索。
二疹蛉、RTTI 相關(guān)數(shù)據(jù)結(jié)構(gòu)
上文說(shuō)到typeid將返回一個(gè)typeinfo對(duì)象的const引用力麸,RTTI就是依賴(lài)typeinfo類(lèi)及其派生類(lèi)來(lái)實(shí)現(xiàn)的克蚂,下面介紹下這些類(lèi)。
在NDK路徑下\android-ndk-r10c\sources\cxx-stl\gabi++\include\typeinfo文件中有定義這個(gè)類(lèi):
classtype_info
{public:
virtual ~type_info();
//....
private:
//....
const char*__type_name;// 這個(gè)字段記錄改寫(xiě)過(guò)后的類(lèi)名
}摸恍;
在NDK路徑下\android-ndk-r10c\sources\cxx-stl\gabi++\src\cxxabi_defines.h有定義一些typeinfo的派生類(lèi)赤屋,此處挑一些我們感興趣的類(lèi)列舉:
class__shim_type_info :publicstd::type_info{....}
// 無(wú)基類(lèi)的類(lèi)的typeinfo類(lèi)型
class__class_type_info :public__shim_type_info{.....}
//只有一個(gè)public非虛基類(lèi),且基類(lèi)偏移為0的類(lèi)的typeinfo
class__si_class_type_info :public__class_type_info{
public:
virtual ~__si_class_type_info();const__class_type_info *__base_type;
//......
}
// 有基類(lèi)但不滿(mǎn)足 __si_class_type_info 約束條件的其它類(lèi)的typeinfo
class__vmi_class_type_info :public__class_type_info{
public:
virtual ~__vmi_class_type_info();
unsignedint__flags;
unsignedint__base_count;
__base_class_type_info __base_info[1];
//......
}
// Used in __vmi_class_type_info
struct __base_class_type_info{
public:
const__class_type_info *__base_type;long__offset_flags;
// .......
}
以第1小節(jié)中的程序?yàn)槔珺ase欣范、Driver1的對(duì)象的內(nèi)存布局如下:
deriver2的內(nèi)存布局與deriver1相似令哟,這里沒(méi)有重復(fù)畫(huà)出屏富。從上圖可以看到,每一個(gè)類(lèi)的虛表索引為-1的位置存放著typeinfo的指針噩死,并根據(jù)類(lèi)的不同神年,該指針指向不同的typeinfo派生類(lèi)實(shí)例。比如Base類(lèi)無(wú)基類(lèi)垛耳,所以其typeinfo指針指向__class_type_info的實(shí)例飘千;而Deriver1繼承自Base, deriver1在其偏移為0的位置包含一個(gè)public非虛基類(lèi)實(shí)例护奈,所以它的typeinfo指針指向__si_class_type_info實(shí)例。使用dynamic_cast的時(shí)候酌予,正是根據(jù)這些typeinfo指針來(lái)判斷一個(gè)基類(lèi)指針是否可以轉(zhuǎn)換為一個(gè)派生類(lèi)指針奖慌。而且由上可見(jiàn),若一個(gè)待操作的類(lèi)沒(méi)有虛函數(shù)表建椰, typeid也只能返回其靜態(tài)類(lèi)型棉姐。
下面我們通過(guò)反編譯代碼來(lái)驗(yàn)證上面的關(guān)系圖。
三笛洛、逆向過(guò)程中利用RTTI恢復(fù)類(lèi)名
將第1小節(jié)中生成的可執(zhí)行程序用IDA Pro打開(kāi)乃坤,此處選用obj\local\armeabi\目錄下未經(jīng)過(guò)strip的程序,以方便分析狱杰。
根據(jù)相關(guān)字符串厅须,可以很快定位各個(gè)類(lèi)的typeinfo信息:
各個(gè)類(lèi)的虛函數(shù)表結(jié)構(gòu):
可見(jiàn)错沽,從反編譯的代碼看眶拉,虛表、typeinfo信息關(guān)系與第3節(jié)中描述一致镰禾。(細(xì)心的朋友可能有疑問(wèn)唱逢,為什么會(huì)產(chǎn)生兩個(gè)析構(gòu)函數(shù)坞古?對(duì)于這個(gè)問(wèn)題,可以以Itanium C++ ABI為關(guān)鍵字搜索了解)
對(duì)于通常的逆向分析织堂,都沒(méi)有沒(méi)有上面的符號(hào)信息的奶陈。所以我們可以通過(guò)RTTI信息來(lái)恢復(fù)類(lèi)名及其類(lèi)間關(guān)系,為逆向工作提供便利潦俺。可以按以下步驟進(jìn)行:
定位__class_type_info, __si_class_type_info, __vmi_class_type_info虛函數(shù)表早像。
查找對(duì)這些虛函數(shù)表的引用卢鹦,我們可以得到這些typeinfo派生類(lèi)的實(shí)例地址劝堪。而這些實(shí)例中type_name字段就表示原始類(lèi)名。
根據(jù)引用這些實(shí)例地址凡纳,就可以得到相關(guān)類(lèi)的虛表地址帝蒿,此處我們可以根據(jù)上一步得到的原始類(lèi)名重命名虛表指針葛超。
查找引用這些虛表指針的代碼延塑,通常都是類(lèi)的構(gòu)造函數(shù),于是我們又可以重命名這些構(gòu)造函數(shù)了侥涵。
以上步驟我們都可以通過(guò)IDAPython腳本自動(dòng)完成宋雏。
四磨总、小結(jié)
其實(shí)上面只是分析了最簡(jiǎn)單的單繼承情景,還有諸如多繼承娶牌、虛繼承等情景待分析馆纳,由于相關(guān)typeinfo類(lèi)已經(jīng)例出鲁驶,相信分析難度不大。
另外需要注意的一個(gè)地方壹罚,在反匯編后的代碼中,并不是直接引用虛表地址赂蠢,而是引用虛表地址-8的位置辨泳,用這個(gè)位置+8寫(xiě)入當(dāng)作虛擬指針菠红。
以上分析過(guò)程與結(jié)論都來(lái)自個(gè)人認(rèn)知,如有錯(cuò)誤蔑滓,歡迎指正遇绞。