首页
社区
课程
招聘
[原创]TX2017游戏安全知识竞赛初赛第1题进阶版
发表于: 2017-7-27 18:51 3598

[原创]TX2017游戏安全知识竞赛初赛第1题进阶版

loudy 活跃值
10
2017-7-27 18:51
3598

初赛第一题PC方向分析文档(进阶版)

一、整体流程

IDA载入,简单分析发现,主要处理流程在sub_403B70处,如下图,函数不长,但是很复杂。




   进阶版引进了RSA思想。上图sub_4033A0检查userName的合法性;sub_4034E0userName生成464位整数,具体算法后面分析;sub_403860sub_405FB0两个函数从输入的userName生成了RSANEsub_405CA0对输入的RegCode进行处理,得到16字节长数组(得不到则失败),即264位整数;接着sub_403160检测之前生成的664位整数是否满足其中公式。

二、关键函数剖析

1sub_4033A0

如下图,IDA中已经标注,key长度为0x27,其中连接符“-”7个,有效位0x20个,分成8段,每段4byte,每个有效byte均为0-9a-fA-F(转化为a-f)。



2sub_4034E0

该函数经过调试化简,可以与以下函数等价,key为输入,parama1a2a3a6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
int get_formula_param(unsigned __int64 * param,char * key)
{
     if(strlen(key)!=0x27)
     {
         return -1;
     }
     if(key[4]!='-' ||key[9]!='-' ||key[14]!='-' ||key[19]!='-' ||key[24]!='-' ||key[29]!='-' ||key[34]!='-')
     {
         return -2;
     }
     for(int i=0;i<0x27;i++)
     {
         if(i!=4 && i!=9 && i!=14 && i!=19 && i!=24 && i!=29 && i!=34)
         {
              if(!(key[i]>='0'&&key[i]<='9'||key[i]>='a'&&key[i]<='f'))
              {
                   return -3;
              }
         }
     }
     unsigned int s0[4],s1[4],s2[4],s3[4],s4[4],s5[4],s6[4],s7[4];
     for(int i=0;i<4;i++)
     {
         s0[i] = (unsigned int)key[i];
         s1[i] = (unsigned int)key[5+i];
         s2[i] = (unsigned int)key[10+i];
         s3[i] = (unsigned int)key[15+i];
         s7[i] = (unsigned int)key[20+i];
         s6[i] = (unsigned int)key[25+i];
         s5[i] = (unsigned int)key[30+i];
         s4[i] = (unsigned int)key[35+i];
     }
     unsigned x0 = s0[0]*s4[0];
     x0 = x0<<0x10;
     param[0] = x0 + s0[1]^s4[1] + s0[2]%(s4[2]+1)+1 + s0[3]/(s4[3]+1);
     x0 = s1[0]^s5[0];
     x0 = x0<<0x10;
     param[1] = x0 + s1[1]%(s5[1]+3)+s1[2]/(s5[2]+1)+5 +s1[3]+s5[3];
     x0 = s2[0]/(s6[0]+3);
     x0 = x0<<0x10;
     param[2] = x0^(s2[1]*s6[1])+s2[2]%(s6[2]+7)+ 5 + s2[3]+s6[3];
  
     x0 = s3[0]+s7[0];
     x0 = x0<<0x10;
     param[3] = x0*(s3[1]/(s7[1]+2)) + (s3[2]%(s7[2]+5))+7 + s3[3]*s7[3];
}

3sub_403860sub_405FB0

涉及到大数的运算以及随机值生成算法,过程比较复杂,应该用到了某大数库,但没找到对应的,解题过程中,直接调用了原程序,得到RSANE(附件程序中使用了Miracl)

4sub_405CA0

该函数比较复杂,首先是对RegCode进行base64解码,如下图:

base64解码做了变形,首先常量从“ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/”变成了“OPWvYny#Nopz0$HI34QRSG@dJKq7fghD9Zi*kAB8rsFu56L&Ca^2tTUVEewxlm+/”,如下图。


其次,下图红线处在基本base64上加了异或操作。

因此将base64编解码做变形如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
const char * base64char = "OPWvYny#Nopz0$HI34QRSG@dJKq7fghD9Zi*kAB8rsFu56L&Ca^2tTUVEewxlm+/";
  
char * base64_encode( const unsigned char * bindata, char * base64, int binlength )
{
    int i, j;
    unsigned char current;
     unsigned char k;
  
    for ( i = 0, j = 0 ; i < binlength ; i += 3 )
    {
        current = (bindata[i] >> 2) ;
        current &= (unsigned char)0x3F;
         for(k=0;k<64;k++)
         {
              unsigned h = k>>4;
              h = k^h;
               
              if(h == current)
              {
                  current = k;
                   break;
              }
         }
        base64[j++] = base64char[(int)current];
  
        current = ( (unsigned char)(bindata[i] << 4 ) ) & ( (unsigned char)0x30 ) ;
  
        if ( i + 1 >= binlength )
        {
            for(k=0;k<64;k++)
              {
                   unsigned h = k>>4;
                   h = k^h;
                    
                   if(h == current)
                   {
                       current = k;
                       break;
                   }
              }
              base64[j++] = base64char[(int)current];
            base64[j++] = '=';
            base64[j++] = '=';
            break;
        }
        current |= ( (unsigned char)(bindata[i+1] >> 4) ) & ( (unsigned char) 0x0F );
         for(k=0;k<64;k++)
         {
              unsigned h = k>>4;
              h = k^h;
               
              if(h == current)
              {
                   current = k;
                   break;
              }
         }
  
        base64[j++] = base64char[(int)current];
  
        current = ( (unsigned char)(bindata[i+1] << 2) ) & ( (unsigned char)0x3C ) ;
          
        if ( i + 2 >= binlength )
        {
            for(k=0;k<64;k++)
              {
                   unsigned h = k>>4;
                   h = k^h;
                    
                   if(h == current)
                   {
                       current = k;
                       break;
                   }
              }
              base64[j++] = base64char[(int)current];
            base64[j++] = '=';
            break;
        }
        current |= ( (unsigned char)(bindata[i+2] >> 6) ) & ( (unsigned char) 0x03 );
         for(k=0;k<64;k++)
         {
              unsigned h = k>>4;
              h = k^h;
               
              if(h == current)
              {
                   current = k;
                   break;
              }
         }
        base64[j++] = base64char[(int)current];
  
        current = ( (unsigned char)bindata[i+2] ) & ( (unsigned char)0x3F ) ;
         for(k=0;k<64;k++)
         {
              unsigned h = k>>4;
              h = k^h;
               
              if(h == current)
              {
                   current = k;
                   break;
              }
         }
        base64[j++] = base64char[(int)current];
    }
    base64[j] = '\0';
    return base64;
}
  
int base64_decode( const char * base64, unsigned char * bindata )
{
    int i, j;
    unsigned char k;
    unsigned char temp[4];
    for ( i = 0, j = 0; base64[i] != '\0' ; i += 4 )
    {
        memset( temp, 0xFF, sizeof(temp) );
        for ( k = 0 ; k < 64 ; k ++ )
        {
            if ( base64char[k] == base64[i] )
                temp[0]= k ^ (k>>4);
        }
        for ( k = 0 ; k < 64 ; k ++ )
        {
            if ( base64char[k] == base64[i+1] )
                temp[1]= k ^ (k>>4);
        }
        for ( k = 0 ; k < 64 ; k ++ )
        {
            if ( base64char[k] == base64[i+2] )
                temp[2]= k ^ (k>>4);
        }
        for ( k = 0 ; k < 64 ; k ++ )
        {
            if ( base64char[k] == base64[i+3] )
                temp[3]= k ^ (k>>4);
        }
  
        bindata[j++] = ((unsigned char)(((unsigned char)(temp[0] << 2))&0xFC)) |
                ((unsigned char)((unsigned char)(temp[1]>>4)&0x03));
        if ( base64[i+2] == '=' )
            break;
  
        bindata[j++] = ((unsigned char)(((unsigned char)(temp[1] << 4))&0xF0)) |
                ((unsigned char)((unsigned char)(temp[2]>>2)&0x0F));
        if ( base64[i+3] == '=' )
            break;
  
        bindata[j++] = ((unsigned char)(((unsigned char)(temp[2] << 6))&0xF0)) |
                ((unsigned char)(temp[3]&0x3F));
    }
    return j;
}

此变形base64RegCode解码后,每个byte都不能大于0x31,如下:

然后转化为16段,每段分别转化为一个大数(十进制),共16个大数,转化方式为:

X1byte,不相关)

大小y2byte

数据(y字节)

  其中每段第一个字节不参与运算,可以任意,故同一个userName可以有很多RegCode。然后利用之前生成的DN对每个大数进行RSA加密(幂模运算),取运算结果16进制的低2位作为一字节。

16字节,前8byte作为a4,后8bytea5

5sub_403160

该函数看起来简单,但因为在32位下实现64位计算,分析起来也难。



经过分析可以发现,前一个if比较(a6 * a1 + a2) * a6 + a3a4*a6 + a5,后一个if比较(a2-a4)*(a2-a4)4*a1*(a3-a5),均相等则注册成功。由于a1a2a3a6key有关,相当于已知量,a4a5code有关相当于未知量,则由

三、注册机编写

1)从UserName得到a1a2a3a6(编码实现),和大数EN(直接从原程序CrackMe.exe中提取)。

2)从a1a2a3a6计算a4a5

3)从a4a5得到RSA加密运算结果(本文认为解密结果就是216进制,当然可以在满足低位不变得情况下,任意扩展加密结果,得到不同的RegCode)。

4)将大整数N分解为PQ(修改ppsiqs.exe程序进行)。

5)从PQ得到私钥D

6)用DN对第(3)部的结果解密,得到16个不同的大数。

7)对大数按如下规则编排,得到base64解码结果。

X1byte,不相关)

大小y2byte

数据(y字节)

8)进行base64编码,得到RegCode

四、使用说明

1CrackMe.exeppsiqs2.exedbg.exe三个文件放在同一目录运行。

2CrackMe.exe(在偏移0x403c88手动加了断点,方便提取内存数据,且决定标准还是进阶的跳转改为只有进阶版)和ppsiqs2.exe(删除了循环,输入从控制台改为文件)均经过手动修改。

3dbg.exe是附件代码生成的的程序。

4)使用时,运行dbg.exe,在跟随运行起来的CrackMe.exeUserName处填上正确的userName,点击按钮“go”,在dbg.exe的控制台界面可以看到正确的RegCode(另外在当前目录下还会生成out.txt文件,内容即为RegCode)。

输入该RegCode,成功注册。

结束,代码见附件中main.cpp。

该版本利用了调试器原理,没有完全编码,判无效。


后来把生成RSA参数的过程看了一下,得到另一版见附件2,其中有源码和可执行文件,分析过程免去,无太多价值。



[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

上传的附件:
收藏
点赞
支持
分享
最新回复 (1)
雪    币: 16690
活跃值: (7016)
能力值: ( LV13,RANK:923 )
在线值:
发帖
回帖
粉丝
2

sub_403210作用  好像只是判断有没有8个'-',你在哪里找到每段只能输入4个字节的哦? 给我说下嘛。我没找到

2017-8-16 20:04
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册