高手談Android NDK C++ RTTI 分析

本文意在說(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ò)誤蔑滓,歡迎指正遇绞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末摹闽,一起剝皮案震驚了整個(gè)濱河市付鹿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俊抵,老刑警劉巖纽匙,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烛缔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡院喜,警方通過(guò)查閱死者的電腦和手機(jī)晕翠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)硫麻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人杠河,你說(shuō)我怎么就攤上這事浇辜。” “怎么了待诅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵卑雁,是天一觀(guān)的道長(zhǎng)轧钓。 經(jīng)常有香客問(wèn)我毕箍,道長(zhǎng)道盏,這世上最難降的妖魔是什么荷逞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任种远,我火速辦了婚禮,結(jié)果婚禮上坠敷,老公的妹妹穿的比我還像新娘膝迎。我一直安慰自己,他們只是感情好芒涡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布费尽。 她就那樣靜靜地躺著旱幼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叹誉。 梳的紋絲不亂的頭發(fā)上闷旧,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天长豁,我揣著相機(jī)與錄音,去河邊找鬼忙灼。 笑死匠襟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的该园。 我是一名探鬼主播酸舍,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼里初!你這毒婦竟也來(lái)了啃勉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤双妨,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后刁品,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體泣特,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年挑随,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了状您。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡兜挨,死狀恐怖膏孟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情暑劝,我是刑警寧澤骆莹,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站担猛,受9級(jí)特大地震影響幕垦,放射性物質(zhì)發(fā)生泄漏丢氢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一先改、第九天 我趴在偏房一處隱蔽的房頂上張望疚察。 院中可真熱鬧,春花似錦仇奶、人聲如沸貌嫡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)岛抄。三九已至,卻和暖如春狈茉,著一層夾襖步出監(jiān)牢的瞬間夫椭,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工氯庆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹭秋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓堤撵,卻偏偏與公主長(zhǎng)得像仁讨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子实昨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 上篇請(qǐng)看:C++編程思想重點(diǎn)筆記(上) 宏的好處與壞處 宏的好處:#與##的使用 三個(gè)有用的特征:字符串定義洞豁、字符...
    小敏紙閱讀 629評(píng)論 0 2
  • Introduction to C++ (Season 1) Unit 1: Overview of C++ 第1...
    我是阿喵醬閱讀 2,753評(píng)論 0 7
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)屠橄,斷路器族跛,智...
    卡卡羅2017閱讀 134,661評(píng)論 18 139
  • 再讀高效c++闰挡,頗有收獲锐墙,現(xiàn)將高效c++中的經(jīng)典分享如下,希望對(duì)你有所幫助长酗。 1溪北、盡量以const \enum\i...
    橙小汁閱讀 1,223評(píng)論 0 1
  • 小小年紀(jì)無(wú)煩惱,手舞足蹈樂(lè)開(kāi)懷夺脾。 調(diào)皮活潑又可愛(ài)之拨,幸福天使美照帥。
    文采樂(lè)閱讀 281評(píng)論 16 8