Android 应用安全-无源码加壳优化

由于Android的开源性,因此在其发展历程中应用安全也得到较大的挑战,其中一种即使将打包好的apk进行解包,分析逻辑并通过修改源码重新进行打包发版成盗版应用,前面我们介绍过如果在应用内通过OS静态文件进行应用指纹校验<Android JNI本地指纹校验>,这里我们可以再通过加大反编译解包的难度来加大被破解的难度。

该博客基于>【http://blog.csdn.net/jiangwei0910410003/article/details/48415225/】
大神的文章分析完善的,若没了解过基础请先移步大神博客看看。

完善点:

1 经过某加密内部测试可兼容98%机型[android4.0-6.0].
2 全自动 重签名打包输出.
3 dex通过开源libturbo-dex库秒级加载dex.

完善点一:
android ActivityThread源码【android-sdk\sources\android-xx\android\app\ActivityThread.java】文件中,版本不一致源码稍有差异,这里不体现其他差异,为了解决apk解壳时的兼容性必须注意以下差异点:
android -Build.VERSION.SDK_INT<=18 :

final HashMap<String, WeakReference<LoadedApk>> mPackages
            = new HashMap<String, WeakReference<LoadedApk>>();
    final HashMap<String, WeakReference<LoadedApk>> mResourcePackages
            = new HashMap<String, WeakReference<LoadedApk>>();
    final HashMap<CompatibilityInfo, DisplayMetrics> mDefaultDisplayMetrics
            = new HashMap<CompatibilityInfo, DisplayMetrics>();
    final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources
            = new HashMap<ResourcesKey, WeakReference<Resources> >();

可以看到其包以及装载LoadApk都是使用HashMap。
而在api>18中则是如下:

    final ArrayMap<String, WeakReference<LoadedApk>> mPackages
            = new ArrayMap<String, WeakReference<LoadedApk>>();
    final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages
            = new ArrayMap<String, WeakReference<LoadedApk>>();
    final ArrayList<ActivityClientRecord> mRelaunchingActivities
            = new ArrayList<ActivityClientRecord>();

很明显可以看到大于18的api使用的是ArrayMap来装载。
这个差异性在解壳的apk中需要区分:

public static void hookDexLoader(String packageName, String apkFileName,
            String odexPath, String libPath) {
        Object currentActivityThread = RefInvoke.invokeStaticMethod(
                "android.app.ActivityThread", "currentActivityThread",
                new Class[]{}, new Object[]{});
        WeakReference wr = null;
        if (Build.VERSION.SDK_INT < 19) {
            HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(
                    "android.app.ActivityThread", currentActivityThread,
                    "mPackages");
            wr = (WeakReference) mPackages.get(packageName);
        } else {
            ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
                    "android.app.ActivityThread", currentActivityThread,
                    "mPackages");
            wr = (WeakReference) mPackages.get(packageName);
        }
        DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
                libPath, (ClassLoader) RefInvoke.getFieldOjbect(
                        "android.app.LoadedApk", wr.get(), "mClassLoader"));
        RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
                wr.get(), dLoader);
    }

完善点二:
加密APK的xxxx.so库的存放加载路径。
出错主要是:api18以下的机子找不到so库。
经过测试API<18 时,需要在data/data/xxx/下新建so存放文件夹,因为18以下加载so库时不能加载原来的data/data/xxx/lib/下的库。因此需要在解壳源码时把so写入新文件夹,同时指定DexClassLoader 的lib环境。
复制so文件:

private void copyLib(File newlib) {
        File old = new File(PlatformInfo.getAppLibDir(this.getPackageName()));
        if (!old.exists()) {
            Log.i("result", "lib not find");
        }
        for (File file : old.listFiles()) {
            String libname = file.getName();
            File storeFile = new File(newlib.getAbsolutePath() + "/" + libname);
            try {
                storeFile.createNewFile();
                nioTransferCopy(file, storeFile);
            } catch (Exception e) {
            }
        }
        for (File file : old.listFiles()) {
            file.delete();
        }
    }

