Java I/O在Android中应用(三):Apk加固去壳


前言(废话)

现在在动车上,因为最近接到一个紧急的出差任务,需要去一趟江苏我们移动应用的客户现场。说真的,本来其实我是很困的,但是车上有一位大汉睡着了,鼾声大作,不知道为什么,我真的是一点睡觉的心思都没有了。

然后我想想,晚上还打算写一点博客,而且我的博客比较特殊,常常都会进行一些吐槽,因此,最后决定不如直接在这嘈杂的高铁上把晚上的一部分博客完成吧。毕竟时间其实是我们每一个人一生中最廉价,但是同时又是最无价的一种资源吧。

少年时候的我,很喜欢看漫画,尤其是对于三大漫特别感兴趣,像死神,火影忍者,我都很喜欢看。我仍记得很多漫画中都有对时间以及工具进行相当程度的讨论。

其中最让我印象深刻的就是《火影忍者》中宇智波佐助和宇智波鼬对决时,绝对于写轮眼和万花筒写轮眼的描述“写轮眼说到底也只是一件工具,工具使用的效果,在很多时候其实取决于使用者的资质。雏鸟所扔出的手里剑往往还不出高手所扔出的小石子。”
然后就是《伪恋》中女主母亲所说的一段话,“我的座右铭是Time is not money,钱并买不到时间不是吗?

我其实很反感一些人一听到我给他们推荐一些动漫的时候所做出的反应,他们的反应告诉我,所谓动漫,就是那些以后宫为主题,打着色情的擦边球的一种东西。太肤浅了!我只想说,如果你仅仅按照载体来直接对事物打上标签,那么你一定会错过很多的东西的。打一个比方吧,《小王子》这本书从题材上,毋庸置疑是偏童话性质的,但是如果你仅仅因为自己那点成人的自尊,就直接将其打入冷宫,不再过问,那么我想说的是,你的内心真的还不如小孩子。

我的生活其实很随意,就比如说写这篇博客也仅仅是我上一篇博客被人点赞了很开心才提笔来写的。很多时候甚至感觉自己的随意甚至辜负了很多人,我也感觉自己很对不起他们,但是我实在没有办法,我天性如此。就像特斯拉一样,虽然我无论是才能还是成就,可能都没有办法和这位一个世纪以前的天才相提并论。

很多时候我自比鬼才,而不是天才,为什么呢?因为天才常常能回应人们的期待,在某个领域能领先他人很多,以此来让别人所知。但是鬼才呢?我认为鬼才的生活规则就相对简单了。我只需要在平常的时候隐藏自己,无论是在芸芸众生之中还是他人的影子之下,当我感觉时机成熟了,如同出击的巨蟒,我会将猎物瞬间征服,然后欣赏众人的那份惊愕亦或是惊讶。


思维导图


Apk加固去壳

步骤零:概述

其实去壳这个说法并不准确,因为在之前的博客中,与其说我们为应用套上了一个壳,还不如说我们十分良心地虚构了一个诱饵,同时将我们希望掩盖的东西通过加密的方式来隐藏起来。

像linux系统的核心设计思想,就是一切都是文件!无论是设备还是管道,甚至是进程,都逃不出这个命运。Java也是有样学样,核心思想之一是一切都是对象!我感觉,Java语言设计出来的目的之一就是为了尽可能地降低Java编程人员的门槛。这也解释了为什么很多程序员会被蔑称为Javaer,因为他们只知道使用别人写好的东西,而对于代码的本质和原理,他们其实一无所知,他们就是刚刚好踏在Java尽可能降低后的门槛上的人!

好的,让我们把话题切回来,当把我前面所说的两个思想合在一起看,就很有意思了。首先,Java的核心思想是一切都是对象,但是对象总不可能凭空诞生吧,计算机目前其实仅仅是一个工具,他所能做的仅仅是按照你提供的规则对于你所录入的数据进行解析。既然不可能凭空诞生,那么他就必定需要通过文件进行读取。我们可以在应用运行前,就完成对于文件的读取操作,也可以在应用运行时来进行对于文件的读取操作,目标都一样,获取构件一个对象的基本数据信息。

所谓加固就是将原本在应用运行前,虚拟机就知道从哪里读取,怎么读取的类相关信息抽出来加密,变为在运行时从特定路径下读取和解密才能使用的数据。通过这样的方式达到所谓的应用安全的目的。

