序言
安卓, 基于动态加载的不落地加载壳: 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"