loudy 发表于 2017-8-7 21:22

Gslab游戏安全竞赛社会组WP(第一轮进阶版)

本帖最后由 loudy 于 2017-10-17 08:35 编辑

最近新开了一个微信公众号:风云处(fyc1687),欢迎大家多多交流。其中我会逐步把我学习逆向的经验、实例、技巧分享出来。

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




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

二、关键函数剖析
(1)sub_4033A0如下图,IDA中已经标注,key长度为0x27,其中连接符“-”共7个,有效位0x20个,分成8段,每段4byte,每个有效byte均为0-9或a-f或A-F(转化为a-f)。




(2)sub_4034E0该函数经过调试化简,可以与以下函数等价,key为输入,param为a1、a2、a3、a6。

int get_formula_param(unsigned __int64 * param,char * key)
{
   if(strlen(key)!=0x27)
   {
         return -1;
   }
   if(key!='-' ||key!='-' ||key!='-' ||key!='-' ||key!='-' ||key!='-' ||key!='-')
   {
         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>='0'&&key<='9'||key>='a'&&key<='f'))
            {
                   return -3;
            }
         }
   }
   unsigned int s0,s1,s2,s3,s4,s5,s6,s7;
   for(int i=0;i<4;i++)
   {
         s0 = (unsigned int)key;
         s1 = (unsigned int)key;
         s2 = (unsigned int)key;
         s3 = (unsigned int)key;
         s7 = (unsigned int)key;
         s6 = (unsigned int)key;
         s5 = (unsigned int)key;
         s4 = (unsigned int)key;
   }
   unsigned x0 = s0*s4;
   x0 = x0<<0x10;
   param = x0 + s0^s4 + s0%(s4+1)+1 + s0/(s4+1);
   x0 = s1^s5;
   x0 = x0<<0x10;
   param = x0 + s1%(s5+3)+s1/(s5+1)+5 +s1+s5;
   x0 = s2/(s6+3);
   x0 = x0<<0x10;
   param = x0^(s2*s6)+s2%(s6+7)+ 5 + s2+s6;

   x0 = s3+s7;
   x0 = x0<<0x10;
   param = x0*(s3/(s7+2)) + (s3%(s7+5))+7 + s3*s7;
}

(3)sub_403860和sub_405FB0涉及到大数的运算以及随机值生成算法,过程比较复杂,应该用到了某大数库,但没找到对应的,解题过程中,直接调用了原程序,得到RSA的N和E(附件程序中使用了Miracl库)。
(4)sub_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 >> 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 = base64char[(int)current];

      current = ( (unsigned char)(bindata << 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 = base64char[(int)current];
            base64 = '=';
            base64 = '=';
            break;
      }
      current |= ( (unsigned char)(bindata >> 4) ) & ( (unsigned char) 0x0F );
         for(k=0;k<64;k++)
         {
            unsigned h = k>>4;
            h = k^h;
               
            if(h == current)
            {
                   current = k;
                   break;
            }
         }

      base64 = base64char[(int)current];

      current = ( (unsigned char)(bindata << 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 = base64char[(int)current];
            base64 = '=';
            break;
      }
      current |= ( (unsigned char)(bindata >> 6) ) & ( (unsigned char) 0x03 );
         for(k=0;k<64;k++)
         {
            unsigned h = k>>4;
            h = k^h;
               
            if(h == current)
            {
                   current = k;
                   break;
            }
         }
      base64 = base64char[(int)current];

      current = ( (unsigned char)bindata ) & ( (unsigned char)0x3F ) ;
         for(k=0;k<64;k++)
         {
            unsigned h = k>>4;
            h = k^h;
               
            if(h == current)
            {
                   current = k;
                   break;
            }
         }
      base64 = base64char[(int)current];
    }
    base64 = '\0';
    return base64;
}

