-
-
[旧帖]
[原创]justinmind prototyper暴力破解(java)
0.00雪花
-
发表于:
2011-2-27 02:47
4212
-
[旧帖] [原创]justinmind prototyper暴力破解(java)
0.00雪花
目标软件:justinmind官方网站下载30天试用版Justinmind Prototyper
保护方式:使用jProductivity的license文件方式保护
调试环境:win7 + MyEclipse8.6
代码阅读:SourceInsight 3.5
文件编辑:UltraEdit
java反编译:XJad 2.2、jd-gui 0.3.3
破解过程:
1. 经过初步分析,定位到jar包com.justinmind.evc_4.0.1.jar
2. 用XJad、jd-gui反编译此jar包。
3. 在SourceInsight中阅读代码,初步确定保护代码位置在com\jp目录,因为其中有com\jp\protection\security目录,com\jp\protection\pub目录下存在License*.java文件,经过网上搜索com.jp.protection,得知是jProductivity的产品。基本确认保护代码位于这里。
4. 暂时放弃反编译得到的除com\jp目录之外的其余部分,结合两个反编译情况,在MyEclipse中修正将com\jp目录的反编译结果,消除编译错误,以便能进行跟踪调试。
此步骤对于反编译软件不能正确反编译而直接显示为java byte code的部分,参考了网上搜索到的相关文章,以及java规范中对于java byte code的描述,在这些知识基础上,此步骤完全是体力活儿,这里略过。
这里稍微值得一提的:
a) 对于不能反编译的情况,XJad反编译结果中,给出了原始的java byte code,相当于c/c++程序的汇编码,可以自己分析处理,而jd-gui中给出的则非常不完整,没法使用。
b) 对于一些try/catch异常保护,以及其他循环、跳转等,jd-gui的反编译结果相对来说,比Xjad要好一些。
c) 代码中做的java代码混淆,主要是:把所有代码中的明文字符串,做了简单异或,在运行使用时才异或回来;除个别类外,大部分的包、类、方法、变量等命名,都采用单字符以降低可读性。
5. 跟踪调试,暴破。
a) 随便造一个文本文件,在软件的license文件选择对话框中选中,提示license文件破损。
b) 经过跟踪调试,发现了com\jp\protection\pub\LicenseReader.java中以下两个方法:
// 验证license文件的入口函数
public synchronized void a(InputStream inputstream, String s1)
throws IOException
{
int i1;
i1 = com.jp.protection.pub.m.q;
g = true;
b = null;
Object obj = null;
byte abyte0[];
try{
abyte0 = b(inputstream);//取文件头明文字符串,验证后续数据的CRC32
}finally{
inputstream.close();
}
label0:
{
if (abyte0 == null)
{
b(this, s1);
if (i1 == 0)
break label0;
}
// 省掉这一步,自己构造license文件,不加密 ????
//abyte0 = c(abyte0); // 解密数据,需要公钥
if (abyte0 == null)
{
b(this, s1);
if (i1 == 0)
break label0;
}
e(abyte0);
if (b != null)
{
((LicenseImpl)b).d(s1);
c(this, s1);
if (i1 == 0)
break label0;
}
b(this, s1);
}
return;
}
protected byte[] c(InputStream inputstream)
{
byte abyte0[] = null;
DataInputStream datainputstream = new DataInputStream(inputstream);
try
{
String s1 = datainputstream.readUTF();
d(this, s1);
long l1 = datainputstream.readLong();
int i1 = datainputstream.available();
abyte0 = new byte[i1];
int j1 = 0;
int k1;
do
{
k1 = datainputstream.read(abyte0, j1, i1);
j1 += k1;
i1 -= k1;
} while (k1 != -1 && i1 > 0);
t t1 = new t();
t1.a(abyte0);
// 去掉校验CRC的部分
/*if (l1 != t1.a() && (inputstream instanceof FileInputStream))
abyte0 = null;
*/
}
catch (IOException ioexception)
{
a(ioexception);
abyte0 = null;
}
return abyte0;
}
其中“//abyte0 = c(abyte0); // 解密数据,需要公钥”,这里的方法c,内部是调用了RSA算法使用公钥解密,这说明正常license应该是需要私钥加密的,私钥我们没法拿到,所以这里只能把解密跳过。
至于“// 去掉校验CRC的部分”下边的代码,当然也可以保留,不过那需要构造license文件时计算一个正确的CRC32值,不方便手工构造license文件,因此注释掉。
事实上,代码中还有其他位置用到了RSA公钥,用于验证部分class文件的签名,以防止这些文件被篡改,幸运的是,LicenseReader.class没在这个验证文件列表中,因此我这里没有列出那部分代码,也没有考虑对其进行额外处理。
c) 以上步骤,基本上已经完成了暴力破解过程。后续就是构造合法的license文件,通过进一步跟踪上述licence文件解析代码,得到license文件格式如下:
[2字节UTF字符个数] + [UTF字符串] + [8字节后续数据的CRC32值,实际CRC32只用4字节,但是java的long型是8字节,所以这里用的8字节] + [文本内容]
其中[文本内容]都是类似如下的字符串:
# type 1:Evaluation , 2:Extended Evaluation , 3:Commercial
typ=3
# Options: 1,2,8(fUserLicensingModel 0,2),64(fUserLicensingModel 3)
opt=0
# issue date: Mon May 31 00:00:00 CST 2010
isd=1283824000000
# expire date: Thu Mar 15 00:00:00 CST 2255
exd=9000000000000
d) 根据c)中分析得到license文件格式,在UltraEdit中手工编辑构造license文件,进一步调试跟踪代码的其他部分,以便确认在程序运行中都需要用到哪些属性,以及这些属性的合法取值范围都应该是多少,并将其以[属性]=[属性值]的形式补充到license文件中,最终形成合法license文件。
6. 以上都是在调试环境运行。
将编译得到的LicenseReader.class,直接覆盖安装目录Justinmind\Justinmind Prototyper 4.0.1\plugins\com.justinmind.evc_4.0.1.jar中的同名文件,程序可以正常运行,在程序About中可以看到预期的license信息描述。
暴破初步成功。
7. 由于此程序是30天试用版,关闭程序,将系统时间增加2个月,重新启动程序,仍能正常运行,且license信息显示正确。
关闭程序,把系统时间还原,再次启动程序,提示许可证已过期,无法正常运行,说明内部还存在其他验证。
继续进调试环境跟踪,在license文件读取解析后信息的访问位置设置断点,发现程序在C盘的两个目录,以及注册表中,都写入了信息,根据跟踪到的信息,将相关位置数据清空后,再次启动程序,正常启动,license信息显示正常,功能正常使用。
这里对c盘文件、注册表项,只作了简单异或处理,而这里的破解过程,也只是基本的跟踪调试,不再详细说明。
事实上这里也可以修改代码,把这个判断屏蔽掉,由于正常使用时,一般不会减小系统时间,这个用处不大,没有做这个修改。
至此,破解完全成功。
另:由于30天试用版在功能上是没有任何限制的(30试用版没有license文件,是代码内嵌的,只要从未提供过license文件,就当作试用版处理),所以根据上述步骤7,该程序可以完全不做暴破,只是在提示license到期时把C盘2个目录,以及注册表项清空,就可以再有30天的试用期。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)