LYQingYe 发表于 2016-8-1 10:28

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//




Shark恒 发表于 2016-8-1 12:32

支持算法分析,感谢LYQingYe的精彩教程!

xuenii 发表于 2016-8-1 13:52

略微高端,前排仰望拜膜一下~~

爱我你怕了吗 发表于 2016-8-1 14:15

算码要支持!

神马既是浮云 发表于 2016-8-6 17:13

感谢LYQingYe分享,这玩意只适用于9.8版本吗

vov369 发表于 2016-8-7 21:15

很不错,谢谢分享

Bu弃 发表于 2016-8-9 20:36

感谢算法牛表哥

woaini 发表于 2016-8-9 21:49

谢谢了我喜欢

fresharplite 发表于 2016-8-9 22:02

向算法大神学习了

hua5551 发表于 2016-8-11 14:11

大神的世界不懂。。
页: [1] 2 3 4 5 6 7
查看完整版本: ZD Soft Screen Recorder v9.8 算法逆向 + Keygen