所以按照上面的思路,去壳的操作整体就分为三步:

  1. 基于密钥初始化AES加密类
  2. 解压壳应用并加密dex文件
  3. 动态加载加密的dex文件
    @Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);//步骤一:密钥获取并根据密钥进行加密算法初始化AES.init(getPassword());//项目应用源码地址File shellApp = new File(getApplicationInfo().sourceDir);//目标项目解压地址File zippedFile = getDir("fake_apk", MODE_PRIVATE);//加密应用解压和解密后存储代码位置文件夹File targetApp = new File(zippedFile, "app");//步骤二:解压并解密目标dex文件unzipAndDecryptTargetDex(shellApp, targetApp);//步骤三:动态加载解密的目标文件try {PluginUtil.install(getClassLoader(), Arrays.asList(targetApp.listFiles((dir, name) -> name.endsWith(".dex"))), zippedFile);} catch (IllegalAccessException | NoSuchFieldException | InvocationTargetException | NoSuchMethodException e) {e.printStackTrace();}}

步骤一:基于密钥初始化AES加密类

这里因为我写的仅仅是示例应用,所以加密的方式就使用简单的对称加密。关于对称和非对称加密,这个真要讲清楚就需要很长的时间了。简单来说,我们使用的对称加密,双方需要拥有同一串密钥来对信息进行加密和解密的操作,也就是说,在密钥的传输过程中,如果被其他人也拿到了,密钥也就失去了意义。

public class AES {@SuppressWarnings("unused")public static final String DEFAULT_PWD = "abcdefghijklmnop";private static final String algorithmStr = "AES/ECB/PKCS5Padding";private static Cipher encryptCipher;private static Cipher decryptCipher;@SuppressLint("GetInstance")static void init(String password) {try {// 生成一个实现指定转换的 Cipher 对象。encryptCipher = Cipher.getInstance(algorithmStr);decryptCipher = Cipher.getInstance(algorithmStr);// algorithmStrbyte[] keyStr = password.getBytes();SecretKeySpec key = new SecretKeySpec(keyStr, "AES");encryptCipher.init(Cipher.ENCRYPT_MODE, key);decryptCipher.init(Cipher.DECRYPT_MODE, key);} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (NoSuchPaddingException e) {e.printStackTrace();} catch (InvalidKeyException e) {e.printStackTrace();}}//加密操作@SuppressWarnings("unused")public static byte[] encrypt(byte[] content) {try {return encryptCipher.doFinal(content);} catch (IllegalBlockSizeException e) {e.printStackTrace();} catch (BadPaddingException e) {e.printStackTrace();}return null;}//解密操作static byte[] decrypt(byte[] content) {try {return decryptCipher.doFinal(content);} catch (IllegalBlockSizeException e) {e.printStackTrace();} catch (BadPaddingException e) {e.printStackTrace();}return null;}
}

步骤二:解压壳应用并解密核心代码

在我们之前的套壳操作中,我们对于应用中的核心代码,进行了加密和重命名操作放到了目标应用中。因此我们需要在目标应用运行时将这部分核心代码进行解密以便于我们后续运行他们。

在应用的Application类中attachBaseContext方法中来进行核心部分代码的解密操作。我们都知道Activity中的onCreate()onStart(),和onResume()方法,这三个方法当然不是Linux平台或者虚拟机直接为进程提供的,他仅仅是android框架通过模板方法模式来搭建好整体的设计,然后放出这几个简单的方法来让开发者进行自由发挥罢了。

加一段日常嘲讽,你也知道,嘲讽技能是我的被动,而且技能等级是点满的!然后有些人竟然以为自己仅仅知道了这几个方法,就以为自己已经掌握Activity了,真的是太搞笑了!

