[源碼下載]
介紹
在Code Project網站有許多關於password spy的文章,但是這些都是基於Windows Hooks的,還有沒有其他方式能實現這種效果呢?是的,有,不過,先讓我們簡要的回顧一下。
要想讀控制項的內容,一般需要給它發送WM_GETTEXT 訊息.edit控制項就有這個特點。如果edit控制項是在別的進程裡,而且被設定成ES_PASSWORD類型,那麼我們想要達到的目的就不會成功。這種情況下只有擁有password控制項的進程才能實現WM_GETTEXT,所以我們的問題集中在了這一點上:
怎樣才能在外部的進程空間內執行::SendMessage( hPwdEdit, WM_GETTEXT, nMaxChars, psBuffer );
通常有三種方式解決這個問題:
1.把代碼寫到Dll內;然後,通過windows hooks將Dll映射到遠程進程。
2.把代碼寫道Dll內;然後,通過CreateRemoteThread & LoadLibrary 技術將Dll映射到遠程進程。
3.不單獨寫一個DLL;通過WriteProcessMemory將你的代碼直接拷貝到遠程進程。
I.Windows Hooks
Windows hools的主要用途是監控一些線程的訊息。
1.Local hooks,監控屬於你的進程的所有線程訊息。
2.Remote hooks:
a.thread-specific,監控屬於其他的進程的線程訊息。
b.system-width,監控當前運行在系統中的所有線程。
如果hook的線程屬於別的進程,你的hook程式必須放置在Dll內。然後系統將包含hook程式的dll映射到線程的地址空間,Windows映射全部的Dll,不僅僅是hook程式,這就是為什麼Windows hooks可以被用來將代碼注入到其他進程的地址空間中去。
這篇文章裡,我不去深入討論hooks(可以在MSDN內詳細的看一下SetWindowHookEx API函數)我還會給你兩個有用的線索,這在文檔裡是找不到的。
1.在調用SetWindowHookEx成功後,系統自動將Dll映射到了線程的地址空間,但不會立即執行。因為Windows hooks全部是基於訊息,直到正確的事件返回Dll才被真正映射。例如:
如果你安裝了一個Hook用來監控一些線程的所有訊息佇列(WH_CALLWNDPROC),在訊息被發送到線程之前DLL不會被映射到進程的地址空間。如果在訊息發送到線程之前,UnhoolWindowsHoolEx被調用,那麼Dll永遠都不會被映射到進程(儘管調用SetWondosHookEx成功了)。要想立即執行,在SetWondosHookEx成功之後立即給線程發送一個合適的event。
在調用UnhoolWindowsHoolEx取消Dll映射的時候,也不會立即取消DLL映射。
2.當你安裝Hooks的時候,他們會影響系統的總效能(尤其是system-wide hooks).如果你將thread-specific hooks單獨作為一個Dll映射機制,那麼你很容易就避免這個缺點,看下面的代碼片斷:
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ if( ul_reason_for_call == DLL_PROCESS_ATTACH ) { // Increase reference count via LoadLibrary char lib_name[MAX_PATH]; ::GetModuleFileName( hModule, lib_name, MAX_PATH ); ::LoadLibrary( lib_name ); // Safely remove hook ::UnhookWindowsHookEx( g_hHook ); } return TRUE;}
那麼,發生了什麼了?首先我們通過Windows hooks將Dll映射到遠程進程。然後,在Dll被真正的映射之後,我們Unhook它,正常情況下,Dll將要被取消映射,同時,第一個到達被hook的線程的訊息也被啟用, 我們可以通過LoadLibrary增加Dll的引用計數來阻止取消映射。
隨之而來的問題是:現在怎麼去卸載Dll,一旦結速了,UnhookWindowsHookEx不會做這些事情的,因為我們已經unhook了線程,可以這樣去做:
1.在你想要取消Dll映射之前,安裝另一個Hook。
2.發送一個特殊的訊息到遠程線程
3.在你的hook程式中,捕獲訊息,在回複訊息中, 調用FreeLibrary & UnhookWindowsHookEx
現在,hooks只在映射/取消映射的時候使用, 在這期間不會對"hooked"線程產生影響。 在第二部分中我們還會討論比LoadLibrary更好的機制來使Dll映射不影響目標進程。然而,相對於LoadLibrary技術,這種解決方式只能在Winnt和Win9x下運行。
但是,什麼時候才使用這些竅門呢?當Dll不得不被放到遠程進程中很長時間時(例如,你將一個控制項變成另一個進程的子類),你可能想盡量小的影響目標進程。我不在HookSpy中使用它,因為此時Dll只會注入片刻。
II.CreateRemoteThread & LoadLibrary技術
通常,所有的進程都能通過LoadLibrary動態匯入Dll。但是,怎麼才能讓外部進程調用這個函數?回答是用CreateRemoteThread.
首先,讓我們看一下LoadLibrary and FreeLibrary APIs 的聲明:
HINSTANCE LoadLibrary( LPCTSTR lpLibFileName // address of filename of library module);BOOL FreeLibrary( HMODULE hLibModule // handle to loaded library module);
現在,跟ThreadProc(線程程式,用來傳給CreateRemoteThread)比較一下:
DWORD WINAPI ThreadProc(
LPVOID lpParameter // thread data
);
正如你所看到的,所有的函數都使用同樣的調用轉換,都接受一個32位的參數。傳回值的大小也是一樣的。換句話說,我們可以將LoadLibrary/FreeLibrary 作為線程函數傳遞給CreateRemoteThread.
然而,還有兩個問題:
1.CreateRemoteThread 裡的lpStartAddress參數必須是遠程進程的線程程式的首地址。
2.如果lpParameter(傳遞給ThreadFunc)被認為是一個普通的32位值,(FreeLibrary 認為它是一個HMODULE),所有事情就好說了,然而,如果lpParameter 被描述成一個指標,(LoadLibraryA 將它描述成一個指向字串的指標),那麼它就必須指向遠程進程的一些資料。
第二個問題好解決,用WriteProcessMemory拷貝Dll的模組名(需要LoadLibrary)。
下面是使用CreateRemoteThread & LoadLibrary 技術的步驟:
1.得到遠程進程的一個控制代碼(OpenPreocess).
2.在遠程進程裡給Dll名稱分配記憶體(VirtualAllocEx).
3.將Dll名稱,包括全路徑,寫到分配的記憶體裡(WritePreocessMemory).
4.通過CreateRemoteThread & LoadLibrary將你的Dll映射到遠程進程.
5.等待遠程進程結束(WaitForSingleObject);也就是等到LoadLibrary返回.當我們的DllMain返回的時候線程就結束了(調用原因DLL_PROCESS_ATTACH).
6.返回遠程線程的結束碼,(GetExitCodeThread).注意這個值是LoadLibrary的傳回值,
7.釋放在步驟#2分配的記憶體(VirtualFreeEx).
8.從遠程進程卸載DLL,將#6得到的返回控制代碼HMODULE傳遞給FreeLibrary(lpParameter in CreateRemoteThread).
注意:如果你將Dll注入任何新的線程,確保在卸載的時候全部結束。
9.等待線程結束(WaitForSingleObject).
而且,一旦完成,別忘了關閉所有的控制代碼,對於在#4和#8建立的線程和在步驟#1返回的遠程進程的控制代碼。
HANDLE hThread;char szLibPath[_MAX_PATH]; // The name of our "LibSpy.dll" module // (including full path!);void* pLibRemote; // The address (in the remote process) where // szLibPath will be copied to;DWORD hLibModule; // Base address of loaded module (==HMODULE);HMODULE hKernel32 = ::GetModuleHandle("Kernel32");// initialize szLibPath//...// 1. Allocate memory in the remote process for szLibPath// 2. Write szLibPath to the allocated memorypLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath), MEM_COMMIT, PAGE_READWRITE );::WriteProcessMemory( hProcess, pLibRemote, (void*)szLibPath, sizeof(szLibPath), NULL );// Load "LibSpy.dll" into the remote process// (via CreateRemoteThread & LoadLibrary)hThread = ::CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32, "LoadLibraryA" ), pLibRemote, 0, NULL );::WaitForSingleObject( hThread, INFINITE );// Get handle of the loaded module::GetExitCodeThread( hThread, &hLibModule );// Clean up::CloseHandle( hThread );::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );
// Unload "LibSpy.dll" from the target process// (via CreateRemoteThread & FreeLibrary)hThread = ::CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32, "FreeLibrary" ), (void*)hLibModule, 0, NULL );::WaitForSingleObject( hThread, INFINITE );// Clean up::CloseHandle( hThread );
III.The CreateRemoteThread & WriteProcessMemory 技術
這種方法是將代碼拷貝到要注入進程的地址空間,然後在進程的上下文中執行這些代碼,包括遠程線程的使用。這就不必再使用分離的DLL了。可以用API函數WriteProcessMemory將代碼直接拷貝到遠程地址空間,用CreateRemoteThread啟動代碼執行。
先看一個CreateRemoteThread的聲明
HANDLE CreateRemoteThread( HANDLE hProcess, // handle to process to create thread in LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to security // attributes DWORD dwStackSize, // initial thread stack size, in bytes LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread // function LPVOID lpParameter, // argument for new thread DWORD dwCreationFlags, // creation flags LPDWORD lpThreadId // pointer to returned thread identifier);
用它和CreateThread的聲明相比,有下面幾點不同:
hProcess參數在CreateRemoteThread內是多加的。它是建立線程的進程控制代碼。
lpStartAddress參數是CreateRemoteThread 內描述遠程地址空間內的線程的起始地址。函數必須存在於遠程空間內。我們首先必須將代碼拷貝到遠程空間。
同樣的, lpParameter指標必須存在於遠程空間, 所以我們也要將它拷貝到那兒。
現在我們將這種技術總結為如下幾步:
1.取得遠程空間的控制代碼(OpenProcess).
2.在遠程地址空間為注入的資料分配記憶體(VirtualAllocEx)
3.將自訂結構INJDATA拷貝到分配的記憶體裡(WriteProcessMemory)
4.在遠程地址空間為注入的代碼分配記憶體。
5.將ThreadFunc拷貝到分配的記憶體裡。
6.通過CreateRemoteThread啟動遠端ThreadFunc。
7.等待遠程線程的結束(WaitForSingleObject).
8.從遠程進程返回結果(ReadProcessMemory or GetExitCodeThread).
9.釋放在步驟2#和步驟#4分配的記憶體(VirtualFreeEx);
10.關閉在步驟#1和步驟#6返回的控制代碼(CloseHandle).
ThreadFunc必須服從下面的要求:
1.ThreadFunc 只能調用kernel32.dll 和 user32.dll內部的函數,只有kernel32.dll 和 user32.dll(注意user32沒有被映射的每一個win32進程)能保證本地和目標進程有同樣的載入地址。如果你需要其他庫中的函數,將LoadLibrary and GetProcAddress的地址傳入注入代碼,如果有其他有爭議的DLL已經被映射到了進程空間你也可以用GetModuleHandle 代替of LoadLibrary,
同樣的,如果你想從ThreadFunc內調用你自己的子程式。可以將地址先通過INJDATA傳入到ThreadFunc,再經過ThreadFunc載入到遠程空間。
2.不要使用任何靜態字串,最好將所有的字串通過INJDATA傳遞給ThreadFunc.
為什麼呢?編譯器將所有的靜態字串放入到了可執行檔的".data"段,並且只是在代碼內引用。這樣的話,遠程空間中的ThreadFunc的拷貝將指向一些不存在的地址,(最少不在它的地址空間內).
3.將/GZ編譯開關關掉,在debug裡它是預設的。
4.要不將ThreadFunc and AfterThreadFunc設為靜態,要不取消增量連結。
5.在ThreadFunc內本地變數必須小於page-worth(4kb)。注意在debug中有效4kb中的大約10位元組是內部變數使用的。
6.如果switch塊大於3個case,就要把它象下面這樣分開。
或者改用if-else if
switch( expression ) {
case constant1: statement1; goto END;
case constant2: statement2; goto END;
case constant3: statement2; goto END;
}
switch( expression ) {
case constant4: statement4; goto END;
case constant5: statement5; goto END;
case constant6: statement6; goto END;
}
END:
如果不使用這些規則,很可能使目標進程崩潰,記住,不要將目標進程的空間當成你自己進程的空間。
[源址]http://www.codeproject.com/KB/threads/winspy.aspx