侧边栏壁纸
博主头像
Fup1p1 's Blog 博主等级

梦想是财富自由~

  • 累计撰写 38 篇文章
  • 累计创建 24 个标签
  • 累计收到 9 条评论

目 录CONTENT

文章目录

【Android逆向】一、加壳与脱壳&刷机(持续更新)

Fup1p1
2022-12-24 / 0 评论 / 8 点赞 / 2656 阅读 / 0 字 / 正在检测是否收录...

前言

环境及设备

●电脑已具备 ADB 环境。
●虚拟机或实体机已有Root权限。

鉴于实体机性能更加出色(脱壳过程中可以节省大量时间),我还是选择在某鱼上以310元购买一台战损外观的Pixel3作为我的测试机。为何更推荐使用Google手机,因为他们搭载着原生安卓,并且它们的镜像都可以在谷歌开发者网站上找到,Root后你可以随意地魔改自己的系统。

实体机刷机

拿到手机,先进行刷机。刷机有很多方式。但主要分为两种,线刷和卡刷。我不过多论述,大致原理查看这个视频。


如果你的设备也是Pixel 3,你可以查看此博客

准备工作

手机电脑连接上数据线,打开手机的OEM开关以及USB调试开关。如果没有adb工具,那么请先在Android Studio里下载好Android SDK以及Google USB Driver(不然之后fastboot会连接不上手机)。输入命令 adb devices来查看连接是否成功。

刷机步骤

解除bootloader锁(会清除手机数据)

输入adb reboot bootloader,手机会进入到fastboot模式下。
此时,在命令框中输入fastboot flashing unlock。
Snipaste_2022-12-21_20-30-34
通过音量键选择Unlock the bootloader。

安装Magisk面具

下载链接
adb+install+Magisk在你电脑上的地址。
下载与你系统版本号相同的谷歌镜像,两次解压后,取boot.img镜像,传输到手机,使用Magisk软件对boot.img进行修补。
QQ截图20221224160447
修补后的镜像重新传回电脑。再次进入fastboot模式下,执行命令fastboot+boot+boot.img的地址。开机后,进入Magisk软件,再选择直接安装。
QQ截图20221224160803
我们能发现,Magisk的超级用户界面已经可以使用,并且adb也能获得root权限了。
QQ截图20221224160929

学习目录

加壳和脱壳

01.FART的初步使用

了解FART

总所周知,从Android5.0开始,ART取代dalvik,成为默认虚拟机。dalvik和ART运行机制的不同,脱壳方法自然也不同。而FART就是通过主动调用的方式来实现ART环境下的脱壳。FART的代码是通过修改少量Android源码文件而成的,经过修改的Android源码编译成系统镜像,刷入手机,这样的手机启动后,就成为一台可以用于脱壳的脱壳机。
此节并不会谈其原理,只是简单配置FART以及使用方法。
想了解其原理的请click博客链接

安装FART

项目地址
环境:Android 8.0
设备:Pixel 1

安装镜像

镜像链接(拒绝度盘,从我做起)。

手机开启USB调试并连接电脑,进入fastboot模式(bl锁要解开),下载镜像包后解压,直接运行flash-all.sh

自动脱壳

理论上,你运行的apk,它都会自动进行脱壳。我们拿看雪的一道题来演示。
题目链接

