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

这篇具有很好参考价值的文章主要介绍了Android app加壳原理分析及壳程序编写。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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://www.toymoban.com/news/detail-801925.html

到了这里,关于Android app加壳原理分析及壳程序编写的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android APP逆向分析工具和方法汇总

    受益于移动设备的广泛普及,移动应用近年来得到了蓬勃发展。基于移动设备集成的各类传感器,众多功能丰富的移动应用被开发出来,聚集了大量高价值用户隐私数据,包括用户身份信息、地理位置信息、账户资料信息等。用户在享受移动应用带来便利的同时,其隐私安全

    2024年02月12日
    浏览(50)
  • Android GNSS 模块分析(一)整体介绍 - App

    目录 1、前言 2、Android GNSS 介绍 3、Android GNSS 各层级流程分析         3.1 API 接口层         3.2 Framework 服务层         3.3 JNI 层级调用         3.4 Native 层 / Hal 层 4、GNSS NMEA 数据概述 正文 1 前言         大家好,本章节是介绍 Android GNSS 整体框架服务。此篇为学习记录

    2023年04月09日
    浏览(45)
  • [Android 13]开机动画原理分析

    hongxi.zhu 2023-6-12 Lineageos_20(Android T) on Pixel 2XL 1.1 init.rc启动相应的进程 开机动画跑起来除了需要自身进程的启动外,还肯定以来显示系统的相关进程,即一定需要SurfaceFlinger的进程的合成和送显,所以这里需要启动SurfaceFlinger服务和bootanim服务,两者是在init.rc中启动。rc的触发阶

    2024年02月09日
    浏览(48)
  • Android APP开机启动,安卓APP开发自启动,安卓启动后APP自动启动 Android让程序开机自动运行APP

    第一步设置获取广播后的业务 第二查权限给APP 理论以上两步做完就可以了。APP也能收到广播信息了, 但是APP没有在桌面启动。 经过再研究,发现要在手机再设置自动开启等业务,以下是小米、魅族的系统设置的一些内容,其它平台自己研究。 这里已经显示收到广播信息  

    2024年02月06日
    浏览(57)
  • Android 编译C程序APP

    在android的底层开发中,经常需要用C程序来测试底层驱动程序,可以利用amdroid.mk来编译c语言。 在安卓的目录下创建Android.mk用来包含目录:cs5263_user_ioctrl 在目录cs5263_user_ioctrl下创建Android.mk: gvs2715.c:  编译后会生成gvs2715app这个可执行程序,将gvs2715app利用adb push推到板子里面

    2024年04月14日
    浏览(48)
  • Android Audio音量设置原理流程分析

    本篇文章主要介绍Android音量设置从App应用层到framework层执行流程,以及相关的细节和原理分析,建议在阅读此文章前去看博主的混音理论篇的声音的音量属性和声音相关公式的推导章节,这对阅读时理解音量等级、音量分贝计算有着很大的益处;如果阅读时发现文章有错误,

    2024年02月15日
    浏览(52)
  • Android事件分发-基础原理和场景分析

    作者:京东零售 郭旭锋 和其他平台类似,Android 中 View 的布局是一个树形结构,各个 ViewGroup 和 View 是按树形结构嵌套布局的,从而会出现用户触摸的位置坐标可能会落在多个 View 的范围内,这样就不知道哪个 View 来响应这个事件,为了解决这一问题,就出现了事件分发机制

    2023年04月21日
    浏览(41)
  • Android APK 签名打包原理分析(一)【APK结构分析】

    最近在看AOSP Apk安装的相关源码时,发现自己对这块知识一直停留到用的层面,并未有深入的了解,例如打包的具体过程、签名的具体过程、渠道打包,最重要的,自己这几年在做系统方面的应用时,也解决过很多apk 安装的问题,修改过部分的系统源码,可是没有把这块知识

    2024年02月02日
    浏览(52)
  • 记一次使用android studio分析app闪退原因的过程

    首页和问题反馈重复切换两次就闪退 (因为是公司内部app,原有视频不做展示) app是原生android studio开发的,部分页面是h5开发的,通过WebView和addJavascriptInterface接口实现js与java的交互 1.由于部分页面是h5开发的,我从代码里直接修改对应的html的代码,比如我在账号的label标签

    2024年02月05日
    浏览(40)
  • Android 10.0 pms中关于启动app时获取app的ActivityInfo信息相关源码分析

     在android10.0的系统rom定制化开发中,在对于app启动时,通过Launcher调用pms来查询app的相关ActivityInfo的相关信息,然后调用 ams来启动activity,这篇来分析pms中获取app的ActivityInfo的相关信息的相关源码分析

    2024年02月02日
    浏览(73)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包