前言:
由于前段时间网鼎杯一道Android题目中用了这个,具有一定研究价值,便做一个壳的实现分析
https://github.com/luoyesiqiu/dpt-shell
其实这个是一个假的抽取壳,虽然函数抽取壳确实是将dex文件中的函数代码给nop,然后在运行时再把字节码给填回,但是不会填回内存中原dex位置,是填回内存中的一个解析后的结构体,比如梆梆加固,普通的工具是dump不到dex的。
虽然但是,能写出这个的也足以说明作者对Android的了解。即使作者也有howtowork文档去解释如何实现的,但是很多都被省略掉,这里做个源码分析,许多抽取壳也有类似的操作。
proccessor模块-源码分析
项目结构这里就不再说了,作者已经给出:https://github.com/luoyesiqiu/dpt-shell/blob/main/doc/HowItWorks.md
我们对照着项目结构对源码进行分析
主要是proccessor和shell两个部分
我们先分析可以将普通apk处理成加壳apk的proccessor模块
代码在dpt\src\main\java\com\luoye\dpt文件夹中
从Dpt.java开始分析
准备工作
- 
读取命令行参数,获取我们电脑上的apk路径,并创建新的加壳apk文件路径 ```java 
 private static void usage(){
 System.err.println("Usage:\n\tjava -jar dpt.jar [--log] <ApkFile>");
 }private static void processApk(String apkPath){ if(!new File("shell-files").exists()) { System.err.println("Cannot find shell files!"); return; } File apkFile = new File(apkPath); String apkFileName = apkFile.getName(); //获取apk名称 String currentDir = new File(".").getAbsolutePath(); // 当前命令行所在的目录 if (currentDir.endsWith("/.")){ currentDir = currentDir.substring(0, currentDir.lastIndexOf("/.")); } String output = FileUtils.getNewFileName(apkFileName,"signed");//新建output文件名 System.err.println("output: " + output); File outputFile = new File(currentDir, output);//创建一个File对象并获取其父文件夹路径 String outputApkFileParentPath = outputFile.getParent();``` 
- 
获取工作目录径dptOut的绝对路径并将apk解压到工作目录 java String apkMainProcessPath = ApkUtils.getWorkspaceDir().getAbsolutePath(); System.out.println("Apk main process path: " + apkMainProcessPath); ApkUtils.extract(apkPath,apkMainProcessPath);//将apk解压到工作目录
- 
解析AndroidManifest.xml获取packageName getPackageName调用了getValue(file,"manifest","android","package");,然后getValue中利用pxb.android.axml.AxmlParser获取了manifest标签内命名空间为android的package属性的值,也就是存放我们包名的地方 java Global.packageName = ManifestUtils.getPackageName(apkMainProcessPath + File.separator + "AndroidManifest.xml");(重点)解析dex代码存入文件, 生成新的代码被nop了的dex
        //从解压的apk工作目录下获取各个dex里面的代码存放到OoooooOooo文件中, 且创建了方法的code被填写为了nop的dex
        ApkUtils.extractDexCode(apkMainProcessPath);
获取解压后的工作目录下的所有dex文件并遍历,调用 DexUtils.extractAllMethods,返回dex代码List<Instruction>,一个Instruction的List,其实一个Instruction就是dex中一个方法解析后存放的对象,不理解随时回去看这几个类

public static void  extractDexCode(String apkOutDir){
        List<File> dexFiles = getDexFiles(apkOutDir);//获取工作目录下所有的dex文件
        List<List<Instruction>> instructionList = new ArrayList<>();
        String appNameNew = "OoooooOooo";
        String dataOutputPath = getOutAssetsDir(apkOutDir).getAbsolutePath() + File.separator + appNameNew; //OoooooOooo文件
        for(File dexFile : dexFiles) {//遍历dex文件
            String newName = dexFile.getName().endsWith(".dex") ? dexFile.getName().replaceAll("\\.dex$", "_tmp.dex") : "_tmp.dex";
            File dexFileNew = new File(dexFile.getParent(), newName);
            //抽取dex的代码, 一个Instruction存放一个方法的code, 返回的ret Instruction数组存放了该dex的方法的code, 并创建了新的被填入了nop的dex
            List<Instruction> ret = DexUtils.extractAllMethods(dexFile, dexFileNew);
            instructionList.add(ret);
            //更新dex的hash
            File dexFileRightHashes = new File(dexFile.getParent(),FileUtils.getNewFileName(dexFile.getName(),"new"));
            DexUtils.writeHashes(dexFileNew,dexFileRightHashes);
            dexFile.delete();
            dexFileNew.delete();
            dexFileRightHashes.renameTo(dexFile);
        }
        MultiDexCode multiDexCode = MultiDexCodeUtils.makeMultiDexCode(instructionList);//生成MultiDexCode结构存放所有dex的原本的code(这里是直接存放的代码, 我们其实可以将其加密之后存放)
        MultiDexCodeUtils.writeMultiDexCode(dataOutputPath,multiDexCode);//将MultiDexCode结构写入到OoooooOooo文件中
    }
