额度更深刻 发表于 2018-4-27 16:30

2018腾讯游戏安全竞赛第一轮PC标准版分析

本帖最后由 额度更深刻 于 2018-4-27 16:52 编辑

大三狗,水平有限,感觉要止步第二轮了。
第一题逆向没什么难度,主要是考察对算法的还原,最重要的是耐心
题目:

输入正确的UserName和RegCode,显示注册成功。

用OD通过对GetDlgItem下断,找到按钮相应事件004026F0处用IDA分析
一.分析4026F0函数发现只要sub_405510函数返回值为1,就显示注册成功,那么显然sub_405510函数是关键
在上面我还发现了这样一行代码(传图麻烦,蓝色的文字就是IDA显示的伪代码)
v20 = SendMessageW(*((HWND *)v4 + 8), 0xF0u, 0, 0) == 1;// /v20为1 说明你选的标准版 为0选的是进阶版
用来标识标准版还是进阶版,暂且不管
二.分析405510函数主要如下:



405510函数进去首先对用户名做一个判断。起名为checkuser
if ( checkuser((int *)&str_name) )
1.分析checkuser函数
用OD发现这个函数在压参数入栈时,压的是我们输入的用户名,所以认为他是对用户名是否输入合法做一个判断
if ( a1 != 39 )                            // a1是我们输入的用户名长度
return 0;首先是进行长度判断,长度不满足0x27,直接返回false

if ( v7 )                                    
{   
v9 = v7;                                                                                    // 每执行一次加一 执行39次                                 
                                                            // 通过toupper函数 把小写字符c转化为大写字母   
do    {      
         *((_BYTE *)v23 + v8) = toupper(*((char *)v6 + v8));   
         ++v8;   
         }   
          while ( v8 != v9 );   
            v1 = a1;
}
其次通过toupper函数将输入用户名中的小写字母转换成大写字母

sub_402A70(&v16, &unk_5AC430, 1u);            // &unk_5AC430是#
sub_404D70((int)v24, (int)v1, *(LPVOID *)&v16, v17, v18, v19, v20, v21);
v10 = (_DWORD *)v24;
if ( (unsigned int)(v24 - v24 - 192) >= 0x18 )   
goto LABEL_30;// LABEL_30将会导致return 0
之后要求输入的用户名必须是XXXX#XXXX#XXXX#XXXX#XXXX#XXXX#XXXX#XXXX形式,X的范围必须在0~9,a~f或A~F之间对用户名合法性检查完毕,若正确,返回1
继续执行get5m((int)&str_name, &v26, &v27, &v28, (int *)&v29, &v30);// 根据用户名 获取五个64位整数 存在12EEA8~
发现这个函数根据输入的用户名得到了五个变量m1,m2,m3,m4,m5,之后会用到2.get5m分析没什么说的,用OD耐心的一步一步调试,发现他是把用户名分为了八段Seg1~Seg8      m1.QuadPart = 0;      
m1.QuadPart += ((seg1 * seg2) << 0x10);      
m1.QuadPart += (seg3 ^ seg1);      
m1.QuadPart += (seg1 % (seg4 + 1)) + 1;      
m1.QuadPart += (seg1 / (seg5+1));         

m2.QuadPart = 0;      
m2.QuadPart += ((seg2 ^ seg6) << 0x10);   
m2.QuadPart += (seg2 % (seg7 + 3));   
m2.QuadPart += (seg2 / (seg8 + 1)) + 5;   
m2.QuadPart += seg1 + seg2;      

m3.QuadPart = 0;   
m3.QuadPart += ((seg3 / (seg2 + 3)) << 0x10);   
m3.QuadPart = m3.QuadPart ^ (seg3 % seg4);   
m3.QuadPart += 0xc + seg6 + seg3;   
m3.QuadPart += seg8 + seg3;

m4.QuadPart = 0;   
m4.QuadPart = m4.QuadPart + (seg3 ^ seg1);      
m4.QuadPart = m4.QuadPart*(seg2 + seg4);   
m4.QuadPart = m4.QuadPart&(seg6 & seg5);   
m4.QuadPart = (seg8 * m4.QuadPart + m2.QuadPart)*seg7 * m1.QuadPart;   
m4.QuadPart = m4.QuadPart - ((m4.QuadPart - m2.QuadPart) % (2 * m1.QuadPart));

m5.QuadPart = 0;   
m5.QuadPart += (seg4 ^ seg5) << 0x10;   
m5.QuadPart = m5.QuadPart*(seg4 % (seg5 + 2));   
m5.QuadPart += 7 + (seg4 % (seg5 + 5));   
m5.QuadPart += seg5 * seg4;
在计算m4的时候我以为要用到高32位和低32位,所以用到了结构体。后来发现不需要,定义M变量的时候直接用INT64定义就行了


3.对if(...)的分析
if ( sub_406080(v14)
&& ((v16 = HIDWORD(v31), v17 = (__m128i *)v31, a13)|| (v33 = xmmword_5AC470, sub_403010(HIDWORD(v31) - v31, v31, (char *)&v33, v15, v31)))
&& v16 - (_DWORD)v17 == 32
&& v17.m128i_i32 ==842019128
&& !v17.m128i_i32 )// 406080函数就是对我们输入的key进行转换
这个判断看起来很复杂,我们先看Sub_406080(伪代码有点长,就不贴了)406080函数是对我们输入的RegCode进行转换得到三个INT64变量结合OD发现首先对我们输入的RegCode,与一个包含65个数的表做对比,我们输入的字符必须在这个表里存在表为:ZO6Kq79L&CPWvNopzQfghDRSG@di*kAB8rsFewxlm+/u5a^2YtTJUVEn0$HI34y#=所以我猜想这肯定跟BASE64有关
果然,之后406080函数把我们输入的RegCode 四个一组 转成三个
char ch1 = a ^ (a >> 3);                                 
char ch2 = b ^ (b >> 3);                  
char ch3 = c ^ (c >> 3);                                 
char ch4 = d ^ (d >> 3);                                     
char ret1 = 4 * ch1 | (ch2 >> 4) & 3;                                 
char ret2 = 16 * ch2 | (ch3 >> 2) & 0xF;         
char ret3 = (ch3 << 6) | ch4 & 0x3F;
将得到的
ret1,ret2,ret3存放到一个地方,后来发现后面的判断需要用到其中的一部分(其实前32位RegCode会最后组合成三个INT64的数a6,a7,a8)
也就是说必须内存中从19B748开始满足38 31 30 32 00 00 00 00如下才能过两个判断
而我选中的部分将作为参数压栈,到最终的check函数中去

三.分析最终的402F20check函数sub_402F20( v26, v27, v28,v29, v30,__PAIR__(_mm_cvtsi128_si32(_mm_srli_si128(v18, 4)), _mm_cvtsi128_si32(v18)),            __PAIR__(_mm_cvtsi128_si32(_mm_srli_si128(v18, 12)), _mm_cvtsi128_si32(_mm_srli_si128(v18, 8))),*((__int64 *)&v33 + 1)) // 关键的check函数 返回1 说明我们输入的key与通过name计算的key相同

通过OD发现他将Get5m中得到的五个INT64变量 m1,m2,m3,m4,m5与406080函数得到的三个INT64变量a6,a7,a8做了这样的运算
bool __cdecl sub_402F20(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7, __int64 a8)
{
return a3 + (a2 + a1 * a6) * a6 == a7      && (a2 - a4) * (a2 - a4) == 4 * (a4 * a6 - (a2 + a1 * a6) * a6) * a1      && a3 + (a2 + a1 * a5 - a4) * a5 == a8;
}
将return后的语句化简得(其实第二句是一个一元二次方程)
a6 = (m4 - m2) / (2 * m1);      
a7 = m3 + (m2 + m1 * a6) * a6;      
a8 = m3 + (m2 + m1 * m5 - m4) * m5;
这是我们编写注册机的关键。先通过输入的合法用户名,得到五个变量m1,m2,m3,m4,m5再根据五个变量得到a6,a7,a8,具体算法在源代码里根据a6,a7,a8得到我们对应的RegCode(后面12位是固定的,有三种)有什么不妥之处,还望各位大牛指正

鱼无论次 发表于 2018-12-17 10:12

本帖最后由 鱼无论次 于 2018-12-17 10:24 编辑

兄弟你的a6 = (m4 - m2) / (2 * m1); 怎么计算出来的?能请教下吗?{:1_907:}
这几句我没办法简化成a6 = (m4 - m2) / (2 * m1);,是不是我漏了什么吗?
bool __cdecl sub_402F20(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7, __int64 a8)
{
return a3 + (a2 + a1 * a6) * a6 == a7      && (a2 - a4) * (a2 - a4) == 4 * (a4 * a6 - (a2 + a1 * a6) * a6) * a1      && a3 + (a2 + a1 * a5 - a4) * a5 == a8;
}

wflb826 发表于 2019-1-19 23:36

a6 = (m4 - m2) / (2 * m1);      
a7 = m3 + (m2 + m1 * a6) * a6;      
a8 = m3 + (m2 + m1 * m5 - m4) * m5;

这个还是很经典的!

Ericky 发表于 2018-4-28 11:46

和去年的题目好像额。。

ssyy91 发表于 2018-4-28 12:13

啥也看不懂

额度更深刻 发表于 2018-4-28 12:26

Ericky 发表于 2018-4-28 11:46
和去年的题目好像额。。

看了一下去年的,发现逻辑上简直一模一样……

cz864259120 发表于 2018-4-28 14:11

厉害了我的哥

陌上寒烟薄雪 发表于 2018-4-28 23:04

大二的表示啥也看不懂{:301_971:}

qiutianqin 发表于 2018-4-29 11:25


厉害了我的哥

南栀倾寒ysd 发表于 2018-5-4 14:42

后悔没有好好学编程

zye 发表于 2018-5-4 21:10

逆向咋学啊,是要学汇编吗

额度更深刻 发表于 2018-5-4 21:13

zye 发表于 2018-5-4 21:10
逆向咋学啊,是要学汇编吗

汇编要学,看王爽的《汇编语言》,写的非常好
页: [1] 2 3 4
查看完整版本: 2018腾讯游戏安全竞赛第一轮PC标准版分析