Java代码反反编译对抗思路
/0x01 几种反反编译的方法
Java代码是可以反编译的,但是很多时候一些Java开发者并不想让其他人知道自己的代码是怎么写的,就会对Java代码进行加密或混淆等操作,一般来说有三个思路:
- 将class文件加密,这个是最安全的,但也费事儿,因为要重写classloader来解密class文件;
- 使用花指令,使得class文件不能反编译(利用反编译工具漏洞);安全性一般,还是有花指令破解器;
- 代码混淆,提高代码阅读成本;简单易操作,一般采用这种或者与其它方式结合;
当然,这几种方法都是可以被破解的,只是不同方法的破解成本不一样而已。
0x02 准备jar包
新建一个ClassEncode项目,再新建两个类如下:
1 | package com.mi1k7ea; |
接着将该项目打包成jar包。
IDEA打包jar
在项目上鼠标右键 –> Open Module Settings:
Artifacts –> + –> JAR –> From modules with dependencies:
接着是设置Create JAR from Modules:
Main Class是这个项目(脚本)的主方法,就是要运行的类,选Test类。
关于JAR files from libraries的两个选项:
选中第一个的话,打完包后是一个Jar包;
选中第二个的话,打完包后是一个Jar包,外带你项目所用的Jar包;
接下来是MF文件的存放目录。注意,不能使用默认目录,必须自定义目录,不然会运行错误,也不能使用src/main/resources这个目录。这里设置的是项目根目录下的src目录下来新建resource目录。
设置完之后,此时应该也必须有META-INF此文件,不然会运行错误。若JAR files from libraries选择src/main/resources目录,是没有META-INF文件的。Output directory是jar包的保存目录:
点击Build:
然后在out目录中看到生成了Jar包:
这时的运行jar包就能执行了:
ok,我们就拿这个包作为Demo进行反编译操作。
手动打包jar
当然,也可以手动打包jar文件。
目录结构如下:
com
mi1k7ea
Test.class
Test2.class
META-INF
MANIFEST.MF
其中MANIFEST.MF中的Main-Class要指定主要的执行类:
1 | Manifest-Version: 1.0 |
注意:最后必须要回车空行出来。
最后在当前目录运行一下命令打包成jar并执行即可:
1 | jar cvfm test.jar META-INF\MANIFEST.MF . |
0x03 加密Jar包和class文件
利用JVMTI实现反反编译
JVMTI(JVM Tool Interface)是Java虚拟机所提供的native编程接口,可以探查JVM内部状态,并控制JVM应用程序的执行。可实现的功能包括但不限于:调试、监控、线程分析、覆盖率分析工具等。
JVMTI能够监听class加载事件,因此我们可以使用一套加密算法,对即将发布的Jar包进行字节码加密,然后在JVM加载这些类之前再解密。由于这部分代码最终会以动态库(.dll、.so文件)的形式发布出去,不容易被破解,因此对源代码可以达到较好的保护效果。
这里用到一个工具:https://github.com/AloneMonkey/JarEncrypt
解压之后得到如下的目录结构及文件:
- JarEncrypt/encrypt:加密库
- encrypt.cpp
- Makefile
- Encrypt.java(Java加密执行文件)
- JarEncrypt/decrypt:解密库
- decrypt.cpp
- Makefile
打开Encrypt.java文件,修改需要进行加密的类为以”com.mi1k7ea”开头的包下的所有类:
然后打开decrypt子目录下的decrypt.cpp文件,修改需要进行解密的类为以”com.mi1k7ea”开头的包下的所有类:
接着,进入encrypt目录,执行make,编译生成libencrypt.so:
注意,在Linux可能会报找不到jni.h和jni_md.h文件的错误,这里需要通过locate jni.h
和locate jni_md.h
命令来找到这两个文件所在的路径,然后在Makefile中第一个INCLUDEDIR项中添加进去即可:
libdecrypt.so的编译同上:
接着,通过javac Encrypt.java
命令将该java文件转换为class文件:
通过如下命令加密jar包:
1 | java -Djava.library.path=. -cp . Encrypt -src ClassEncode.jar |
此时用反编译工具是没办法成功反编译的:
此时运行肯定是会失败的:
使用刚刚的编译生成的解密库来执行就ok了:
1 | export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/root/jvmti/decrypt |
至此,已经通过加密Jar包和class文件实现了反反编译。在发布时候,需要把ClassEncode_encrypt.jar和libdecrypt.so发布出去,执行时候引入libdecrypt即可。
通过Java-Agent绕过反反编译
这部分在JavaAgent中会具体说明。
MainAgent.java:
1 | package com.dumpclass; |
DumpClassTransformer.java:
1 | package com.dumpclass; |
MANIFEST.MF:
1 | Manifest-Version: 1.0 |
打包成ClassEncode_encrypt.jar。
通过以下命令,指定JavaAgent的jar包,然后在目标jar包主执行类方法执行之前先执行DumpClass.jar中的premain()方法,从而从内存将加密的目标jar类的字节码Dump下来:
1 | java -Ddump_package=com.mi1k7ea -Ddump_out_folder=/tmp -agentlib:decrypt -javaagent:DumpClass.jar -jar ClassEncode_encrypt.jar |
下载下来,此时就能从成功反编译
获取到加密class文件的内容了:
0x04 Java代码混淆
参考:https://www.cnblogs.com/nevermorewang/p/8041548.html
这种反反编译方法的没啥破解方法,就是代码比较难读而已,花点时间精力就可以搞定。
0x05 使用花指令
这部分还未研究,有待补充。。。