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就是版本注册了
心得:编写注册机,就是要尽可能的寻找限制条件,不要漏掉关键代码,不要中途绝望(可能是你没找到那个限制点),还需要灵活,发现哪些变量本就是可以随意给一个数值的,从而赋予前面的限制条件以活力,具体的还需实践去体会
只是对逆向感兴趣,希望作者不要见怪~
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-4-12 10:06
被树梢之上编辑
,原因: