1. 如何将原始的DEX文件加密隐藏
整体型壳的核心思想是将原始的DEX文件加密后嵌入到一个新的“壳”DEX文件中。DEX文件(即Android应用的Dalvik Executable文件)包含了应用的代码,通常是一个或多个.dex文件。为了防止DEX文件被反编译或者泄露,可以采取以下策略:
- 加密原始DEX文件:将原始的DEX文件加密,存放在壳DEX文件的末尾。
- 修改文件头信息:DEX文件有一部分头信息包括checksum(校验和)、signature(签名)和file_size(文件大小)。这些信息会被用来验证文件的完整性和格式正确性。为了让壳DEX文件在经过修改后仍然能被识别为合法的DEX文件,必须更新这些头信息以反映添加的加密DEX文件的变化。
- 重打包APK:将壳DEX文件重新打包进APK中,这样原始的DEX文件就被隐藏在了加密壳的末尾。
2. 如何让壳代码在应用启动时获取控制权并访问加密的DEX文件
壳的代码需要在应用启动时优先执行,这样才能在合适的时机解密并加载原始的DEX文件。为了实现这一点,通常会:
- 替换Application类:Android应用的启动流程通常由Application类的onCreate()方法开始。壳会通过修改AndroidManifest.xml中的<application>标签,指定自己的Application类,从而在应用启动时优先执行壳代码。
- 获取APK路径:壳代码可以通过getApplicationInfo().sourceDir获取APK文件的路径。然后,可以解压APK,找到壳DEX文件,并从中解密出原始的DEX文件。
3. 如何动态加载解密后的DEX文件并让虚拟机识别四大组件
解密后的DEX文件需要动态加载到应用中,并且让Android虚拟机能够正确识别其中的四大组件(Activity、Service、BroadcastReceiver、ContentProvider)。要做到这一点,需要关注以下几个方面:
- 动态加载DEX文件:使用Android的DexClassLoader来加载解密后的DEX文件。DexClassLoader可以加载APK中的DEX文件,并将其中的类加载到Java虚拟机中。
- 修正类加载器:为了让应用中的四大组件能够正确运行,必须修正类加载器。Android中的每个组件(如Activity)都会在一个特定的类加载器上下文中运行。默认情况下,DexClassLoader加载的类并不与系统的PathClassLoader(应用的默认类加载器)共享上下文,这会导致无法找到相关的组件类,进而抛出ClassNotFoundException。为了解决这个问题,有几种方案:
-
- 替换系统类加载器:将系统的类加载器替换为自定义的DexClassLoader,并将DexClassLoader的parent设为系统的类加载器。
- 修正类加载器的父类关系:将DexClassLoader插入到系统PathClassLoader和BootClassLoader之间,使得DexClassLoader加载的DEX文件可以与系统类加载器共享同一个类加载上下文。
- 生命周期问题:Android中的组件(如Activity)是在ActivityThread类的performLaunchActivity()方法中通过反射创建的。如果加载的DEX文件没有正确嵌入到类加载器的上下文中,虚拟机会报ClassNotFoundException。因此,修正类加载器的上下文关系至关重要。
4. 为什么要修改类加载器
类加载器的隔离:Android系统中的每个组件(如Activity)都在特定的类加载器上下文中运行。DexClassLoader加载的类与PathClassLoader(系统默认的加载器)或BootClassLoader(系统引导加载器)不共享同一上下文。因此,虽然可以通过DexClassLoader加载类,但如果这些类未被正确地嵌入到组件的生命周期中,系统无法识别其存在,导致无法找到相应的类。(DexClassLoader加载的类是没有组件生命周期的) 过一遍pathclassloader就可以加入声明周期了
加载完原始的dex以后还需要对ClassLoader进行修正,否则加载组件类运行的时候会报ClassNotFoundException,为什么会报这种错误呢? 这就涉及到了组件类的创建过程,比如对于Activity来说,应用程序的Activity对象是在ActivityThread类的performLaunchActivity()方法中通过调用mInstrumentation.newActivity()创建出来的,这个函数的实现逻辑为:
可以看到是通过ClassLoader先加载Activity类,再通过newInstance()来实现化类对象。
这个方法传递进来的ClassLoader是加载应用程序的ClassLoader,它所加载的dex为应用程序的主体的dex,对应的DexPathList是没有原始的dex路径的,因此会报ClassNotFoundException。这里也可以看出,一个BaseClassLoader对应着其实是一个Dex文件的列表,如果尝试让BaseClassLoader加载不在这个列表中的类,就会报ClassNotFoundException。
脱壳原理
脱壳是指将加密或混淆的代码还原成原始可执行的代码。脱壳的关键是找到解密后的DEX文件,并将其从内存中提取出来。
- art::DexFile:在ART虚拟机中,DexFile是用来加载DEX文件的类。无论使用哪种类加载器(如BaseClassLoader、DexClassLoader),最终都会通过art::DexFile加载DEX文件。因此,获取art::DexFile的内存地址和大小信息,就可以通过内存dump的方式轻松地提取解密后的DEX文件。
- 内存dump:通过在合适的时机(如应用启动后,DEX文件被加载到内存时)获取art::DexFile的内存地址和大小,可以进行内存dump,提取解密后的DEX文件。这种方法可以绕过加壳的机制,获取到原始的DEX文件。
总结
整体型壳的核心是在应用的启动时通过自定义的Application类获取控制权,加载并解密隐藏在壳DEX文件中的原始DEX文件。为了确保这些解密后的DEX文件能够正常运行,必须修改类加载器的父类关系,使得加载的DEX文件可以与系统类加载器共享同一个上下文。脱壳的核心思路则是通过内存dump获取解密后的DEX文件,利用art::DexFile类的内存结构提取出完整的DEX文件。
原文始发于微信公众号(Ting丶的安全笔记):安卓逆向之第一代:整体型壳的工作原理
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论