adb install apk地址,直接安装。
手机上点开apk,然后直接进入data/data目录下。
QQ图片20221229194857
cd com.kanxue.craceme,就能找到脱壳下来的dex文件了。
image-1673080478231
那么这么多dex,哪个包含MainActivity呢?
我们使用grep -ril “MainActivity” ./*dex 命令,直接找到我们所需要的dex
然后cp到sdcard目录下,便于之后pull到计算机中。
image-1673080713944
如果此时提示没有读写权限,可以看这篇论坛地址
只要之前加上mount -o rw,remount /sys命令即可。
image-1673080974868
pull 到本地,jadx/jeb打开分析。
image-1673081040374

02.ClassLoader与动态加载

安卓中常见的类加载器

3.1 BootClassLoader:单例模式,用来加载系统类
3.2 BaseDexClassLoader:是PathClassLoader、DexClassLoader、InMemoryDexClassLoader的父类 dex和类加载的主要逻辑都是在BaseDexClassLoader完成的
3.3 PathClassLoader:是默认使用的类加载器,用于加载app自身的dex
3.4 DexClassLoader:用于实现插件化、热修复、dex加固等
3.5 InMemoryDexClassLoader:安卓8.0以后引入,用于内存加载dex

image-20230107131815748

image-20230107132121811

image

image-20230107132843986
我们可以去验证一下,来到Android的在线源码网站
Link:http://www.aospxref.com/
如图,在Defination中输入你要查的类即可。
image-1673076804952

查看ClassLoader源代码

image-1673071150293
红框处的代码,记录了parent,实现了后续的双亲委派。

查看BaseDexClassLoader源码

image-1673071255686
继承了ClassLoader

查看DexClassLoader源码

image-1673071299216
继承了BaseDexClassLoader

限于篇幅,其他的请自行探究…

APP从点击图标到运行至第一个Activity的过程,ClassLoader的情况和双亲委派的关系是怎么样的呢?

验证调用的顺序以及双亲委派的关系

我们使用Android Studio进行演示

package com.example.classloadertest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        testclassloader();
    }
    public void testclassloader(){
        ClassLoader thisclassloader=MainActivity.class.getClassLoader();
        Log.i("Fup1p1","thisclassloader: "+thisclassloader);
        ClassLoader tmpclassloader=null;
        ClassLoader parentclassloader=thisclassloader.getParent();
        while(parentclassloader!=null){
            Log.i("Fup1p1","this:"+thisclassloader+"- - -"+parentclassloader);
            tmpclassloader=parentclassloader.getParent();
            thisclassloader=parentclassloader;
            parentclassloader=tmpclassloader;
        }
        Log.i("Fup1p1","root:"+thisclassloader);
    }
    /*
output:
2023-01-07 14:26:45.730 29165-29165/com.example.classloadertest I/Fup1p1: this:classloader: dalvik.system.PathClassLoader[DexPathList[[dex file "/data/data/com.example.classloadertest/code_cache/.overlay/base.apk/classes3.dex", zip file "/data/app/~~opxAMwK2t0T-zvgjwnQaMw==/com.example.classloadertest-9IxjuL8mBmeHnJbr0nW5xQ==/base.apk"],nativeLibraryDirectories=[/data/app/~~opxAMwK2t0T-zvgjwnQaMw==/com.example.classloadertest-9IxjuL8mBmeHnJbr0nW5xQ==/lib/arm64, /system/lib64, /system_ext/lib64, /product/lib64]]]
2023-01-07 14:26:45.731 29165-29165/com.example.classloadertest I/Fup1p1: this:dalvik.system.PathClassLoader[DexPathList[[dex file "/data/data/com.example.classloadertest/code_cache/.overlay/base.apk/classes3.dex", zip file "/data/app/~~opxAMwK2t0T-zvgjwnQaMw==/com.example.classloadertest-9IxjuL8mBmeHnJbr0nW5xQ==/base.apk"],nativeLibraryDirectories=[/data/app/~~opxAMwK2t0T-zvgjwnQaMw==/com.example.classloadertest-9IxjuL8mBmeHnJbr0nW5xQ==/lib/arm64, /system/lib64, /system_ext/lib64, /product/lib64]]]- - -java.lang.BootClassLoader@dcac047
2023-01-07 14:26:45.731 29165-29165/com.example.classloadertest I/Fup1p1: root:java.lang.BootClassLoader@dcac047
可以发现最后的根加载类就是BootLoader
     */
}

编写一个动态加载

Android Studio新建一个empty项目,新建一个TestClass类。
TestClass类:

package com.example.test02;
import android.util.Log;
public class TestClass {
    public void testfunc(){
        Log.i("Fup1p1","i am from com.example.test02.TestClass.testfunc");
    }
}

然后构建中选择build apk,解压后提取其dex。
然后adb push ./classes3.dex /sdcard 4.dex
我们的目的就是让APP去加载这个dex。
MainActivity:

package com.example.test02;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Context appcontext=this.getApplicationContext();
        testdexClassloader(appcontext,"/sdcard/4.dex");
    }
    public void testdexClassloader(Context context,String dexfilepath){
        File optfile=context.getDir("opt_dex",0);
        File libfile=context.getDir("lib_path",0);
        DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(), libfile.getAbsolutePath(),MainActivity.class.getClassLoader());
        Class<?> clazz=null;
        try {
            clazz = dexClassLoader.loadClass("com.example.test02.TestClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if (clazz!=null){
            try {
                Method testfuncMethod=clazz.getDeclaredMethod("testfunc");
                Object obj=clazz.newInstance();
                testfuncMethod.invoke(obj);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }
}

image-1673081909001
给apk写入需要读写权限,运行后需要在手机上授予程序对应的权限。
image-1673081919332

03.加壳App运行流程和ClassLoader修正

博客链接
image-1673264964153

image-1673265101820

image-1673265181380

image-1673265212056

解决方案1

包含组件的插件Dex
package com.example.test03;

import android.os.Bundle;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        Log.i("Fup1p1","i am from TestActivity.oncreate");

    }
}

