部分内容摘自:android软件安全权威指南:丰生强
从根源上搞懂基础的原理是很有必要的,这样有助于我们更方便的利用它的特性,达到我们的目的。
我把内容主要分为二个部分。原理探索、案例分析
我们在正向开发app编译时,编写的java代码,会编译成java字节码保存在.class后缀的文件中。然后再用dx工具将java字节码转换成dex文件(Dalvik字节码)。在转换的过程中,会将所有java字节码中的所有冗余信息组成一个常量池。例如多个class文件中都存在的字符串"hello world"。转换后将单独存放在一个地方,并且所有类共享。包括方法的签名也会组成常量池。我们将编译好的apk文件解压后就能拿到classes.dex文件。
上面拿到的classes.dex文件包含了apk的可执行代码。Dalvik虚拟机会解析加载文件并执行代码。只要我们了解这个文件格式的组成,那么就可以自己解析这个文件获取到想要的数据。
首先是安卓源码中的dalvik/libdex/DexFile.h这里可以找到dex文件的数据结构。下面贴上源码部分
为了更好的理解。dex文件格式,我们可以用010编辑器打开一个dex文件对照这个结构体来观察一下。

可以看到,这个dex文件由这8个部分组成。
dex_header:dex文件头,指定了dex文件的一些数据,记录了其他数据结构在dex文件中的物理偏移
string_ids:字符串列表(前面说的去掉冗余信息组成的常量池,全局共享使用的)
type_ids:类型签名列表(去掉冗余信息组成的常量池)
proto_ids:方法声明列表(去掉冗余信息组成的常量池)
field_ids:字段列表(去掉冗余信息组成的常量池)
method_ids:方法列表(去掉冗余信息组成的常量池)
class_def:类型结构体列表(去掉冗余信息组成的常量池)
map_list:这里记录了前面7个部分的偏移和大小。
然后我们开始逐个的看各个部分的结构。
先是贴上源码看看这个部分的结构体
这里可以看到,如果在DexHeader中,可以找到其他部分的偏移和大小,以及整个文件的大小,解析出这块数据,其他部分的任意数据,我们都可以获取到。然后再使用对应的结构体来解析。另外留意这里DexHeader的结构体的大小是固定0x70字节的。所以有的脱壳工具中会将70 00 00 00来作为特征在内存中查找dex进行脱壳(比如FRIDA-DEXDump的深度检索)
然后我们贴一下真实classes.dex文件的DexHeader数据是什么样的。

先看看字符串列表的结构体,非常简单,就是字符串的偏移,但是并不是普通的ascii字符串,而是MUTF-8编码的。这个是一个经过修改的UTF-8编码。和传统的UTF-8相似

类型签名列表的结构体也是非常简单,和上面字符串列表差不多
真实数据图如下,可以看到值类型签名都在前面,后面都是引用类型签名。

方法声明的列表的结构体较为复杂,因为方法签名必然是有几点信息构成:返回值类型、参数类型列表(就是每个参数是什么类型)。方法声明的结构体如下
同样看看这个结构的真实数据

字段描述的结构体,我们可以先想象一下,要找一个字段,我们需要些什么:字段所属的类,字段的类型,字段名称。有这些信息,就可以找到各自对应的字段了。接下来看看定义的结构体
然后看一段真实数据

方法描述的结构体,同样先了解找一个方法的几个必须项:方法所属的类,方法的签名(签名中有方法的返回值和方法的参数,也就是上面的proto_ids中记录的),方法的名称。然后下面看结构体
看看一组方法的真实数据

类定义的结构体,这个比较复杂。直接贴上结构体和原文的说明。这里大致可以看出来,和上面的原理差不多,通过这个结构体来描述类的内容。
下面同样展示一组真实数据

上面数据看到里面的class_data也是一个结构体,然后继续看这个类数据的结构体
到这里我们基本看到了在开发中,一个类的所有特征。完整的描述出了一个类的所有信息。
9、DexCode上面最后看到方法的代码是通过上面的DexCode结构体来找到的。最后看下这个结构体
到了这里,存储的就是执行的指令集了。通过执行指令来跑这个方法。下面看一组真实数据

这里看到这个类的具体描述字段和函数,还有访问标志等等信息。然后我们继续看里面函数的执行代码部分。看下面一组数据
观察的函数是MainActivity类的DoTcp函数。DexCode(也就是code_item,也叫codeOff)的偏移地址是0x127ec。
下面观察到DexCode结构体的偏移到指令集insns字段的偏移是codeOff+2+2+2+2+4+4=0x127fc(这里+的2和4是看下面结构体insns前面的字段占了多少个字节计算的,可以当做固定+16个字节)。指令集的长度是0x93。

最后看看指令集的开始数据是0x62、0xe26、0x11a、0x232。但是我们要注意前面有说明,这里是两字节空间对齐。所以,这里的值我们应该前面填充一下。
前面四个字节我们要看做0x0062、0x0e26、0x011a、0x0232。但是我们还要注意,还有个端序问题会影响字节的顺序,这里是小端序,所以我们再调整下
前面四个字节我们要看做0x6200、0x260e、0x1a01、0x3202。把这段指令集的数据看明白后,我们用gda打开这个dex文件。然后找到对应的方法,查看一下。
然后发现数据对上了。这里存储的果然就是我们dex分析方法的字节码了。

在书中的意思是,Dalvik虚拟机解析Dex后,将其映射成DexMapList的数据结构,然后在里面可以找到前面8个部分的偏移和大小。先看看结构体
每个DexMapItem对应了一块数据,例如type=kDexTypeHeaderItem则对应DexHeader的偏移地址和大小。下面看真实数据

这里就能看到string_ids的偏移和大小。如此,根据这个map_list就能找到所有块的数据了。
那么在源码中是如何使用这个数据的呢,我好奇的翻了一下。然后再dex文件优化的流程中dexSwapAndVerify函数找到了使用的地方。
这个函数中获取了DexMapList。然后交给swapMap函数来处理。
通过这里的例子。我们可以看到他是如何使用这个map_list来访问所有的部分的。到这里dex文件的格式基本差不多了。
然后我们看看一个实战的项目。大佬写的fart中的py部分就的运用了dex文件格式相关的知识。
FART
这是一个脱壳工具,使用主动调用的方式来解决二代抽取壳。脱出来的数据不止是dex。还有一种.bin的数据,这种数据可以用来辅助我们修复dex的一些没有脱出来的函数。我们先看下.bin数据是什么,下面是.bin中的一组数据
name:是随意填的,因为并不是使用name来找对应函数,而是通过method_idx
method_idx:函数的索引。
offset:函数的偏移
code_item_len:code_item的大小
ins:code_item结构体这段数据的base64编码
另外贴一下这几个数据的dump来源的代码,以防有人把ins当成指令集了。
这几个数据是一个函数最关键的片段。拿到就可以还原出最关键的code_item了。
然后看看fart.py的使用./fart.py -d 431528_29868.dex -i 431528_29868.bin。下面是执行结果,基本对这个dex进行完整的解析,打印出来大多数的数据。

我们可以通过对这个项目的阅读,直观的了解到是如何进行dex文件格式进行解析的。下面开始看看具体的实现流程
然后看看是如何加载.bin文件的
可以看到就是遍历,组装好那些数据,最后保存到一个methodTable里面,索引就是method_id。
再看看最关键的dex_parse
这里看到是通过init_header来解析dex文件中的dexHeader的。我就不贴代码了,整体就是偏移然后取数据。
这里有一点要说的是get_uleb128函数,uleb128在android里面是一个特殊的类型。是一个可变长度的类型。大致意思就是例如第一个字节的最高位,如果是1,则第二个字节也是有效数据,如果第二个字节的最高位也是1,下一个字节也是有效数据,如果最高位不是1,就结束了。最后左移拼接就ok了。
这个类型有没有很眼熟?没错,特别像是protobuf里面的varint编码。所以我觉得完全可以用protobuf包自带的varint解码来获取这个数据。也可以用这种自己写的函数来处理。
继续看后面的关键函数dex_class,这里来处理每一个类的打印
先看看初始化函数,基本和之前的initHeader的差不多,就是偏移取数据,然后保存下来。把classDef相关的数据都读取出来了。
最后就是print函数,这里比较大,就只挑最关键的部分出来说下
最后我们看下怎么获取的bin文件数据的函数
最后还有个打印指令集的部分就不贴了。感兴趣的可以自己看一看
dex2jar
这个工具基本大家都用过。功能非常强大,不过这里只分析下d2j_dex2jar功能。也就是把dex给转换成jar文件。
下载release版本直接./d2j-dex2jar.sh classes.dex,就生成出了对应的classes-dex2jar.jar文件。然后我们直接用其他分析工具就能愉快的看java代码了。那么神奇的事情是如何做到的呢。下面跟踪分析一下大佬的作品。
第一步是找到入口,根据我们上面的使用例子,先搜索下d2j-dex2jar。然后找到了下面的文件
./dex2jar/dex-tools/src/main/java/com/googlecode/dex2jar/tools/Dex2jarCmd.java
先简单的看一下这里的代码,看来这里就是入口函数了。
然后看看doMain的实现
这里调用了抽象方法,doCommandLine,所以继续看看实现
到这里就可以看出来两个最重要的部分了
1、解析dex的 MultiDexFileReader.open方法
2、执行转换的Dex2jar的to方法
下面先看看解析的处理
我们直接看参数为dex文件的情况就好了,所以继续看DexFileReader的构造函数
上面看了dexHeader的解析的核心部分,接下来我们看看转换实现to方法
继续看看doTranslate方法
我们主要观察的是dex的结构和解析,所以就不详细看转换部分了,继续看解析部分的后续accept方法
这里最关键的就是acceptClass来解析具体的类的内容
对类数据进行详细解析后,接着是对字段和方法的解析填充。先看看字段的解析处理
字段填充完毕,然后看看方法是怎么解析填充的。
继续看code_item的解析
最后的acceptInsn指令集的解析方法太大了,我就不放上来了。整个解析填充的流程就完成了。后面就使用解析好的数据进行转换的操作。
可以看到两个案例的解析的方式差不多,总体都是根据结构体的大小偏移来取得想要的数据。最后一层一层的处理。
为了方便使用和测试,我将fart的解析整理了一下,改到了python3运行的。然后整合到了我的整合怪里面。感兴趣的可以看看。刚跑通,不知道有没啥问题,后面我再慢慢修复把。
github:fridaUiTools
贴上效果图

