好友
阅读权限10
听众
最后登录1970-1-1
|
本帖最后由 额度更深刻 于 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[4] != 39 ) // a1[4]是我们输入的用户名长度
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[0];
if ( (unsigned int)(v24[1] - v24[0] - 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[0] * seg2[0]) << 0x10);
m1.QuadPart += (seg3[1] ^ seg1[1]);
m1.QuadPart += (seg1[2] % (seg4[2] + 1)) + 1;
m1.QuadPart += (seg1[3] / (seg5[3]+1));
m2.QuadPart = 0;
m2.QuadPart += ((seg2[0] ^ seg6[0]) << 0x10);
m2.QuadPart += (seg2[1] % (seg7[1] + 3));
m2.QuadPart += (seg2[2] / (seg8[2] + 1)) + 5;
m2.QuadPart += seg1[3] + seg2[3];
m3.QuadPart = 0;
m3.QuadPart += ((seg3[0] / (seg2[0] + 3)) << 0x10);
m3.QuadPart = m3.QuadPart ^ (seg3[1] % seg4[1]);
m3.QuadPart += 0xc + seg6[2] + seg3[2];
m3.QuadPart += seg8[3] + seg3[3];
m4.QuadPart = 0;
m4.QuadPart = m4.QuadPart + (seg3[3] ^ seg1[1]);
m4.QuadPart = m4.QuadPart*(seg2[3] + seg4[1]);
m4.QuadPart = m4.QuadPart&(seg6[2] & seg5[2]);
m4.QuadPart = (seg8[3] * m4.QuadPart + m2.QuadPart)*seg7[0] * m1.QuadPart;
m4.QuadPart = m4.QuadPart - ((m4.QuadPart - m2.QuadPart) % (2 * m1.QuadPart));
m5.QuadPart = 0;
m5.QuadPart += (seg4[0] ^ seg5[0]) << 0x10;
m5.QuadPart = m5.QuadPart*(seg4[1] % (seg5[1] + 2));
m5.QuadPart += 7 + (seg4[2] % (seg5[2] + 5));
m5.QuadPart += seg5[3] * seg4[3];
在计算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[1].m128i_i32[2] ==842019128
&& !v17[1].m128i_i32[3] ) // 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位是固定的,有三种)有什么不妥之处,还望各位大牛指正 |
免费评分
-
查看全部评分
|