build apk,解压后将classes.dex push到sdcard中,重命名为66.dex
adb push classes3.dex /sdcard/66.dex

反射替换
package com.example.test02;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.ArrayMap;

import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Context appcontext = this.getApplicationContext();
        startTestActivityFirstmethod(this, "/sdcard/66.dex");
    }
    public void replaceClassloader(ClassLoader classloader){
        try {
            Class<?> Activitythreadclazz=classloader.loadClass("android.app.ActivityThread");
            Method currenTActivityThreadMethod=Activitythreadclazz.getDeclaredMethod("currentActivityThread");
            currenTActivityThreadMethod.setAccessible(true);
            Object Activitythreadobj=currenTActivityThreadMethod.invoke(null);
            Field mpackagesField=Activitythreadclazz.getDeclaredField("mPackages");
            mpackagesField.setAccessible(true);
            ArrayMap mpackagesobj=(ArrayMap) mpackagesField.get(Activitythreadobj);
            WeakReference wr=(WeakReference) mpackagesobj.get(this.getPackageName());
            Object loadedApkobj=wr.get();
            Class LoadedApkclazz=classloader.loadClass("android.app.LoadedApk");
            Field mClassloader=LoadedApkclazz.getDeclaredField("mClassLoader");
            mClassloader.setAccessible(true);
            mClassloader.set(loadedApkobj,classloader);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

    }
    public void startTestActivityFirstmethod(Context context, String dexfilepath) {
        File optfile = context.getDir("opt_dex", 0);
        File libfile = context.getDir("lib_path", 0);
        DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath, optfile.getAbsolutePath(), libfile.getAbsolutePath(), MainActivity.class.getClassLoader());
        replaceClassloader(dexClassLoader);
        Class<?> clazz = null;
        try {
            clazz = dexClassLoader.loadClass("com.example.test03.TestActivity");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        context.startActivity(new Intent(context, clazz));
    }
实现结果

image-1673265452749

解决方案二

image-1673265605310

包含组件的插件Dex

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class TestActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        Log.i("Fup1p1","i am from TestActivity.oncreate");

    }
}
方案二代码
package com.example.test02;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.ArrayMap;

import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Context appcontext = this.getApplicationContext();
//        startTestActivityFirstmethod(this, "/sdcard/66.dex");
        startTestActivitySecondmethod(this,"/sdcard/77.dex");

    }
    public void startTestActivityFirstmethod(Context context, String dexfilepath) {
        File optfile = context.getDir("opt_dex", 0);
        File libfile = context.getDir("lib_path", 0);
        DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath, optfile.getAbsolutePath(), libfile.getAbsolutePath(), MainActivity.class.getClassLoader());
        replaceClassloader(dexClassLoader);
        Class<?> clazz = null;
        try {
            clazz = dexClassLoader.loadClass("com.example.test03.TestActivity");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        context.startActivity(new Intent(context, clazz));
    }
    public void startTestActivitySecondmethod(Context context, String dexfilepath) {
        File optfile = context.getDir("opt_dex", 0);
        File libfile = context.getDir("lib_path", 0);
        ClassLoader pathclassloader=MainActivity.class.getClassLoader();
        ClassLoader bootclassloader=MainActivity.class.getClassLoader().getParent();
        DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath, optfile.getAbsolutePath(), libfile.getAbsolutePath(),bootclassloader);
        try {
            Field parentField=ClassLoader.class.getDeclaredField("parent");
            parentField.setAccessible(true);
            parentField.set(pathclassloader,dexClassLoader);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        Class<?> clazz = null;
        try {
            clazz = dexClassLoader.loadClass("com.example.test03.TestActivity");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        context.startActivity(new Intent(context, clazz));
    }
}
实现结果

image-1673266224940

验证关系

添加代码

