学习脱壳笔记 如有问题还请各位大佬指正
近期整理的脱壳笔记有点多
笔记地址
https://github.com/madking177/zzj_md/blob/main/%E9%80%86%E5%90%91/%E8%84%B1%E5%A3%B3.md
视频地址
整体壳
https://www.bilibili.com/video/BV1iZ421e7Xs/?spm_id_from=333.999.0.0
类加载器
https://www.bilibili.com/video/BV1yF4m1N7sb/?spm_id_from=333.788&vd_source=d1067370dca5b90f8acffbc140968f18
抽取壳还原
https://www.bilibili.com/video/BV1uT421y7DL/?spm_id_from=333.788&vd_source=d1067370dca5b90f8acffbc140968f18
整体壳
加壳流程
1.原始dex加密
2.记录dex还原信息
3.壳dex尾部追加加密的dex
4.追加dex还原信息(加密dex偏移量等还原信息)
5.追加还原信息长度(末尾4字节)
6.filesize:dex原来长度+dex加密字节长度+dex还原信息长度+4字节(还原信息长度)
6.计算signature(除了标识头,checksum其余的hash值)
7.计算checksum(除了标识头,checksum外所有)
8.重新打包
实现还原:
- 末尾4字节
- 得到还原信息
- 得到dex字节列表进行加密
- dexclassloader进行加载替换 结束
被保护apk+脱壳dex 合并生成新的dex 重新签名打包apk
打包后的结构
工具脱壳
简述
通用脱壳工具好使,遇到反脱壳的检测手段稍微上点难度就用不了。
dexdump,blackdex,fart
找包packagae
dumpsys window windows | grep mCurrentFocus
adb shell dumpsys window windows | grep mCurrentFocus
mCurrentFocus=Window{e415d0f u0 com.chaoxing.mobile/com.chaoxing.mobile.main.ui.MainTabActivity}
frida-dexdump脱壳
1 2 3 4 5 6 7 8 9 10 11 | 核心原理:暴力搜索
git地址:https: / / github.com / hluwa / frida - dexdump?tab = readme - ov - file
公众号地址:https: / / mp.weixin.qq.com / s / n2XHGhshTmvt2FhxyFfoMA
frida - dexdump - U - n com.chaoxing.mobile - o . / xuexitong - d - - sleep 5
- o 输出目录
- d 深度搜索
- - sleep 等待?s开始脱壳
结果:扫描出来一堆没用的dex文件
|
-n运行
1 2 3 4 5 6 7 | 显示 all done说明结束
然后整体编译刚脱壳出来的文件
jadx - d <输出目录路径> * .dex
jadx - d output * .dex
如果hook时间不合适 如下图
|
-f运行
反编译失败
blackdex 32位,64位apk脱壳
blackdex地址 https://github.com/CodingGay/BlackDex/releases
选择要被脱壳的应用
blackdex脱壳成功了
开始抓包(分析三个变化参数 inf_enc明显是个hash串,token没有变化过)
分析笔记页面 多刷新几次 观察参数变化
找到笔记接口
找inf_enc参数
这是一个变化参数 因为同接口刷新几次以后发现-time,inf_enc变化
搜索inf_enc发现上文在拼接一个stringbuilder函数
inf_enc rpc的hook点
inf_enc只是一个md5码而已
以后深入探讨学习一下blackdex的源代码
1 2 3 | 深入探讨文章地址
https: / / blog.niunaijun.top / index.php / tag / BlackDex /
|
classloader探讨
职责:类加载器的职责是寻找和加载类
类加载器的种类
根据用途划分
系统加载器
扩展类加载器
应用程序加载器
- inmemorydexclassloader 内存捞dex文件
- pathclassloader
- dexclassloader
用户自定义加载器
双亲委派
目的
类加载器的层次结构
高评率使用的加载器(gpt答案)
通义千问答案
简单说 不同加载其有不同职责,职责若干 加载器若干!
开发者角度
引导类加载器(BootClassLoader C++),扩展加载器,应用程序加载器(统称 原生的系统类加载器)
主要还是因为了解不够的情况下 分不太清
用户类自定义类加载器
jdk下 系统类加载器
注意:类加载器的层次关系不代表是类加载器的继承关系 如下图
第一层
classloader 抽象 定义
secure classloader 继承+安全权限 定义
urlclassloader 通过url路径从jar文件和文件夹加载类和资源
art角度下的classloader
注意:
开发代码上区别不大,但是jdk下的类加载与art下类加载并不同
art,jdk都是虚拟机 并遵守了jvm设计上的一些规范 art完全兼容支持 jvm 这也是java可以在sdk环境运行的主要原因
art下开发者角度
都是引导类加载器 解释如下
- 总结1 art中引导类加载类是java类 可以java代码引用 并且实现classloader抽象类
- 总结2 jdk中 bootstrap 加载jdk核心类库 c++实现 无java引用 也实现classloader抽象类
- classloader是顶层规范的抽象类 约束加载器继承者的行为
介绍art下的系统类加载器
注意:真实情况下的类加载器数不胜数
bootclassloader(核心类库),pathclassloader(扩展),dexclassloader(app程序)
dexclassloader继承basedexclassloader
可加载dex相关文件
pathclassloader继承baseclassloader
加载系统类和应用程序类
MainActivity的每一次层classloader验证
运行结果显示dexpathclassloader->bootclassloader
继承关系
加载类介绍
引导类加载器如何被创建
zygote进程
main方法是zygoteinit的入口
调用zygoteinit的preload
zygote的preloadClasses(初始化时候预加载常用类)
罗列部分类名
结论就是预加载越多(通用) 后期app启动就会越快
结论
art下的BootClassLoader用于在zygote的初始化阶段创建 用于预加载类的加载
总结
classloader案例(分析)
BootClassLoader (因为包名不可见 所以无法引用)
DexClassLoader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
File dexFile = new File( "/data/local/tmp/classes.dex" );
try {
DexFile dex = new DexFile(dexFile);
Enumeration<String> entries = dex.entries();
while (entries.hasMoreElements()) {
String className = entries.nextElement();
Log.d( "dex-test" , className);
}
} catch (IOException e) {
e.printStackTrace();
}
|
PathClassLoader
1 | PathClassLoader pathClassLoader = new PathClassLoader( "/data/local/tmp/classes.dex" );
|
输出
InmemoryClassLoader
编译原理前奏知识补充
(逆向分析)art虚拟机中只涉及优化器和后端
编译模式迭代过程(jit just-in-time,aot ahead-of-time)
elf文件格式
so文件就是elf(理解elf最好的方式就是写一个解析工具解析一下so文件)
可用readelf工具解析elf
- readelf -h xxso 查看头部信息
- readelf -S xxxso 查看so section 头信息
- readelf -l xxxso 查看so得段头信息
- readelf -a xxxso 查看elf全部内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | elf头部定义得伪代码
/ / ELF文件头部结构体
struct ElfHeader {
uint8_t magic[ 4 ]; / / Magic Number
uint8_t file_type; / / 文件类型
uint8_t machine_type; / / 机器类型
uint8_t version; / / 版本号
uint64_t entry_point_addr; / / 入口地址
uint64_t program_header_offset; / / 程序头表偏移
uint64_t section_header_offset; / / 节头表偏移
uint32_t flags; / / 标志
uint16_t header_size; / / 头部大小
uint16_t program_header_entry_size; / / 程序头表条目大小
uint16_t num_program_headers; / / 程序头表条目数量
uint16_t section_header_entry_size; / / 节头表条目大小
uint16_t num_section_headers; / / 节头表条目数量
uint16_t string_table_index; / / 字符串表索引
};
/ / 定义ELF文件头部的魔术数字
const uint8_t ELF_MAGIC[ 4 ] = { 0x7F , 'E' , 'L' , 'F' };
/ / 根据实际情况定义其他常量和结构体
|
头部信息解释
- Magic Number: ELF文件的开始处包含一个特殊的魔术数字,用于标识文件的格式。在32位ELF文件中,这个数字是0x7F, 'E', 'L', 'F'的组合;在64位ELF文件中,这个数字是0x7F, 'E', 'L', 'F', '2'的组合。
- 文件类型(File Type): 指示文件的类型,如可执行文件、目标文件、共享目标文件等。
- 机器类型(Machine Type): 指示文件目标体系结构的类型,如x86、ARM、MIPS等。
- 版本号(Version): 指示ELF格式的版本号。
- 入口地址(Entry Point Address): 可执行文件的入口点地址,即程序开始执行的地址。
- 程序头表偏移(Program Header Table Offset): 指示程序头表的偏移量,程序头表包含了有关可执行文件的段信息。
- 节头表偏移(Section Header Table Offset): 指示节头表的偏移量,节头表包含了有关文件中各个节(sections)的信息。
- 标志(Flags): 包含了一些标志位,用于指示一些特殊的属性,比如是否包含重定位信息等。
- 头部大小(Header Size): 指示ELF头部的大小。
- 程序头表条目大小(Program Header Table Entry Size): 指示每个程序头表条目的大小。
- 程序头表条目数量(Number of Program Header Table Entries): 指示程序头表中的条目数量。
- 节头表条目大小(Section Header Table Entry Size): 指示每个节头表条目的大小。
- 节头表条目数量(Number of Section Header Table Entries): 指示节头表中的条目数量。
- 字符串表索引(String Table Index): 指示节头表中字符串表的索引,用于存储字符串信息,比如节名等。
安卓打包
dex文件格式
dex头部信息描述
classdef 数据结构
存放类信息,相比dex文件更复杂,因为有数据指向了data区
- class_idx: 一个指向类型描述符列表(type_ids)的索引,指示类的类型。
- access_flags: 类的访问标志,指示类的访问权限和特性,例如
public
, private
, final
等。
- superclass_idx: 一个指向类型描述符列表(type_ids)的索引,指示父类的类型。
- interfaces_off: 指向接口列表的偏移量,列出了该类实现的所有接口。
- source_file_idx: 指向源文件名的索引,指示该类的源文件名(如果有的话)。
- annotations_off: 指向注解列表的偏移量,列出了与该类关联的所有注解。
- class_data_off: 指向类数据项的偏移量,其中包含了该类的字段和方法的详细信息。
- static_values_off: 指向静态变量值列表的偏移量,列出了该类的所有静态变量的初始值。
codeitem 定义
registers_size
: 该方法使用的寄存器数量。
ins_size
: 方法的输入参数数量。
outs_size
: 方法的输出参数数量。
tries_size
: 异常处理器列表的大小。
debug_info_off
: 调试信息的偏移量,指向方法的调试信息。
insns_size
: 方法的指令数量。
insns
: 方法的指令数组,包含方法的所有指令。
blackdex原理
总结:
虚拟化技术让app进程运行,加固壳自解密
前置原理
- 安卓虚拟化技术
- dex加载原理
- dexfile结构
- 安卓hook
基于blackbox开发的
简单说在blackdex app中安装一个安卓系统,在这个安卓系统中安装需要脱壳的app,然后基于dexfile结构体和加载原理脱壳进行代码还原
dex加载进内存
application通过loadedapk#makeapplication完成
fart脱壳 -理解hanbing大佬系列文章
fart系列1 拨云见日
- (明文阶段)获取dex起点和尺寸
- 两个脱壳点 dexfileparser(dex明文字节流解析),openmemory参数包含dex开始位置和大小(解密明文写入内存)
- fart的脱壳核心就是明文dex的起点和尺寸
- java中脱壳,可以通过类关联到其所在的dex,class->classloader->getDex->getbytes
- 类关联到classloader
- classloader关联到dex对象
- dex对象中字节流就是dex明文
art下关键字快速定位
1. ART编译机制与脱壳
dex2oat: 将编译的dex文件(编译代码的中间表示形式)编译成art虚拟机可以直接执行的oat机器码
编译粒度: oat编译是函数粒度的编译,但有一部分函数不会被预编译,而是动态编译执行。
脱壳机会: 壳会干扰dex2oat
的编译过程,即使预编译的代码也不一定会执行,那么结论就是还是倾向于动态编译执行,这就是脱壳机会,拿到dex句柄或者指针,从而获得dex尺寸和偏移量。
fart系列2 FART正餐前甜点
重点1 类加载
上图右下角 art下类加载流程 经历了 loadclass ---> loadclassmember(一个脱壳点) ->linkcode的过程(逻辑推理 就是代码执行过程中需要什么就提前加载什么到自己的作用域,如指令,类,方法等)
重点2 抽取壳子的思路(code_off和insns的smali数组)
insns_size代表smali数组长度
insns代表smali数组 通常两个字节数组
抽取壳子的主要下手思路(占坑型或重构型策略,)
总结就是:
直接,间接有dexfile引用的地方都可以整一个dump点,进行整体dex的脱壳
Android ART环境中的一种通用、简单且高效的内存中DEX脱壳方法。这种方法利用了ART类加载执行流程中的关键类ArtMethod
及其相关函数,通过Hook技术(如Xposed或Frida)实现对加固应用的脱壳。以下是该方法的概述和实现细节:
fart系列3 FART:ART环境下基于主动调用的自动化脱壳方案
app启动流程
ActivityThread.main()
是App进程的入口,负责启动主消息循环和初始化关键组件。
handleBindApplication
方法在接收到系统bindApplication
请求时被调用,完成应用的真正启动过程。
- 在这一过程中,首先创建
LoadedApk
、ContextImpl
和Instrumentation
对象,然后创建并初始化Application
对象。
Application
的attachBaseContext
和onCreate
方法是App代码执行的最早入口,它们分别负责与应用上下文关联和执行全局初始化逻辑。这也是加固工具常选择在此处进行干预(如代码脱壳、安全检查、权限控制等)的原因。
oncreate和attachBaseContext
加壳原理与运行流程:
- 加壳入口:
- 加壳技术利用
Application
类的attachBaseContext
和onCreate
方法作为切入点,因为它们是App启动时最先获得执行权限的函数。在这些方法中,壳程序执行加密DEX文件的解密操作。
- 加载解密后的DEX:
- 解密完成后,壳使用自定义的
ClassLoader
在内存中加载解密后的DEX文件。这种自定义ClassLoader
通常绕过标准的类加载机制,直接加载内存中的DEX数据,避免在磁盘上留下未加密的痕迹。
- 修复类加载环境:
- 为了使应用能够正常加载并调用解密后的DEX中的类和方法,壳通过Java反射技术修复关键变量,特别是
ClassLoader
。如果不修正ClassLoader
,双亲委派机制会导致系统无法找到解密DEX中的类,引发ClassNotFoundException
,导致应用崩溃。
- 获取解密DEX:
- 一旦壳通过反射设置好了正确的
ClassLoader
,攻击者可以通过一系列反射操作获取到当前应用所加载的解密后的内存中DEX文件,实现对加密内容的提取。
对抗与反制:
- 指令抽取型壳:
- 针对整体加密的DEX容易被内存Dump并还原原始DEX的问题,加固厂商发展出指令抽取型壳技术。这种壳只在函数执行前才解密对应方法的指令区域,使得即使Dump内存中的DEX,也无法获取到关键方法的原始指令,增加了脱壳难度。
- FUPK3工具:
- 作为应对指令抽取型壳的脱壳工具,FUPK3采用欺骗策略。它主动调用DEX中的各个函数,触发壳在方法执行前的解密操作,从而暴露解密后的指令区域。通过这种方法,FUPK3能够完成对指令抽取型壳的脱壳,提取出原本加密的方法体。
将dex文件和bin文件 拼接搞定的抽取壳的smali代码
整体壳dump+主动调用
脱壳总结
- dexfile结构体完整dump
- 主动调用每一个方法,实现对codeitem得dump
- 通过主动dump下来的codeitem对dex被抽取方法进行修复
dumpDexfile
APP中的Application类中的attachBaseContext和onCreate函数是app中最先执行的方法。壳都是通过替换APP的Application类并自己实现这两个函数,并在这两个函数中实现dex的解密加载,hook系统中Class和method加载执行流程中的关键函数,最后通过反射完成关键变量如最终的Classloader,Application等的替换从而完成执行权的交付。、
dumpMethodCode
activityThread中的performLaunchActivity 函数作为时机,获取最终classloader,还有一个好处该函数与最终的application都在activityThread类中
通过classloader得到mcookie (so层dexfile的句柄) 在framework添加两个函数提供调用 dumpDexFile,dumpMethodCode
- dumpDexFile 保存dex文件
- dumpMethodCode 保存codeitem
调用art的invoke完成方法的主动调用
在invoke中判断 如果是我们自己的调用 使用dump 然后返回从而完成对壳的欺骗
对java层传来的method结构体进行类型转换,转成native的ArtMethod对象,接下来调用artMethod的myfartinvoke实现,并完成方法体的dump,
artMethod的invoke我们在第一个参数Thread传递null标志作为主动调用的标识,
(art原理将的不是很好 因为没有手动去做实验所以并不知道里面的坑,移植fart编译rom镜像之后再给大家汇报成果)
fart脱一代壳 二代壳实验
刷fart8 的镜像
网页上就能搜到 (https://zhuanlan.zhihu.com/p/573577231)
找pixel ,pixel xl2,nexus5三种机型的fart镜像(百度就有)
刷机文章链接
https://blog.csdn.net/blackblackblack223/article/details/137341966?spm=1001.2014.3001.5501
镜像成功以后装需要的app,在/sdcard/xxxx下面找自己需要脱的代码包
脱壳以后的代码
对dex进行反编译
切换到smali视角
可以得到类的信息和函数的smali信息 说明dex的整体是抽取下来了,问题是代码全部是nop 怎么办
这里就需要做第二件事情了,被抽取smali的还原。
这里运行hanbing大佬写的fart python脚本还原
可以还原但是有一个问题,python2版本太老了,并且smali代码看着不习惯
因为找到了github的第二个项目 dexrepair,luoyesiqiu写的还原dex文件的抽取代码的项目
https://github.com/luoyesiqiu/DexRepair
结论:
出发点是好的,但是由于fart脚本的迭代,导致项目的适用性变差,思路一致 ,但是还原,解析格式的字节偏移量也需要做一些相应的改动,虽然只是单纯的解析脚本,但是对于不懂dex抽取壳子,codeitem原理的同学来说会有一些吃力。
codeItem原理
再次关于dex文件格式的废话
三个区
- 头部 dex整体概括
- 索引区域 (字符串,类型,原型,类变量,函数,类定义 链接到数据区) 数据目录
- 数据区 (完全是一大片字节流 ,全靠索引定位)
classdef 数据结构
类id,访问标志,父类id,接口偏移量,源文件id,注解偏移量,类数据偏移量,静态值偏移量等
重点1 class_data_item
class_data_off链接到数据库的class_data_item中(从索引区链接到数据区了)
- 静态变量总数(地址)
- 类变量数量
- 类函数数量
- 虚函数总数
- 静态变量,类变量数组
- 静态方法,类方法数组
encodeed_field
描述静态变量,类变量 (类变量 id,访问权限标志)
encoded_method
注意:
描述类变量,静态变量,类方法,静态方法都用的数组!!!!
描述结构体都加了idx_off后缀,因为除了第一个id真的存id以外,后面的所有id都存的是相对于前一个id的差值,这种匪夷所思的设计据说是为了避免id太大造成内存浪费。
重点2 code_off!!终于链接到codeItem了
classdef 到class_data_item 到 encode_method的code_off
code_off 就是codeitem的偏移量了
针对二代壳 codeItem里面全部是重点,确定一下有什么
- 虚拟寄存器数量
- 输入参数总数
- 输出参数总数
- try-catch 块总数
- debug-info 调试信息地址 (行号,文件名,堆栈等)
!!!! 注意以上全部是两字节
- insns_size smali指令长度
- ushort []insns 真实smali指令数组 !!!!!!!!!!!!!!!!!
到此为止 后面不分析了(数据够了)
抽取壳就是把insns给抽取掉了
这块借鉴寒冰大佬总结的
抽取壳 大多情况两种思路
以后造成的结果就是 除非执行前还原,否则离线分析,codeitem里面的代码永远都是坏的
如何还原2代壳代码
dexrepair里面代码没什么 ,就是解析,定义和dex文件一定的数据结构(解析需要的就行),然后计算偏移量
适配 dexrepair 中对应fart的偏移量
解析地址是对的,但是函数没正确还原出来(就是dexRepair和fart版本没对应上)
经过按照上述原理修正偏移量和部分适配逻辑以后执行的代码在010editor中去验证
修复之前 除了大面积的 00 00 00 00
修复以后
jadx反编译验证(二代抽取壳抽取的smali代码)
抽取壳代码还原以后
到此二代壳 就还原完毕了
补充信息
反调试,反脱壳有时候脱壳前需要解决的问题,特征屏蔽。在脱壳之前加一个证书几乎无解
目前能把二代壳还原可以更大程度解决一些问题
现状
脱壳脱不下来(脱壳闪退 运行了但是壳脱不下来)
加hook有反调试 (反调试 找不到地方)
抓包有证书双向校验 (不知道证书在哪里)
改aosp 自编译系统镜像破局
- java2c,so加固,so抽取,ollvm混淆(99%+的垃圾代码),vmp 正在研究
rpc ,objection,frida-trace,frida-talker ,等基于frida的上层工具去进行辅助
总结到目前全是java层的内容居多
壳厂商特征
娜迦
libchaosvmp.so , libddog.solibfdog.so
爱加密
libexec.so, libexecmain.so,ijiami.dat
梆梆
libsecexe.so, libsecmain.so,libSecShell.so
梆梆企业版
libDexHelper.so , libDexHelper-x86.so
360
libprotectClass.so, libjiagu.so
libjiagu.so, libjiagu_art.so
libjiagu.so, libjiagu_x86.so
通付盾
libegis.so,libNSaferOnly.so
网秦
libnqshield.so
百度
libbaiduprotect.so
阿里聚安全
aliprotect.dat,libsgmain.so,libsgsecuritybody.so
腾讯
libtup.so, libexec.so,libshell.so
mix.dex
lib/armeabi/mix.dex ,lib/armeabi/mixz.dex
腾讯御安全
libtosprotection.armeabi.so,
libtosprotection.armeabi-v7a.so,
libtosprotection.x86.so
网易易盾
libnesec.so
APKProtect
libAPKProtect.so
几维安全
libkwscmm.so, libkwscr.so, libkwslinker.so
顶像科技
libx3g.so
商业壳逆向案例
特征:
- assets下有ijiami文件夹 xxxxx.dat中隐藏so文件
- 在脱壳代码中找到了,libexec,libmainexec
确定是爱加密的壳
反调试思路 为了不阻塞app主进程 一般都是异步检测
hook create_pthread 进行绕过解决frida反调试问题
frida可以注入后dump出libexec的so文件(源文件加密 dump运行时明文) 用sofix进行还原 可以在ida中正常打开
刷fart8 到手机脱壳
使用dexrepair进行二代壳还原
加上双向证书绕过的代码
进入正常分析流程
数据可能会加密 通过r0capture 抓包,但是最上层的堆栈都是线程的run方法 因为通过hook Thread 的构造函数关联到r0capture的打印堆栈,即可将加密的数据代码与流量堆栈快速关联起来
objection,frida-trace,frida-stalker辅助定位代码,strace,readelf等辅助分析so文件
最后于 9分钟前
被mb_hdptcgkk编辑
,原因: 修复图片