2011年7月18日星期一

QQ類顯IP源代碼

QQTM插件原理源代碼



採用msimg32.dll加載,無需更改QQ任何文件。


默認全部選項打開。
安裝成功後,在聊天窗口中將會出現下圖:





鼠標懸停在上面將會顯示對方使用的客戶端。點擊右鍵可以彈出下面的設置界面。



默認保留插件:
Com.Tencent.AudioVideo
Com.Tencent.FileTransfer
Com.Tencent.Mail
Com.Tencent.NetDisk
Com.Tencent.PayCenter
Com.Tencent.QQGame
Com.Tencent.QQLive
Com.Tencent.QQMusic
Com.Tencent.QQPet
Com.Tencent.QQShow
Com.Tencent.QQVip
Com.Tencent.QQVipMisc
Com.Tencent.Qzone
Com.Tencent.RemoteHelp
Com.Tencent.SNSApp
Com.Tencent.WBlog
Com.Tencent.Weather
Com.Tencent.TMBasic

安裝方式:
將下載的壓縮包解壓後,把msimg32.dll和shuax.ini複製到Bin目錄下即可(和QQ.exe、TM.exe在同一個目錄)。
卸載方式:
直接刪除msimg32.dll和shuax.ini文件。


下载: QQTMSRC.rar

---------------------------------------------------------------------------------

QQ2009去廣告+本地會員(可去廣告下載+表情塗鴉)

下载: QQExt.rar
LYSoft QQExt採用自動代碼分析,支持QQ2007,QQ2008和QQ2009的全部版本的去廣告和本地會員功能(不支持IP顯示功能)

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] // 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 [ecx + $54] // 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, [eax]
      lea edx, dwIP
      push edx
      push szdwIP
      push eax   
     call [ecx + $34] // IQQUDD.vf_34h
      mov eax, pIQQUDD
      mov ecx, [eax]
      lea edx, wPort
      push edx
      push szwPort
      push eax   
      call [ecx + $30] // 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",它的上面是"SEC​​TION_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 [ebp+10], 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 [ebp-48]
004E9D7A |. E8 657BFAFF call <jmp.&MFC42.#537_CString::CStrin>
004E9D7F |. BF 84DB5500 mov edi, 0055DB84
004E9D84 |. C645 FC 0C mov byte ptr [ebp-4], 0C
004E9D88 |. 897D B4 mov dword ptr [ebp-4C], edi
004E9D8B |. B8 70F55A00 mov eax, 005AF570 ; ASCII "SECTION_AD"
004E9D90 |. C645 FC 0D mov byte ptr [ebp-4], 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 [ebp-48] ; /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 [ebp+10], eax
004E9DC3 |> C645 FC 0B mov byte ptr [ebp-4], 0B
004E9DC7 |. 897D B4 mov dword ptr [ebp-4C], edi
004E9DCA |. 8D4D B8 lea ecx, dword ptr [ebp-48]
004E9DCD |. C645 FC 0B mov byte ptr [ebp-4], 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:perat>
004E9DE5 |. BF 906C5B00 mov edi, 005B6C90
004E9DEA |. 68 40165400 push 00541640
004E9DEF |. 8BCF mov ecx, edi
004E9DF1 |. C705 8C6C5B00>mov dword ptr [5B6C8C], 470
004E9DFB |. E8 7E7AFAFF call <jmp.&MFC42.#860_CString:perat>
004E9E00 |. 8B45 10 mov eax, dword ptr [ebp+10]
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 [ebp-14], 0
004E9E2​​3 |. 74 17 je short 004E9E3C
004E9E2​​5 |. 6A FF push -1
004E9E2​​7 |. FF75 EC push dword ptr [ebp-14]
004E9E2​​A |. 56 push esi
004E9E2​​B |. FF75 10 push dword ptr [ebp+10]
004E9E2​​E |. 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:perat>
004E9E4B |. 68 40165400 push 00541640
004E9E50 |. 8BCF mov ecx, edi
004E9E52 |. C705 8C6C5B00>mov dword ptr [5B6C8C], 476
004E9E5C |. E8 1D7AFAFF call <jmp.&MFC42.#860_CString:perat>
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 [ebp-14]
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 [0..254] 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 ca​​ll dword ptr [<&BasicCtrlDll.IsVIP>] ; BasicCtr.IsVIP
* ^判斷當前登陸的QQ是否為VIP,因為VIP用戶是可以關閉QQ廣告的
03605F05 8365 FC 00 and dword ptr [ebp-4], 0
03605F09 8BF0 mov esi, eax
03605F0B 8D45 FC lea eax, dword ptr [ebp-4]
03605F0E 6A 01 push 1
03605F10 50 push eax
03605F11 68 E8A76D03 push 036DA7E8 ; ASCII "m_bMemberDisableAD"
03605F16 FF15 206D6C03 ca​​ll 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 [ebp-4], 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月18by LYSoft Liu Yang)

用昨日的方法,會出現一個問題,就是所有好友都成了VIP了,因為Is​​FlagValid對所有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:6202E608?o
.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在登陸後才加載的,加載後會很快執行下載廣告的線程,所以,必須提前加載並處理才能生效.

下次有空再繼續玩吧,這次就到這裡啦^_^

没有评论:

发表评论