看雪论坛
发新帖
1

[原创]010editor 8.0算法分析[附注册机]

树梢之上 2017-9-11 21:22 1247
今天又看了看自己写的文章,觉得不够清晰,好歹浪费了我周末的时间,就试着写的更符合分析思路吧.如有不足,希望各位指教

1. 定位到注册算法函数处

此处步骤略,注册算法函数在地址013BD900,
参数情况为:arg1 = 0x9 ;arg2 = 0x4389;
如果要注册成功,需要其返回0xDB,
所以需要进入case:0x2D,需要函数0040A82B返回0x2D
013BE220      PUSH EBP
013BE221      MOV EBP,ESP
013BE223      PUSH ESI
013BE224      MOV ESI,ECX
013BE226      CMP DWORD PTR DS:[ESI+0x2C],0x0
013BE22A  |.  JE SHORT 010Edito.013BE236
013BE22C  |.  MOV EAX,0x113
013BE231  |.  POP ESI
013BE232  |.  POP EBP
013BE233  |.  RETN 0x8
013BE236  |>  PUSH EDI
013BE237  |.  PUSH [ARG.2]
013BE23A  |.  MOV EDI,[ARG.1]
013BE23D  |.  PUSH EDI
013BE23E  |.  CALL 010Edito.0040A82B
013BE243  |.  CMP EAX,0x2D                       ;  Switch (cases 2D..E7)
.....................................
013BE2EF  |>  POP EDI                            ;  Case 2D of switch 013BE243
013BE2F0  |.  MOV EAX,0xDB
013BE2F5  |.  POP ESI
013BE2F6  |.  POP EBP
013BE2F7  \.  RETN 0x8
这里需要进入函数0040A82B

2. 整体轮廓

2.1 注册码的保存形式

进入函数0040A82B
函数首先将注册码按每两位16进制数的形式存入局部变量数组中
013BD96D MOV ECX,EDI
013BD96F PUSH EAX                                                   
013BD970 CALL 010Edito.00409B74         
比如输入注册码12345678876543210011
对应
[ebp-0x24]  KEY[0] = 0x12
[ebp-0x23]  KEY[1] = 0x34
......
[ebp-0x1B]  KEY[9] = 0x11

2.2 对注册码的处理和函数00402E50(处理用户名)的调用

接下来,寄存器BL = KEY[3], 寄存器BH = KEY[5];
根据KEY[3]的值进行注册类型的判断,有0X9C 0XFC 0XAC 三种情况
每种类型会对 esi,DWORD PTR DS:[EDI+0x20] 等进行赋值,影响后面的操作
由于篇幅有限,所以这里不逐句写汇编,更加详细的写在附件文档中

为了有整体的了解,这里先跳过每种类型的处理,来观察在处理后(也就是上面的值都有了着落)调用用户名处理函数的汇编代码

