加壳与脱壳之基本原理

篇幅有限

完整内容及源码关注公众号:ReverseCode,发送

逆向基本流程

  1. 使用自动化检测工具检测apk是否加壳,或者借助一些反编译工具依靠经验推断是否加壳

GDA3.85 或者 常见so文件

《娜迦》企业版本加固libedog.so
《娜迦》免费版本加固libddog.so
《梆梆》企业版本加固libDexHelper.so
《梆梆》免费版本加固libsecexe.so
《爱加密》企业版本加固ijiami.ajm
《爱加密》免费版本加固libexec.so
《通付盾》加固libegis.so
《360》加固libprotectClass.so,libjiagu.so
《百度》加固libbaiduprotect.so
《阿里》加固libmobisec.so
《腾讯》加固libtup.so
《盛大》加固libapssec.so
《瑞星》加固librsprotect.so
《网秦》加固nqdata
《国信灵通》加固libnqshield.so
《apkprotect》加固apkprotect
《几维安全》加固libkwscmm.so,libkwscr.so,libkwslinker.so
《UU安全》加固libuusafe.jar.so,libuusafe.so,libuusafeempty.so
几维安全lib/armeabi-v7a/libkwscmm.so,lib/armeabi-v7a/libkwscr.so,lib/armeabi-v7a/libkwslinker.so
UU安全assets/libuusafe.jar.so,assets/libuusafe.so,lib/armeabi/libuusafeempty.so
  1. 如果apk加壳,则需要首先对apk进行脱壳

  2. 使用jeb,jadx,apktool等反编译工具对apk进行反编译

    如果反编译工具打不开,使用010Editor把文件魔术字修复前8个字节

    64 65 78 0A 30 33 35 00 dex.035. 再使用jadx反编译

    grep -ril “MainAcitvity” ./*.txt 找到对应dex前缀名

  3. 先依据静态分析中得到的关键字字符串,关键api调用等方法快速定位需要分析的关键函数和流程

  4. 如果依据简单的字符串,关键api无法快速定位,则apk可能使用了字符串加密,反射调用等手段,此时可以结合hook,动态调试等

  5. 定位到关键函数后,再根据是java实现还是jni实现进一步分析

类加载基本原理

JVM类加载器

  1. Bootstrap ClassLoader(引导类加载器)

    C/C++代码实现的加载器,用于加载指定的JDK的核心类库,比如java. lang、java.utI等这些系统类。Java虚拟机的启动就是通过 Bootstrap,该 Classloader在java里无法获取,负责加载/lib下的类。

  2. Extensions Classloader(拓展类加载器)

    Java中的实现类为 Extclassloader,提供了除了系统类之外的额外功能,可以在java里获取,负责加载/lib/ext下的类

  3. Application ClassLoader(应用程序类加载器)

    Java中的实现类为 AppClassLoader,是与我们接触对多的类加载器,开发人员写的代码默认就是由它来加载, ClassLoader.getSystemClassLoader返回的就是它。

可以自定义类加载器,只需要通过java.lang.ClassLoader来实现自己的类加载器。

加载顺序:Bootstrap ClassLoader->Extensions Classloader->Application ClassLoader

双亲委派

类加载流程

如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。

1)避免重复加载,如果已经加载过一次class,可以直接读取已经加载的class

2)更加安全,无法自定义类来替代系统的类,可以防止核心API库被随意篡改

Android类加载器

加载时机

  • 隐式加载:创建类的实例,访问类的静态变量,或者为静态变量赋值,调用类的静态方法,使用反射方式来强制创建某个类或接口对应的java.lang.Class对象,初始化某个类的子类
  • 显式加载:使用LoadClass()加载,使用forName()加载

加载过程

类加载过程
  1. 装载:查找和导入Class文件
  2. 链接:其中解析步骤是可以选择的
    (a)检查:检查载入的class文件数据的正确性
    (b)准备:给类的静态变量分配存储空间
    (c)解析:将符号引用转成直接引用
  3. 初始化:即调用<clinit>函数,对静态变量,静态代码块执行初始化工作

ClassLoader继承关系

classloader继承关系

ClassLoader:抽象类;
BootClassLoader:预加载常用类,单例模式。与Java中的BootClassLoader不同,它并不是由C/C++代码实现,而是由Java实现的;
BaseDexClassLoader是PathClassLoader、DexClassLoader、InMemoryDexClassLoader的父类,类加载的主要逻辑都是在BaseDexClassLoader完成的。
SecureClassLoader继承了抽象类ClassLoader,拓展了ClassLoader类加入了权限方面的功能,加强了安全性,其子类URLClassLoader是用URL路径从jar文件中加载类和资源。
其中重点关注的是PathClassLoaderDexClassLoader
PathClassLoader是Android默认使用的类加载器,一个apk中的Activity等类便是在其中加载。
DexClassLoader可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现插件化、热修复以及dex加壳的重点。

DexClassLoader方法参数

dexPath:目标所在的apk或者jar文件的路径,装载器将从路径中寻找指定的目标类。
dexOutputDir:由于dex 文件在APK或者 jar文件中,所以在装载前面前先要从里面解压出dex文件,这个路径就是dex文件存放的路径,在 android系统中,一个应用程序对应一个linux用户id ,应用程序只对自己的数据目录有写的权限,所以我们存放在这个路径中。
libPath :目标类中使用的C/C++库。
parent:该装载器的父装载器,一般为当前执行类的装载器。

Android8.0新引入InMemoryDexClassLoader,用于直接从内存中加载dex。

http://androidxref.com/8.0.0_r4/ 搜索位于libcore中的Definition的DexClassLoader 、PathClassLoader、 InmemoryDexClassLoader查看源码。

ClassLoaderTest

新建项目ClassLoaderTest验证类加载器的加载顺序

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
28
29
30
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testClassLoader();
}

/**
* I/kanxue: thisClassLoader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.onejane.classloadertest-8BzVJE0RmCd4VMY0V2ZBAQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.onejane.classloadertest-8BzVJE0RmCd4VMY0V2ZBAQ==/lib/x86, /system/lib, /system/product/lib]]]
* I/kanxue: this:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.onejane.classloadertest-8BzVJE0RmCd4VMY0V2ZBAQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.onejane.classloadertest-8BzVJE0RmCd4VMY0V2ZBAQ==/lib/x86, /system/lib, /system/product/lib]]]===java.lang.BootClassLoader@232d58a
* I/kanxue: root:java.lang.BootClassLoader@232d58a
*
*/

public void testClassLoader(){
ClassLoader thisClassLoader=MainActivity.class.getClassLoader();
Log.i("kanxue","thisClassLoader:"+thisClassLoader);
ClassLoader tmpClassLoader = null;
ClassLoader parentClassLoader=thisClassLoader.getParent();
while (parentClassLoader!=null){
Log.i("kanxue","this:"+thisClassLoader+"==="+parentClassLoader);
tmpClassLoader=parentClassLoader.getParent();
thisClassLoader=parentClassLoader;
parentClassLoader=tmpClassLoader;
}
Log.i("kanxue","root:"+thisClassLoader);
}
}

LoadDex

通过DexClassLoader实现一个动态加载的dex插件

在ClassLoaderTest项目中打印log

1
2
3
4
5
public class TestDexClass {
public void testFunc(){
Log.i("kanxue","I'm from com.onejane.classloadertest.TestDexClass.testFunc");
}
}

通过build生成ClassLoaderTest\app\build\outputs\apk\debug\app-debug.apk, apktool d -s app-debug.apk保留并抽取出classes.dex

adb push classes.dex /sdcard

创建LoadDex空白Android项目,加载调用位于dex下com.onejane.classloadertest.TestDexClass

在AndroidManifest.xml中添加读写权限

1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>

新增application配置android:requestLegacyExternalStorage=”true”

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Context context = this.getApplicationContext();
/**
* Android 10 sdk30 无法获取sdcard权限,通过获取当前app的cache目录
* if(!getExternalCacheDir().exists()) getExternalCacheDir().mkdirs();
* testDexClassLoader(context,getExternalCacheDir().getAbsolutePath()+"/classes.dex");
* 再将TestDexClass所在的adb push classes.dex /sdcard/Android/data/com.onejane.loaddex/cache/
*/
// 若sdk30需要sdcard,通过requestMyPermissions动态获取读写权限
testDexClassLoader(context,"/sdcard/classes.dex");

}

