LYSoft出品之QQ去广告+本地会员外挂原理
LYSoft出品之QQ去广告+本地会员外挂原理 (QQ2007,QQ 2008和QQ2009版本)最近更新内容: QQ2009去广告+本地会员(可去广告下载+表情涂鸦)
LYSoft QQExt采用自动代码分析,支持QQ2007,QQ2008和QQ2009的全部版本的去广告和本地会员功能(不支持IP显示功能)下载 v90419
08年8月针对QQ2007而写
QQ2007和QQ2008第一版的显示IP主要技术原理:(by LYSoft Liu Yang)
做IPQQ,那么这几个必备工具是不可缺的:OllyDbg,PEExplorer,DASM32,MFCSpy2
QQ是基于接口调用架构的,这为窥探其内部提供了方便之门
0) 经过分析,获知QQ获取IP信息是通过接口调用实现的,其步骤为
IQQCore->IQQData->IQQUserDynData->dwIP方法
1) 获得IQQCore.要获得此全局描述接口的方法有很多,最好的就是通过QQHelperDll.dll的 ?IsLogin@@YAHPAUIQQCore@@@Z方法获得,函数表达为int __cdecl IsLogin (struct IQQCore **).因为这个IsLogIn方法被QQ频繁调用,于是Hook这个函数,便能轻易获得IQQCore了
function IsLogin(pQQCore: Pointer): Integer; cdecl;
begin
Result := Call original Func;调用原函数
pIQQCore := pQQCore;获得IQQCore
end;
2) 从IQQCore获得IQQData.这个事情好办,QQ的BasicCtrlDll.dll的?GetFriendQQData@@YAHPAUIQQCore@@KPAPAUIQQData@@@Z方法,就是从IQQCore和UIN获得IQQData,函数表达为int __cdecl GetFriendQQData(struct IQQCore *,unsigned long,struct IQQData * *)
asm
// int __cdecl GetFriendQQData(struct IQQCore *,unsigned long,struct IQQData * *)
mov eax, pIQQCore
mov edx, UIN// QQ Uin (QQ number)
lea ecx, Result// return = pIQQData
push ecx
push edx
push eax
call GetFriendQQData
add esp, $C// fix call stack
end;
3) 从IQQData获得IQQUserDynData.很不幸,QQ没有直接提供该方法,只好DASM QQ的内部,来模拟此过程的调用.
const
szQQUSER_DYNAMIC_DATA : PChar = 'QQUSER_DYNAMIC_DATA';
clsid_IQQData : TGUID = '{BA863A1E-C979-498A-975C-C501C4F310A3}';
asm
// pIQQData = Pointer(IQQData);
mov ecx, pIQQData
mov ecx, // ecx = IQQData.vtbl
mov eax, pIQQData// this pIQQData
lea edx, Result// return = pIQQUDD
push edx
lea edx, clsid_IQQData// clsid_IQQData
push edx
push szQQUSER_DYNAMIC_DATA
push eax
call // IQQData.vf_54h QQUSER_DYNAMIC_DATA proc entry
end;
4) 从IQQUserDynData获得IP信息.
const
szdwIP : PChar = 'dwIP';
szwPort : PChar = 'wPort';
asm
// get Uin info
mov eax, pIQQUDD
mov ecx,
lea edx, dwIP
push edx
push szdwIP
push eax
call // IQQUDD.vf_34h
mov eax, pIQQUDD
mov ecx,
lea edx, wPort
push edx
push szwPort
push eax
call // IQQUDD.vf_30h
end;
上面的代码,懂ASM的人很容易就能理解的,其实这些代码也是来自QQ的DASM工程.
注意一下接口调用和Cdecl就行了,因为用Delphi写的,所以不好直接支持C++的thiscall,故采用BASM方式来调用~
QQ2007和QQ2008去广告原理:(by LYSoft LiuYang)
有人发现把目录AD下全部文件和Dat下Ad.gif删除了,广告就不会出来了.
可是这样QQ还是会下载新的广告的,怎么办呢?只好修改QQ内部了,这是属于破解的范畴,做起来也并不复杂.
DASM分析QQ.EXE即可查询到"广告"和"下载逻辑"的文本常量和OD查找字符串常量"Download_Start",它的上面是"SECTION_AD",然后把相关的地方NOP了就能使QQ不再下载广告.不同的版本要修改的地方不一样,这里就仅以QQ2007II Beta1为例
004E9D49|.57 push edi
004E9D4A|.50 push eax
004E9D4B|.57 push edi
004E9D4C|.53 push ebx
004E9D4D|.68 EFB14E00 push 004EB1EF ;Entry address
^ 这里是广告下载过程入口,到入口改为retn直接返回就OK了
004E9D52|.E8 DE4EF2FF call 0040EC35
004E9D57|.83C4 14 add esp, 14
004E9D5A|.BF 7CF55A00 mov edi, 005AF57C ;ASCII "C:\config_asam.ini"
004E9D5F|.C745 10 60EA0>mov dword ptr , 0EA60
004E9D66|.57 push edi ; /FileName => "C:\config_asam.ini"
004E9D67|.FF15 E0035400 call dword ptr [<&KERNEL32.GetFileAtt>; \GetFileAttributesA
004E9D6D|.83F8 FF cmp eax, -1
004E9D70|.74 64 je short 004E9DD6
004E9D72|.A8 10 test al, 10
004E9D74|.75 60 jnz short 004E9DD6
004E9D76|.57 push edi
004E9D77|.8D4D B8 lea ecx, dword ptr
004E9D7A|.E8 657BFAFF call <jmp.&MFC42.#537_CString::CStrin>
004E9D7F|.BF 84DB5500 mov edi, 0055DB84
004E9D84|.C645 FC 0C mov byte ptr , 0C
004E9D88|.897D B4 mov dword ptr , edi
004E9D8B|.B8 70F55A00 mov eax, 005AF570 ;ASCII "SECTION_AD"
004E9D90|.C645 FC 0D mov byte ptr , 0D
004E9D94|.8BC8 mov ecx, eax
004E9D96|.85C9 test ecx, ecx
004E9D98|.74 1A je short 004E9DB4
004E9D9A|.B9 60F55A00 mov ecx, 005AF560 ;ASCII "Download_Start"
004E9D9F|.8BD1 mov edx, ecx
004E9DA1|.85D2 test edx, edx
004E9DA3|.74 0F je short 004E9DB4
004E9DA5|.FF75 B8 push dword ptr ; /IniFileName
004E9DA8|.6A 00 push 0 ; |Default = 0
004E9DAA|.51 push ecx ; |Key => "Download_Start"
004E9DAB|.50 push eax ; |Section => "SECTION_AD"
004E9DAC|.FF15 3C035400 call dword ptr [<&KERNEL32.GetPrivate>; \GetPrivateProfileIntA
004E9DB2|.EB 02 jmp short 004E9DB6
004E9DB4|>33C0 xor eax, eax
004E9DB6|>85C0 test eax, eax
004E9DB8|.74 09 je short 004E9DC3
004E9DBA|.69C0 E8030000 imul eax, eax, 3E8
004E9DC0|.8945 10 mov dword ptr , eax
004E9DC3|>C645 FC 0B mov byte ptr , 0B
004E9DC7|.897D B4 mov dword ptr , edi
004E9DCA|.8D4D B8 lea ecx, dword ptr
004E9DCD|.C645 FC 0B mov byte ptr , 0B
004E9DD1|.E8 D679FAFF call <jmp.&MFC42.#800_CString::~CStri>
004E9DD6|>68 A0F45A00 push 005AF4A0 ;ASCII
"D:\QQ\qqbuilder_QQ2007IIbeta1Proj_int\Basic_QQ_VOB\QQ\QQMainApp\QQCSCenterSubApp.cpp"
004E9DDB|.B9 886C5B00 mov ecx, 005B6C88
004E9DE0|.E8 997AFAFF call <jmp.&MFC42.#860_CString::operat>
004E9DE5|.BF 906C5B00 mov edi, 005B6C90
004E9DEA|.68 40165400 push 00541640
004E9DEF|.8BCF mov ecx, edi
004E9DF1|.C705 8C6C5B00>mov dword ptr , 470
004E9DFB|.E8 7E7AFAFF call <jmp.&MFC42.#860_CString::operat>
004E9E00|.8B45 10 mov eax, dword ptr
004E9E03|.33D2 xor edx, edx
004E9E05|.B9 E8030000 mov ecx, 3E8
004E9E0A|.F7F1 div ecx
004E9E0C|.50 push eax
004E9E0D|.68 40F55A00 push 005AF540
004E9E12|.68 38F55A00 push 005AF538 ;ASCII "AD|asam"
004E9E17|.E8 AE78F1FF call 004016CA
004E9E1C|.83C4 0C add esp, 0C
004E9E1F|.837D EC 00 cmp dword ptr , 0
004E9E23|.74 17 je short 004E9E3C
004E9E25|.6A FF push -1
004E9E27|.FF75 EC push dword ptr
004E9E2A|.56 push esi
004E9E2B|.FF75 10 push dword ptr
004E9E2E|.6A 0B push 0B
004E9E30|.E8 ED4CF2FF call 0040EB22
004E9E35|.83C4 14 add esp, 14
004E9E38|.85C0 test eax, eax
004E9E3A|.74 3D je short 004E9E79
^ 这里是判断广告是否要下载, 直接JMP就可以跳过广告下载了
004E9E3C|>68 A0F45A00 push 005AF4A0 ;ASCII
"D:\QQ\qqbuilder_QQ2007IIbeta1Proj_int\Basic_QQ_VOB\QQ\QQMainApp\QQCSCenterSubApp.cpp"
004E9E41|.B9 886C5B00 mov ecx, 005B6C88
004E9E46|.E8 337AFAFF call <jmp.&MFC42.#860_CString::operat>
004E9E4B|.68 40165400 push 00541640
004E9E50|.8BCF mov ecx, edi
004E9E52|.C705 8C6C5B00>mov dword ptr , 476
004E9E5C|.E8 1D7AFAFF call <jmp.&MFC42.#860_CString::operat>
004E9E61|.68 18F55A00 push 005AF518
004E9E66|.68 38F55A00 push 005AF538 ;ASCII "AD|asam"
004E9E6B|.E8 5A78F1FF call 004016CA
004E9E70|.59 pop ecx
004E9E71|.59 pop ecx
004E9E72|.8BCB mov ecx, ebx
004E9E74|.E8 76130000 call 004EB1EF
004E9E79|>8B45 EC mov eax, dword ptr
004E9E7C|.33FF xor edi, edi
004E9E7E|.3BC7 cmp eax, edi
004E9E80|.74 09 je short 004E9E8B
可是广告窗口还是照样存在的,而且点击了仍旧会有响应的.这就靠外挂才好处理的.要找到QQ聊天窗口中任意一个WinControl的Handle就能轻松用代码干掉广告窗口的.
procedure DisableQQAd(Wnd: LongInt);
label DoNext;
var
h, t: THandle;
cn: array of Char;
function RemoveAdLabel(hStatic: THandle): Boolean;
begin
Result := False;
GetClassName(hStatic, @cn, SizeOf(cn));
if cn = 'Static' then// class name should be "Static"
if GetWindowText(hStatic, @cn, SizeOf(cn)) > 0 then
if Trim(cn) <> '' then// if Static control contain any Text
begin
DestroyWindow(hStatic);// remove it!
Result := True;
Exit;
end;
end;
begin
// get root Win control
while GetParent(Wnd) > 0 do Wnd := GetParent(Wnd);
// remove QQ Ad url label
h := GetWindow(Wnd, GW_CHILD or GW_HWNDFIRST);
while h > 0 do
begin// search child controls in chat dialog root
cn := '';
// for QQ 2008 final or above
if RemoveAdLabel(h) then goto DoNext;
// for QQ 2007 II to 2008 beta
if cn = '#32770' then// QQ frame
begin// searh child controls in frame control "#32770"
h := GetWindow(h, GW_CHILD or GW_HWNDFIRST);
while h > 0 do
begin
if RemoveAdLabel(h) then goto DoNext;
h := GetWindow(h, GW_HWNDNEXT);
end;
end;
h := GetWindow(h, GW_HWNDNEXT);
end;
DoNext:
// remove QQ AD panel
h := GetWindow(Wnd, GW_CHILD or GW_HWNDFIRST);
while h > 0 do
begin
cn := '';
GetClassName(h, @cn, SizeOf(cn));
if cn = '#32770' then// QQ frame
begin
h := GetWindow(h, GW_CHILD or GW_HWNDFIRST);
while h > 0 do
begin
t := GetWindow(h, GW_CHILD or GW_HWNDFIRST);
if t > 0 then// has child control
begin
GetClassName(h, @cn, SizeOf(cn));
if cn = 'Static' then// found!
begin
DestroyWindow(t);// destroy Ad window
{ CreateWindow('Static', 'Hello world!!!', // 这里可以做什么?
// 创建一个Form,用SetParent让你的Form附着在上面的,
// 这样可以用你自己的窗口替换QQ的广告栏,TX一定会非常生气的,
// 为了避免麻烦,最好还是不要做此类事情啦.这里只是讨论方法而已.
// 如果要添加自己的Form,那么你还得用SetWindowLong来Hook WndProc过程,
// 以用来处理WM_CLOSE,确保关闭聊天窗口时能释放你的Form.
WS_VISIBLE or WS_CHILD or SS_LEFT,
0, 0, 242, 36, h, 0, h, nil); }
Exit;
end;
end;
h := GetWindow(h, GW_HWNDNEXT);
end;
end;
h := GetWindow(h, GW_HWNDNEXT);
end;
end;
问题是如何找到QQ聊天窗口中的任意个对象的Handle?
方法可以是EnumWindows列举窗口,从标题栏入手,但是这个方法不保险.最好的做法就是
Hook QQBaseClassInDll.dll中的函数,
QQ2007为?SetUin@CAllInOneStatusBar@@QAEX_JH@Z
QQ2007II Beta为?SetUin@CAllInOneStatusBar@@QAEX_JKH@Z
这个函数用于设置QQ聊天窗口中对方号码的信息用的,调用此函数必定传递一个Handle,这个Handle必定在聊天窗口中的,于是一切好办,剩下要注意的就是Delphi不支持thiscall的,所以Hook这个函数必须用assembler方式.
至于Handle在那里,用MFCSpy2分析就知道,在+0x20那里嘛~
另外此函数同时传递对方的QQ号码,也是目前很多在窗口上现实IP显示的外挂所喜欢Hook的函数之一.
到此时,就显IP+去广告上一切OK了,花了俺2天功夫,大功告成!!
顺便公开另外一个去除广告的方法,此方法不必给QQ程序中打硬补丁,而且兼容性更理想,但是QQ广告下载还是必须Nop掉, 不然广告会照样下载而只是不显示而已.
这就是BasicCtrlDll中的?IsVIP@@YAHPAUIQQCore@@@Z其原型为int __cdecl IsVIP(struct IQQCore *)
和QQHelperDll.dll中的?GetSysBoolData@@YAHPBDAAHH@Z.
OD分析QQAllInOne有:
03605EFF FF15 38506C03 call dword ptr [<&BasicCtrlDll.IsVIP>] ; BasicCtr.IsVIP
* ^判断当前登陆的QQ是否为VIP,因为VIP用户是可以关闭QQ广告的
03605F05 8365 FC 00 and dword ptr , 0
03605F09 8BF0 mov esi, eax
03605F0B 8D45 FC lea eax, dword ptr
03605F0E 6A 01 push 1
03605F10 50 push eax
03605F11 68 E8A76D03 push 036DA7E8 ; ASCII "m_bMemberDisableAD"
03605F16 FF15 206D6C03 call dword ptr [<&QQHelperDll.GetSysBoolData'>; QQHelper.GetSysBoolData
* ^获取广告显示设置
03605F1C 83C4 10 add esp, 10
03605F1F 85F6 test esi, esi
03605F21 5E pop esi
03605F22 74 0B je short 03605F2F
* ^关键!!! 不是VIP就跳的,所以把这个NOP了
03605F24 837D FC 00 cmp dword ptr , 0
03605F28 74 05 je short 03605F2F
* ^关键!!! 没关闭AD就跳,所以再把这个NOP了
03605F2A 6A 01 push 1
03605F2C 58 pop eax
这样,就实现了去AD了
具体可以使直接NOP代码,或者采用Hook方法:
function IsVIP(pQQCore: Pointer): Integer; cdecl;
begin
Result := 1;
end;
function GetSysBoolData(AText: PChar; p: Pointer; bIsVIP: Boolean): Integer; cdecl;
// int __cdecl GetSysBoolData(char const *,int &,int)
begin
if AText = 'm_bMemberDisableAD' then
begin
Integer(p^) := 1;
Result := 1;
Exit;
end;
Result := Call original Func;调用原函数
end;
说到这里,肯定有人会说,如果实现了本地会员,那就不用这么麻烦了嘛?!
显然,这是个捷径,实现起来也不难,关键是找到突破口
QQ2007和QQ2008本地会员原理:(by LY Soft Liu Yang)
经过DASM分析,QQHelperDll是个入手点
用PEExplorer认真查找看看,果然有发现,那就是IsVipUser@qdatCurrentUser@@QAEHXZ
一个无参数函数,Hook了,并让其返回EAX=1,嘿嘿,果然成了本地VIP,这个本地VIP可以享受QQ2007II的涂鸦表情哦~
可是到了这里却仍旧发现QQ的设置上,还是说你是"非会员",不能屏蔽广告,咋办?
显然的是QQQSettingCtrl.dll并没有调用qdatCurrentUser::IsVipUser来判断.那它调用了那个函数呢?
继续努力...N小时后发现!原来是IsQQServiceEnable@@YAHI@Z
这是一个unsigned int入口的函数,估计是服务功能号,由此函数判断当前登录QQ用户可用的服务,于是Hook了,不管三七二十一,一律返回EAX=1,再测试...一切OK!
至此,QQ外挂可告一段落也~~
不经意又到了09年4月17了,此文发布已经大半年了,QQ2009正式版刚好推出
以前的QQ2009Beta版本,偶就懒的动了,正式版嘛,还要看看的
QQ2009本地会员原理:(by LYSoft Liu Yang)
PE Explorer分析看了,新的QQ2009采用COM结构,以前的本地VIP之类的功能都不能用了
忍不住分析看看,仍见IsVIP函数,但是已经失效,Hook了毫无效果
找了1个多小时,终于发现秘密,原来QQ2009已经换了函数来处理这个
在KernelUtil的?IsFlagValid@Contact@Util@@YAHKK@Z
就是判断QQ可用的服务,其实质处理的事情就是QQ2008的IsQQServiceEnable@@YAHI@Z
这次破解连反汇编和动态调试都用不上,一个看DLL Import和Export表的工具足矣,呵呵
至于去除广告下载就懒得弄的,删除用户Application Data\Tencent\QQ\Misc\com.tencent.advertisement文件夹,建立一个文件,名com.tencent.advertisement,不需要内容的,这样QQ2009就没办法写入广告文件了
09年4月18:(by LYSoft Liu Yang)
用昨日的方法,会出现一个问题,就是所有好友都成了VIP了,因为IsFlagValid对所有QQ联系人都会调用,用来判断其服务标记,它有两个参数,都是DWORD的,第一个是QQ号码,第二个是服务类型,具体细节不清楚,不过这无关紧要.
说到这里,该知道如何处理了吧?那就是IsFlagValid(QQUIN,Flag)调用时,QQUIN为当前登陆帐号时返回1即可开启本地会员而其它情况就调用正常的处理流程,这样别的好友不受影响了.
问题又来了~怎么知道当前QQ登陆号码?在QQ2007和QQ2008的年代,有GetCurrentUin这个函数,而QQ2009也有,但问题在于,QQ2008上有IsLogon可以获得IQQCore,而QQ2009没有这个函数,而且也不再用IQQCore的
认真寻觅一番,又有发现:同为Contact管理的GetSelfUin函数!它是无参数的,调用后EAX=当前登陆QQ号码,这下本地会员完美实现了.
继续研究一下QQ广告下载逻辑,有这个发现:QQ\Plugin\Com.Tencent.Advertisement\bin下有COM插件Advertisement.dll,明显是广告下载和显示用的,删除之,QQ2009拒绝工作,看来TX还是有保护的
IDA分析Advertisement.dll,有此发现
.text:62027DF8 ; protected: virtual unsigned int __thiscall CTXHttpDownload::Run(void)
.text:62027DF8 ?Run@CTXHttpDownload@@MAEIXZ proc near; DATA XREF: .rdata:6202E608o
.text:62027DF8 jmp ds:__imp_?Run@CTXHttpDownload@@MAEIXZ ; CTXHttpDownload::Run(void)
.text:62027DF8 ?Run@CTXHttpDownload@@MAEIXZ endp
Advertisement会调用Common.dll来下载广告,强制在CTXHttpDownload的Run方法上直接Retn,会导致QQ无法更新资源,此方法一刀切,牺牲了正常功能,不可取,那么直接改动Advertisement的Import表,让JMP见鬼去,修改为Retn,此后QQ2009就不再下载广告了.
最后,补充一下,Advertisement在登陆后才加载的,加载后会很快执行下载广告的线程,所以,必须提前加载并处理才能生效.
下次有空再继续玩吧,这次就到这里啦^_^
--- by LYSoft Liu Yang
转帖一份存个档 谢谢分析~~~~~ 楼主咋被禁言了? 先收藏了,谢谢分享 分析得很好,受教了
页:
[1]