    private void unzipAndDecryptTargetDex(File shellApp, File targetApp) {if (!targetApp.exists()) {//遍历壳文件中的dex文件,将标识好的目标应用dex文件解密并对原来的dex文件进行覆盖Zip.unZip(shellApp, targetApp);for (File file : targetApp.listFiles((dir, name) -> !name.equals("classes.dex") && name.endsWith(".dex"))) {//对于壳文件中的dex文件不进行操作,对于目标应用中的文件则进行解密操作try {byte[] bytes = getBytes(file);FileOutputStream fos = new FileOutputStream(file);byte[] decrypt = AES.decrypt(bytes);fos.write(decrypt);fos.flush();fos.close();} catch (Exception e) {e.printStackTrace();}}}}

步骤三:动态加载核心代码

对于类加载基础请参考:双亲委托机制。文章挺短的,只需要花费你5分钟左右的时间就行了。

class PluginUtil {/*** A wrapper around* {@code private static final dalvik.system.DexPathList#makePathElements}.*/private static Object[] makePathElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory,ArrayList<IOException> suppressedExceptions)throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {Method makePathElements;try {makePathElements = RefUtil.findMethod(dexPathList, "makePathElements", List.class, File.class,List.class);} catch (NoSuchMethodException e) {Log.e(ShellApplication.TAG, "NoSuchMethodException: makePathElements(List,File,List) failure");try {makePathElements = RefUtil.findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class);} catch (NoSuchMethodException e1) {Log.e(ShellApplication.TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");try {Log.e(ShellApplication.TAG, "NoSuchMethodException: try use v19 instead");return V19.makeDexElements(dexPathList, files, optimizedDirectory, suppressedExceptions);} catch (NoSuchMethodException e2) {Log.e(ShellApplication.TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure");throw e2;}}}return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);}private static void expandFieldArray(Object instance, Object[] extraElements)throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {Field jlrField = RefUtil.findField(instance, "dexElements");Object[] original = (Object[]) jlrField.get(instance);Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);System.arraycopy(original, 0, combined, 0, original.length);System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);jlrField.set(instance, combined);}/*** Wrapper method of {@link V19#install(ClassLoader, List, File)}*/static void install(ClassLoader loader, List<File> additionalClassPathEntries,File optimizedDirectory) throws IllegalArgumentException,IllegalAccessException, InvocationTargetException,NoSuchFieldException, NoSuchMethodException {V19.install(loader, additionalClassPathEntries, optimizedDirectory);}static final class V19 {private V19() {}/*** @param loader                     类加载器* @param additionalClassPathEntries 额外类的文件实体* @param optimizedDirectory         文件夹* @throws IllegalArgumentException  参数异常* @throws IllegalAccessException    获取异常* @throws NoSuchFieldException      成员变量异常* @throws InvocationTargetException 调用异常* @throws NoSuchMethodException     方法不存在异常*/static void install(ClassLoader loader, List<File> additionalClassPathEntries,File optimizedDirectory) throws IllegalArgumentException,IllegalAccessException, NoSuchFieldException, InvocationTargetException,NoSuchMethodException {Field pathListField = RefUtil.findField(loader, "pathList");Object dexPathList = pathListField.get(loader);ArrayList<IOException> suppressedExceptions = new ArrayList<>();Log.d(ShellApplication.TAG, "Build.VERSION.SDK_INT " + Build.VERSION.SDK_INT);if (Build.VERSION.SDK_INT >= 23) {expandFieldArray(dexPathList, makePathElements(dexPathList, newArrayList<>(additionalClassPathEntries), optimizedDirectory,suppressedExceptions));} else {expandFieldArray(dexPathList, makeDexElements(dexPathList, newArrayList<>(additionalClassPathEntries), optimizedDirectory,suppressedExceptions));}if (suppressedExceptions.size() > 0) {for (IOException dexElementsSuppressedExceptions : suppressedExceptions) {Log.w("MultiDex", "Exception in makeDexElement",dexElementsSuppressedExceptions);}Field suppressedExceptionsField1 = RefUtil.findField(loader,"dexElementsSuppressedExceptions");IOException[] dexElementsSuppressedExceptions1 = (IOException[])suppressedExceptionsField1.get(loader);if (dexElementsSuppressedExceptions1 == null) {dexElementsSuppressedExceptions1 = suppressedExceptions.toArray(new IOException[0]);} else {IOException[] combined = new IOException[suppressedExceptions.size() +dexElementsSuppressedExceptions1.length];suppressedExceptions.toArray(combined);System.arraycopy(dexElementsSuppressedExceptions1, 0, combined,suppressedExceptions.size(), dexElementsSuppressedExceptions1.length);dexElementsSuppressedExceptions1 = combined;}suppressedExceptionsField1.set(loader, dexElementsSuppressedExceptions1);}}private static Object[] makeDexElements(Object dexPathList,ArrayList<File> files, FileoptimizedDirectory,ArrayList<IOException> suppressedExceptions) throwsIllegalAccessException, InvocationTargetException, NoSuchMethodException {Method makeDexElements = RefUtil.findMethod(dexPathList, "makeDexElements",ArrayList.class, File.class, ArrayList.class);return ((Object[]) makeDexElements.invoke(dexPathList, new Object[]{files,optimizedDirectory, suppressedExceptions}));}}
}

