ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CTF-Wiki DEX 文件格式深度解析:从 Dalvik 可执行文件到逆向实战

CTF-Wiki DEX 文件格式深度解析:从 Dalvik 可执行文件到逆向实战 CTF-Wiki DEX 文件格式深度解析从 Dalvik 可执行文件到逆向实战【免费下载链接】ctf-wikiCome and join us, we need you!项目地址: https://gitcode.com/gh_mirrors/ct/ctf-wiki导读DEXDalvik eXecutable File是 Android 平台专门为 Java/Kotlin 代码设计的可执行文件格式面向内存受限、处理器性能有限的移动设备做了大量空间优化。本文以 CTF-Wiki 仓库中的 DEX 文件章节 为核心系统讲解 DEX 的底层数据类型含 LEB128 变长编码、文件头、索引区、数据区与 map section 的完整结构并结合仓库内相关文档与源码级示例进行纵深拓展。读完本文你将掌握 DEX 文件的二进制布局、索引指向关系具备使用 010 Editor 等工具手工解析 DEX 文件的能力为后续 Android 逆向smali 分析、APK 加固对抗打下坚实基础。一、为什么 Android 需要 DEX 文件1.1 移动平台的资源约束Google 在设计 Android 时没有直接使用 JVM 的.class文件格式而是专门设计了 DEXDalvik eXecutable File格式。原因在于手机这类移动平台的两个显著特征内存低每个应用可用的堆内存有限不能容忍冗余的字节码布局处理器性能较差CPU 指令集相对精简解析和执行开销需要尽量压低。.class文件面向桌面 JVM 设计每个类一个文件存在大量重复的字符串常量、类型描述符与方法签名信息而 DEX 将整个 APK 中所有类合并进一个文件通过**统一的索引表index**共享常量数据从而大幅压缩可执行文件体积。从仓库目录结构看CTF-Wiki 将 Android 逆向知识组织为 Android 中 Java 层的运行机制 这一大章节其中明确把研究内容划分为三条主线Java 虚拟机宏观执行流程、Smali 代码指令级视角、Java 层可执行文件即本文的 DEX 文件格式。1.2 DEX 在 Android 运行机制中的位置Android 中 Java 层代码的完整链路大致是Java/Kotlin 源码 ──► .class 文件 ──► dx/d8 ──► classes.dex ──► Dalvik/ART 虚拟机编译产物classes.dex位于 APK 包内运行时由 DalvikAndroid 4.x 及以前或 ARTAndroid 5.0 以后加载执行。DEX 中的字节码可读性较差逆向领域使用 smali 语法 作为其汇编级表示——这从侧面印证了 DEX 是理解整个 Android Java 层逆向的地基。二、DEX 基础数据类型与 LEB128 变长编码2.1 数据类型定义总表在介绍 DEX 具体结构之前必须先掌握文件中使用的基础数据类型。下表完整列出 DEX 格式规范中定义的原子类型名称说明byte8 位有符号整数ubyte8 位无符号整数short16 位有符号整数采用小端字节序ushort16 位无符号整数采用小端字节序int32 位有符号整数采用小端字节序uint32 位无符号整数采用小端字节序long64 位有符号整数采用小端字节序ulong64 位无符号整数采用小端字节序sleb128有符号 LEB128可变长度见下文uleb128无符号 LEB128可变长度见下文uleb128p1无符号 LEB128 加1可变长度见下文注意除byte/ubyte外的多字节整数均显式规定为小端字节序little-endian这与 x86/ARM 等主流移动处理器一致在手工解析二进制时无需字节交换。2.2 为什么需要变长整数DEX 之所以引入变长类型核心目标是尽可能减小可执行文件的体积。举例来说如果某个字符串的长度为 5那么其实只需要 1 个字节即可表示但如果为此专门定义 1 字节的u1类型就会把所有字符串的长度都限制在 255 以内无法表达更长的字符串。LEB128 编码根据数字大小动态选择字节数完美兼顾了空间与表达能力。2.3 LEB128 编码原理LEB128Little-Endian Base 128是一种基于小端的变长整数编码可表示 32 位大小的整数。其编码规则是每个字节的最高位第 7 位作为延续标志continuation bit1表示后面还有下一个字节0表示当前字节是最后一个因此每个字节实际上只有7 个有效 bit用来表示数值有效 bit 按小端顺序排列即第一个字节存放最低的 7 位后续字节依次存放更高位。一个 LEB128 类型的变量最多需要 5 个字节如果第 5 个字节的最高位仍然为1说明数据非法溢出了 32 位范围。2.4 无符号 LEB128uleb128的读取实现Dalvik 虚拟机源码libdex中读取无符号 LEB128 的函数如下它逐字节取出、掩掉最高位并移位拼接DEX_INLINE int readUnsignedLeb128(const u1** pStream) { const u1* ptr *pStream; int result *(ptr); //取第一个字节 if (result 0x7f) { //如果第1个字节大于0x7f,表示第一个字节最高位为1 int cur *(ptr); //第2个字节 result (result 0x7f) | ((cur 0x7f) 7); //前两个字节 if (cur 0x7f) { cur *(ptr); result | (cur 0x7f) 14; if (cur 0x7f) { cur *(ptr); result | (cur 0x7f) 21; if (cur 0x7f) { /* * Note: We dont check to see if cur is out of * range here, meaning we tolerate garbage in the * high four-order bits. */ cur *(ptr); result | cur 28; } } } } *pStream ptr; return result; }从实现可以看出两点关键细节每个字节仅取低 7 位参与运算 0x7f依次左移 7、14、21、28 位最高 4 位第 5 字节的第 4~7 bit不做越界检查容忍垃圾数据——这是 Dalvik 出于性能考虑的务实取舍也是逆向分析时需要注意的宽松解析行为。手算示例计算字节流c0 83 92 25的 uleb128 值第一个字节最高位为 1说明还有后续字节result1 0xc0 0x7f 0x40第二个字节result2 (0x83 0x7f) 7 0x180第三个字节result3 (0x92 0x7f) 14 0x48000第四个字节最高位 0结束result4 0x25 21 0x4a00000最终值result1 result2 result3 result4 0x4a481c02.5 有符号 LEB128sleb128的读取实现有符号版本在拼接完所有有效位后还需根据最高有效位做符号扩展DEX_INLINE int readSignedLeb128(const u1** pStream) { const u1* ptr *pStream; int result *(ptr); if (result 0x7f) { result (result 25) 25; //符号扩展 } else { int cur *(ptr); result (result 0x7f) | ((cur 0x7f) 7); if (cur 0x7f) { result (result 18) 18; //符号扩展 } else { cur *(ptr); result | (cur 0x7f) 14; //符号扩展 if (cur 0x7f) { result (result 11) 11; //符号扩展 } else { cur *(ptr); result | (cur 0x7f) 21; if (cur 0x7f) { result (result 4) 4; //符号扩展 } else { /* * Note: We dont check to see if cur is out of * range here, meaning we tolerate garbage in the * high four-order bits. */ cur *(ptr); result | cur 28; } } } } *pStream ptr; return result; }手算示例计算字节流d1 c2 b3 40的 sleb128 值result1 0xd1 0x7f 0x51result2 (0xc2 0x7f) 7 0x21000result3 (0xb3 0x7f) 14 0xcc000result4 0x40 21 0x8000000符号扩展(result1 result2 result3 result4) 4 4 0xf80ce151注意此处示例的第二、三步结果与文档原文一致0x21000、0xcc000最终通过算术右移完成符号扩展得到负数补码0xf80ce151。2.6 uleb128p1加一编码uleb128p1表示数值加 1 后再进行 uleb128 编码适用于以下场景要求数字的表示必须非负如用于表示前驱索引0xffffffff即无前驱的哨兵值当数字为0xffffffff时加 1 后为 0此时只需 1 个字节即可表示极大节约空间其余细节有待进一步结合 Dalvik 源码深入思考。三、DEX 文件整体结构概览DEX 文件的整体布局如下整个文件主要分为三大部分文件头header给出 DEX 文件的基本属性magic、校验和、各索引区大小与偏移等索引区index给出各类数据的索引真正的数据存放在数据区数据区data存放真实的字符串、代码、类型列表等原始数据。这种索引区 数据区分离的设计是 DEX 空间优化的关键索引区中的string_ids、type_ids、proto_ids、field_ids、method_ids、class_defs全部是定长结构便于二分查找而数据区中的字符串、代码等变长内容按需存放。四、DEX 文件头Header详解4.1 DexHeader 结构文件头固定占用0x70112个字节包含 magic 字段、adler32 校验值、SHA-1 哈希值以及各索引区的个数与偏移地址。其 C 结构体定义如下struct DexHeader { u1 magic[8]; /* includes version number */ u4 checksum; /* adler32 checksum */ u1 signature[kSHA1DigestLen]; /* SHA-1 hash */ u4 fileSize; /* length of entire file */ u4 headerSize; /* offset to start of next section */ u4 endianTag; u4 linkSize; u4 linkOff; u4 mapOff; 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; };4.2 字段逐一说明名称格式说明magicubyte[8] DEX_FILE_MAGIC标识 DEX 文件其中DEX_FILE_MAGIC dex\n035\0checksumuint除magic和此字段之外的文件剩余内容的adler32 校验和用于检测文件损坏情况signatureubyte[20]除magic、checksum和此字段之外的文件内容的SHA-1 签名哈希用于对文件进行唯一标识file_sizeuint整个文件包括文件头的大小以字节为单位header_sizeuint 0x70文件头的大小以字节为单位endian_taguint ENDIAN_CONSTANT字节序标记大端序或小端序link_sizeuint如果此文件未进行静态链接则该值为0反之为链接区段的大小link_offuint如果link_size 0则该值为0反之该偏移量是文件开头到link_data区段的偏移量map_offuint该偏移量必须非零标识从文件开头到data区段的偏移量string_ids_sizeuint字符串标识符列表中的字符串数量string_ids_offuint如果string_ids_size 0一种奇怪的极端情况则该值为0反之表示从文件开头到string_ids的偏移量type_ids_sizeuint类型标识符列表中的元素数量最大为 65535type_ids_offuint如果type_ids_size 0则该值为0反之表示从文件开头到type_ids区段开头的偏移量proto_ids_sizeuint原型方法标识符列表中的元素数量最多为 65535proto_ids_offuint如果proto_ids_size 0则该值为0反之表示文件开头到proto_ids区段开头的偏移量field_ids_sizeuint字段标识符列表中的元素数量field_ids_offuint如果field_ids_size 0则该值为0反之表示文件开头到field_ids区段的偏移量method_ids_sizeuint方法标识符列表中的元素数量method_ids_offuint如果method_ids_size 0则该值为0反之表示从文件开头到method_ids区段的偏移量class_defs_sizeuint类定义列表中的元素数量class_defs_offuint如果class_defs_size 0则该值为0反之表示文件开头到class_defs区段的偏移量data_sizeuintdata区段以字节为单位的大小必须是sizeof(uint)的偶数倍说明按 8 字节对齐data_offuint从文件开头到data区段开头的偏移量4.3 逆向视角的关键字段从逆向和文件校验的角度文件头中三个字段特别值得关注magicdex\n035\0前 8 字节其中035是 DEX 格式版本号。不同 Android 版本可能使用035、037、038、039等识别版本有助于判断对应 ART 的字节码特性checksumadler32覆盖magic之后到文件末尾的内容用于快速检测文件是否损坏或被修改signatureSHA-120 字节覆盖signature字段之后的内容用于唯一标识文件。在对抗 APK 加固与 dex 修改时这两个校验值常被用作完整性校验点篡改后需同步重算。另外endian_tag固定为ENDIAN_CONSTANT小端值为0x12345678若在解析时发现该值与预期不符说明文件使用了反向字节序需要整体翻转解析。五、DEX 索引区六大表索引区由六张定长表组成所有表项都只是指针/索引真正的数据在数据区。这种设计保证索引区可以连续、定长地存放便于快速定位与二分查找。5.1 string_id字符串索引string_ids区段包含stringIdsSize个DexStringId结构struct DexStringId { u4 stringDataOff; /* 字符串数据偏移也就是数据区中各 StringData 的文件偏移 */ };关键点DexStringId中只存储每个字符串的文件偏移相对文件头每个偏移占 4 字节因此字符串索引区总共占用4 * stringIdsSize字节在对应偏移处字符串以MUTF-8Modified UTF-8格式存储开头是一个 uleb128 变量表示字符串长度不含末尾\x00之后紧跟字符串内容最后以\x00结尾。补充MUTF-8 与标准 UTF-8 的差异主要在于对 U0000NUL使用双字节0xC0 0x80编码、对补充平面字符使用代理对surrogate pair编码。由于 NUL 在 DEX 中用作字符串终止符MUTF-8 避免了嵌入 NUL 带来的歧义。5.2 type_id类型索引type_ids区索引了 Java 代码中使用的所有类型类、数组或基本类型。此列表必须按string_id索引排序且不能重复struct DexTypeId { u4 descriptorIdx; /* 指向 DexStringId 列表的索引 */ };descriptorIdx指向字符串索引表例如Ljava/lang/String;的 descriptor 字符串就存放在 string 区type_id 只是其下标。5.3 proto_id方法原型索引proto_ids针对 Java 方法的**原型签名**设计包含返回类型与参数列表不涉及方法名struct DexProtoId { u4 shortyIdx; /* 返回类型参数类型简写指向 DexStringId 列表的索引 */ u4 returnTypeIdx; /* 返回类型指向 DexTypeId 列表的索引 */ u4 parametersOff; /* 参数类型指向 DexTypeList 的偏移 */ } struct DexTypeList { u4 size; /* DexTypeItem 的个数即参数个数 */ DexTypeItem list[1]; /* 指向 DexTypeItem 开始处 */ }; struct DexTypeItem { u2 typeIdx; /* 参数类型指向 DexTypeId 列表的索引最终指向字符串索引 */ };三个字段的语义shortyIdx指向一个简短的字符串如VI、L等描述返回类型参数类型的压缩表示便于快速比较returnTypeIdx返回类型的 type_id 索引parametersOff指向DexTypeList的文件偏移DexTypeList中size给出参数个数list[]中每个DexTypeItem的typeIdx指向 type_ids 表。5.4 field_id字段索引field_ids针对 Java 类中的字段设计struct DexFieldId { u2 classIdx; /* 类的类型指向 DexTypeId 列表的索引 */ u2 typeIdx; /* 字段类型指向 DexTypeId 列表的索引 */ u4 nameIdx; /* 字段名指向 DexStringId 列表的索引 */ };一个字段通过所属类 字段类型 字段名三元组唯一定义三者分别指向 type_ids 与 string_ids 索引表。5.5 method_id方法索引method_ids直接为 Java 方法设计包含方法所在的类、方法原型和方法名struct DexMethodId { u2 classIdx; /* 类的类型指向 DexTypeId 列表的索引 */ u2 protoIdx; /* 声明类型指向 DexProtoId 列表的索引 */ u4 nameIdx; /* 方法名指向 DexStringId 列表的索引 */ };可以看到 method 索引通过类 原型 方法名三元组描述完整的方法签名其中原型又进一步引用 type_ids 与 string_ids。5.6 class_def类定义索引classDefsSize表明 class_def 区域的大小classDefsOff表明其偏移。该区为 Java 中的类设计核心结构如下// 类的基本信息 struct DexClassDef { u4 classIdx; /* 类的类型指向 DexTypeId 列表的索引 */ u4 accessFlags; /* 访问标志 */ u4 superclassIdx; /* 父类类型指向 DexTypeId 列表的索引 */ u4 interfacesOff; /* 接口指向 DexTypeList 的偏移 */ u4 sourceFileIdx; /* 源文件名指向 DexStringId 列表的索引 */ u4 annotationsOff; /* 注解指向 DexAnnotationsDirectoryItem 结构 */ u4 classDataOff; /* 指向 DexClassData 结构的偏移 */ u4 staticValuesOff; /* 指向 DexEncodedArray 结构的偏移 */ }; // 类的字段与方法概况 struct DexClassData { DexClassDataHeader header; /* 指定字段与方法的个数 */ DexField* staticFields; /* 静态字段DexField 结构 */ DexField* instanceFields; /* 实例字段DexField 结构 */ DexMethod* directMethods; /* 直接方法DexMethod 结构 */ DexMethod* virtualMethods; /* 虚方法DexMethod 结构 */ }; // 详细描述类的字段个数与方法个数 struct DexClassDataHeader { u4 staticFieldsSize; /* 静态字段个数 */ u4 instanceFieldsSize; /* 实例字段个数 */ u4 directMethodsSize; /* 直接方法个数 */ u4 virtualMethodsSize; /* 虚方法个数 */ }; // 字段定义 struct DexField { u4 fieldIdx; /* 指向 DexFieldId 的索引 */ u4 accessFlags; /* 访问标志 */ }; // 方法定义 struct DexMethod { u4 methodIdx; /* 指向 DexMethodId 的索引 */ u4 accessFlags; /* 访问标志 */ u4 codeOff; /* 指向 DexCode 结构的偏移 */ }; // 代码概况 struct DexCode { u2 registersSize; /* 使用的寄存器个数 */ u2 insSize; /* 参数个数 */ u2 outsSize; /* 调用其他方法时其它方法使用的寄存器个数会在自己的调用栈申请并压栈推测 */ u2 triesSize; /* Try/Catch 个数 */ u4 debugInfoOff; /* 指向调试信息的偏移 */ u4 insnsSize; /* 指令集个数以 2 字节为单位 */ u2 insns[1]; /* 指令集 */ };从源码结构可以推断出的层次关系DexClassDef是类的门面通过classDataOff指向DexClassDataDexClassData由 header四个计数 静态/实例字段 直接/虚方法四部分组成DexMethod的codeOff指向DexCodeDexCode才是真正存放字节码指令insns[]与寄存器元信息的地方registersSize、insSize、outsSize三个寄存器计数与 smali 中的.registers/.locals/.params声明一一对应。可结合仓库 smali 章节 理解registers_size是 16 位无符号数最大 65535Dalvik 调用方法时据此为该方法的寄存器分配栈空间。outsSize的语义在文档中标注为会在自己的调用栈申请并压栈推测即方法调用其他方法时被调用方参数在调用方栈帧上预留的空间。逆向分析指令流时outsSize往往与invoke-*指令的参数传递方式相关。5.7 小结索引区的指向关系索引区看起来复杂但本质上是一条多级索引链string_ids字符串 ← type_ids类型 ← proto_ids/field_ids/method_ids ← class_defs可以用 Dalvik 设计者在 Google Developer Day 2008 China 演讲中给出的经典图示直观理解这种巧妙设计这张图清楚地展示了各索引表之间的引用关系字符串是最底层的数据类型描述符、字段名、方法名都引用字符串方法原型引用类型方法/字段/类定义再引用前两者。理解这条链是手工解析 DEX 与编写解析工具的基础。六、DEX 数据区数据区存放的是索引区所指的各种真实数据主要包括StringDataMUTF-8 编码的字符串前面由 uleb128 长度前缀TypeList参数/接口类型列表ClassData类的字段与方法概要DexClassDataCodeItem方法字节码DexCode以及注解annotation、调试信息debug info等辅助数据。从文件头来看data_size必须为sizeof(uint)的偶数倍8 字节对齐data_off给出数据区起始偏移。数据区内部各项通过各索引项的*Off字段定位这也是 map section见下节存在的意义——为 Dalvik 虚拟机提供一份内容清单便于一次性映射全部区段。七、DEX map sectionDexHeader中的mapOff字段给出了DexMapList结构在 DEX 文件中的偏移。当 Dalvik 虚拟机解析 DEX 文件后会将内容映射到DexMapList数据结构可以说该结构描述了对应 DEX 文件的整体概况。struct DexMapList { u4 size; /* DexMapItem 的个数方便解析 */ DexMapItem list[1]; /* 指向 DexMapItem */ }; struct DexMapItem { u2 type; /* kDexType 开头的类型 */ u2 unused; /* 未使用用于字节对齐 */ u4 size; /* 指定相应类型的个数 */ u4 offset; /* 指定相应类型的数据的文件偏移 */ }; /* type 字段为一个枚举常量通过类型名称很容易判断它的具体类型。 */ /* map item type codes */ 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, };map section 的用途可以归纳为三点全局索引DexMapList中的每一项都记录某类数据在文件中的偏移 个数相当于整个文件的目录合法性校验枚举值0x0000 ~ 0x0006对应 header 与五大索引区0x1000 ~ 0x1003与0x2000 ~ 0x2006对应数据区中的各种 item。逆向工具如 010 Editor 模板可借助 map 快速列出文件内所有区段并校验偏移是否合法加载优化Dalvik 解析时可根据 map 一次性完成内存映射无需逐个扫描。注意枚举值区间暗示的类别归属0x0000~0x0006是头部与索引区 item0x1000~0x1003是类型列表与注解集合相关0x2000~0x2006是类数据、代码、字符串数据、调试信息、注解与编码数组——这与前文索引区/数据区的三段式划分完全对应。八、实战手工解析一个真实 DEX 文件原文档给出的实战建议是自己找一个 APK然后拿 010 Editor 的模板解析。这里给出完整的实操路径8.1 获取样本任意 APK可从自己手机上提取或使用 SDK 构建一个 hello-world 应用APK 本质是 zip 包其中的classes.dex即目标文件。解压后即可获得 DEX 样本。8.2 使用 010 Editor 解析打开 010 Editor加载 DEX 文件安装/加载 DEX 模板如Dex.bt解析后即可看到与本文结构体一一对应的树形结果先看DexHeader确认 magic 为dex\n035\0、headerSize 为 0x70、endianTag 为 0x12345678依次展开string_ids、type_ids、proto_ids、field_ids、method_ids、class_defs核对各表的大小字段与偏移字段通过map_off找到DexMapList对照 map item 枚举检查各数据区段手动验证一个字符串取string_ids中第一个stringDataOff跳转到对应偏移按uleb128 长度 MUTF-8 字节 \x00的布局读取验证前文所述编码规则。8.3 命令行辅助工具除 010 Editor 外也可以使用现成的开源工具链交叉验证dexdumpAndroid SDK 自带dexdump classes.dex可输出文件头摘要、各索引表与 class 定义baksmali/apktool将 dex 反汇编为 smali 代码把二进制结构还原为可读的.registers、.field、.method声明与DexCode中的寄存器计数相互印证。8.4 与 ODEX 的关系在实际逆向中还会遇到 ODEX 文件——它是 DEX 的优化产物首次加载 dex 时进行优化生成 ODEX 并存放在/data/dalvik-cache目录ROM 自带 app 则直接以 odex 形式存放在 apk 同目录以加快开机启动速度。其具体结构与生成过程可参考仓库中 ODEX 文件 一文的介绍。理解 DEX 是理解 ODEX、以及后续脱壳与修复工作的前提。九、延伸从 DEX 到 Smali 与逆向实战DEX 是二进制格式可读性差逆向工程领域使用 smali 语法作为其汇编级表示。仓库 Smali 章节 指出Android Java/Kotlin 代码最终编译为 dex 字节码由 Dalvik 或 ART 运行时加载执行ART 视情况采用解释执行、JIT 或 AOT 编译借助 apktool、baksmali 可将 dex 反汇编为多个 smali 文件一般一个 smali 文件对应一个 Java 类。从本文的DexCode结构可以看到这种对应的底层依据registersSize寄存器总数、insSize参数个数直接对应 smali 方法声明中的.registers与.locals/.paramsclass_defs中的 accessFlags、staticFields/instanceFields、directMethods/virtualMethods 划分对应 smali 文件中的类信息、字段描述与方法描述triesSize与debugInfoOff对应 smali 中的异常处理.catch与调试信息。因此在 CTF 逆向题中常见思路是先用工具如unzip取出 classes.dex、再用baksmali/jadx将其反汇编/反编译再结合本文的二进制结构知识定位关键类与方法最后在寄存器级smali或源码级jadx 反编译分析算法。参考阅读Android 软件安全与逆向分析仓库 DEX 文件本文核心来源仓库 Smali 语法DEX 字节码的汇编表示仓库 ODEX 文件DEX 的优化形态仓库 Android 中 Java 层的运行机制本主题在仓库中的章节定位【免费下载链接】ctf-wikiCome and join us, we need you!项目地址: https://gitcode.com/gh_mirrors/ct/ctf-wiki创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表