ARTICLE DETAIL

资讯详情

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

360加固DEX解密与ELF修复:移动应用逆向的完整技术链路

360加固DEX解密与ELF修复:移动应用逆向的完整技术链路 1. 这个标题到底在解决什么问题加固、解密与ELF修复的关系做移动端安全分析的朋友应该都见过这种场景一个APK拿到手里用常规手段一跑jadx或者dex2jar打开的 classes.dex 里全是com.stub.StubApp、com.secneo.apkwrapper这类壳入口真正业务代码根本看不见。这个时候你就知道这个APK上了加固最常见的就是360加固、腾讯乐固、爱加密、梆梆这一类。而标题里把“360加固DEX解密”和“ELF修复”放在一起背后是一条完整的技术链路先脱壳拿到真实DEX再把加固过的SO文件修到能用的状态。先给新手一个概念上的对照APK加固的本质是做“运行时还原”。加壳器在打包阶段把原始DEX加密、抽取、或者隐藏起来App启动时由壳的入口代码完成解密和加载。这个过程一旦发生在内存里就给了分析者机会——你可以让App自己把真实代码吐出来再在内存里抓取、转储、修复。360加固在几代版本里用了不同的方案早年主要是整体加密 自定义ClassLoader加载后来演变成DEX抽取、So加固、VMP虚拟机并存。不管哪种方案最终都会落到两个对象上一个是DEX一个是ELF。DEX解密说的是把壳隐藏的真实字节码还原成可静态分析的DEX文件ELF修复说的是被加壳改造过的Native库.so文件在解密、dump之后需要把ELF结构补全才能被IDA、Ghidra这类工具正常加载。这两个环节经常同时出现因为360加固的很多版本里DEX的还原逻辑就在Native层你把SO修好了脱壳点才找得准DEX才能完整dump出来。所以这不是两件独立的事而是同一条分析流水线的上下站。这条内容适合谁看三类人最需要一类是做移动端安全评估、合规测试的工程师一类是做App崩溃治理、兼容性适配的Android开发遇到加固SDK导致的Native崩溃需要理解ELF改动到底动了什么还有一类是刚入门逆向、想系统搞懂“加固 ≠ 无敌”的朋友。前提只有一条分析的样本必须是你自己开发的App或者你有明确授权的测试目标。没有授权去拆别人的App这不是技术问题是法律问题下面讲的思路全部默认建立在合规场景上。2. DEX解密流程从脱壳点定位到指令修复2.1 脱壳的基本思路什么时候dump最关键DEX脱壳核心在于找准dump时机。壳再复杂也要在某个时间点把真实DEX加载到内存里交给虚拟机执行。你要做的就是在“DEX完整存在于内存”的那一刻把这块内存抠出来。360加固的脱壳点不同版本差异很大。老版本里壳代码会先加载一个加密的DEX文件解密后放到内存里再调用DexClassLoader或者自定义的ClassLoader去加载。这种方案下只要hook住ClassLoader的构造过程或者直接hookdalvik.system.DexFile的构造函数就能拿到解密后的DEX。新版360加固喜欢用抽取动态还原也就是把DEX的CodeItem方法体里的指令抽走运行到某个方法时再去Native层还原。这个时候就不能光靠ClassLoader hook了得配合dump下运行时的完整DEX回来再修被抽掉的CodeItem。实际操作里遇到360加固的样本我会先用手机或模拟器装上目标App然后起一个Frida脚本把常见脱壳点全部hook一遍dalvik.system.DexFile的构造函数拿到DexFile对象后直接调getBytes()java.lang.ClassLoader的loadClass方法这里能观察到类加载的时机libdexfile.so里的OpenMemory、DexFileLoader::Open这类Native函数如果是Android 8.0以上重点看art::DexFileLoader::OpenCommon或者art::DexFile::DexFile。Frida脚本里比较保守的做法是先用Java.perform把Java层ClassLoader相关的hook挂上启动App自动跑一遍业务逻辑触发大部分类加载然后在内存里搜dex\n035\0这个DEX魔数。搜到就按地址和大小dump出来。这个办法对整体加密型的加固基本是通杀但对抽取型加固dump到的DEX虽然文件头完备指令却被抽空了这时候就需要下一步修复。还有一条经验dump最好在App启动后延迟1到2秒再做别一上来就dump。因为360加固的解密是分阶段触发的主界面没起来很多DEX根本还没解密。实测中很多朋友dump出的DEX只有几十KB就是因为时机太早只抓到了壳的启动模块真实业务DEX还没进内存。2.2 修复DEX文件头拿到dump后第一件事你dump下来的内存镜像跟一个标准DEX文件之间差着几个关键字段。要让它能被jadx、baksmali这类工具正常解析第一步是把DEX header修复干净。一个标准DEX header共112字节0x70关键在于下面这些字段magic前8字节必须是dex\n035\0或者dex\n037\0、dex\n038\0对应不同DEX版本。360加固有的版本会在运行时把魔数临时改掉或者内存里出现多个DEX拼接这时候要根据实际情况裁剪。file_size整个DEX文件的长度。Dump内存时你只能按区域抠这个值经常不对要么是0要么是虚大。修复方法很简单用十六进制工具找到文件尾部的实际偏移直接改这个字段。header_size通常是0x70这个值一般不用动。map_offMAP列表的偏移。很多工具修复时会忽略map_off但baksmali校验严格的时候会报错。稳妥的做法是用baksmali的fixDex参数或者用现成的DEX修复工具把map补一遍。data_off/data_size数据区偏移和大小抽取型加固修完指令后这两个字段经常要跟着调。link_off/link_size一般没用到静态链接默认0。实际操作中我最常用的工具组合是先用frida-dexdumpGitHub上开源的那个自动dump再用010 Editor加载DEX模板手动核对头部字段最后用baksmali反汇编验证。如果dump出来的是正常的完整DEXjadx直接打开就能看到代码如果提示“DEX file size mismatch”或者“unexpected end of file”基本就是file_size和实际长度对不上改一下就好。这里有个坑有时候dump出来的DEX文件头是好的但打开后类列表是空的或者一堆方法体缺失。这种情况多半不是文件头的问题而是抽取型加固造成的指令缺失得做下一步指令修复。2.3 指令修复抽取型加固的硬骨头抽取型加固是把DEX里各个方法的CodeItem指令区抽走运行时通过Native代码按需还原。你dump到的DEX类的字段、方法签名都在但每个方法体里只剩下空壳指令区全是0或者被填充成NOP。静态分析工具看到的就是“方法存在但没有代码”。修复抽取型加固的思路很直接既然运行时每个方法执行前都会把指令填回去那就hook住这个填充过程。360加固的抽取实现早期在Java层后来移到Native层。在Native层关键函数是art::ArtMethod::Invoke它在真正执行方法前会检查CodeItem是否存在不存在就去解密恢复。分析的时候可以hookArtMethod::Invoke拿到ArtMethod指针后直接读取它指向的DexCache和ClassLinker定位到对应的CodeItem在DEX文件中的位置再把内存里的真实指令写回dump下来的DEX副本。这套流程听起来复杂实操上有一个取巧的技巧只要你能完整dump到一份“运行过足够多方法”的内存DEX那么完整度已经很高了。怎么让App运行足够多方法跑一遍核心业务流程该点的页面点一遍该触发的方法都触发一遍。此时dump下来的DEX跟静态修复出来的结果差别不大再用FARTFrida ART这类半自动脱壳机去主动调用遍历把每个方法都主动Invoke一遍就能把指令全部补回来。FART的基本思路是主动调用hook住ClassLoader.loadClass拿到每个Class后遍历所有方法一个个触发ArtMethod::Invoke。方法执行完指令肯定被还原到了内存里这时候统一dump。实测下来360加固的抽取型方案用主动调用方式能修出80%以上的方法剩下20%有JNI动态注册、反射调用、或者极端混淆的方法需要手工补。这一块没有银弹只能靠经验和耐心一点点对。3. ELF修复从文件格式到重定位3.1 ELF文件结构速览用010 Editor看懂So文件说完了DEX现在进入标题的另一半ELF修复。Android的Native库.so就是ELF格式和Linux上的可执行文件同源。加固过的SO静态看往往是无害的加载器真正的逻辑要么被加密要么被打散。分析的第一步是用010 Editor打开SO文件加载ELF模板把结构读懂。ELF文件分成几个层面ELF Header在最前面定义文件的基本属性接下来是Program Header Table程序头描述运行时如何加载再往后是Section Header Table节头描述静态链接时的各个节.text、.data、.dynsym、.rel.dyn等。加固壳经常做的事情是把敏感的节加密成自定义数据块只留一个极小的加载器运行时解密后按正确的加载地址映射到内存。用010 Editor看ELF时优先确认这几项e_type是ET_DYN共享目标文件Android的.so基本都是这个如果是ET_EXEC就要注意是不是被改过了。e_machineEM_ARM40是32位ARMEM_AARCH64183是64位ARM。32位和64位的修复逻辑不一样别混。e_entry入口点地址。加固SO的入口通常被改成壳的初始化函数原始入口被隐藏了。脱壳后要观察它是否指向了合理的导出函数。e_phoff/e_phnumProgram Header偏移和数量。PT_LOAD段必须覆盖整个运行时代码区如果PT_LOAD段数量或范围不对加载器就起不来。e_shoff/e_shnumSection Header偏移和数量。很多加固SO会把section header表删掉或者加密导致IDA加载时报“The file is not a valid ELF”或者解析不到符号。网上搜“relocations in generic elf”经常出现的是IDA或Ghidra在加载某些ELF时给出的警告。意思是文件中存在重定位项但IDA的通用ELF加载器无法解析它们。这类情况十有八九不是文件坏了而是节表缺失、重定位表被加密或者section header里对应的字符串表被清空了。修复的目标就是把这些被壳隐藏的表补回来。3.2 加固SO的解密流程壳到底改了哪些东西360加固的Native部分通常有两条处理路径。一条是在Java层通过System.loadLibrary加载时SO里的JNI_OnLoad负责解密DEX另一条是把SO自身的代码段也加密运行到init_array时在内存中解密这种叫做“自解密SO”。自解密SO的工作方式是这样的链接器加载SO时会先执行PT_INIT_ARRAY里的初始化函数。加固壳把自己的解密函数放进init_array的最前面执行时先解密被加密的代码段然后跳转到原始入口。如果你只是一股脑从内存里把SO抠出来拿到的是解密后的内存镜像但它的ELF头和程序头描述的加载方式还是加密前的状态直接保存成文件给IDA加载就会出现各种解析异常。修复ELF在脱壳场景下要做的事情很明确从内存中dump出解密后的SO镜像完整映射到data段的区域修正ELF Header里的e_entry、e_shoff、e_shnum根据内存布局重建Program Header Table确保PT_LOAD段的范围和权限与运行时一致重建或修复Section Header Table让IDA能正确识别节和符号表修复重定位表让静态分析工具能把GOT/PLT的跳转关系看清。前两步相对机械难的是后三步因为加固壳通常不会保留原始的section信息。3.3 修复重定位与GOT/PLT热词背后的坑“relocations in generic elf”这个报错之所以频繁出现在跟360加固相关的讨论里是因为加固SO里重定位表的处理往往不标准。Android的.so文件在链接时会生成.rel.dyn和.rel.plt64位是.rela.dyn和.rela.plt两类重定位表。.rel.dyn保存绝对地址重定位如R_ARM_ABSOLUTE、R_AARCH64_ABS64.rel.plt保存函数跳转重定位如R_ARM_JUMP_SLOT、R_AARCH64_JUMP_SLOT。加固壳为了不让分析者一眼看出函数之间的调用关系会把这些重定位表加密或删除。运行时壳自己维护一份重定位信息在解密代码段后动态修正GOT表。你从内存dump出来的SOGOT表已经是修复后的运行时状态了但静态文件里没有对应的.rel.plt段IDA就无从得知哪个GOT项对应哪个外部函数。于是它给出“relocations in generic elf”的警告后续的F5反编译结果也就变得非常不可靠。修复重定位表的思路是依赖“运行时GOT已经正确”这一点反推GOT表里每一个有效指针都对应一个被解析过的外部函数。通过对比so文件的动态符号表.dynsym和运行时GOT表的内容可以手工建出一份重定位表。具体做法用readelf -S查看SO的section列表找到.dynsym动态符号表和.dynstr动态字符串表。用readelf -r看看现有的重定位表是否为空或者缺失。用010 Editor打开文件的GOT区域通常在.got或.data.rel.ro节中逐个读取指针值。将每个GOT项的地址偏移与动态符号表里的符号地址做匹配匹配上的就能还原出一条重定位记录。这个过程工作量不小但修复完后IDA的分析质量会明显提升可以正常看出SO调用了哪些系统API如mmap、open、dlopen等对还原加固逻辑非常关键。另外静态修复时需要特别关注GOT表的权限位。Android的RELRO机制会将GOT表设为只读如果修复后的SO文件里GOT表所在段的权限标记错误比如标成可写运行时可能导致崩溃。你要是做的是“修复后能在设备上跑起来”的目标这个细节必须检查。4. 实操中的高频问题与排查方法4.1 bad ELF interpreter与文件权限最常见但最容易被忽略“bad ELF interpreter: No such file or directory”报错在热词里跟修复相关但它其实不是加固导致的而是在Linux/Android环境里跑ELF时系统找不到动态链接器的经典错误。32位ELF需要32位的/lib/ld-linux.so.264位ELF需要64位的/lib64/ld-linux-x86-64.so.2。如果你把一个32位的加固SO或者旧的命令行工具放到64位系统上直接运行没装32位运行库就会报这个错。处理方式很简单确认ELF位数和系统架构然后安装对应的兼容库。Debian/Ubuntu系装libc6-i386或者lib32z1CentOS/RHEL系装glibc.i686。在Android设备上如果自己写的Native工具报这个错检查一下是不是用错了NDK的toolchain编出了错误架构的so。另一个跟文件权限相关的问题是Permission denied。这个看起来简单但很多人在修复或分析ELF时都会踩从内存dump出来的so文件默认权限可能是600直接拿去执行就报权限不够。chmod x一把梭就能解决。还有的情况是NTFS或FAT32挂载分区不支持可执行权限把so拷到ext4或tmpfs上再跑就正常了。这种“环境问题”排查起来有时候比技术问题还费时间先打个file xxx.so和ls -l xxx.so能省不少冤枉路。对于文件权限修复分布式场景下还常见一个坑如果你通过CI/CD管道把so文件分发到多台测试机压缩包或Git仓库可能把可执行位丢了。Android的AGP构建其实会自动处理so的执行权限但你要是手动去改APK里的so一定要用zip工具带权限属性别用普通的文件管理器重新打包。4.2 加固DEX和ELF修复后的运行崩溃从日志反推问题费了半天劲把DEX和ELF都修好了结果一运行就崩这是家常便饭。关键要区分是修复文件的问题还是原App本身的代码就敏感。如果崩溃发生在启动早期logcat里看到ClassNotFoundException或者NoSuchMethodError通常是DEX修复不完整类或方法没补全。先回DEX文件头检查file_size和map_off再用baksmali重新反编译一次看报错的具体类在哪里断档。这个方法很笨但很管用baksmali的报错信息通常会指出某个类的某个方法索引越界你就能定位到是哪一部分指令还没修复。如果崩溃发生在Native层logcat里能看到Fatal signal 11 (SIGSEGV)或者Abort message: art::ArtMethod::...要优先怀疑ELF修复的问题。常见原因有三种SO的PT_LOAD段范围不对运行时访问到未映射区域GOT表修复错误导致某个外部函数指针指向了无效地址.init_array被改了但顺序不对壳的初始化函数比真实业务代码晚执行。自己在分析的时候先用IDA加载修复后的SO看它能不能正确识别导入导出函数。如果F5出来的伪代码里一堆off_xxxxxx说明GOT/PLT关系没修好。这个阶段别急着继续往下分析先把重定位表补完否则后面全是无用功。另外补充一点热词里的“双系统引导修复”“麒麟系统修复助手”这类场景跟ELF修复其实是同一类底层思维——引导程序、加载器、文件系统三者的关系没对上。Windows的双系统引导修复是修BCDLinux的ELF加载失败是修动态链接器路径Android的加固So修复是修ELF头和重定位表。核心逻辑都一样让加载器能找到下一个要执行的东西。4.3 工具选型与操作流程整理做360加固DEX解密和ELF修复工具不需要多但要配合顺。我自己长期用的组合是环节工具用途动态hookFrida、FART找脱壳点、主动调用、dump内存十六进制编辑010 Editor带ELF/DEX模板查看、修复文件头、节表二进制分析readelf、llvm-readelf、Ghidra查看节表、符号表、重定位表静态反编译jadx、baksmali验证DEX修复成果Native分析IDA Pro、Ghidra分析SO逻辑验证ELF修复成果用010 Editor看ELF文件时很多人上来就加载官方ELF模板但Android的SO很多字段在模板里显示正常实际却被加固壳改得面目全非。建议对照readelf -h和readelf -l的输出逐项核对工具之间交叉验证能发现不少问题。如果你做的是大批量样本分析建议把这套流程脚本化。Frida先做自动化dump然后写个Python脚本自动修正DEX的header字段批处理跑下来最后再用baksmali全量反编译做质量校验。人工只处理那些自动修复失败的特例。这样效率能提升好几倍也减少手误。注意所有操作必须在授权范围内。加固技术本身是合法的商业产品分析和解密只应该用于你自己拥有、或有权测试的App。写在授权测试报告里的脱壳分析是技术成果拿去拆别人商业App就是另外一回事了这个边界自己想清楚。5. 合规边界与一点个人体会聊完技术细节我多说几句大白话。360加固这类产品本质上是一种软件保护技术它存在的意义是延缓分析者理解App逻辑的速度。解密和修复技术则是从“被保护对象”和“运行环境”的关系中找突破口。这个对抗关系跟猫鼠游戏一样没有一劳永逸的方案只有不断升级的手段。我个人在实际操作中的体会有三点。第一不要迷信任何“一键脱壳工具”。那些工具不是没用而是只能覆盖一部分加固版本遇到新版本、新抽取策略工具就失灵了。真正值钱的能力是理解原理能定位到“壳必须在某个时机还原真实代码”这个不变的事实然后顺着这个事实设计自己的方案。第二修复工作要沉得住气。DEX修到一半、ELF重定位表修到想吐的时候最容易出错。建议每修完一步就做一次验证修完header就baksmali一把修完重定位就重新用IDA看导入表验证通过再进行下一步。第三保留好dump原始内存镜像。修复过程中改坏了随时回到原始dump重新来别在已经改过的文件上反复改越改越乱。如果后续你想深入扩展建议往两个方向走一个是VMP虚拟机保护方向代码被编译成自定义字节码在虚拟机解释器里执行那已经不是“还原DEX”的问题了而是“还原解释器”的问题另一个是ART虚拟机内部结构搞清楚ArtMethod、DexCache、ClassLinker这些运行时对象到底怎么关联的很多表面上的加密手段一看就能理解它为什么这么设计。这两个方向啃下来再回头看360加固这类方案你会觉得它只是同一套思路下的一个具体实现。
返回列表