安卓逆向系列教程(二)APK 和 DEX
作者:飛龍
APK
APK 是 Android 軟件包的分發(fā)格式帕胆,它本身是個 Zip 壓縮包谎柄。APK 根目錄下可能出現(xiàn)的目錄和文件有:
名稱 | 用途 |
---|---|
META-INF |
存放元數(shù)據(jù) |
AndroidManifest.xml |
編譯后的全局配置文件 |
assets |
存放資源文件乍狐,不會編譯 |
classes.dex |
編譯并打包后的源代碼 |
lib |
存放二進制共享庫荚孵,含有armeabi-* 余耽、mips 耍缴、x86 等文件夾戴而,對應具體的平臺 |
res |
存放資源文件 |
resources.arsc |
編譯并打包后的res/values 中的文件 |
res
res 中可能出現(xiàn)的目錄如下:
名稱 | 用途 |
---|---|
anim |
存放編譯后的動畫 XML 文件(<XXXAnimation> ) |
color |
存放編譯后的選擇器 XML 文件(<selector> ) |
drawable-* |
存放圖片凑术,* 為不同分辨率,圖片按照不同分辨率歸類所意。其中帶.9 的圖片為可拉伸的圖片淮逊。 |
layout |
存放編譯后的布局 XML 文件(<XXXLayout> ) |
menu |
存放編譯后的菜單 XML 文件(<menu> ) |
mipmap-* |
存放使用 mipmap 技術加速的圖片,一般用來存放應用圖標扶踊,其它同drawable-*
|
raw |
存放資源文件泄鹏,不會編譯,比如音樂秧耗、視頻备籽、純文本等 |
xml |
存放編譯后的自定義 XML 文件 |
resources.arsc
在 APK 中是找不到res/values
這個目錄的,因為它里面的文件編譯后打包成了resources.arsc
分井。為了理解它车猬,我們先看一看原始的res/values
霉猛。
res/values
中保存資源 XML 文件,根節(jié)點為<resources>
诈唬。一般可能會出現(xiàn)以下幾種文件:
名稱 | 用途 |
---|---|
arrays.xml |
存放整數(shù)數(shù)組和字符串數(shù)組韩脏,使用<integer-array> 或<string-array> 定義,元素使用<item> 定義 |
bools.xml |
存放布爾值铸磅,使用<bool> 定義 |
colors.xml |
存放顏色赡矢,使用<color> 定義 |
dimens.xml |
存放尺寸,使用<dimen> 定義 |
drawables.xml |
存放顏色阅仔,使用<drawable> 定義 |
ids.xml |
存放 ID吹散,使用<item type="id"> 定義 |
integers.xml |
存放整數(shù),使用<integers> 定義 |
strings.xml |
存放字符串八酒,使用<strings> 定義 |
styles.xml |
存放顏色空民,使用<style> 定義,元素使用<item> 定義 |
res/values
中的文件名稱是無所謂的羞迷,這些名稱只是約定界轩。也就是說,任何res/values
中的文件中的字符串都會出現(xiàn)在R.strings
里面衔瓮。
雖然我們在 APK 中無法直接看到這些文件浊猾,但是反編譯之后就可以了。反編譯之后热鞍,我們也會找到一個public.xml
文件葫慎,是res
里所有東西的索引:
<resources>
<public type="drawable" name="ic_launcher" id="0x7f020000" />
<public type="layout" name="activity_main" id="0x7f030000" />
<public type="layout" name="activity_sub" id="0x7f030001" />
<public type="dimen" name="activity_horizontal_margin" id="0x7f040000" />
<public type="dimen" name="activity_vertical_margin" id="0x7f040001" />
<public type="string" name="action_settings" id="0x7f050000" />
<public type="string" name="app_name" id="0x7f050001" />
<public type="string" name="hello_world" id="0x7f050002" />
<public type="string" name="title_activity_sub" id="0x7f050003" />
<public type="style" name="AppTheme" id="0x7f060000" />
<public type="menu" name="main" id="0x7f070000" />
<public type="menu" name="sub" id="0x7f070001" />
<public type="id" name="button1" id="0x7f080000" />
<public type="id" name="action_settings" id="0x7f080001" />
</resources>
DEX
DEX 即 Dalvik Executable,Dalvik 可執(zhí)行文件薇宠。它的結(jié)構(gòu)如下:
struct DexFile{
DexHeader Header;
DexStringId StringIds[stringIdsSize];
DexTypeId TypeIds[typeIdsSize];
DexFieldId FieldIds[fieldIdsSize];
DexMethodId MethodIds[methodIdsSize];
DexProtoId ProtoIds[protoIdsSize];
DexClassDef ClassDefs[classDefsSize];
DexData Data;
DexLink LinkData;
};
我們可以看到偷办,它可以分為九個區(qū)段,如下:
Header |
---|
StringIds |
TypeIds |
FieldIds |
MethodIds |
ProtoIds |
ClassDefs |
Data |
LinkData |
大體結(jié)構(gòu)如這張圖所示:
另外澄港,在講解各個區(qū)段之前椒涯,需要首先了解一些數(shù)據(jù)類型的定義:
類型 | 定義 |
---|---|
u1 |
等同于uint8_t ,表示 1 字節(jié)的無符號數(shù) |
u2 |
等同于uint16_t 回梧,表示 2 字節(jié)的無符號數(shù) |
u4 |
等同于uint32_t 逐工,表示 4 字節(jié)的無符號數(shù) |
u8 |
等同于uint64_t ,表示 8 字節(jié)的無符號數(shù) |
Header 區(qū)段
Header 區(qū)段用于儲存版本標識漂辐、校驗和泪喊、文件大小、各部分的大小及偏移髓涯。結(jié)構(gòu)以及描述如下:
struct DexHeader {
u1 magic[8]; /* 版本標識 */
u4 checksum; /* adler32 檢驗和 */
u1 signature[kSHA1DigestLen]; /* SHA-1 哈希值 */
u4 fileSize; /* 整個文件大小 */
u4 headerSize; /* Header 區(qū)段大小 */
u4 endianTag; /* 字節(jié)序標記 */
u4 linkSize; /* 鏈接區(qū)段大小 */
u4 linkOff; /* 鏈接區(qū)段偏移 */
u4 mapOff; /* MapList 的偏移 */
u4 stringIdsSize; /* StringId 的個數(shù) */
u4 stringIdsOff; /* StringIds 區(qū)段偏移 */
u4 typeIdsSize; /* TypeId 的個數(shù) */
u4 typeIdsOff; /* TypeIds 區(qū)段偏移 */
u4 protoIdsSize; /* ProtoId 的個數(shù) */
u4 protoIdsOff; /* ProtoIds 區(qū)段偏移 */
u4 fieldIdsSize; /* FieldId 的個數(shù) */
u4 fieldIdsOff; /* FieldIds 區(qū)段偏移 */
u4 methodIdsSize; /* MethodId 的個數(shù) */
u4 methodIdsOff; /* MethodIds 區(qū)段偏移 */
u4 classDefsSize; /* ClassDef 的個數(shù) */
u4 classDefsOff; /* ClassDefs 區(qū)段偏移 */
u4 dataSize; /* 數(shù)據(jù)區(qū)段的大小 */
u4 dataOff; /* 數(shù)據(jù)區(qū)段的文件偏移 */
};
有幾個條目需要特別提醒袒啼。
-
magic
:必須為DEX_FILE_MAGIC
:ubyte[8] DEX_FILE_MAGIC = { 0x64 0x65 0x78 0x0a 0x30 0x33 0x35 0x00 } = "dex\n035\0";
checksum
:是整個文件除去它本身以及魔數(shù)的校驗和。signature
:是整個文件除去它本身、校驗和以及魔數(shù)的哈希值蚓再。headerSize
:一般為 70滑肉。-
endianTag
:有兩種順序,小端和大端摘仅,定義如下:uint ENDIAN_CONSTANT = 0x12345678; /* 小端序 */ uint REVERSE_ENDIAN_CONSTANT = 0x78563412; /* 大端序 */
一般為小端序靶庙,反正我還沒見過大端的。
stringIdsOff
:由于前一個區(qū)段的偏移加上它的長度一般為后一個區(qū)段的偏移娃属,所以這個條目一般也為 70六荒。xxxSize
:要注意有幾個是個數(shù),后綴也是Size
矾端。xxxOff
:如果對應的xxxSize
為 0掏击,那么它也為 0(很奇怪)。
StringIds 區(qū)段
StringIds 區(qū)段包含stringIdsSize
個DexStringId
結(jié)構(gòu)秩铆,如下:
struct DexStringId {
u4 stringDataOff; /* 字符串內(nèi)容砚亭,字符串數(shù)據(jù)偏移 */
};
其中數(shù)據(jù)偏移指向 Data 區(qū)段的字符串數(shù)據(jù)。
TypeIds 區(qū)段
TypeIds 包含typeIdsSize
個DexTypeId
結(jié)構(gòu)殴玛,如下:
struct DexTypeId {
u4 descriptorIdx; /* 類型的完全限定符捅膘,指向 DexStringId 列表的索引 */
};
索引是一個從 0 開始的數(shù)字,表示對應第幾個DexStringId
滚粟。這些DexStringId
指向的字符串都是類型名稱寻仗,比如I
、Ljava/lang/String;
之類的坦刀。DexTypeId
的索引也會用于后面的結(jié)構(gòu)。
ProtoIds 區(qū)段
ProtoIds 包含ProtoIdsSize
個DexProtoId
結(jié)構(gòu)蔬咬。這里的 Proto 指方法原型鲤遥,包含返回類型和參數(shù)類型。
struct DexProtoId {
u4 shortyIdx; /* 原型縮寫林艘,指向 DexStringId 列表的索引 */
u4 returnTypeIdx; /* 返回類型盖奈,指向 DexTypeId 列表的索引 */
u4 parametersOff; /* 參數(shù)類型列表,指向 DexTypeList 的偏移 */
};
struct DexTypeList {
u4 size; /* 接下來 DexTypeItem 的個數(shù) */
DexTypeItem list[size]; /* DexTypeItem 結(jié)構(gòu) */
};
struct DexTypeItem {
u2 typeIdx; /* 參數(shù)類型狐援,指向 DexTypeId 列表的索引 */
};
原型縮寫是把所有返回類型和參數(shù)類型的名稱拼在一起钢坦,對象的話只寫L
。比如int(int,int)
寫為III
啥酱,void()
寫為V
爹凹,void(String)
寫為VL
。
參數(shù)類型列表一般保存在Data
區(qū)段中镶殷,如果沒有禾酱,parametersOff
為 0。
FieldIds 區(qū)段
TypeIds 包含fieldIdsSize
個DexFieldId
結(jié)構(gòu),如下:
struct DexFieldId {
u2 classIdx; /* 類的類型颤陶,指向 DexTypeId 列表的索引 */
u2 typeIdx; /* 字段類型颗管,指向 DexTypeId 列表的索引 */
u4 nameIdx; /* 字段名稱,指向 DexStringId 列表的索引 */
};
MethodIds 區(qū)段
MethodIds 包含methodIdsSize
個DexMethodId
結(jié)構(gòu)滓走,如下:
struct DexMethodId {
u2 classIdx; /* 類的類型垦江,指向 DexTypeId 列表的索引 */
u2 protoIdx; /* 方法原型,指向 DexProtoId 列表的索引 */
u4 nameIdx; /* 方法名稱搅方,指向 DexStringId 列表的索引 */
};
ClassDefs 區(qū)段
ClassDefs 包含classDefsSize
個DexClassDef
結(jié)構(gòu)比吭,如下:
struct DexClassDef {
u4 classIdx; /* 類的類型,指向 DexTypeId 列表的索引 */
u4 accessFlags; /* 訪問標志 */
u4 superclassIdx; /* 父類類型腰懂,指向 DexTypeId列表的索引 */
u4 interfacesOff; /* 接口梗逮,指向 DexTypeList 的偏移 */
u4 sourceFileIdx; /* 源文件名,指向 DexStringId 列表的索引 */
u4 annotationsOff; /* 注解绣溜,指向 DexAnnotationsDirectoryItem 結(jié)構(gòu) */
u4 classDataOff; /* 指向 DexClassData 結(jié)構(gòu)的偏移 */
u4 staticValuesOff; /* 指向 DexEncodedArray 結(jié)構(gòu)的偏移 */
};
struct DexClassData {
DexClassDataHeader header; /* 各個字段與方法的個數(shù) */
DexField staticFields[staticFieldsSize]; /* 靜態(tài)字段 */
DexField instanceFields[instanceFieldsSize]; /* 實例字段 */
DexMethod directMethods[directMethodsSize]; /* 直接方法 */
DexMethod virtualMethods[virtualMethodsSize]; /* 虛方法 */
};
struct DexClassDataHeader {
u4 staticFieldsSize; /* 靜態(tài)字段個數(shù) */
u4 instanceFieldsSize; /* 實例字段個數(shù) */
u4 directMethodsSize; /* 直接方法個數(shù) */
u4 virtualMethodsSize; /* 虛方法個數(shù) */
};
struct DexField {
u4 fieldIdx; /* 指向 DexFieldId 的索引 */
u4 accessFlags; /* 訪問標志 */
};
struct DexMethod {
u4 methodIdx; /* 指向 DexMethodId 的索引 */
u4 accessFlags; /* 訪問標志 */
u4 codeOff; /* 方法指令慷彤,指向DexCode結(jié)構(gòu)的偏移 */
};
struct DexCode {
u2 registersSize; /* 使用的寄存器個數(shù) */
u2 insSize; /* 參數(shù)個數(shù) */
u2 outsSize; /* 調(diào)用其他方法時使用的寄存器個數(shù) */
u2 triesSize; /* Try/Catch個數(shù) */
u4 debugInfoOff; /* 指向調(diào)試信息的偏移 */
u4 insnsSize; /* 指令集個數(shù),以2字節(jié)為單位 */
u2 insns[insnsSize]; /* 指令集 */
};
DexClassData
和DexCode
保存在 Data 區(qū)段中怖喻。
Data 區(qū)段
這個區(qū)段中除了存放二級結(jié)構(gòu)和字符串底哗,還有個重要的結(jié)構(gòu)叫做DexMapList
,它實際上 DEX 中所有東西的索引锚沸,包括各種二級結(jié)構(gòu)跋选、字符串和它本身。DEX 中同類結(jié)構(gòu)都會保存在一起哗蜈,所以一類結(jié)構(gòu)只占用一個條目前标。
struct DexMapList {
u4 size; /* 條目個數(shù) */
DexMapItem list[size]; /* 條目列表 */
};
struct DexMapItem {
u2 type; /* 結(jié)構(gòu)類型,kDexType 開頭 */
u2 unused; /* 未使用距潘,用于字節(jié)對齊 */
u4 size; /* 連續(xù)存放的結(jié)構(gòu)個數(shù) */
u4 offset; /* 結(jié)構(gòu)的偏移 */
};
/* 結(jié)構(gòu)類型代碼 */
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,
};