squaretest逆向记录
squaretest逆向记录
单测编写在程序开发实践中至关重要,我想每个写过单测的小伙伴都深有感受。但编写单测费时费力,极大影响研发的工作效率。凭借拿来主义精神,我熟练地打开百度并在互联网中搜寻自动化单元测试生成工具,功夫不负有心人,在众多质量参差不齐的单测生成工具中发现了squareTest,使用过感觉相当不错,能够自动生成Mockito测试用例还能兼容多Module工程,但30天的免费试用就让人不开心了,内外网也没对应开心版本,但毕竟java能够轻松反编译,就尝试pj,并记录下
环境:
系统:macos
工具:idea 2021.3;jclasslib
问渠那得清如许,为有源头活水。哪个小伙伴不想知道程序验证的原理呢?那我们就尝试反编译程序看看他是怎么加密的。最佳反编译实践可以看我这篇https://www.cnblogs.com/vycz/p/15624996.html
java -cp "/Applications/IntelliJ IDEA.app/Contents/plugins/java-decompiler/lib/java-decompiler.jar" org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true TestStarter-1.6.4.jar.jar mysrc
反编译可以看出其实代码是有混淆的,但不打紧,关键字符串是没混淆的。咱们可以从ui上的提示文案入手。
校验按钮Validate and Save就是咱们的入手点,简单在代码中全局搜索下
在com.squaretest.c.l下这个按钮被引用了。
这里面包含了3个关键方法createActions() doOKAction() doValidate()
大致我们可以知道一个是创建组建的方法,一个是点击ok时的回调,一个是验证方法
通过DialogWrapper我们能知道doValidate是在doOKAction方法内做校验逻辑的,校验成功调用OkActions,失败返回错误提示。
到此咱们主要关心,一个是doValidate中this.a.b(var1)到底做了啥逻辑
第二是doOKAction做了啥
咱们看this.a.b(var1)到底在干啥
absolutely 就是咱们的验密逻辑,返回var6.verify(var7) 就是var6就是a公钥+var5签名消息进行验密,正确返回true,错误返回false。这个咱们先不急先接下里看
既然doValidate已经完成验密工作,那么我们自然而然会想到是不是要缓存许可证书了!毕竟不能每次启动idea都得重新验证吧
咱们可以看到var1被set了,按照业务开发逻辑,只是一个简单的对象set操作,但实际到代码中咱们看看
实际是实现了一个idea提供的持久化存储接口,所以咱们就懂了,在doValidate通过公钥对文本和签名就行核验,核验通过后太多doActions就行证书的持久化仓储!
到目前为止原理咱们都知道的了,常规咱们能想到2种破解方法
第一种是通过java agent对代码就行增强,增强证书校验方法,令其直接返回true。测试发现在idea中通过custom vm进行java agent进行配置后,idea启动直接崩溃了!后来发现可能是idea的一种启动保护措施,防止进行字节码增强。
第二种是直接修改字节码,通过修改字节码常量池中的公钥串,实现生产证书。咱们来实验下!
通过反编译代码我们得知,公钥串位于com.squaretest.c包的r对象下
将class文件通过jclasslib打开
咱们轻松找到了公钥的存放地址,现在问题来了,怎么生产公钥和证书呢
先放生成代码
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keys = keyPairGenerator.generateKeyPair(); PublicKey publicKey = keys.getPublic(); System.out.printf("pub:"+Base64.getEncoder().encodeToString(publicKey.getEncoded())); PrivateKey privateKey = keys.getPrivate(); System.out.println("\n"); System.out.println("priv:"+Base64.getEncoder().encodeToString(privateKey.getEncoded())); Signature sig = Signature.getInstance("SHA256withDSA", "SUN"); sig.initSign(privateKey); String clearText = "SQT1-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "SQT1-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "SQT1-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"; sig.update(clearText.getBytes(StandardCharsets.UTF_8)); byte[] signature = sig.sign(); System.out.println("sign:"+Base64.getEncoder().encodeToString(signature)); sig.initVerify(publicKey); sig.update(clearText.getBytes(StandardCharsets.UTF_8)); boolean verify = sig.verify(signature); System.out.println("verify:"+verify);