调用处
013BDAD8   .  PUSH DWORD PTR DS:[EDI+0x20]   ;参数4:DWORD PTR DS:[EDI+0x20]
013BDADB   .  XOR EAX,EAX ;
013BDADD   .  MOV DWORD PTR SS:[EBP-0x4],0x0  
013BDAE4   .  CMP BL,0xFC   ;由前面可知BL为KEY[3]
013BDAE7   .  LEA ECX,DWORD PTR SS:[EBP-0x14]
013BDAEA   .  PUSH ESI ;ESI ;参数3,ESI
013BDAEB   .  SETNE AL ;如果BL != 0xFC, AL = 1
013BDAEE   .  PUSH EAX ; ;参数2:这里根据KEY[3]的值和0xFC比较,如果不是0xFC类型就设置为1,否则为0
013BDAEF   .  CALL DWORD PTR DS:[<&Qt5Core.?data>;  Qt5Core.?data@QByteArray@@QAEPADXZ// 这个函数没有参数,传出用户名
013BDAF5   .  PUSH EAX     ;参数1:用户名
013BDAF6   .  CALL 010Edito.00402E50   ;处理用户名

之后会根据CALL 010Edito.00402E50   的返回值和其他一些值进行判断给eax赋值,最后程序返回

2.3 最后,更多有用的信息:

为了有整体的了解,先跳过处理用户名函数中的具体内容,我们来观察最后程序是怎样进行处理的
这里由于确定了返回值需要为0x2D,所以从下往上看,可以跟着序号看,倒着看可以看到013BDB24地址处,
可以得出结论:
1.0xFC情况无法返回正确值,
2.0x9C情况:需要满足[ EBP + 0x8 ] <= [ EDI + 0x1C ] 等价于 参数1也就是9应该小于等于[ EDI + 0x1C ]
3.0xAC情况:需要满足[ EBP - 0x10 ] >= [ EBP + 0xC ] 等价于 [ EBP - 0x10 ]大于等于参数2也就是0x4389
具体的0x9C情况中的[ EDI + 0x1C ]和0xAC情况的[ EBP - 0x10 ]的值各是多少,需要结合这两种情况的汇编代码来看,这里先放放

接着我们从前往看,若定义char result[4], *(DWORD*)result = 处理用户名函数();
则KEY[ 4 ] = result[ 0 ];
KEY[ 5 ] = result[ 1 ];
KEY[ 6 ] = result[ 2 ];
KEY[ 7 ] = result[ 3 ];
这些信息对最后写注册机很有帮助

013BDAF6   .  CALL 010Edito.00402E50   ;处理用户名
013BDAFB   .  MOV EDX,EAX
013BDAFD   .  ADD ESP,0x10
013BDB00   .  CMP BYTE PTR SS:[EBP-0x20],DL ;KEY[4]和返回值比较
013BDB03   .  JNZ 010Edito.013BDB8A ;应该相等
013BDB09   .  MOV ECX,EDX 
013BDB0B   .  SHR ECX,0x8
013BDB0E   .  CMP BH,CL  ;返回值>>0x8 和 KEY[5]比较
013BDB10   .  JNZ SHORT 010Edito.013BDB8A;应该相等
013BDB12   .  MOV ECX,EDX
013BDB14   .  SHR ECX,0x10
013BDB17   .  CMP BYTE PTR SS:[EBP-0x1E],CL;返回值>>0x10 和 KEY[6]比较
013BDB1A   .  JNZ SHORT 010Edito.013BDB8A;应该相等
013BDB1C   .  SHR EAX,0x18
013BDB1F   .  CMP BYTE PTR SS:[EBP-0x1D],AL;返回值>>0x18 和 KEY[7]比较
013BDB22   .  JNZ SHORT 010Edito.013BDB8A;应该相等

013BDB24   .  CMP BL,0x9C                        ;  Switch (cases 9C..FC)
013BDB27   .  JNZ SHORT 010Edito.013BDB38

013BDB29   .  MOV EAX,DWORD PTR SS:[EBP+0x8]     ;  Case 9C of switch 013BDB24
013BDB2C   .  CMP EAX,DWORD PTR DS:[EDI+0x1C]
013BDB2F   .  JBE SHORT 010Edito.013BDB83   // <= 跳到013BDB83,即正确处③


013BDB31   .  MOV ESI,0x4E
013BDB36   .  JMP SHORT 010Edito.013BDB8F
013BDB38   >  CMP BL,0xFC   ;
013BDB3B   .  JNZ SHORT 010Edito.013BDB6B
013BDB3D   .  MOVZX ECX,BYTE PTR SS:[EBP-0x22]   ;  Case FC of switch 013BDB24
...........;期间无跳到正确答案处


013BDB70   .  MOV EAX,DWORD PTR SS:[EBP-0x10]    ;  Case AC of switch 013BDB24
013BDB73   .  TEST EAX,EAX
013BDB75   .  JE SHORT 010Edito.013BDB8A
013BDB77   .  CMP DWORD PTR SS:[EBP+0xC],EAX
013BDB7A   .  JBE SHORT 010Edito.013BDB83   //<=跳到013BDB83,即正确处③
013BDB7C   .  MOV ESI,0x4E
013BDB81   .  JMP SHORT 010Edito.013BDB8F
013BDB83   >  MOV ESI,0x2D  // 对ESI赋值0x2D ②
013BDB88   .  JMP SHORT 010Edito.013BDB8F
013BDB8A   >  MOV ESI,0xE7                       ;  Default case of switch 013BDB24
013BDB8F   >  LEA ECX,DWORD PTR SS:[EBP-0x14]
013BDB92   .  MOV DWORD PTR SS:[EBP-0x4],-0x1
013BDB99   .  CALL DWORD PTR DS:[<&Qt5Core.??1QB>;  Qt5Core.??1QByteArray@@QAE@XZ
013BDB9F   .  MOV EAX,ESI  // 设置返回值 = ESI ①
013BDBA1   .  MOV ECX,DWORD PTR SS:[EBP-0xC]
013BDBA4   .  MOV DWORD PTR FS:[0],ECX
013BDBAB   .  POP ECX
013BDBAC   .  POP EDI
013BDBAD   .  POP ESI
013BDBAE   .  POP EBX
013BDBAF   .  MOV ESP,EBP
013BDBB1   .  POP EBP
013BDBB2   .  RETN 0x8

3.疑惑的解开:

2.3中提到的0x9C情况中的[ EDI + 0x1C ]和0xAC情况的[ EBP - 0x10 ]的值各是多少???各自的限制条件又有什么用???
这里就需要看在处理用户名函数之前对注册码的处理了,这里由于篇幅只列出伪代码,更详细的见附件
另外,在处理完这些代码后,我们就能得到函数00402E50(也就是用户名处理函数)的参数ESI和DWORD PTR DS:[EDI+0x20]

3.1对于0x9c情况

case 0x9C:
{
DWORD EAX = (((((KEY[ 1 ] ^ KEY[ 7 ]) * 0x100) + (KEY[ 2 ] ^ KEY[ 5 ])) ^ 0x7892) + 0x4d30) ^ 0x3421;
DWORD ECX = (((KEY[ 0 ] ^ KEY[ 6 ]) ^ 0x18) + 0x3D) ^ 0xA7;
if (EAX % 0xB != 0)
{
EAX = 0;//error
}
if (EAX == 0 || EAX > 0x3E8)
{
return;//error
}
if(ECX == 0)
{
return;//error
}
if (ECX < 0x2)// 这里ECX如果大于0x2 ESI=0否则=ECX=1,至于ECX的值是多少,在后面可以看到
{
// ECX == 1,ESI=1
ESI = ECX; 
}
else
{
ESI = 0; 
}
DWORD PTR DS:[EDI+0x1C] = ECX = ((((KEY[0] xor KEY[6]) xor 0x18) + 0x3D)xor 0xA7);
DWORD PTR DS:[EDI+0x20] = 
((((((KEY[ 1 ] ^ KEY[ 7 ]) * 0x100) + (KEY[ 2 ] ^ KEY[ 5 ])) ^ 0x7892) + 0x4d30) ^ 0x3421)/0xB;

break;

}

可以看出ESI可能等于ECX或者0,具体等于多少呢,我们看本文2.3得出的一个结论
0x9C情况:需要满足[ EBP + 0x8 ] <= [ EDI + 0x1C ] 等价于 参数1也就是9应该小于等于[ EDI + 0x1C ],
这个至关重要,由上面的条件,由于ECX = DWORD PTR DS:[EDI+0x1C],所以ECX >= 9 ,所以ESI只能等于0

所以得出结论:
对于0x9C情况,函数00402E50(也就是用户名处理函数)的参数情况为
参数3_ESI:0,参数4_[EDI+0x20]:((((((KEY[ 1 ] ^ KEY[ 7 ]) * 0x100) + (KEY[ 2 ] ^ KEY[ 5 ])) ^ 0x7892) + 0x4d30) ^ 0x3421)/0xB
参数2(只要非0xFC就是1):1, 参数1:用户名)
以及:
DWORD PTR DS:[EDI+0x1C] = ECX = ((((KEY[0] xor KEY[6]) xor 0x18) + 0x3D)xor 0xA7) >= 9;
((((((KEY[ 1 ] ^ KEY[ 7 ]) * 0x100) + (KEY[ 2 ] ^ KEY[ 5 ])) ^ 0x7892) + 0x4d30) ^ 0x3421)能够被0xB整除


3.2对于0xac情况

case 0xAC:
{
DWORD EAX = (((((KEY[ 1 ] ^ KEY[ 7 ]) * 0x100) + (KEY[ 2 ] ^ KEY[ 5 ])) ^ 0x7892) + 0x4d30) ^ 0x3421;
if(EAX % 0xB != 0)
{
EAX = 0;//error
}
if(EAX == 0 || EAX > 0x3E8)
{
return;//error
}
DWORD ECX = (((((((((KEY[ 9 ] ^ KEY[ 5 ]) << 8) + (KEY[ 4 ] ^ KEY[ 5 ])) << 8) + (KEY[ 6 ] ^ KEY[ 0 ])) ^ 0x05B8C27) ^ 0x22c078) - 0x2C175) ^ 0xFFE53167) & 0xFFFFFF;
// 这里EAX被重新复制
EAX = 0xF0F0F0F1;
DWORD EDX = 0;
伪代码:EDX:EAX = EAX*ECX;
EDX = EDX >> 0x4;
EAX = (EDX << 0x4) + EDX;
ECX = ECX - EAX;
if (ECX == 0)// ECX = EAX
{
EAX = EDX;//right
}
else
{
EAX = 0;//error
}
// 结果:
EDI0x20 = ((((((KEY[ 1 ] ^ KEY[ 7 ]) << 2) + (KEY[ 2 ] ^ KEY[ 5 ])) ^ 0x7892) + 0x4d30) ^ 0x3421)/0xB;
// 这里的EAX是最后结尾的EAX,也就是上面的判断中的EAX
伪代码:DWORD PTR SS:[EBP-0x10] = ESI = EAX;
break;
}
程序很复杂, 中间的一个乘法(伪代码:EDX:EAX = EAX*ECX;)令人崩溃,但是后面发现还是有解决方案的
简单的说,前面和0x9c的差不多,同样(((((KEY[ 1 ] ^ KEY[ 7 ]) * 0x100) + (KEY[ 2 ] ^ KEY[ 5 ])) ^ 0x7892) + 0x4d30) ^ 0x3421要被0xB整除,且0<值<0x3E8,但是后面的就大不相同了,ESI的值很难确定.........
这时候我们来看看DWORD PTR SS:[EBP-0x10] = ESI = EAX,其中EAX有上面的ifelse来决定
回头看本文2.3的结论3:0xAC情况:需要满足[ EBP - 0x10 ] >= [ EBP + 0xC ] 等价于 [ EBP - 0x10 ]大于等于参数2也就是0x4389
也就是说WORD PTR SS:[EBP-0x10] = ESI = EAX,这三个都要大于0x4389,就不可能等于0,
那么回到上面的代码中,EAX !=0>>EAX = EDX>>ECX == EAX,那么这里就可以暴力获取了,思路为:设置ECX=0xFFFFFF,
接下来按上面代码走,打印的条件为EAX = ECX, 这样就可以获得最后的DWORD PTR SS:[EBP-0x10]和ESI,以及对应得ECX(用于后面写注册机)

具体代码为
VOID main()
{
unsigned int num_ecx = 0xFFFFFF;
unsigned int num_eax = 0;
unsigned int num_edx = 0;
while(1)
{
__asm {
MOV ECX , num_ecx;
MOV EAX , 0XF0F0F0F1;
MUL ECX;
SHR EDX , 0x4 ;
MOV EAX , EDX;
SHL EAX , 0x4;
ADD EAX , EDX;
MOV num_eax , EAX;
MOV num_edx , EDX;
}
if (num_eax == num_ecx)
{
printf("%x_%x\n", num_ecx, num_edx);
}
num_ecx--;
}
}

其中num_ecx用于写注册机时使用,而其对应的num_edx用作传给 函数00402E50(也就是用户名处理函数)的参数_esi(参数3)

具体怎么写注册机呢?请看下面~~

4. 注册机编写

接下来进入注册机编写阶段,这是这次实践收获最多的地方
当前我们已经可以得到五个KEY值了
KEY[ 3 ] = 选择的情况0x9C或者0xAC;
由于只要输入了用户名和注册号,处理用户名的函数就一定会返回一个值,所以下面的这些值也是可以确定的
KEY[ 4 ] = result[ 0 ];
KEY[ 5 ] = result[ 1 ];
KEY[ 6 ] = result[ 2 ];
KEY[ 7 ] = result[ 3 ];

为了获得result,这里还必须要看函数00402E50(处理用户名的函数):

//arg1:为用户名,
//arg2:判定版本使用,非0xFC情况为1; 
//arg3:esi,0xac情况用于控制注册天数,0x9c情况为0 
//arg4:((((((KEY[ 1 ] ^ KEY[ 7 ]) << 2) + (KEY[ 2 ] ^ KEY[ 5 ])) ^ 0x7892) + 0x4d30) ^ 0x3421)/0xB, 用于控制注册人数
int sub_00402E50(int arg1, int arg2, int arg3, int arg4)
{
DWORD LOCAL3 = 0;
DWORD LOCAL4 = 0;
DWORD LOCAL2 = (arg3*17)&0xFF; // 用于设置注册天数,注意要&0xFF
DWORD LOCAL1 = 0;
DWORD LOCAL0 = arg4*15;   用于设置注册人数
INT NameLength = strlen(Name);
int i = 0;
char EAX[ 4 ] = {0};
DWORD  BUF[ 256 ] = { 0x39CB44B8, .........很多,具体看附件......... 0x16F23945 };//一组值,具体看附件中的汇编分析
while (NameLength)
{
char nameChar = toupper(Name[ i ]);
if (arg2 == 1)
{
LOCAL1 =
(((LOCAL1 + BUF[ nameChar ]) ^ BUF[ (nameChar + 0xD)]) * BUF[ (nameChar + 0x2F)])
+ BUF[ LOCAL2 ]
+ BUF[ LOCAL0 ]
+ BUF[ LOCAL3 ];
}
else  //这一情况无用,应为注册成功情况不会是0xfc
{
LOCAL1 =
(((LOCAL1 + BUF[ nameChar ]) ^ BUF[ (nameChar + 0x3F)]) * BUF[ (nameChar + 0x17)])
+ BUF[ LOCAL2 ]
+ BUF[ LOCAL0 ]
+ BUF[ LOCAL4 ];
}

*(DWORD*)EAX = LOCAL1;

LOCAL0 += 0xD;
LOCAL3 += 0x13;
LOCAL2 += 0x9;
LOCAL4 += 0x7;
i++;
NameLength--;
}
return EAX;
}


对于0x9C情况:  

void CkeygenDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
char szName[ 64 ] = { 0 };

DWORD EDI0x20 = 1;// 表示人数
DWORD ESI = 0;

UpdateData(TRUE);
WideCharToMultiByte(CP_ACP , 0 ,
m_user.GetBuffer() , -1 ,
szName , 64 ,
NULL , NULL);
char result[4] = {0};
*(DWORD*)result = sub_00402E50(szName, 
1, //表示非0xFC 
ESI, // 0x9c情况为0
EDI0x20,// 可以随便设置人数
);
//首先我们可以得到:
KEY[ 3 ] = 0x9c;
KEY[ 4 ] = result[ 0 ];
KEY[ 5 ] = result[ 1 ];
KEY[ 6 ] = result[ 2 ];
KEY[ 7 ] = result[ 3 ];

//接下来呢?
//我们需要看自己自己传入的参数EDI0x20,这个参数是对0x9c的处理后得出来的
//EDI0x20 = ((((((KEY[ 1 ] ^ KEY[ 7 ]) * 0x100) + (KEY[ 2 ] ^ KEY[ 5 ])) ^ 0x7892) + 0x4d30) ^ 0x3421)/0xB
// 这里我们只需要根据前面函数中随便传入的参数来倒推即可,这里很巧妙,利用了一些不定的条件
WORD tmp = ((EDI0x20*11) ^ 0x3421 - 0x4d30) ^ 0x7892;
KEY[ 1 ] = (HIBYTE(tmp)) ^ KEY[ 7 ];
KEY[ 2 ] = (LOBYTE(tmp)) ^ KEY[ 5 ];
//另外:还记得上文中得出的结论吗?
//"0x9C情况:需要满足[ EBP + 0x8 ] <= [ EDI + 0x1C ] 等价于 参数1也就是9应该小于等于[ EDI + 0x1C ],
//这个至关重要,由上面的条件,由于ECX = DWORD PTR DS:[EDI+0x1C],所以ECX >= 9"
//也就是:(((KEY[ 0 ] ^ KEY[ 6 ]) ^ 0x18) + 0x3D) ^ 0xA7 >= 9
// 我们只需假设一个大于9的版本号即可
KEY[ 0 ] = (((10 ^ 0xa7) - 0x3d) ^ 0x18) ^ KEY[ 6 ];//这里取版本号10, 10大于9嘛

// 这里最后几个KEY不用给也没事
char reg[ 200 ] = { 0 };
sprintf_s(reg , 200 , "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X" , KEY[ 0 ] , KEY[ 1 ] , KEY[ 2 ] , KEY[ 3 ] , KEY[ 4 ] , KEY[ 5 ] , KEY[ 6 ] , KEY[ 7 ] , 0 , 0);
m_reg = CString(reg);
UpdateData(FALSE);
}

