目录
安卓包内容分析
META-INF里边是签名的文件 ,dex文件就是Dalvik,反编译主要是针对这个文件。
混淆之后的代码基本逻辑没有变化,还是可以了解到其中的逻辑。
免费的加固方案加固的意义不是很大。
代码加固的几种方式
反模拟器:模拟器的代码是可以自己写的,这样子我们可以安卓源码上进行log的打印,可以监控模拟器上代码的运行。
代码虚拟化:所有代码都运行在虚拟机上,那么我们能不能让你的代码不运行在虚拟机上?因为我们的代码最终都要运行在虚拟机上,无论怎么加固,我们都逃不了编解码的方式,所以现在商用的加固方式采用了他们自定义的虚拟机运行,通过他们的虚拟机,转换成另一种编码,相当于多了一种编码方式,代码虚拟化,相当于在虚拟机上边再套一个虚拟机。整体上代码就运行在自定义的虚拟机上边。这就是代码虚拟化。
加密方式:dex文件必须要加载到内存中才能运行,如果没有找到,必须在磁盘中加载进来。类是需要的时候才加载。
代码并不一定需要全部加固。加固核心代码就行了。有两个原因:1、全部加固影响性能;2、全部加固,那么我们用谁来对加固的代码进行解密呢?微信就是使用加密技术。
参考文章[原创]从第一代到第五代,App加固技术详解-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com
面向二进制代码的多线程虚拟化保护技术 - 安全内参 | 决策者的网络安全知识库
Dex文件加固技术原理解析
打包需要进行对齐,dex的一个优化。这样可以减少加固后的包的体积。
加固需要解决的问题
1、dex文件可以随便拼凑吗?
2、壳dex怎么来的?
3、如何签名?
4、如何运行新的apk(如何脱壳)?
一个Dex文件的整体结构:
在加固的时候,可以对将类的定义区展示,将类的数据区进行加密,这样在最后展示的dex文件中,就会出现只看得到定义区 ,而数据区是空的情况,例如微信的一部分代码:
checksun:对文件的整体的校验,signature:签名信息;file_size:文件大小,我们进行dex文件加密的时候需要注意这几个参数的修改。正是因为dex文件的构造我们才能对dex文件进行加密。
apk打包流程:
都是在SDK里边的build-tools文件夹下边的工具帮我们做的。
加固流程
1、apk->解压缩->过滤文件(找到所有的dex文件)
文件操作中flush()方法的的作用:强制将未填满的缓存数据写到硬盘中。缓存之所以能够提高新能,是通过减少磁盘的磁头操作来提高性能。
如果是写的是字符,调用close的时候,也会自动调用flush(),字节流也有自动flush(),看源码。
调用bwdef.close()会报错;bwdef关闭--》oswDef关闭--》fos关闭,所以会报错。
File file = new File("src/testtxt/OutputStreamWriter.txt");
// true, 设置内容可以追加
FileOutputStream fos = new FileOutputStream(file, true);
//todo 是否有一个封装好的writer?
OutputStreamWriter oswDef = new OutputStreamWriter(fos);
BufferedWriter bwdef = new BufferedWriter(oswDef);
bwdef.write(STRING);
bwdef.newLine();
bwdef.flush();
// bwdef.close(); // 为什么不能写?
System.out.println("oswDef encoding: " + oswDef.getEncoding());
Exception in thread "main" java.io.IOException: Stream Closed
at java.base/java.io.FileOutputStream.writeBytes(Native Method)
at java.base/java.io.FileOutputStream.write(FileOutputStream.java:349)
at java.base/sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:234)
at java.base/sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:313)
at java.base/sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:318)
at java.base/sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:160)
at java.base/java.io.OutputStreamWriter.flush(OutputStreamWriter.java:250)
at java.base/java.io.BufferedWriter.flush(BufferedWriter.java:257)
at classTop.OutputStreamWriterTest.testOutputStreamWriter(OutputStreamWriterTest.java:38)
at classTop.OutputStreamWriterTest.main(OutputStreamWriterTest.java:15)
2、将一个文件文件按照字节流读出来的方式:
RandomAccessFile fis = new RandomAccessFile(dexFile, "r");
byte[] buffer = new byte[(int)fis.length()];
// 把这个文件全部读出来。
fis.readFully(buffer);
fis.close();
加密之后,需要对文件重新命名,用来区分壳文件和源文件。
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));
}
}
}
arr文件与apk的区别:仅仅没有签名文件而已。
为什么壳从aar来?
我的application来自module的application,正式因为application在aar中的,代码在aar的jar包里边,正式因为这个jar是和我们的dex文件是分开的,那么这个时候aar的application就不会被加密,正是因为不会被加密,我们可以这个application来进行解密,因为我们把apk里边所有的dex文件进行加密。这就是我们的主要目的。
加固的整体框架
我们要明白,要能运行加固后的apk,必须要多加密的dex文件进行解密。加密的dex文件分两部分,一部分是壳dex,另一部分是加密的dex,那么我们的源dex文件要能解密运行,就需要利用可以运行的程序解密,这个程序要在java虚拟机中运行,这个代码必然没有加密,如何让这部分代码没有加密呢?需要将两部分dex文件分开,加密的称为源,未加密的称为壳,不加密的壳应该具有什么功能?需要能够解密的功能,解密最好在application中进行。所以application成为了我们解密的入口代码,这个代码一定不能加密。
此时我们就需要将这个application写到一个独立的module里边去。编译成一个aar文件:
最终会被编译到这个jar包里边去。
但是真正的项目中,一般不会这么写,因为application里边一般会做很多初始化的操作,一般的,我们会在也会另外再写一个主application,利用插件化的技术,将mylibrary变成一个插件。 当主application启动的时候,通过反射hookapplication的启动流程,进而启动module中的未加密的application,利用这个application解密之后再启动主application。
解密流程
脱壳->源dex 解密->加载这些dex文件
脱壳应该有两个时机,安装apk(错误的,不可以这么用)和运行apk时。但是一般更可靠的时机是运行apk,因为只有此时才会运行代码。运行时越早越好,在attachBaseContext中进行解密。
我们也不需要在每次启动的时候都进行解密,在app启动时增加一个判断,如果解密后的文件已经存在了,就不用再走解密流程了。
加密解密是一个攻防的工程。
加载dex文件涉及到热修复的流程。hook就是用反射技术去反射安卓源码的过程,就是搞我们想做的事情。
拓展知识:对称加密与非对称加密
对称加密:加密与解密所有的密钥是同一个,一旦泄露,就会出现不安全的情况。
非对称加密:加密与解密的密钥不同,分为公钥和私钥。
常见的算法:1022 ^ 2343 %1110(无数种可能) = 4567
1024 输入 4567 输出
1203是私钥
2343是公钥
HTTPS是通过证书来保证后续服务器与客户端的对称加密的密钥的安全。