首页
社区
课程
招聘
[原创]一个CrackMe,简单算法,请您给出一个可用Serial(第四个CM)
发表于: 2010-3-1 11:09 6763

[原创]一个CrackMe,简单算法,请您给出一个可用Serial(第四个CM)

2010-3-1 11:09
6763
纯简单算法保护的一个CM,爆破的请您走开。
我会在一周之后给出CM源码和注册机源码实现。
其实这个CM跟逆向不怎么沾边,跟实际问题的数学分析有莫大的关系。

看谁最先给出一个可用Serial。。。
由于Serial之间有关联性,这里就先不给出可用Serial了

4楼和12楼 分别是 柳痕.blueapplez.KK的研究结果,不嫌麻烦就看看吧哈哈
顺便贴上CM伪源码
伪代码如下:(TRUE表示注册成功,FALSE注册失败)
---------------------------------------------------------------------------------------
char szSerial[100] = {0};
~@!#¥%……&           //这一句是取得界面上的Serial
if(strlen(szSerial) != 20)
return FALSE;
DWORD *p = (DWORD*)szSerial;
DWORD dwValue = 0;
for(int i=1; i<=5; i++)
{
dwValue += *p*i;  p++;
}
if(dwValue == 0xCD8C7262)
return TRUE;
return FALSE;

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (14)
雪    币: 314
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
唉,不得要领,凑了好一会得到一组:
1200QABBRKRAAQQBQQRB
2010-3-1 14:54
0
雪    币: 458
活跃值: (421)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
3
这个CM的意义就是凑的过程  呵呵  然后编程实现  
大体说下凑的过程吧  我很想知道   
我开始拿到这个题目也是凑出来的 后来就想能不能编程实现
2010-3-1 15:01
0
雪    币: 314
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
算法很简单,注册码20位,四位一组构成一个DWORD,设从左到右0~3位为a,4~7位为b,……16~19位为e,则要求(a + 2b + 3c + 4d + 5e) mod 0xFFFFFFFF = 0xCD8C7262
认为注册码只由字母和数字构成,把上面a b c d e全部减去0x30303030,得到U V W X Y
于是[(U+0x30303030) + (V + 0x30303030) * 2 + ... ] mod 0xFFFFFFFF
= {[(U + V*2 + ...) mod 0xFFFFFFFF] + (0x30303030*15) mod 0xFFFFFFFF} mod 0xFFFFFFFF
= [(U + 2V + ... ) mod 0xFFFFFFFF + 0xD2D2D2D0] mod 0xFFFFFFFF = 0xCD8C7262
由于U V W X Y的最大取值为0x4A4A4A4A,计算0x4A4A4A4A*15+0xD2D2D2D0 = 52D2D2D26,结合上面mod 0xFFFFFFFF =  0xCD8C7262,可知[(U + 2V + ... ) mod 0xFFFFFFFF + 0xD2D2D2D0]的取值只能为0x1CD8C7262 0x2CD8C7262 0x3CD8C7262 0x4CD8C7262
于是分别计算,我只计算了第一个,即[(U + 2V + ... ) mod 0xFFFFFFFF + 0xD2D2D2D0 = 0x1CD87262,即(U + 2V + ... ) mod 0xFFFFFFFF = 0xFAB99F92
结合0xFAB99F92这个数,然后再把U先撇开(即认为U=0x00000000,因为它没有系数,方便配合调整),将V W X Y再减去0x11111111,即认为它们都是字母,计为 v w x y
得到2v + 3w + 4x + 5y = 0xBCAB0A4,很幸运,每一位都小于E,这样就不用考虑进位了,然后逐位凑,比如最高位B = E - 3,那么w的最高位为0,其它最高位为1,这样得到一组结果,再把前面减去的数加上,就得到最后注册码了。

我上面给出的注册码是之前计算时算错了,mod 0xFFFFFFFF的结果是0xCD8C8262,然后就在OD里调试同时去调整相应的位,得出来的
2010-3-1 15:19
0
雪    币: 2362
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
不会算...
2010-3-1 15:36
0
雪    币: 458
活跃值: (421)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
6
认为注册码只由字母和数字构成,把上面a b c d e全部减去0x30303030,得到U V W X Y

这句看不懂  这个U V W X Y到底咋得到的? abcde都的未知的
2010-3-1 17:09
0
雪    币: 314
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
a b c d e是输入的注册码四位一组组成DWORD数
U = a - 0x30303030
比如我注册码前四位是1234,那a = 0x34333231,U = 0x04030201
可能我表达得不清楚…………
当时这么弄的想法,主要是能把后面凑的过程弄得简单点,另外也使得凑出来的注册码可以仅由数字和字母组成,实际计算时我是先根据
2v + 3w + 4x + 5y = 0xBCAB0A4
凑出了v w x y,然后将它们加上0x11111111即得到V W X Y,而U的值是0,
最后再把U V W X Y的值加上0x30303030,转换成对应的字符,得到注册码的
2010-3-1 17:33
0
雪    币: 458
活跃值: (421)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
8
可知[(U + 2V + ... ) mod 0xFFFFFFFF + 0xD2D2D2D0]的取值只能为0x1CD87262 0x2CD87262 0x3CD87262 0x4CD87262
于是分别计算,我只计算了第一个,即[(U + 2V + ... ) mod 0xFFFFFFFF + 0xD2D2D2D0 = 0x1CD87262,即(U + 2V + ... ) mod 0xFFFFFFFF = 0xFAB99F92

这俩红色地方真的看不懂   那个0x4a4a4a4a好不容易看懂是因为字母和数字
另外我认为   mod 0xFFFFFFFF =  0xFAB99F92  这地方应该是
mod 0xFFFFFFFF =  0x4a059f92
2010-3-1 21:25
0
雪    币: 314
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
实在抱歉,仔细看了下发现我上面打错了,0x1CD87262 应该是0x1CD8C7262,漏了8后面的那个C
2010-3-1 21:39
0
雪    币: 76
活跃值: (27)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
强大!~~~~~顶
2010-3-3 00:50
0
雪    币: 689
活跃值: (23)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
拜读了..柳痕很强大..
2010-3-6 15:04
0
雪    币: 458
活跃值: (421)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
12
分析:由于算法太过简单,所以最先找到的方法就是暴力查找。那么,是写一个五层循环来实现吗?
五层循环就是160bit的长度,这样写代码的话 估计在普通的PC机器上跑10天半个月的有可能得到一个可以Key。。。所以这种方法pass掉!所以暂时想到以下5种方法:
①手动找一个可用key
②暴力查找一
③暴力查找二
④随机数方法
⑤从大数运算得来的灵感,模拟手动查找
下面分别用代码来实现我的五种方法
---------------------------------------------方法①手动找一个可用key------------------------------------------------
思路分析:先把最后12byte定死,然后用0xCD8C7262 减去最后12位的累加得到一个数dw,然后用这个数除以3,
这样就能得到了一个数data满足 data+2*data + temp = dw (temp的存在是因为存在一个不能整除的问题,temp = 0,1,2),令data1 = data + temp,data2 = data,这样就得到俩数 满足data1 + 2*data2 = dw
然后就开始手动调整了 下面看代码 (可能你会有一个疑问说你把后面的定死了 那万一无论如何调整前面的都不能满足呢?其实这里应该是不存在这种不满足的情况(这里我没有进行证明,这句话可争议), 因为倍数关系是2,而且可视字符范围是0x20~0x7e  通过数学应该可以证明,这里就先不讨论了)
//可见字符  0x20~0x7e
void CMyDlg::OnOK()
{
DWORD dwCount = 0xCD8C7262;
char szPartSerial[21] = {"12345678()blueapplez"}; //把最后12byte定死,前8byte是乱填的
DWORD *p = (DWORD*)szPartSerial;

//通过运算得到data1 = 0x20f7164e;   data2 = 0x20f7164c,运算过程请看思路分析
//通过高亮显示,希望您能看懂我的手动操作过程
//////////////////////////////////////////////////////////////////////////////////////////////
//     DWORD dwFirst =  0x20f7164e;   /*0x20f7164c+2; */       -12 ->0xc
//     DWORD dwSecond = 0x20f7164c;  /*0x20f7164c; */
//     DWORD dw = dwFirst + dwSecond*2;  /*0x62e542e6*/        +6

//////////////////////////////////////////////////////////////////////////////////////////////
//     DWORD dwFirst =  0x2237164e;   /*0x20f7164c+2; */       -12 ->0xc
//     DWORD dwSecond = 0x2057164c;  /*0x20f7164c; */          +6
//     DWORD dw = dwFirst + dwSecond*2;  /*0x62e542e6*/

//////////////////////////////////////////////////////////////////////////////////////////////
DWORD dwFirst =  0x2236564e;   /*0x20f7164c+2; */      
DWORD dwSecond = 0x2057764c;  /*0x20f7164c; */         
DWORD dw = dwFirst + dwSecond*2;  /*0x62e542e6*/

*p = dwFirst;
*(p+1) = dwSecond;
dw = 0;
for (int i=0; i<5; i++)
{
dw += *(p+i)*(i+1);
}
if(dw == 0xCD8C7262)
::SetDlgItemText(m_hWnd, IDC_EDIT1, szPartSerial);
}
得到了这组可用key:NV6"LvW ()blueapplez

--------------------------------------------方法②暴力查找法一------------------------------------------------
思路分析:既然5层循环的暴力不可取,那么我们就用一个2层的循环,64bit的暴力查找是否可取呢?(方法还是先把后面的12byte定死!这样循环起来运算量就小多了)
通过实践证明 这个方法同样不可取!因为在我的机器上跑了3分钟仍然没有得到结果!
//判断一个DWORD的数据内容拆成4个单字符是否都是可见字符
BOOL IsDWORDVisible(DWORD dw)
{
DWORD dwTemp = dw;
BYTE *bTemp = (BYTE *)&dwTemp;
for (int i=0; i<4; i++)
{
if (bTemp[i]<0x20 || bTemp[i] > 0x7e)
{
return FALSE;
}
}
return TRUE;
}
BOOL CMyDlg::SetBeVisible(DWORD *dw1, DWORD *dw2)
{
DWORD dwCount = *dw1 + *dw2*2;
for (DWORD dwTemp1 = 0x20202020; dwTemp1<=0x7e7e7e7e; dwTemp1++ )
{
for (DWORD dwTemp2 = 0x20202020; dwTemp2<=0x7e7e7e7e; dwTemp2++)
{
if (dwTemp2*2 + dwTemp1 == dwCount &&IsDWORDVisible(dwTemp1)&&IsDWORDVisible(dwTemp2))
{
*dw1 = dwTemp1;
*dw2 = dwTemp2;
return TRUE;
}
}
}

::MessageBoxA(NULL, "没有找到,请重新试一下", NULL, NULL);
return FALSE;
}

//0x20~0x7e
void CMyDlg::OnOK()
{
DWORD dwCountBeConst = 0xCD8C7262;
DWORD dwCountTemp = 0xCD8C7262;

char szPartSerial[21] = {0};
for (int i=8; i<20; i++)
{
szPartSerial[i] = (char)(rand()%(0x7e-0x20+1) + 0x20);
}
DWORD *p = (DWORD*)szPartSerial;
for (i=3; i<=5; i++)
{
dwCountTemp -= p[i-1]*i;
}
DWORD dwFirst = dwCountTemp/3;
DWORD dwSecond = dwFirst;
if ((dwFirst + dwSecond*2) != dwCountTemp)
{
if ((dwFirst + dwSecond*2 + 1) != dwCountTemp)
{
dwFirst += 2;
}
else
{
dwFirst++;
}
}
ASSERT(dwFirst+dwSecond*2 == dwCountTemp);
SetBeVisible(&dwFirst, &dwSecond);   

*p = dwFirst;
*(p+1) = dwSecond;
dwCountTemp = 0;
for (i=0; i<5; i++)
{
dwCountTemp += *(p+i)*(i+1);
}
if(dwCountTemp == dwCountBeConst)
::SetDlgItemText(m_hWnd, IDC_EDIT1, szPartSerial);
}
--------------------------------------------方法③暴力查找法二-----------------------------------------------
思路分析:既然2层循环的暴力查找同样不可取,那么我就想到了另一种暴力查找,下面看SetBeVisible函数的实现,如果你认为这种方法和第一种暴力的相同的话那你就错了!这种暴力比第一种暴力节省一半的时间,但是实践证明,依然不可取。。。
BOOL SetBeVisible(DWORD *dw1, DWORD *dw2)
{
DWORD dwCount = *dw1 + *dw2*2;
DWORD dwTemp1 = *dw1;
DWORD dwTemp2 = *dw2;
//这是一个8层循环,每层循环0x7e - 0x20次
BYTE b[8] = {0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20};
for (b[0] = 0x20; b[0]<=0x7e; b[0]++)
{
for (b[1] = 0x20; b[1]<=0x7e; b[1]++)
{
for (b[2] = 0x20; b[2]<=0x7e; b[2]++)
{
for (b[3] = 0x20; b[3]<=0x7e; b[3]++)
{
for (b[4] = 0x20 ;b[4]<=0x7e; b[4]++)
{
for (b[5] = 0x20 ;b[5]<=0x7e; b[5]++)
{
for (b[6] = 0x20 ;b[6]<=0x7e; b[6]++)
{
for (b[7] = 0x20 ;b[7]<=0x7e; b[7]++)
{
*dw1 = *((DWORD*)b);
*dw2 = *((DWORD*)(b+4));
if (dwCount == *dw1 + *dw2*2)
{
return TRUE;
}
}
}
}
}
}
}
}

}
::MessageBoxA(NULL, "没有找到,请重新试一下", NULL, NULL);
return FALSE;
}
--------------------------------------------方法④随机数方法-----------------------------------------------
思路分析:记得以前看过一个RSA的算法 里面用到大素数,就用了一种类似的随机数验证的方法
下面看代码,事实证明依然不可取,且看第五种方法,哈哈哈
#include "stdlib.h"
#include "time.h"
BOOL SetBeVisible(DWORD *dw1, DWORD *dw2)
{
srand((unsigned int)time(NULL));
DWORD dwCount = *dw1 + *dw2*2;
DWORD dwTemp1 = *dw1;
DWORD dwTemp2 = *dw2;
BYTE b[8] = {0};
while (1)
{
for (int i=0; i<8; i++)
{
b[i] = rand()%(0x7e-0x20 + 1) + 0x20;
}
*dw1 = *((DWORD*)b);
*dw2 = *((DWORD*)(b+4));
if (dwCount == *dw1 + *dw2*2)
{
return TRUE;
}
}
::MessageBoxA(NULL, "没有找到,请重新试一下", NULL, NULL);
return FALSE;
}
--------------------------------方法⑤从大数运算得来的灵感,模拟手动查找----------------------------------------
思路分析:这个比较难讲明白思路,还是直接代码吧,感觉不像是模拟手动查找了 呵呵  但是这种方法是所以我想到的方法里面唯一可取的一个批量产生注册码的实现,为了简单起见  我用暴力循环16bit  当然有更好的实现方法  这里我就不去优化了 (一般在1秒之内能算出一个注册码,但也有些时候需要5~6秒时间)
BOOL IsWORDVisible(WORD dw)
{
WORD dwTemp = dw;
BYTE *bTemp = (BYTE *)&dwTemp;
for (int i=0; i<2; i++)
{
if (bTemp[i]<0x20 || bTemp[i] > 0x7e)
{
return FALSE;
}
}
return TRUE;
}

BOOL SetBeVisible(DWORD *dw1, DWORD *dw2)
{
DWORD dwTemp1 = *dw1;
DWORD dwTemp2 = *dw2;
WORD *b1 = (WORD *)((DWORD)(&dwTemp1) + 2);
WORD *b2 = (WORD *)((DWORD)(&dwTemp2) + 2);
for (*b1 = 0x2020; *b1<=0x7e7e; *b1 = *b1 + 1)
{
for (*b2 = 0x2020; *b2<=0x7e7e; *b2 = *b2 + 1)
{   
if (IsWORDVisible(*b1) && IsWORDVisible(*b2) && (dwTemp1 + 2*dwTemp2 == *dw1 + *dw2*2))
{
*dw1 = dwTemp1;
*dw2 = dwTemp2;
goto biaoji1;
}
}   
}
biaoji1:
dwTemp1 = *dw1;
dwTemp2 = *dw2;
b1 = (WORD *)((DWORD)(&dwTemp1) + 1);
b2 = (WORD *)((DWORD)(&dwTemp2) + 1);
for (*b1 = 0x2020; *b1<=0x7e7e; *b1 = *b1 + 1)
{
for (*b2 = 0x2020; *b2<=0x7e7e; *b2 = *b2 + 1)
{   
if (IsWORDVisible(*b1) && IsWORDVisible(*b2) && (dwTemp1 + 2*dwTemp2 == *dw1 + *dw2*2))
{
*dw1 = dwTemp1;
*dw2 = dwTemp2;
goto biaoji2;
}
}   
}
biaoji2:
dwTemp1 = *dw1;
dwTemp2 = *dw2;
b1 = (WORD *)((DWORD)(&dwTemp1) + 0);
b2 = (WORD *)((DWORD)(&dwTemp2) + 0);
for (*b1 = 0x2020; *b1<=0x7e7e; *b1 = *b1 + 1)
{
for (*b2 = 0x2020; *b2<=0x7e7e; *b2 = *b2 + 1)
{   
if (IsWORDVisible(*b1) && IsWORDVisible(*b2) && (dwTemp1 + 2*dwTemp2 == *dw1 + *dw2*2))
{
*dw1 = dwTemp1;
*dw2 = dwTemp2;
goto biaoji3;
}
}   
}
biaoji3:
dwTemp1 = *dw1;
dwTemp2 = *dw2;
BYTE *bb1 = (BYTE *)((DWORD)(&dwTemp1) + 0);
BYTE *bb2 = (BYTE *)((DWORD)(&dwTemp2) + 0);
for (*bb1 = 0x20; *bb1<=0x7e; *bb1 = *bb1 + 1)
{
for (*bb2 = 0x20; *bb2<=0x7e; *bb2 = *bb2 + 1)
{   
if (dwTemp1 + 2*dwTemp2 == *dw1 + *dw2*2)
{
*dw1 = dwTemp1;
*dw2 = dwTemp2;
return TRUE;
}
}   
}
::MessageBoxA(NULL, "没有找到,请重新试一下", NULL, NULL);
return FALSE;
}
--------下面是K.K同学的算法(同样是定死后12byte 但效率比我的第5种方法好多了)-----------------------------
#include "stdafx.h"
#include <locale.h>
#include <windows.h>
#include <time.h>
#include "stdlib.h"

int main()
{

DWORD dwValue = 0xCD8C7262;                    //最终结果
CHAR SetForByteOfA2[5] = {0x70,0x71,0x20,0x40,0x41};   //A2的某个字节落入的集合
CHAR szSerial[21] = {0};                    //序列号
srand((unsigned)time(NULL));
for(int i=0;i<20;i++){
szSerial[i] = rand()%(0x7e-0x20+1) + 0x20;    //以随机字符填充序列号
}
DWORD & A1 = *((DWORD *)szSerial);
DWORD & A2 = *((DWORD *)szSerial+1);
DWORD & A3 = *((DWORD *)szSerial+2);
DWORD & A4 = *((DWORD *)szSerial+3);
DWORD & A5 = *((DWORD *)szSerial+4);
printf("A1==%0#10X\nA2==%0#10X\nA3==%0#10X\nA4==%0#10X\nA5==%0#10X\n",A1,A2,A3,A4,A5);
//dwValueExpected = A1 + 2A2
DWORD dwValueExpected = dwValue - 3*A3 - 4*A4 - 5*A5;
printf("3*A3 + 4*A4 + 5*A5 == %0#10X\n",3*A3 + 4*A4 + 5*A5);
printf("dwValueExpected == %0#10X\n",dwValueExpected);
CHAR carry = 0;

for(i=0;i<=3;i++){
CHAR cValueExpected = *((CHAR *)&dwValueExpected+i);    //dwValueExpected的某个字节
BOOL bSuccess = FALSE;                                    //表示A1的某个字节+2*A2的某个字节是否等于dwValueExpected的某个字节
for(szSerial[i] = 0x7e; szSerial[i] >= 0x20; szSerial[i]--){//A1的某个字节,
for(int j=0;j<5;j++){
szSerial[i+4] = SetForByteOfA2[j];                    //A2的某个字节
WORD temp = (WORD)szSerial[i] + (WORD)szSerial[i+4]*2 + carry;    //temp表示A1的某个字节+2*A2的某个字节,高位表示进位,低位表示实际结果
CHAR cLowValue = *(CHAR *)&temp;    //temp的低位表示实际结果
if(cLowValue == cValueExpected){
carry =  *((CHAR *)&temp+1);    //temp的高位表示进位
bSuccess = TRUE;
printf("szSerial[%d]==%0#4X\tszSerial[%d]==%0#4X\n",i,szSerial[i],i+4,szSerial[i+4]);
break;
}
}
if(bSuccess)break;
}
}
printf("Serial Number is %s\n",szSerial);
//用原算法验证一下
DWORD *p = (DWORD*)szSerial;
DWORD dwResult = 0;
for(i=1; i<=5; i++)
{
dwResult += *p*i;
p++;
}
if(dwResult == dwValue)
printf("This Serial Number is available!\n");
else
printf("This Serial Number is not available!\n");

return 0;
}
2010-3-11 10:29
0
雪    币: 119
活跃值: (10)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
13
没弄出来,等LZ公布思路许久了,看了N天的新帖竟然没等到!
今天实在等不急了,找到此贴,发现已经贴出来了,汗!

另KK是楼主的兄弟?嘻嘻
2010-3-11 20:23
0
雪    币: 458
活跃值: (421)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
14
KK 就是咱论坛的 珂珂 同学  哈哈
2010-3-11 23:00
0
雪    币: 119
活跃值: (10)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
15
哦,哈哈,看了看kk同学的精华帖,学习了一下!呵呵,很不错!
(有人一起讨论感觉一定很好,嘻嘻!)
2010-3-12 11:37
0
游客
登录 | 注册 方可回帖
返回
//