private void requestMyPermissions() {

if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//没有授权,编写申请权限代码
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
} else {
Log.d("kanxue", "requestMyPermissions: 有写SD权限");
}
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//没有授权,编写申请权限代码
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 100);
} else {
Log.d("kanxue", "requestMyPermissions: 有读SD权限");
}
}

// 可应对app热更新bug修复
public void testDexClassLoader(Context context, String dexfilepath){
// 存放dex文件
File optFile = context.getDir("opt_dex",0);
// 存放依赖的so文件
File libFile = context.getDir("lib_path",0);
ClassLoader parentClassLoader=MainActivity.class.getClassLoader();
Log.i("kanxue",parentClassLoader.toString()); //PathClassLoader
ClassLoader tmpClassLoader=context.getClassLoader();
Log.i("kanxue",tmpClassLoader.toString()); // PathClassLoader
requestMyPermissions();
DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath,optFile.getAbsolutePath(),libFile.getAbsolutePath(),MainActivity.class.getClassLoader());
Class<?> clazz=null;
try{
clazz=dexClassLoader.loadClass("com.onejane.classloadertest.TestDexClass");
}catch (ClassNotFoundException e){
e.printStackTrace();
}
if(clazz!=null){
try {
Method testFuncMethod=clazz.getDeclaredMethod("testFunc");
Object obj = clazz.newInstance();
testFuncMethod.invoke(obj);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}

}
}

利用外部dex下发热修复bug,用户无感知更新。

APP启动流程

由发起进程通过binder进程间通信告知system_server进程启动指定的app,system_server通过socket方式与安卓孵化器进程进行通信,告诉孵化器进程启动指定app,由进程fork产生新的进程真正进入ActivityThread.main(),在此之前一直处于安卓的framework中。

APP启动流程

ActivityThread是单例模式,在app整个进程的生命周期中,只存在一个实例,http://androidxref.com/8.0.0_r4/ 搜索位于frameworks中的ActivityThread

调用静态函数currentActivityThread获取当前进程中的ActivityThread实例,进而可以获取ActivityThread的重要变量,如mPackages

1
2
3
4
public static ActivityThread currentActivityThread() {
return sCurrentActivityThread;
}
final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();

其中LoadedApk中有加载app组件的PathClassLoader,即mClassLoader

  1. 通过反射获取app进程中单例ActivityThread,其中的sCurrentActivityThread静态变量用于全局保存创建的ActivityThread实例,同时还提供了currentActivityThread()静态函数用于获取当前虚拟机创建的ActivityThread实例。

  2. 反射拿到ActivityThread中的 mPackages的ArrayMap,通过当前app包名获取LoadedApk

  3. 最后通过LoadedApk获取mClassLoader->PathClassLoader

PathClassLoader: app运行过程中用于加载四大组件类的ClassLoader

**ActivityThread.main()**函数是java中的入口main函数,这里会启动主消息循环,并创建ActivityThread实例,之后调用thread.attach(false)完成一系列初始化准备工作,并完成全局静态变量sCurrentActivityThread的初始化。之后主线程进入消息循环,等待接收来自系统的消息。当收到系统发送来的bindapplication的进程间调用时,调用函数handleBindApplication来处理该请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void handleBindApplication(AppBindData data) {
//step 1: 创建LoadedApk对象
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
...
//step 2: 创建ContextImpl对象;
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);

//step 3: 创建Instrumentation
mInstrumentation = new Instrumentation();

//step 4: 创建Application对象;在makeApplication函数中调用了newApplication,真正执行app代码。在该函数中又调用了app.attach(context),在attach函数中调用了Application.attachBaseContext函数
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;

//step 5: 安装providers
List<ProviderInfo> providers = data.providers;
installContentProviders(app, providers);

//step 6: 执行Application.Create回调
mInstrumentation.callApplicationOnCreate(app);
...
}

handleBindApplication函数中第一次进入了app的代码世界,该函数功能是启动一个application,并把系统收集的apk组件等相关信息绑定到application里,在创建完application对象后,接着调用了application的attachBaseContext方法,之后调用了application的onCreate函数。由此可以发现,app的Application类中的attachBaseContextonCreate这两个函数是最先获取执行权进行代码执行的。这也是为什么各家的加固工具的主要逻辑都是通过替换app入口Application,并自实现这两个函数,在这两个函数中进行加密dex释放以及执行权交付的原因。

