欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]TX2017游戏安全知识竞赛第二轮第一题

[原创]TX2017游戏安全知识竞赛第二轮第一题

TX2017游戏安全知识竞赛第二轮第一题

 

一、整体轮廓

IDA载入,发现关键位置函数sub_401694(),如下,其中的注释已经很清楚。重点就是sub_4011D4sub_4014B8sub_401634三个函数。


其中sub_4011D4处理通过调用zapus_get得到的16字节数据。

结果与如下16字节数据依次比较。

unsigned char fii[16] = {'G','S','L','a','b','1','7'};//对比字符串

unsigned int xy = GetCurrentProcessId();

unsigned int * fi1 = (unsigned int *)fii;

fi1[3] = xy;

sub_4014B8sub_401634都是对zapus_dll.dll文件求hash,结果都与0x614C5347比较。

二、重点函数sub_4011D4的逆向

IDA进入该函数,


转化为C代码如下,其中fcode是固定值,具体见代码,inbuf是测试数据,v5是处理结果。

int getTheKey1(){

       int i,j,k;

       unsigned char inbuf[] = "0123456789abcdef";

       DWORD v8 = 0x1000193;

       DWORD v7 = 0x811C9DC5;

       for (i = 0; i < 0x800; ++i ){

              v7 *= v8;

              fcode[i] ^= v7;

              v7 ^= fcode[i];

       }

       char v5[16] = {0}; 

       for (j = 0; j < 0x80; ++j ){

              unsigned char v3 = 0;

              for (k = 0; k < 0x80; ++k )   

                     v3 = ( ((signed int)fcode[16*j+k/8] >> k % 8) & ((signed int)inbuf[k/8]>> (7 - k % 8)) ^ v3) & 1;

              v5[j / 8] |= v3 << (7 - j % 8); 

       }

       int ret = 0;

       return ret;

}

分析易知,在已知处理结果的情况下求输入,实际上就是解异或方程组,代码如下。

unsigned char ut1[0x80][0x81]={0};//fcode2bit(j,k)

unsigned char ut2[0x80]={0};//inbuf2bit

unsigned char inb[0x10]={0};

 

//高斯消元法解异或方程

void Gauss ()

{

              int i,j,k;

              for (k=0;k<0x80;k++)

              {

                     //i=k;

                     for (i=k;i<0x80;i++)//对于k=0..N-1,找到一个M[i][k]不为0的行i 

                     {

                            if (ut1[i][k]==1) break;

                     }

 

                            for (j=0;j<=0x80;j++)  //把找到的第i行与第k行交换

                            {

                                   unsigned char tmp = ut1[k][j];

                                   ut1[k][j] = ut1[i][j];

                                   ut1[i][j] = tmp;

                            }

                            

                            for (i=0;i<0x80;i++)

                            {

                                   if (i!=k && ut1[i][k])

                                   {

                                          for (j=0;j<=0x80;j++)   // <=

                                                 ut1[i][j]=ut1[k][j]^ut1[i][j];

                                   }

                            }

              }

              for (i=0;i<0x80;i++)

              {

                     ut2[i]=ut1[i][0x80];

                     inb[i/8] |= ut2[i]<<(7-i%8);

              }

}

 

 

__declspec(dllexport) int zapus_get(char * c)