ClassLoader tmpclassloader=pathclassloader;
        ClassLoader parentclassloader=pathclassloader.getParent();
        while(parentclassloader!=null){
            Log.i("Fup1p1","this:"+tmpclassloader+"--parent:"+parentclassloader);
            tmpclassloader=parentclassloader;
            parentclassloader=parentclassloader.getParent();
        }
        Log.i("Fup1p1","root:"+tmpclassloader);

output:

/com.example.test02 I/Fup1p1: this:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.test02-pMLEUUkjX85dw_l9WeN5rg==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.test02-pMLEUUkjX85dw_l9WeN5rg==/lib/arm64, /system/lib64, /vendor/lib64]]]--parent:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/77.dex"],nativeLibraryDirectories=[/data/user/0/com.example.test02/app_lib_path, /system/lib64, /vendor/lib64]]]
/com.example.test02 I/Fup1p1: this:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/77.dex"],nativeLibraryDirectories=[/data/user/0/com.example.test02/app_lib_path, /system/lib64, /vendor/lib64]]]--parent:java.lang.BootClassLoader@e9d1226
/com.example.test02 I/Fup1p1: root:java.lang.BootClassLoader@e9d1226

即:
BootClassLoader

DexClassLoader

PathClassLoader

04.一二三代壳和加壳技术分类识别

动态加载是dex加壳、插件化、热更新的基础
动态加载就是用到的时候才去加载,也叫懒加载。
动态加载的dex不具有生命周期特征,APP中的Activity,Service等组件无法正常工作,只能完成一般函数的调用;
这时就需要对ClassLoader进行修正,APP才能够正常运行,两种修正手段,上文已提及。

壳史

第一代壳 Dex加密

Dex整体加固,主要有两种方式,落地加载和内存加载。
落地加载:动态解密后候会用到DexClassLoader函数,第一个参数要传入一个路径,也就是说解密后的dex必须出现在硬盘中,加载完后会删除这个解密后的dex。也就是说会有一段时间,解密后的dex会出现。
类似于下图
image-1690205868200
内存加载:Dex解密后加载的时候会全部映射到内存中,所以可以从内存中dump下来

第二代壳 Dex抽取与so混淆

对重要的代码进行抽取,只有当函数和代码需要被执行的时候,才会被加载。

第三代壳 Dex动态解密与so混淆

Dex在内存中始终不是一个完整的状态。二代壳可以使用dexhunter工具遍历dex中所有的类然后初始化,使得内存中的dex变得完整。

第四代壳 vmp壳与dex2c(Java函数Native化)

05.ART下的整体脱壳原理

Dex的加载流程去脱壳

通过mCookie脱壳,因为mCookie是一个jlong类型的数组,存放了DexFile类型的指针,其中有begin和size,即可保存dex文件
通过openCommen等函数脱壳,这个函数调用时传入两个参数,base和size,可以保存dex文件
通过DexFile 的构造函数脱壳,因为最后加载的时候还是要变成DexFile对象的,调用构造函数进行实例化
youpk则是通过ClassLinker来得到DexFile

总结一下就是只要能获取带dexfile的地方,都可以尝试脱壳。
也可以是间接得到DexFile的地方,比如ArtMethod->getDexFile()
加固厂商加载dex的方式,也决定了脱壳点,有些脱壳点可能会被禁用掉

InMemoryDexClassLoader源码分析

找到其父类BaseDexClassLoader,然后发现initByteBufferDexPath函数传入了我们关心的dexFiles
image-1690266614100
继续查看initByteBufferDexPath的实现
image-1690266738597
继续跟进,发现mCookie这个比较关键的值(后续可知mCookie是一个jlong数组,每个数组都存放着每个dex文件的指针)
image-1690266786537
查看openInMemoryDexFiles这个函数,发现最后会返回一个JNI函数的值
image-1690267186191
进一步查看
image-1690282629802
所以其实在createCookieFromOatFileManagerResult之前,就已经加载了dex,这个函数只是进行一个转换

分析其中的脱壳点

本质上就是找到DexFile对象,然后通过begin和size就能得到dex文件的起始位置和大小,便可以脱壳。
接下来我们关注一下dex的加载
image-1690356115082
image-1690357407105

image-1690372096255

image-1690372423463

DexClassLoader源码分析

DexClassLoader继承了BaseDexClassLoader
image-1690375851804
其中这个openDexFile 和之前分析的openInMemoryDexFiles是差不多的,接下来我们就仔细分析分析。
image-1690380241296

image-1690380524794

youpk脱壳原理

image-1690436059162
看到这一段源码,也是通过获得DexFile对象,然后再进行脱壳的。