APP运行流程

app运行流程

无壳:PathClassLoader加载自身app自身dex,包括app声明的Application及所有其他类信息

加壳:PathClassLoader只加载壳自身代码,不包含app自身真正代码。首先进入壳的application的attachBaseContext,解密原始dex,再完成执行权的交付。

加壳app启动流程

DexClassLoader加载的类是没有组件生命周期的,也就是说即使DexClassLoader通过对APK的动态加载完成了对组件类的加载,当系统启动该组件时,依然会出现加载类失败的异常,因为插件没有组件相关的类,如一些activity或service,若只用DexClassLoader进行动态加载,系统PathClassLoader无法找到相关组件信息,app将直接崩溃。所以如何解决动态加载dex中的生命周期成为加壳厂商首先需要解决的问题!!!

在项目ClassLoaderTest中新建TestActivity ,并将build的apk中的classes.dex取出,adb push classes.dex /sdcard

1
2
3
4
5
6
7
8
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// adb push classes.dex /sdcard
Log.i("kanxue","I'm from TestActivity.onCreate");
}
}

在项目LoadDex中,配置AndroidManifest.xml<activity android:name="com.onejane.classloadertest.TestActivity"></activity>,在MainActivity中新增方法,并启动运行

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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Context context = this.getApplicationContext();
startTestActivity(context,"/sdcard/classes.dex");
}
public void startTestActivity(Context context,String dexfilepath){
// 存放dex文件
File optFile = context.getDir("opt_dex",0);
// 存放依赖的so文件
File libFile = context.getDir("lib_path",0);
ClassLoader parentClassLoader=MainActivity.class.getClassLoader();
Log.i("kanxue",parentClassLoader.toString()); //PathClassLoader
ClassLoader tmpClassLoader=context.getClassLoader();
Log.i("kanxue",tmpClassLoader.toString()); // PathClassLoader
requestMyPermissions();
DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath,optFile.getAbsolutePath(),libFile.getAbsolutePath(),MainActivity.class.getClassLoader());
Class<?> clazz=null;
try{
clazz=dexClassLoader.loadClass("com.onejane.classloadertest.TestActivity");
}catch (ClassNotFoundException e){
e.printStackTrace();
}
if(clazz!=null){
context.startActivity(new Intent(context,clazz));
}
}

启动app将报错ClassNotFoundException: Didn’t find class “com.onejane.classloadertest.TestActivity”

说明组件相关的Activity由mClassLoader->PathClassLoader加载了,虽然获取到非空TestActivity,却无法找到启动的目标Activity

方案一

反射替换,替换系统组件类加载器mClassLoader为我们的DexClassLoader,同时设置DexClassLoader的parent为系统组件类加载器PathClassLoader;

反射替换

修改LoadDex项目的MainActivity

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Context context = this.getApplicationContext();
startTestActivityReplaceMethod(this,"/sdcard/classes.dex");

}

private void requestMyPermissions() {

if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//没有授权,编写申请权限代码
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
} else {
Log.d("kanxue", "requestMyPermissions: 有写SD权限");
}
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//没有授权,编写申请权限代码
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 100);
} else {
Log.d("kanxue", "requestMyPermissions: 有读SD权限");
}
}