{

       int i,j,k;

 

       DWORD v8 = 0x1000193;//FNVHash常量

       DWORD v7 = 0x811C9DC5;

 

       unsigned char fii[16] = {'G','S','L','a','b','1','7'};//对比字符串

       unsigned int xy = GetCurrentProcessId();

       unsigned int * fi1 = (unsigned int *)fii;

       fi1[3] = xy;

 

       for (i = 0; i < 0x800; ++i )

       {

              v7 *= v8;

              fcode[i] ^= v7;

              v7 ^= fcode[i];

       }

 

       for (j = 0; j < 0x80; ++j )//常量,转化为异或方程组的系数矩阵

       {

              for (k = 0; k < 0x80; ++k )   

              {

                     ut1[j][k] = (fcode[16*j+k/8]>>k%8) & 1;

              }

       }

 

       for(i=0;i<0x80;i++) //对比字符串转化为异或方程组的结果矩阵

       {

              ut1[i][0x80] = (fii[i/8]>> (7 - i % 8))&1;

              //            printf("%x",ut3[i]);

       }

 

       Gauss();//高斯消元法解方程

 

       /*

       //此题如果不要求算法分析,则可在此处算好结果后,直接传回主程序32字节,直接传全0都可满足要求

       for (int j = 0; j < 0x80; ++j )

       {

              unsigned char v3 = 0;

              for (int k = 0; k < 0x80; ++k )      

                     v3 = ( ((signed int)fcode[16*j+k/8] >> k % 8) & ((signed int)inbuf[k/8]>> (7 - k % 8)) ^ v3) & 1;

              //v3 = (((signed int)(unsigned __int8)*(&fcode[16 * j] + k / 8) >> k % 8) & ((signed int)inbuf[k/8]>> (7 - k % 8)) ^ v3) & 1;

              inbuf[16+j / 8] |= v3 << (7 - j % 8); 

       }

       memcpy(c,inbuf,32);

       */

       memcpy(c,inb,16);//将解方程结果传回主程序。

       return 16;

}

然而,由于zapus_get返回值没有限制长度,且返回值紧贴预设的比较字符串,导致可以直接覆盖预设字符串达到目的,进而跳过此步验证。

由于编写的zapus_get和题目预设的返回方式不一样,预设为“retn 4”返回(可能为delphi或者Borland C++),而VC++默认为“retn”返回,导致堆栈不平衡,出现错误。


手动修改程序的返回方式为“retn 4”如下,即可完美实现该函数。

二、重点函数sub_4014B8的逆向

该函数实际就是crc32,核心如下,下图生成CRC32使用的表。

下图处理输入

因为表固定,不做重点分析,输入处理过程可化简为:

//CRC32编码

int getTheKey2(unsigned char * buf,int bufsize)

{

       DWORD ret = -1;

       DWORD * bb = (DWORD*)aa;

       for(int i=0;i<bufsize;i++)

       {

              int xt = (ret&0xff)^buf[i];

              ret = bb[1+xt]^(ret>>8);

       }

       

       return ~ret;

}

容易发现通过控制最后的4个字节,即可完全控制最终生成的CRC32结果,那么我们可以通过在文件后加4个字节来达到控制CRC32结果为0x614C5347的目的,求此4个字节的代码如下。

unsigned char buf[4]={0};

int get2(DWORD a)

{

       DWORD confirm1 = 0x9e; //0x9eb3acb8 == ~0x614C5347

       DWORD confirm2 = 0xb3;

       DWORD confirm3 = 0xac;

       DWORD confirm4 = 0xb8;

       DWORD tmp,x[4]={0};

       int i,y[4]={0};

       DWORD * bb = (DWORD*)aa;

       for(i=1;i<=0x100;i++){

              tmp = bb[i]>>24;

              if(tmp==confirm1){

                     x[0] = bb[i];

                     y[0] = i;

                     break;

              }

       }

       tmp = x[0]>>16;

       tmp = tmp&0xff;

       confirm2 = confirm2^tmp;

       for(i=1;i<=0x100;i++){

              tmp = bb[i]>>24;

              if(tmp==confirm2){

                     x[1] = bb[i];

                     y[1] = i;

                     break;

              }

       }

       tmp = x[0]>>8;

       tmp = tmp&0xff;

       confirm3 = confirm3^tmp;

       tmp = x[1]>>16;

       tmp = tmp&0xff;

       confirm3 = confirm3^tmp;

       for(i=1;i<=0x100;i++){

              tmp = bb[i]>>24;

              if(tmp==confirm3){

                     x[2] = bb[i];

                     y[2] = i;

                     break;

              }

       }

 

       tmp = x[0];

       tmp = tmp&0xff;

       confirm4 = confirm4^tmp;

       tmp = x[1]>>8;

       tmp = tmp&0xff;

       confirm4 = confirm4^tmp;

       tmp = x[2]>>16;

       tmp = tmp&0xff;

       confirm4 = confirm4^tmp;

       for(i=1;i<=0x100;i++)

       {

              tmp = bb[i]>>24;

              if(tmp==confirm4){

                     x[3] = bb[i];

                     y[3] = i;

                     break;

              }

       }

       DWORD ret = a;//0x32f38783;

       for(i=3;i>=0;i--){

              buf[3-i] = ((ret&0xff)^y[i]-1);

              ret = x[i]^(ret>>8);

       }

       return 0;

}

