吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 21898|回复: 53
收起左侧

[原创] 2016游戏安全技术竞赛题PC第一题逆向分析详解

  [复制链接]
DeathMemory 发表于 2016-5-31 10:22
本帖最后由 DeathMemory 于 2016-5-31 10:35 编辑

最近看到腾讯的 2016游戏安全技术竞赛,咱不是大学生只能以凑热闹的形式参与一下。
遂下载第一题进行分析
由于要求写注册机,所以这里不考虑暴破
先看一下注册机成功结果:
01.png
正式开始分析:
从图中可以看出注册成功,失败都以 static text 控件来显示
用OD载入,搜索注册关键字符都没有找到
在 GetWindowTextW GetDlgItemTextWGetDlgItem 下断,在GetDlgItem 处断下,向上返回到程序领域就是注册算法主体了。
单步向下调试,先走一遍,最大程度的浏览一遍有用信息,这里可以发现,注册信息是在代码段拼接的
02.png
[Asm] 纯文本查看 复制代码
00CA1F90   .  8D47 FA                     lea     eax, dword ptr [edi-6]
00CA1F93   .  83F8 0E                     cmp     eax, 0E
00CA1F96   .  0F87 4B020000               ja      00CA21E7

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

接下来是一系列加密算法,用IDA载入并整理
下面是用户名的第一次加密
OD:
[Asm] 纯文本查看 复制代码
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:
[C++] 纯文本查看 复制代码
  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:
[Asm] 纯文本查看 复制代码
.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:
[C++] 纯文本查看 复制代码
  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 );

再往下就是加密后的用户名和密码进行验证:
[C++] 纯文本查看 复制代码
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 的值
[C++] 纯文本查看 复制代码
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[i] / 10;
                *(int*)&name_encrypt_2[i] = 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);
}

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

发现有两处加密
第一处:
[Asm] 纯文本查看 复制代码
.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 的位置索引
密码基串为:
[C++] 纯文本查看 复制代码
char* BASE_CODE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@%";

返回:位置索引(例如:=> 2 错误返回 -1)
第二处(关键算法):
OD:
[Asm] 纯文本查看 复制代码
.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给出的整理不容易观察我们自己手动整理)
[C++] 纯文本查看 复制代码
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条关系式,乍看上去不好逆算,这里也是我花费时间比较长的地方,解决方法主要是得开窍哈哈,下面贴上主要分析过程:
[C++] 纯文本查看 复制代码
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
**/

之后再以公式的形式展现一下现在的进度:验证用户名串(已知)+ 逆算(已知)= 注册码
算法函数:
[C++] 纯文本查看 复制代码
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);
}

完整调用:
[C++] 纯文本查看 复制代码
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://download.csdn.net/download/deathmemory/9474750

免费评分

参与人数 22吾爱币 +1 热心值 +22 收起 理由
daizp + 1 + 1 谢谢@Thanks!
炒鸡之路 + 1 我很赞同!
immr.x + 1 谢谢@Thanks!
Alog + 1 热心回复!
xiejunliu + 1 感谢您的宝贵建议,我们会努力争取做得更好!
小hihi + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
丶峰宇 + 1 热心回复!
c402168009 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
安静的小酒吧 + 1 谢谢@Thanks!
Terrorblade + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
胡子啪啪 + 1 用心讨论,共获提升!
F_雪 + 1 用心讨论,共获提升!
yAYa + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
海盗小K + 1 我很赞同!
回归自然 + 1 我很赞同!
狗蛋老汉 + 1 我很赞同!
wnagzihxain + 1
yang936 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
Sound + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
我爱小白兔 + 1 我很赞同!
myqqq + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
scx + 1 已答复!

查看全部评分

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

Sound 发表于 2016-5-31 15:36
新人贴 加精鼓励。
 楼主| DeathMemory 发表于 2016-6-1 11:05
wsxqaz13245 发表于 2016-6-1 09:10
@DeathMemory  楼主请教个问题,你都是用什么逆向工具逆向的?比如IDA,OD,还有什么别的IDE吗?比如说第三张 ...

呃,一共就两张截图,黑底的是插入的代码段格式,本文中主要也就用到了IDA和OD,实际应用中还可以配合使用一些查壳工具PEID等其他工具,看需要
wsxqaz13245 发表于 2016-6-2 09:05
DeathMemory 发表于 2016-6-1 11:05
呃,一共就两张截图,黑底的是插入的代码段格式,本文中主要也就用到了IDA和OD,实际应用中还可以配合使 ...

好的,谢谢楼主
Hmily 发表于 2016-5-31 10:31
不错,下载地址丢了呦~
 楼主| DeathMemory 发表于 2016-5-31 10:37
Hmily 发表于 2016-5-31 10:31
不错,下载地址丢了呦~

本来想以添加链接的方式增加下载地址,后来发现添加的链接无效,只好把地址都贴出来
Sound 发表于 2016-5-31 10:53 来自手机
good , .pc时候再处理 make
a5680497 发表于 2016-5-31 11:02
哇,膜拜大神啊!!!
王美君 发表于 2016-5-31 11:25
练习后是各种失败,郁闷的
 楼主| DeathMemory 发表于 2016-5-31 11:30
王美君 发表于 2016-5-31 11:25
练习后是各种失败,郁闷的

加油~~~等搞出来了就会觉得很畅快哈哈
天地无空 发表于 2016-5-31 12:58
谢谢分享,学习一下
头像被屏蔽
红客鄙哥 发表于 2016-5-31 15:43
woc好厉害。。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2024-4-19 20:10

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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