pollux's Dairy

内存壳学习

字数统计: 973阅读时长: 6 min
2020/01/31 Share

内存壳的保护方式主要是通过动态加载的方式保护源dex。

DexShellS的主要功能是解密释放源dex,并移交控制权,开始源dex的生命周期。

1、重写attachBaseContext,替换LoadedApk中的mClassLoader

加载App的classloader被保存在LoadedApk类的mClassLoader属性中,所以首先要找到这个mClassLoader。每一个App都有的ActivityThread对象,可以通过静态方法currentActivityThread获得,ActivityThread类有一个ArrayMap类型的变量mPackages,保存了packageName到LoadedApk对象的映射

1
2
3
4
5
6
7
8
Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread",
"currentActivityThread",
new Class[]{},new Object[]{});
ArrayMap mPackages = (ArrayMap)RefInvoke.getFieldObject("android.app.ActivityThread",
currentActivityThread,
"mPackages");
String packageName = this.getPackageName();
WeakReference wr = (WeakReference) mPackages.get(packageName);//get LoadedApk

获得LoadedApk进而可以获得源classloader,源classloader保存了原先加载的资源,由于双亲委派机制,我们不能放弃源classloader

1
ClassLoader mClassLoader = (ClassLoader)RefInvoke.getFieldObject("android.app.LoadedApk",wr.get(),"mClassLoader");

加载释放后的源dex,并继承于源classloader

1
DexClassLoader dexClassLoader = new DexClassLoader(apkFileName, odexPath, libPath, mClassLoader);

这个新的classloader是通过DexClassLoader加载得到的,并不能拥有onCreate等生命周期,所以还要将其赋值给LoadedApk的mClassLoader属性

1
RefInvoke.setFieldObject("android.app.LoadedApk", "mClassLoader",wr.get(),dexClassLoader);

以上就完成了替换LoadedApk中的mClassLoader的替换。因为application等属性还是DexShellS的,所以此时加载的源Dex还不能拥有正常的生命周期,需要做进一步屏蔽。

2、重写onCreate

因为此时application还是DexShellS的,所以要调用LoadedApk#makeApplication创建源Dex的application,通过查看源码可以知道:

1)LoadedApk的mApplication属性要为空,不然将返回DexShellS的application
2)修改LoadedApk#mApplicationInfo#appClass为源Dex的Applicaiton的Class
3)创建applicaiton后会新添加在mActivityThread.mAllApplications中,所以要删除DexShellS的application

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
26
27
public Application makeApplication(boolean forceDefaultAppClass,Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
initializeJavaContextClassLoader();
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
......
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
......

return app;
}

根据以上3步,具体代码实现如下:

1)LoadedApk#mApplication==NULL

1
2
3
4
5
6
7
8
//---set LoadedApk#mApplication = null
Object mcurrentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread",
"currentActivityThread",new Class[]{},new Object[]{});
Object mBoundApplication = RefInvoke.getFieldObject("android.app.ActivityThread",
mcurrentActivityThread,"mBoundApplication");
Object mLoadedApk = RefInvoke.getFieldObject("android.app.ActivityThread$AppBindData",
mBoundApplication,"info");
RefInvoke.setFieldObject("android.app.LoadedApk","mApplication",mLoadedApk,null);

2)修改LoadedApk#mApplicationInfo#className

1
2
3
4
//---set LoadedApk#mApplicationInfo   ActivityThread$AppBindData#appInfo
ApplicationInfo appInfoLoadedApk = (ApplicationInfo) RefInvoke.getFieldObject("android.app.LoadedApk",
mLoadedApk,"mApplicationInfo");
appInfoLoadedApk.className = applicationClassName;

3)修改ActivityThread$AppBindData#appInfo#className

1
2
3
ApplicationInfo appInfoAT$AppBindData = (ApplicationInfo) RefInvoke.getFieldObject("android.app.ActivityThread$AppBindData",
mBoundApplication,"appInfo");
appInfoAT$AppBindData.className = applicationClassName;

4)移除ActivityThread#mAllApplications中DexShellS的application

1
2
3
4
5
6
7
//---remove the old application in the ActivityThread#mAllApplications
Object oldApplication = RefInvoke.getFieldObject("android.app.ActivityThread",
mcurrentActivityThread,
"mInitialApplication");
ArrayList<Application> mAllApplications = (ArrayList<Application>)RefInvoke.getFieldObject("android.app.ActivityThread",
mcurrentActivityThread, "mAllApplications");
mAllApplications.remove(oldApplication);

5)创建新application

1
2
Application app = (Application)RefInvoke.invokeMethod("android.app.LoadedApk","makeApplication",
mLoadedApk,new Class[]{boolean.class, Instrumentation.class},new Object[]{false,null});

6)替换ActivityThread#mInitialApplication为新application

1
2
RefInvoke.setFieldObject("android.app.ActivityThread","mInitialApplication",
mcurrentActivityThread,app);

7)替换mProviderMap中的application

1
2
3
4
5
6
7
8
9
ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldObject("android.app.ActivityThread",
mcurrentActivityThread,"mProviderMap");
Iterator iterator = mProviderMap.values().iterator();
while(iterator.hasNext()){
Object providerClientRecord = iterator.next();
Object mLocalProvider = RefInvoke.getFieldObject("android.app.ActivityThread$ProviderClientRecord",
providerClientRecord,"mLocalProvider");
RefInvoke.setFieldObject("android.content.ContentProvider",
"mContext",mLocalProvider,app);

之后新的applicaiton就可以开始新的生命周期了

主要学习了app的启动流程,这种内存壳已经被完全破解了,下面的指令抽取壳也可以通过主动调用类方法去实现脱壳,将核心算法放入so文件,并混淆可以有效加强代码的保护强度,java2c不稳定,和系统版本强相关,现在vmp壳只是对部分函数做虚拟化,比如onCreate

CATALOG
  1. 1. 1、重写attachBaseContext,替换LoadedApk中的mClassLoader
  2. 2. 2、重写onCreate