您的位置:首页 > 移动开发 > Android开发

Android加壳

2015-12-14 16:06 609 查看
网上找来的一片文章,要FQ的

android应用加壳

1需求背景
在某些特殊场景下,我们需要在不修改原应用apk的情况下对其做些额外的事情,比方集成广告、增加鉴权、增加防止反编译的逻辑等。那么我们需要在应用外嵌套一层壳,该壳集成我们想要实现的功能。

2、方案一
首先想到的是将原apk作为一个完整包直接放入到壳工程中,然后在sdk鉴权成功后直接拉起应用(启动非安装应用)。
问题1如果根据游戏包名来启动游戏,需要先对该应用进行显示安装(如果想跟手机助手等静默安装是需要获得root权限的),并且该应用可以作为正常应用运行,违背加壳的目的。

3、方案二
通过反射方法利用dexClassLoader来动态加载apk,从而拉起应用的入口activity,首先替换壳工程的manifest为待加壳应用的,修改主activity为新功能,应用原入口activity仅作为普通activity申明,代码如下:
[align=left]File dexOutputDir = getDir("dex", Context.MODE_PRIVATE);[/align]
[align=left] final String dexOutputPath = dexOutputDir.getAbsolutePath();[/align]
[align=left] [/align]
[align=left]Log.i(TAG, "dexOutputPath = " + dexOutputPath);[/align]
[align=left]Object currentActivityThread =[/align]
[align=left] RefInvoke.invokeStaticMethod("android.app.ActivityThread","currentActivityThread",new Class[] {},new Object[] {});[/align]
[align=left] [/align]
[align=left]String packageName = this.getPackageName();[/align]
[align=left]HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect("android.app.ActivityThread",[/align]
[align=left] currentActivityThread,[/align]
[align=left] "mPackages");[/align]
[align=left]WeakReference wr = (WeakReference) mPackages.get(packageName);[/align]
[align=left]DexClassLoader dLoader =[/align]
[align=left] new DexClassLoader(apkFilePath, dexOutputPath,[/align]
[align=left] getApplicationInfo().nativeLibraryDir,[/align]
[align=left] (ClassLoader) RefInvoke.getFieldOjbect("android.app.LoadedApk",[/align]
[align=left] wr.get(),[/align]
[align=left] "mClassLoader")); RefInvoke.setFieldOjbect("android.app.LoadedApk",[/align]
[align=left] "mClassLoader",[/align]
[align=left] wr.get(),[/align]
[align=left] dLoader);[/align]
[align=left] [/align]
[align=left]// 启动应用主activity[/align]
[align=left]try[/align]
[align=left]{[/align]
[align=left] Log.i(TAG, "start SDKProxyActivity");[/align]
[align=left] Class localClass =[/align]
[align=left] dLoader.loadClass("com.huawei.game.sdkshell.SDKProxyActivity");[/align]
[align=left] Intent intent = new Intent();[/align]
[align=left] Bundle bundle = new Bundle();[/align]
[align=left] bundle.putString(GAME_NAME_KEY, gameClasseName);[/align]
[align=left] intent.putExtras(bundle);[/align]
[align=left] intent.setClass(GameShellProxyActivity.this, localClass);[/align]
[align=left] [/align]
[align=left] startActivity(intent);[/align]
[align=left]}[/align]
[align=left]catch (Exception e)[/align]
[align=left]{[/align]
[align=left] Log.i(TAG, "start SDKProxyActivity failed e = " + e.toString());[/align]
[align=left]}[/align]
[align=left] [/align]
[align=left]问题1该种方式启动应用后无法加载到应用本身的资源,因为这些资源并未在当前壳应用的dexClassLoader中。[/align]
[align=left]解决方案:将应用的apk利用apktool工具反编译,将反编译的除了smali目录剩下的所有目录copy至壳相应的目录下。[/align]
[align=left] [/align]
[align=left]问题2因为该方法需要用到应用原apk,所以将应用原apk放置assets目录下,在壳启动时将该apk拷贝至sd卡中,那么应用如果很大的话,这个copy读写操作太耗时并且可能overmemory,同时由于资源已经copy至壳应用中,导致加壳后的应用是原来游戏大小的近两倍。[/align]
[align=left]解决方案:将反编译的应用apk目录除了smali文件其他的资源全部删除,然后再通过apktool重新打包生成没有资源的新应用apk,这样的apk一般就几百kb。[/align]
[align=left] [/align]
[align=left]问题3单独将应用apk放置assets目录下,这样对于应用完全性有隐患,因此需要将该apk隐藏并加密。[/align]
[align=left]解决方案:将应用apk(后面更改为classes.dex)和壳的classes.dex合并在一起后通过修改dex文件的头信息达到将应用简化后的apk(classes.dex)隐藏在壳应用自身的classes.dex,这边生成的新dex文件必须修改一些文件信息使其可正常加载。[/align]

