Android应用程序加壳是一种保护机制,通过在原始APK文件的外部添加额外的层(壳),使得攻击者更难分析、修改或破解应用程序。这些壳通常包含一些技术手段,旨在防御逆向工程和破解行为。以下是一些常见的Android应用程序加壳原理:
代码加密/混淆:
动态加载:
反调试和反动态分析:
自定义ClassLoader:
反静态分析:
应用壳保护:
核心点在于壳程序会先于被保护的代码运行,它负责检查运行环境是否安全、解密并执行我们的代码。
下面是一个整体加载dex文件的壳程序,它会解密并加载解密后的dex文件,壳程序的实现在native层,增加了静态分析的难度。
java入口代码,loadApp为 native方法使用C/C++开发,也就是壳程序为native。
public class PackApp extends Application {
public static final String TAG="ithuiyilu";
static {
try{
System.loadLibrary("pack");
}catch (Exception ex){
ex.printStackTrace();
}
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
loadApp(getClassLoader(),base);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public native void loadApp(ClassLoader clsLoader,Context base);
}
壳程序主要方法
/***
* 获取dex文件并解密返回dex文件数据
* @param env
* @param thiz
* @return
*/
jbyteArray getDex(JNIEnv *env,jobject thiz);
/***
* 获取lib文件搜索目录
* @param env
* @param thiz
* @return
*/
jstring getSearchDir(JNIEnv *env,jobject thiz);
/****
* 将dex文件数据封装成ByteBuffer数组
* @param env
* @param thiz
* @param dex
* @return
*/
jobjectArray getDexBuffers(JNIEnv *env,jobject thiz,jbyteArray dex);
/***
* 创建DexClassLoader对象
* @param env
* @param thiz
* @param dexBuffers dex文件数据
* @param searchDir so库搜索目录
* @param cls_loader 当前类加载器
* @return
*/
jobject newDexClassLoader(JNIEnv *env,jobject thiz,jobjectArray dexBuffers,jstring searchDir,jobject cls_loader);
/****
* 替换当前类加载器的dex文件
* @param env
* @param thiz
* @param dex
* @param classLoader
* @param base
*/
void entryApp(JNIEnv *env,jobject thiz,jobject dex,jobject classLoader,jobject base);
壳程序启动入口,它负责解密并加载原代码dex文件,并替换当前的类加载器。
extern "C"
JNIEXPORT void JNICALL
Java_com_example_pack_PackApp_loadApp(JNIEnv *env, jobject thiz, jobject cls_loader,jobject base) {
jbyteArray dex=getDex(env,thiz);
jstring searchDir= getSearchDir(env,thiz);
jobjectArray dexBuffers= getDexBuffers(env,thiz,dex);
jobject objDexClassLoader= newDexClassLoader(env,thiz,dexBuffers,searchDir,cls_loader);
entryApp(env,thiz,objDexClassLoader,cls_loader,base);
}
getDex方法从assets里读取被加密后的dex文件并解密,返回dex字节数组
jbyteArray getDex(JNIEnv *env,jobject thiz){
jclass clsContextWrapper= env->FindClass("android/content/ContextWrapper");
if(clsContextWrapper==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"FindClass faild android/content/ContextWrapper");
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"FindClass android/content/ContextWrapper %p",clsContextWrapper);
jmethodID mthgetAssets=env->GetMethodID(clsContextWrapper,"getAssets",
"()Landroid/content/res/AssetManager;");
if(mthgetAssets==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"mthgetAssets %p",mthgetAssets);
jobject objAssets= env->CallObjectMethod(thiz,mthgetAssets);
if(objAssets==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"objAssets %p",objAssets);
jclass clsAssetManager= env->FindClass("android/content/res/AssetManager");
if(clsAssetManager==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"clsAssetManager %p",clsAssetManager);
jmethodID m_open=env->GetMethodID(clsAssetManager,"open",
"(Ljava/lang/String;)Ljava/io/InputStream;");
if(m_open==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"m_open %p",objAssets);
jobject obj_inputstream= env->CallObjectMethod(objAssets,m_open,env->NewStringUTF("classes.dex"));
if(obj_inputstream==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"obj_inputstream %p",obj_inputstream);
jclass cls_InputStream= env->FindClass("java/io/InputStream");
if(cls_InputStream==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"cls_InputStream %p",clsAssetManager);
jmethodID m_readNBytes=env->GetMethodID(cls_InputStream,"readNBytes",
"(I)[B");
if(m_readNBytes==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"m_readNBytes %p",m_readNBytes);
jbyteArray dexData= static_cast<jbyteArray>(env->CallObjectMethod(obj_inputstream, m_readNBytes,
(jint) 0x10000000));
jsize dexBuffSize= env->GetArrayLength(dexData);
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"dexBuffSize %d",dexBuffSize);
jbyte* data= env->GetByteArrayElements(dexData,JNI_FALSE);
for(int i=0;i<dexBuffSize;i++){
*(data+i)=(*(data+i))^48;
}
env->SetByteArrayRegion(dexData,0,dexBuffSize,data);
if(dexData==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"dexData %p",dexData);
jmethodID m_close=env->GetMethodID(cls_InputStream,"close",
"()V");
if(m_close==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"m_close %p",m_close);
env->CallVoidMethod(obj_inputstream,m_close);
if( env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"m_close call ok");
return dexData;
}
getSearchDir 获取壳程序安装后的native 库搜索路径?
jstring getSearchDir(JNIEnv *env,jobject thiz){
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"Java_com_example_pack_PackApp_loadApp");
jclass clsContextWrapper= env->FindClass("android/content/ContextWrapper");
if(clsContextWrapper==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"clsContextWrapper %p",clsContextWrapper);
jmethodID m_getApplicationInfo=env->GetMethodID(clsContextWrapper,"getApplicationInfo",
"()Landroid/content/pm/ApplicationInfo;");
if(m_getApplicationInfo==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"m_getApplicationInfo %p",m_getApplicationInfo);
jobject obj_ApplicationInfo= env->CallObjectMethod(thiz,m_getApplicationInfo);
if(obj_ApplicationInfo==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"obj_ApplicationInfo %p",obj_ApplicationInfo);
jclass cls_ApplicationInfo= env->FindClass("android/content/pm/ApplicationInfo");
if(cls_ApplicationInfo==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"cls_ApplicationInfo %p",cls_ApplicationInfo);
jfieldID f_sourceDir=env->GetFieldID(cls_ApplicationInfo,"sourceDir",
"Ljava/lang/String;");
if(f_sourceDir==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
jstring sourceDir= static_cast<jstring>(env->GetObjectField(obj_ApplicationInfo,
f_sourceDir));
if(sourceDir==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
const char* dir=env->GetStringUTFChars(sourceDir,JNI_FALSE);
char searchDir[1000];
memset(searchDir,0,1000);
strcpy(searchDir,dir);
const char* libDir="!/lib/x86_64";
strcat(searchDir,libDir);
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"nativeDir %p %s",sourceDir,searchDir);
return env->NewStringUTF(searchDir);
}
?getDexBuffers 将解密后的dex文件数据转换成ByteBuffer数组
jobjectArray getDexBuffers(JNIEnv *env,jobject thiz,jbyteArray dex){
jclass cls_ByteBuffer = env->FindClass("java/nio/ByteBuffer");
if(cls_ByteBuffer==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"cls_ByteBuffer %p",cls_ByteBuffer);
jmethodID m_wrap =env->GetStaticMethodID(cls_ByteBuffer,"wrap", "([B)Ljava/nio/ByteBuffer;");
if(m_wrap==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"m_wrap %p",m_wrap);
jobject dexBuffer= env->CallStaticObjectMethod(cls_ByteBuffer,m_wrap,dex);
if(dexBuffer==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"dexBuffer %p",dexBuffer);
jobjectArray dexBuffers= env->NewObjectArray(1,cls_ByteBuffer,0);
env->SetObjectArrayElement(dexBuffers,0,dexBuffer);
if(env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
return dexBuffers;
}
?newDexClassLoader 创建新的类加载器,加载已解密的dex文件。
jobject newDexClassLoader(JNIEnv *env,jobject thiz,jobjectArray dexBuffers,jstring searchDir,jobject cls_loader){
jclass cls_InMemoryDexClassLoader= env->FindClass("dalvik/system/InMemoryDexClassLoader");
if(cls_InMemoryDexClassLoader==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"cls_InMemoryDexClassLoader %p",cls_InMemoryDexClassLoader);
jmethodID m_InMemoryDexClassLoader= env->GetMethodID(cls_InMemoryDexClassLoader,"<init>",
"([Ljava/nio/ByteBuffer;Ljava/lang/String;Ljava/lang/ClassLoader;)V");
jobject obj_InMemoryDexClassLoader= env->NewObject(cls_InMemoryDexClassLoader,m_InMemoryDexClassLoader,dexBuffers,
searchDir,cls_loader);
if(obj_InMemoryDexClassLoader==NULL || env->ExceptionCheck()){
env->ExceptionDescribe();
return nullptr;
}
__android_log_print(ANDROID_LOG_DEBUG,"ithuiyilu",
"obj_InMemoryDexClassLoader %p",obj_InMemoryDexClassLoader);
return obj_InMemoryDexClassLoader;
}
以上是壳程序的核心实现,这个壳子程序防止了app被静态分析,不过通过内存dump、动态调试、Hook等手段还是可以拿到解密后的dex文件,当前可以增加其破解难度,如:
1.加载dex后抹除掉内存中的dex文件特征(头部)
2.抽取方法代码,方法被执行时动态解密,执行完成后动态抹除。
native库混淆 ollvm,到这一步破解难度应该不小了。
完整壳程序demo地址??simplepack