好友
阅读权限40
听众
最后登录1970-1-1
|
本帖最后由 lizhirui 于 2018-5-12 14:13 编辑
本次我花费了大约两个半小时破解了一款名为“Serial Port Utility”的软件(又名友善串口调试助手),该软件的串口调试功能比较强大,今天试着分析了一下其序列号算法,这也是我第一次做序列号分析。 注册成功截图如下:
我们首先使用Peid对程序查壳,结果如下:
发现是一个叫做“yoda's Protector v1.02 (.dll,.ocx)-> Ashkbiz Danehkar (h) *”的壳,在网上查询后得知,这个结果有可能是错误的。 于是我们更换了另一个工具“ExeInfoPE”,结果如下:
这个软件的结果显示,这个程序没有壳。
我们从软件目录下看到了大量Qt5开头的动态链接库,证明其软件是用Qt5编写的。
现在用OD加载程序,观察ASCII字符串,我们会注意到这样一个字符串:
双击定位:
其中,经过分析可以得知,eax保存的是Qt独有格式的字符串(输入的序列号字符串),并且偏移地址0x04处是字符串长度,0x0C处是前导说明信息块的字节数,或者说是字符串信息的偏移地址。这一段代码说明,若序列号长度不为0x1D(即29个字符),就会报错。那么我们任意输入一个29字符的字符串,出现以下提示:
然后我们定位到具体字符串,结果如下:
双击定位到代码:
这个代码显然是通过别的地方的代码通过跳转指令跳转过来的(因为上方有一个jmp,顺序执行的流程直接被阻断),我们发现跳转来自004150C0:
je成立的条件是,al=0,而在test al,al上方是一条call语句,我们知道函数的返回值通常通过eax寄存器返回,因此可以推得,al来自于函数00439F30的返回值,我们跟进去:
发现一条call指令,我们跟进去:
可见该函数首先判断长度是否为0x1D(地址0x4399F5处),上方Qt5Core._ZN7QString14trimmed_helperERKS_从名字上就可看出是用于去除字符串两端空白字符的。如果长度不为0x1D,直接返回0(因为004399F3处有一句xor eax,eax等价于mov eax,0)。我们跟着004399F8处的je来到下面:
刚开始的代码都是一些和Qt5内核有关的代码,直接无视,看到下面关键代码:
我们注意到这个循环,我发现Qt的程序有一个风格,就是函数传值的时候,不使用push指令,而是直接通过mov去写堆栈。
这段汇编代码等价于下面的代码:
[C++] 纯文本查看 复制代码 *edi = 0B217808;//Qt字符串对象地址
eax = *edi;
ebx = 0;
esi = 0;
do
{
edx = eax + ebx * 2;//字符串当前字符位置地址计算
edx += *(eax + 0xC);//加上字符串信息起始偏移地址
ecx = 0;
edx = (unsigned short)*edx;
eax = (short)dl;
if(dx > 0xFF)
{
eax = ecx;
}
ecx = *(ebp - 0x5C);
sub_00438680(eax);
esi += eax;//校验和计算
eax = *edi;
ebx++;
ecx = *(eax + 4);
edx = ecx - 2;
}while(edx > ebx)
我们看一下00438680函数:
其中有一个jmp.&libstdc++-6._ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4findEcj引起了我们的注意,从名称上我猜测是char_trait的find函数,经过百度查询如下:
详见:https://msdn.microsoft.com/zh-cn/library/y33bd1yh.aspx
可见,这是一个字符串定位函数,这个call的作用是将在一个字符串中查找某一个字符并返回该字符的索引。
那个字符串经过研究测试是“0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ”。
该函数的作用:如果输入的字符是”-”,那么直接返回0,否则返回索引。
我们回到之前调用该函数的代码,可以发现计算的校验和就是序列号前27个字符对应字符串中的索引之和,除了”-”符号。 我们继续往下看:
作者没有删除代码中的调试信息,里面提供了很多信息,这里提醒我们刚才计算的是校验和,我们将字符串最后两个字符按照上面校验和的算法计算,发现这个数值和刚才的校验和正好相等,说明字符串最后两个字符就是用于校验的。
我们往下可以看到大量的信息,这里只挑选一个字段说明:
其中,00439F95处的eax是字符串内容开始偏移0x0C处(由00439F78处得到的)的文本(注意该字符串是wchar_t*类型),调用了一个函数,地址位于004386B0,我们跟进去看到如下代码:
我们发现该函数和00438680处的函数类似,除了下面多了一条div指令以外,经过分析可以得到,其将会将find函数的返回值对一个参数中指定的值取余数。
我们由00439F7D处的代码,可以看到那个值是9。00439FA0处,把转换后的值保存到了[ebp-0xB0]的位置。
右边的说明提醒我们这个字段是”License Version”,类似的还有”Product Name”,”Product Version”,”Active Mode”,”Language”,”SoftwareType”,”Valid Period”,其中前两个参数的转换后的值分别保存到了[ebp-0xB4]和[ebp-0xB8]处,其它参数的值转换完仅供调试信息输出用。
我们继续跟踪到这里,在0043A8A8行附近,可以在右边寄存器窗口eax处看到输出的调试信息(作者居然忘了删除掉调试信息输出代码,看的一清二楚)。
由0043A8CA到0043A8F2的代码我们可知,只有当”License Version”等于3,”Product Name”等于1并且”ProductVersion”不等于2,函数才会返回1表示成功。
我们去网上搜索了一个序列号:
SA56W-UR34V-7KY76-XB31F-HZPAU
有效期:1年
可以看到序列号的格式类似于这样,并且我们知道以下信息:
Calculate Sum:390
License Version:3
Product Name:1
Product Version:3
Active Mode:1
Language:1
Software Type:2
Valid Period:2
我们开始尝试修改Software Type的值,发现其值等于2时表示个人版,为3时表示专业版。
然后我们尝试修改Valid Period的值,发现其值等于1时表示一个月,2表示一年,3表示3年,4表示5年,5表示终生。
所以最终序列号是这样的:11111-UR311-85111-11111-1112J
其中“UR311”以及”85”必须固定,其它的随便写,校验码算对,这样出来的序列号一定是专业版终生。
软件下载链接:
链接:https://pan.baidu.com/s/1T_zvD2aUjn-xJrL5CNATVA 密码:zyv9
另附分析程序:
[C++] 纯文本查看 复制代码 // Serial Port Utility破解.cpp: 定义控制台应用程序的入口点。
//
#include "stdafx.h"
char *ss = (char *)"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char sn[29];
int GetSSN(char ch)
{
char p[2];
p[1] = 0;
p[0] = ch;
return strstr(ss,p) - ss;
}
int sndiv(int x,int p)
{
return x % p;
}
int main()
{
char *str = (char *)"SA56W-UR34V-7KY76-XB31F-HZPAU";
//06 - License Version [ebp - 0xB0] = 3 /9
//07 - Product Name [ebp - 0xB4] = 1 /2
//08 - Product Version [ebp - 0xB8] != 2 /5
//09 - Active Mode /3
//0A - Language /5
//0C - Software Type /5
//0D - Valid Period /6
//char *str = (char *)"11111-UR311-81111-11111-1112F";
int i;
int sum = 0;
for(i = 0;i < 29;i++)
{
if(str[i] != '-')
{
sn[i] = GetSSN(str[i]);
}
else
{
sn[i] = 0;
}
}
for(i = 0;i < 27;i++)
{
sum += sn[i];
}
printf("Calculate Sum:%d\n",sum);
printf("License Version:%d\nProduct Name:%d\nProduct Version:%d\nActive Mode:%d\nLanguage:%d\nSoftware Type:%d\nValid Period:%d\n",sndiv(sn[0x06],9),sndiv(sn[0x07],2),sndiv(sn[0x08],5),sndiv(sn[0x09],3),sndiv(sn[0x0A],5),sndiv(sn[0x0C],5),sndiv(sn[0x0D],6));
return 0;
}
|
免费评分
-
查看全部评分
|
发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。 |
|
|
|
|