前言
感谢scz、surferxyz、h3110w0r1d-y等各位老师傅在前面开路,这篇文章只是跟着前面的老师傅脚步走,用了自己的方式(ClassFileTransformer)开刀。
基于burpsuite_pro_v2022.3.9.jar
使用工具
正文
这里直接用h3110w0r1d-y老师傅的jar开看
1 2 3 4 5 6 7 8 | 文件名称: E:\xxx\burp_latest\BurpLoaderKeygen.jar
文件大小: 18.1 KB ( 18 , 580 字节)
修改时间: 2021 年 07 月 23 日, 10 : 20 : 48
MD5: B7E345B5594331516F49A709C04A3E41
SHA1: B9F4E9CA60E7493E459E21019441D115D2F43D98
SHA256: 63BE39F1AEEBEA9B86477ACEBBEFEE469A7562216BA8B253A4BB87B0A7A68566
CRC32: BCF7BD4F
计算时间: 0.00s
|
启动方式:java -javaagent:BurpLoaderKeygen.jar -noverify -jar burpsuite_pro_v2022.3.9.jar
启动解析
- -noverify 这个是跳过jvm对class文件的验证,后面详细说为什么要跳过
- -javaagent 这个是class加载之前对class进行修改
原理分析
关于javaagent的详细介绍,可以看知道创宇的paper 认识 JavaAgent
破解原理
上jadx,先看看老师傅们都搞了啥.
这里很明显,找到Class里面包含751a8be34c1a9ed9633d04be3ba075a7
的Class文件进行修改,变量p以及p2是负责定位要修改的位置,找到位置后进行patch.
先照搬进IDEA看看到底干了啥
这里可以看到,修改的地方其实很相近,那就dump整个class下来看看情况.
dump的办法
然后调用下
得到两个class文件
还记得之前的启动参数么,-noverify,由于这里简单粗暴的修改了原class的byte,导致jvm的字节码校验失败,根据这个道理,很容易就能找到师傅们干了什么,直接上图把.
用cfr反编译后,直接找empty
两个if被改成empty了,但是到这里我们还是不知道老师傅这么干的道理,继续往下走把.
查找调用链
根据之前cfr反编译出来的源码,能看到两个超级混淆的办法,void a以及bool b,知道类名跟方法名了,直接写个btrace,把调用链弄出来.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | import org.openjdk.btrace.core.annotations. * ;
import org.openjdk.btrace.core.BTraceUtils;
@BTrace (unsafe = true)
public class nls {
@OnMethod (clazz = "burp.nls" , method = "a" )
public static void void_a( Object [] var1, Object var2) {
BTraceUtils.println( "calleded void a" );
BTraceUtils.println( "1st arg" );
BTraceUtils.println( "arg length: " + var1.length);
BTraceUtils.printArray(var1);
byte[] my_var1 = (byte [])var1[ 0 ];
BTraceUtils.println(new String(my_var1));
BTraceUtils.println( "2nd arg" );
String var2_str = String.valueOf(var2);
BTraceUtils.println(var2_str);
BTraceUtils.println( "-----------" );
}
@OnMethod (clazz = "burp.nls" , method = "b" )
public static void bool_b( Object [] var1, Object var2) {
BTraceUtils.println( "called boolean b" );
BTraceUtils.println( "1st arg" );
BTraceUtils.println( "arg length: " + var1.length);
BTraceUtils.printArray(var1);
BTraceUtils.println( "2nd arg" );
String var2_str = String.valueOf(oos);
BTraceUtils. print (var2_str);
BTraceUtils.jstack();
BTraceUtils.println( "-----------" );
}
}
|
过滤后,得到以下输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | burp.nls.b(Unknown Source)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 )
java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 )
java.base / java.lang.reflect.Method.invoke(Method.java: 566 )
burp.pev.a(Unknown Source)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 )
java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 )
java.base / java.lang.reflect.Method.invoke(Method.java: 566 )
burp.dcw.b(Unknown Source)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 )
java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 )
java.base / java.lang.reflect.Method.invoke(Method.java: 566 )
burp.ib2.b(Unknown Source)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 )
java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 )
java.base / java.lang.reflect.Method.invoke(Method.java: 566 )
burp.gw4.a(Unknown Source)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 )
java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 )
java.base / java.lang.reflect.Method.invoke(Method.java: 566 )
burp.hcp.b(Unknown Source)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 )
java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 )
java.base / java.lang.reflect.Method.invoke(Method.java: 566 )
burp.ciq.b(Unknown Source)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 )
java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 )
java.base / java.lang.reflect.Method.invoke(Method.java: 566 )
burp.a_q.a(Unknown Source)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 )
java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 )
java.base / java.lang.reflect.Method.invoke(Method.java: 566 )
burp.f09.a(Unknown Source)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 )
java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 )
java.base / java.lang.reflect.Method.invoke(Method.java: 566 )
burp.bh5.a(Unknown Source)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 )
java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 )
java.base / java.lang.reflect.Method.invoke(Method.java: 566 )
burp.fqs.a(Unknown Source)
burp.e20.b(Unknown Source)
burp.etx.b(Unknown Source)
burp.etx.g(Unknown Source)
burp.etx.<init>(Unknown Source)
burp.epi.d(Unknown Source)
burp.StartBurp.main(Unknown Source)
|
这里能看出,很多类都是动态加载进去的,想知道怎么调用还是得看看上一个调用类干了什么.
还记得之前的javaagent工程里面写了writeModifyClass方法不,把他放到最外层,然后修改下即可dump整个burp加载的class.
分析后发现...pev.class其实就是把nls.class从string数组变成byte数组然后解密加载
无实质性进展..尝试硬啃混淆过的验证类
代码分析
首先,nls里面有两个核心方法,一个void a,另外一个boolean b
这里看到b方法,直接调用了a方法,其中var_9是new的object array,内容是var3_3的byte array,整个传入a方法第一个参数就是object [][],第二个参数就是固定的一串字符串
再查找整个nls.a调用,发现整个文件就两次调用,第二次调用也是差不多
那就可以认定,nls.a的第二个参数是固定的字符串,第一个参数是object [][]类型,具体传入了什么上arthas看看传入
获取验证函数的调用信息
由于是Windows,写个bat放到arthas的目录方便arthas attach到burp的进程
这里有个小技巧,如果burp调用太快手速不够,可以进入主界面后,用help->License->Update License来触发验证类的调用
1 2 3 4 5 | @echo off
for / f "tokens=1" % % a in ( 'jps ^| findstr /rc:"burpsuite_pro_"' ) do @ set BURP_PID = % % a
as.bat % BURP_PID % - - ignore - tools
|
用老师傅的jar先启动,然后运行bat就行
这里监控用watch 类名 方法名 -b -s -x 4
先输入个错误的license key,看看nls类里面方法a跟b什么情况
这里输入的是abcd,所以对着的是97,98,99,100,那就知道了其实nls.a传入的是字符串的byte array再放进Object[]
再尝试正确的License Key,根据之前代码进行分析得到下面的结论
- b方法进入时收到的参数是Object[][],第一个数组是int的数组负责控制走哪个流程,第二个是字符串获取到的Byte数组;退出时第一个数组变成了Object数组套着的验证信息,第二数组没变
- a方法进入时收到的参数是Object[][],第一个参数是字符串获取到的Byte数组,第二个参数是固定的字符串;退时,第一个参数变成验证信息,第二个参数没变
结合注册流程,可以得知
- 用户输入的License Key,先进入nls.b,然后由nls.b调用nls.a进行解密,解密成功则修改传入的第一个变量作为字符串数组给nls.b进行下一步操作
- nls.a是解密函数
- nls.a会被调用两次,两次修改的变量长度不一致,对应License Key解密跟Activation Respond解密
有了以上信息,尝试构造nls.a的调用
分析解密函数
之前有dump过破解跟原版的验证class,使用破解后的class用jar打包
jar -cvf burp.jar burp/nls.class
作为依赖塞进IDEA的工程,这里注意Run的配置要加上jvm参数-noverify
这里能成功解密了,现在就是这串License Key是怎么生成的问题了
继续用jadx查看Loader的注册机部分
这里很明显了,DES加密,key在最顶上是祖传的burpr0x!
大体流程就是字符串数组然后转成byte array用0作为分隔,再进行DES加密,最后base64
那么我们就可以根据这个流程反向写出解密函数
写自己的解密函数
这里没什么好说的,直接跟着原理,先base64解码,然后des解密成byte array,转化成字符串后再用0分割成字符串数组
这里注意,解密出来的字符串数组长度会比原生的nls.a解密出来的字符串数组长,需要根据字符串数组长度删减返回的数组长度
其中长度是7的是License Key解密,长度10的是Activation Respond解密
到这里就差不多了,可以开始写自己的javaagent了
编写Javaagent
获取java版本的asm代码
要把上面的java代码覆盖到原来的文件,我用的org.ow2.asm这个库,当然你用其他的库也可以,例如bytebuddy.
首先把java方法变成asm库的代码,这里我直接用asm库里面的asmifier(org.ow2.asm:asm-util:9.3)
- 下载asm本体以及asm.util两个jar
- 把之前写的Main编译成class,什么方式都行
- 把class丢到下载的jar同目录
java -cp asm-9.3.jar;asm-util-9.3.jar org.objectweb.asm.util.ASMifier Main.class
- 得到java版的asm代码
写ClassFileTransformer
在manifest定义Premain-Class以及Agent-Class
实现接口ClassFileTransformer
覆写激活类的方法
代码部分我就不详说了,有兴趣的可以自己围观
展示
测试版本burpsuite_pro_v2022.5.1.jar
结束
自己还是太菜不会用动态debug,只能用这样的笨方法来研究,代码附上,成品附上,欢迎各位大佬指导.
在这里感谢各位老师傅的路子,让刀burp不用找具体激活类具体是哪个,方便我这种后来破解的小白.
截至写完本文,2022.5.1还能这么刀
[2022冬季班]《安卓高级研修班(网课)》月薪三万班招生中~