Android app加壳原理分析及壳程序编写

发布时间:2024年01月17日

Android应用程序加壳是一种保护机制,通过在原始APK文件的外部添加额外的层(壳),使得攻击者更难分析、修改或破解应用程序。这些壳通常包含一些技术手段,旨在防御逆向工程和破解行为。以下是一些常见的Android应用程序加壳原理:

  1. 代码加密/混淆:

    • 使用加密算法对DEX文件中的代码进行加密,防止简单的反编译。
    • 利用代码混淆工具,重命名类、方法、变量名,增加代码的复杂性,使得反编译变得更加困难。
  2. 动态加载:

    • 将应用程序的核心逻辑延迟到运行时加载,通过动态加载DEX文件或SO库来执行一些敏感的代码。
    • 加载的DEX文件可以通过网络下载,从而避免将所有代码都打包在APK中,增加破解的难度。
  3. 反调试和反动态分析:

    • 在应用程序中嵌入反调试和反动态分析的代码,以阻止攻击者使用调试器或分析工具来查看应用程序的内部运行状态。
    • 通过检测运行时环境,防止在虚拟机或模拟器上执行应用程序。
  4. 自定义ClassLoader:

    • 使用自定义的ClassLoader加载应用程序的类,增加破解的难度。
    • 对ClassLoader进行修改,使其能够动态加载经过加密或混淆的DEX文件。
  5. 反静态分析:

    • 防止使用静态分析工具对APK文件进行逆向工程,包括防止反编译、反汇编等操作。
  6. 应用壳保护:

    • 将整个应用程序打包到一个专门设计的壳中,这个壳可能会在运行时解密或加载应用程序的核心组件。
    • 在应用程序执行之前,进行一些检查,以确保应用程序没有被篡改或破解。

核心点在于壳程序会先于被保护的代码运行,它负责检查运行环境是否安全、解密并执行我们的代码。

下面是一个整体加载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

文章来源:https://blog.csdn.net/vs2008ASPNET/article/details/135641827
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。