项目要部署到第三方服务器上,于是研究了一下jar包加密的方式,其中在github上有一个项目XJar,挺多使用用户,也搜到了破解的教程,于是研究了一下。详细说下如何加密、破解。这些加密只能说“防君子不防【小人】”,大神总有办法,只要项目有足够的价值,那其他都不值一提。
可以查看github源码,作者也说了原理:基于对JAR包内资源的加密以及拓展ClassLoader来构建的一套程序加密启动, 动态解密运行的方案, 避免源码泄露以及反编译。总结一下,大致就是:
tuienStart.exe 程序A(java) 参数 -jar tuine-1.0.0.jar 参数
Maven项目可通过集成 xjar-maven-plugin 以免去每次加密都要执行一次上述的代码, 随着Maven构建自动生成加密后的JAR和Go启动器源码文件.
XCryptos.encryption()
.from("/path/to/read/tuine-1.0.0.jar.jar")
.use("tuinepassword")
.include("/io/xjar/**/*.class")
.include("/mapper/**/*Mapper.xml")
.exclude("/static/**/*")
.exclude("/conf/*")
.to("/path/to/save/tuine-1.0.0.jar.jar");
go build xjar.go
/path/to/xjar /path/to/java [OPTIONS] -jar /path/to/encrypted.jar [ARGS]
/path/to/xjar /path/to/javaw [OPTIONS] -jar /path/to/encrypted.jar [ARGS]
nohup /path/to/xjar /path/to/java [OPTIONS] -jar /path/to/encrypted.jar [ARGS]
既然知道了加密原理,而且还有源码,所以破解思路就清晰很多了,如果没有源码的话,还是能难道一大批人的,若需应用到项目中,可以根据作者的项目进行优化升级形成自己的加密程序,这样来说更安全。
明确了“密码信息”是通过流的方式输入给程序A的,而且go程序没有前置校验程序A,所以这里来个弄巧成拙,把程序A替换为其他程序,并且把流信息打印出来就可以查看到密码信息了!
dec.php
var_dump(file_get_contents('php://stdin'));
执行程序及结果:
解密在jvm内存中进行,所有堆内的class文件肯定是解密后的,所以理论上完全可以通过查看堆内文件信息来获取class文件。
JAVA官方提供了可视化工具HSDB,可以下载class文件。
jps -l
,查找对应程序的IDjava -cp your_javahome_path/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
your_javahome_path/bin/jhsdb.exe hsdb
说明碰到的一个问题,下载class文件报错:Create .class File java.io.IOException: 系统找不到指定的路径。原因就是系统没有权限,当时考虑windows应该不涉及权限问题啊。后来google也搜不到解决方案,然后仔细想了一下,如果在执行下载,也没让选择保存目录,那只能基于我执行的当前目录去相对路径创建,因为java安装在C盘的Program Files目录,而我是进入到javahome目录中后执行的命令(C:\Program Files\Java\jdk1.8.0_251\lib> java -cp .\sa-jdi.jar sun.jvm.hostspot.HSDB
)所以考虑极有可能是系统目录权限的问题。
于是改为:java -cp C:\Program Files\Java\jdk1.8.0_251\lib\sa-jdi.jar sun.jvm.hostspot.HSDB
。果然执行成功,验证了猜想。而下载的文件则是在当前执行目录下,并且按照包名进行创建的文件夹。
既然能够在内存中获取class文件并下载出来,并且是扩展ClassLoader实现的,于是查看xjar的源代码ClassLoader部分得知,XKey类中包含了密码的各种信息:
public XBootClassLoader(URL[] urls, ClassLoader parent, XDecryptor xDecryptor, XEncryptor xEncryptor, XKey xKey) throws Exception {
super(urls, parent);
this.xBootURLHandler = new XBootURLHandler(xDecryptor, xEncryptor, xKey, this);
this.urlClassPath = XReflection.field(URLClassLoader.class, "ucp").get(this).value();
this.getResource = XReflection.method(urlClassPath.getClass(), "getResource", String.class).method();
this.getCodeSourceURL = XReflection.method(getResource.getReturnType(), "getCodeSourceURL").method();
this.getCodeSigners = XReflection.method(getResource.getReturnType(), "getCodeSigners").method();
}
使用Java程序性能分析工具VisualVM,使用其Heap Dump功能,查看堆栈中的信息。
如经过破解方式1和3,可以拿到加密信息,则可以通过自带的解密程序进行解密:
XCryptos.decrypt("D:\\tuine-1.0.0.jar","D:\\tmp.zip", "tuinepassword");
解压拿到的tmp.zip即为加密前的所有class文件。
反编译:
记录一次破解xjar加密的经历
破解及修复issue
99%的人都搞错了的java方法区存储内容,通过可视化工具HSDB和代码示例一次就弄明白了