int base64_decode( const char * base64, unsigned char * bindata )
{
    int i, j;
    unsigned char k;
    unsigned char temp;
    for ( i = 0, j = 0; base64 != '\0' ; i += 4 )
    {
      memset( temp, 0xFF, sizeof(temp) );
      for ( k = 0 ; k < 64 ; k ++ )
      {
            if ( base64char == base64 )
                temp= k ^ (k>>4);
      }
      for ( k = 0 ; k < 64 ; k ++ )
      {
            if ( base64char == base64 )
                temp= k ^ (k>>4);
      }
      for ( k = 0 ; k < 64 ; k ++ )
      {
            if ( base64char == base64 )
                temp= k ^ (k>>4);
      }
      for ( k = 0 ; k < 64 ; k ++ )
      {
            if ( base64char == base64 )
                temp= k ^ (k>>4);
      }

      bindata = ((unsigned char)(((unsigned char)(temp << 2))&0xFC)) |
                ((unsigned char)((unsigned char)(temp>>4)&0x03));
      if ( base64 == '=' )
            break;

      bindata = ((unsigned char)(((unsigned char)(temp << 4))&0xF0)) |
                ((unsigned char)((unsigned char)(temp>>2)&0x0F));
      if ( base64 == '=' )
            break;

      bindata = ((unsigned char)(((unsigned char)(temp << 6))&0xF0)) |
                ((unsigned char)(temp&0x3F));
    }
    return j;
}

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

然后转化为16段,每段分别转化为一个大数(十进制),共16个大数,转化方式为:
X(1byte,不相关)大小y(2byte)数据(y字节)

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

共16字节,前8byte作为a4,后8byte为a5。
(5)sub_403160该函数看起来简单,但因为在32位下实现64位计算,分析起来也难。


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

三、注册机编写
(1)从UserName得到a1、a2、a3、a6(编码实现),和大数E、N(直接从原程序CrackMe.exe中提取)。
(2)从a1、a2、a3、a6计算a4、a5。
(3)从a4、a5得到RSA加密运算结果(本文认为解密结果就是2位16进制,当然可以在满足低位不变得情况下,任意扩展加密结果,得到不同的RegCode)。
(4)将大整数N分解为P和Q(修改ppsiqs.exe程序进行)。
(5)从P、Q得到私钥D。
(6)用D和N对第(3)步的结果解密,得到16个不同的大数。
(7)对大数按如下规则编排,得到base64解码结果。

X(1byte,不相关)大小y(2byte)数据(y字节)

(8)进行base64编码,得到RegCode。

四、使用说明
(1)CrackMe.exe、ppsiqs2.exe、dbg.exe三个文件放在同一目录运行。
(2)CrackMe.exe(在偏移0x403c88手动加了断点,方便提取内存数据,且决定标准还是进阶的跳转改为只有进阶版)和ppsiqs2.exe(删除了循环,输入从控制台改为文件)均经过手动修改。
(3)dbg.exe是附件代码生成的的程序。
(4)使用时,运行dbg.exe,在跟随运行起来的CrackMe.exe的UserName处填上正确的userName,点击按钮“go”,在dbg.exe的控制台界面可以看到正确的RegCode(另外在当前目录下还会生成out.txt文件,内容即为RegCode)。

输入该RegCode,成功注册。


结束,代码见附件中main.cpp。该版本利用了调试器原理,没有完全编码。
后来把生成RSA参数的过程看了一下,得到另一版见附件2,其中有源码和可执行文件,分析过程免去,无太多价值。
附件(http://bbs.pediy.com/attach-download-128525.htm)

Hmily 发表于 2017-8-10 16:08

loudy 发表于 2017-8-10 15:30
谢谢,没经常发帖,不太会用

你可以在源代码格式下复制进去,再编辑好点,代码用论坛的代码框,也可以用markdown插件写。

loudy 发表于 2017-8-10 22:35

Hmily 发表于 2017-8-10 16:08
你可以在源代码格式下复制进去,再编辑好点,代码用论坛的代码框,也可以用markdown插件写。

ok,感谢指点

WHO 发表于 2017-8-7 21:34

防盗链6666

抢我所爱的你 发表于 2017-8-7 22:00

有没有跟我一样看不懂的

演员风 发表于 2017-8-7 22:47

+1 ...{:1_932:}

海之帆 发表于 2017-8-7 22:58

我是水手,基本看不懂的

luckyangman 发表于 2017-8-8 01:22

没有看懂,够难呀。

dad9 发表于 2017-8-8 08:41

666666 终于有人放出来了,表示当时看了没做出来

孤妄啊 发表于 2017-8-8 08:54

感觉好难的样子,我还是算了

lyq1732 发表于 2017-8-8 10:14

厉害啊!666

946885280 发表于 2017-8-8 12:43

萌新默默的研究中....
页: [1] 2
查看完整版本: Gslab游戏安全竞赛社会组WP(第一轮进阶版)