通过此函数还可以求出,在CRC32结果为0x614C5347的情况下,在文件后附加上整数倍的{0x5C, 0xA4, 0x88, 0xC9CRC32结果保持不变。

二、重点函数sub_401634的逆向

该函数最简单,但逆向最麻烦,IDA中如下

化简为:

//FNV-1a Hash运算

DWORD getTheKey3(unsigned char * buf,int bufsize){

    DWORD ret = 0x811C9DC5;

       for(int i=0;i<bufsize;i++){

              DWORD xx = (DWORD)buf[i];

              ret = 0x1000193 * (ret^xx);

       }

       return ret;

}

此函数没有找到更好的破解办法,考虑枚举。枚举过程如下:

(1)将文件最后一个字节作为变量(0x00-0xff)。

(2)对每一个字节值,在文件后增加4字节使CRC32结果为0x614C5347

(3)依次添加{0x5C, 0xA4, 0x88, 0xC9},保持CRC32不变,求FNv-1a的值,如果等于0x614C5347或者出现循环,则结束循环,转到第一步。

(4)代码如下:

int get3(DWORD a){

       unsigned char dd[4] = {0x5C, 0xA4 ,0x88 ,0xC9};

       DWORD ret = a;

       int i,j;

       for(i=0;;i++)//614C5347<-A19947FD<-CE19CA2F<-92F5E675<-F4659CD7<-0D33122D<-F32BF53F<-66263925<-7BDE6D67<-127F995D<-CDAA8F4F<-8379C0D5

       {

              for(j=0;j<4;j++)

              {

                     DWORD xx = (DWORD)dd[j];

                     ret = 0x1000193 * (ret^xx);//359C449B(1000193^-1)

              }

              if(ret ==0x614C5347  || ret == a)//0x614C5347

              {

                     break;

              }

              

       }

       if(ret ==0x614C5347)

       {

              return i;

       }

       else

       {

              return -1;

       }

}

 

       for(unsigned char i=0;i<0xff;i++)

       {

              bbuf[xs-1]=i;

       

              DWORD yy1 = getTheKey2(bbuf,xs);

              get2(yy1);

 

              bbuf[xs]= buf[0];

              bbuf[xs+1]= buf[1];

              bbuf[xs+2]= buf[2];

              bbuf[xs+3]= buf[3];

 

              //DWORD yy1 = sub_1244(bbuf, xs);

              

              DWORD yy2 = getTheKey3(bbuf,xs+4);

 

              int udd = get3(yy2);

              if(udd!=-1)

              {

                     printf("%02X %08X\n",i,udd);

              }

       }

对此DLL文件(和之前生成的DLL文件有关)得到结果如下:

i=0x20时,需要加入的{0x5C, 0xA4, 0x88, 0xC9}串长度为0x2E3BCA2(已经足够小),即替换生成的dll文件的最后一个字节为0x20,然后编码如下:

   bbuf[xs-1]=0x20;

       DWORD yy1 = getTheKey2(bbuf,xs);

       get2(~yy1);

       bbuf[xs]= buf[0];

       bbuf[xs+1]= buf[1];

       bbuf[xs+2]= buf[2];

       bbuf[xs+3]= buf[3];      

       DWORD yy2 = getTheKey3(bbuf,xs+4);

       int udd = get3(yy2);

       unsigned char * memm = (unsigned char * )malloc(udd*4+8+xs);

       memcpy(memm,bbuf,xs+4);

       for(int i=0;i<=udd;i++)

       {

              memm[xs+4+i*4+0] = 0x5C;

              memm[xs+4+i*4+1] = 0xA4;

              memm[xs+4+i*4+2] = 0x88;

              memm[xs+4+i*4+3] = 0xC9;

       }

       fp = fopen("zapus_dll1.dll","wb");

       fwrite(memm,udd*4+8+xs,1,fp);

       fclose(fp);

生成新dll文件zapus_dll1.dll,将此文件重命名为zapus_dll.dll,放在zapus.exe同目录下,得到结果如下:

全文完。

上传的附件 src.zip

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功