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

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

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

初赛第一题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

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编解码做变形如下:

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,其中有源码和可执行文件,分析过程免去,无太多价值。



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

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

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

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