ZD Soft Screen Recorder v9.8 算法逆向 + Keygen
[软件名称]:ZD SoftScreen Recorder v9.8[编译类型]:VC++
[是否有壳]:无壳
[注册类型]:注册码注册
[作者信息]:LYQingYe
//核心算法CALL查找方法,MFC按钮事件定位 -> 算法CALL ->sub_0042C640 , 如下图. 0x727B6350 跟下去就是核心算法
//MFC按钮事件查找 (PS:查找时,程序禁止被调试)
//浅述,验证方式.//采用 MD5加密 用户输入的 E-mail , 然后计算拼装成注册码,为了防止一个 E-mail 只固定一个死码,作者采用多重加密,生成多个Key,加密次数最高为 0x3e8 , 次数来源sub_0042C640 ,参数 push 0x3e8 .//分为两个阶段,第一阶段MD5加密,与简单计算,第二阶段,拼装注册码,具体算法看分析.//本次测试 Name = LYQingYeE-mail = xuepojie Key =0123456789
//进入核心算法CALL
//准备工作,检查参数,合并输入的E-mail 然后初次加密,拷贝我们输入的Key到新的缓冲区,以便以后的比较0042C640/>sub esp,0x270
0042C646|>xor eax,eax
0042C648|>push ebx
0042C649|>push ebp
0042C64A|>push esi
0042C64B|>mov esi,dword ptr ss: ; 获取输入的参数->"Software\ZD Soft\Screen Recorder"
0042C652|>xor ebx,ebx
0042C654|>push edi
0042C655|>cmp esi,ebx
0042C657|>mov ebp,ecx ;检查传入参数是否非法->"Software\ZD Soft\Screen Recorder"
0042C659|>jle ScnRec.0042C7E2
0042C65F|>mov ecx,0x8
0042C664|>lea edi,dword ptr ss:
0042C668|>mov byte ptr ss:,al
0042C66C|>rep stos dword ptr es: ;将传入的参数字符串合并,路径 + E-mail
0042C66E|>mov eax,dword ptr ss: ;UNICODE "Software\ZD Soft\Screen Recorder\xuepojie"
0042C675|>lea ecx,dword ptr ss:
0042C679|>push eax ;传入要加密的数据 "Software\ZD Soft\Screen Recorder\xuepojie"
0042C67A|>push ecx
0042C67B|>mov ecx,ebp
0042C67D|>call ScnRec.0042C480 ;MD5加密
0042C682|>test eax,eax ;加密后的数据 ASCII "971E2087B3DFED4279B6E06949A5F157"
0042C684|>jnz short ScnRec.0042C696 ;加密失败则返回
0042C686|>pop edi
0042C687|>pop esi
0042C688|>pop ebp
0042C689|>or eax,-0x1
0042C68C|>pop ebx
0042C68D|>add esp,0x270
0042C693|>retn 0x8
0042C696|>mov ecx,0x10
0042C69B|>xor eax,eax
0042C69D|>lea edi,dword ptr ss:
0042C6A1|>mov word ptr ss:,bx
0042C6A6|>rep stos dword ptr es:
0042C6A8|>mov ecx,0x81
0042C6AD|>lea edi,dword ptr ss:
0042C6B1|>mov word ptr ss:,bx
0042C6B6|>lea edx,dword ptr ss:
0042C6BA|>rep stos dword ptr es:
0042C6BC|>stos word ptr es:
0042C6BE|>lea eax,dword ptr ss: ;获得参数,我们输入的假码
0042C6C4|>push eax ; /假码 - >01234-56789-
0042C6C5|>push edx ; |dest = 00000011
0042C6C6|>call dword ptr ds:[<&MSVCRT.wcscpy>] ; \将输入的假码拷贝到新的缓冲区,以便后面的比较
//循环加密,并且计算,EDI作为加密循环计数器,ESI作为字符串计算的索引,每次取两个字节,进行计算。
//加密一次后将字符串转换为UNICODE,然后计算.
0042C6CC|.>add esp,0x8
0042C6CF|.>xor edi,edi ;初始化EDI作为循环计数器
0042C6D1|.>cmp esi,ebx ;kernel32.MultiByteToWideChar
0042C6D3|.>jle ScnRec.0042C7DF
0042C6D9|.>mov ebx,dword ptr ds:[<&KERNEL32.MultiByte>;kernel32.MultiByteToWideChar
0042C6DF|>>/lea eax,dword ptr ss:
0042C6E3|.>|push 0x21
0042C6E5|.>|push eax
0042C6E6|.>|lea ecx,dword ptr ss: ;ECX - >"971E2087B3DFED4279B6E06949A5F157"
0042C6EA|.>|push -0x1
0042C6EC|.>|push ecx ;压入加密数据"971E2087B3DFED4279B6E06949A5F157"
0042C6ED|.>|push 0x0
0042C6EF|.>|push 0x0
0042C6F1|.>|call ebx ;将加密后的数据转换为 UNICODE ,以便计算
0042C6F3|.>|lea edx,dword ptr ss:
0042C6F7|.>|lea eax,dword ptr ss:
0042C6FB|.>|push edx ;压入第一次加密的数据 "971E2087B3DFED4279B6E06949A5F157"
0042C6FC|.>|push eax
0042C6FD|.>|mov ecx,ebp ;ScnRec.004A7DE8
0042C6FF|.>|call ScnRec.0042C480 ;MD5再次加密
0042C704|.>|xor esi,esi ;清空ESI 当作 字符串的索引,每次取两个字节
0042C706|>>|/mov cx,word ptr ss: ;这里取的是第一次加密数据,也就是前一次,每次取两个字节
0042C70B|.>||push ecx ; /w
0042C70C|.>||call dword ptr ds:[<&MSVCRT.iswalpha>] ; \判断是否为字母
0042C712|.>||add esp,0x4
0042C715|.>||test eax,eax
0042C717|.>||jnz ScnRec.0042C7A0 ;不是字母则不计算,跳到尾不,继续循环
0042C71D|.>||mov eax,esi
0042C71F|.>||and eax,0x80000001 ;取 索引ESI 判断 索引是奇数还是偶数
0042C724|.>||jns short ScnRec.0042C72B ;判断是否为负数
0042C726|.>||dec eax ;为负数则进行补码操作
0042C727|.>||or eax,-0x2 ;再判断是奇数还是偶数
0042C72A|.>||inc eax
0042C72B|>>||je short ScnRec.0042C743
0042C72D|.>||xor edx,edx
0042C72F|.>||mov dx,word ptr ss: ;获得加密后的数据
0042C734|.>||and edx,0x80000001 ;同上判断是奇数还是偶数
0042C73A|.>||jns short ScnRec.0042C741
0042C73C|.>||dec edx
0042C73D|.>||or edx,-0x2
0042C740|.>||inc edx
0042C741|>>||jnz short ScnRec.0042C7A0
0042C743|>>||test eax,eax
0042C745|.>||jnz short ScnRec.0042C75A
0042C747|.>||mov ax,word ptr ss: ;取获得的加密后的数据,每次两个字节
0042C74C|.>||and eax,0x80000001
0042C751|.>||jns short ScnRec.0042C758 ;判断是奇数还是偶数
0042C753|.>||dec eax
0042C754|.>||or eax,-0x2
0042C757|.>||inc eax
0042C758|>>||je short ScnRec.0042C7A0
0042C75A|>>||xor eax,eax ;若 ESI 为奇数 以及 加密数据为偶数 ,或者 ESI 为偶数 加密数据为奇数,才跳到这进行计算
0042C75C|.>||mov ecx,0x14 ;ECX = 0x14,作为除数
0042C761|.>||mov ax,word ptr ss: ;获得加密数据
0042C766|.>||add eax,esi ;加密数据加上索引
0042C768|.>||add eax,edi ;加密数据加上循环记数
0042C76A|.>||cdq ;把EDX的所有位都设成EAX最高位的值
0042C76B|.>||idiv ecx ;将计算后的值 / 0x14
0042C76D|.>||lea eax,dword ptr ds:
0042C770|.>||cmp ax,0x4F ;判断计算后的值是否为0x4f
0042C774|.>||mov word ptr ss:,ax
0042C779|.>||jnz short ScnRec.0042C782
0042C77B|.>||mov word ptr ss:,0x30 ;若为0x4f 则写入0x30
0042C782|>>||cmp word ptr ss:,0x49 ;判断计算后的值是否为0x49
0042C788|.>||jnz short ScnRec.0042C791
0042C78A|.>||mov word ptr ss:,0x31 ;若为49 ,则写入0x31
0042C791|>>||cmp word ptr ss:,0x5A ;判断计算后的值是否为0x5a
0042C797|.>||jnz short ScnRec.0042C7A0
0042C799|.>||mov word ptr ss:,0x32 ;若为5A则写入0x32
0042C7A0|>>||inc esi
0042C7A1|.>||cmp esi,0x20 ;加密后的数据长度作为循环次数
0042C7A4|.>|\jl ScnRec.0042C706 ;继续循环计算
//最后一个阶段,拼装字符串,与比较我们输入的key若相等则跳出循环
0042C7AA|>|lea edx,dword ptr ss:
0042C7AE|>|mov ecx,ebp ;ScnRec.004A7DE8
0042C7B0|>|push edx
0042C7B1|>|call ScnRec.0042C1B0 ;这里为第二阶段,注册码拼装
0042C7B6|>|lea eax,dword ptr ss:
0042C7BA|>|push eax ; /wstr2 = 00000058 ???
0042C7BB|>|lea eax,dword ptr ss: ; |
0042C7C1|>|push eax ; |wstr1 = 00000058 ???
0042C7C2|>|call dword ptr ds:[<&MSVCRT.wcscmp>] ; \注册码与我们输入的假码比较
0042C7C8|>|add esp,0x8
0042C7CB|>|test eax,eax
0042C7CD|>|je short ScnRec.0042C7DF ;成功则跳出循环
0042C7CF|>|mov eax,dword ptr ss:
0042C7D6|>|inc edi
0042C7D7|>|cmp edi,eax
0042C7D9|>\jl ScnRec.0042C6DF ;循环MD5加密和计算
//第一阶段算法解读,有一个寄存器EDI作为for循环的计数器,另外一个寄存器ESI作为读取加密数据字符串的索引,这两个值都用到计算上.
//若 ESI 为奇数 以及 加密数据为偶数 ,或者 ESI 为偶数 加密数据为奇数,才会进行计算,否则不计算,计算算法 很简单0042C75C|.>||mov ecx,0x14 ;ECX = 0x14,作为除数
0042C761|.>||mov ax,word ptr ss: ;获得加密数据
0042C766|.>||add eax,esi ;加密数据加上索引
0042C768|.>||add eax,edi ;加密数据加上循环记数
0042C76A|.>||cdq ;把EDX的所有位都设成EAX最高位的值
0042C76B|.>||idiv ecx ;将计算后的值 / 0x14
设加密数据为 Code 所以 -> calccode = (Code + ESI + EDI) % 0x14 ,然后判断 calccode 是否等于0x4f 0x49 0x5a ,然后写入相应数据.然后组装注册码,进行和输入的假码比较,若想等则结束,否则继续加密循环
//简单总结算法过程 ,具体注释可以看下面的逆向还原注释。//检查参数,将 Software\ZDSoft\Screen Recorder 与我们输入的E-main字符串链接//然后进行第一次加密,接着就是计算,计算后拼装注册码,再与我们输入的Key进行比较,若相等则结束,否则继续加密.//加密次数最大值为0x3e8,这就避免了一个E-mail 只能对应 一个Key 他可以对应 0X3E8个key,校验时,要循环0X3E8次 进行校验,直到相等或者不相等
//两个阶段函数逆向 -> 核心算法CALL 逆向,与 字符串拼装函数逆向
//
//计算注册码
//
char * Keygen(char * PathEmali)
{
char szDigest;//加密后的Hex数值
char *encrypt; //要加密的数据
char *Key; //加密后的字符串
wchar_t *Key2; //加密后的字符串
wchar_t * FuckKey;//最终计算的Key
//循环次数
ULONG loop = 0x3E8 ; //0042C869|.68 E8030000 push 0x3E8
encrypt = PathEmali;
Key2 = (WCHAR *)malloc(33);
signed int Index;
bool IsEven;
unsigned int bitflag;
bool IsEven2;
unsigned int bitflag2;
int code;
//第一次加密,对传入的参数MD5加密
MD5Digest(encrypt, strlen(encrypt), szDigest);
//转为字符串
Key = (char*)HexToAscii((DWORD)&szDigest, 16, TRUE);
//为最终计算后的Key分配内存
FuckKey = (wchar_t *)malloc(60);
memset(FuckKey, 0x0, 60);
for (int i = 0; i < loop; ++i)
{
//转换为宽字符
MultiByteToWideChar(0, 0, Key, -1, Key2, 33);
//再次加密数据
MD5Digest(Key, strlen(Key), szDigest);
Key = (char*)HexToAscii((DWORD)&szDigest, 16, TRUE);
Index = 0;
//
//这个算法只处理 Index为奇数,code为偶数,或者Index为偶数,code为奇数 。
//
do
{
if (!iswalpha(*(Key2 + Index))) //字节递进
{
if ((Index % 2))
{
//
//Index为奇数
//
//获取keycode的最高位和最低位
bitflag = *(Key2 + Index) & 0x80000001;
IsEven = bitflag == 0;
//判断最高位是否为0->判断是否为负数
if ((bitflag & 0x80000000) != 0)
{
//负数,则判断是否为偶数
IsEven = (((BYTE)bitflag - 1) | 0xFFFFFFFE) == -1;
}
if (IsEven)
{
//keycode为偶数
code = (i + Index + *(Key2 + Index)) % 20;
*(Key2 + Index) = code + 71;
if ((WORD)code == 8)
*(Key2 + Index) = 48;
if (*(Key2 + Index) == 73)
*(Key2 + Index) = 49;
if (*(Key2 + Index) == 90)
*(Key2 + Index) = 50;
}
}
else{
//
//Index为偶数
//
//判断最高位是否为0->判断是否为负数
bitflag2 = *(Key2 + Index) & 0x80000001;
IsEven2 = bitflag2 == 0;
//判断最高位是否为0->判断是否为负数
if ((bitflag2 & 0x80000000) != 0)
{
//判断奇偶
IsEven2 = (((BYTE)bitflag2 - 1) | 0xFFFFFFFE) == -1;
}
if (!IsEven2)
{
//Code为奇数
code = (i + Index + *(Key2 + Index)) % 20;
*(Key2 + Index) = code + 71;
if ((WORD)code == 8)
*(Key2 + Index) = 48;
if (*(Key2 + Index) == 73)
*(Key2 + Index) = 49;
if (*(Key2 + Index) == 90)
*(Key2 + Index) = 50;
}
}
}
++Index;
} while (Index < 32);
//组装注册码_> 0042C1B0
memset(FuckKey, 0X0, 60);
LinkKey(FuckKey, Key2);
//在这可以随机输出Key,一个用户名生成的Key可以有0x3e8组
printf("%ws \n", FuckKey);
}
return NULL;
}
**** Hidden Message *****
////附上测试图,以及keygen//
支持算法分析,感谢LYQingYe的精彩教程! 略微高端,前排仰望拜膜一下~~ 算码要支持! 感谢LYQingYe分享,这玩意只适用于9.8版本吗 很不错,谢谢分享 感谢算法牛表哥 谢谢了我喜欢 向算法大神学习了 大神的世界不懂。。