XJar加密 + SprintBoot + 自有 API Token 验证破解
结构分析
目标服务采用 docker 容器化部署,入口点命令为。
/xjar java -jar /app.jar
JAR 分析
可以看到,除了 Spring 的 Bootloader 和静态资源都被主角 XJar 加密了,但好在 XJar 加密的过程完整开源,方便我们进行逆向分析。
XJar
https://github.com/core-lib/xjar
支持原生JAR,基于对包内资源加密及拓展 ClassLoader ,以此构建一套程序加密启动、动态解密运行的方案。技术非常值得学习,但本质上不能避免源码泄露以及反编译。
特性
无代码侵入,只需要把编译好的JAR包通过工具加密即可.
完全内存解密,降低源码以及字节码泄露或反编译的风险.
支持所有JDK内置加解密算法。
可选择需要加解密的字节码或其他资源文件。
支持Maven插件, 加密更加便捷。
动态生成 Go 启动器,保护密码不泄露。
审查 XJar 源码
XJar 生成加密的 JAR 后,只需要一个 xjar 二进制文件作为启动器。其源码位于 xjar/src/main/resources/xjar/xjar.go
中。这里主要是将加密参数拼接起来,向 Java 传入。
侧信道攻击
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
input := bufio.NewScanner(os.Stdin) // 初始化扫描对象
for input.Scan() { // 扫描输入内容
line := input.Text() // 转换为字符串
fmt.Println(line) // 输出到标准输出
}
}
在 XJar 的加密原理背景下,每个 JAR 的加密参数不同,解密数据位于启动器中。虽然其有加密,但不妨碍我们用伪造的后继程序骗出加密参数。
JAR 包解密
package app;
import io.xjar.XCryptos;
import io.xjar.XEntryFilter;
import io.xjar.XKit;
import io.xjar.boot.XBoot;
import io.xjar.jar.XJar;
import io.xjar.key.XKey;
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
import java.security.NoSuchAlgorithmException;
public class run {
public static void main(String[] args) {
String password = "密码";
XKey xKey;
{
try {
xKey = XKit.key(password);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
try {
XCryptos.decryption().from("C:/Users/sfc9982/Desktop/app.jar")
.use("密码")
.exclude("/static/**/*")
.to("C:/Users/sfc9982/Desktop/app-decrypt.jar");
// XBoot.decrypt("C:/Users/sfc9982/Desktop/app.jar", "C:/Users/sfc9982/Desktop/app-decrypt.jar", xKey);
// XJar.encrypt("C:/Users/sfc9982/Desktop/app-decrypt.jar", "C:/Users/sfc9982/Desktop/app-reenc.jar", xKey);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
这里 OOP 的味道很浓,解密用到的方法都可以在相关的 Class 中找到。成功运行后,就得到未加密的原始 JAR 文件了。
Java 项目逆向
逆向工具使用 JADX 或者 JD-GUI。
业务代码逻辑比较清晰,这里我的目标主要是绕过一个验证服务,使自己获得高级授期。
代码
这里我们投机取巧,将 this.authUrl
改为一个非法值,使其丢出 Exception。然后修改最后 reutrn 回去的 JSON,加入激活信息。
激活信息的格式可以观察校验逻辑和利用原有验证 API 得到。
结果
成功激活!
Summary
条条大路通罗马,有些看起来逆天,但是比较短。