[软件名称] : 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 = LYQingYe E-mail = xuepojie Key =0123456789
//进入核心算法CALL
//准备工作,检查参数,合并输入的E-mail 然后初次加密,拷贝 我们输入的Key到新的缓冲区,以便以后的比较 [Asm] 纯文本查看 复制代码 0042C640 />sub esp,0x270
0042C646 |>xor eax,eax
0042C648 |>push ebx
0042C649 |>push ebp
0042C64A |>push esi
0042C64B |>mov esi,dword ptr ss:[esp+0x280] ; 获取输入的参数->"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:[esp+0x55]
0042C668 |>mov byte ptr ss:[esp+0x54],al
0042C66C |>rep stos dword ptr es:[edi] ; 将传入的参数字符串合并,路径 + E-mail
0042C66E |>mov eax,dword ptr ss:[esp+0x288] ; UNICODE "Software\ZD Soft\Screen Recorder\xuepojie"
0042C675 |>lea ecx,dword ptr ss:[esp+0x54]
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:[esp+0x12]
0042C6A1 |>mov word ptr ss:[esp+0x10],bx
0042C6A6 |>rep stos dword ptr es:[edi]
0042C6A8 |>mov ecx,0x81
0042C6AD |>lea edi,dword ptr ss:[esp+0x7A]
0042C6B1 |>mov word ptr ss:[esp+0x78],bx
0042C6B6 |>lea edx,dword ptr ss:[esp+0x78]
0042C6BA |>rep stos dword ptr es:[edi]
0042C6BC |>stos word ptr es:[edi]
0042C6BE |>lea eax,dword ptr ss:[ebp+0x2A64] ;获得参数,我们输入的假码
0042C6C4 |>push eax ; /假码 - >01234-56789-
0042C6C5 |>push edx ; |dest = 00000011
0042C6C6 |>call dword ptr ds:[<&MSVCRT.wcscpy>] ; \将输入的假码拷贝到新的缓冲区,以便后面的比较
//循环加密,并且计算,EDI作为加密循环计数器,ESI作为字符串计算的索引,每次取两个字节,进行计算。
//加密一次后将字符串转换为UNICODE,然后计算.
[Asm] 纯文本查看 复制代码 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:[esp+0x10]
0042C6E3 |.>|push 0x21
0042C6E5 |.>|push eax
0042C6E6 |.>|lea ecx,dword ptr ss:[esp+0x5C] ; 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:[esp+0x10]
0042C6F7 |.>|lea eax,dword ptr ss:[esp+0x54]
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:[esp+esi*2+0x10] ; 这里取的是第一次加密数据,也就是前一次,每次取两个字节
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:[esp+esi*2+0x10] ;获得加密后的数据
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:[esp+esi*2+0x10] ; 取获得的加密后的数据,每次两个字节
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:[esp+esi*2+0x10] ; 获得加密数据
0042C766 |.>||add eax,esi ; 加密数据加上索引
0042C768 |.>||add eax,edi ; 加密数据加上循环记数
0042C76A |.>||cdq ; 把EDX的所有位都设成EAX最高位的值
0042C76B |.>||idiv ecx ; 将计算后的值 / 0x14
0042C76D |.>||lea eax,dword ptr ds:[edx+0x47]
0042C770 |.>||cmp ax,0x4F ; 判断计算后的值是否为0x4f
0042C774 |.>||mov word ptr ss:[esp+esi*2+0x10],ax
0042C779 |.>||jnz short ScnRec.0042C782
0042C77B |.>||mov word ptr ss:[esp+esi*2+0x10],0x30 ; 若为0x4f 则写入0x30
0042C782 |>>||cmp word ptr ss:[esp+esi*2+0x10],0x49 ; 判断计算后的值是否为0x49
0042C788 |.>||jnz short ScnRec.0042C791
0042C78A |.>||mov word ptr ss:[esp+esi*2+0x10],0x31 ; 若为49 ,则写入0x31
0042C791 |>>||cmp word ptr ss:[esp+esi*2+0x10],0x5A ; 判断计算后的值是否为0x5a
0042C797 |.>||jnz short ScnRec.0042C7A0
0042C799 |.>||mov word ptr ss:[esp+esi*2+0x10],0x32 ; 若为5A则写入0x32
0042C7A0 |>>||inc esi
0042C7A1 |.>||cmp esi,0x20 ; 加密后的数据长度作为循环次数
0042C7A4 |.>|\jl ScnRec.0042C706 ; 继续循环计算
[align=left]
//最后一个阶段,拼装字符串,与比较我们输入的key若相等则跳出循环
[Asm] 纯文本查看 复制代码 0042C7AA |>|lea edx,dword ptr ss:[esp+0x10]
0042C7AE |>|mov ecx,ebp ; ScnRec.004A7DE8
0042C7B0 |>|push edx
0042C7B1 |>|call ScnRec.0042C1B0 ; 这里为第二阶段,注册码拼装
0042C7B6 |>|lea eax,dword ptr ss:[esp+0x78]
0042C7BA |>|push eax ; /wstr2 = 00000058 ???
0042C7BB |>|lea eax,dword ptr ss:[ebp+0x2A64] ; |
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:[esp+0x284]
0042C7D6 |>|inc edi
0042C7D7 |>|cmp edi,eax
0042C7D9 |>\jl ScnRec.0042C6DF ; 循环MD5加密和计算
//第一阶段算法解读,有一个寄存器EDI作为for循环的计数器,另外一个寄存器ESI作为读取加密数据字符串的索引,这两个值都用到计算上.
//若 ESI 为奇数 以及 加密数据为偶数 ,或者 ESI 为偶数 加密数据为奇数,才会进行计算,否则不计算,计算算法 很简单 [Asm] 纯文本查看 复制代码 0042C75C |.>||mov ecx,0x14 ;ECX = 0x14,作为除数
0042C761 |.>||mov ax,word ptr ss:[esp+esi*2+0x10] ; 获得加密数据
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 逆向,与 字符串拼装函数逆向
[C] 纯文本查看 复制代码 //
//计算注册码
//
char * Keygen(char * PathEmali)
{
char szDigest[16];//加密后的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;
}
// //附上测试图,以及keygen //
|