【破文标题】Matlab2007b PLP 之不完全分析
【破文作者】Ptero
【破解工具】Peid, Ollydbg, IDA, DJ Java Decomplier
【注册方式】序列号
【加壳方式】无
【加密算法】变形 RC5 + 自定义
【软件限制】安装限制
【软件简介】不介绍了,很强大的工具。用过的都知道。想看的话去这里吧:http://www.mathworks.cn/products/new_products/latest_features.html
【破解难度】中等
----------
【破解分析】
Mathworks 在9月份推出了 Matlab 的最新版本 Matlab2007b,在网上找了好久,终于用BT拉了回来。可是没有PLP,安装不了。眼看着最新版本就不能用吗?自己动手,丰衣足食!
下载到的文件是个rar压缩包,将其全部解压到C盘,安装文件在这里:C:\Matlab 2007b Full Release (no keygen)\win32\setup.exe。用Peid看一下,没有加壳。
软件是用java编译的。软件启动的时候把 C:\Matlab 2007b Full Release (no keygen)\win32\bin\win32\ 目录和 C:\Matlab 2007b Full Release (no keygen)\win32\java\ 目录复制到Temp文件夹,然后就开始调用java的函数了。
因为是第一次接触java编译的东西,还不熟悉,在这里耽搁了很长时间之后,终于找到了启动的文件:
C:\Matlab 2007b Full Release (no keygen)\win32\java\jre\win32\jre\lib\ext\installer.jar.
将其解压,反编译其中的licenseUtil.class,找到了关键部分:
static boolean processPLP()
{
String s = getPLP();
if(s.length() == 0)
{
String s1 = myRes.getString("plpalert.title");
String s2 = myRes.getString("plpalert.message");
WIOptionPane.show(Installer.getInstance(), s2, s1, 0, -1);
return false;
}
int ai[] = new int[250];
for(int j = 0; j < 250; j++)
ai[j] = 0;
int i = mwinstall.DecipherPLP(plp, ai);
String s3 = myRes.getString("plperroralert.title");
if(i != 0)
{
String s4 = myRes.getString("plperroralert.incorrect");
WIOptionPane.show(Installer.getInstance(), s4, s3, 0, -1);
return false;
}
int k = mwinstall.getCDVolNbr();
if(k < util.getPasscodeVersionNumber())
{
String s5 = myRes.getString("plperroralert.release");
WIOptionPane.show(Installer.getInstance(), s5, s3, 0, -1);
return false;
}
if(mwinstall.CheckExpDate() != 0)
{
String s6 = myRes.getString("plperroralert.expired");
WIOptionPane.show(Installer.getInstance(), s6, s3, 0, -1);
return false;
}
if(!isDemo())
{
int l = ai[0];
ai[0] = getNextProduct();
ai[0] = l;
}
PLPArray = ai;
ProductContainer productcontainer = Installer.getProductContainer();
productcontainer.resetSelection();
int ai1[] = PLPArray;
int i1 = ai1.length;
for(int j1 = 0; j1 < i1; j1++)
{
int k1 = ai1[j1];
ArrayList arraylist = productcontainer.getProductsByProductNumber(k1);
MWProduct mwproduct;
for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); mwproduct.setLicenseNumber(getLicenseNumber()))
mwproduct = (MWProduct)iterator.next();
}
if(Installer.isDws() && !isDemo() && !isStudent() && !util.getUpdate())
DownloadServiceWrapper.checkForUpdates();
return true;
}
代码没有被混淆过,一目了然。
关键的判断PLP的函数在这里:
mwinstall.DecipherPLP(plp, ai)。
当然这里可以爆破的:修改代码,绕过所有的验证,然后再编译回去,这样就可以输入任何 PLP 进行安装了。但是这样做,不知道程序安装之后还会不会再次验证 PLP。万一再次验证,那样跟踪起来就没现在看着源代码分析起来这么方便了。还是在这里用釜底抽薪的办法,得出 PLP 的好。这样就不用对原程序进行任何修改了,而且也不用担心安装以后的再次验证。一劳永逸。
下面反编译 mwinstall.class,看到这样一句:
static native int DecipherPLP(String s, int ai[]);
这个重要函数被编译成native方式了。这样就看不到源代码啦。不过没关系,我们有OD呢。
用 OD 载入setup.exe,按 F9 运行。
出现输入 PLP 的对话框之后,在 OD 里面按 Alt+E,然后选择 mwinstall 模块。按 Ctrl+N 查找当前模块的所有标签,在输出表里面找到Java_com_mathworks_installer_mwinstall_DecipherPLP 函数,按 F2 下断点,然后回到安装窗口输入PLP,断下。
找到 C:\Matlab 2007b Full Release (no keygen)\win32\bin\win32\mwinstall.dll 文件,发现没有加壳,于是用 IDA 分析。OD+IDA,动静结合。
下面的代码来自 IDA:
……
……
.text:10008AAF push edi
.text:10008AB0 push 0
.text:10008AB2 push ebx
.text:10008AB3 push esi
.text:10008AB4 call ecx ; 获得输入的 PLP
.text:10008AB6 lea edx, [esp+10h]
.text:10008ABA mov edi, eax
.text:10008ABC push edx
.text:10008ABD push edi
.text:10008ABE call sub_10008A00 ; 这个就是关键 Call 啦 按 F7 跟进:
……
……
.text:10008A39 sub eax, edx
.text:10008A3B push 1 ; size_t
.text:10008A3D push eax ; size_t
.text:10008A3E call _calloc
.text:10008A3E
.text:10008A43 mov edi, eax
.text:10008A45 push edi
.text:10008A46 push esi
.text:10008A47 call sub_100094F0 ; 把输入的 PLP 去掉前 2 位,去掉连字符,把每个用连字符连接的数字转换成 16 进制
按 F7 跟进:
……
……
.text:10007F1A push ebp
.text:10007F1B lea edi, [esp+234h+var_1F8]
.text:10007F1F call sub_10009030 ; 初始化 RC5 密钥
按 F7 跟进:
……
……
.text:10009085 push eax
.text:10009086 push ebx ; 字符串 "Copyright (C) 1990-2003 by The MathWorks, Inc."
.text:10009087 call sub_10008E60 ; 初始化 RC5 密钥
按 F7 跟进,看看密钥的初始化:
……
……
.text:10008E83 mov word ptr [esp+18h+var_8], 6Fh ; 字母 'o'
……
……
.text:10008ED0 lea eax, [esp+18h+var_8]
.text:10008ED4 mov ecx, eax
……
……
.text:10008EE3 mov esi, ecx
……
……
.text:10008EF2 mov ecx, eax
.text:10008EF4 shr ecx, 2
.text:10008EF7 rep movsd
这些代码的作用就是在原来字符串的末尾加上一个 'o',这样,就得到了密钥字符串:
"Copyright (C) 1990-2003 by The MathWorks, Inc.o"
加上末尾的0,一共是48字节。
……
……
.text:10008F58 mov dword ptr [eax], 0B7E15163h ; RC5的初始常数之一
……
……
.text:10008F72 mov esi, [ebp+0]
.text:10008F75 mov edi, [esi+eax*4-4]
.text:10008F79 lea esi, [esi+eax*4]
.text:10008F7C sub edi, 61C88647h ; 就是加上0x9E3779B9,RC5的另一个初始常数
.text:10008F82 add eax, 1
.text:10008F85 cmp eax, ecx
.text:10008F87 mov [esi], edi
.text:10008F89 jl short loc_10008F72
上面这个循环初始化一个常数数组。
接着,把数组与初始密钥混合:
计算循环次数:
……
……
.text:10008F8F cmp esi, ecx
.text:10008F91 lea eax, [esi+esi*2]
.text:10008F94 jg short loc_10008F99
.text:10008F94
.text:10008F96 lea eax, [ecx+ecx*2]
初始化一些值:
……
……
.text:10008F99 xor edi, edi ; B=0
.text:10008F9B xor esi, esi ; A=0
.text:10008F9D xor ebx, ebx ; j=0
下面正式进入循环:
.text:10008FB0 mov ecx, [esp+18h+arg_0]
.text:10008FB0
.text:10008FB4 mov eax, ebx
.text:10008FB6 cdq
.text:10008FB7 idiv ecx ; i=(i+1) mod 2(r+1)
.text:10008FB9 mov ecx, [ebp+0]
.text:10008FBC add edx, edx
.text:10008FBE add edx, edx
.text:10008FC0 lea eax, [ecx+edx]
.text:10008FC3 lea ecx, [edi+esi]
.text:10008FC6 add ecx, [eax]
.text:10008FC8 rol ecx, 3 ; A=S[i]=(S[i]+A+B)<<<3
.text:10008FCB mov [eax], ecx
.text:10008FCD mov eax, [ebp+0]
.text:10008FD0 mov esi, [edx+eax]
.text:10008FD3 mov eax, ebx
.text:10008FD5 cdq
.text:10008FD6 idiv [esp+18h+arg_4] ; j=(j+1) mod c
.text:10008FDA mov ecx, [esp+18h+var_8]
.text:10008FDE lea eax, [edi+esi]
.text:10008FE1 add eax, [ecx+edx*4]
.text:10008FE4 add ebx, 1
.text:10008FE7 lea edx, [ecx+edx*4]
.text:10008FEA lea ecx, [edi+esi]
.text:10008FED and ecx, 1Fh
.text:10008FF0 rol eax, cl ; B=L[j]=(L[j]+A+B)<<<((A+B)&0x1F)
.text:10008FF2 cmp ebx, [esp+18h+arg_8] ; 循环3*max(2(r+1),c)次
.text:10008FF6 mov [edx], eax
.text:10008FF8 mov edi, eax
.text:10008FFA jl short loc_10008FB0
至此,密钥初始化完毕。
结合 OD 跟踪,可以得到 RC5 所用的参数:
分组长度 2w=64
叠代层数 r=10
密钥长度 b=48
因此,所用的算法是 RC5 - w=32/r=10/b=48
下面就是混合后的48字节密钥:
43 6F 70 79 72 69 67 68 74 20 28 43 29 20 31 39 39 30 2D 32 30 30 33 20 62 79 20 54 68 65 20 4D 61 74 68 57 6F 72 6B 73 2C 20 49 6E 63 2E 6F 00
产生密钥以后,来到这里解密:
.text:100090E0 movzx eax, byte ptr [edi]
.text:100090E3 movzx ecx, byte ptr [esi-2]
.text:100090E7 movzx edx, byte ptr [esi-1]
.text:100090EB shl eax, 8
.text:100090EE add eax, ecx
.text:100090F0 movzx ecx, byte ptr [esi]
.text:100090F3 shl eax, 8
.text:100090F6 add eax, edx
.text:100090F8 movzx edx, byte ptr [esi+3]
.text:100090FC shl eax, 8
.text:100090FF add eax, ecx
.text:10009101 movzx ecx, byte ptr [esi+2]
.text:10009105 mov [esp+18h+var_8], eax
.text:10009109 movzx eax, byte ptr [esi+1]
.text:1000910D shl eax, 8
.text:10009110 add eax, ecx
.text:10009112 movzx ecx, byte ptr [esi+4]
.text:10009116 shl eax, 8
.text:10009119 add eax, edx
.text:1000911B push 1
.text:1000911D lea edx, [esp+1Ch+var_8]
.text:10009121 shl eax, 8
.text:10009124 push edx
.text:10009125 add eax, ecx
.text:10009127 push ebp
.text:10009128 mov [esp+24h+var_4], eax
.text:1000912C call sub_10008DD0 ; RC5 解密函数
.text:1000912C
.text:10009131 mov eax, [esp+24h+var_8]
.text:10009135 mov [esi], al
.text:10009137 shr eax, 8
.text:1000913A mov [esi-1], al
.text:1000913D shr eax, 8
.text:10009140 mov [esi-2], al
.text:10009143 shr eax, 8
.text:10009146 mov [edi], al
.text:10009148 mov eax, [esp+24h+var_4]
.text:1000914C mov [esi+4], al
.text:1000914F shr eax, 8
.text:10009152 mov [esi+3], al
.text:10009155 shr eax, 8
.text:10009158 mov [esi+2], al
.text:1000915B shr eax, 8
.text:1000915E mov [esi+1], al
.text:10009161 add esp, 0Ch
.text:10009164 sub edi, 1
.text:10009167 sub esi, 1
.text:1000916A sub ebx, 1
.text:1000916D jnz loc_100090E0 注意!这里不是直接用 RC5 解密,而是循环从输入字符串的最后一个分组开始,然后逐步往钱移动一个字节,再解密,一共循环 (输入字符串长度-8)次。相应的,加密过程就是从第一个分组开始,循环(输入字符串长度-8)次调用 RC5 加密算法。
解密过程:
for(i=sizeof(input)-8; i>=0; i--)
{
A=makedword(input[i+3],input[i+2],input[i+1],input[i]);
B=makedword(input[i+7],input[i+6],input[i+5],input[i+4]);
RC5 解密(A, B);
把A,B字节颠倒,然后放回原来的数组;
}
相应地,加密过程就是:
for(i=0; i<=sizeof(input)-8; i++)
{
A=makedword(input[i+3],input[i+2],input[i+1],input[i]);
B=makedword(input[i+7],input[i+6],input[i+5],input[i+4]);
RC5 加密(A, B);
把A,B字节颠倒,然后放回原来的数组;
}
例如,
E3 5C 99 54 1D D2 2D 32 41 D1 24 EF 59 19 15 45 35 30 71 82 20 54 1A E5 2F B7 A4 03 55 86 EC 07 E1 16 48 D7 E4 2B 5E AE 4F DB E6 93 1C 29 6C 3A 6E 84 39 11 3C 21 BC A5 32 EC 30 05 AA C1 E2 14 A9 62
解密之后就变成:
42 09 E2 2D 33 00 FF FE 11 0C 85 31 D0 95 2D 8D 73 E1 19 4E 95 B5 F1 9D 6F 9D F7 C2 21 90 A6 3A 12 A5 B1 AE 7C 23 29 D2 B6 BE 33 AD F3 BE F8 44 32 14 C7 42 54 B6 35 CF 84 65 3A 56 D7 C6 75 BE 77 DF
用 RC5 解密完之后来到这里:
.text:10007FA7 push edx
.text:10007FA8 push esi
.text:10007FA9 call sub_10007A40
按 F7 跟进:
.text:10007A40 sub esp, 0Ch
.text:10007A43 push esi
.text:10007A44 push edi
.text:10007A45 mov edi, [esp+14h+arg_4]
.text:10007A49 push edi
.text:10007A4A call sub_10008D50 ; 取解密后字串的前两个字节
.text:10007A4A
.text:10007A4F movzx eax, ax
.text:10007A52 mov esi, [esp+18h+arg_0]
.text:10007A56 movzx ecx, ax
.text:10007A59 mov [esp+18h+var_C], eax
.text:10007A5D mov edx, ecx
.text:10007A5F and edx, 0FFh
.text:10007A65 mov eax, 55555556h
.text:10007A6A imul edx
.text:10007A6C mov eax, edx
.text:10007A6E shr eax, 1Fh
.text:10007A71 lea eax, [edx+eax+0Bh]
.text:10007A75 add esp, 4
.text:10007A78 cmp eax, 0Eh ; 这个就是版本号啦,版本号小于 0x0E 就失败啦
.text:10007A7B mov [esi+8], eax
.text:10007A7E jge short loc_10007A8B
看看版本号是怎么来的:
解密后字串的第2个字节×0x55555556+0x0B>=0x0E
一看就知道是编译器优化过的代码。原始代码是这样的:
解密后字串的第2个字节×5/0x0F+0x0B>=0x0E
解这个不等式,得到:
解密后字串的第2个字节 >= 0x09
我的解密字符串是
42 09 E2 2D …………
第2个字节是 0x09,满足这个条件的。
接下来,这个 call 里面还验证了好多东西。由于时间原因,我没有仔细跟踪算法,所以现在暂时还不能写出注册机。
不过,有一个巧妙的方法可以获得 PLP:这里我用了个投机取巧的方法,我借用了 Matlab2007a 的 PLP,幸运地通过了这里的所有验证。
这样,mwinstall.DecipherPLP 这个函数就返回 TRUE 了。
问题是,Matlab2007a 的 PLP 无法用在 Matlab2007b 上面,原因在于 mwinstall.getCDVolNbr() 函数,检测输入 PLP 的版本。版本不对的话,也不能安装。
下面看看 C:\com\mathworks\installer\util.class 里面的 getPasscodeVersionNumber() 函数:
static int getPasscodeVersionNumber()
{
return Integer.parseInt("18");
}
原来是返回一个固定的常数 18。只要版本号大于 18 就可以了。
那么,只要解这个不等式就可以了:
5x/0x0F+0x0B>=0x18
解得
x >= 0x15
这就是版本号的来源。
这样,就顺利通过了所有验证。
顺便说一下获得获得 PLP 的思路:
旧版 PLP → 循环 RC5 解密 → 修改版本号 → 循环 RC5 加密 → 新版 PLP
举例:
旧版 PLP:
14-58204-39252-07634-11570-16849-09455-22809-05445-13616-29058-08276-06885-12215-41987-21894-60423-57622-18647-58411-24238-20443-59027-07209-27706-28292-14609-15393-48293-13036-12293-43713-57876-43362
↓
转换成 16 进制:
E3 5C 99 54 1D D2 2D 32 41 D1 24 EF 59 19 15 45 35 30 71 82 20 54 1A E5 2F B7 A4 03 55 86 EC 07 E1 16 48 D7 E4 2B 5E AE 4F DB E6 93 1C 29 6C 3A 6E 84 39 11 3C 21 BC A5 32 EC 30 05 AA C1 E2 14 A9 62
↓
循环 RC5 解密之后:
42 09 E2 2D 33 00 FF FE 11 0C 85 31 D0 95 2D 8D 73 E1 19 4E 95 B5 F1 9D 6F 9D F7 C2 21 90 A6 3A 12 A5 B1 AE 7C 23 29 D2 B6 BE 33 AD F3 BE F8 44 32 14 C7 42 54 B6 35 CF 84 65 3A 56 D7 C6 75 BE 77 DF
↓
修改版本号:
42 15 E2 2D 33 00 FF FE 11 0C 85 31 D0 95 2D 8D 73 E1 19 4E 95 B5 F1 9D 6F 9D F7 C2 21 90 A6 3A 12 A5 B1 AE 7C 23 29 D2 B6 BE 33 AD F3 BE F8 44 32 14 C7 42 54 B6 35 CF 84 65 3A 56 D7 C6 75 BE 77 DF
↓
循环 RC5 加密之后:
AE 29 97 73 A0 6F E3 3D D6 EA DF 55 12 DD 48 F1 0C A7 8C AB F0 EA C9 EB B5 ED 0B EE B5 31 CB 75 1B 0E 5A F4 42 1D B7 56 05 FA 4C F0 6B B8 66 C1 32 C8 C9 31 C3 24 96 59 DC 3A B3 1D 79 97 48 1D 26 57
↓
新版 PLP:
15-44585-38771-41071-58173-55018-57173-04829-18673-03239-36011-61674-51691-46573-03054-46385-52085-06926-23284-16925-46934-01530-19696-27576-26305-13000-51505-49956-38489-56378-45853-31127-18461-09815
这样,借用了旧版 PLP,就不用细细跟踪 PLP 的验证过程了。这样生成的 PLP 可以顺利通过验证。
后记:由于时间原因,没有仔细跟踪 RC5 解密以后的算法,破解过程也没有详细写。见谅!
其实 RC5 我也是现学的。跟 Rijndael 什么的比起来,RC5 算是个比较简单的算法了。共享一点 RC5/RC6 的资料,大家共同学习。高手请飘过。呵呵。
【版权声明】 本文纯属技术交流, 转载请注明作者并保持文章的完整, 谢谢!
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: