(Frezrik/Jiagu)-Github项目分析-安卓不落地加载壳

序言

安卓, 基于动态加载的不落地加载壳: https://github.com/Frezrik/Jiagu

基于学习的目录,我分析了该项目Frezrik/Jiagu

项目结构

.
├── app
├── jiagu
├── JiaguTool
├── pack
└── ...

app: 原始待加载项目

jiagu: dex加载器

JiaguTool : 类似于与release文件, , 负责加壳的

pack: 加壳器,负责提取, 解包打包签名…

不落地加载壳区别于一代的落地动态加载壳,有什么区别…以下是我的一些看法…

动态加载壳

一个运行的apk通过某种方式拿到xxx.dex, 此刻xxx.dex是最原始的待加载dex

什么方式?

  • 比如读取当前classs.dex文件内容,从自身结构中提取xxx.dex内容(提前把xxx.dex塞进classs.dex), ,然后解密
  • 比如在asset目录中读取加密的dex文件,然后解密

解密后, 拿到xxx.dex, 我们把它至于某个磁盘目录下, 比如位于 /data/data/com.example.xxxxx./目录下

然后使用DexClassLoader去加载xxx.dex, 这应该叫落地的动态加载壳

落地体现在xxx.dex最后以原始的形式位于了/data/data/com.example.xxxxx./目录下

不落地加载壳

区别于动态加载壳, 它提取出原始的dex, 不需要把它置于 /data/data/com.example.xxxxx./目录下

在在内存中把读取到的内容给classloader加载即可.

不落地体现在成品的,原始的xxx.dex不需要置于某个目录,然后再去加载


为了方便学习,我修改了该项目的一些功能

  • dex的加载是直接去asset/i11111i111.zip中提取dex文件,灵感来自项目函数抽取壳: dpt-shell

  • 把子项目’jiagu’整合进了’app’中…修改app的Androidmanifest.xml的Application为自定义的加载器,==>为了方便调试和理解项目

  • 去掉libjiagu.so的主动加载

        static {
            System.loadLibrary("jiagu");
        }
    
  • 去掉了attach函数的在JNI_LOAD注册

  • 直接把项目一分为二吧…眼不见,心不烦,一个用于Android8.0以上,一个用于Android8.0以下,分开单独分析

修改后的项目如下 https://github.com/redqx/Jiagu2

同时该项目对apk起作用分为两套流程, Android8.0以下版本和以上版本

加载器分析

Android8.0以上分析: InMemoryDexClassLoader版本

Android8.0及以上采用系统提供的InMemoryDexClassLoader实现内存加载dex

详细分析

attachBaseContext()部分

首先进入函数 StubApp.attachBaseContext(),然后调用native的attch函数

@Override
protected void attachBaseContext(Context context)
{
   
    super.attachBaseContext(context);
   // System.load(AssetsUtil.copyJiagu(context));
    attach(this);
}
//ps: Application类继承自 ontext

进入attach函数

extern "C"
JNIEXPORT void JNICALL
Java_com_example_jiagu_StubApp_attach(JNIEnv *env, jclass clazz, jobject cur_application)
{
   
    //JNIEnv *env, jclass clazz, jobject application
    // TODO: implement attach()
    init(env, cur_application);

    // 从/data/app/xxx/base.apk获取dex
    LOGD("[-]getDex");
    //jbyteArray dexArray = getDex(env, application); //修改了dex加载的方式,从asset的i11111i111.zip读取dex

    // 内存加载dex
    LOGD("[-]loadDex");
    loadDex(env, cur_application);

    uninit(env);
}

首先进入attach.init()函数, 传入的参数是env和cur_application

static void init(JNIEnv *env, jobject application)
{
   
    SetEnv(env);//全局变量赋值, g_envPtr = env
    ndk_init(env);//创建一块区域,写入了字节码..估计是为了后续的Hook, 这是大于Android 8.0的情况
    
    //然后是全局变量的赋值
    jobject ctx = CallObjectMethod(application, "getBaseContext", "()Landroid/content/Context;").l;
    //getBaseContext是ContextWrapper的方法,application继承于ContextWrapper
    
    g_context = env->NewGlobalRef(ctx);
    g_sdk_version = GetStaticField("android/os/Build$VERSION", "SDK_INT", "I").i;
}

