Java IO在Android中应用(三):Apk加固去壳
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java IO在Android中应用(三):Apk加固去壳,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含11823字,纯文字阅读大概需要17分钟。
内容图文
![Java IO在Android中应用(三):Apk加固去壳](/upload/InfoBanner/zyjiaocheng/621/7d37ad33084e4754b158a4ea2dfc32ad.jpg)
Java I/O在Android中应用(三):Apk加固去壳
前言(废话)
现在在动车上,因为最近接到一个紧急的出差任务,需要去一趟江苏我们移动应用的客户现场。说真的,本来其实我是很困的,但是车上有一位大汉睡着了,鼾声大作,不知道为什么,我真的是一点睡觉的心思都没有了。
然后我想想,晚上还打算写一点博客,而且我的博客比较特殊,常常都会进行一些吐槽,因此,最后决定不如直接在这嘈杂的高铁上把晚上的一部分博客完成吧。毕竟时间其实是我们每一个人一生中最廉价,但是同时又是最无价的一种资源吧。
少年时候的我,很喜欢看漫画,尤其是对于三大漫特别感兴趣,像死神,火影忍者,我都很喜欢看。我仍记得很多漫画中都有对时间
以及工具
进行相当程度的讨论。
其中最让我印象深刻的就是《火影忍者》中宇智波佐助和宇智波鼬对决时,绝对于写轮眼和万花筒写轮眼的描述“写轮眼说到底也只是一件工具,工具使用的效果,在很多时候其实取决于使用者的资质。雏鸟所扔出的手里剑往往还不出高手所扔出的小石子
。”
然后就是《伪恋》中女主母亲所说的一段话,“我的座右铭是Time is not money,钱并买不到时间不是吗?
”
我其实很反感一些人一听到我给他们推荐一些动漫的时候所做出的反应,他们的反应告诉我,所谓动漫,就是那些以后宫为主题,打着色情的擦边球的一种东西。太肤浅了!我只想说,如果你仅仅按照载体来直接对事物打上标签,那么你一定会错过很多的东西的
。打一个比方吧,《小王子》这本书从题材上,毋庸置疑是偏童话性质的,但是如果你仅仅因为自己那点成人的自尊,就直接将其打入冷宫,不再过问,那么我想说的是,你的内心真的还不如小孩子。
我的生活其实很随意,就比如说写这篇博客也仅仅是我上一篇博客被人点赞了很开心才提笔来写的。很多时候甚至感觉自己的随意甚至辜负了很多人,我也感觉自己很对不起他们,但是我实在没有办法,我天性如此。就像特斯拉一样,虽然我无论是才能还是成就,可能都没有办法和这位一个世纪以前的天才相提并论。
很多时候我自比鬼才,而不是天才,为什么呢?因为天才常常能回应人们的期待,在某个领域能领先他人很多,以此来让别人所知。但是鬼才呢?我认为鬼才的生活规则就相对简单了。我只需要在平常的时候隐藏自己,无论是在芸芸众生之中还是他人的影子之下,当我感觉时机成熟了,如同出击的巨蟒,我会将猎物瞬间征服,然后欣赏众人的那份惊愕亦或是惊讶。
思维导图
Apk加固去壳
步骤零:概述
其实去壳这个说法并不准确,因为在之前的博客中,与其说我们为应用套上了一个壳,还不如说我们十分良心地虚构了一个诱饵,同时将我们希望掩盖的东西通过加密的方式来隐藏起来。
像linux系统的核心设计思想,就是一切都是文件
!无论是设备还是管道,甚至是进程,都逃不出这个命运。Java也是有样学样,核心思想之一是一切都是对象
!我感觉,Java语言设计出来的目的之一就是为了尽可能地降低Java编程人员的门槛。这也解释了为什么很多程序员会被蔑称为Javaer
,因为他们只知道使用别人写好的东西,而对于代码的本质和原理,他们其实一无所知,他们就是刚刚好踏在Java尽可能降低后的门槛上的人!
好的,让我们把话题切回来,当把我前面所说的两个思想合在一起看,就很有意思了。首先,Java的核心思想是一切都是对象,但是对象总不可能凭空诞生吧,计算机目前其实仅仅是一个工具,他所能做的仅仅是按照你提供的规则对于你所录入的数据进行解析。既然不可能凭空诞生,那么他就必定需要通过文件进行读取。我们可以在应用运行前,就完成对于文件的读取操作,也可以在应用运行时来进行对于文件的读取操作,目标都一样,获取构件一个对象的基本数据信息。
所谓加固就是将原本在应用运行前,虚拟机就知道从哪里读取,怎么读取的类相关信息抽出来加密,变为在运行时从特定路径下读取和解密才能使用的数据。通过这样的方式达到所谓的应用安全的目的。
所以按照上面的思路,去壳的操作整体就分为三步:
- 基于密钥初始化AES加密类
- 解压壳应用并加密dex文件
- 动态加载加密的dex文件
@Override
protected 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);// algorithmStr
byte[] 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, new
ArrayList<>(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
} else {
expandFieldArray(dexPathList, makeDexElements(dexPathList, new
ArrayList<>(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, File
optimizedDirectory,
ArrayList<IOException> suppressedExceptions) throws
IllegalAccessException, 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加固去壳全部内容,希望文章能够帮你解决Java IO在Android中应用(三):Apk加固去壳所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。