(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)好好活就是做有意义的事情.
(8)亡羊补牢,为时未晚
(9)科技领域,没有捷径与投机取巧。
(10)有实力,一年365天都是应聘的旺季,没实力,天天都是应聘的淡季。
(11)基础不牢,地动天摇
(12)写博客初心:成长自己,辅助他人。当某一天离开人世,希望博客中的思想还能帮人指引方向.
(13)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~
File IO 项目实战—dex文件改造
1.dex文件加密
(1)热修复:大部分时间用的是gradle
(2)为什么用IO讲解?
因为学的是IO,思想是相通的。
2.APK文件反编译
2.1什么是反编译
(1)定义
利用编译程序从源语言编写的源程序产生目标程序的过程。
2.2怎么进行反编译
(1)先了解apk的文件构造
(2)Android中的dex文件就相当于Java中的jar文件,反编译的过程其实就是将classes.dex文件进行反编译,找出源代码之后,就可以修改功能。
(3)如果应用程序没有加固,就没有密秘可言,至少需要prguard进行代码混淆。
-
混淆其实是一个代码替换,ActivityA—>a,但是里面的一些逻辑是看的很清楚的。意义并不是很大,但是有意义,因为这样做确实可以带来一定的安全性。
-
免费的加固方案意义也不是很大,付费与不付费是有区别的。
2.3加固方案的手段
(1)反模拟器
(2)代码虚拟化
-
由Android系统架构得知我们所开发的应用程序在上层,代码很容易使用通用的模式与通用的API,这种情况之下,所有的代码都是运行在虚拟机之上的。
-
既然APP是在虚拟机上运行的,可以尝试不让其在虚拟机上运行。
-
因为所有的Java代码都是运行在虚拟机上的,所有的编解码都是一样的,在这种情况下如何去加固?无论如何去加固,都逃不脱编解码方式。
-
现在的商业加固方案,是另外起一套内存虚拟机的方式,让所有的应用都是通过这种内存虚拟机的方式编解码,相当于是多了一套编解码的方式。相当于在虚拟机上多了一个虚拟机,即运行在自己的虚拟机上,而不是在JVM虚拟机上,这就是代码虚拟化。
(3)加密
-
样本的部分代码以压缩或者加密的形式存在。
-
因为dex文件加载,都是一个类加载。
-
任何一个.class文件都是需要在内存里面才能运行,因此必须将dex文件加载到内存中。
-
如何加载dex文件呢?
-
加载到内存中之后,首先会检查这个类有没有加载进来,如果没有加载进来,虚拟机就把这个类填充到内存中来,然后运行。
-
这一过程当中,可以给我们一个空隙,即在运行之前加载进来即可。
-
一个app存在多个dex文件,它并不是一次性就加载到内存中来,而是需要的时候再去做加载。因此可以将dex文件分成很多部分,非核心代码与核心代码。
-
可以将非核心代码展示出来,当非核心代码在去使用一些核心代码的时候,然后再将它加载进来,动态的去加载。加载的过程中核心代码加固,非核心代码不加固,以此来提高代码的安全性。
-
没必要全部加固,因为解密速度会比较慢。
2.4加固方案的总体思想
一个程序员的故事:
辛辛苦苦找到一个对象,结婚后发现是个母夜叉。不给管钱就闹,晚上睡觉她趴着睡,导致这
程序员无法去洗脚了。然而这个程序员很努力,平时除了上班,还能够做点外包,赚点外快。
所以他就想到了把工资卡上交,而把赚到的外快放到了自己的小金库。从此过上了性福生活。
2.5加固的方案
(1)将dex文件分为两个部分,一个是壳dex文件,一个是源dex文件。
(2)源dex文件给它加密,壳dex文件不加密。所以看到的是壳,而运行的时候,去解密源dex文件。即可以使用壳dex文件去解密源dex文件。
3.APK加固的方案原理
3.1APK加固总体架构
3.1.1壳dex与源dex如何划分?
(1)将apk的文件分为两类,第一类为dex文件,其他的文件为第二类。是因为我们需要对dex文件进行加密,不加密的东西踢出去,需要加密的文件加进来。
(2)文件解压缩,对文件进行分类。即对文件进行unzip操作,然后对文件进行过滤,过滤完成之后,一类是dex文件,一类是除了dex文件之外其他的文件。
(3)dex文件加密,即使用AES加密,需要将文件的二进制全部读取出来,读出来之后,然后对每一个二进制用AES进行加密,加密完之后再把这个二进制写回去。 因此就涉及到大量的文件IO。
(4)做一个壳dex,将加了密的源dex文件和索dex文件进行合并,合并之后构成新的dex文件。这个新的dex文件即为整个apk文件。加了密之后的文件有可能为变得更大,因此可以做一个对齐操作。
- 对齐:即对dex文件的优化。
(5)此时的APK文件是不能运行的,因为没有签名。因此需要完成签名。
(6)整个过程会涉及到大量的文件IO操作。
3.1.2问题
(1)dex文件可以随便拼凑吗?
(2)壳dex怎么来的
(3)如何签名?
(4)如何运行新apk(如何脱壳)?
3.2dex文件的意义
3.2.1Dex文件是什么?
(1)加固的目的是保护dex,直接而言就是对dex文件进行操作,对dex文件动刀子,必须知道dex文件是什么,能否直接动刀子。什么是源dex?什么是壳dex?
3.2.2APK打包的流程
(1)加壳是在原来apk的基础上加一层保护壳,dex文件修改了就需要重新打包,否则apk安装不了。这就需要我们详细学习apk如何打包的。
3.2.3Dex文件加载流程
(1)加壳后的文件是不能直接用的,dex文件是加密的,所以我们需要对他进行解密,解密后的dex文件如何加载?
3.3Dex文件
(1)每一个文件右键-属性中看到的信息,都来自于文件头。
(2)每一个dex文件分为文件头、索引区与数据区
(3)数据区中类的定义区可以展示,而数据区不展示,即方法名可看见,而方法体看不见。
(4)如果要对文件进行修改的话,需要对checksum,file_size,siganature修改一下即可。
(5)因为明白了dex文件的构造,才能完成对dex文件的修改。
3.4APK打包的过程
(1)依赖的工具
D:\sdk\build-tools\28.0.2
即打包的过程依赖这些工具去完成,而Android Studio将打包的过程可视化了。比如在xml布局文件中添加一个控件,就会相应的在R文件中产生生成一个对应的id,就是通过这些工具自动去完成的。打包的过程是由gradle去完成的。
(1)首先将资源文件利用aapt工具将其纳入到R文件中管理。
例如往xml中添加一个控件,就会在R文件中添加一个id.
aapt工具谁调用的?是由gradle调用的。
(2)有了aidl文件,会通过aidl工具自动的生成java的接口。
(3)再加上Android本身的java代码(Application Source Code)
(4)通过以上三步将所有的文件变成了Java代码,然后使用Java编译器将其打包,编译成class文件。
(5)Android运行的不是java包,是dex文件,因此首先需要将class文件生成dex文件,dex与jar包之间是互通的,因此这一步是使用dx.bat工具将class文件生成dex文件。
(6)将Compiled Resources 编译资源、.dex files (dex文件)、Other Resources(其他资源文件)通过apkbuilder工具打包成apk文件。
(7)使用jarsigner工具对apk文件进行签名,生成签名的apk文件。
学任何技术首先并不是马上就深入,而是先学一些基础性的知识,然后再往深处走。
例如学习Android的AMS,需要先掌握如下技术基础:
(1)handler
(2)binder
(3)WMS
(4)热修复
然后再去学。
3.5加密过程
- 加密文件只需要对apk文件中的dex文件进行加密
3.5.1将apk中的dex文件进行加密
(1)首先需要对dex文件进行解压缩,要从一系列文件中找到dex文件,即过滤文件。
- BufferOutputStream缓存是减少磁盘磁头的操作。
- 输出流只要不close就一定内存泄漏
(2)对加密后的文件进行重新命名
- 因为所有的文件都是.dex,而dex文件会有壳dex文件和源dex文件
- 为了区分源dex与壳dex,因此做一个重命名的操作
File tempFileApk = new File("src/source/apk/temp");
if (tempFileApk.exists()) {
File[]files = tempFileApk.listFiles();
for(File file: files){
if (file.isFile()) {
file.delete();
}
}
}
File tempFileAar = new File("src/source/aar/temp");
if (tempFileAar.exists()) {
File[]files = tempFileAar.listFiles();
for(File file: files){
if (file.isFile()) {
file.delete();
}
}
}
/**
* 1.处理原始APK,加密dex
*/
AES.init(AES.DEFAULT_PWD);
//1.1解压apk
File apkFile = new File("src/source/apk/app-debug.apk");
File newApkFile = new File(apkFile.getParent() + File.separator + "temp");
if(!newApkFile.exists()) {
newApkFile.mkdirs();
}
Zip.unZip(apkFile,newApkFile);
//1.2加密文件
File mainDexFile = AES.encryptAPKFile(apkFile,newApkFile);
/*
1.3对加密后的文件进行重新命名
(1)因为所有的文件都是.dex,而dex文件会有壳dex文件和源dex文件
(2)为了区分源dex与壳dex,因此做一个重命名的操作
*/
if (newApkFile.isDirectory()) {
File[] listFiles = newApkFile.listFiles();
for (File file : listFiles) {
if (file.isFile()) {
if (file.getName().endsWith(".dex")) {
String name = file.getName();
System.out.println("rename step1:"+name);
int cursor = name.indexOf(".dex");
String newName = file.getParent()+ File.separator + name.substring(0, cursor) + "_" + ".dex";
System.out.println("rename step2:"+newName);
file.renameTo(new File(newName));
}
}
}
}
3.5.2处理aar 获得壳dex
(1)aar文件就是lib
(2)new Module的时候,选择的是android library,这个library,一旦执行Build-make,就会生成一个aar文件。
(3)aar文件与apk文件的差别是怎样的
- 它除了没有签名文件Meta INFO,其他的都是一模一样的。
- aar中也没有dex文件,只有jar包,因为jar与dex文件是相通的。如果要把jar包转变为dex文件,又是大量的文件IO操作。
3.5.2.1将aar文件解压缩并且转换为dex文件
File aarFile = new File("src/source/aar/mylibrary-debug.aar");
//2.1将aar文件解压缩并且转换为dex文件
File aarDex = Dx.jar2Dex(aarFile);
public static File jar2Dex(File aarFile) throws IOException, InterruptedException {
File fakeDex = new File(aarFile.getParent() + File.separator + "temp");
System.out.println("jar2Dex: aarFile.getParent(): " + aarFile.getParent());
//解压aar到 fakeDex 目录下
Zip.unZip(aarFile, fakeDex);
//过滤找到对应的fakeDex 下的classes.jar
File[] files = fakeDex.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String s) {
return s.equals("classes.jar");
}
});
if (files == null || files.length <= 0) {
throw new RuntimeException("the aar is invalidate");
}
File classes_jar = files[0];
// 将classes.jar 变成classes.dex
File aarDex = new File(classes_jar.getParentFile(), "classes.dex");
//我们要将jar 转变成为dex 需要使用android tools 里面的dx.bat
//使用java 调用windows 下的命令
Dx.dxCommand(aarDex, classes_jar);
return aarDex;
}
3.5.2.2将得到的壳dex文件写到apk/temp/classes.dex文件中
File tempMainDex = new File(newApkFile.getPath() + File.separator + "classes.dex");
if (!tempMainDex.exists()) {
tempMainDex.createNewFile();
}
FileOutputStream fos = new FileOutputStream(tempMainDex);
byte[] fbytes = Utils.getBytes(aarDex);
fos.write(fbytes);
fos.flush();
fos.close();
3.5.2.1项目中Application写在library中会引发的问题
(1)意味着在App里面无法写自己的Application了,这样就会导致Application会很臃肿。
(2)在真正的商业开发中,在Library中写了Application之后,也要在App中写一个Application,然后通过Hook Application的方式(插件化的方式),也就是将library库变成一个插件.就像activity hook插件一样,即在清单文件中写一个虚假的Activity,然后在虚假Activity加载的时候,将真的Activity替换上去。
(3)这个技术在反射中使用
3.5.3混合打包
/**
* 3.打包签名
*/
File unsignedApk = new File("src/result/apk-unsigned.apk");
unsignedApk.getParentFile().mkdirs();
//3.1对apk/temp中的文件文件进行打包压缩
Zip.zip(newApkFile, unsignedApk);
//3.2不用插件就不能自动使用原apk的签名...
File signedApk = new File("src/result/apk-signed.apk");
Signature.signature(unsignedApk, signedApk);
3.5.4解密的过程
(1)即脱壳
(2)对源dex解密,加载这些dex文件
(3)脱壳的过程是在运行apk的过程完成,不是在安装apk的过程完成。
(4)安装apk的过程app不会运行,apk都没有启动,是无法运行自己写的代码的。
(5)APP运行最早的时机去解密是最好的,而最早是什么时候呢?
是Application的attachBaseContext()方法
/**
* 解密APK中dex文件的最早时机
* @param base
*/
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
AES.init(getPassword());
//1.获取APK包
File apkFile = new File(getApplicationInfo().sourceDir);
//data/data/包名/files/fake_apk/
//2.解压缩apk包
File unZipFile = getDir("fake_apk", MODE_PRIVATE);
File app = new File(unZipFile, "app");
//3.2一旦APK已经解密了,就不需要再解密
if (!app.exists()) {
Zip.unZip(apkFile, app);
File[] files = app.listFiles();
for (File file : files) {
String name = file.getName();
if (name.equals("classes.dex")) {
} else if (name.endsWith(".dex")) {
/**
* 3.对所有的dex文件进行解密,解密完之后,再将dex文件写回去,即将dex文件
* 3.1即将data/data/包名/files/fake_apk/中的大量的解密之后的dex文件同样
* 需要去加载进来。
* 3.2是否每次启动的时候都会解密呢?
*不需要,加个标识。
* 3.3真正的保护是要保护好密钥。
*/
try {
byte[] bytes = getBytes(file);
FileOutputStream fos = new FileOutputStream(file);
byte[] decrypt = AES.decrypt(bytes);
// fos.write(bytes);
fos.write(decrypt);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
List list = new ArrayList<>();
Log.d("FAKE", Arrays.toString(app.listFiles()));
for (File file : app.listFiles()) {
if (file.getName().endsWith(".dex")) {
list.add(file);
}
}
Log.d("FAKE", list.toString());
try {
V19.install(getClassLoader(), list, unZipFile);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
3.5.5加载dex文件
(1)涉及到classloader
(2)双亲委拖机制
(3)涉及到如何从dex中找class
(4)tinker热修复代码
- 官方代码
https://github.com/Tencent/tinker
-
tinker实现hook dex文件加载的流程
-
hook技术就是利用反射技术去反射Android源码的过程,加勾子。
因为反射是可以修改源码的
- Android源码流程是正常运行的,改变不了源码的流程,因此去反射源码的某个点之后,让它去执行我Hook的点,然后回过头来再执行Android源码的过程。
-正因为有了hook技术,才有了对activity进行hook,插件化
private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
File optimizedDirectory) throws IllegalArgumentException,
IllegalAccessException, NoSuchFieldException, InvocationTargetException,
NoSuchMethodException {
Field pathListField = findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList suppressedExceptions = new ArrayList();
Log.d(TAG, "Build.VERSION.SDK_INT " + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= 23) {
expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList, new
ArrayList(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
} else {
expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new
ArrayList(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
}
if (suppressedExceptions.size() > 0) {
Iterator suppressedExceptionsField = suppressedExceptions.iterator();
while (suppressedExceptionsField.hasNext()) {
IOException dexElementsSuppressedExceptions = (IOException)
suppressedExceptionsField.next();
Log.w("MultiDex", "Exception in makeDexElement",
dexElementsSuppressedExceptions);
}
Field suppressedExceptionsField1 = findField(loader,
"dexElementsSuppressedExceptions");
IOException[] dexElementsSuppressedExceptions1 = (IOException[]) ((IOException[])
suppressedExceptionsField1.get(loader));
if (dexElementsSuppressedExceptions1 == null) {
dexElementsSuppressedExceptions1 = (IOException[]) suppressedExceptions
.toArray(new IOException[suppressedExceptions.size()]);
} 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 = findMethod(dexPathList, "makeDexElements", new
Class[]{ArrayList.class, File.class, ArrayList.class});
return ((Object[]) makeDexElements.invoke(dexPathList, new Object[]{files,
optimizedDirectory, suppressedExceptions}));
}
4.对称加密与非对称加密
4.1对称加密
(1)加密和解密的秘钥使用的是同一个
(2)例如:DES、3DES、Blowfish、IDEA、RC4、RC5、RC6 和 AES
(3)问题是无论哪一方泄漏了密码,这个密码就无效了。
4.2非对称加密算法
(1)公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
(2)加密的密钥与解密的密钥不同。
(3)公钥是别人拿着的,私钥是自己拿着的。
(4)用公钥对用户名与密码进行加密,加密后的用户名与密码,只有私钥可以打开,而这个私钥只有银行有。如果公钥被别人拦截了,是没有用的。因为只有银行知道私钥,只有银行才知道你的用户名和密码是否是正确的,银行将判断结果反馈给客户,不会将密码反馈回来,告诉你说你可以登录了。
(5)如果公钥泄密,私钥有无数个可能。要解密有无限个可能,暴力破解要花很久的时间,比如一百年。
5.打赏鼓励
感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!