fart的修复方案仅仅是打印出了保存的指令数据。如果我们是想要直接转成.class文件或者是jar文件。是否可行呢。
我设想了两种方案来做,但是最后都被自己给否定了。
1、bin文件中的指令数据直接插入到dex文件的对应数据中,最后保存为一个新的文件。但是这样中间插一段数据,会有大量的偏移数据要修改。
2、和dex2jar的模式一样。构造好一个fileNode对象,里面就不存在偏移的问题了。最后把里面的类数据导出成.class文件。
疑问中的目的主要是想要fart.py跑完后直接输出一个新的dex文件,里面把bin文件的函数都填充到新的dex了。然后可以用jadx之类的工具直接打开查看
最早先从修复的角度想,简单的处理就是把bin中的内容解析成codeitem后,插到method_idx的里面对应的函数去。但是由于指令集的大小和原来不一样。肯定会导致后面数据的偏移全部发生变化。
于是我分析了一下dex2jar。然后发现确实有更好的办法
和原来的目前一样。但是把修复两个字调整一下说法。具体应该叫dex转换dex
先看看dex2jar的做法是:解析dex,dex数据全部展开解析为java结构体,与偏移无关了,然后转换.class的结构,写入文件,最后打包成jar
学习到人家的思路之后。我们可以变化下自己的思路,下面是我设想的两个解决方案。目前还没有做哦。但是感觉可行性非常高
一:fart修改的解决方案
fart中定义dex的完整结构体,fart解析完dex后,直接将数据保存在结构体中,达到了偏移无关了。然后在遍历bin的数据。将code_item数据替换。然后再将整个dex结构体重新解析生成一个新的dex。
二:dex2jar的功能新增解决方案
就是给dex2jar新增一个功能。前面完全按照他的方式解析出fileNode,然后读取bin文件解析出code_item。然后替换fileNode中对应的code_item数据。然后再重新生成回dex文件。
struct DexFile {
/* odex的头 */
const DexOptHeader* pOptHeader;
/* dex文件头,指定了dex文件的一些数据,记录了其他数据结构在dex文件中的物理偏移 */
const DexHeader* pHeader;
/* 索引结构区 */
const DexStringId* pStringIds;
const DexTypeId* pTypeIds;
const DexFieldId* pFieldIds;
const DexMethodId* pMethodIds;
const DexProtoId* pProtoIds;
/* 真实的数据存放 */
const DexClassDef* pClassDefs;
/* 静态链接数据区 */
const DexLink* pLinkData;
/*
* These are mapped out of the "auxillary" section, and may not be
* included in the file.
*/
const DexClassLookup* pClassLookup;
const void* pRegisterMapPool; // RegisterMapClassPool
/* points to start of DEX file data */
const u1* baseAddr;
/* track memory overhead for auxillary structures */
int overhead;
/* additional app-specific data structures associated with the DEX */
//void* auxData;
};
struct DexFile {
/* odex的头 */
const DexOptHeader* pOptHeader;
/* dex文件头,指定了dex文件的一些数据,记录了其他数据结构在dex文件中的物理偏移 */
const DexHeader* pHeader;
/* 索引结构区 */
const DexStringId* pStringIds;
const DexTypeId* pTypeIds;
const DexFieldId* pFieldIds;
const DexMethodId* pMethodIds;
const DexProtoId* pProtoIds;
/* 真实的数据存放 */
const DexClassDef* pClassDefs;
/* 静态链接数据区 */
const DexLink* pLinkData;
/*
* These are mapped out of the "auxillary" section, and may not be
* included in the file.
*/
const DexClassLookup* pClassLookup;
const void* pRegisterMapPool; // RegisterMapClassPool
/* points to start of DEX file data */
const u1* baseAddr;
/* track memory overhead for auxillary structures */
int overhead;
/* additional app-specific data structures associated with the DEX */
//void* auxData;
};
struct DexHeader {
u1 magic[8]; /* 表示是一个有效的dex文件。值一般固定为64 65 78 0A 30 33 35 00(dex.035) */
u4 checksum; /* adler32 checksum dex文件的校验和,用来判断文件是否已经损坏或者篡改 */
u1 signature[kSHA1DigestLen]; /* SHA-1 hash 用来识别未经dexopt优化的dex文件*/
u4 fileSize; /* length of entire file 记录了包括dexHeader在内的整个dex文件的大小*/
u4 headerSize; /* offset to start of next section dexHeader占用的字节数,一般都是0x70*/
u4 endianTag; /* 指定dex运行环境的cpu字节序。预设是ENDIAN_CONSTANT等于0x12345678,也就是默认小端字节序 */
u4 linkSize; /* 链接段的大小 */
u4 linkOff; /* 链接段的偏移 */
u4 mapOff; /* DexMapList结构的文件偏移 */
u4 stringIdsSize; /* 下面都是数据段的大小和文件偏移 */
u4 stringIdsOff;
u4 typeIdsSize;
u4 typeIdsOff;
u4 protoIdsSize;
u4 protoIdsOff;
u4 fieldIdsSize;
u4 fieldIdsOff;
u4 methodIdsSize;
u4 methodIdsOff;
u4 classDefsSize;
u4 classDefsOff;
u4 dataSize;
u4 dataOff;
};
struct DexHeader {
u1 magic[8]; /* 表示是一个有效的dex文件。值一般固定为64 65 78 0A 30 33 35 00(dex.035) */
u4 checksum; /* adler32 checksum dex文件的校验和,用来判断文件是否已经损坏或者篡改 */
u1 signature[kSHA1DigestLen]; /* SHA-1 hash 用来识别未经dexopt优化的dex文件*/
u4 fileSize; /* length of entire file 记录了包括dexHeader在内的整个dex文件的大小*/
u4 headerSize; /* offset to start of next section dexHeader占用的字节数,一般都是0x70*/
u4 endianTag; /* 指定dex运行环境的cpu字节序。预设是ENDIAN_CONSTANT等于0x12345678,也就是默认小端字节序 */
u4 linkSize; /* 链接段的大小 */
u4 linkOff; /* 链接段的偏移 */
u4 mapOff; /* DexMapList结构的文件偏移 */
u4 stringIdsSize; /* 下面都是数据段的大小和文件偏移 */
u4 stringIdsOff;
u4 typeIdsSize;
u4 typeIdsOff;
u4 protoIdsSize;
u4 protoIdsOff;
u4 fieldIdsSize;
u4 fieldIdsOff;
u4 methodIdsSize;
u4 methodIdsOff;
u4 classDefsSize;
u4 classDefsOff;
u4 dataSize;
u4 dataOff;
};
struct DexStringId {
u4 stringDataOff; /* 字符串数据偏移 */
};
struct DexStringId {
u4 stringDataOff; /* 字符串数据偏移 */
};
struct DexTypeId {
u4 descriptorIdx; /* index into stringIds list for type descriptor */
};
struct DexTypeId {
u4 descriptorIdx; /* index into stringIds list for type descriptor */
};
struct DexTypeList {
u4 size; /* dexTypeItem的个数 */
DexTypeItem list[1]; /* entries */
};
struct DexTypeItem {
u2 typeIdx; /* DexTypeId的索引 */
};
struct DexProtoId {
u4 shortyIdx; /* DexStringId列表的索引,方法签名字符串,由返回值和参数类型列表组合*/
u4 returnTypeIdx; /* DexTypeId的索引,返回值的类型 */
u4 parametersOff; /* 指向DexTypeList的偏移,参数类型列表 */
};
struct DexTypeList {
u4 size; /* dexTypeItem的个数 */
DexTypeItem list[1]; /* entries */
};
struct DexTypeItem {
u2 typeIdx; /* DexTypeId的索引 */
};
struct DexProtoId {
u4 shortyIdx; /* DexStringId列表的索引,方法签名字符串,由返回值和参数类型列表组合*/
u4 returnTypeIdx; /* DexTypeId的索引,返回值的类型 */
u4 parametersOff; /* 指向DexTypeList的偏移,参数类型列表 */
};
struct DexFieldId {
u2 classIdx; /* 类的类型,指向DexTypeId的索引,字段所属的类 */
u2 typeIdx; /* 字段类型,指向DexTypeId的索引,字段的类型 */
u4 nameIdx; /* 字段名,指向DexStringId的索引,字段的名称 */
};
struct DexFieldId {
u2 classIdx; /* 类的类型,指向DexTypeId的索引,字段所属的类 */
u2 typeIdx; /* 字段类型,指向DexTypeId的索引,字段的类型 */
u4 nameIdx; /* 字段名,指向DexStringId的索引,字段的名称 */
};
struct DexMethodId {
u2 classIdx; /* 类的类型,指向DexTypeId的索引,方法所属的类 */
u2 protoIdx; /* 声明类型,指向DexProtoId的索引,方法的签名 */
u4 nameIdx; /* 方法名,指向DexStringId索引,方法的名称 */
};
struct DexMethodId {
u2 classIdx; /* 类的类型,指向DexTypeId的索引,方法所属的类 */
u2 protoIdx; /* 声明类型,指向DexProtoId的索引,方法的签名 */
u4 nameIdx; /* 方法名,指向DexStringId索引,方法的名称 */
};
struct DexClassDef {
u4 classIdx; /* 类的类型,指向DexTypeId的索引 */
u4 accessFlags; /* 访问标志 */
u4 superclassIdx; /* 父类的类型,指向DexTypeId的索引 */
u4 interfacesOff; /* 接口,指向DexTypeList的偏移,如果没有接口的声明和实现,值为0 */
u4 sourceFileIdx; /* 类所在的源文件名,指向DexStringId的索引 */
u4 annotationsOff; /* 注释,根据类型不同会有注解类,注解字段,注解方法,注解参数,没有注解值就是0,指向DexAnnotationsDirectoryItem的结构体 */
u4 classDataOff; /* 类的数据部分,指向DexClassData结构的偏移 */
u4 staticValuesOff; /* 类中的静态数据,指向DexEncodeArray结构的偏移 */
};
struct DexClassDef {
u4 classIdx; /* 类的类型,指向DexTypeId的索引 */
u4 accessFlags; /* 访问标志 */
u4 superclassIdx; /* 父类的类型,指向DexTypeId的索引 */
u4 interfacesOff; /* 接口,指向DexTypeList的偏移,如果没有接口的声明和实现,值为0 */
u4 sourceFileIdx; /* 类所在的源文件名,指向DexStringId的索引 */
u4 annotationsOff; /* 注释,根据类型不同会有注解类,注解字段,注解方法,注解参数,没有注解值就是0,指向DexAnnotationsDirectoryItem的结构体 */
u4 classDataOff; /* 类的数据部分,指向DexClassData结构的偏移 */
u4 staticValuesOff; /* 类中的静态数据,指向DexEncodeArray结构的偏移 */
};
/* expanded form of a class_data_item header */
struct DexClassDataHeader {
u4 staticFieldsSize; /* 静态字段的个数 */
u4 instanceFieldsSize; /* 实例字段的个数 */
u4 directMethodsSize; /* 直接方法的个数 */
u4 virtualMethodsSize; /* 虚方法的个数 */
};
/* expanded form of encoded_field */
struct DexField {
u4 fieldIdx; /* 指向DexFieldId的索引 */
u4 accessFlags; /* 访问标志 */
};
/* expanded form of encoded_method */
struct DexMethod {
u4 methodIdx; /* 指向DexMethodId的索引 */
u4 accessFlags; /* 访问标志 */
u4 codeOff; /* 指向DexCode结构的偏移 */
};
struct DexClassData {
DexClassDataHeader header; /* 指定字段和方法的个数 */
DexField* staticFields; /* 静态字段 */
DexField* instanceFields; /* 实例字段 */
DexMethod* directMethods; /* 直接方法 */
DexMethod* virtualMethods; /* 虚方法 */
};
/* expanded form of a class_data_item header */
struct DexClassDataHeader {
u4 staticFieldsSize; /* 静态字段的个数 */
u4 instanceFieldsSize; /* 实例字段的个数 */
u4 directMethodsSize; /* 直接方法的个数 */
u4 virtualMethodsSize; /* 虚方法的个数 */
};
/* expanded form of encoded_field */
struct DexField {
u4 fieldIdx; /* 指向DexFieldId的索引 */
u4 accessFlags; /* 访问标志 */
};
/* expanded form of encoded_method */
struct DexMethod {
u4 methodIdx; /* 指向DexMethodId的索引 */
u4 accessFlags; /* 访问标志 */
u4 codeOff; /* 指向DexCode结构的偏移 */
};
struct DexClassData {
DexClassDataHeader header; /* 指定字段和方法的个数 */
DexField* staticFields; /* 静态字段 */
DexField* instanceFields; /* 实例字段 */
DexMethod* directMethods; /* 直接方法 */
DexMethod* virtualMethods; /* 虚方法 */
};
struct DexCode {
u2 registersSize; /* 使用寄存器的个数 */
u2 insSize; /* 参数的个数 */
u2 outsSize; /* 调用其他方法时使用的寄存器个数 */
u2 triesSize; /* try/catch语句的个数 */
u4 debugInfoOff; /* 指向调试信息的偏移 */
u4 insnsSize; /* 指令集的个数,以2字节为单位 */
u2 insns[1]; /* 指令集 */
/* 2字节空间用于对齐 */
/* followed by try_item[triesSize] DexTry结构体 */
/* followed by uleb128 handlersSize */
/* followed by catch_handler_item[handlersSize] DexCatchHandler结构体 */
};
struct DexCode {
u2 registersSize; /* 使用寄存器的个数 */
u2 insSize; /* 参数的个数 */
u2 outsSize; /* 调用其他方法时使用的寄存器个数 */
u2 triesSize; /* try/catch语句的个数 */
u4 debugInfoOff; /* 指向调试信息的偏移 */
u4 insnsSize; /* 指令集的个数,以2字节为单位 */
u2 insns[1]; /* 指令集 */
/* 2字节空间用于对齐 */
/* followed by try_item[triesSize] DexTry结构体 */
/* followed by uleb128 handlersSize */
/* followed by catch_handler_item[handlersSize] DexCatchHandler结构体 */
};
struct DexMapItem {
u2 type; /* kDexType开头的类型 */
u2 unused; /* 未使用,用于字节对齐 */
u4 size; /* 数据的大小 */
u4 offset; /* 指定类型数据的文件偏移 */
};
/*
* Direct-mapped "map_list".
*/
struct DexMapList {
u4 size; /* 有多少个DexMapItem */
DexMapItem list[1]; /* entries */
};
enum {
kDexTypeHeaderItem = 0x0000,
kDexTypeStringIdItem = 0x0001,
kDexTypeTypeIdItem = 0x0002,
kDexTypeProtoIdItem = 0x0003,
kDexTypeFieldIdItem = 0x0004,
kDexTypeMethodIdItem = 0x0005,
kDexTypeClassDefItem = 0x0006,
kDexTypeCallSiteIdItem = 0x0007,
kDexTypeMethodHandleItem = 0x0008,
kDexTypeMapList = 0x1000,
kDexTypeTypeList = 0x1001,
kDexTypeAnnotationSetRefList = 0x1002,
kDexTypeAnnotationSetItem = 0x1003,
kDexTypeClassDataItem = 0x2000,
kDexTypeCodeItem = 0x2001,
kDexTypeStringDataItem = 0x2002,
kDexTypeDebugInfoItem = 0x2003,
kDexTypeAnnotationItem = 0x2004,
kDexTypeEncodedArrayItem = 0x2005,
kDexTypeAnnotationsDirectoryItem = 0x2006,
};
struct DexMapItem {
u2 type; /* kDexType开头的类型 */
u2 unused; /* 未使用,用于字节对齐 */
u4 size; /* 数据的大小 */
u4 offset; /* 指定类型数据的文件偏移 */
};
/*
* Direct-mapped "map_list".
*/
struct DexMapList {
u4 size; /* 有多少个DexMapItem */
DexMapItem list[1]; /* entries */
};
enum {
kDexTypeHeaderItem = 0x0000,
kDexTypeStringIdItem = 0x0001,
kDexTypeTypeIdItem = 0x0002,
kDexTypeProtoIdItem = 0x0003,
kDexTypeFieldIdItem = 0x0004,
kDexTypeMethodIdItem = 0x0005,
kDexTypeClassDefItem = 0x0006,
kDexTypeCallSiteIdItem = 0x0007,
kDexTypeMethodHandleItem = 0x0008,
kDexTypeMapList = 0x1000,
kDexTypeTypeList = 0x1001,
kDexTypeAnnotationSetRefList = 0x1002,
kDexTypeAnnotationSetItem = 0x1003,
kDexTypeClassDataItem = 0x2000,
kDexTypeCodeItem = 0x2001,
kDexTypeStringDataItem = 0x2002,
kDexTypeDebugInfoItem = 0x2003,
kDexTypeAnnotationItem = 0x2004,
kDexTypeEncodedArrayItem = 0x2005,
kDexTypeAnnotationsDirectoryItem = 0x2006,
};
//字节排序优化
int dexSwapAndVerify(u1* addr, size_t len)
{
...
if (okay) {
/*
* Look for the map. Swap it and then use it to find and swap
* everything else.
*/
if (pHeader->mapOff != 0) {
DexFile dexFile;
DexMapList* pDexMap = (DexMapList*) (addr + pHeader->mapOff);
okay = okay && swapMap(&state, pDexMap);
okay = okay && swapEverythingButHeaderAndMap(&state, pDexMap);
dexFileSetupBasicPointers(&dexFile, addr);
state.pDexFile = &dexFile;
okay = okay && crossVerifyEverything(&state, pDexMap);
} else {
ALOGE("ERROR: No map found; impossible to byte-swap and verify");
okay = false;
}
}
...
return !okay; // 0 == success
}
//字节排序优化
int dexSwapAndVerify(u1* addr, size_t len)
{
...
if (okay) {
/*
* Look for the map. Swap it and then use it to find and swap
* everything else.
*/
if (pHeader->mapOff != 0) {
DexFile dexFile;
DexMapList* pDexMap = (DexMapList*) (addr + pHeader->mapOff);
okay = okay && swapMap(&state, pDexMap);
okay = okay && swapEverythingButHeaderAndMap(&state, pDexMap);
dexFileSetupBasicPointers(&dexFile, addr);
state.pDexFile = &dexFile;
okay = okay && crossVerifyEverything(&state, pDexMap);
} else {
ALOGE("ERROR: No map found; impossible to byte-swap and verify");
okay = false;
}
}
...
return !okay; // 0 == success
}
static bool swapMap(CheckState* state, DexMapList* pMap)
{
DexMapItem* item = pMap->list;
u4 count;
u4 dataItemCount = 0; // Total count of items in the data section.
u4 dataItemsLeft = state->pHeader->dataSize; // See use below.
u4 usedBits = 0; // Bit set: one bit per section
bool first = true;
u4 lastOffset = 0;
SWAP_FIELD4(pMap->size);
count = pMap->size;
const u4 sizeOfItem = (u4) sizeof(DexMapItem);
CHECK_LIST_SIZE(item, count, sizeOfItem);
while (count--) {
SWAP_FIELD2(item->type);
SWAP_FIELD2(item->unused);
SWAP_FIELD4(item->size);
SWAP_OFFSET4(item->offset);
if (first) {
first = false;
} else if (lastOffset >= item->offset) {
ALOGE("Out-of-order map item: %#x then %#x",
lastOffset, item->offset);
return false;
}
if (item->offset >= state->pHeader->fileSize) {
ALOGE("Map item after end of file: %x, size %#x",
item->offset, state->pHeader->fileSize);
return false;
}
if (isDataSectionType(item->type)) {
u4 icount = item->size;
/*
* This sanity check on the data section items ensures that
* there are no more items than the number of bytes in
* the data section.
*/
if (icount > dataItemsLeft) {
ALOGE("Unrealistically many items in the data section: "
"at least %d", dataItemCount + icount);
return false;
}
dataItemsLeft -= icount;
dataItemCount += icount;
}
u4 bit = mapTypeToBitMask(item->type);
if (bit == 0) {
return false;
}
if ((usedBits & bit) != 0) {
ALOGE("Duplicate map section of type %#x", item->type);
return false;
}
if (item->type == kDexTypeCallSiteIdItem) {
state->pCallSiteIds = item;
} else if (item->type == kDexTypeMethodHandleItem) {
state->pMethodHandleItems = item;
}
usedBits |= bit;
lastOffset = item->offset;
item++;
}
if ((usedBits & mapTypeToBitMask(kDexTypeHeaderItem)) == 0) {
ALOGE("Map is missing header entry");
return false;
}
if ((usedBits & mapTypeToBitMask(kDexTypeMapList)) == 0) {
ALOGE("Map is missing map_list entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeStringIdItem)) == 0)
&& ((state->pHeader->stringIdsOff != 0)
|| (state->pHeader->stringIdsSize != 0))) {
ALOGE("Map is missing string_ids entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeTypeIdItem)) == 0)
&& ((state->pHeader->typeIdsOff != 0)
|| (state->pHeader->typeIdsSize != 0))) {
ALOGE("Map is missing type_ids entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeProtoIdItem)) == 0)
&& ((state->pHeader->protoIdsOff != 0)
|| (state->pHeader->protoIdsSize != 0))) {
ALOGE("Map is missing proto_ids entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeFieldIdItem)) == 0)
&& ((state->pHeader->fieldIdsOff != 0)
|| (state->pHeader->fieldIdsSize != 0))) {
ALOGE("Map is missing field_ids entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeMethodIdItem)) == 0)
&& ((state->pHeader->methodIdsOff != 0)
|| (state->pHeader->methodIdsSize != 0))) {
ALOGE("Map is missing method_ids entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeClassDefItem)) == 0)
&& ((state->pHeader->classDefsOff != 0)
|| (state->pHeader->classDefsSize != 0))) {
ALOGE("Map is missing class_defs entry");
return false;
}
state->pDataMap = dexDataMapAlloc(dataItemCount);
if (state->pDataMap == NULL) {
ALOGE("Unable to allocate data map (size %#x)", dataItemCount);
return false;
}
return true;
}
static bool swapMap(CheckState* state, DexMapList* pMap)
{
DexMapItem* item = pMap->list;
u4 count;
u4 dataItemCount = 0; // Total count of items in the data section.
u4 dataItemsLeft = state->pHeader->dataSize; // See use below.
u4 usedBits = 0; // Bit set: one bit per section
bool first = true;
u4 lastOffset = 0;
SWAP_FIELD4(pMap->size);
count = pMap->size;
const u4 sizeOfItem = (u4) sizeof(DexMapItem);
CHECK_LIST_SIZE(item, count, sizeOfItem);
while (count--) {
SWAP_FIELD2(item->type);
SWAP_FIELD2(item->unused);
SWAP_FIELD4(item->size);
SWAP_OFFSET4(item->offset);
if (first) {
first = false;
} else if (lastOffset >= item->offset) {
ALOGE("Out-of-order map item: %#x then %#x",
lastOffset, item->offset);
return false;
}
if (item->offset >= state->pHeader->fileSize) {
ALOGE("Map item after end of file: %x, size %#x",
item->offset, state->pHeader->fileSize);
return false;
}
if (isDataSectionType(item->type)) {
u4 icount = item->size;
/*
* This sanity check on the data section items ensures that
* there are no more items than the number of bytes in
* the data section.
*/
if (icount > dataItemsLeft) {
ALOGE("Unrealistically many items in the data section: "
"at least %d", dataItemCount + icount);
return false;
}
dataItemsLeft -= icount;
dataItemCount += icount;
}
u4 bit = mapTypeToBitMask(item->type);
if (bit == 0) {
return false;
}
if ((usedBits & bit) != 0) {
ALOGE("Duplicate map section of type %#x", item->type);
return false;
}
if (item->type == kDexTypeCallSiteIdItem) {
state->pCallSiteIds = item;
} else if (item->type == kDexTypeMethodHandleItem) {
state->pMethodHandleItems = item;
}
usedBits |= bit;
lastOffset = item->offset;
item++;
}
if ((usedBits & mapTypeToBitMask(kDexTypeHeaderItem)) == 0) {
ALOGE("Map is missing header entry");
return false;
}
if ((usedBits & mapTypeToBitMask(kDexTypeMapList)) == 0) {
ALOGE("Map is missing map_list entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeStringIdItem)) == 0)
&& ((state->pHeader->stringIdsOff != 0)
|| (state->pHeader->stringIdsSize != 0))) {
ALOGE("Map is missing string_ids entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeTypeIdItem)) == 0)
&& ((state->pHeader->typeIdsOff != 0)
|| (state->pHeader->typeIdsSize != 0))) {
ALOGE("Map is missing type_ids entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeProtoIdItem)) == 0)
&& ((state->pHeader->protoIdsOff != 0)
|| (state->pHeader->protoIdsSize != 0))) {
ALOGE("Map is missing proto_ids entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeFieldIdItem)) == 0)
&& ((state->pHeader->fieldIdsOff != 0)
|| (state->pHeader->fieldIdsSize != 0))) {
ALOGE("Map is missing field_ids entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeMethodIdItem)) == 0)
&& ((state->pHeader->methodIdsOff != 0)
|| (state->pHeader->methodIdsSize != 0))) {
ALOGE("Map is missing method_ids entry");
return false;
}
if (((usedBits & mapTypeToBitMask(kDexTypeClassDefItem)) == 0)
&& ((state->pHeader->classDefsOff != 0)
|| (state->pHeader->classDefsSize != 0))) {
ALOGE("Map is missing class_defs entry");
return false;
}
state->pDataMap = dexDataMapAlloc(dataItemCount);
if (state->pDataMap == NULL) {
ALOGE("Unable to allocate data map (size %#x)", dataItemCount);
return false;
}
return true;
}
{name:ooxx,method_idx:1830,offset:180516,code_item_len:24,ins:AQABAAEAAAB+oAMABAAAAHAQwAsAAA4A};
{name:ooxx,method_idx:1830,offset:180516,code_item_len:24,ins:AQABAAEAAAB+oAMABAAAAHAQwAsAAA4A};
var base64ptr = funcBase64_encode(ptr(codeitemstartaddr), codeitemlength, ptr(base64lengthptr));
var b64content = ptr(base64ptr).readCString(base64lengthptr.readInt());
funcFreeptr(ptr(base64ptr));
var content = "{name:ooxx,method_idx:" + dex_method_index_ + ",offset:" + dex_code_item_offset_ + ",code_item_len:" + codeitemlength + ",ins:" + b64content + "};";
var base64ptr = funcBase64_encode(ptr(codeitemstartaddr), codeitemlength, ptr(base64lengthptr));
var b64content = ptr(base64ptr).readCString(base64lengthptr.readInt());
funcFreeptr(ptr(base64ptr));
var content = "{name:ooxx,method_idx:" + dex_method_index_ + ",offset:" + dex_code_item_offset_ + ",code_item_len:" + codeitemlength + ",ins:" + b64content + "};";
def main():
dex = dex_parser(filename)
if __name__ == "__main__":
init()
methodTable.clear()
parseinsfile()
print "methodTable length:" + str(len(methodTable))
main()
def main():
dex = dex_parser(filename)
if __name__ == "__main__":
init()
methodTable.clear()
parseinsfile()
print "methodTable length:" + str(len(methodTable))
main()
def parseinsfile():
global insfilename
insfile=open(insfilename)
content=insfile.read()
insfile.close()
insarray=re.findall(r"{name:(.*?),method_idx:(.*?),offset:(.*?),code_item_len:(.*?),ins:(.*?)}",content)
for eachins in insarray:
methodname=eachins[0].replace(" ","")
number=(int)(eachins[1])
offset=(int)(eachins[2])
inssize=int(eachins[3])
ins=eachins[4]
tempmethod=CodeItem(number,methodname,inssize,ins)
methodTable[number]=tempmethod
def parseinsfile():
global insfilename
insfile=open(insfilename)
content=insfile.read()
insfile.close()
insarray=re.findall(r"{name:(.*?),method_idx:(.*?),offset:(.*?),code_item_len:(.*?),ins:(.*?)}",content)
for eachins in insarray:
methodname=eachins[0].replace(" ","")
number=(int)(eachins[1])
offset=(int)(eachins[2])
inssize=int(eachins[3])
ins=eachins[4]
tempmethod=CodeItem(number,methodname,inssize,ins)
methodTable[number]=tempmethod
class dex_parser:
def __init__(self,filename):
global DEX_MAGIC
global DEX_OPT_MAGIC
self.m_javaobject_id = 0
self.m_filename = filename
self.m_fd = open(filename,"rb")
self.m_content = self.m_fd.read()
self.m_fd.close()
self.m_dex_optheader = None
self.m_class_name_id = {}
self.string_table = []
if self.m_content[0:4] == DEX_OPT_MAGIC:
self.init_optheader(self.m_content)
self.init_header(self.m_content,0x40)
elif self.m_content[0:4] == DEX_MAGIC:
self.init_header(self.m_content,0)
bOffset = self.m_stringIdsOff
if self.m_stringIdsSize > 0:
for i in xrange(0,self.m_stringIdsSize):
offset, = struct.unpack_from("I",self.m_content,bOffset + i * 4)
if i == 0:
start = offset
else:
skip, length = get_uleb128(self.m_content[start:start+5])
self.string_table.append(self.m_content[start+skip:offset-1])
start = offset
for i in xrange(start,len(self.m_content)):
if self.m_content[i]==chr(0):
self.string_table.append(self.m_content[start+1:i])
break
for i in xrange(0,self.m_classDefSize):
str1 = self.getclassname(i)
self.m_class_name_id[str1] = i
for i in xrange(0,self.m_classDefSize):
str1 = self.getclassname(i)
dex_class(self,i).printf(self)
pass
class dex_parser:
def __init__(self,filename):
global DEX_MAGIC
global DEX_OPT_MAGIC
self.m_javaobject_id = 0
self.m_filename = filename
self.m_fd = open(filename,"rb")
self.m_content = self.m_fd.read()
self.m_fd.close()
self.m_dex_optheader = None
self.m_class_name_id = {}
self.string_table = []
if self.m_content[0:4] == DEX_OPT_MAGIC:
self.init_optheader(self.m_content)
self.init_header(self.m_content,0x40)
elif self.m_content[0:4] == DEX_MAGIC:
self.init_header(self.m_content,0)
bOffset = self.m_stringIdsOff
if self.m_stringIdsSize > 0:
for i in xrange(0,self.m_stringIdsSize):
offset, = struct.unpack_from("I",self.m_content,bOffset + i * 4)
if i == 0:
start = offset
else:
skip, length = get_uleb128(self.m_content[start:start+5])
self.string_table.append(self.m_content[start+skip:offset-1])
start = offset
for i in xrange(start,len(self.m_content)):
if self.m_content[i]==chr(0):
self.string_table.append(self.m_content[start+1:i])
break
for i in xrange(0,self.m_classDefSize):
str1 = self.getclassname(i)
self.m_class_name_id[str1] = i
for i in xrange(0,self.m_classDefSize):
str1 = self.getclassname(i)
dex_class(self,i).printf(self)
pass
def varint_encode(number):
buf = b''
while True:
towrite = number & 0x7f
number >>= 7
if number:
buf += struct.pack("B",(towrite | 0x80))
else:
buf += struct.pack("B",towrite)
break
return buf
def varint_decode(buff):
shift = 0
result = 0
idx=0
while True:
if idx>len(buff):
return ""
i = buff[idx]
idx+=1
result |= (i & 0x7f) << shift
shift += 7
if not (i & 0x80):
break
return result
def varint_encode(number):
buf = b''
while True:
towrite = number & 0x7f
number >>= 7
if number:
buf += struct.pack("B",(towrite | 0x80))
else:
buf += struct.pack("B",towrite)
break
return buf
def varint_decode(buff):
shift = 0
result = 0
idx=0
while True:
if idx>len(buff):
return ""
i = buff[idx]
idx+=1
result |= (i & 0x7f) << shift
shift += 7
if not (i & 0x80):
break
return result
class dex_class:
def __init__(self,dex_object,classid):
if classid >= dex_object.m_classDefSize:
return ""
offset = dex_object.m_classDefOffset + classid * struct.calcsize("8I")
self.offset = offset
format = "I"
self.thisClass,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.modifiers,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.superClass,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.interfacesOff,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.sourceFileIdx,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.annotationsOff,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.classDataOff,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.staticValuesOff,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.index = classid
self.interfacesSize = 0
if self.interfacesOff != 0:
self.interfacesSize, = struct.unpack_from("I",dex_object.m_content,self.interfacesOff)
if self.classDataOff != 0:
offset = self.classDataOff
count,self.numStaticFields = get_uleb128(dex_object.m_content[offset:])
offset += count
count,self.numInstanceFields = get_uleb128(dex_object.m_content[offset:])
offset += count
count,self.numDirectMethods = get_uleb128(dex_object.m_content[offset:])
offset += count
count,self.numVirtualMethods = get_uleb128(dex_object.m_content[offset:])
else:
self.numStaticFields = 0
self.numInstanceFields = 0
self.numDirectMethods = 0
self.numVirtualMethods = 0
class dex_class:
def __init__(self,dex_object,classid):
if classid >= dex_object.m_classDefSize:
return ""
offset = dex_object.m_classDefOffset + classid * struct.calcsize("8I")
self.offset = offset
format = "I"
self.thisClass,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.modifiers,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.superClass,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.interfacesOff,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.sourceFileIdx,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.annotationsOff,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.classDataOff,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.staticValuesOff,=struct.unpack_from(format,dex_object.m_content,offset)
offset += struct.calcsize(format)
self.index = classid
self.interfacesSize = 0
if self.interfacesOff != 0:
self.interfacesSize, = struct.unpack_from("I",dex_object.m_content,self.interfacesOff)
if self.classDataOff != 0:
offset = self.classDataOff
count,self.numStaticFields = get_uleb128(dex_object.m_content[offset:])
offset += count
count,self.numInstanceFields = get_uleb128(dex_object.m_content[offset:])
offset += count
[培训]传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-7-16 09:22
被misskings编辑
,原因: 修改细节