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) loudy 发表于 2017-8-10 15:30
谢谢,没经常发帖,不太会用
你可以在源代码格式下复制进去,再编辑好点,代码用论坛的代码框,也可以用markdown插件写。 Hmily 发表于 2017-8-10 16:08
你可以在源代码格式下复制进去,再编辑好点,代码用论坛的代码框,也可以用markdown插件写。
ok,感谢指点 防盗链6666 有没有跟我一样看不懂的 +1 ...{:1_932:} 我是水手,基本看不懂的 没有看懂,够难呀。 666666 终于有人放出来了,表示当时看了没做出来 感觉好难的样子,我还是算了 厉害啊!666 萌新默默的研究中....
页:
[1]
2