Java IO在Android中应用(三):Apk加固去壳相关推荐

  1. Java IO在Android中应用(二):APK加固

    Java I/O在Android中应用(二):APK加固套壳 前言(废话) 我,有两把键盘,第一把是Poker III(黑轴),第二把是Poker II(红轴).工作的时候我常用的是红轴的Poker ...

  2. java io系统_java中的io系统详解

    Java 流在处理上分为字符流和字节流.字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符.字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组. Java 内用 U ...

  3. android java 调用js,Android中Java和JavaScript交互实例

    Android提供了一个很强大的WebView控件用来处理Web网页,而在网页中,JavaScript又是一个很举足轻重的脚本.本文将介绍如何实现Java代码和Javascript代码的相互调用. 如 ...

  4. IO操作Dex文件加密,APK加固项目实战

    APK加固原理分析 1.1 APK文件结构 首先让我们先了解一下一个完整的Android应用程序都由哪些文件组成.解压一个apk包,我们可以看到一下的这些文件及文件夹: 每个文件及文件夹的作用如下表所 ...

  5. java 反应堆模式_Netty中的三种Reactor(反应堆)

    目录: Reactor(反应堆)和Proactor(前摄器) <Java NIO系列教程(八)JDK AIO编程>-- java AIO的proactor模式 Netty的I/O线程Nio ...

  6. cotlin java go_Aspectj 在Android中的简单使用(Java + Kotlin)-Go语言中文社区

    OOP&AOP OOP(Object Oriented Programming):面向对象编程.把问题或功能模块化,每个模块处理自己的事. AOP(Aspect Oriented Progra ...

  7. android java代码加密,Android中AES256加密的实现 – Leo Chin – 博客园

    AES加密是我们在工作中常用到一种加密方式,并且在java中也已经实现好了其相应的接口. 但是Java自带的JDK默认最多实现128位及其以下的加密.如果使用java自带的api实现aes256将会报 ...

  8. Java 在HashSet集合中添加三个Person对象,把姓名相同的人当做同一个人,禁止重复添加。Person类中定义name和age属性。

    import java.util.HashSet;public class Person {private String name;private int age;public Person(Stri ...

  9. android 处理通话焦点,java – AUDIOFOCUS_LOSS在Android中打电话后打电话

    当手机响起时,我试图暂停媒体播放器.我使用 android站点的示例代码.就这样 public void onAudioFocusChange(int focusChange) { switch (f ...

最新文章

  1. 粗选公式-通达信(东方财富网)
  2. vue源码-对于「计算属性」的理解
  3. (3.2)将分词和去停用词后的评论文本基于“环境、卫生、价格、服务”分类...
  4. Python基础day04【函数(定义与调用、文档说明、传参函数、全局变量、返回值、嵌套调用)】
  5. ubuntu+idea intellij配置android开发环境
  6. sigmoid函数的数值稳定性
  7. 'umi' 不是内部或外部命令,也不是可运行的程序 或批处理文件或umi: command not found
  8. UIAlertView, UIAlertViewController
  9. 算法导论4--求最大和数组
  10. 30岁的你收入是多少?用数据可视化,看看大家的30岁工资真相
  11. 判断当前日期是否在[startDate, endDate]区间
  12. IE8的css hack
  13. 亲测:真正免费的音频转文字软件
  14. 会计行业最新的法律和准则
  15. 《失业的程序员》(十二):潜意识的智商 .
  16. VO快速搜索 宝马VO 宝马VO翻译 VO码查询工具
  17. linux把大文件分成小文件,linux文件分割(将大的日志文件分割成小的)
  18. CTA入网认证业务办理
  19. 影视影评类微信公众号图文排版有哪些技巧?
  20. 相亲遇到喜欢的IT男有感

热门文章

  1. 为什么说 Java 中只有值传递?
  2. 准确率不变 损失率下降_最新斯诺克排名奥沙利文排在第二,丁俊晖排第十,第一保持不变...
  3. delphi xls 线程_锐龙9 5950X怒超近6GHz!关键是16核心32线程全开-锐龙,锐龙9 5950X,超频 ——快科技(驱动之家旗下媒体)-...
  4. gdb调试多进程和多线程命令 .
  5. centos6.5 安装mysql8,centos6上安装mysql8.0版本
  6. 微博php-sdk使用教程,腾讯微博api(php-sdk)的使用
  7. 手机显示服务器无数据返回,服务器无返回数据处理
  8. python做商品推荐系统_一种商品智能推荐系统的设计的制作方法
  9. php route取值,route命令详解
  10. mysql容器创建命令_centos7下docker创建基本的mysql容器