接下来我们分析DexUtils.extractAllMethods,这个函数的作用很重要,也就是利用com.android.dex.Dex来解析dex文件,得到dex原本的各个方法的代码,一个方法对应一个解析后的Instruction对象,并且创建新的dex,将指令nop掉,后面会使用这个dex重新打包
public static List<Instruction> extractAllMethods(File dexFile, File outDexFile) {
        List<Instruction> instructionList = new ArrayList<>();   //方法结构体 数组
        Dex dex = null;
        RandomAccessFile randomAccessFile = null;
        byte[] dexData = IoUtils.readFile(dexFile.getAbsolutePath());
        IoUtils.writeFile(outDexFile.getAbsolutePath(),dexData);//将原本代码先write到新的outDexFile
        try {
            dex = new Dex(dexFile);//利用com.android.dex.Dex来解析
            randomAccessFile = new RandomAccessFile(outDexFile, "rw");
            Iterable<ClassDef> classDefs = dex.classDefs();//获取dex中的全部类
            for (ClassDef classDef : classDefs) {      //遍历dex文件中的类
                boolean skip = false;
                //跳过系统类(上面的excludeRule中定义的不抽取的类的规则)
                for(String rule : excludeRule){
                    if(classDef.toString().matches(rule)){
                        skip = true;
                        break;
                    }
                }
                if(skip){
                    continue;
                }
                if(classDef.getClassDataOffset() == 0){//获取类数据偏移
                    String log = String.format("class '%s' data offset is zero",classDef.toString());
                    logger.warn(log);
                    continue;
                }
                ClassData classData = dex.readClassData(classDef);//获取class数据
                String className = dex.typeNames().get(classDef.getTypeIndex());
                String humanizeTypeName = TypeUtils.getHumanizeTypeName(className);
                ClassData.Method[] directMethods = classData.getDirectMethods();
                ClassData.Method[] virtualMethods = classData.getVirtualMethods();
                for (ClassData.Method method : directMethods) {//遍历directMethods
                    Instruction instruction = extractMethod(dex,randomAccessFile,classDef,method);
                    if(instruction != null) {
                        instructionList.add(instruction);
                    }
                }
                for (ClassData.Method method : virtualMethods) {//遍历virtualMethods
                    Instruction instruction = extractMethod(dex, randomAccessFile,classDef, method);
                    if(instruction != null) {
                        instructionList.add(instruction);
                    }
                }
                processedClassList.add(humanizeTypeName);
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }
        finally {
            IoUtils.close(randomAccessFile);
        }
        return instructionList;
    }
extractMethod方法(看我的注释进行理解)
private static Instruction extractMethod(Dex dex ,RandomAccessFile outRandomAccessFile,ClassDef classDef,ClassData.Method method)
            throws Exception{
        String returnTypeName = dex.typeNames().get(dex.protoIds().get(dex.methodIds().get(method.getMethodIndex()).getProtoIndex()).getReturnTypeIndex());//获取返回值类型
        String methodName = dex.strings().get(dex.methodIds().get(method.getMethodIndex()).getNameIndex());
        String className = dex.typeNames().get(classDef.getTypeIndex());
        //native函数,abstract函数的codeoffset为0
        if(method.getCodeOffset() == 0){
            String log = String.format("method code offset is zero,name =  %s.%s , returnType = %s",
                    TypeUtils.getHumanizeTypeName(className),
                    methodName,
                    TypeUtils.getHumanizeTypeName(returnTypeName));
            logger.warn(log);
            return null;
        }
        Instruction instruction = new Instruction();//创建一个instruction的类来保存方法代码
        //16 = registers_size + ins_size + outs_size + tries_size + debug_info_off + insns_size
        int insnsOffset = method.getCodeOffset() + 16;//方法的ins是从偏移16开始的
        Code code = dex.readCode(method);//读取方法的code
        //容错处理
        if(code.getInstructions().length == 0){
            String log = String.format("method has no code,name =  %s.%s , returnType = %s",
                    TypeUtils.getHumanizeTypeName(className),
                    methodName,
                    TypeUtils.getHumanizeTypeName(returnTypeName));
            logger.warn(log);
            return null;
        }
        int insnsCapacity = code.getInstructions().length;//获取指令长度
        //insns容量不足以存放return语句,跳过
        byte[] returnByteCodes = getReturnByteCodes(returnTypeName);//根据类型缩写获取return语句的code
        if(insnsCapacity * 2 < returnByteCodes.length){
            logger.warn("The capacity of insns is not enough to store the return statement. {}.{}() ClassIndex = {}-> {} insnsCapacity = {}byte(s) but returnByteCodes = {}byte(s)",
                    TypeUtils.getHumanizeTypeName(className),
                    methodName,
                    classDef.getTypeIndex(),
                    TypeUtils.getHumanizeTypeName(returnTypeName),
                    insnsCapacity * 2,
                    returnByteCodes.length);
            return null;
        }
        instruction.setOffsetOfDex(insnsOffset);
        //这里的MethodIndex对应method_ids区的索引
        instruction.setMethodIndex(method.getMethodIndex());
        //注意:这里是数组的大小
        instruction.setInstructionDataSize(insnsCapacity * 2);
        byte[] byteCode = new byte[insnsCapacity * 2];
        //循环读取原先方法的指令写入到bytecode数组中, 然后填入nop到新dex中
        for (int i = 0; i < insnsCapacity; i++) {
            outRandomAccessFile.seek(insnsOffset + (i * 2));
            byteCode[i * 2] = outRandomAccessFile.readByte();
            byteCode[i * 2 + 1] = outRandomAccessFile.readByte();
            outRandomAccessFile.seek(insnsOffset + (i * 2));
            outRandomAccessFile.writeShort(0);//写入两条nop
        }
        instruction.setInstructionsData(byteCode);//将原先的code填入instruction结构体
        outRandomAccessFile.seek(insnsOffset);
        //写入return语句
        outRandomAccessFile.write(returnByteCodes);
        return instruction;
    }
其实他这里是直接存放了原先dex的code,没有加密,我们可以加密存放,增加安全性,避免能直接从assets/OoooooOooo文件中把本来的dex code拿到
AndroidManifest.xml修改及其修改后的影响
主要是修改了两个地方,也有较多其他壳会修改这个andoroid:name和android:appComponentFactory,写了下application里面android:name的影响以及android:appComponentFactory是怎么被获取到的分析
//获取AndroidManifest.xml里的application标签内的android:name属性保存到**app_name**文件中
ApkUtils.saveApplicationName(apkMainProcessPath);
//写入com.luoyesiqiu.shell.ProxyApplication到application标签内的android:name属性中
ApkUtils.writeProxyAppName(apkMainProcessPath);
//获取AndroidManifest.xml里的android:appComponentFactory属性保存到**app_acf**文件中
ApkUtils.saveAppComponentFactory(apkMainProcessPath);
//写入com.luoyesiqiu.shell.ProxyComponentFactory到application标签内的android:appComponentFactory属性
ApkUtils.writeProxyComponentFactoryName(apkMainProcessPath);
application中的android:name属性指定了当应用程序进程开始时,该类在所有应用程序组件之前被实例化。
AppComponentFactory理解:原本代码
public class AppComponentFactory {
        //提供类加载器
    public @NonNull ClassLoader instantiateClassLoader(@NonNull ClassLoader cl,
            @NonNull ApplicationInfo aInfo) {
        return cl;
    }
        //创建Application
    public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
            @NonNull String className)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (Application) cl.loadClass(className).newInstance();
    }
        //Activity组件
    public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
            @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (Activity) cl.loadClass(className).newInstance();
    }
        //BroadcastReceiver组件
    public @NonNull BroadcastReceiver instantiateReceiver(@NonNull ClassLoader cl,
            @NonNull String className, @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (BroadcastReceiver) cl.loadClass(className).newInstance();
    }
    //server组件
    public @NonNull Service instantiateService(@NonNull ClassLoader cl,
            @NonNull String className, @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (Service) cl.loadClass(className).newInstance();
    }
        //ContentProvider组件
    public @NonNull ContentProvider instantiateProvider(@NonNull ClassLoader cl,
            @NonNull String className)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (ContentProvider) cl.loadClass(className).newInstance();
    }
    public static final AppComponentFactory DEFAULT = new AppComponentFactory();
}
这个类主要有六个方法和一个指向自己的变量,六个方法包括一个方法是提供类加载器,一个是创建Application,例外四个是创建Android的四大组件。
只要在application中配置了android:appComponentFactory,系统在创建Application和四大组件时就会调用到这里配置的这个类,如果没有配置,那么就会直接使用上面类内部定义的DEFAULT变量。
在android启动流程中,ActivityThread.main()是进入app的大门,在进程创建后会在其中调用ActivityThread的attach(),接着就会调用到ActivityManagerService.attachApplication()—>attachApplicationLocked(),在attachApplicationLocked()又会调用到ActivityThread.ApplicationThread.bindApplicaiton(),其实这个主要就是赋值,然后通过handler发送消息,最终调用到的是ActivityThread.handleBindApplication

