根据上面的原理图,我们需要3个对象。
源Apk:需要加壳的apk。
壳Apk:将apk解密还原并执行。
加密工具:将源apk和壳dex进行组合成新的dex并且修正新的dex。
IDE:Android Studio 4.1.3
Android版本:4.4+
项目源码:nisosaikou/AndroidDEX壳 - 码云 - 开源中国 (gitee.com)
自定义的一个Application
类,让这个类继承Application
,并且实现onCreate
方法。
修改MainActivity.java
的父类,使得MainActivity
继承于Activity
。将文本显示修改为运行的是源APK。
生成一个release版本apk,把这个apk保存起来。
新建一个类叫ProxyApplication
,继承于类Application
。这个类用来解密原始的APK。
把壳dex中包含的源apk释放出来。
把释放的apk进行解密。
把源apk中的lib
目录中的文件复制到当前程序(壳)的路径下。
创建一个新的DexClassLoader
,替换到父节点的DexClassLoader
。
DexClassLoader
继承自BaseDexClassLoader,这个比较灵活,每个参数都可以自定义,我们一般用这个来加载自定义的apk/dex/jar文件。
ActivityThread功能
它管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),并根据AMS的要求(通过IApplicationThread接口,AMS为Client、ActivityThread.ApplicationThread为Server)负责调度和执行activities、broadcasts和其它操作。
在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的【主线程】负责执行。
在Android系统中,如果有特别指定(通过android:process),也可以让特定组件在不同的进程中运行。无论组件在哪一个进程中运行,默认情况下,他们都由此进程的【主线程】负责执行。
【主线程】既要处理Activity组件的UI事件,又要处理Service后台服务工作,通常会忙不过来。为了解决此问题,主线程可以创建多个子线程来处理后台服务工作,而本身专心处理UI画面的事件。
【主线程】的主要责任:
注意事项:
类结构参考
调用currentActivityThread
方法获取ActivityThread
中的成员变量sCurrentActivityThread
。
获取sCurrentActivityThread
中的mBoundApplication
。
获取mBoundApplication
中成员变量info
。
观察LoadedApk
这个类,能发现一些重要的属性,这个下面会用到。
将info
中的mApplication
属性置空。
在sCurrentActivityThread
下的链表mAllApplications
中移除mInitialApplication
。mInitialApplication
存放初始化的应用(当前壳应用),mAllApplications
存放的是所有的应用。把当前的应用,从现有的应用中移除掉,然后再把新构建的加入到里面去。
构造新的Application
更新2处className
。
注册application(用LoadedApk中的makeApplication方法注册)。
替换mInitialApplication
为刚刚创建的app
。
更新ContentProvider。
执行新app的onCreate
方法。
Java反射调用的方法。
到目前为止,源程序能够运行起来了,但是apk在运行的时候肯定会用到相关的资源,如布局文件等等,我们并没有介绍如何处理资源。
资源有2中大的处理方法。第一种是在壳dex解压出源apk时,把apk中的资源复制到现在程序下。第二种是替换壳apk中dex文件时,顺便用源apk中的资源文件替换到壳apk中。因为本文不重点讨论资源的处理问题,所以采用第二种方法,直接复制替换资源即可。
将APK和壳DEX文件合并,生成一个新的DEX文件,并且校正新的DEX文件头。
用DEXFixed.jar工具把src.apk和classes.dex进行合并,生成一个新的Dex,替换到壳APK中。
替换壳APK中的classes.dex
、res
、resources.arsc
。
apk重新签名。
正常运行。
dex壳是比较基础的壳,只是将源APK加密后放入dex文件中,在运行时进行释放。我们只需要在壳程序解密出原始的APK运行后,在内存中把dexdump下来就可以了,我们也可以用frida框架进行脱壳。
Object
currentActivityThread
=
RefInvoke.invokeStaticMethod(
"android.app.ActivityThread"
,
"currentActivityThread"
, new Class[] {}, new
Object
[] {});
Object
currentActivityThread
=
RefInvoke.invokeStaticMethod(
"android.app.ActivityThread"
,
"currentActivityThread"
, new Class[] {}, new
Object
[] {});
Object
mBoundApplication
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
, currentActivityThread,
"mBoundApplication"
);
Object
mBoundApplication
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
, currentActivityThread,
"mBoundApplication"
);
Object
loadedApkInfo
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread$AppBindData"
, mBoundApplication,
"info"
);
Object
loadedApkInfo
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread$AppBindData"
, mBoundApplication,
"info"
);
RefInvoke.setFieldOjbect(
"android.app.LoadedApk"
,
"mApplication"
, loadedApkInfo, null);
RefInvoke.setFieldOjbect(
"android.app.LoadedApk"
,
"mApplication"
, loadedApkInfo, null);
Object
oldApplication
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
, currentActivityThread,
"mInitialApplication"
);
ArrayList<Application> mAllApplications
=
(ArrayList<Application>) RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
, currentActivityThread,
"mAllApplications"
);
mAllApplications.remove(oldApplication);
Object
oldApplication
=
RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
, currentActivityThread,
"mInitialApplication"
);
ArrayList<Application> mAllApplications
=
(ArrayList<Application>) RefInvoke.getFieldOjbect(
"android.app.ActivityThread"
, currentActivityThread,
"mAllApplications"
);
mAllApplications.remove(oldApplication);
ApplicationInfo appinfo_In_LoadedApk
=
(ApplicationInfo) RefInvoke.getFieldOjbect(
"android.app.LoadedApk"
, loadedApkInfo,
"mApplicationInfo"
);
ApplicationInfo appinfo_In_AppBindData
=
(ApplicationInfo) RefInvoke.getFieldOjbect(
"android.app.ActivityThread$AppBindData"
, mBoundApplication,
"appInfo"
);
appinfo_In_LoadedApk.className
=
srcAppClassName;
appinfo_In_AppBindData.className
=
srcAppClassName;
ApplicationInfo appinfo_In_LoadedApk
=
(ApplicationInfo) RefInvoke.getFieldOjbect(
"android.app.LoadedApk"
, loadedApkInfo,
"mApplicationInfo"
);
ApplicationInfo appinfo_In_AppBindData
=
(ApplicationInfo) RefInvoke.getFieldOjbect(
"android.app.ActivityThread$AppBindData"
, mBoundApplication,
"appInfo"
);
appinfo_In_LoadedApk.className
=
srcAppClassName;
appinfo_In_AppBindData.className
=
srcAppClassName;
Application app
=
(Application) RefInvoke.invokeMethod(
"android.app.LoadedApk"
,
"makeApplication"
, loadedApkInfo, new Class[] { boolean.
class
, Instrumentation.
class
}, new
Object
[] { false, null });
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)