吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4512|回复: 20
收起左侧

[会员申请] 申请会员ID:DeathMemory【申请通过】

[复制链接]
吾爱游客  发表于 2016-5-19 14:37
1、申 请 I D:DeathMemory
2、个人邮箱:DeathMemory@163.com
3、原创技术文章:2016游戏安全技术竞赛题PC第一题逆向分析详解
最近看到腾讯的 2016游戏安全技术竞赛,咱不是大学生只能以凑热闹的形式参与一下。

遂下载第一题进行分析

由于要求写注册机,所以这里不考虑暴破

先看一下注册机成功结果:


正式开始分析:

从图中可以看出注册成功,失败都以 static text 控件来显示

用OD载入,搜索注册关键字符都没有找到

在 GetWindowTextW GetDlgItemTextWGetDlgItem 下断,在GetDlgItem 处断下,向上返回到程序领域就是注册算法主体了。

单步向下调试,先走一遍,最大程度的浏览一遍有用信息,这里可以发现,注册信息是在代码段拼接的




[cpp] view plain copy


  • 00CA1F90   .  8D47 FA                     lea     eax, dword ptr [edi-6]  
  • 00CA1F93   .  83F8 0E                     cmp     eax, 0E  
  • 00CA1F96   .  0F87 4B020000               ja      00CA21E7  


此外是用户名长度判断,用户长度在 6-20 之间

接下来是一系列加密算法,用IDA载入并整理

下面是用户名的第一次加密

OD:


[cpp] view plain copy


  • 00CA1FE0   > /8BC1                        mov     eax, ecx  
  • 00CA1FE2   . |99                          cdq  
  • 00CA1FE3   . |F7FF                        idiv    edi  
  • 00CA1FE5   . |8DB40C 88000000             lea     esi, dword ptr [esp+ecx+88]  
  • 00CA1FEC   . |41                          inc     ecx  
  • 00CA1FED   . |0FBE8414 9C000000           movsx   eax, byte ptr [esp+edx+9C]  
  • 00CA1FF5   . |8D142E                      lea     edx, dword ptr [esi+ebp]  
  • 00CA1FF8   . |0FAFC2                      imul    eax, edx  
  • 00CA1FFB   . |0FAFC7                      imul    eax, edi  
  • 00CA1FFE   . |0106                        add     dword ptr [esi], eax  
  • 00CA2000   . |83F9 10                     cmp     ecx, 10  
  • 00CA2003   .^\7C DB                       jl      short 00CA1FE0  


IDA:

[cpp] view plain copy


  • do                                            // 加密用户名 ↓  
  • {  
  •   tmp_i = i % namelen;  
  •   ptrTmp = &name_encrypt[i++];  
  •   *(_DWORD *)ptrTmp += namelen * (_DWORD)&ptrTmp[20160126 - (_DWORD)name_encrypt] * username[tmp_i];  
  • }  
  • while ( i < 16 );                              


接下来的两个Call是跟密码加密相关的,回头再看,先把用户名的加密跑完
到第二次用户名加密的地方

OD:


[cpp] view plain copy


  • .text:00402102                 mov     ecx, dword ptr [esp+esi+2B4h+name_encrypt]  
  • .text:00402109                 mov     eax, 66666667h  
  • .text:0040210E                 imul    ecx  
  • .text:00402110                 sar     edx, 2  
  • .text:00402113                 mov     ecx, edx  
  • .text:00402115                 shr     ecx, 1Fh  
  • .text:00402118                 add     ecx, edx  
  • .text:0040211A                 mov     edx, [esp+2B4h+val_1]  
  • .text:0040211E                 sub     edx, edi  
  • .text:00402120                 mov     [esp+esi+2B4h+name_encrypt_2], ecx ; 计算结果  
  • .text:00402124                 cmp     esi, edx  
  • .text:00402126                 jb      short loc_402131  
  • .text:00402128                 call    __invalid_parameter_noinfo  
  • .text:0040212D                 mov     edi, [esp+2B4h+val_0]  
  • .text:00402131  
  • .text:00402131 loc_402131:                             ; CODE XREF: sub_401E60+2C6j  
  • .text:00402131                 mov     eax, [esi+edi]  
  • .text:00402134                 mov     [esp+esi+2B4h+pass_encrypt], eax  
  • .text:00402138                 add     esi, 4  
  • .text:0040213B                 cmp     esi, 14h  
  • .text:0040213E                 jl      short loc_402102  


IDA:

[cpp] view plain copy


  • do  
  • {  
  •   v12 = *(_DWORD *)&name_encrypt[index] / 10;  
  •   len1 = val_1 - (_DWORD)val_0_1;  
  •   name_encrypt_2[index / 4] = v12;            // 用户名第二次加密  
  •   if ( index >= len1 )  
  •   {  
  •     _invalid_parameter_noinfo(v12);  
  •     val_0_1 = val_0;  
  •   }  
  •   pass_encrypt[index / 4] = *(_DWORD *)((char *)val_0_1 + index);// 密码解密编码后的密码  
  •   index += 4;  
  • }  
  • while ( (signed int)index < 20 );  


再往下就是加密后的用户名和密码进行验证:

[cpp] view plain copy


  • if ( pass_encrypt[4] + name_encrypt_2[0] != pass_encrypt[2]  
  •     || pass_encrypt[2] + name_encrypt_2[1] != 2 * pass_encrypt[4]  
  •     || pass_encrypt[3] + name_encrypt_2[2] != pass_encrypt[0]  
  •     || pass_encrypt[0] + name_encrypt_2[3] != 2 * pass_encrypt[3]  
  •     || name_encrypt_2[4] + pass_encrypt[1] != 3 * name_encrypt_2[2] )// 验证  


以上验证如果有一条为真,则跳转到失败。

分析到这里,我们还没有分析密码的加密,这个是关键点,我们先设为 X

则就有了以下这条假设的公式: 用户名 + x = 验证

所以在已知用户名和验证关系后,我们可以推导出加密后 X 的值


[cpp] view plain copy


  • void EncryptUsername(char* username) {  
  •     BYTE name_encrypt[20] = { NULL };  
  •     int namelen = strlen((char*)username);  
  •     int i = 0;  
  •     do// 第一次加密  
  •     {  
  •         int tmp_i = i % namelen;  
  •         BYTE* tmp_ptr = &name_encrypt[i++];  
  •         *(DWORD *)tmp_ptr += namelen * (DWORD)&tmp_ptr[20160126 - (DWORD)name_encrypt] * username[tmp_i];  
  •     } while (i < 16);  
  •     i = 0;  
  •     BYTE name_encrypt_2[20] = { NULL };  
  •     do// 第二次加密  
  •     {  
  •         int tmp = *(int*)&name_encrypt / 10;  
  •         *(int*)&name_encrypt_2 = tmp;  
  •         i += 4;  
  •     } while (i < 20);  
  •   
  •     /************************************************************************/  
  •     /* pass_encrypt[4] + name_encrypt_2[0] != pass_encrypt[2]
  •     || pass_encrypt[2] + name_encrypt_2[1] != 2 * pass_encrypt[4]
  •     name_encrypt_2[0] + name_encrypt_2[1] = pass_encrypt[4]
  •     || pass_encrypt[3] + name_encrypt_2[2] != pass_encrypt[0]
  •     || pass_encrypt[0] + name_encrypt_2[3] != 2 * pass_encrypt[3]
  •     name_encrypt_2[2] + name_encrypt_2[3] = pass_encrypt[3]
  •     || name_encrypt_2[4] + pass_encrypt[1] != 3 * name_encrypt_2[2] )// 验证
  •     pass_encrypt[1] = 3 * name_encrypt_2[2] - name_encrypt_2[4]
  •     */  
  •     /************************************************************************/  
  •   
  •     DWORD * ptrdwNameEncrypt = (DWORD*)name_encrypt_2;  
  •     DWORD ptrdwPassEncrypt[5] = { NULL };// 注意这里是 DWORD  
  •   
  •     ptrdwPassEncrypt[4] = ptrdwNameEncrypt[0] + ptrdwNameEncrypt[1];  
  •     ptrdwPassEncrypt[3] = ptrdwNameEncrypt[2] + ptrdwNameEncrypt[3];  
  •     ptrdwPassEncrypt[1] = 3 * ptrdwNameEncrypt[2] - ptrdwNameEncrypt[4];  
  •     ptrdwPassEncrypt[2] = ptrdwPassEncrypt[4] + ptrdwNameEncrypt[0];  
  •     ptrdwPassEncrypt[0] = 2 * ptrdwPassEncrypt[3] - ptrdwNameEncrypt[3];  
  •     memcpy_s(username, 20, (char*)ptrdwPassEncrypt, 20);  
  • }  



接下来重点分析密码加密的部分

发现有两处加密

第一处:


[cpp] view plain copy


  • .text:00401A72                 mov     al, [esp+edi+30h+index_code]  
  • .text:00401A76                 lea     ebx, [esp+30h+passByte]  
  • .text:00401A7A                 mov     byte ptr [esp+30h+passByte], al  
  • .text:00401A7E                 call    selectFromBaseCode  
  • .text:00401A83                 mov     [esp+edi+30h+index_code], al  
  • .text:00401A87                 inc     edi  
  • .text:00401A88                 cmp     edi, 4  
  • .text:00401A8B                 jl      short loc_401A72  


这里将密码以四字节为单位,逐字节传入 selectFromBaseCode 函数中返回的密码基串所在位置的索引

简单讲一下 selectFromBaseCode(自定义名称) 函数机制:

参数:BYTE (例如:=> C)

从密码基串中计算出 C 的位置索引

密码基串为:

[cpp] view plain copy


  • char* BASE_CODE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@%";  

返回:位置索引(例如:=> 2 错误返回 -1)
第二处(关键算法):

OD:


[cpp] view plain copy


  • .text:00401A91                 mov     al, cl          ; val[0]  
  • .text:00401A93                 add     al, al          ; val[0] *= 2  
  • .text:00401A95                 mov     dl, ch          ; val[1]  
  • .text:00401A97                 shr     dl, 4           ; val[1] >>= 4  
  • .text:00401A9A                 and     dl, 3           ; val[1] &= 3  
  • .text:00401A9D                 add     al, al          ; val[0] *= 2  
  • .text:00401A9F                 add     dl, al          ; val[1] += val[0]  
  • .text:00401AA1                 mov     al, [esp+30h+index_code+2] ; val[2]  
  • .text:00401AA5                 mov     [esp+30h+encryptKey], dl ; key0 = val[1]  
  • .text:00401AA9                 mov     dl, al          ; val[2]  
  • .text:00401AAB                 shr     dl, 2           ; val[2] >>= 2  
  • .text:00401AAE                 mov     cl, ch          ; val[1]  
  • .text:00401AB0                 shl     al, 6           ; y = val[2] << 6  
  • .text:00401AB3                 add     al, [esp+30h+index_code+3] ; y += val[3]  
  • .text:00401AB7                 and     dl, 0Fh         ; val[2] &= 0xF  
  • .text:00401ABA                 shl     cl, 4           ; val[1] <<= 4  
  • .text:00401ABD                 xor     dl, cl          ; val[2] ^= val[1]  
  • .text:00401ABF                 mov     [esp+30h+encryptKey+1], dl ; key1 = val[2]  
  • .text:00401AC3                 mov     [esp+30h+encryptKey+2], al ; key2 = y  


HAND: (这里IDA给出的整理不容易观察我们自己手动整理)

[cpp] view plain copy


  • key[0] = (val[1] >> 4) & 3 + (val[0] << 2)  
  • key[1] = (val[2] >> 2) & 0xF ^ (val[1] << 4)  
  • key[2] = val[2] << 6 + val[3]  

到这里核心算法就完成了,你会发现下面还有一部分加密,经过分析后会知道下面的加密跟这里的相同,只是对密码后3位进行了一下特殊处理

我们再假设一个公式:x(加密后的密码已知) + 逆算 = 注册码

接下来就是重点了,如何将算法逆回去就可以得出注册码了

从上面看是将 val[4] => key[3], 逆算就是要将 key[3] => val[4]

正算时是将4字节转成3字节,而逆算是将3字节转成4字节,这相当于有4个未知数,而只有3条关系式,乍看上去不好逆算,这里也是我花费时间比较长的地方,解决方法主要是得开窍哈哈,下面贴上主要分析过程:


[cpp] view plain copy


  • key0 = (val[1] >> 4) & 3 + (val[0] << 2)  
  • /**
  • 因为  (val[1] >> 4) & 3  => 0000 0011b
  •     +   (val[0] << 2)        => 1111 1100b
  •     =   key0
  • 所以
  •     val[0] = key0 >> 2
  •     val[1] = (key0 & 3) << 4
  • **/  
  • key1 = (val[2] >> 2) & 0xF ^ (val[1] << 4)  
  • /**
  • 同理  (val[2] >> 2) & 0xF  => 0000 1111b
  •     ^   (val[1] << 4)          => 1111 0000b
  •     =   key1
  • 所以
  •     val[1] = key1 >> 4
  •     val[2] = (key1 & 0xF) << 2
  • **/  
  • key2 = val[2] << 6 + val[3]  
  • /**
  • 因为  val[2] << 6             => 1100 0000b
  •     +   val[3]                => val[3] < 0x40
  •     =   key3
  • 所以
  •     val[2] = key3 >> 6
  •     val[3] = key3 & 0x3F
  • **/  
  • /********************************************************
  • 因为     val[1] = (key0 & 3) << 4
  • 和    val[1] = key1 >> 4
  • 均为真
  • 所以是或关系
  • 则    val[1] = (key0 & 3) << 4 | key1 >> 4
  • 同理     val[2] = (key1 & 0xF) << 2 | key3 >> 6
  • **/  

之后再以公式的形式展现一下现在的进度:验证用户名串(已知)+ 逆算(已知)= 注册码

算法函数:


[cpp] view plain copy


  • void calcSN(char* str) {  
  •     BYTE* curptr = (BYTE*)str;  
  •     BYTE idxArr[5] = { NULL };  
  •     char sn[260] = { NULL };  
  •     for (int i = 0, j = 0; i < 20; i += 3, j += 4, curptr += 3){  
  •         idxArr[0] = curptr[0] >> 2;  
  •         idxArr[1] = (curptr[0] & 3) << 4 | curptr[1] >> 4;  
  •         idxArr[2] = (curptr[1] & 0xF) << 2 | curptr[2] >> 6;  
  •         idxArr[3] = curptr[2] & 0x3F;  
  •   
  •         for (int idx = 0; idx < ((i == 18) ? 3 : 4); ++idx){// 最后3位稍做字符串上的处理  
  •             sn[j + idx] = BASE_CODE[idxArr[idx]];  
  •         }  
  •     }  
  •     strcpy_s(str, MAX_PATH, sn);  
  • }  


完整调用:

[cpp] view plain copy


  • int _tmain(int argc, _TCHAR* argv[]) {  
  •     char username[MAX_PATH] = { NULL };  
  •     printf("input username: ");  
  •     scanf_s("%s", username, MAX_PATH);  
  •     int namelen = strlen(username);  
  •     if (namelen < 6 || namelen > 20){  
  •         printf("username length must be between 6 and 20\n");  
  •     }  
  •     else {  
  •         EncryptUsername(username);  
  •         //EncryptPassword(NULL);  
  •         calcSN(username);  
  •         printf("sn: %s\n", username);  
  •     }  
  •     system("pause");  
  •     return 0;  
  • }  


到这里就结束了,在逆向过程中验证逆算的正确性可以结合OD调试结果,或直接修改内存来验证一下。

点评

百度一下发现了这个http://www.52pojie.cn/thread-112099-1-1.html  发表于 2016-5-19 15:23

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Hmily 发表于 2016-5-19 14:43
是原创吗?看起来是从其他地方复制来的,原文出处哪?
cohon 发表于 2016-5-19 14:57
wsd10086 发表于 2016-5-19 15:02
头像被屏蔽
新西兰天气晴 发表于 2016-5-19 15:14
提示: 作者被禁止或删除 内容自动屏蔽
ppszxc 发表于 2016-5-19 15:14

点评

应该是这个,如果是作者本人来申请,麻烦在博客中发一个内容,确认本帖是本人申请。  详情 回复 发表于 2016-5-19 15:57
尼霸Rc_ 发表于 2016-5-19 15:18
大兄弟下次记得去水印
qingliang87 发表于 2016-5-19 15:28
我怎么看不懂啊
Hmily 发表于 2016-5-19 15:57
ppszxc 发表于 2016-5-19 15:14
原版可能是这个http://blog.csdn.net/deathmemory/article/details/50999149

应该是这个,如果是作者本人来申请,麻烦在博客中发一个内容,确认本帖是本人申请。
qinyuanjun1993 发表于 2016-5-19 16:12
人呢???
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 21:38

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表