对于0xac情况:


void CkeygenDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
char szName[ 64 ] = { 0 };

// 此时ESI的值还记得怎么得出的吗,使用暴力遍历即可,此处可以回头看看本文3.2
// VOID main()
//{
// unsigned int num_ecx = 0xFFFFFF;
// unsigned int num_eax = 0;
// unsigned int num_edx = 0;
// while(1)
// {
// __asm {
//
// MOV ECX , num_ecx;
// MOV EAX , 0XF0F0F0F1;
// MUL ECX;
// SHR EDX , 0x4 ;
// MOV EAX , EDX;
// SHL EAX , 0x4;
// ADD EAX , EDX;
// MOV num_eax , EAX;
// MOV num_edx , EDX;
// }
// if (num_eax == num_ecx)
// {
// printf("%x_%x\n", num_ecx, num_edx);
// }
// num_ecx--;
// }
//}
//有这个遍历得到 esi = num_edx作为参数传入,对应的num_ecx用作下面的反推算
//比如这里有这么一些值(不要从打印一开始取),注意这些值很注册天数有关,并且这些值对应的天数都是连续的,由此可以编写一个指定天数的功能
//ff7711_f0701
//ff7700_f0700
//ff76ef_f06ff
//ff76de_f06fe
//ff76cd_f06fd
//ff76bc_f06fc
//ff76ab_f06fb
//ff769a_f06fa
//num_ecx_num_edx
//其中num_ecx用于倒推KEY时使用,而其对应的num_edx作为 函数00402E50(也就是用户名处理函数)的参数_esi(参数3)
DWORD ESI = 0xf06fa;
DWORD num_ecx = 0xff769a;

