首页
论坛
课程
招聘
[原创]安卓加壳 Android加壳 Apk加密原理
2022-3-10 15:22 5138

[原创]安卓加壳 Android加壳 Apk加密原理

2022-3-10 15:22
5138

加壳工具和说明:https://bbs.pediy.com/thread-270122.htm
更多惊喜:https://bbs.pediy.com/user-854079-1.htm
加壳后的APK帖子最下放(名子越长,强度越高)
图片描述
加密原理如下:
1,首先了解apk的加载过程
第一步系统找到AndroidManifest.xml中的application项目的Class(在壳中需要替换此Class)
2,加壳不对dex加密等于裸奔,再说一下安卓加载dex的方法有多种方式:
BaseDexClassLoader;
PathClassLoader;
DexClassLoader;
DexFile
以上的都可以从dex中实例出class对象,DexFile是都会使用到的,其中PathClassLoader类加载器,在有的手机中会生产dex优化后的odex文件,会提高运行速度,(华为手机很多没有这个操作,之前有人问加壳后为啥华为的手机运行不了,因为生成odex后,原dex就给删除了)
前三个加载器大同小异
4,application 是系统第一加载的类,可以从attachBaseContext和onCreate两个方法中入手,替换默认加载流程
了解以上关系后,就可以动手了:
1,替换AndroidManifest.xml中的application为自己的Class
Class
以下代码可在C中实现
attachBaseContext()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    try {
        //创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
 
        String dexPath = Environment.getExternalStorageDirectory()+File.separator+"classes.dex";
        File odex = this.getDir("payload_odex", MODE_PRIVATE);
        File libs = this.getDir("payload_lib", MODE_PRIVATE);
        String odexPath = odex.getAbsolutePath();
        libPath = libs.getAbsolutePath();
        String apkFileName = odex.getAbsolutePath() + "/payload.apk";
        apkFileName = dexPath;
        File dexFile = new File(apkFileName);
        Log.i("demo", "apk size:"+dexFile.length());
        // 配置动态加载环境
        Object currentActivityThread = RefInvoke.invokeStaticMethod(
                "android.app.ActivityThread", "currentActivityThread",
                new Class[] {}, new Object[] {});//获取主线程对象
        String packageName = this.getPackageName();//当前apk的包名
        //下面两句不是太理解
        ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
                "android.app.ActivityThread", currentActivityThread,
                "mPackages");
        WeakReference wr = (WeakReference) mPackages.get(packageName);
        //创建被加壳apk的DexClassLoader对象  加载apk内的类和本地代码(c/c++代码)
        PathClassLoader dLoader = new PathClassLoader(odexPath, getClassLoader());
        DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
                libPath, (ClassLoader) RefInvoke.getFieldOjbect(
                        "android.app.LoadedApk", wr.get(), "mClassLoader"));
        //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?
        //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader  ----有点c++中进程环境的意思~~
        RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
                wr.get(), dLoader);
 
        Log.i("demo","classloader:"+dLoader);
 
        try{
            ApplicationInfo ai = this.getPackageManager()
                    .getApplicationInfo(this.getPackageName(),
                            PackageManager.GET_META_DATA);
            Bundle bundle = ai.metaData;
            String frastAct = null;
            if (bundle != null && bundle.containsKey("ACTIVITY_MAIN_CLASS_NAME")) {
                frastAct = bundle.getString("ACTIVITY_MAIN_CLASS_NAME");//className 是配置在xml文件中的。
            } else {
                Log.e("demo", "have no application class name");
 
            }
            Object actObj = dLoader.loadClass(frastAct);
            Log.e("demo", actObj.toString());
 
        }catch(Exception e){
            Log.e("demo", "activity:"+Log.getStackTraceString(e));
        }
 
 
    } catch (Exception e) {
        Log.i("demo", "error:"+Log.getStackTraceString(e));
        e.printStackTrace();
    }
}