在API>=18,不需要拷贝so文件到新文件夹,直接引用apk原来lib下面的库环境即可。
这个差别主要源于so linker 加载器,这个加载器我也是云里雾里,解决该问题主要靠错误日志,因此so加载器不做过多说明。


完善点三:
加密工具完善:
1 使用apktool.jar解开apk包。

......
String cmdUnpack = "tools\\apktool.jar d -s "+ file.getAbsolutePath()+" -o "+outputfile.getAbsolutePath();
            System.out.println("正在执行解包命令....");
CMDUtils.runCMD(cmdUnpack);
......

2 修改AndroidManifest.xml文件【添加指定解壳dex的Application】

//解析原apk项目清单文件
Document doc=XMLPares1.parse("force\\"+file.getName().substring(0, file.getName().lastIndexOf("."))+"\\AndroidManifest.xml");
//获取到Application节点
Element elt_root=doc.getDocumentElement();
Element elt_application=XMLPares1.getChildElement(elt_root, "application");
//添加格式:String add_child_elt="<meta-data android:name=\"APPLICATION_CLASS_NAME\" android:value=\""+elt_application.getAttribute("android:name")+"\"/>";
//新建一个节点
Element child_elt_tag=doc.createElement("meta-data");
    child_elt_tag.setAttribute("android:name", "APPLICATION_CLASS_NAME");
            String tag_application_value=elt_application.getAttribute("android:name");
            if(tag_application_value!=null&&tag_application_value.length()>0){
        child_elt_tag.setAttribute("android:value", tag_application_value);
}else{
        child_elt_tag.setAttribute("android:value", "");
}
elt_application.appendChild(child_elt_tag);
elt_application.setAttribute("android:name",rename_application);
//把当前更改的文档变成xml
String update_xml =XMLPares1.doc2String(doc);
//写入xml
writeString(mainfest_path,update_xml);

3 加壳解压的原apk -dex【这里的加密自己定义】

4 重新打包,并且自动签名,输出到指定路径。

System.out.println("------------->开始重新打包");
String unsignApk = file.getName().substring(0, file.getName().lastIndexOf("."))+ "_jiagu_unsing.apk";
String cmdpack="tools\\apktool.jar b force\\"+file.getName().substring(0, file.getName().lastIndexOf("."))+" -o force\\"+unsignApk;
CMDUtils.runCMD(cmdpack);
Thread.sleep(PACKAGE_TIME);
CMDUtils.CMD("exit");
//签名
System.out.println("------------->开始重新签名");
String outputsingapkname=file.getName().substring(0, file.getName().lastIndexOf("."))+"_jiagu_sing.apk";
String cmdsingapk="jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore "+key_store_path+" -storepass "+key_store_pwd+" -signedjar "+apkoutputdir+"\\"+outputsingapkname+" force\\"+unsignApk+" "+key_aleas;
CMDUtils.runCMD(cmdsingapk);

完善点四:
在art环境下快速加载dex文件,主要用到的技术是so 的hook技术,art模式下dex会有一个转换oat格式优化文件。使用的是git上开源的hook库libturbo-dex【https://github.com/asLody/TurboDex】
简单使用:
在解壳appliactoin中:
@Override
protected void attachBaseContext(Context base) {
//调用hook
boolean flag=DexOperation.enableTurboDex();
super.attachBaseContext(base);
……..
}


以上是项目的完善点。

总结:

该加密方式是第二代dex隐藏加壳加密,使破解者使用源码查看软件不能直接查看源码。
这中方式加密的dex确实无法直接查看源码,反编译后只能看到解壳代码,但是缺点也很明显:只要一部root过的手机就可以拿到源码,因此在进一步优化应该是直接在内存中加载dex字节码,无需释放保存到本地,这样可以提高破解难度。但是仍然也可以获取到源码,通过dump内存。攻与防 都是相对的,新的加密方式始终会有破解方法计算目前最主流加密方式VMP也无法挡住广大技术爱好者的爆破。
android本开源,何须太加密。加密越深,第一次打开应用速度就越慢。

最后附上部分源码(加壳工具类代码写的有点乱):
https://github.com/lambertlei/Dexencryption.git