高手谈Android NDK C++ RTTI 分析

本文意在说明Android NDK 在实现C++ RTTI时的相关数据结构,并从汇编角度分析其内存布局,以帮助理解RTTI的实现原理,同时,分析在逆向过程中如何利用RTTI恢复C++类名信息。

用ndk-build编译C++代码时,默认的C++运行时库(libstdc++)是不支持RTTI的, 需要在Application.mk与Android.mk中进行配置。其它可以选择的C++运行时库有GAbi++、STLport、GNU STL、LLVM libc++, 各种库又分静态链接库与动态链接库。其中中STLport的RTTI是借用了GAbi++中的实现,另外GNU STL、LLVM libc++的实现也与GAbi++非常相似(相关数据结构的命名、结构都相似, 可能是因为都是基于Itanium C++ ABI。

所以本文将选择STLPort为C++运行时库, 在Application.mk中配置:

APP_STL := stlport_static

在Android.mk中配置:

LOCAL_CPP_FEATURES := rtti

另外,本文使用 Android NDK 10c编译,编译abi为armeabi,编译32位代码时其默认使用GCC 4.8。若使用其它版本NDK或者其它编译器,可能与本文分析结果有差异。

一、C++ RTTI 简介

RTTI是Runtime Type Identification的缩写,即运行时类型识别。程序能够借此使用基类的指针或引用,来检查这些指针或引用所指的对象的实际派生类型。C++通过typeid与dynamic_cast来提供RTTI。typeid返回一个typeinfo对象的引用,它记录了与类型相关的信息,后文将详细分析这个结构;dynamic_cast用于安全而有效地进行向下转型(down_cast),即安全地将一个基类指针转换为一个派生类指针。

它们的基本使用方法如下:

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;//错误

cout << pDeriver2 << endl;

pDeriver2 = static_cast(pBase);//错误

cout << pDeriver2 << endl;return 0;

}

编译成可执行文件,push到android 手机上运行,输出:

i <------- typeid(int).name(), 变量类型

4Base <------- typeid(Base).name(), 类名

4Base <------- typeid(base).name(), 变量

P4Base <------- typeid(pBase).name(), Base的指针类型

8Deriver1 <------- typeid(*pBase).name(), pBase实际指向一个Deriver1

0xbec87a20

0xbec87a20 <----- 正确的转换,指向deriver1的基类指针可以转换为Deriver1类型指针

0x00000000 <----- 正确的转换,因为指向deriver1的基类指针并不能转换为Deriver2类型指针

0xbec87a20 <----- 错误,若继续使用,可能会导致内存访问出错,即将Dervier1当Deriver2用

0xbec87a20 <----- 错误,若继续使用,可能会导致内存访问出错

P.S. 上面看到显示的类名与我们定义的不完全一样,是因为为了保证每个类名称在程序中的唯一性,编译器会通过一定的规则对原始类名进行改写,如想了解这一规则,可以以name mangling为关键词进行搜索。

二、RTTI 相关数据结构

上文说到typeid将返回一个typeinfo对象的const引用,RTTI就是依赖typeinfo类及其派生类来实现的,下面介绍下这些类。

在NDK路径下\android-ndk-r10c\sources\cxx-stl\gabi++\include\typeinfo文件中有定义这个类:

classtype_info

{public:

virtual ~type_info();

//....

private:

//....

const char*__type_name;// 这个字段记录改写过后的类名

};

在NDK路径下\android-ndk-r10c\sources\cxx-stl\gabi++\src\cxxabi_defines.h有定义一些typeinfo的派生类,此处挑一些我们感兴趣的类列举:

class__shim_type_info :publicstd::type_info{....}

// 无基类的类的typeinfo类型

class__class_type_info :public__shim_type_info{.....}

//只有一个public非虚基类,且基类偏移为0的类的typeinfo

class__si_class_type_info :public__class_type_info{

public:

virtual ~__si_class_type_info();const__class_type_info *__base_type;

//......

}

// 有基类但不满足 __si_class_type_info 约束条件的其它类的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小节中的程序为例,Base、Driver1的对象的内存布局如下:

deriver2的内存布局与deriver1相似,这里没有重复画出。从上图可以看到,每一个类的虚表索引为-1的位置存放着typeinfo的指针,并根据类的不同,该指针指向不同的typeinfo派生类实例。比如Base类无基类,所以其typeinfo指针指向__class_type_info的实例;而Deriver1继承自Base, deriver1在其偏移为0的位置包含一个public非虚基类实例,所以它的typeinfo指针指向__si_class_type_info实例。使用dynamic_cast的时候,正是根据这些typeinfo指针来判断一个基类指针是否可以转换为一个派生类指针。而且由上可见,若一个待操作的类没有虚函数表, typeid也只能返回其静态类型。

下面我们通过反编译代码来验证上面的关系图。

三、逆向过程中利用RTTI恢复类名

将第1小节中生成的可执行程序用IDA Pro打开,此处选用obj\local\armeabi\目录下未经过strip的程序,以方便分析。

根据相关字符串,可以很快定位各个类的typeinfo信息:

各个类的虚函数表结构:

可见,从反编译的代码看,虚表、typeinfo信息关系与第3节中描述一致。(细心的朋友可能有疑问,为什么会产生两个析构函数?对于这个问题,可以以Itanium C++ ABI为关键字搜索了解)

对于通常的逆向分析,都没有没有上面的符号信息的。所以我们可以通过RTTI信息来恢复类名及其类间关系,为逆向工作提供便利。可以按以下步骤进行:

定位__class_type_info, __si_class_type_info, __vmi_class_type_info虚函数表。

查找对这些虚函数表的引用,我们可以得到这些typeinfo派生类的实例地址。而这些实例中type_name字段就表示原始类名。

根据引用这些实例地址,就可以得到相关类的虚表地址,此处我们可以根据上一步得到的原始类名重命名虚表指针。

查找引用这些虚表指针的代码,通常都是类的构造函数,于是我们又可以重命名这些构造函数了。

以上步骤我们都可以通过IDAPython脚本自动完成。

四、小结

其实上面只是分析了最简单的单继承情景,还有诸如多继承、虚继承等情景待分析,由于相关typeinfo类已经例出,相信分析难度不大。

另外需要注意的一个地方,在反汇编后的代码中,并不是直接引用虚表地址,而是引用虚表地址-8的位置,用这个位置+8写入当作虚拟指针。

以上分析过程与结论都来自个人认知,如有错误,欢迎指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 上篇请看:C++编程思想重点笔记(上) 宏的好处与坏处 宏的好处:#与##的使用 三个有用的特征:字符串定义、字符...
    小敏纸阅读 606评论 0 2
  • Introduction to C++ (Season 1) Unit 1: Overview of C++ 第1...
    我是阿喵酱阅读 2,694评论 0 7
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 再读高效c++,颇有收获,现将高效c++中的经典分享如下,希望对你有所帮助。 1、尽量以const \enum\i...
    橙小汁阅读 1,152评论 0 1
  • 小小年纪无烦恼,手舞足蹈乐开怀。 调皮活泼又可爱,幸福天使美照帅。
    文采乐阅读 266评论 16 8