在step4,调用了makeApplication,里面又调用了newApplication
public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = getFactory(context.getPackageName())
                .instantiateApplication(cl, className);
        app.attach(context);
        return app;
    }
    private AppComponentFactory getFactory(String pkg) {
        if (pkg == null) {
            Log.e(TAG, "No pkg specified, disabling AppComponentFactory");
            return AppComponentFactory.DEFAULT;
        }
        if (mThread == null) {
            Log.e(TAG, "Uninitialized ActivityThread, likely app-created Instrumentation,"
                    + " disabling AppComponentFactory", new Throwable());
            return AppComponentFactory.DEFAULT;
        }
        LoadedApk apk = mThread.peekPackageInfo(pkg, true);
        // This is in the case of starting up "android".
        if (apk == null) apk = mThread.getSystemContext().mPackageInfo;
        return apk.getAppFactory();
    }
调用getFactory获取AppComponentFactory实例化Application,然后调用Application的attach()方法,进而调用Application的attachBaseContext(),这也说明attachBaseContext()比ContentProvide先执行,这里我们继续看AppComponentFactory的获取LoadedApk.getAppFactory():
public AppComponentFactory getAppFactory() {
        return mAppComponentFactory;
    }
只是简单的返回mAppComponentFactory属性,因为AppComponentFactory的创建是在LoadedApk的构造函数中调用createAppFactory()创建的
private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) {
  if (appInfo.appComponentFactory != null && cl != null) {
      try {
          return (AppComponentFactory)
                  cl.loadClass(appInfo.appComponentFactory).newInstance();
      } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
          Slog.e(TAG, "Unable to instantiate appComponentFactory", e);
      }
  }
  return AppComponentFactory.DEFAULT;
}
这里就获取了我们在Manifest中配置的AppComponentFactory。
然后上面填写的com.luoyesiqiu.shell.ProxyApplication和com.luoyesiqiu.shell.ProxyComponentFactory我们一会儿再仔细分析(这两个类都在后面会打包进新apk的代理dex中)
代理dex添加、删除meta-data、壳lib支持添加、重打包及重签名
//往apk中添加代理dex(将项目文件shell-files/dex/classes.dex写入到apk目录中)
ApkUtils.addProxyDex(apkMainProcessPath);
//删除meta-data
ApkUtils.deleteMetaData(apkMainProcessPath);
//把shell-libs(壳的so文件)也copy到apk目录中
ApkUtils.copyShellLibs(apkMainProcessPath, new File(outputApkFileParentPath,"shell-files/libs"));
//重打包以及签名
new BuildAndSignApkTask(false, apkMainProcessPath, output).run();
//删除工作目录
File apkMainProcessFile = new File(apkMainProcessPath);
if (apkMainProcessFile.exists()) {
    FileUtils.deleteRecurse(apkMainProcessFile);
}
代理dex结构:
shell模块-源码分析
我们先来分析代理实现(就是上面往apk中添加的那个dex),也就是
android:appComponentFactory="com.luoyesiqiu.shell.ProxyComponentFactory"
android:name="com.luoyesiqiu.shell.ProxyApplication"
其实主要是com.luoyesiqiu.shell.ProxyApplication
首先我们要知道在ActivityThread启动流程中,handleBindApplication会调用makeApplication,然后makeApplication又会调用newApplication,而newApplication就是调用getFactory获取AppComponentFactory实例化Application,然后调用Application的attach()方法,进而调用Application的attachBaseContext(),然后回到handleBindApplication进入callApplicationOnCreate()函数就执行了Application.onCreate()方法
而这个项目的com.luoyesiqiu.shell.ProxyApplication就是Override了OnCreate方法和attachBaseContext方法
ProxyApplication-attachBaseContext重写解析
我们先看attachBaseContext
先调用原本的(父类的)attachBaseContext,然后调用native的init_app函数,并重新加载了一次dex,将新的dex也添加到本来的classload的DexPathList obj中, 以便classloader从中加载类
@Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);//先调用原本的attachBaseContext
        Log.d(TAG,"dpt attachBaseContext");
        Log.d(TAG,"attachBaseContext classloader = " + base.getClassLoader());
        if(!initialized) {
            Log.d(TAG,"ProxyApplication init");//ProxyApplication初始化
            //调用native的init_app函数
            //init_app函数(将zip open后mmap到内存中, 然后获取加载到内存中apk的assets/OoooooOooo文件的指针, 然后调用readCodeItem将这个MultiDexCode结构体解析出来获得各个dex的代码, 然后添加到dexMap中)
            JniBridge.ia(base,base.getClassLoader());
            //获取原本的ClassLoader
            ClassLoader oldClassLoader = base.getClassLoader();
            //获取新的ClassLoader, 重新加载一次dex
            //dex在App启动的时候已经被加载过一次了,但是,我们为什么还要再加载一次?因为系统加载的dex是以只读方式加载的,我们没办法去修改那一部分的内存
            //而且App的dex加载早于我们Application的启动,这样,我们的代码根本没法感知到,所以我们要重新加载dex
            //loadDex, 实际上就是查找了下路径然后然后调用super实例化了PathClassLoader并返回
            ClassLoader shellClassLoader = ShellClassLoader.loadDex(base);
            //然后利用获取到的shellClassLoader, 将新的dex也添加到本来的classload的DexPathList obj中, 以便classloader从中加载类
            JniBridge.mde(oldClassLoader,shellClassLoader);
            initialized = true;
        }
    }