dex2oat

早期的脱壳原理:https://www.jianshu.com/p/7af31cc5130e
不推荐,Android10不再从应用进程调用dex2oat,只接受系统生成的oat。对于早期的系统也有可能被加固厂商给禁用掉了
(但是也有好多骚操作,也有人绕过了Link

fdex2

适用于8.0以下的系统
脱壳原理 classObj.getDex().getBytes()
image-1690440902453

image-1690440919481

类的加载和初始化流程

如 DexHunter在defineClass进行类解析
比如下图Class_linker中已经开始加载类的方法了,一般来说加固厂商都会使用这些函数,自己实现就太麻烦了。如果这也不行,那就只能走下面的ART虚拟机,加固厂商不会连ART虚拟机都要自己实现吧!
image-1690441656097
可以看到dex_file,这也都是可以保存的。包括LinkCode也是脱壳点,art_method->getDexFIle()

函数执行过程中的脱壳点

Fart源码分析

我们查看Fart的作者的源码可以发现,其在interpret.cc的Execute函数中添加了一个自实现的函数dumpDexFileByExecute()函数。
image-1691298802576

无论有无dex2oat对Dex进行编译,但是类的初始化函数都没有被编译,所以始终运行在解释模式下,这时候就会经过interpreter.cc的Execute()函数,然后再进入到ART下的解释器解释执行,因此我们可以在Execute()函数中执行Dump操作。
image-1691300380565

Fart迁移到安卓10

现在的Fart的特征被很多加固厂商检测了,而且直接把Fart 8.0移植到Fart 10.0会报错,所以需要小小地修改一下。变量名也可以改一改。
Android 10 中有些函数有改动主要是两个地方

问题一

image-1691309036975
根据提示,进行修改即可

问题二

image-1691309176219
flags中要两个参数了,提示说要加上O_CREAT,但是加上就行了吗?O_CREAT参数是存在就访问,不存在就新建。
看看左图的代码,这里是判断是否已经加载过这个dex,如果没加载过,再执行后面的代码。但是如果你加上了O_CREAT这个参数,如果这个Dex不存在,就直接新建一个空的dex,后面的代码也不会执行了。

所以我们可以用其他的函数,比如access,改成下图即可
image-1691309693243

成品测试

image-1691318395029

06.ART下的指令抽取原理

本质其实就是提取出dex中方法体的字节码,然后在方法运行的时候去还原

实现形式

1.抽空方法体代码,运行方法之后再去回填,回填后不再抽取 solution:延时保存
2.抽空方法体代码,运行方法之后再去回填,回填之后又抽取 solution:Fart、youpk主动调用
3.将原有函数体替换为解密代码,运行时解密执行

对dex的处理形式

有用的函数体数据空间置为0,但是保留原有空间
对dex文件进行重构,不保留原有空间,在还原的时候没修改CodeItemOffset·

解决方案

1.解决思路:在函数运行时,保存被抽取的数据
2.被动调用 app正常运行过程中所发生的函数调用只对dex中部分的类完成加载,只对dex中的部分函数完成调用调用函数不全,导致能够恢复的函数有限
3.主动调用构造虚拟调用,对app中所有函数完成调用在这些函数执行时,保存函数体Code|tem数据保存数据的时机越晚,效果越好
4.常见的抽取加固脱壳系统 DexHunter、Fupk3、FART、youpk

在分析Fart源码之前,还是再先回顾一下双亲委派和类加载器的知识

双亲委派
类加载:

隐式加载
显式加载 : 明确的说明要加载某个类 ,

  • 使用 Class.forName() 加载指定的类 ;
  • 使用 ClassLoader.loadClass 加载指令的类 ,后者可以设置是否在加载的时候初始化;
    image-1691327623805
    有的加固厂商的壳会生成这类代码,若对这个函数进行了主动调用,而且默认了加载时初始化,那么就会执行static中的代码,就会退出.
双亲委派机制的工作原理

1.如果一个类加载器收到了类加载请求,会先把这个请求委托给父类的加载器去执行
2.知果父类加载器还存在其父类加载器,则进一步向上委托,依次类推,最终到达顶层的启动类加载器
3.如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载
image-1691328083196

为什么要有双亲委派?

1.避免重复加载,已经加载的Class,可以直接读取。
2.更加安全,无法自定义类来替代系统的类,可以防止核心API库被随意篡改 。因为系统类都是BootClassLoader加载的,而用户定义的类是由pathClassLoader去加载的,其又会交给父加载类BootClassLoader去加载,如果BootClassLoader里面已经有了,就不会再次加载了。所以替换成实现的核心库是不可能的,除非hook。
* 3.方便脱壳机主动调用。因为抽取加固的解决方案就是主动调用app中的所有函数,而想调用app中的所有函数,就要获得所有类—>所有Dex—>所有ClassLoader。而我们只要得到一个classloader,就可以顺着这个classloader的parent属性向上遍历。

那么又如何得到一个ClassLoader呢?

在PathClassLoader加载dex以后,会记录在LoadedApk的mClassLOader属性中,默认会使用这个ClassLoader去寻找类
image-1691373218898

普通app与加固app运行流程的区别

一开始都是BootClassLoader去加载系统相关的类。
普通app先加载app自身的dex
加壳app加载壳的dex,然后在壳的dex/so中再去加载原先app的dex。但是需要你对类加载器进行修正,02那章其实已经提及。

未修正之前:
BootClassLoader 系统核心库
PathClassLoader 壳的dex mClassLoader保存PathClassLoader
DexCLassLoader(也有可能是自实现的ClassLoader) app自身dex

DexClassLoader 加载的类是没有组件生命周期的,即 DexClassLoader 即使通过对 APK 的动态加载完成了对组件类的加载,当系统启动该组件时,依然会出现加载类失败的异常。又因为双亲委派的存在,如果如上图一样,默认的类加载器PathClassLoader又加载不到app自身的dex,所以是不可取的。

解决方案一. 插入ClassLoader
将DexCLassLoader(也有可能是自实现的ClassLoader) 的父加载器设置成BootClassLoader, 然后将PathClassLoader的父加载器设置成自己,就会形成下面这个结构

BootClassLoader 系统核心库
DexCLassLoader(也有可能是自实现的ClassLoader) app自身dex
PathClassLoader 壳的dex mClassLoader保存PathClassLoader

解决方案二. 反射替换
直接替换mClassLoader的属性,修改成 DexCLassLoader(也有可能是自实现的ClassLoader)

BootClassLoader 系统核心库
PathClassLoader 壳的dex
DexCLassLoader(也有可能是自实现的ClassLoader) app自身dex mClassLoader被替换成改类加载器

Fart源码分析

遍历所有的ClassLoader

先得到一个ClassLoader,然后根据双亲委派得到向上的所有ClassLoader
(如果仅仅只是动态加载,没有加入到双亲委派中,那么只能用Frida枚举类加载器)1
image-1691391460617

遍历所有的类名
 public static void fartwithClassloader(ClassLoader appClassloader) {                         //dex -->element-->Dexelements-->Dexpathlist-->pathlist    所以想获取dex就要通过反射一步一步得到
        List<Object> dexFilesArray = new ArrayList<Object>();
        Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");
        Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");
        Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");
        Field dexFile_fileField = null;
        try {
            dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");
        } catch (Exception e) {
                e.printStackTrace();
            } catch (Error e) {
                e.printStackTrace();
            }
        Class DexFileClazz = null;
        try {
            DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");
        } catch (Exception e) {
                e.printStackTrace();
            } catch (Error e) {
                e.printStackTrace();
            }
        Method getClassNameList_method = null;
        Method defineClass_method = null;
        Method dumpDexFile_method = null;
        Method dumpMethodCode_method = null;

        for (Method field : DexFileClazz.getDeclaredMethods()) { //遍历方法,保存之后需要反射调用的的方法。
            if (field.getName().equals("getClassNameList")) {
                getClassNameList_method = field;
                getClassNameList_method.setAccessible(true);
            }
            if (field.getName().equals("defineClassNative")) {
                defineClass_method = field;
                defineClass_method.setAccessible(true);
            }
            if (field.getName().equals("dumpDexFile")) {
                dumpDexFile_method = field;
                dumpDexFile_method.setAccessible(true);
            }
            if (field.getName().equals("dumpMethodCode")) {
                dumpMethodCode_method = field;
                dumpMethodCode_method.setAccessible(true);
            }
        }
        Field mCookiefield = getClassField(appClassloader, "dalvik.system.DexFile", "mCookie");
        Log.v("ActivityThread->methods", "dalvik.system.DexPathList.ElementsArray.length:" + ElementsArray.length);//5个
        for (int j = 0; j < ElementsArray.length; j++) {
            Object element = ElementsArray[j];
            Object dexfile = null;
            try {
                dexfile = (Object) dexFile_fileField.get(element);
            } catch (Exception e) {
                e.printStackTrace();
            } catch (Error e) {
                e.printStackTrace();
            }
            if (dexfile == null) {
                Log.e("ActivityThread", "dexfile is null");
                continue;
            }
            if (dexfile != null) {
                dexFilesArray.add(dexfile);
                Object mcookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");  //有的时候加固厂商会禁用其中一个
                if (mcookie == null) {
                    Object mInternalCookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mInternalCookie");  //有的时候加固厂商会禁用其中一个
                    if(mInternalCookie!=null)
                    {
						mcookie=mInternalCookie;
					}else{
							Log.v("ActivityThread->err", "get mInternalCookie is null");
							continue;
							}
                    
                }
                String[] classnames = null;
                try {
                    classnames = (String[]) getClassNameList_method.invoke(dexfile, mcookie);//通过之前保存的函数去得到Dex中的类名
                } catch (Exception e) {
                    e.printStackTrace();
                    continue;
                } catch (Error e) {
                    e.printStackTrace();
                    continue;
                }
                if (classnames != null) {
                    for (String eachclassname : classnames) {
                        loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);  //类名的遍历与加载
                    }
                }

            }
        }
        return;
    }
   