OnCreate()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public void onCreate() {
    Log.i("demo", "onCreate");
    // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
    String appClassName = null;
    try {
        ApplicationInfo ai = this.getPackageManager()
                .getApplicationInfo(this.getPackageName(),
                        PackageManager.GET_META_DATA);
        Bundle bundle = ai.metaData;
        if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {
            appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。
        } else {
            Log.i("demo", "have no application class name");
            return;
        }
    } catch (NameNotFoundException e) {
        Log.i("demo", "error:"+Log.getStackTraceString(e));
        e.printStackTrace();
    }
    //有值的话调用该Applicaiton
    Object currentActivityThread = RefInvoke.invokeStaticMethod(
            "android.app.ActivityThread", "currentActivityThread",
            new Class[] {}, new Object[] {});
    Object mBoundApplication = RefInvoke.getFieldOjbect(
            "android.app.ActivityThread", currentActivityThread,
            "mBoundApplication");
    Object loadedApkInfo = RefInvoke.getFieldOjbect(
            "android.app.ActivityThread$AppBindData",
            mBoundApplication, "info");
    //把当前进程的mApplication 设置成了null
    RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",
            loadedApkInfo, null);
    Object oldApplication = RefInvoke.getFieldOjbect(
            "android.app.ActivityThread", currentActivityThread,
            "mInitialApplication");
    ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke
            .getFieldOjbect("android.app.ActivityThread",
                    currentActivityThread, "mAllApplications");
    mAllApplications.remove(oldApplication);//删除oldApplication
 
    ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
            .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,
                    "mApplicationInfo");
    ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
            .getFieldOjbect("android.app.ActivityThread$AppBindData",
                    mBoundApplication, "appInfo");
    appinfo_In_LoadedApk.className = appClassName;
    appinfo_In_AppBindData.className = appClassName;
    Application app = (Application) RefInvoke.invokeMethod(
            "android.app.LoadedApk", "makeApplication", loadedApkInfo,
            new Class[] { boolean.class, Instrumentation.class },
            new Object[] { false, null });//执行 makeApplication(false,null)
    RefInvoke.setFieldOjbect("android.app.ActivityThread",
            "mInitialApplication", currentActivityThread, app);
 
 
    ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(
            "android.app.ActivityThread", currentActivityThread,
            "mProviderMap");
    Iterator it = mProviderMap.values().iterator();
    while (it.hasNext()) {
        Object providerClientRecord = it.next();
        Object localProvider = RefInvoke.getFieldOjbect(
                "android.app.ActivityThread$ProviderClientRecord",
                providerClientRecord, "mLocalProvider");
        RefInvoke.setFieldOjbect("android.content.ContentProvider",
                "mContext", localProvider, app);
    }
 
    Log.e("demo", "app:"+app);
    app.onCreate();
    super.onCreate();
}

上以为最原始代码(各网站均有讲解),这不是关键(但很核心),要把这些放到C++中执行才安全,解密dex后,替换APK壳的dex过程,此可以通过C++调用java来实现如:env->FindClass("dalvik/system/PathClassLoader");
如果说用C++调用java实现比较难,那对于想要逆向的人来说会更难,
重点中的重点:
先了解逆向过程:
apk->dex->smali:可修改后打包,实现破解

 

加强1:
由于APK发布时打包有签名文件,加壳时可把sha1和sha256的值保存,在要释放解密后的dex前,先做签名验证,不通过不对dex进行解密

 

加强2:
APK本身可以认为是一个ZIP包,打包后可做伪加密(自行百度)
加强3:
在对dex解密前可做root权限判断,不符合不解密dex

 

加强4:
有的不破解apk,直接用抓包方式获取访问数据,很多app用的是明文,导致请求数据,和返回数据直接暴露,可在解密前做代理判断,不符合要求不解密dex,或直接关闭网络访问代理(方式百度)

 

加强5:
HOOK检查,同上,也可在解密前先检查是否有Xposed,不符合不解密

 

加强6:
so做为解密过程,也是可以被单独拿来调试的,可以在JNI_OnLoad加载时创建一个线程,用于以上几步的操作验证,以及反调试验证,用一个while(1)一直做检查(中间间隔几秒),不用担心耗资源,因很多app开发者使用大量无用代码相比要少的多,当程序被销毁时,循环也会退出
以上操作可实现:root手机不能运行,装有Xposed不能运行,有网络代理的不能运行,加壳后的APK被重新打包后也不能运行(签名发生变化)
在C++解密的过程里可放烟雾弹,方法名相似,代码相似,但没用到,可以让逆向者认为找到了突破口,(我用的是java调用C++,C++再调用java,java再调用C++)

 

DEX加载器不仅可以加载单独的dex文件,也可以加载jar,或ZIP文件,ZIP文件中可放置多个dex,大家可自由发挥,目前好像不能放一个文件目录,官方提示可指定为目录而非一个文件
再放经验:
安卓目录中如果一个文件或文件夹的名字前是以"."开头的,这个文件或目录是为隐藏文件,大家可自由发挥,不影响加载DEX文件。
APK伪加密,有些破解工具无法正常打开,
最后外加一个拼接521字节(自行百度)和资源混淆

 

以上所有操作流程可用写个加密工具进行测试

授人以鱼,不如授人以渔

有什么问题和建议可发在留言区,以上操作均以在加壳工具中实现,可在开头找到下载地址


[2022冬季班]《安卓高级研修班(网课)》月薪两万班招生中~

最后于 2022-9-30 13:39 被富到流油^-^编辑 ,原因: 补充
上传的附件:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回