然后,我们回到函数attach(),并进入函数loadDex()

首先是获取asset/app_name, 该文件记录了原始的application的名称

然后是获取asset/i11111i111.zip, 它是dex的压缩包

    // 调用源文件的java层代码, 获取application的原始类名, 也就是读取文本文件, asset/app_name
    jstring appname_java= static_cast<jstring>(CallObjectMethod(
            cur_application,
            "getAppName",
            "(Landroid/content/Context;)Ljava/lang/String;", cur_application).l);

    char *app_name = const_cast<char *>(env->GetStringUTFChars(appname_java, nullptr));
    LOGD("app name: %s", app_name);


    // 调用源文件的java层代码, 读取目录asset/i11111i111.zip, 它是dex的压缩包
    jobjectArray dexList= static_cast<jobjectArray>(CallObjectMethod( //返回的类似于byte[][]
            cur_application,
            "readDex",
            "(Landroid/content/Context;)[[B", cur_application).l);
    jsize dex_cnt = env->GetArrayLength(dexList);//原始dex个数

其中readDex返回的是一个 byte[][]类型的数组, 装的是dex文件的内容,

然后获取一下classLoader

jobject classLoader = CallObjectMethod(g_context, "getClassLoader", "()Ljava/lang/ClassLoader;").l;

接着取出每个dex文件内容,转化形式,让入dexBuffers

    for(int i=0; i < dex_cnt; i++)
    {
   
        jbyteArray innerArray = static_cast<jbyteArray>(env->GetObjectArrayElement(dexList, i));
        jbyte* dex_data = env->GetByteArrayElements(innerArray, nullptr);
        jsize innerLength = env->GetArrayLength(innerArray);

        //把每个dex的内容放进 vector dexBuffers
        jobject dexBuffer = env->NewDirectByteBuffer(reinterpret_cast<char *>(dex_data), innerLength);
        dexBuffers.push_back(dexBuffer);

    }

接着,又转化形式, 把dexBuffers转dexBufferArr , dexBufferArr 是一个ByteBuffer[]类型

大概是为了之后的InMemoryDexClassLoader,此类的构造函数需要ByteBuffer[]类型

    jclass ElementClass_ByteBuffer = env->FindClass("java/nio/ByteBuffer");
    jobjectArray dexBufferArr = env->NewObjectArray(dexBuffers.size(), ElementClass_ByteBuffer, NULL);
    for (int i = 0; i < dexBuffers.size(); i++)
    {
   
        //转存dex内容? 放进 jobjectArray dexBufferArr
        env->SetObjectArrayElement(dexBufferArr, i, dexBuffers[i]);
    }

接着调用InMemoryDexClassLoader 完成dex的加载

    jclass InMemoryDexClassLoaderClass = env->FindClass("dalvik/system/InMemoryDexClassLoader");
    jmethodID InMemoryDexClassLoaderInit = env->GetMethodID(
            InMemoryDexClassLoaderClass,
            "<init>",
            "([Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");

    //调用构造函数 public InMemoryDexClassLoader (ByteBuffer[] dexBuffers, ClassLoader parent)
    // 这里已经完成了dex的自动加载....疑惑的是它并没有指定so的加载路径
    jobject mm_InMemoryDexClassLoader = env->NewObject(
            InMemoryDexClassLoaderClass,
            InMemoryDexClassLoaderInit,
            dexBufferArr,//成员得是ByteBuffer[],类似于byte[][]
            classLoader);//并没有用确定so的加载路径

接着,调用DexPathList.makeInMemoryDexElements(...),生成dexElements[]

    jobjectArray dexElements;
    //替换DexPathList列表...

    jclass list_jcs = env->FindClass("java/util/ArrayList");
    jmethodID list_init = env->GetMethodID(list_jcs, "<init>", "()V");
    jobject list_obj = env->NewObject(list_jcs, list_init);
    dexElements = static_cast<jobjectArray>(CallStaticMethod(
            "dalvik/system/DexPathList",
            "makeInMemoryDexElements"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值