我们先看init_app,其实上面我已经注释的很详细了,就是将zip open后mmap到内存中, 然后获取加载到内存中apk的assets/OoooooOooo文件的指针, 然后调用readCodeItem将这个MultiDexCode结构体解析出来获得各个dex的代码, 然后添加到dexMap中,dexMap后面回填代码的时候会被用到
其实native定义的CodeItem类和我们的Instruction类相对应,成员都是一样的
//在dex中的偏移
private int offsetOfDex;
//对应dex中的method_idx
private int methodIndex;
//instructionsData数组的长度
private int instructionDataSize;
//指令数据
private byte[] instructionsData;
uint32_t mMethodIdx; //和上面相同
uint32_t mOffsetDex;
uint32_t mInsnsSize;
uint8_t* mInsns;
ProxyApplication-onCreate重写解析(hook点)
先调用本身的onCreate,然后调用了两个native的函数,callRealApplicationAttach和callRealApplicationOnCreate
@Override
    public void onCreate() {
        super.onCreate();//先调用本身的onCreate
        Log.d(TAG, "dpt onCreate");
        Log.d(TAG, "onCreate() classLoader = " + getApplicationContext().getClassLoader());
        //读取app真实名称
        String realApplicationName = FileUtils.readAppName(getApplicationContext());
        if (!TextUtils.isEmpty(realApplicationName)) {//字符串非空会进入
            //调用native的callRealApplicationAttach函数去调用原始apk Application类的attach方法
            JniBridge.craa(getApplicationContext(), realApplicationName);
        }
        if (!TextUtils.isEmpty(realApplicationName)) {
            JniBridge.craoc(realApplicationName);
            //调用native的callRealApplicationOnCreate函数调用原始apk Application类的onCreate方法
        }
    }