public void replaceClassloader(ClassLoader classloader){
try {
Class<?> ActivityThreadClazz=classloader.loadClass("android.app.ActivityThread");
// 获取静态函数currentActivityThread
Method currentActivityThreadMethod= ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object activityThreadObj=currentActivityThreadMethod.invoke(null); // 获取ActivityThread对象
//final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
Field mPackagesField=ActivityThreadClazz.getDeclaredField("mPackages"); // 获取ActivityThread对象的ArrayMap的Field
mPackagesField.setAccessible(true);
ArrayMap mPackagesObj= (ArrayMap) mPackagesField.get(activityThreadObj); // 通过Field获取mPackages实例
WeakReference wr= (WeakReference) mPackagesObj.get(this.getPackageName()); // 从ArrayMap中获取LoadApk
Object loadedApkObj=wr.get();

Class LoadedApkClazz=classloader.loadClass("android.app.LoadedApk");
//private ClassLoader mClassLoader;
Field mClassLoaderField=LoadedApkClazz.getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);
mClassLoaderField.set(loadedApkObj,classloader); // 将LoadApk(mClassLoader)替换为我们的DexClassLoader
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}


}
public void startTestActivityReplaceMethod(Context context,String dexfilepath){
// 存放dex文件
File optFile = context.getDir("opt_dex",0);
// 存放依赖的so文件
File libFile = context.getDir("lib_path",0);
requestMyPermissions();
DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath,optFile.getAbsolutePath(),libFile.getAbsolutePath(),MainActivity.class.getClassLoader());
replaceClassloader(dexClassLoader);
Class<?> clazz=null;
try{
clazz=dexClassLoader.loadClass("com.onejane.classloadertest.TestActivity");
}catch (ClassNotFoundException e){
e.printStackTrace();
}
if(clazz!=null){
context.startActivity(new Intent(context,clazz));
}
}
}

方案二

打破原有的双亲关系,在系统组件类加载器和BootClassLoader的中间插入我们自己的DexClassLoader即可

反射插入

修改ClassLoaderTest中的TestActivity类继承自Activity

1
2
3
4
5
6
7
8
9
public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
// adb push classes.dex /sdcard
Log.i("kanxue","I'm from TestActivity.onCreate");
}
}

修改LoadDex中的MainActivity继承Activity

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Context context = this.getApplicationContext();
// 若sdk30需要sdcard,通过requestMyPermissions动态获取读写权限
// testDexClassLoader(context,"/sdcard/classes.dex");
// startTestActivityReplaceMethod(this,"/sdcard/classes.dex");
startTestActivityInsertMethod(this,"/sdcard/classes.dex");

}

private void requestMyPermissions() {

if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//没有授权,编写申请权限代码
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
} else {
Log.d("kanxue", "requestMyPermissions: 有写SD权限");
}
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//没有授权,编写申请权限代码
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 100);
} else {
Log.d("kanxue", "requestMyPermissions: 有读SD权限");
}
}


public void startTestActivityInsertMethod(Context context,String dexfilepath){
File optfile=context.getDir("opt_dex",0);
File libfile=context.getDir("lib_path",0);
requestMyPermissions();
ClassLoader pathClassloader=MainActivity.class.getClassLoader();
ClassLoader bootClassloader=MainActivity.class.getClassLoader().getParent();
// 设置自定义dexClassLoader父ClassLoader为bootClassloader
DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),bootClassloader);
try {
Field parentField=ClassLoader.class.getDeclaredField("parent");
parentField.setAccessible(true);
// 设置pathClassLoader父ClassLoader为自定义dexClassLoader
parentField.set(pathClassloader,dexClassLoader);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}

ClassLoader tmpClassloader=pathClassloader;
ClassLoader parentClassloader=pathClassloader.getParent();
while(parentClassloader!=null){
Log.i("kanxue","this:"+tmpClassloader+"--parent:"+parentClassloader);
tmpClassloader=parentClassloader;
parentClassloader=parentClassloader.getParent();
}
Log.i("kanxue","root:"+tmpClassloader);
Class<?> clazz=null;
try {
clazz = dexClassLoader.loadClass("com.onejane.classloadertest.TestActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
context.startActivity(new Intent(context,clazz));
}
}

以上都是基于文件形式的外部加载,Android 8.0引入InMemoryDexClassLoader实现从内存中直接加载字节流,更加安全,不能通过文件监控形式达到脱壳目的,需要从进程内存中扣出这块完整的dex实现脱壳。

  • 部分app在AndroidManifest.xml中没有声明application,加固厂商只需要添加一个application,在自己的application中完成classloader的替换。
  • 部分app在AndroidManifest.xml中已经声明application,加固需要一个代理的application,壳的application不仅需要完成解密dex以及classloader相关修复,还需要完成解密dex后原app的application的attachBaseContext和onCreate函数调用。

  • 逆向分析和脱壳的意义
  • ClassLoader和动态加载
  • 加壳APP运行流程和ClassLoader修正
文章作者: J
文章链接: http://onejane.github.io/2021/02/04/加壳与脱壳之基本原理/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