Android学习笔记3--逆向1--Dex
来源:互联网 发布:zigbee实战演练 源码 编辑:程序博客网 时间:2024/05/19 12:41
DEX文件
Dalvik虚拟机作为Android平台的核心组件,拥有如下几个特点:
1) 原理上运行Dalvik字节码,常量池采用32位索引值,基于32位寄存器架构并拥有一套完整的指令系统,采用了DEX可执行文件格式。
2) 功能上提供对象生命周期管理、堆栈管理、线程管理、安全和异常管理以及垃圾回收等重要功能;
3) 运行上所有app都运行在Android系统进程里,每个进程对应着一个Dalvik虚拟机实例。
odex是一种经过优化的dex文件格式,大部分厂商发布的手机上都会做odex处理。odex处理过的镜像包,jar/apk包中是不存在dex文件的,其系统jar/apk包的可执行文件是存在同目录下的同名.odex 文件。对于odex文件,只需知道两点:
1) odex文件是dex文件的一个超集,会在dex文件基础上附加一些数据;
2) odex文件对镜像包中的/system/framework(可具体到目录中core.jar、ext.jar、framework.jar、services.jar和android.policy.jar这5个jar包)具有强依赖性,odex文件在不同的framework上是无法被dalvik虚拟机加载的。
构建文件
构建过程
看一下这篇文章
构件流程如下:
1) 用appt和aidl和ndk将源码和文件进行整理。
2) 用javac将java源文件编译为class文件。
3) 用dx将class文件转为dex文件(还有消除冗余,压缩常量池)。
4) 用apkbuilder将上一步的所有资源打包成apk文件。
5) 用signapk将apk进行签名。
6) 用zipalign对签名后的apk进行对齐。
安装方式如下:
1) 通过系统程序安装。开机时由PMS安装,无安装界面。所在位置为system/app目录。
2) 通过市场或者SD卡安装。手动由PI.apk安装,有安装界面。
3) 由ADB安装。手动由PI.apk安装,有安装界面。
工具介绍
smali和baksmali是针对DEX执行文件格式的第三方汇编器和反汇编器。apktool是一个编译与反编译apk的第三方工具,该工具中使用了smail和baksmail的代码。反汇编后DEX文件会产生smali后缀的代码文件。smali代码拥有特定的格式与语法,smali语言是对Dalvik虚拟机字节码的一种解释。
# 反编译apk时可能需要资源apk,这时需要先安装再反编译.# -t指定安装到本机资源存储目录中apk的名字扩展部分。-p指定安装到本机资源存储目录的子位置apktool if res.apk -t ui -p test/1 #安装资源apk到本地目录(if:install-framework)# > I: Framework installed to: test/1/res-ui.apkapktool -f d ./test.apk [-o ./spy] # 反编译apk并将反编译文件输出到spy文件夹中(f:force,d:decode)apktool -f b ./spy [-o ./test.apk] # 编译修改后的spy文件夹并打包成apk文件(f:force,b:build)
使用apktool是无法直接对odex文件进行反编译,我们必须先使用baksmali进行deodex处理。
# 通过aapt命令获取apk使用的SDK的版本号targetSdkVersion为15aapt d badging Calculator.apk | grep targetSdkVersion # 把apk文件中其他的资源等文件反编译出来apktool d Calculator.apk -o ./Calculator # 用baksmali对Calculator.odex去odex并反编译成smali代码文件# -x指定需deodex的odex文件,-d指明依赖的framework目录,-a指明sdk版本,-o 指定去输出目录。baksmali -x Calculator.odex -d framework -a 15 -o Calculator/smali#修改完相应的smali文件和资源文件后,重新编译打包成apk文件apktool b Calculator -o Calculator.apk
当生成dex后可以通过adb命令push到手机上,并使用dalvikvm命令运行这个dex文件,执行命令如下:
adb push Test.dex /data/local/adb shell dalvikvm -cp /data/local/Test.dex Test
文件结构
DEX数据类型
点这里看sleb128讲解。
u1 # 等同于uint8_t,表示1字节的无符号数u2 # 等同于uint16_t,表示2字节的无符号数u4 # 等同于uint32_t,表示4字节的无符号数u8 # 等同于uint64_t,表示8字节的无符号数sleb128 # 有符号LEB128,可变长度1~5字节uleb128 # 无符号LEB128,可变长度1~5字节uleb128p1 # 无符号LEB128值加1,可变长1~5字节
dex文件结构
参考网上的这篇文章。DEX是十六进制的小端字节。
struct DexFile{ const DexOptHeader* pOptHeader; const DexHeader* pHeader; const DexStringId* pStringIds; const DexTypeId* pTypeIds; //... const DexLink* pLinkData; const DexClassLoopkup* pClassLookup; const void* pRegisterMapPool; const u1* baseAddr; int overead;}
头部(DexHeader)段
DexHeader:是DEX文件头,其结构很固定,占用0x70个字节。
magic[8]:dex版本标志,共1个字节。目前为固定值64 65 78 0a 30 33 35 00。
checksum:文件校验码,使用alder32算法校验文件除去magic、checksum外余下的所有文件区域,用于检查文件错误。
signature[20]:共1个字节。使用SHA-1算法hash除去magic,checksum和signature外余下的所有文件区域 ,用于唯一识别本文件 。
fileSize:DEX文件的长度,包括DexHeader。
headerSize:DexHeader本身的长度,一般固定为0x70字节。
endianTag:指定了DEX运行环境的cpu字节序,预设值ENDIAN_CONSTANT等于0x12345678(00 bc 61 4e),表示默认采用Little-Endian字节序。mapOff:索引段之DexMapList文件的偏移。
stringIdsSize和stringIdsOff:索引段之DexStringId文件的大小与偏移。
typeIdsSize和typeIdsOff:索引段之DexTypeId文件的大小与偏移。
protoIdsSize和protoIdsSize:索引段之DexProtoId文件的大小与偏移。
fieldIdsSize和fieldIdsSize:索引段之DexFieldId文件的大小与偏移。
methodIdsSize和methodIdsSize:索引段之DexMethodId文件的大小与偏移。
classDefsSize和classDefsOff:索引段之DexClassDef文件的大小与偏移。dataSize和dataOff:数据段文件的大小与偏移。
linkSize和linkOff:链接段文件的大小与偏移,大多数情况下它们的值都为0。link_size:LinkSection大小,如果为0则表示该DEX文件不是静态链接。
link_off:LinkSection距离DEX头的偏移地址,如果LinkSize为0,此值也会为0。
索引(DexMapList)段
详情参考这里。Dalvik虚拟机解析DEX文件的内容,最终将其映射成DexMapList数据结构,它实际上包含所有其他区段的结构大纲。DexHeader中的mapOff字段指明了DexMapList结构在DEX文件中的偏移。
struct DexMapList { u4 size; /* DexMapItem的个数 */ DexMapItem list[1]; /* DexMapItem的结构 */};struct DexMapItem { u2 type; /* 类型 */ u2 unused; /* 未使用,用于字节对齐 */ u4 size; /* type指定类型的个数,它们在dex文件中连续存放 */ u4 offset; /* 指定类型数据的文件偏移 */};/* type字段为一个枚举常量,通过类型名称很容易判断它的具体类型 */enum { kDexTypeHeaderItem = 0x0000, kDexTypeStringIdItem = 0x0001, kDexTypeTypeIdItem = 0x0002, kDexTypeProtoIdItem = 0x0003, kDexTypeFieldIdItem = 0x0004, kDexTypeMethodIdItem = 0x0005, kDexTypeClassDefItem = 0x0006, kDexTypeMapList = 0x1000, kDexTypeTypeList = 0x1001, kDexTypeAnnotationSetRefList = 0x1002, kDexTypeAnnotationSetItem = 0x1003, kDexTypeClassDataItem = 0x2000, kDexTypeCodeItem = 0x2001, kDexTypeStringDataItem = 0x2002, kDexTypeDebugInfoItem = 0x2003, kDexTypeAnnotationItem = 0x2004, kDexTypeEncodedArrayItem = 0x2005, kDexTypeAnnotationsDirectoryItem = 0x2006,};
1 DexStringId
// 在DexMapList中(在header中也可以找到)找到其偏移和个数struct DexStringId{ u4 stringDataOff; // 字符串数据偏移}
2.1 DexTypeId
// 在DexMapList中(在header中也可以找到)找到其偏移和个数struct DexTypeId{ u4 descriptorIdx; // 指向stringIds的索引}struct DexTypeList{ u4 size; /* DexTypeItem的个数 */ DexTypeItem list[1]; /* DexTypeItem的结构 */}struct DexTypeItem{ u2 typeIdx; // 指向DexTypeId的索引}
2.2 DexProtoId
// 在DexMapList中(在header中也可以找到)找到其偏移和个数struct DexProtoId{ u4 shortyIdx; // 指向DexStringId的索引,方法名类型 u4 returnTypeIdx; // 指向DexTypeId的索引,方法返回类型 u4 parametersOff; // 指向DexTypeList的偏移,方法参数签名}
3.1 DexFieldId
// 在DexMapList中(在header中也可以找到)找到其偏移和个数struct DexProtoId{ u2 classIdx; // 指向DexTypeId for defining class u2 typeIdx; // 指向DexTypeId for method prototype u4 nameIdx; // 指向DexStringId for method name}
3.2 DexMethodId
// 在DexMapList中(在header中也可以找到)找到其偏移和个数struct DexMethodId { u2 classIdx; // 指向DexTypeId for defining class u2 protoIdx; // 指向DexProtoId for method prototype u4 nameIdx; // 指向DexStringId for method name};
3.3 DexClassDef
ClassDefs以4字节对齐,即总大小为4 * 8 * ClassDefsSize。详情请看网上博客和谷歌官方。
// 在DexMapList中(在header中也可以找到)找到其偏移和个数struct DexClassDef { u4 classIdx; // 指向DexTypeId的索引,表示类的类型 u4 accessFlags; // 类的访问标识,如public、final等。 u4 superclassIdx; // 指向DexTypeId的索引,表示父类类型(如没值为NO_INDEX) u4 interfacesOff; // 指向DexTypeList的文件偏移,表示接口(如没值为0) u4 sourceFileIdx; // 指向DexStringId的索引,表示源文件名(如没值为NO_INDEX) u4 annotationsOff; // 指向某文件偏移,表示注解(如没值为0) u4 classDataOff; // 指向DexClassData文件的偏移 u4 staticValuesOff; // 指向DexEncodedArray文件的偏移};typedef struct DexClassData { DexClassDataHeader header; // DexClassDataBody部分 DexField* staticFields; DexField* instanceFields; DexMethod* directMethods; DexMethod* virtualMethods; };typedef struct DexClassDataHeader { u4 staticFieldsSize; u4 instanceFieldsSize; u4 directMethodsSize; u4 virtualMethodsSize;};// DexClassDataBody部分typedef struct DexField { u4 fieldIdx; // 指向DexFiledId的索引 u4 accessFlags;};// DexClassDataBody部分typedef struct DexMethod { u4 methodIdx; // 指向DexMethodId的索引 u4 accessFlags; u4 codeOff; // 指向DexCode结构};typedef struct DexCode { u2 registersSize; // 使用寄存器的个数 u2 insSize; // 自己运行的使用的寄存器(参数)个数 u2 outsSize; // 调用其它方法时使用的寄存器个数 u2 triesSize; // try的个数 u4 debugInfoOff; // 指向调试信息的偏移 u4 insnsSize; // 指令集个数,以2字节为单位 u2 insns[1]; // 指令集,即真正的代码段};
odex文件结构
参考这里,odex生成通过dexopt-wrapper程序。odex文件的结尾肯定是以64 65 79 0a 30 33 36 00开头,以44 4E 45 41 00 00 00 00结尾的,odex有两种存在形式:
一种是从apk程序中提取出来,与apk存放的同级目录,文件后缀为odex的文件。这类型多是AndroidROM的系统程序。
一种是dalvik-cache缓存,文件后缀为dex的文件。这类型多存放在cache/dalvik-cache目录下。保存的形式为app路径@apk名@classes.dex。app路径中的/也用@表示,apk名包含.apk后缀。由于安卓程序的apk文件为zip压缩包形式,虚拟机每次加载它们时需要从apk中读取而太消耗CPU,而odex是dex的超集,因此利用缓存即可更快的读取运行dex。
// 抽象的看是如下的定义struct ODexFile{ DexOptHeader* header;// odex头部 DexFile* dexfile;// dex文件 Dependences deps;// 依赖库列表 ChunkDexClassLookup lookup;// 类查询结构 ChunkRegisterMapPoll mappool;// 映射池 ChunkEnd end;// 结束标志}
头部(DexOptHeader)段
DexOptHeader:是ODEX文件头,其结构很固定,占用0x27个字节。
struct DexOptHeader { u1 magic[8]; // odex版本标志,目前固定值为64 65 79 0a 30 33 36 00 u4 dexOffset; // dex文件头的偏移,目前固定值为0x28 u4 dexLength; // dex文件总长度 u4 depsOffset; // dex依赖库列表偏移 u4 depsLength; // dex依赖库列表总长度 u4 optOffset; // 辅助段偏移 u4 optLength; // 辅助段总长度 u4 flags; // 标志,标识了㣈虚拟机加载odex的优化与验证选项 u4 checksum; // 依赖库与辅助数据的校验和,标识odex是否合法};
依赖段
DexDependences
Dependences不会加载进内存,并且也没有相关的官方资料,通过系统源码整理如下:
struct DexDependences{ u4 timestamp;// 时间戳,用来记录优化前的classes.dex的时间戳 u4 crc; // 校验值,用来记录优化前的classes.dex的校验值 u4 version; // 虚拟机版本号 u4 size; // 依赖库个数 struct { u4 len; // name字符串的长度 u1 name[len]; // 依赖库的名称 SHA1DigeslLen signature; // 当前指定依赖库的SHA1校验值 }} table[size];
辅助段
Dependences下面为3个Chunk块,他们被虚拟机加载到一个叫auxillay的段中。3个块由DexPrepare.cpp文件的writeOptData()函数写入,实际writeOptData调用了writeChunk写入。writeChunk函数中定义了一个header(占用8个字节),writeChunk函数写入时会先填充这个结构:
union{ char raw[8]; struct{ u4 type; // 指下面三种类型的某个的类型常量 u4 size; // 需要为指定的类型结构填充数据的字节数 } ts;} header;
ChunkDexClassLookup
虚拟机通过ChunkDexClassLookup检索dex文件中的所有类。
写入时会向writeChunk函数传递一个DexClassLookup指针。
struct ChunkDexClassLookup{ Header header; struct{ int len; // 本结构的字节数 int size; // 下面struct的个数,通常值为2 struct{ u4 classDescriptorHash; // 类的哈希 int classDescriptorOffset; // 类的描述 int classDefOffset; // classDef的偏移 } table[1]; } DexClassLookup;}
ChunkRegisterMapPoll
写入时会向writeChunk函数传递一个RegisterMapPoll指针。
struct ChunkRegisterMapPoll{ Header header; struct{ struct{ u4 numClasses; u4 classDataOffset[1]; } classpool; struct{ u2 numMethod; u4 methodDataOffset[1]; } lookup; } RegisterMapPoll;}
ChunkEnd
写入时会向writeChunk函数传递一个NULL指针。
struct ChunkEnd{ Header header;}
dexopt的验证与优化
暂时省略,参看《Android软件安全与逆向分析》的第四章的4.5小节。
- Android学习笔记3--逆向1--Dex
- 安卓逆向学习笔记(1) - 反编译classes.dex获取apk的java源代码
- Android逆向笔记(3)--- dex文件Dalvik指令的解析
- Android学习笔记3--逆向2--Smali
- android逆向日记-Dex文件格式详解
- Android逆向之Xposedhook多dex问题
- [学习笔记]逆向练习1
- android逆向之多dex(multiDex)文件apk的逆向
- android逆向学习,笔记(一)
- android逆向学习,笔记(二)
- 安卓逆向学习笔记(1)
- 安卓逆向学习(二 DEX文件说明)
- 逆向工程学习笔记
- 驱动逆向学习笔记
- 逆向工程学习笔记
- android逆向学习,笔记(三)静态分析android程序
- Android逆向之旅---解析编译之后的Dex文件格式
- Android逆向之旅---解析编译之后的Dex文件格式
- Android探索之旅(第二十一篇)Android中事件冲突的解决收集(持续收录中......)
- Java-对象数组空指针异常问题
- svn is not a working copy 怎么解决
- 出生年 c语言 转载别人的加上了自己的理解
- Android 利用WebViewJavascriptBridge 实现js和java的交互
- Android学习笔记3--逆向1--Dex
- Android学习笔记3--逆向2--Smali
- iptables 实现端口转发
- 机器学习相关知识
- Android插件化:从入门到放弃
- 一文了解强化学习
- 软件配置管理提高业务价值的7个关键点
- HashMap、LinkedHashMap、TreeMap、HashTable的区别
- [CF] Educational Codeforces Round 23