遍历所有的类,调用类中的所有函数
public static void loadClassAndInvoke(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method) {
        Class resultclass = null;
        Log.i("ActivityThread", "go into loadClassAndInvoke->" + "classname:" + eachclassname);
        try {
            resultclass = appClassloader.loadClass(eachclassname);   //使用loadClass,避免初始化类的时候加载加固厂商写的垃圾类中的static代码块
        } catch (Exception e) {
            e.printStackTrace();
            return;
        } catch (Error e) {
            e.printStackTrace();
            return;
        }
        if (resultclass != null) {
            try {
                Constructor<?> cons[] = resultclass.getDeclaredConstructors();   
                for (Constructor<?> constructor : cons) {  //遍历所有的构造函数
                    if (dumpMethodCode_method != null) {
                        try {
                            dumpMethodCode_method.invoke(null, constructor);
                        } catch (Exception e) {
                            e.printStackTrace();
                            continue;
                        } catch (Error e) {
                            e.printStackTrace();
                            continue;
                        }
                    } else {
                        Log.e("ActivityThread", "dumpMethodCode_method is null ");
                    }

                }
            } catch (Exception e) {
                e.printStackTrace();
            } catch (Error e) {
                e.printStackTrace();
            }
            try {
                Method[] methods = resultclass.getDeclaredMethods();      
                if (methods != null) {
                    for (Method m : methods) {    //遍历所有的方法
                        if (dumpMethodCode_method != null) {
                            try {
                               dumpMethodCode_method.invoke(null, m);
                             } catch (Exception e) {
                                e.printStackTrace();
                                continue;
                            } catch (Error e) {
                                e.printStackTrace();
                                continue;
                            }
                        } else {
                            Log.e("ActivityThread", "dumpMethodCode_method is null ");
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } catch (Error e) {
                e.printStackTrace();
            }
        }
    }
遍历所有的方法

Thread参数传递了一个nulptr,作为主动调用的标识,并在Invoke函数的开头加了对Thread参数的判断。
image-1691460384485

extern "C" void dumpArtMethod(ArtMethod* artmethod)  REQUIRES_SHARED(Locks::mutator_lock_) {
			char *dexfilepath=(char*)malloc(sizeof(char)*1000);	
			if(dexfilepath==nullptr)
			{
				LOG(ERROR) << "ArtMethod::dumpArtMethodinvoked,methodname:"<<artmethod->PrettyMethod().c_str()<<"malloc 1000 byte failed";
				return;
			}
			int result=0;
			int fcmdline =-1;
			char szCmdline[64]= {0};
			char szProcName[256] = {0};
			int procid = getpid();
			sprintf(szCmdline,"/proc/%d/cmdline", procid);
			fcmdline = open(szCmdline, O_RDONLY,0644);
			if(fcmdline >0)
			{
				result=read(fcmdline, szProcName,256);
				if(result<0)
				{
					LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,open cmdline file file error";									
				}
				close(fcmdline);
			}
			
			if(szProcName[0])
			{
					  const DexFile* dex_file = artmethod->GetDexFile();
					  const uint8_t* begin_=dex_file->Begin();  // Start of data.
					  size_t size_=dex_file->Size();  // Length of data.
					  
					  memset(dexfilepath,0,1000);
					  int size_int_=(int)size_;
					  
					  memset(dexfilepath,0,1000);
					  sprintf(dexfilepath,"%s","/sdcard/fart");
					  mkdir(dexfilepath,0777);
							  
					  memset(dexfilepath,0,1000);
					  sprintf(dexfilepath,"/sdcard/fart/%s",szProcName);
					  mkdir(dexfilepath,0777);
							  	  
					  memset(dexfilepath,0,1000);
					  sprintf(dexfilepath,"/sdcard/fart/%s/%d_dexfile.dex",szProcName,size_int_);
					  int dexfilefp=open(dexfilepath,O_RDONLY,0666);
					  if(dexfilefp>0){
						  close(dexfilefp);
						  dexfilefp=0;
						  
						  }else{
									  int fp=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);
									  if(fp>0)
									  {
										  result=write(fp,(void*)begin_,size_);
										  if(result<0)
											{
												LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,open dexfilepath file error";
														
											}
										  fsync(fp); 
										  close(fp);  
										  memset(dexfilepath,0,1000);
										  sprintf(dexfilepath,"/sdcard/fart/%s/%d_classlist.txt",szProcName,size_int_);
										  int classlistfile=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);
											if(classlistfile>0)
											{
												for (size_t ii= 0; ii< dex_file->NumClassDefs(); ++ii) 
												{
													const DexFile::ClassDef& class_def = dex_file->GetClassDef(ii);
													const char* descriptor = dex_file->GetClassDescriptor(class_def);
													result=write(classlistfile,(void*)descriptor,strlen(descriptor));
													if(result<0)
													{
														LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";
														
														}
													const char* temp="\n";
													result=write(classlistfile,(void*)temp,1);
													if(result<0)
													{
														LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";
														
														}
													}
												  fsync(classlistfile); 
												  close(classlistfile); 
												
												}
										  }


									  }
						  //----------------------------上面又脱了一次整体加固-----------------------
						  const DexFile::CodeItem* code_item = artmethod->GetCodeItem(); //得到codeItem的指针
						  if (LIKELY(code_item != nullptr)) 
						  {
							  
								// ----------//计算codeItem的长度----------------  也可以一句代码去替代   dex_file->GetCodeitemSize(const CodeItem& code) 
								  int code_item_len = 0;
								  uint8_t *item=(uint8_t *) code_item;
								  if (code_item->tries_size_>0) { 
									  const uint8_t *handler_data = (const uint8_t *)(DexFile::GetTryItems(*code_item, code_item->tries_size_));
									  uint8_t * tail = codeitem_end(&handler_data);
									  code_item_len = (int)(tail - item);
								  }else{
									  code_item_len = 16+code_item->insns_size_in_code_units_*2;   //16是codeItem的头部信息       后面每个code
								  }  
								  // --------------------------------------------	
									  memset(dexfilepath,0,1000);
									  int size_int=(int)dex_file->Size();  
									  uint32_t method_idx=artmethod->GetDexMethodIndexUnchecked();//代表这个codeItem属于哪个函数的 
									  sprintf(dexfilepath,"/sdcard/fart/%s/%d_ins_%d.bin",szProcName,size_int,(int)gettidv1());
								      int fp2=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);
									  if(fp2>0){
										  lseek(fp2,0,SEEK_END);
										  memset(dexfilepath,0,1000);
										  int offset=(int)(item - begin_);
										  sprintf(dexfilepath,"{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:",artmethod->PrettyMethod().c_str(),method_idx,offset,code_item_len);
										  int contentlength=0;
										  while(dexfilepath[contentlength]!=0) contentlength++;
										  result=write(fp2,(void*)dexfilepath,contentlength);
										  if(result<0)
													{
														LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";
														
														}
										  long outlen=0;
										  char* base64result=base64_encode((char*)item,(long)code_item_len,&outlen);  //对指令数据进行编码
										  result=write(fp2,base64result,outlen);
										  if(result<0)
													{
														LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";
														
														}
										  result=write(fp2,"};",2);
										  if(result<0)
													{
														LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";
														
														}
										  fsync(fp2); 
										  close(fp2);
										  if(base64result!=nullptr){
											  free(base64result);
											  base64result=nullptr;
											  }
										   }
					
							}

					
			}
			
			if(dexfilepath!=nullptr)
			{
				free(dexfilepath);
				dexfilepath=nullptr;
			}
		
}
8

评论区