UpdateData(TRUE);
WideCharToMultiByte(CP_ACP , 0 ,
m_user.GetBuffer() , -1 ,
szName , 64 ,
NULL , NULL);
char result[4] = {0};
DWORD EDI0x20 = 1;// 同样随便传入一个人数即可
*(DWORD*)result = sub_00402E50(szName, 
1, //表示非0xFC 
ESI, 
EDI0x20,// 可以随便设置人数
);
//首先我们可以得到:
KEY[ 3 ] = 0xac;
KEY[ 4 ] = result[ 0 ];
KEY[ 5 ] = result[ 1 ];
KEY[ 6 ] = result[ 2 ];
KEY[ 7 ] = result[ 3 ];

//这一步和上面一样
//EDI0x20 = ((((((KEY[ 1 ] ^ KEY[ 7 ]) * 0x100) + (KEY[ 2 ] ^ KEY[ 5 ])) ^ 0x7892) + 0x4d30) ^ 0x3421)/0xB
WORD tmp = ((EDI0x20*11) ^ 0x3421 - 0x4d30) ^ 0x7892;
KEY[ 1 ] = (HIBYTE(tmp)) ^ KEY[ 7 ];
KEY[ 2 ] = (LOBYTE(tmp)) ^ KEY[ 5 ];
//0xac情况和版本无关,其限制条件应该为和传入参数ESI对应一个值,也就是0xac情况那段汇编中的ECX(可以看看本文的3.2),也可以说是上面那段暴力遍历的num_ecx
//num_ecx=(((((((((KEY[9] xor KEY[5]) << 8)+(KEY[4] xor KEY[8]))<<8) + (KEY[6]  xor KEY[0]))xor 005B8C27) xor 0x22c078) - 0x2C175) xor 0xFFE53167) and 0xFFFFFF 
// 那么由此可以到倒推处其他KEY值
// 这段如果不理解,可以回到上面看文章的3.2
DWORD tmp2 = (((num_ecx ^ 0xFFE53167) + 0x2C175) ^ 0x22c078) ^ 0x05B8C27;// 注意理解:num_ecx
KEY[ 0 ] = (tmp2 & 0xFF) ^ KEY[ 6 ];
DWORD tmp3 = (tmp2 - (tmp2 & 0xFF)) >> 0x8;
KEY[ 8 ] = (tmp3 & 0xFF) ^ KEY[ 4 ];
KEY[ 9 ] = ((tmp3 - (tmp3 & 0xFF)) >> 0x8) ^ KEY[ 5 ];