[align=left] [/align]
[align=left]加壳source dex数据就是应用apk,下方增加其数据长度是用来在解壳时根据该长度来从dex中将应用apk取出来,在加入的过程中可以用一些加密算法进行加密。[/align]
[align=left]具体实现部分代码如下:[/align]
[align=left]/**[/align]
[align=left] * 应用加壳方法<BR>[/align]
[align=left] * @param apkPath apk路径[/align]
[align=left] * @param unShellDexPath[/align]
[align=left] * @param classDexPath[/align]
[align=left] * @throws NoSuchAlgorithmException[/align]
[align=left] * @throws IOException[/align]
[align=left] */[/align]
[align=left] public static void dexShell(String apkPath, String unShellDexPath,[/align]
[align=left] String classDexPath)[/align]
[align=left] {[/align]
[align=left] try[/align]
[align=left] {[/align]
[align=left] File apkFile = new File(apkPath);[/align]
[align=left] File unShellFile = new File(unShellDexPath);[/align]
[align=left] byte[] apkArray = readFileBytes(apkFile);[/align]
[align=left] LogUtil.getInstance().i(TAG + "dexShell apkArray len = " + apkArray.length);[/align]
[align=left] byte[] apkAesArray = encrpt(apkArray);[/align]
[align=left] byte[] unShellArray = readFileBytes(unShellFile);[/align]
[align=left] int apkFileLen = apkAesArray.length;[/align]
[align=left] int unShellFileLen = unShellArray.length;[/align]
[align=left] LogUtil.getInstance().i(TAG + "apkAesFileLen = " + apkFileLen + ", unShellFileLen = " + unShellFileLen);[/align]
[align=left] int totalLen = apkFileLen + unShellFileLen + 4;[/align]
[align=left] byte[] newDex = new byte[totalLen];[/align]
[align=left] [/align]
[align=left] //添加解壳代码 [/align]
[align=left] System.arraycopy(unShellArray, 0, newDex, 0, unShellFileLen);[/align]
[align=left] //添加加密后的解壳数据 [/align]
[align=left] System.arraycopy(apkAesArray, 0, newDex, unShellFileLen, apkFileLen);[/align]
[align=left] //添加解壳数据长度 [/align]
[align=left] System.arraycopy(intToByte(apkFileLen), 0, newDex, totalLen - 4, 4);[/align]
[align=left] //修改DEX file size文件头 [/align]
[align=left] fixFileSizeHeader(newDex);[/align]
[align=left] //修改DEX SHA1 文件头 [/align]
[align=left] fixSHA1Header(newDex);[/align]
[align=left] //修改DEX CheckSum文件头 [/align]
[align=left] fixCheckSumHeader(newDex);[/align]
[align=left] [/align]
[align=left] File classDexFile = new File(classDexPath);[/align]
[align=left] if (classDexFile.exists())[/align]
[align=left] {[/align]
[align=left] LogUtil.getInstance().i(TAG + "classDexFile exists");[/align]
[align=left] classDexFile.delete();[/align]
[align=left] }[/align]
[align=left] classDexFile.createNewFile();[/align]
[align=left] [/align]
[align=left] FileOutputStream localFileOutputStream =[/align]
[align=left] new FileOutputStream(classDexPath);[/align]
[align=left] localFileOutputStream.write(newDex);[/align]
[align=left] localFileOutputStream.flush();[/align]
[align=left] localFileOutputStream.close();[/align]
[align=left] [/align]
[align=left] }[/align]
[align=left] catch (Exception e)[/align]
[align=left] {[/align]
[align=left] e.printStackTrace();[/align]
[align=left] }[/align]
[align=left]}[/align]
[align=left] [/align]
[align=left]问题4如何将包含应用和壳的新dex文件替换壳的dex文件并能重新打包。[/align]
[align=left]壳代码中包括解析dex拿到应用apk的方法。[/align]
[align=left]解决方案:将已经copy了应用资源的壳应用编译,在bin目录下保留manifest和resources.ap_,然后将加壳的apk解压后的classes.dex文件替换成新的dex文件,然后通过命令apkbuilder D:\aaaa.apk -u -z C:\Users\g00216986\Desktop\coveredDemo\resources.ap_ -f C:\Users\g00216986\Desktop\coveredDemo\classes.dex -nf C:\Users\g00216986\Desktop\coveredDemo\lib[/align]
[align=left]重新打包并签名即可生成最终加壳的apk[/align]
[align=left] [/align]
[align=left]问题5通过上面的方法的确可以实现应用加壳,但是步骤太繁琐,该方案作为备选方案暂放。[/align]
[align=left] [/align]
[align=left]4、方案三[/align]
[align=left]上面的方案都是将应用反编译通过各种手段用于生成新的加壳应用,我们是否可以不动用应用apk本身的情况下,将我们壳的相关东西直接copy至游戏,然后生成新的apk。该方案就是将应用apk反编译,然后将纯壳apk反编译生成的smali文件直接copy至应用目录,然后copy尽量少的资源。包括封装功能的apk、使用到的lib等。[/align]
[align=left] [/align]
[align=left]问题1壳不能包括太多的资源,否则在copy的时候可能会引起冲突。[/align]
[align=left]解决方案:将鉴权逻辑等封装在底层中,然后底层通过一个activity(无view)调用底层的功能接口。[/align]
[align=left] [/align]
[align=left]问题2通过该方式动态加载底层的lib工程,底层封装新增功能的apk时会报错,无法在当前壳运行的classLoader中找到so。[/align]
[align=left]解决方案:将这三部分作为资源copy至游戏相应目录下,在拷贝lib工程时注意应用本身lib是否有相应目录的so,如果没有就全部拷贝,如果已有so目录则只拷贝相对应目录下的so即可(比方armeabi-v7a目录)。否则应用运行时加载so可能会找到其他目录。[/align]
[align=left] [/align]
[align=left]问题3发现某些应用可能采用某些特殊平台,比方j2me的kjava,其中部分资源和class类并非像android应用放在res或者assets目录下,而apktool反编译工具只是针对android通用目录,从而导致这些资源和类丢失。[/align]
[align=left]解决方案:判断应用除了通用的目录外是否还有其他文件,如果有将这些文件直接copy至解压后的加壳apk,然后利用jarsigner命令重新签名。[/align]
[align=left] [/align]
[align=left]问题4该方案因为未对游戏原代码和目录结构做任何修改,只是修改manifest的入口为壳的activity,同时增加部分activity申明和权限等,那么如果有人想破解这个加壳方案的话,只需要将manifest的activity入口修改回原来游戏的主activity入口即可绕过我们的壳直接拉起游戏。[/align]
[align=left]解决方案:?[/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: