Android APK加固-完善内存dex
编写JNI打开openDexFile函数代码
思路:
1.再MyDexClassLoader中添加native方法
2.再NDK中编写方法,调用native中的openDexFile
3.返回值是cookie。其他代码与之前是一样
1.在MyDexClassLoader中添加的native方法
private native int openDexFile(byte[] bytes, int len);
2.在NDK中编写方法
思路:
1.获取系统中的OpenDexFile函数
2.构造参数,调用系统的OpenDexFile函数
在加载so文件时的JNI_Onload函数中获取函数地址,保存起来。
为了方便,先定义一个全局的函数指针保存OpenDexFile的函数地址。
typedef void (* OPEN_DEX_FILE)(const uint32_t* args, JValue* pResult);
typedef void (* DEFINE_CLASS)(const uint32_t* args, JValue* pResult);
OPEN_DEX_FILE g_openDexFile = NULL;
DEFINE_CLASS g_defineClass = NULL;
然后在JNI_Onload函数中添加获取函数地址代码
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv env;
jint jRet = vm->GetEnv((void**)&env, JNI_VERSION_1_6);
if(jRet != JNI_OK){
return JNI_ERR;
}
?
// 初始化
g_openDexFile = (OPEN_DEX_FILE)(GetFunAddr("openDexFile", "([B)I"));
?
return JNI_VERSION_1_6;
}
获取系统中的OpenDexFile函数需要先知道怎么获取导出的符号地址,在android的ndk中,与Linux下的动态库操作一样,动态库操作有以下几个函数
dlclose 关闭
dlerror 错误
dlopen 打开
dlsym 搜索
函数使用起来简单,先通过dlopen加载模块,然后在使用dlsym获取导出符号,最后用完关闭模块
// 函数功能 :获取dvm_dalvik_system_DexFile数组中的函数地址
// 参数1 :找到的函数名,即openDexFile
// 参数2 :找到函数的签名
// 返回值 :函数地址
void* GetFunAddr(char*methodName, char* sig){
// 1. 获取模块基地址,使用dlopen 需要头文件 #include <dlfcn.h>
void *handle = dlopen("libdvm.so", RTLD_LAZY);
// __android_log_print(ANDROID_LOG_DEBUG, "15pb-log","模块基址: %p",handle);
LOGD("模块基址: %p", handle);
// 2. 获取模块对应的导出符号
JNINativeMethod* jniNativeMethod = (JNINativeMethod*)dlsym(handle, "dvm_dalvik_system_DexFile");
LOGD("数组基址: %p", jniNativeMethod);
// 3. 解析导出符号,即遍历结构体
int i=0;
JNINativeMethod* nativeMethod = NULL;
do {
nativeMethod = (jniNativeMethod + i++);
LOGD("函数名称: %s", nativeMethod->name);
if (0 == strcmp(nativeMethod->name, methodName) &&
0 == strcmp(nativeMethod->signature, sig)){
break;
}
} while (nativeMethod->name != NULL);
// 4. 卸载模块
dlclose(handle);
// 5. 返回函数地址
return nativeMethod->fnPtr;
}
构造参数,调用系统的OpenDexFile函数
ArrayObject对象其实是一个不定长的对象,其第一个成员变量是元素长度,而第二元素是第一个元素中所指长度*sizeof(type)。可以理解为这个数组是根据不同类型的元素类型以及元素长度而定的。
对第一个变量进行了获取,就是一个字符串长度,而后申请了对应长度的缓冲区,而第二个变量指向的是一个缓冲区,这个由于我们知道这是一个字节数组,所以第二个元素数组的长度和第一个元素长度的值一致。开始构造:
extern "C"//必须声明,不然导出名称不一样
JNIEXPORT jint JNICALL
Java_com_bluelesson_mydexclassloader2_MyDexClassLoader_openDexFile(JNIEnv *env, jobject instance,jbyteArray bytes_, jint len) {
jbyte *bytes = env->GetByteArrayElements(bytes_, NULL);
// 构建 参数
// static void Dalvik_dalvik_system_DexFile_openDexFile_bytearray(
//const u4* args,//参数数组
//JValue* pResult//返回值指针)
//申请足够大的空间 对象类型大小加上字节数组大小,肯定够放了
ArrayObject* pObject = static_cast<ArrayObject *>(malloc(sizeof(ArrayObject) + len));
//初始化数组长度
pObject->length = len;
//拷贝ArrayObject对象中的缓冲区
memcpy(pObject->contents,bytes,len);
//构造参数数组
uint32_t args = {*(uint32_t *)&pObject};
//构造返回值
JValue jRet = {0};
// 调用c++的函数
g_openDexFile(&args, &jRet);
env->ReleaseByteArrayElements(bytes_, bytes, 0);
//返回cookied
return jRet.i;
}
完整MainActivity.java
package com.bluelesson.mydexclassloader2;
?
import android.content.Intent;
import android.content.res.AssetManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
?
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
?
import dalvik.system.DexClassLoader;
?
public class MainActivity extends AppCompatActivity {
private static final String TAG = "15pb-log";
?
// Used to load the ‘native-lib‘ library on application startup.
static {
System.loadLibrary("native-lib");
}
?
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
?
/**
* A native method that is implemented by the ‘native-lib‘ native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
?
public void btnClick(View view) {
?
// 1. 获取dex字节数组
byte bytes[] = getdexFromAssets("m2a1.dex");
// 2. 加载dex,返回dexClassLoader对象
MyDexClassLoader dex = new MyDexClassLoader(bytes,getPackageCodePath(),
getCacheDir().toString(),null,getClassLoader()
);
// 3. 加载类
Class clz = null;
try {
clz = dex.loadClass("com.bluelesson.mydexclassloader2.Main2Activity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 4. 替换ClassLoader
replaceClassLoader1(dex);
// 5. 启动activity
startActivity(new Intent(this,clz));
}
?
?
byte[] getdexFromAssets(String dexName){
// 获取assets目录管理器
AssetManager as = getAssets();
// 合成路径
String path = getFilesDir() + File.separator + dexName;
Log.i(TAG, path);
try {
// 创建文件流
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 打开文件
InputStream is = as.open(dexName);
// 循环读取文件,拷贝到对应路径
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return null;
}
?
public void replaceClassLoader1(DexClassLoader dexClassLoader){
try {
// 1. 获取ActivityThead类对象
// android.app.ActivityThread
// 1.1 获取类类型
Class clzActivityThead = Class.forName("android.app.ActivityThread");
// 1.2 获取类方法
Method currentActivityThread = clzActivityThead.getMethod("currentActivityThread",new Class[]{});
// 1.3 调用方法
currentActivityThread.setAccessible(true);
Object objActivityThread = currentActivityThread.invoke(null);
?
// 2. 通过类对象获取成员变量mBoundApplication
//clzActivityThead.getDeclaredField()
Field field = clzActivityThead.getDeclaredField("mBoundApplication");
// AppBindData
field.setAccessible(true);
Object data = field.get(objActivityThread);
// 3. 获取mBoundApplication对象中的成员变量info
// 3.1 获取 AppBindData 类类型
Class clzAppBindData = Class.forName("android.app.ActivityThread$AppBindData");
// 3.2 获取成员变量info
Field field1 = clzAppBindData.getDeclaredField("info");
// 3.3 获取对应的值
//LoadedApk
field1.setAccessible(true);
Object info = field1.get(data);
// 4. 获取info对象中的mClassLoader
// 4.1 获取 LoadedApk 类型
Class clzLoadedApk = Class.forName("android.app.LoadedApk");
// 4.2 获取成员变量 mClassLoader
Field field2 = clzLoadedApk.getDeclaredField("mClassLoader");
field2.setAccessible(true);
?
// 5. 替换ClassLoader
field2.set(info,dexClassLoader);
?
} catch (Exception e) {
e.printStackTrace();
}
}
}
?
完整MyDexClassLoader.java
package com.bluelesson.mydexclassloader2;
?
import java.lang.reflect.Method;
?
import dalvik.system.DexClassLoader;
?
public class MyDexClassLoader extends DexClassLoader {
public MyDexClassLoader(byte bytes[],
String dexPath,
String optimizedDirectory,
String librarySearchPath,
ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
?
createDexClassLoader(bytes,parent);
?
}
private ClassLoader mClassLoader;
private int mCookie;
private void createDexClassLoader(byte[] bytes, ClassLoader parent) {
mClassLoader = parent;
try {
// Android 4.4 系统
mCookie = openDexFile(bytes, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
}
?
private native int openDexFile(byte[] bytes, int len);
?
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try{
// Android 4.4 系统
// 调用 defineClass 返回cookie值
// 1. 获取类
Class clzDexFile = Class.forName("dalvik.system.DexFile");
// 2. 获取方法
Method method = clzDexFile.getDeclaredMethod("defineClassNative",
String.class,ClassLoader.class,int.class
);
method.setAccessible(true);
// 3. 调用
Class clz = (Class) method.invoke(null,new Object[]{name,mClassLoader,mCookie});
?
return clz;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
?
private native Class myDefileClass(String name, ClassLoader mClassLoader, int mCookie);
}
?
完整nativa-lib.cpp
#include <jni.h>
#include <string>
#include <dlfcn.h>
#include <android/log.h>
?
union JValue {
u_char z;
char b;
u_short c;
short s;
int32_t i;
int64_t j;
float f;
double d;
void *l;
};
?
struct Object {
/* ptr to class object */
void* clazz;
?
/*
* A word containing either a "thin" lock or a "fat" monitor. See
* the comments in Sync.c for a description of its layout.
*/
uint32_t lock;
};
?
struct ArrayObject : Object {
/* number of elements; immutable after init */
uint32_t length;
?
/*
* Array contents; actual size is (length * sizeof(type)). This is
* declared as u8 so that the compiler inserts any necessary padding
* (e.g. for EABI); the actual allocation may be smaller than 8 bytes.
*/
uint64_t contents[1];
};
?
#define LOGD(a,b) __android_log_print(ANDROID_LOG_DEBUG, "15pb-log",a,b);
typedef void (* OPEN_DEX_FILE)(const uint32_t* args, JValue* pResult);
typedef void (* DEFINE_CLASS)(const uint32_t* args, JValue* pResult);
OPEN_DEX_FILE g_openDexFile = NULL;
DEFINE_CLASS g_defineClass = NULL;
?
// 函数功能 :获取dvm_dalvik_system_DexFile数组中的函数地址
// 参数1 :找到的函数名,即openDexFile
// 参数2 :找到函数的签名
// 返回值 :函数地址
void* GetFunAddr(char*methodName, char* sig){
// 1. 获取模块基地址,使用dlopen 需要头文件 #include <dlfcn.h>
void *handle = dlopen("libdvm.so", RTLD_LAZY);
// __android_log_print(ANDROID_LOG_DEBUG, "15pb-log","模块基址: %p",handle);
LOGD("模块基址: %p", handle);
// 2. 获取模块对应的导出符号
JNINativeMethod* jniNativeMethod = (JNINativeMethod*)dlsym(handle, "dvm_dalvik_system_DexFile");
LOGD("数组基址: %p", jniNativeMethod);
// 3. 解析导出符号,即遍历结构体
int i=0;
JNINativeMethod* nativeMethod = NULL;
do {
nativeMethod = (jniNativeMethod + i++);
LOGD("函数名称: %s", nativeMethod->name);
if (0 == strcmp(nativeMethod->name, methodName) &&
0 == strcmp(nativeMethod->signature, sig)){
break;
}
} while (nativeMethod->name != NULL);
// 4. 卸载模块
dlclose(handle);
// 5. 返回函数地址
return nativeMethod->fnPtr;
}
?
?
extern "C" JNIEXPORT jstring JNICALL
Java_com_bluelesson_mydexclassloader2_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
?
extern "C"
JNIEXPORT jint JNICALL
Java_com_bluelesson_mydexclassloader2_MyDexClassLoader_openDexFile(JNIEnv *env, jobject instance,
jbyteArray bytes_, jint len) {
jbyte *bytes = env->GetByteArrayElements(bytes_, NULL);
?
// 构建 参数
// static void Dalvik_dalvik_system_DexFile_openDexFile_bytearray(const u4* args, JValue* pResult)
ArrayObject* pObject = static_cast<ArrayObject *>(malloc(sizeof(ArrayObject) + len));
pObject->length = len;
memcpy(pObject->contents,bytes,len);
uint32_t args = {*(uint32_t *)&pObject};
JValue jRet = {0};
// 调用c++的函数
g_openDexFile(&args, &jRet);
?
env->ReleaseByteArrayElements(bytes_, bytes, 0);
?
return jRet.i;
}
?
extern "C"
JNIEXPORT jclass JNICALL
Java_com_bluelesson_mydexclassloader2_MyDexClassLoader_myDefileClass(JNIEnv *env, jobject instance,
jstring name_,
jobject mClassLoader,
jint mCookie) {
const char *name = env->GetStringUTFChars(name_, 0);
?
// TODO
?
env->ReleaseStringUTFChars(name_, name);
?
return NULL;
}
?
?
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv env;
jint jRet = vm->GetEnv((void**)&env, JNI_VERSION_1_6);
if(jRet != JNI_OK){
return JNI_ERR;
}
?
// 初始化
g_openDexFile = (OPEN_DEX_FILE)(GetFunAddr("openDexFile", "([B)I"));
?
return JNI_VERSION_1_6;
}
清单
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bluelesson.mydexclassloader2">
?
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
?
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Main2Activity"></activity>
</application>
?
</manifest>
资源
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
?
<Button
android:text="启动一个activity"
android:onClick="btnClick"
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
?
</LinearLayout>