- UID
- 70756
注册时间2010-11-1
阅读权限85
最后登录1970-1-1
见习版主
TA的每日心情 | 擦汗 2016-4-19 21:35 |
---|
签到天数: 3 天 [LV.2]偶尔看看I
|
本帖最后由 wai1216 于 2019-12-3 09:58 编辑
好久不见,上来冒个泡
前几天在windows上登qq,被提示如下文字
因当前版本存在安全风险,将无法继续使用................
无奈,逆向之.
逆向前,先思考了下.
这样突然出现不能登录,从逻辑上来讲应该是服务器返回过来判断的.因此发包的时候会去效验了版本之类的东西.
但是呢,我又不太想通过走网络层面去patch,虽然有可能patch就不是以下地方了。
1. 进行修改
一番搜索后,发现有一款叫ntrqq的小插件,可以搞定这个限制.
下载了最新版,载入后dump了下来,看了看作者是如何实现的.
[Asm] 纯文本查看 复制代码
{
if ( v31 == 1 )
v31 = 0x1636;
__fuckqqloginlimit_pubno = v31;
v34 = sub_68F73290(v31, L"PreloginLogic.dll");
v35 = GetProcAddress(dword_68F3B7C4, "?GetPubNo@Version@@YAKXZ");
if ( v34 )
{
if ( !v35 )
{
v36 = v7(L"AFBase.dll");
v35 = GetProcAddress(v36, "?GetPubNo@Version@@YAKXZ");
if ( !v35 )
{
v37 = v7(L"AppUtil.dll");
v35 = GetProcAddress(v37, "?GetPubNo@Version@@YAKXZ");
if ( !v35 )
{
v38 = v7(L"PreloginLogic.dll");
v35 = GetProcAddress(v38, "?GetPubNo@Version@@YAKXZ");
}
}
}
result = sub_68EF2A30(v35, "KernelUtil.dll", v7, sub_68EE3500, v34);
}
else
{
v39 = v35;
if ( v35
|| (v40 = v7(L"AFBase.dll"), (v39 = GetProcAddress(v40, "?GetPubNo@Version@@YAKXZ")) != 0)
|| (v41 = v7(L"AppUtil.dll"), (v39 = GetProcAddress(v41, "?GetPubNo@Version@@YAKXZ")) != 0)
|| (v42 = v7(L"PreloginLogic.dll"), result = GetProcAddress(v42, "?GetPubNo@Version@@YAKXZ"), (v39 = result) != 0) )
{
LOBYTE(Buffer) = -23;
result = (sub_68EE3500 - v39 - 5);
*(&Buffer + 1) = sub_68EE3500 - v39 - 5;
if ( v39 != -1 )
{
flOldProtect = 0;
VirtualProtect(v39, 5u, 0x40u, &flOldProtect);
WriteProcessMemory(0xFFFFFFFF, v39, &Buffer, 5u, 0);
result = VirtualProtect(v39, 5u, flOldProtect, 0);
}
}
}
return result;
}
其中 v31 = GetPrivateProfileIntW("Login", "FuckQQLoginLimit", 0, "NtrQQ.ini");
作者把有调用GetPubNo这个函数的几个dll进行了patch,以至于返回NtrQQ.ini中设定好的数值,如果为1,则为0x1636
当然还在其它地方进行了修改,这个稍后再谈
现在有了大概方向就好办了,首先看了看这个函数的代码
[Asm] 纯文本查看 复制代码
kernelutil.dll <?GetPubNo@Version@@YAKXZ>
int Version::GetPubNo()
{
return dword_5331CCCC;
}
只是一个全局变量,在下面函数进行的初始化
[Asm] 纯文本查看 复制代码
kernelutil.dll<?Init@Version@@YAHXZ> // hummerengine.dll 中调用
int __thiscall Version::Init(void *ecx0)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v23 = L"vi.dat";
v22 = ecx0;
Target = 0;
NumberOfBytesRead = 0;
CTXStringW::CTXStringW((CTXStringW *)&v22, &Default);
v1 = Util::Sys::GetProgramBinDir(&v27);
operator+(&v24, v1);
CTXStringW::~CTXStringW((CTXStringW *)&v27);
v23 = 0;
v22 = 0;
v2 = (const WCHAR *)CTXStringW::operator wchar_t const *(&v24);
v3 = CreateFileW(v2, 0x80000000, 1u, 0, 3u, (DWORD)v22, (HANDLE)v23);
if ( v3 != (HANDLE)-1 )
{
FileSizeHigh = 0;
v4 = GetFileSize(v3, &FileSizeHigh);
v5 = v4;
if ( !FileSizeHigh && v4 < 0x5000 )
{
Target = unknown_libname_95(v4);
ReadFile(v3, (LPVOID)Target, v5, &NumberOfBytesRead, 0);
}
CloseHandle(v3);
}
v32 = 15;
v31 = 0;
LOBYTE(v30) = 0;
sub_532970AA(&v30, NumberOfBytesRead);
sub_53270CB5(&Target);
if ( v31 < 0x10 )
goto LABEL_20;
v6 = v30;
if ( v32 < 0x10 )
v6 = &v30;
memcpy(&byte_5331CCB4, v6, 0xCu);
v7 = *((_WORD *)v6 + 6);
v8 = v31 - 14;
v9 = (int)v6 + 14;
v29 = v7;
if ( v31 - 14 < v7 + 2 )
goto LABEL_20;
CTXStringW::CTXStringW(&FileSizeHigh, v9, v7 >> 1);
v10 = (_WORD *)(v29 + v9);
v11 = *v10;
v12 = v8 - v29 - 2;
v13 = (int)(v10 + 1);
v29 = v11;
if ( v12 < v11 )
{
CTXStringW::~CTXStringW((CTXStringW *)&FileSizeHigh);
LABEL_20:
v20 = 0;
goto LABEL_18;
}
CTXStringW::CTXStringW(&v27, v13, v11 >> 1);
v14 = CTXStringW::operator wchar_t const *(&v27);
CTXStringW::operator=(&unk_5331CCC0, v14);
v15 = CTXStringW::operator wchar_t const *(&FileSizeHigh);
CTXStringW::operator=(&unk_5331CCC4, v15);
v16 = v29 + v13;
v17 = v12 - v29;
dword_5331CCC8 = 65793;
dword_5331CCCC = 25600;
if ( (unsigned __int8)byte_5331CCB5 + 100 * (unsigned __int8)byte_5331CCB4 >= 2574 && v17 >= 8 )
{
memcpy(&dword_5331CCC8, (const void *)v16, 4u);
memcpy(&dword_5331CCCC, (const void *)(v16 + 4), 4u);
v16 += 8;
v17 -= 8;
}
if ( v17 >= 2 )
{
v18 = *(_WORD *)v16;
if ( v17 - 2 >= v18 )
{
CTXStringW::CTXStringW(&v29, v16 + 2, v18 >> 1);
v19 = CTXStringW::operator wchar_t const *(&v29);
CTXStringW::operator=(&unk_5331CCD0, v19);
CTXStringW::~CTXStringW((CTXStringW *)&v29);
}
}
CTXStringW::~CTXStringW((CTXStringW *)&v27);
CTXStringW::~CTXStringW((CTXStringW *)&FileSizeHigh);
v20 = 1;
LABEL_18:
std::basic_string<char,std::char_traits<char>,std::allocator<char>>::_Tidy(&v30, 1, 0);
CTXStringW::~CTXStringW((CTXStringW *)&v24);
return v20;
}
注意到有个call sub_532970AA,该函数首先通过CTXCommPack::GetWord得到dat文件的开头两字节判断是否为0,之后调用GetByte判断是否2了
也就是说dat头部要满足00 00 02,之后通过GetModuleFileNameW得到qq.exe计算md5,再将其md5循环20次得到的hash作为解密vi.dat的密钥(变形tea),伪代码如下
[Asm] 纯文本查看 复制代码
signed int __cdecl sub_53296DB9(char a1, unsigned __int8 *a2)
{
MD5_Init((struct MD5state_st *)&v14);
for ( i = ReadFile(hFile, &Buffer, 0x1000u, &NumberOfBytesRead, 0);
i && NumberOfBytesRead;
i = ReadFile(hFile, &Buffer, 0x1000u, &NumberOfBytesRead, 0) )
{
MD5_Update((struct MD5state_st *)&v14, (const unsigned __int8 *)&Buffer, NumberOfBytesRead);
}
CloseHandle(hFile);
if ( a1 != 3 )
goto LABEL_12;
v7 = GetModuleHandleW(0);
if ( v7 )
{
MD5_Update(
(struct MD5state_st *)&v14,
(const unsigned __int8 *)v7 + *(_DWORD *)((char *)v7 + *((_DWORD *)v7 + 15) + 44),
*(_DWORD *)((char *)v7 + *((_DWORD *)v7 + 15) + 28));
LABEL_12:
MD5_Final(a2, (struct MD5state_st *)&v14);
v8 = 20;
do
{
MD5_Init((struct MD5state_st *)&v13);
MD5_Update((struct MD5state_st *)&v13, a2, 0x10u);
MD5_Final(a2, (struct MD5state_st *)&v13);
--v8;
}
while ( v8 );
v3 = 1;
}
}
之后通过GetBuf和GetWord获取到加密数据的大小,而此时文件的偏移刚好是加密后数据的起始位置
那么有关vi.dat的结构就可以抽象成如下
[Asm] 纯文本查看 复制代码
struct s_buf{
short flag;
byte type;
short offset;
byte unknown_like_padding[offset];
short define_tea_encrypt_size;
byte define_tea_encrypt_data[encrypt_size];
};
接着通过oi_symmetry_decrypt2进行解密
[Asm] 纯文本查看 复制代码
.text:532971D1 lea ecx, [ebp+var_40]
.text:532971D4 push ecx
.text:532971D5 push eax
.text:532971D6 mov [ebp+var_48], eax
.text:532971D9 lea eax, [ebp+var_14]
.text:532971DC push eax
.text:532971DD movzx eax, [ebp+var_34]
.text:532971E1 push eax
.text:532971E2 push [ebp+var_44]
.text:532971E5 call ds:?oi_symmetry_decrypt2@@YAHPBEH0PAEPAH@Z ; oi_symmetry_decrypt2(uchar const *,int,uchar const *,uchar *,int *)
.text:532971EB add esp, 18h
以我所使用的版本得到如下数据
[Asm] 纯文本查看 复制代码
00526280 37 03 00 00 C9 4A 00 00 08 00 00 00 46 00 24 00 7...éJ......F.$.
00526290 50 00 52 00 4F 00 44 00 4E 00 41 00 4D 00 45 00 P.R.O.D.N.A.M.E.
005262A0 24 00 20 00 50 00 72 00 65 00 76 00 69 00 65 00 $. .P.r.e.v.i.e.
005262B0 77 00 34 00 28 00 38 00 2E 00 37 00 2E 00 31 00 w.4.(.8...7...1.
005262C0 39 00 31 00 34 00 35 00 2E 00 32 00 30 00 31 00 9.1.4.5...2.0.1.
005262D0 29 00 00 00 1C 00 38 00 2E 00 37 00 2E 00 31 00 ).....8...7...1.
005262E0 39 00 31 00 34 00 35 00 2E 00 32 00 30 00 31 00 9.1.4.5...2.0.1.
005262F0 00 00 01 01 01 00 24 68 00 00 70 01 48 00 75 00 ......$h..p.H.u.
00526300 6D 00 6D 00 65 00 72 00 45 00 6E 00 67 00 69 00 m.m.e.r.E.n.g.i.
00526310 6E 00 65 00 2E 00 64 00 6C 00 6C 00 3A 00 46 00 n.e...d.l.l.:.F.
00526320 30 00 41 00 32 00 46 00 45 00 36 00 41 00 42 00 0.A.2.F.E.6.A.B.
00526330 41 00 46 00 32 00 42 00 35 00 43 00 34 00 32 00 A.F.2.B.5.C.4.2.
00526340 43 00 36 00 34 00 39 00 36 00 43 00 32 00 45 00 C.6.4.9.6.C.2.E.
00526350 36 00 39 00 35 00 43 00 30 00 42 00 43 00 7C 00 6.9.5.C.0.B.C.|.
00526360 4C 00 6F 00 6E 00 67 00 43 00 6E 00 6E 00 2E 00 L.o.n.g.C.n.n...
00526370 64 00 6C 00 6C 00 3A 00 35 00 33 00 35 00 42 00 d.l.l.:.5.3.5.B.
00526380 42 00 33 00 39 00 43 00 39 00 45 00 42 00 35 00 B.3.9.C.9.E.B.5.
00526390 31 00 30 00 42 00 42 00 36 00 46 00 46 00 32 00 1.0.B.B.6.F.F.2.
005263A0 35 00 32 00 33 00 44 00 31 00 30 00 30 00 35 00 5.2.3.D.1.0.0.5.
005263B0 31 00 39 00 45 00 37 00 7C 00 4B 00 65 00 72 00 1.9.E.7.|.K.e.r.
005263C0 6E 00 65 00 6C 00 55 00 74 00 69 00 6C 00 2E 00 n.e.l.U.t.i.l...
005263D0 64 00 6C 00 6C 00 3A 00 39 00 31 00 34 00 33 00 d.l.l.:.9.1.4.3.
005263E0 39 00 43 00 33 00 36 00 33 00 36 00 32 00 36 00 9.C.3.6.3.6.2.6.
005263F0 41 00 37 00 32 00 30 00 42 00 46 00 42 00 33 00 A.7.2.0.B.F.B.3.
00526400 33 00 32 00 45 00 42 00 34 00 45 00 32 00 32 00 3.2.E.B.4.E.2.2.
00526410 31 00 31 00 36 00 37 00 7C 00 54 00 53 00 49 00 1.1.6.7.|.T.S.I.
00526420 50 00 2E 00 44 00 41 00 54 00 3A 00 31 00 39 00 P...D.A.T.:.1.9.
00526430 37 00 34 00 33 00 38 00 31 00 30 00 43 00 30 00 7.4.3.8.1.0.C.0.
00526440 36 00 43 00 42 00 39 00 31 00 44 00 32 00 37 00 6.C.B.9.1.D.2.7.
00526450 33 00 35 00 45 00 34 00 44 00 33 00 34 00 46 00 3.5.E.4.D.3.4.F.
00526460 44 00 42 00 34 00 38 00 31 00 37 00 00 00 00 00 D.B.4.8.1.7.....
给出分析的结构
[Asm] 纯文本查看 复制代码
struct s_data{
typedef struct s_ver{
byte majorver:1;
byte minorver:1;
short padding:2;
}t_ver;
dword buildver;
dword unknown;
byte vername[];
dword clienttype;
dowrd pubno;
short dll_information_size;
byte dll_information[dll_information_size];
};
到这里就可以构造出想要的pubno号,然后调用oi_symmetry_encrypt2加密数据替换掉vi.dat.
然后打开qq却发现提示了两个初始化错误0x0d以及0x30,继续进行分析,注意到仅替换了pubno.
int Version::GetPubNo()处下断,发现是在HummerEngine.dll中进行的check,伪代码如下: //ntrqq的作者好像对此函数也进行了path
[Asm] 纯文本查看 复制代码
signed int sub_528CAE70()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
if ( sub_528D2997()
&& ((CTXStringW::CTXStringW(&v10, L"platform"),
operator+(&v9, &v10, L"res:"),
CTXStringW::~CTXStringW(&v10),
v1 = sub_528CAD0A(&v9),
CTXStringW::~CTXStringW(&v9),
CTXStringW::CTXStringW(&v9, L"platform"),
operator+(&v10, &v9, L"xtml:"),
CTXStringW::~CTXStringW(&v9),
v8 = sub_528CAD0A(&v10),
CTXStringW::~CTXStringW(&v10),
CTXStringW::CTXStringW(&v9, L"platform"),
operator+(&v10, &v9, L"data:"),
CTXStringW::~CTXStringW(&v9),
v2 = sub_528CAD0A(&v10),
CTXStringW::~CTXStringW(&v10),
v3 = CTXStringW::CTXStringW(&v9, L"platformtheme:"),
v4 = sub_528CAD0A(v3),
CTXStringW::~CTXStringW(&v9),
v5 = Version::GetPubNo(),
v1 != v5)
|| v8 != v5
|| v2 != v5
|| v4 != v5) )
{
.....
}
}
sub_528D2997() 点进去看了看,大概是为创建另一进程起的部分参数
对赋值函数进行分析,伪代码如下
[Asm] 纯文本查看 复制代码
int __stdcall sub_528CAD0A(int a1)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
qmemcpy(&v12, L"pubversion.dat", 0x1Eu);
v1 = CTXStringW::operator wchar_t const *(a1);
FS::CombineQNC(&v9, v1, &v12);
v11 = 0;
v2 = CTXStringW::operator wchar_t const *(&v9);
FS::CreateFileW(v2, 0, &v11);
...
CTXStringA::CTXStringA(&v6, v7, v8);
v3 = CTXStringA::GetBuffer(&v6);
v4 = StrToIntA(v3);
CTXStringA::~CTXStringA(&v6);
...
}
动态跟了跟,原来读取的安装目录下Resource.8.7.19145的四个.rdb
搜索了下网上对rdb解析的工具,发现解析不了.
无奈之下只有分析下结构对值进行修改.给出逆向后的结构
[Asm] 纯文本查看 复制代码
struct s_rdb{
byte feature[0x10];
dword count;
dword res_struct_offset;
dword padding;
dword res_data_offset;
};
struct s_res{
wchar name[];
dword offset;
dowrd unknown;
dword size;
};
用资源下的data.rdb来举例子
[Asm] 纯文本查看 复制代码
00000000 35 33 31 45 39 38 32 30 34 46 38 35 34 32 46 30 531E98204F8542F0
00000010 18 09 00 00 24 00 00 00 00 00 00 00 CC 3D 02 00 ....$........=..
00000020 00 00 00 00 62 00 74 00 6E 00 72 00 65 00 70 00 ....b.t.n.r.e.p.
00000030 6F 00 72 00 74 00 5C 00 67 00 75 00 69 00 64 00 o.r.t.\.g.u.i.d.
00000040 32 00 74 00 74 00 2E 00 64 00 61 00 74 00 00 00 2.t.t...d.a.t...
00000050 00 00 00 00 00 00 00 00 85 F1 01 00 00 00 00 00 ................
00000060 70 00 75 00 62 00 76 00 65 00 72 00 73 00 69 00 p.u.b.v.e.r.s.i.
00000070 6F 00 6E 00 2E 00 64 00 61 00 74 00 00 00 85 F1 o.n...d.a.t.....
00000080 01 00 00 00 00 00 05 00 00 00 00 00 00 00 61 00 ..............a.
pubversion.data数据的位置应该是 0x23dcc + 0x1f185 + 0x24
[Asm] 纯文本查看 复制代码
00042F6B B9 DD DD F0 C2 82 18 00 08 01 32 36 36 36 30 7B ..........26660{
为什么需要再加0x24,见如下代码
[Asm] 纯文本查看 复制代码
705BD10D | 76 09 | jbe common.705BD118 |
705BD10F | 8B42 10 | mov eax,dword ptr ds:[edx+10] |
705BD112 | 2B45 0C | sub eax,dword ptr ss:[ebp+C] |
705BD115 | 8945 14 | mov dword ptr ss:[ebp+14],eax |
705BD118 | 8B41 28 | mov eax,dword ptr ds:[ecx+28] |
705BD11B | 0342 08 | add eax,dword ptr ds:[edx+8] |
705BD11E | 8B71 2C | mov esi,dword ptr ds:[ecx+2C] |
705BD121 | 1372 0C | adc esi,dword ptr ds:[edx+C] |
705BD124 | 0345 0C | add eax,dword ptr ss:[ebp+C] |
705BD127 | 1375 10 | adc esi,dword ptr ss:[ebp+10] |
705BD12A | 83C0 24 | add eax,24 |
705BD12D | 13F7 | adc esi,edi |
705BD12F | 8975 10 | mov dword ptr ss:[ebp+10],esi |
705BD132 | 897D FC | mov dword ptr ss:[ebp-4],edi |
也正是因为这个原因,导致解析失败么
得到数值,之后在进行转换.
[Asm] 纯文本查看 复制代码
v4 = StrToIntA(v3);
对四个rdb进行size和data的修改.
至此打开没问题,也可以进行登录了.
2.问题来了
登录后却提示,qq损坏.
发现不点提示框时,不会结束掉qq.
还有收到消息,但是不能打开好友和群的聊天框.
无奈又只有挂上调试器,进行分析.
先对打不开聊天框进行分析
先逆了半天,以为对修改的文件进行了校验或者触发了暗桩,转念一想发现不应该,回想起来有些惯性思维了.
随意找了个能断下来的地方,跟踪发现弹窗是由于版本变化了,加载时对Plugin目录下的插件版本进行check导致.
[Asm] 纯文本查看 复制代码
ChatFrameApp.dll:
signed int __cdecl sub_51529DC6(const wchar_t *a1, _DWORD **a2, int a3)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v80 = &v77;
sub_51526E09(v3);
CTXStringW::CTXStringW(&v81, L"com.tencent.advertisement", v78);
v77 = (int *)&v81;
v4 = *((_DWORD *)v79 + 1);
v82 = (char *)v79 + 4;
v5 = 1;
v6 = sub_51527264(v79, v4, &v81);
sub_51526E59(1);
*(_DWORD *)v82 = v6;
**(_DWORD **)(v6 + 4) = v6;
CTXStringW::~CTXStringW((CTXStringW *)&v81);
CTXStringW::CTXStringW(&v81, L"com.tencent.audiovideo", v78);
...
}
signed int __cdecl sub_51529CFF(_DWORD **a1, _DWORD **a2, int a3)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v9 = &v7;
sub_51526E09(v3);
for ( i = *(char **)v8; i != v8; i = *(char **)i )
{
sub_51527FC9(&v11, **a1, *a1, i + 8);
if ( v11 == *a1 )
{
sub_51527FC9(&v10, **a2, *a2, i + 8);
if ( v10 == *a2 )
{
v9 = (int **)CTXStringW::operator wchar_t const *(i + 8);
v7 = (int *)&v9;
sub_51511243(L"file", 120, L"func", 2, L"plc", (const char *)L"%s", &v9);
sub_515280C1(*(_DWORD *)a3, i + 8);
}
}
}
v5 = 0;
if ( !*(_DWORD *)(a3 + 4) )
v5 = 1;
sub_51528BF0(&v8);
operator delete(v8);
return v5;
}
patch了下,能打开聊天框,进行聊天了,由于时间挂太久,还T我下线了.
依法炮制干掉了AppMisc.dll对于插件的加载.
再次打开qq,进行登录.
恩,能进行聊天就行,作为基本功能满足即可.
事后发现由于插件没加载,不能传文件.
打开Com.Tencent.FileTransfer,将目录里Bundle.rdb文件中pubversion.dat有关数据进行修改.
再再次打开qq,进行登录.测试了下功能发现可行.
舒服了.
还好只是用来聊天交流.
3.反思
方式不是太好,理想方式,应该只在发包的时候对pubno和ver相关进行修改,可能hook的地方多一些.
然后在根据ntrqq中,再对一些校验的地方在进行patch.
|
|