• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            大龍的博客

            常用鏈接

            統計

            最新評論

            向其他進程注入代碼的三種方法

            向其他進程注入代碼的三種方法

            原版地址:http://www.codeproject.com/threads/winspy.asp?df=100&forumid=16291&select=1025152&msg=1025152

            下載整個壓縮包

            下載WinSpy 

            作者:Robert Kuster

            翻譯:袁曉輝(hyzs@sina.com

            摘要:如何向其他線程的地址空間中注入代碼并在這個線程的上下文中執行之。

            目錄:

            ●導言

            Windows 鉤子(Hooks

            CreateRemoteThread LoadLibrary 技術

                   ○進程間通訊

            CreateRemoteThread WriteProcessmemory 技術

                   ○如何使用該技術子類(SubClass)其他進程中的控件

                   ○什么情況下適合使用該技術

            ●寫在最后的話

            ●附錄

            ●參考

            ●文章歷史

            導言:

            我們在Code projectwww.codeproject.com)上可以找到許多密碼間諜程序(譯者注:那些可以看到別的程序中密碼框內容的軟件),他們都依賴于Windows鉤子技術。要實現這個還有其他的方法嗎?有!但是,首先,讓我們簡單回顧一下我們要實現的目標,以便你能弄清楚我在說什么。

            要讀取一個控件的內容,不管它是否屬于你自己的程序,一般來說需要發送 WM_GETTEXT 消息到那個控件。這對edit控件也有效,但是有一種情況例外。如果這個edit控件屬于其他進程并且具有 ES_PASSWORD 風格的話,這種方法就不會成功。只有“擁有(OWNS)”這個密碼控件的進程才可以用 WM_GETTEXT 取得它的內容。所以,我們的問題就是:如何讓下面這句代碼在其他進程的地址空間中運行起來:

            ::SendMessage( hPwdEdit, WM_GETTEXT, nMaxChars, psBuffer );

            一般來說,這個問題有三種可能的解決方案:

            1. 把你的代碼放到一個DLL中;然后用 windows 鉤子把它映射到遠程進程。
            2. 把你的代碼放到一個DLL中;然后用 CreateRemoteThread LoadLibrary 把它映射到遠程進程。
            3. 不用DLL,直接復制你的代碼到遠程進程(使用WriteProcessMemory)并且用CreateRemoteThread執行之。在這里有詳細的說明:

            . Windows 鉤子

            示例程序:HookSpy HookInjEx

            Windows鉤子的主要作用就是監視某個線程的消息流動。一般可分為:

            1. 局部鉤子,只監視你自己進程中某個線程的消息流動。

            2. 遠程鉤子,又可以分為:

            a. 特定線程的,監視別的進程中某個線程的消息;

            b. 系統級的,監視整個系統中正在運行的所有線程的消息。

            如果被掛鉤(監視)的線程屬于別的進程(情況2a2b),你的鉤子過程(hook procedure)必須放在一個動態連接庫(DLL)中。系統把這包含了鉤子過程的DLL映射到被掛鉤的線程的地址空間。Windows會映射整個DLL而不僅僅是你的鉤子過程。這就是為什么windows鉤子可以用來向其他線程的地址空間注入代碼的原因了。

            在這里我不想深入討論鉤子的問題(請看MSDN中對SetWindowsHookEx的說明),讓我再告訴你兩個文檔中找不到的訣竅,可能會有用:

            1. SetWindowHookEx調用成功后,系統會自動映射這個DLL到被掛鉤的線程,但并不是立即映射。因為所有的Windows鉤子都是基于消息的,直到一個適當的事件發生后這個DLL才被映射。比如:

            如果你安裝了一個監視所有未排隊的(nonqueued)的消息的鉤子(WH_CALLWNDPROC),只有一個消息發送到被掛鉤線程(的某個窗口)后這個DLL才被映射。也就是說,如果在消息發送到被掛鉤線程之前調用了UnhookWindowsHookEx那么這個DLL就永遠不會被映射到該線程(雖然SetWindowsHookEx調用成功了)。為了強制映射,可以在調用SetWindowsHookEx后立即發送一個適當的消息到那個線程。

            同理,調用UnhookWindowsHookEx之后,只有特定的事件發生后DLL才真正地從被掛鉤線程卸載。

            2. 當你安裝了鉤子后,系統的性能會受到影響(特別是系統級的鉤子)。然而如果你只是使用的特定線程的鉤子來映射DLL而且不截獲如何消息的話,這個缺陷也可以輕易地避免。看一下下面的代碼片段:

            BOOL APIENTRY DllMain( HANDLE hModule,

                                   DWORD ul_reason_for_call,

                                   LPVOID lpReserved )

            {

                if( ul_reason_for_call == DLL_PROCESS_ATTACH )

                {

                    // LoadLibrary增加引用次數

                    char lib_name[MAX_PATH];

                    ::GetModuleFileName( hModule, lib_name, MAX_PATH );

                    ::LoadLibrary( lib_name );

                    // 安全卸載鉤子

                    ::UnhookWindowsHookEx( g_hHook );

                }   

                return TRUE;

            }

            我們來看一下。首先,我們用鉤子映射這個DLL到遠程線程,然后,在DLL被真正映射進去后,我們立即卸載掛鉤(unhook)。一般來說當第一個消息到達被掛鉤線程后,這DLL會被卸載,然而我們通過LoadLibrary來增加這個DLL的引用次數,避免了DLL被卸載。

            剩下的問題是:使用完畢后如何卸載這個DLLUnhookWindowsHookEx不行了,因為我們已經對那個線程取消掛鉤(unhook)了。你可以這么做:

                   ○在你想要卸載這個DLL之前再安裝一個鉤子;

                   ○發送一個“特殊”的消息到遠程線程;

                   ○在你的新鉤子的鉤子過程(hook procedure)中截獲該消息,調用FreeLibrary (譯者注:對新鉤子調用)UnhookwindowsHookEx

            現在,鉤子只在映射DLL到遠程進程和從遠程進程卸載DLL時使用,對被掛鉤線程的性能沒有影響。也就是說,我們找到了一種(相比第二部分討論的LoadLibrary技術)WinNTWin9x下都可以使用的,不影響目的進程性能的DLL映射機制。

            但是,我們應該在何種情況下使用該技巧呢?通常是在DLL需要在遠程進程中駐留較長時間(比如你要子類[subclass]另一個進程中的控件)并且你不想過于干涉目的進程時比較適合使用這種技巧。我在HookSpy中并沒有使用它,因為那個DLL只是短暫地注入一段時間――只要能取得密碼就足夠了。我在另一個例子HookInjEx中演示了這種方法。HookInjEx把一個DLL映射進“explorer.exe”(當然,最后又從其中卸載),子類了其中的開始按鈕,更確切地說我是把開始按鈕的鼠標左右鍵點擊事件顛倒了一下。

            你可以在本文章的開頭部分找到HookSpyHookInjEx及其源代碼的下載包鏈接。

            . CreateRemoteThread LoadLibrary 技術

            示例程序:LibSpy

            通常,任何進程都可以通過LoadLibrary動態地加載DLL,但是我們如何強制一個外部進程調用該函數呢?答案是CreateRemoteThread

            讓我們先來看看LoadLibraryFreeLibrary的函數聲明:

                 HINSTANCE LoadLibrary(

             

                 HINSTANCE LoadLibrary(

             

                 HINSTANCE LoadLibrary(

             

                 HINSTANCE LoadLibrary(

             LPCTSTR lpLibFileName   // address of filename of library module

            );

            BOOL FreeLibrary(

             HMODULE hLibModule      // handle to loaded library module

            );

            再和CreateRemoteThread的線程過程(thread procedureThreadProc比較一下:

            DWORD WINAPI ThreadProc(

             LPVOID lpParameter   // thread data

            );

            你會發現所有的函數都有同樣的調用約定(calling convention)、都接受一個32位的參數并且返回值類型的大小也一樣。也就是說,我們可以把LoadLibrary/FreeLibrary的指針作為參數傳遞給CrateRemoteThread

            然而,還有兩個問題(參考下面對CreateRemoteThread的說明)

            1. 傳遞給ThreadProclpStartAddress 參數必須為遠程進程中的線程過程的起始地址。

            2. 如果把ThreadProclpParameter參數當做一個普通的32位整數(FreeLibrary把它當做HMODULE)那么沒有如何問題,但是如果把它當做一個指針(LoadLibrary把它當做一個char*),它就必須指向遠程進程中的內存數據。

            第一個問題其實已經迎刃而解了,因為LoadLibraryFreeLibrary都是存在于kernel32.dll中的函數,而kernel32可以保證任何“正常”進程中都存在,且其加載地址都是一樣的。(參看附錄A)于是LoadLibrary/FreeLibrary在任何進程中的地址都是一樣的,這就保證了傳遞給遠程進程的指針是個有效的指針。

            第二個問題也很簡單:把DLL的文件名(LodLibrary的參數)用WriteProcessMemory復制到遠程進程。

            所以,使用CreateRemoteThreadLoadLibrary技術的步驟如下:

            1. 得到遠程進程的HANDLE(使用OpenProcess)。

            2. 在遠程進程中為DLL文件名分配內存(VirtualAllocEx)。

            3. DLL的文件名(全路徑)寫到分配的內存中(WriteProcessMemory

            4. 使用CreateRemoteThreadLoadLibrary把你的DLL映射近遠程進程。

            5. 等待遠程線程結束(WaitForSingleObject),即等待LoadLibrary返回。也就是說當我們的DllMain(是以DLL_PROCESS_ATTACH為參數調用的)返回時遠程線程也就立即結束了。

            6. 取回遠程線程的結束碼(GetExitCodeThtread),即LoadLibrary的返回值――我們DLL加載后的基地址(HMODULE)。

            7. 釋放第2步分配的內存(VirtualFreeEx)。

            8. CreateRemoteThreadFreeLibraryDLL從遠程進程中卸載。調用時傳遞第6步取得的HMODULEFreeLibrary(通過CreateRemoteThreadlpParameter參數)。

            9. 等待線程的結束(WaitSingleObject)。

            同時,別忘了在最后關閉所有的句柄:第48步得到的線程句柄,第1步得到的遠程進程句柄。

            現在我們看看LibSpy的部分代碼,分析一下以上的步驟是任何實現的。為了簡單起見,沒有包含錯誤處理和支持Unicode的代碼。

            HANDLE hThread;

            char    szLibPath[_MAX_PATH]; // "LibSpy.dll"的文件名

                                           // (包含全路徑!);

            void*   pLibRemote;             // szLibPath 將要復制到地址

            DWORD   hLibModule;   //已加載的DLL的基地址(HMODULE;

            HMODULE hKernel32 = ::GetModuleHandle("Kernel32");

            //初始化 szLibPath

            //...

            // 1. 在遠程進程中為szLibPath 分配內存

            // 2. szLibPath到分配的內存

            pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath),

                                           MEM_COMMIT, PAGE_READWRITE );

            ::WriteProcessMemory( hProcess, pLibRemote, (void*)szLibPath,

                                  sizeof(szLibPath), NULL );

            // 加載 "LibSpy.dll" 到遠程進程

            // (通過 CreateRemoteThread & LoadLibrary)

            hThread = ::CreateRemoteThread( hProcess, NULL, 0,

                        (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,

                                                   "LoadLibraryA" ),

                         pLibRemote, 0, NULL );

            ::WaitForSingleObject( hThread, INFINITE );

            //取得DLL的基地址

            ::GetExitCodeThread( hThread, &hLibModule );

            //掃尾工作

            ::CloseHandle( hThread );

            ::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );

            我們放在DllMain中的真正要注入的代碼(比如為SendMessage)現在已經被執行了(由于DLL_PROCESS_ATTACH),所以現在可以把DLL從目的進程中卸載了。

            // 從目標進程卸載LibSpu.dll

            // (通過 CreateRemoteThread & FreeLibrary)

            hThread = ::CreateRemoteThread( hProcess, NULL, 0,

                        (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,

                                                   "FreeLibrary" ),

                        (void*)hLibModule, 0, NULL );

            ::WaitForSingleObject( hThread, INFINITE );

            // 掃尾工作

            ::CloseHandle( hThread );

            進程間通訊

            到目前為止,我們僅僅討論了任何向遠程進程注入DLL,然而,在多數情況下被注入的DLL需要和你的程序以某種方式通訊(記住,那個DLL是被映射到遠程進程中的,而不是在你的本地程序中!)。以密碼間諜為例:那個DLL需要知道包含了密碼的的控件的句柄。很明顯,這個句柄是不能在編譯期間硬編碼(hardcoded)進去的。同樣,當DLL得到密碼后,它也需要把密碼發回我們的程序。

            幸運的是,這個問題有很多種解決方案:文件映射(Mapping),WM_COPYDATA,剪貼板等。還有一種非常便利的方法#pragma data_seg。這里我不想深入討論因為它們在MSDN(看一下Interprocess Communications部分)或其他資料中都有很好的說明。我在LibSpy中使用的是#pragma data_seg

            你可以在本文章的開頭找到LibSpy及源代碼的下載鏈接。

            .CreateRemoteThreadWriteProcessMemory技術

             

             

             

            示例程序:WinSpy

            另一種注入代碼到其他進程地址空間的方法是使用WriteProcessMemory API。這次你不用編寫一個獨立的DLL而是直接復制你的代碼到遠程進程(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參數。這是要在其中創建線程的進程的句柄。

             

             

             

            CreateRemoteThreadlpStartAddress參數必須指向遠程進程的地址空間中的函數。這個函數必須存在于遠程進程中,所以我們不能簡單地傳遞一個本地ThreadFucn的地址,我們必須把代碼復制到遠程進程。

             

             

             

            ●同樣,lpParameter參數指向的數據也必須存在于遠程進程中,我們也必須復制它。

            現在,我們總結一下使用該技術的步驟:

            1. 得到遠程進程的HANDLEOpenProcess)。

             

             

             

            2. 在遠程進程中為要注入的數據分配內存(VirtualAllocEx)、

             

             

             

            3. 把初始化后的INJDATA結構復制到分配的內存中(WriteProcessMemory)。

             

             

             

            4. 在遠程進程中為要注入的數據分配內存(VirtualAllocEx)。

             

             

             

            5. ThreadFunc復制到分配的內存中(WriteProcessMemory)。

             

             

             

            6. CreateRemoteThread啟動遠程的ThreadFunc

             

             

             

            7. 等待遠程線程的結束(WaitForSingleObject)。

             

             

             

            8. 從遠程進程取回指執行結果(ReadProcessMemory GetExitCodeThread)。

             

             

             

            9. 釋放第24步分配的內存(VirtualFreeEx)。

             

             

             

            10. 關閉第61步打開打開的句柄。

            另外,編寫ThreadFunc時必須遵守以下規則:

             

             

             

            1. ThreadFunc不能調用除kernel32.dlluser32.dll之外動態庫中的API函數。只有kernel32.dlluser32.dll(如果被加載)可以保證在本地和目的進程中的加載地址是一樣的。(注意:user32并不一定被所有的Win32進程加載!)參考附錄A。如果你需要調用其他庫中的函數,在注入的代碼中使用LoadLibraryGetProcessAddress強制加載。如果由于某種原因,你需要的動態庫已經被映射進了目的進程,你也可以使用GetMoudleHandle代替LoadLibrary。同樣,如果你想在ThreadFunc中調用你自己的函數,那么就分別復制這些函數到遠程進程并通過INJDATA把地址提供給ThreadFunc

             

             

             

            2. 不要使用static字符串。把所有的字符串提供INJDATA傳遞。為什么?編譯器會把所有的靜態字符串放在可執行文件的“.data”段,而僅僅在代碼中保留它們的引用(即指針)。這樣,遠程進程中的ThreadFunc就會執行不存在的內存數據(至少沒有在它自己的內存空間中)。

             

             

             

            3. 去掉編譯器的/GZ編譯選項。這個選項是默認的(看附錄B)。

             

             

             

            4. 要么把ThreadFuncAfterThreadFunc聲明為static,要么關閉編譯器的“增量連接(incremental linking)”(看附錄C)。

             

             

             

            5. ThreadFunc中的局部變量總大小必須小于4k字節(看附錄D)。注意,當degug編譯時,這4k中大約有10個字節會被事先占用。

             

             

             

            6. 如果有多于3switch分支的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:

             

             

             

            (參考附錄E

            如果你不按照這些游戲規則玩的話,你注定會使目的進程掛掉!記住,不要妄想遠程進程中的任何數據會和你本地進程中的數據存放在相同內存地址!(參看附錄F

             

             

             

            (原話如此:You will almost certainly crash the target process if you don't play by those rules. Just remember: Don't assume anything in the target process is at the same address as it is in your process.

            GetWindowTextRemote(A/W)

            所有取得遠程edit中文本的工作都被封裝進這個函數:GetWindowTextRemote(A/W)

             

             

             

            int GetWindowTextRemoteA( HANDLE hProcess, HWND hWnd, LPSTR lpString );

             

             

             

            int GetWindowTextRemoteW( HANDLE hProcess, HWND hWnd, LPWSTR lpString );

            參數:

             

             

             

            hProcess

             

             

             

                目的edit所在的進程句柄

             

             

             

            hWnd

             

             

             

                目的edit的句柄

             

             

             

            lpString

             

             

             

                接收字符串的緩沖

            返回值:

             

             

             

                成功復制的字符數。

            讓我們看以下它的部分代碼,特別是注入的數據和代碼。為了簡單起見,沒有包含支持Unicode的代碼。

            INJDATA

            typedef LRESULT     (WINAPI *SENDMESSAGE)(HWND,UINT,WPARAM,LPARAM);

            typedef struct {   

             

             

             

                HWND hwnd;                    // handle to edit control

             

             

             

                SENDMESSAGE fnSendMessage;   // pointer to user32!SendMessageA

                char psText[128];    // buffer that is to receive the password

             

             

             

            } INJDATA;

             

             

             

             

             

             

             

             

             

             

            INJDATA是要注入遠程進程的數據。在把它的地址傳遞給SendMessageA之前,我們要先對它進行初始化。幸運的是unse32.dll在所有的進程中(如果被映射)總是被映射到相同的地址,所以SendMessageA的地址也總是相同的,這也保證了傳遞給遠程進程的地址是有效的。

            ThreadFunc

            static DWORD WINAPI ThreadFunc (INJDATA *pData)

             

             

             

            {

             

             

             

                pData->fnSendMessage( pData->hwnd, WM_GETTEXT,   // 得到密碼

             

             

             

                                      sizeof(pData->psText),

             

             

             

                                      (LPARAM)pData->psText ); 

             

             

             

                return 0;

             

             

             

            }

            // This function marks the memory address after ThreadFunc.

             

             

             

            // int cbCodeSize = (PBYTE) AfterThreadFunc - (PBYTE) ThreadFunc.

             

             

             

            static void AfterThreadFunc (void)

             

             

             

            {

             

             

             

            }

            ThreadFunc是遠程線程實際執行的代碼。

             

             

             

            ●注意AfterThreadFunc是如何計算ThreadFunc的代碼大小的。一般地,這不是最好的辦法,因為編譯器會改變你的函數中代碼的順序(比如它會把ThreadFunc放在AfterThreadFunc之后)。然而,你至少可以確定在同一個工程中,比如在我們的WinSpy工程中,你函數的順序是固定的。如果有必要,你可以使用/ORDER連接選項,或者,用反匯編工具確定ThreadFunc的大小,這個也許會更好。

            如何用該技術子類(subclass)一個遠程控件

             

             

             

            示例程序:InjectEx

            讓我們來討論一個更復雜的問題:如何子類屬于其他進程的一個控件?

            首先,要完成這個任務,你必須復制兩個函數到遠程進程:

             

             

             

            1. ThreadFunc,這個函數通過調用SetWindowLong API來子類遠程進程中的控件,

             

             

             

            2. NewProc, 那個控件的新窗口過程(Window Procedure)。

            然而,最主要的問題是如何傳遞數據到遠程的NewProc。因為NewProc是一個回調(callback)函數,它必須符合特定的要求(譯者注:這里指的主要是參數個數和類型),我們不能再簡單地傳遞一個INJDATA的指針作為它的參數。幸運的我已經找到解決這個問題的方法,而且是兩個,但是都要借助于匯編語言。我一直都努力避免使用匯編,但是這一次,我們逃不掉了,沒有匯編不行的。

            解決方案1

             

             

             

            看下面的圖片:

             

             

             

             

            不知道你是否注意到了,INJDATA緊挨著NewProc放在NewProc的前面?這樣的話在編譯期間NewProc就可以知道INJDATA的內存地址。更精確地說,它知道INJDATA相對于它自身地址的相對偏移,但是這并不是我們真正想要的。現在,NewProc看起來是這個樣子:

             

             

             

            static LRESULT CALLBACK NewProc(

             

             

             

             HWND hwnd,       // handle to window

             

             

             

             UINT uMsg,       // message identifier

             

             

             

             WPARAM wParam,   // first message parameter

             

             

             

             LPARAM lParam ) // second message parameter

             

             

             

            {

             

             

             

                INJDATA* pData = (INJDATA*) NewProc; // pData 指向

             

             

             

                                                      // NewProc;

             

             

             

                pData--;              // 現在pData指向INJDATA;

             

             

             

                                      // 記住,INJDATA 在遠程進程中剛好位于

             

             

             

                                      // NewProc的緊前面;

                //-----------------------------

             

             

             

                // 子類代碼

             

             

             

                // ........

             

             

             

                //-----------------------------

                //調用用來的的窗口過程;

             

             

             

                // fnOldProc (SetWindowLong返回) 是被ThreadFunc(遠程進程中的)初始化

             

             

             

               // 并且存儲在遠程進程中的INJDATA里的;

             

             

             

                return pData->fnCallWindowProc( pData->fnOldProc,

             

             

             

                                                hwnd,uMsg,wParam,lParam );

             

             

             

            }

            然而,還有一個問題,看第一行:

             

             

             

            INJDATA* pData = (INJDATA*) NewProc;

            pData被硬編碼為我們進程中NewProc的地址,但這是不對的。因為NewProc會被復制到遠程進程,那樣的話,這個地址就錯了。

            C/C++沒有辦法解決這個問題,可以用內聯的匯編來解決。看修改后的NewProc

            static LRESULT CALLBACK NewProc(

             

             

             

             HWND hwnd,      // handle to window

             

             

             

             UINT uMsg,      // message identifier

             

             

             

             WPARAM wParam, // first message parameter

             

             

             

             LPARAM lParam ) // second message parameter

             

             

             

            {

             

             

             

                // 計算INJDATA 的地址;

             

             

             

                // 在遠程進程中,INJDATA剛好在

             

             

             

                //NewProc的前面;

             

             

             

                INJDATA* pData;

             

             

             

                _asm {

             

             

             

                    call    dummy

             

             

             

            dummy:

             

             

             

                    pop     ecx         // <- ECX 中存放當前的EIP

             

             

             

                    sub     ecx, 9      // <- ECX 中存放NewProc的地址

             

             

             

                    mov     pData, ecx

             

             

             

                }

             

             

             

                pData--;

                //-----------------------------

             

             

             

                // 子類代碼

             

             

             

                // ........

             

             

             

                //-----------------------------

                // 調用原來的窗口過程

             

             

             

                return pData->fnCallWindowProc( pData->fnOldProc,

             

             

             

                                                hwnd,uMsg,wParam,lParam );

             

             

             

            }

            這是什么意思?每個進程都有一個特殊的寄存器,這個寄存器指向下一條要執行的指令的內存地址,即32IntelAMD處理器上所謂的EIP寄存器。因為EIP是個特殊的寄存器,所以你不能像訪問通用寄存器(EAXEBX等)那樣來訪問它。換句話說,你找不到一個可以用來尋址EIP并且對它進行讀寫的操作碼(OpCode)。然而,EIP同樣可以被JMPCALLRET等指令隱含地改變(事實上它一直都在改變)。讓我們舉例說明32位的IntelAMD處理器上CALL/RET是如何工作的吧:

            當我們用CALL調用一個子程序時,這個子程序的地址被加載進EIP。同時,在EIP被改變之前,它以前的值會被自動壓棧(在后來被用作返回指令指針[return instruction-pointer])。在子程序的最后RET指令自動把這個值從棧中彈出到EIP

            現在我們知道了如何通過CALLRET來修改EIP的值了,但是如何得到他的當前值?

             

             

             

            還記得CALLEIP的值壓棧了嗎?所以為了得到EIP的值我們調用了一個“假(dummy)函數”然后彈出棧頂值。看一下編譯過的NewProc

            Address   OpCode/Params   Decoded instruction

             

             

             

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

             

             

             

            :00401000 55       push ebp            ; entry point of

             

             

             

                                                           ; NewProc

             

             

             

            :00401001 8BEC            mov ebp, esp

             

             

             

            :00401003 51              push ecx

             

             

             

            :00401004 E800000000      call 00401009       ; *a*    call dummy

             

             

             

            :00401009 59            pop ecx            ; *b*

             

             

             

            :0040100A 83E909          sub ecx, 00000009   ; *c*

             

             

             

            :0040100D 894DFC          mov [ebp-04], ecx   ; mov pData, ECX

             

             

             

            :00401010 8B45FC          mov eax, [ebp-04]

             

             

             

            :00401013 83E814          sub eax, 00000014   ; pData--;

             

             

             

            .....

             

             

             

            .....

             

             

             

            :0040102D 8BE5            mov esp, ebp

             

             

             

            :0040102F 5D              pop ebp

             

             

             

            :00401030 C21000          ret 0010

            a. 一個假的函數調用;僅僅跳到下一條指令并且(譯者注:更重要的是)把EIP壓棧。

             

             

             

            b. 彈出棧頂值到ECXECX就保存的EIP的值;這也就是那條“pop ECX”指令的地址。

             

             

             

            c. 注意從NewProc的入口點到“pop ECX”指令的“距離”為9字節;因此把ECX減去9就得到的NewProc的地址了。

            這樣一來,不管被復制到什么地方,NewProc總能正確計算自身的地址了!然而,要注意從NewProc的入口點到“pop ECX”的距離可能會因為你的編譯器/鏈接選項的不同而不同,而且在ReleaseDegub版本中也是不一樣的。但是,不管怎樣,你仍然可以在編譯期知道這個距離的具體值。

             

             

             

            1. 首先,編譯你的函數。

             

             

             

            2. 在反匯編器(disassembler)中查出正確的距離值。

             

             

             

            3. 最后,使用正確的距離值重新編譯你的程序。

            這也是InjectEx中使用的解決方案。InjectExHookInjEx類似,交換開始按鈕上的鼠標左右鍵點擊事件。

            解決方案2

            在遠程進程中把INJDATA放在NewProc的前面并不是唯一的解決方案。看一下下面的NewProc

             

             

             

            static LRESULT CALLBACK NewProc(

             

             

             

             HWND hwnd,      // handle to window

             

             

             

             UINT uMsg,      // message identifier

             

             

             

             WPARAM wParam, // first message parameter

             

             

             

             LPARAM lParam ) // second message parameter

             

             

             

            {

             

             

             

                INJDATA* pData = 0xA0B0C0D0;    // 一個假設

                //-----------------------------

             

             

             

                // 子類代碼

             

             

             

                // ........

             

             

             

                //-----------------------------

                // 調用以前的窗口過程

             

             

             

                return pData->fnCallWindowProc( pData->fnOldProc,

             

             

             

                                                hwnd,uMsg,wParam,lParam );

             

             

             

            }

            這里,0XA0B0C0D0僅僅是INJDATA在遠程進程中的地址的占位符(placeholder)。你無法在編譯期得到這個值,然而你在調用VirtualAllocEx(為INJDATA分配內存時)后確實知道INJDATA的地址!(譯者注:就是VirtualAllocEx的返回值)

            我們的NewProc編譯后大概是這個樣子:

             

             

             

            Address   OpCode/Params     Decoded instruction

             

             

             

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

             

             

             

            :00401000 55                push ebp

             

             

             

            :00401001 8BEC              mov ebp, esp

             

             

             

            :00401003 C745FCD0C0B0A0    mov [ebp-04], A0B0C0D0

             

             

             

            :0040100A ...

             

             

             

            ....

             

             

             

            ....

             

             

             

            :0040102D 8BE5              mov esp, ebp

             

             

             

            :0040102F 5D                pop ebp

             

             

             

            :00401030 C21000            ret 0010

            編譯后的機器碼應該為:558BECC745FCD0C0B0A0......8BE55DC21000

            現在,你這么做:

             

             

             

            1. INJDATAThreadFuncNewFunc復制到目的進程。

             

             

             

            2. 改變NewPoc的機器碼,讓pData指向INJDATA的真實地址。

             

             

             

            比如,假設INJDATA的的真實地址(VirtualAllocEx的返回值)為0x008a0000,你把NewProc的機器碼改為:

            558BECC745FCD0C0B0A0......8BE55DC21000

             

             

             

             

             

             

            <- 修改前的 NewProc 1

             

             

             

             

             

             

            558BECC745FC00008A00......8BE55DC21000

             

             

             

             

             

             

            <- 修改后的 NewProc

             

             

             

             

             

             

                也就是說,你把假值 A0B0C0D0改為INJDATA的真實地址2

             

             

             

             

             

             

             

            3. 開始指向遠程的ThreadFunc,它子類了遠程進程中的控件。

            ¹ 你可能會問,為什么A0B0C0D0008a0000在編譯后的機器碼中為逆序的。這時因為IntelAMD處理器使用littl-endian標記法(little-endian notation)來表示它們的(多字節)數據。換句話說:一個數的低字節(low-order byte)在內存中被存放在最低位,高字節(high-order byte)存放在最高位。

             

             

             

            想像一下,存放在四個字節中的單詞“UNIX”,在big-endia系統中被存儲為“UNIX”,在little-endian系統中被存儲為“XINU”。

            ² 一些蹩腳的破解者用類似的方法來修改可執行文件的機器碼,但是一個程序一旦載入內存,就不能再更改自身的機器碼(一個可執行文件的.text段是寫保護的)。我們能修改遠程進程中的NewProc是因為它所處的那塊內存在分配時給予了PAGE_EXECUTE_READWRITE屬性。

            何時使用CreateRemoteThreadWriteProcessMemory技術

            通過CreateRemoteThreadWriteProcessMemory來注入代碼的技術,和其他兩種方法相比,不需要一個額外的DLL文件,因此更靈活,但也更復雜更危險。一旦你的ThreadFunc中有錯誤,遠程線程會立即崩潰(看附錄F)。調試一個遠程的ThreadFunc也是場惡夢,所以你應該在僅僅注入若干條指令時才使用這個方法。要注入大量的代碼還是使用另外兩種方法吧。

            再說一次,你可以在文章的開頭部分下載到WinSpyInjectEx和它們的源代碼。

            寫在最后的話

            最后,我們總結一些目前還沒有提到的東西:

             

             

             

            方法

             

             

             

             

             

             

             

            適用的操作系統

             

             

             

             

             

             

             

            可操作的進程進程

             

             

             

             

             

             

             

            I. Windows鉤子

            Win9x WinNT

             

             

             

             

             

             

             

            僅僅鏈接了USER32.DLL的進程1

             

             

             

             

             

             

             

            II. CreateRemoteThread & LoadLibrary

            WinNT2

             

             

             

             

             

             

             

            所有進程3,包括系統服務4

             

             

             

             

             

             

             

            III. CreateRemoteThread & WriteProcessMemory

            WinNT

             

             

             

             

             

             

             

            所有進程,包括系統服務

             

             

             

             

             

             

             

            1. 很明顯,你不能給一個沒有消息隊列的線程掛鉤。同樣SetWindowsHookEx也對系統服務不起作用(就算它們連接了USER32)。

             

             

             

            2. Win9x下沒有CreateRemoteThreadVirtualAllocEx(事實上可以在9x上模擬它們,但是到目前為止還只是個神話)

             

             

             

            3.  所有進程 所有的Win32進程 csrss.exe

             

             

             

             

             

             

             

            本地程序(native application)比如smss.exe, os2ss.exe, autochk.exe,不使用Win32 APIs,也沒有連接到kernel32.dll。唯一的例外是csrss.exewin32子系統自身。它是一個本地程序,但是它的一些庫(比如winsrv.dll)需要Win32 DLL包括kernel32.dll.

             

             

             

             

             

             

             

            4.如果你向注入代碼到系統服務或csrss.exe,在打開遠程進程的句柄(OpenProcess)之前把你的進程的優先級調整為“SeDebugprovilege(AdjustTokenPrivileges)

            大概就這些了吧。還有一點你需要牢記在心:你注入的代碼(特別是存在錯誤時)很容易就會把目的進程拖垮。記住:責任隨權利而來(Power comes with responsibility)!

            這篇文章中的很多例子都和密碼有關,看過這篇文章后你可能也會對Zhefu Zhang(譯者注:大概是一位中國人,張哲夫??)寫的Supper Password Spy++感興趣。他講解了如何從IE的密碼框中得到密碼,也說了如何保護你的密碼不被這種攻擊。

            最后一點:讀者的反饋是文章作者的唯一報酬,所以如果你認為這篇文章有作用,請留下你的評論或給它投票。更重要的是,如果你發現有錯誤或bug;或你認為什么地方做得還不夠好,有需要改進的地方;或有不清楚的地方也都請告訴我。

            感謝

             

             

             

             

             

             

             

            首先,我要感謝我在CodeGuru(這篇文章最早是在那兒發表的)的讀者,正是由于你們的鼓勵和支持這篇文章才得以從最初的1200單詞發展到今天這樣6000單詞的“龐然大物”。如果說有一個人我要特別感謝的話,他就是Rado Picha。這篇文章的一部分很大程度上得益于他對我的建議和幫助。最后,但也不能算是最后,感謝Susan Moore,他幫助我跨越了那個叫做“英語”的雷區,讓這篇文章更加通順達意。

            ――――――――――――――――――――――――――――――――――――

            附錄

            A)     為什么kernel32.dlluser32.dll中是被映射到相同的內存地址?

             

             

             

             

             

             

             

            我的假定:以為微軟的程序員認為這么做可以優化速度。讓我們來解釋一下這是為什么。

             

             

             

             

             

             

             

            一般來說,一個可執行文件包含幾個段,其中一個為“.reloc”段。

            當鏈接器生成EXEDLL時,它假定這個文件會被加載到一個特定的地址,也就是所謂的假定/首選加載/基地址(assumed/preferred load/base address)。內存映像(image)中的所有絕對地址都時基于該“鏈接器假定加載地址”的。如果由于某些原因,映像沒有加載到這個地址,那么PE加載器(PE loader)就不得不修正該映像中的所有絕對地址。這就是“.reloc”段存在的原因:它包含了一個該映像中所有的“鏈接器假定地址”與真正加載到的地址之間的差異的列表(注意:編譯器產生的大部分指令都使用一種相對尋址模式,所以,真正需要重定位[relocation]的地方并沒有你想像的那么多)。如果,從另一方面說,加載器可以把映像加載到鏈接器首選地址,那么“.reloc”段就會被徹底忽略。

            但是,因為每一個Win32程序都需要kernel32.dll,大部分需要user32.dll,所以如果總是把它們兩個映射到其首選地址,那么加載器就不用修正kernel32.dlluser32.dll中的任何(絕對)地址,加載時間就可以縮短。

            讓我們用下面的例子來結束這個討論:

             

             

             

             

             

             

             

            把一個APP.exe的加載地址改為kernel32的(/base:"0x77e80000")或user32的(/base:"0x77e10000")首選地址。如果App.exe沒有引入UESE32,就強制LoadLibrary。然后編譯App.exe,并運行它。你會得到一個錯誤框(“非法的系統DLL重定位”),App.exe無法被加載。

            為什么?當一個進程被創建時,Win2000WinXP的加載器會檢查kernel32.dlluser32.dll是否被映射到它們的首選地址(它們的名稱是被硬編碼進加載器的),如果沒有,就會報錯。在WinNT4 ole32.dll也會被檢查。在WinNT3.51或更低版本中,則不會有任何檢查,kernel32.dlluser32.dll可以被加載到任何地方。唯一一個總是被加載到首選地址的模塊是ntdll.dll,加載器并不檢查它,但是如果它不在它的首選地址,進程根本無法創建。

            總結一下:在WinNT4或更高版本的操作系統中:

             

             

             

             

             

             

             

            ●總被加載到它們的首選地址的DLL有:kernel32.dlluser32.dllntdll.dll

            Win32程序(連同csrss.exe)中一定存在的DLLkernel32.dllntdll.dll

            ●所有進程中都存在的dllntdll.dll

            B)     /GZ編譯開關

             

             

             

             

             

             

             

            Debug時,/GZ開關默認是打開的。它可以幫你捕捉一些錯誤(詳細內容參考文檔)。但是它對我們的可執行文件有什么影響呢?

            /GZ被使用時,編譯器會在每個函數,包含函數調用中添加額外的代碼(添加到每個函數的最后面)來檢查ESP棧指針是否被我們的函數更改過。但是,等等,ThreadFunc中被添加了一個函數調用?這就是通往災難的道路。因為,被復制到遠程進程中的ThreadFunc將調用一個在遠程進程中不存在的函數。

            C)     static函數和增量連接(Incremental linking

             

             

             

             

             

             

             

            增量連接可以縮短連接的時間,在增量編譯時,每個函數調用都是通過一個額外的JMP指令來實現的(一個例外就是被聲明為static的函數!)這些JMP允許連接器移動函數在內存中的位置而不用更新調用該函數的CALL。但是就是這個JMP給我們帶來了麻煩:現在ThreadFuncAfterThreadFunc將指向JMP指令而不是它們的真實代碼。所以,當計算ThreadFunc的大小時:

            const int cbCodeSize = ((LPBYTE) AfterThreadFunc - (LPBYTE) ThreadFunc);

            實際得到的將是指向ThreadFuncAfterThreadFuncJMP指令之間的“距離”。現在假設我們的ThreadFunc004014C0,和其對應的JMP指令在00401020

            :00401020   jmp 004014C0

             ...

            :004014C0   push EBP          ; ThreadFunc的真實地址

            :004014C1   mov EBP, ESP

             

             

             

             

             

             

             

             ...

             

             

             

             

             

             

             

            然后,

            WriteProcessMemory( .., &ThreadFunc, cbCodeSize, ..);

             

             

             

             

             

             

             

            將把“JMP 004014C0和其后的cbCodeSize范圍內的代碼而不是ThreadFunc復制到遠程進程。遠程線程首先會執行“JMP 004010C0,然后一直執行到這個進程代碼的最后一條指令(譯者注:這當然不是我們想要的結果)。

            然而,如果一個函數被聲明為static,就算使用增量連接,也不會被替換為JMP指令。這就是為什么我在規則#4中說把ThreadFuncAfterThreadFunc聲明為static或禁止增量連接的原因了。(關于增量連接的其他方面請參看Matt Pietrek寫的“Remove Fatty Deposits from Your Applications Using Our 32-bit Liposuction Tools”)

            D)     為什么ThreadFunc只能有4K的局部變量?

                  局部變量總是保存在棧上的。假設一個函數有256字節的局部變量,當進入該函數時(更確切地說是在functions prologue中),棧指針會被減去256。像下面的函數:

            void Dummy(void) {

                BYTE var[256];

                var[0] = 0;

                var[1] = 1;

                var[255] = 255;

            }

             

             

             

             

             

             

             

            會被編譯為類似下面的指令:

             

             

             

             

             

             

             

            :00401000   push ebp

            :00401001   mov ebp, esp

            :00401003   sub esp, 00000100           ; change ESP as storage for

                                                                  ; local variables is needed

            :00401006   mov byte ptr [esp], 00      ; var[0] = 0;

            :0040100A   mov byte ptr [esp+01], 01   ; var[1] = 1;

            :0040100F   mov byte ptr [esp+FF], FF   ; var[255] = 255;

            :00401017   mov esp, ebp                ; restore stack pointer

            :00401019   pop ebp

            :0040101A   ret

            請注意在上面的例子中ESP(棧指針)是如何被改變的。但是如果一個函數有多于4K的局部變量該怎么辦?這種情況下,棧指針不會被直接改變,而是通過一個函數調用來正確實現ESP的改變。但是就是這個“函數調用”導致了ThreadFunc的崩潰,因為它在遠程進程中的拷貝將會調用一個不存在的函數。

            讓我們來看看文檔關于棧探針(stack probes)和/Gs編譯選項的說明:

             

             

             

             

             

             

             

            /Gssize選項是一個允許你控制棧探針的高級特性。棧探針是編譯器插入到每個函數調用中的一系列代碼。當被激活時,棧探針將溫和地按照存儲函數局部變量所需要的空間大小來移動。

            如果一個函數需要大于size指定的局部變量空間,它的棧探針將被激活。默認的size為一個頁的大小(在80x86上為4k)。這個值可以使一個Win32程序和Windows NT的虛擬內存管理程序和諧地交互,在運行期間向程序棧增加已提交的內存總數。

            我能確定你們對上面的敘述(“棧探針將溫和地按照存儲函數局部變量所需要的空間大小來移動”)感到奇怪。這些編譯選項(他們的描述!)有時候真的讓人很惱火,特別是當你想真的了解它們是怎么工作的時候。打個比方,如果一個函數需要12kb的空間來存放局部變量,棧上的內存是這樣“分配”的

             

             

             

             

             

             

             

            sub    esp, 0x1000    ; 先“分配”4 Kb

             

             

             

             

             

             

             

            test [esp], eax      ; touches memory in order to commit a

             

             

             

             

             

             

             

                                  ; new page (if not already committed)

             

             

             

             

             

             

             

            sub    esp, 0x1000    ; “分配”第二個 4 Kb

             

             

             

             

             

             

             

            test [esp], eax      ; ...

             

             

             

             

             

             

             

            sub    esp, 0x1000

             

             

             

             

             

             

             

            test [esp], eax

            注意棧指針是如何以4Kb為單位移動的,更重要的是每移動一步后使用test對棧底的處理(more importantly, how the bottom of the stack is "touched" after each step)。這可以確保了在“分配”下一個頁之前,包含棧底的頁已經被提交。

            繼續閱讀文檔的說明:

             

             

             

             

             

             

             

            “每一個新的線程會擁有(receives)自己的棧空間,這包括已經提交的內存和保留的內存。默認情況下每個線程使用1MB的保留內存和一個頁大小的以提交內存。如果有必要,系統將從保留內存中提交一個頁。”(看MSDNGreateThread > dwStackSize > “Thread Stack Size”

            ..現在為什么文檔中說“這個值可以使一個Win32程序和Windows NT的虛擬內存管理程序和諧地交互”也很清楚了。

            E)     為什么我要把多于3case分支的swith分割開來呢?

             

             

             

             

             

             

             

            同樣,用例子來說明會簡單些:

             

             

             

             

             

             

             

            int Dummy( int arg1 )

             

             

             

             

             

             

             

            {

             

             

             

             

             

             

             

                int ret =0;

                switch( arg1 ) {

             

             

             

             

             

             

             

                case 1: ret = 1; break;

             

             

             

             

             

             

             

                case 2: ret = 2; break;

             

             

             

             

             

             

             

                case 3: ret = 3; break;

             

             

             

             

             

             

             

                case 4: ret = 0xA0B0; break;

             

             

             

             

             

             

             

                }

             

             

             

             

             

             

             

                return ret;

             

             

             

             

             

             

             

            }

             

             

             

             

             

             

             

            將會被編譯為類似下面的代碼:

             

             

             

             

             

             

             

            Address   OpCode/Params    Decoded instruction

             

             

             

             

             

             

             

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

             

             

             

             

             

             

             

                                                         ; arg1 -> ECX

             

             

             

             

             

             

             

            :00401000 8B4C2404         mov ecx, dword ptr [esp+04]

             

             

             

             

             

             

             

            :00401004 33C0             xor eax, eax     ; EAX = 0

             

             

             

             

             

             

             

            :00401006 49               dec ecx          ; ECX --

             

             

             

             

             

             

             

            :00401007 83F903           cmp ecx, 00000003

             

             

             

             

             

             

             

            :0040100A 771E             ja 0040102A

            ; JMP to one of the addresses in table <B>***</B>

             

             

             

             

             

             

             

            ; note that ECX contains the offset

             

             

             

             

             

             

             

            :0040100C FF248D2C104000   jmp dword ptr [4*ecx+0040102C]

            :00401013 B801000000       mov eax, 00000001   ; case 1: eax = 1;

             

             

             

             

             

             

             

            :00401018 C3               ret

             

             

             

             

             

             

             

            :00401019 B802000000       mov eax, 00000002   ; case 2: eax = 2;

             

             

             

             

             

             

             

            :0040101E C3               ret

             

             

             

             

             

             

             

            :0040101F B803000000       mov eax, 00000003   ; case 3: eax = 3;

             

             

             

             

             

             

             

            :00401024 C3               ret

             

             

             

             

             

             

             

            :00401025 B8B0A00000       mov eax, 0000A0B0   ; case 4: eax = 0xA0B0;

             

             

             

             

             

             

             

            :0040102A C3               ret

             

             

             

             

             

             

             

            :0040102B 90               nop

            ; 地址表 ***

             

             

             

             

             

             

             

            :0040102C 13104000         DWORD 00401013   ; jump to case 1

             

             

             

             

             

             

             

            :00401030 19104000         DWORD 00401019   ; jump to case 2

             

             

             

             

             

             

             

            :00401034 1F104000         DWORD 0040101F   ; jump to case 3

             

             

             

             

             

             

             

            :00401038 25104000         DWORD 00401025   ; jump to case 4

            看到switch-case是如何實現的了嗎?

             

             

             

             

             

             

             

            它沒有去測試每個case分支,而是創建了一個地址表(address table)。我們簡單地計算出在地址表中偏移就可以跳到正確的case分支。想想吧,這真是一個進步,假設你有一個50個分支的switch語句,假如沒有這個技巧,你不的不執行50CMPJMP才能到達最后一個case,而使用地址表,你可以通過一次查表即跳到正確的case。使用算法的時間復雜度來衡量:我們把O2n)的算法替換成了O(5)的算法,其中:

             

             

             

             

             

             

             

            1.              O代表最壞情況下的時間復雜度。

             

             

             

             

             

             

             

            2.              我們假設計算偏移(即查表)并跳到正確的地址需要5個指令。

            現在,你可能認為上面的情況僅僅是因為case常量選擇得比較好,(12345)。幸運的是,現實生活中的大多數例子都可以應用這個方案,只是偏移的計算復雜了一點而已。但是,有兩個例外:

             

             

             

             

             

             

             

            ●如果少于3case分支,或

             

             

             

             

             

             

             

            ●如果case常量是完全相互無關的。(比如 1 13 50 1000)。

             

             

             

             

             

             

             

            最終的結果和你使用普通的if-else if是一樣的。

            有趣的地方:如果你曾經為case后面只能跟常量而迷惑的話,現在你應該知道為什么了吧。這個值必須在編譯期間就確定下來,這樣才能創建地址表。

            回到我們的問題!

             

             

             

             

             

             

             

            注意到0040100C處的JMP指令了嗎?我們來看看Intel的文檔對十六進制操作碼FF的說明:

             

             

             

             

             

             

             

            Opcode    Instruction    Description

             

             

             

             

             

             

             

            FF /4     JMP r/m32      Jump near, absolute indirect,

             

             

             

             

             

             

             

                                     address given in r/m32

            JMP使用了絕對地址!也就是說,它的其中一個操作數(在這里是0040102C)代表一個絕對地址。還用多說嗎?現在遠程的ThreadFunc會盲目第在地址表中004101C然后跳到這個錯誤的地方,馬上使遠程進程掛掉了。

            F)     到底是什么原因使遠程進程崩潰了?

             

             

             

             

             

             

             

            如果你的遠程進程崩潰了,原因可能為下列之一:

             

             

             

             

             

             

             

            1.              你引用了ThreadFunc中一個不存在的字符串。

             

             

             

             

             

             

             

            2.              ThreadFunc中一個或多個指令使用了絕對尋址(看附錄E中的例子)

             

             

             

             

             

             

             

            3.              ThreadFunc調用了一個不存在的函數(這個函數調用可能是編譯器或連接器添加的)。這時候你需要在反匯編器中尋找類似下面的代碼:

             

             

             

             

             

             

             

            :004014C0    push EBP         ; entry point of ThreadFunc

             

             

             

             

             

             

             

            :004014C1    mov EBP, ESP

             

             

             

             

             

             

             

             ...

             

             

             

             

             

             

             

            :004014C5    call 0041550     ; 在這里崩潰了

             

             

             

             

             

             

             

                                          ; remote process

             

             

             

             

             

             

             

             ...

             

             

             

             

             

             

             

            :00401502    ret

             

             

             

             

             

             

             

            如果這個有爭議的CALL是編譯器添加的(因為一些不該打開的編譯開關比如/GZ打開了),它要么在ThreadFunc的開頭要么在ThreadFunc接近結尾的地方

            不管在什么情況下,你使用CreateRemoteThread & WriteProcessMemory技術時必須萬分的小心,特別是編譯器/連接器的設置,它們很可能會給你的ThreadFunc添加一些帶來麻煩的東西。

            參考(省略)

             

             

             

             

             

             

             

            文章歷史(省略)

            <結束>



            Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=348728

            posted on 2007-12-19 14:41 大龍 閱讀(310) 評論(0)  編輯 收藏 引用

            亚洲国产成人久久一区WWW| 久久精品国产精品亚洲艾草网美妙| 伊人久久五月天| 国内精品久久人妻互换| 久久国产成人| 91久久成人免费| 97视频久久久| 国产叼嘿久久精品久久| 亚洲精品乱码久久久久久自慰 | 中文字幕人妻色偷偷久久| 久久综合综合久久97色| 亚洲国产日韩综合久久精品| 91久久精一区二区三区大全| 无码任你躁久久久久久久| 久久久精品2019免费观看| 亚洲国产精品成人AV无码久久综合影院 | 久久福利片| 久久96国产精品久久久| 国产成人精品综合久久久久| 久久九九久精品国产| 国产精品久久久久…| 丁香色欲久久久久久综合网| 久久久无码精品午夜| 四虎国产精品免费久久久| 久久精品国产亚洲av日韩| 亚洲va久久久久| 久久人与动人物a级毛片| 久久人人爽人人爽人人片AV东京热| 国产精品久久久久久久久免费| 亚洲熟妇无码另类久久久| 手机看片久久高清国产日韩| 国产精品成人精品久久久| 久久99精品久久久久久| 丁香五月网久久综合| 国产成人无码久久久精品一| 久久婷婷五月综合色奶水99啪 | 91精品国产高清久久久久久91| 97久久超碰国产精品旧版| 精品久久久久久无码专区不卡| 久久久久人妻一区精品色| 2021少妇久久久久久久久久|