char reg[ 200 ] = { 0 };
sprintf_s(reg , 200 , "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X" , KEY[ 0 ] , KEY[ 1 ] , KEY[ 2 ] , KEY[ 3 ] , KEY[ 4 ] , KEY[ 5 ] , KEY[ 6 ] , KEY[ 7 ] , KEY[8] , KEY[9]);
m_regsec = CString(reg);

UpdateData(FALSE);

}
综上:0xac就是天数注册,0x9c就是版本注册了,注册机使用vs2015编写,附件解压密码是15pb(打个小广告哈)

心得:编写注册机,就是要尽可能的寻找限制条件,不要漏掉关键代码,不要中途绝望(可能是你没找到那个限制点),还需要灵活,发现哪些变量本就是可以随意给一个数值的,从而赋予前面的限制条件以活力,具体的还需实践去体会

只是对逆向感兴趣,希望作者不要见怪~







   

上传的附件:
本主题帖已收到 0 次赞赏,累计¥0.00
最新回复 (17)
vaguem 2017-9-11 21:46
2
顶楼主~~
jjjackup 2017-9-11 22:01
3
谢谢楼主分享。
1
树梢之上 2017-9-11 22:12
4
版主申个精~
qqsunqiang 2017-9-12 07:39
5
谢谢楼主,正需要。
Vamcy 2017-9-12 07:51
6
支持下
马哲邓论 2017-9-12 08:28
7
mark之,真有耐心
1
布衣勇者 2017-9-12 08:55
8
楼主写的很用心,支持一下
misslisten 2017-9-12 09:33
9
谢谢楼主分享
MaMy 2017-9-12 13:58
10
这个我记得是网络验证的,so..
IronMannnn 2017-9-12 14:00
11
满开西行妖 2017-9-12 16:28
12
顶一个
月凉如水 2017-9-13 15:08
13
不错
胡俊康 2017-9-13 19:23
14

聖blue 2017-9-13 19:31
15
不错
bunnyrene 2017-9-14 18:54
16
那个好心的宝宝来个二进制啊。没有windows
longpoxin 2017-9-15 18:08
17
Umiade 4天前
18
思路很清晰,谢谢分享
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 域名 加速乐 保护 | SSL证书 又拍云 提供 | 微信公众号:ikanxue
Time: 0.014, SQL: 10 / 京ICP备10040895号-17