craa和craoc中都会调用getApplicationInstance, 然后里面会调用_init函数, 也就是去调用dpt_hook函数
extern "C" void _init(void)
{
    DLOGI("_init!");
    dpt_hook();
}
void dpt_hook()
{
    bytehook_init(BYTEHOOK_MODE_AUTOMATIC, false);
    g_sdkLevel = android_get_device_api_level(); //获取sdklevel
    hook_mmap();                                 //修改mmap以便在我们加载dex后能够修改dex的属性为可写
    hook_ClassLinker_LoadMethod();               // hook loadmethod以便将我们存放到dexmap中的代码写回到内存中的dex原本位置(里面也调用了changeDexProtect, 利用mprotect来修改了dex权限为可读写),一次回填一个method
    hook_GetOatDexFile();
}
重写了AppComponentFactory类
反正六个方法里面都是getTargetAppComponentFactory获取自身这个ProxyComponentFactory类并实例化,并设置到返回sAppComponentFactory静态变量且返回这个对象,然后方法里面回去判断是否为null,如果≠null就重新调用本类的本方法,否则就调用super AppComponentFactory的本方法
总之就是为了尽早初始化,不太重要
package com.luoyesiqiu.shell;
import android.app.Activity;
import android.app.AppComponentFactory;
import android.app.Application;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.util.Log;
import com.luoye.dpt.ThisApplication;
import com.luoyesiqiu.shell.util.ShellClassLoader;
import com.luoyesiqiu.shell.util.StringUtils;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.lang.reflect.Method;
@RequiresApi(api = 28)
public class ProxyComponentFactory extends AppComponentFactory {
    private static final String TAG = "dpt " + ProxyComponentFactory.class.getSimpleName();
    private static AppComponentFactory sAppComponentFactory;
    //readAppComponentFactory, 读取我们设置的AppComponentFactory属性的值
    //也就是com.luoyesiqiu.shell.ProxyComponentFactory 
    private String getTargetClassName(ClassLoader classLoader){
        return JniBridge.rcf(classLoader);
    }
    //加载自身类,并实例化类,从此系统中有了两个类
    private AppComponentFactory getTargetAppComponentFactory(ClassLoader classLoader){
        if(sAppComponentFactory == null){
            String targetClassName = getTargetClassName(classLoader);//获取到com.luoyesiqiu.shell.ProxyComponentFactory
            Log.d(TAG,"targetClassName = " + targetClassName);
            if(!StringUtils.isEmpty(targetClassName)) {
                try {//用目标类名创建一个新的AppComponentFactory对象并返回
                    sAppComponentFactory = (AppComponentFactory) Class.forName(targetClassName).newInstance();
                    return sAppComponentFactory;
                } catch (Exception e) {
                }
            }
        }
        return sAppComponentFactory;
    }
    private void init(ClassLoader cl){
        if(!ProxyApplication.initialized){
            ProxyApplication.initialized = true;
            JniBridge.ia(null,cl);//init app
            String apkPath = JniBridge.gap(cl);//getApkPath
            ClassLoader classLoader = ShellClassLoader.loadDex(apkPath);
            //将新的dex也添加到本来的classload的DexPathList obj中, 以便classloader从中加载类
            JniBridge.mde(cl,classLoader);
            Log.d(TAG,"ProxyComponentFactory init() classLoader = " + classLoader);
        }
    }
    //重写Activity
    @Override
    public Activity instantiateActivity(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Log.d(TAG, "instantiateActivity() called with: cl = [" + cl + "], className = [" + className + "], intent = [" + intent + "]");
        AppComponentFactory targetAppComponentFactory = getTargetAppComponentFactory(cl);
        if(targetAppComponentFactory != null) {//如果是壳程序
            try {
                Method method = AppComponentFactory.class.getDeclaredMethod("instantiateActivity", ClassLoader.class, String.class, Intent.class);
                return (Activity) method.invoke(targetAppComponentFactory, cl, className, intent);
            } catch (Exception e) {
            }
        }
        return super.instantiateActivity(cl, className, intent);//不是壳程序
    }
    //重写Application的加载
    @Override
    public Application instantiateApplication(ClassLoader cl, String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Log.d(TAG, "instantiateApplication() called with: cl = [" + cl + "], className = [" + className + "]");
        init(cl);
        AppComponentFactory targetAppComponentFactory = getTargetAppComponentFactory(cl);
        if(targetAppComponentFactory != null) {//如果是壳程序
            try {
                Method method = AppComponentFactory.class.getDeclaredMethod("instantiateApplication", ClassLoader.class, String.class);
                return (Application) method.invoke(targetAppComponentFactory, cl, className);
            } catch (Exception e) {
            }
        }
        return super.instantiateApplication(cl, ThisApplication.class.getName());//如果不是
    }
    //重写获取类加载器的instantiateClassLoader
    @Override
    public ClassLoader instantiateClassLoader(ClassLoader cl, ApplicationInfo aInfo) {
        Log.d(TAG, "instantiateClassLoader() called with: cl = [" + cl + "], aInfo = [" + aInfo + "]");
        init(cl);
        AppComponentFactory targetAppComponentFactory = getTargetAppComponentFactory(cl);
        //获取本类自身实例化后的对象
        if(targetAppComponentFactory != null) {
            try {
                Method method = AppComponentFactory.class.getDeclaredMethod("instantiateClassLoader", ClassLoader.class, ApplicationInfo.class);
                return (ClassLoader) method.invoke(targetAppComponentFactory, cl, aInfo);
            } catch (Exception e) {
            }
        }
        return super.instantiateClassLoader(cl, aInfo);
    }
    @Override
    public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Log.d(TAG, "instantiateReceiver() called with: cl = [" + cl + "], className = [" + className + "], intent = [" + intent + "]");
        AppComponentFactory targetAppComponentFactory = getTargetAppComponentFactory(cl);
        if(targetAppComponentFactory != null) {
            try {
                Method method = AppComponentFactory.class.getDeclaredMethod("instantiateReceiver", ClassLoader.class, String.class, Intent.class);
                return (BroadcastReceiver) method.invoke(targetAppComponentFactory, cl, className, intent);
            } catch (Exception e) {
            }
        }
        return super.instantiateReceiver(cl, className, intent);
    }
    @Override
    public Service instantiateService(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Log.d(TAG, "instantiateService() called with: cl = [" + cl + "], className = [" + className + "], intent = [" + intent + "]");
        AppComponentFactory targetAppComponentFactory = getTargetAppComponentFactory(cl);
        if(targetAppComponentFactory != null) {
            try {
                Method method = AppComponentFactory.class.getDeclaredMethod("instantiateReceiver", ClassLoader.class, String.class, Intent.class);
                return (Service) method.invoke(targetAppComponentFactory, cl, className, intent);
            } catch (Exception e) {
            }
        }
        return super.instantiateService(cl, className, intent);
    }
    @NonNull
    @Override
    public ContentProvider instantiateProvider(@NonNull ClassLoader cl, @NonNull String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Log.d(TAG, "instantiateProvider() called with: cl = [" + cl + "], className = [" + className + "]");
        AppComponentFactory targetAppComponentFactory = getTargetAppComponentFactory(cl);
        if(targetAppComponentFactory != null) {
            try {
                Method method = AppComponentFactory.class.getDeclaredMethod("instantiateProvider", ClassLoader.class, String.class);
                return (ContentProvider) method.invoke(targetAppComponentFactory, cl, className);
            } catch (Exception e) {
            }
        }
        return super.instantiateProvider(cl, className);
    }
}
脱壳处理(案例:2022网鼎杯-whereiscode)
方法1:frida-dexdump直接dump以及能直接dump的原因
原因:frida-dexdump这个工具是直接暴力搜索的内存,这个壳因为是将解析出来的code hook的loadmethod填回了内存中dex的原位,所以能直接dump到,所以为什么说它不是真的抽取壳
记得使用最新版的frida-dexdump,添加-d(deep search)参数


就能看见关键的check代码

方法2:静态解析assets/OoooooOooo文件
我们分析了源码之后是知道它是怎么提取的代码,以及各种类的结构,实际上这个文件就是MultiDexCode类的对象,我们按照它的代码写个解析即可
仔细看MultiDexCodeUtils.java,尤其是writeMultiDexCode,就是作者将code写入到文件中的操作,我们根据这个把oo..文件解析出来提取到各个dex的code(都恢复成那个List<Instruction> instructions类型)
public class MultiDexCodeUtils {
    /**
     * 生成MultiDexCode结构
     */
    public static MultiDexCode makeMultiDexCode(List<List<Instruction>> multiDexInsns){
        int fileOffset = 0;
        MultiDexCode multiDexCode = new MultiDexCode();
        multiDexCode.setVersion(Const.MULTI_DEX_CODE_VERSION);
        fileOffset += 2;
        multiDexCode.setDexCount((short) multiDexInsns.size());//通过二维数组的长度判断dex个数
        fileOffset += 2;
        List<Integer> dexCodeIndex = new ArrayList<>();
        multiDexCode.setDexCodesIndex(dexCodeIndex);
        fileOffset += 4 * multiDexInsns.size();
        List<DexCode> dexCodeList = new ArrayList<>();
        List<Integer> insnsIndexList = new ArrayList<>();
        for (List<Instruction> insns : multiDexInsns) {
            System.out.println("DexCode offset = " + fileOffset);
            dexCodeIndex.add(fileOffset);
             DexCode dexCode = new DexCode();
            dexCode.setMethodCount((short)insns.size());
            fileOffset += 2;
            dexCode.setInsns(insns);
            insnsIndexList.add(fileOffset);
            dexCode.setInsnsIndex(insnsIndexList);
            for (Instruction ins : insns) {
                fileOffset += 4; //Instruction.offsetOfDex
                fileOffset += 4; //Instruction.methodIndex
                fileOffset += 4; //Instruction.instructionDataSize
                fileOffset += ins.getInstructionsData().length; //Instruction.instructionsData
            }
            dexCodeList.add(dexCode);
        }
        System.out.println("fileOffset = " + fileOffset);
        multiDexCode.setDexCodes(dexCodeList);
        return multiDexCode;
    }
    /**
     * 写入MultiDexCode结构
     * @param multiDexCode MultiDexCode结构
     */
    public static void writeMultiDexCode(String out, MultiDexCode multiDexCode){
        if(multiDexCode.getDexCodes().isEmpty()){
            return;
        }
        RandomAccessFile randomAccessFile = null;
        try {
            randomAccessFile = new RandomAccessFile(out, "rw");
            //写入版本号(short)
            randomAccessFile.write(Endian.makeLittleEndian(multiDexCode.getVersion()));
            //写入dex数量(short)
            randomAccessFile.write(Endian.makeLittleEndian(multiDexCode.getDexCount()));
            //写入每个dex在文件中的位置
            for (Integer dexCodesIndex : multiDexCode.getDexCodesIndex()) {
                    //DexCode的索引,List长度为dex的数量     private List<Integer> dexCodesIndex;
                randomAccessFile.write(Endian.makeLittleEndian(dexCodesIndex));
            }
            //写入每个dex的数据
            for (DexCode dexCode : multiDexCode.getDexCodes()) {
                List<Instruction> insns = dexCode.getInsns();
                int methodCount = dexCode.getMethodCount() & 0xFFFF;
                System.out.println("insns item count:" + insns.size() + ",method count : " + methodCount);
                //写入该dex的方法数
                randomAccessFile.write(Endian.makeLittleEndian(dexCode.getMethodCount()));
                for (int i = 0; i < insns.size(); i++) {//写入单个dex的方法代码(长度的话就读取4+4+4+1*getInstructionDataSize() )
                    Instruction instruction = insns.get(i);
                    randomAccessFile.write(Endian.makeLittleEndian(instruction.getMethodIndex()));
                    randomAccessFile.write(Endian.makeLittleEndian(instruction.getOffsetOfDex()));
                    randomAccessFile.write(Endian.makeLittleEndian(instruction.getInstructionDataSize()));
                    randomAccessFile.write(instruction.getInstructionsData());
                }
            }
        }
        catch (IOException e){
            e.printStackTrace();
        }
        finally {
            IoUtils.close(randomAccessFile);
        }
    }
}
最后调用这个代码即可恢复

资料:
https://github.com/luoyesiqiu/dpt-shell
https://github.com/luoyesiqiu/dpt-shell/blob/main/doc/HowItWorks.md
https://bbs.pediy.com/thread-273293.htm#msg_header_h2_1
 跳跳糖
 跳跳糖
          
 
  