一、什么是API Hook
見下圖所示,API Hook就是對API的正常調用起一個攔截或中間層的作用,這樣可以
在調用正常的API之前得到控制權,執行自己的代碼。其中Module指映射到內存中的可執
行文件或DLL。
module0 module1
| |
CALL module1!API001 --------------------------------->| API001
|<-------------------------------------------|
| |
API215 |<----------------------------------CALL module0!API215
|------------------------------------------->|
| |
* *
vs.
module0 Hooooks.dll module1
| | |
CALL module1!API001 -------->API001>----------------->| API001
|<-----------------<HOOOOK<------------------|
| | |
API215 |<-----------------<API215<---------CALL module0!API215
|------------------>HOOOOK>----------------->|
| | |
* * *
二、API Hook的原理
這里的API既包括傳統的Win32 APIs,也包括任何Module輸出的函數調用。熟悉PE文件格
式的朋友都知道,PE文件將對外部Module輸出函數的調用信息保存在輸入表中,即.idata段。
下面首先介紹本段的結構。
輸入表首先以一個IMAGE_IMPORT_DESCRIPTOR(簡稱IID)數組開始。每個被PE文件隱式鏈接
進來的DLL都有一個IID.在這個數組中的最后一個單元是NULL,可以由此計算出該數組的項數。
例如,某個PE文件從兩個DLL中引入函數,就存在兩個IID結構來描述這些DLL文件,并在兩個
IID結構的最后由一個內容全為0的IID結構作為結束。幾個結構定義如下:
IMAGE_IMPORT_DESCRIPTOR struct
union{
DWORD Characteristics; ;00h
DWORD OriginalFirstThunk;
};
TimeDateStamp DWORD ;04h
ForwarderChain DWORD ;08h
Name DWORD ;0Ch
FirstThunk DWORD ;10h
IMAGE_IMPROT_DESCRIPTOR ends
typedef struct _IMAGE_THUNK_DATA{
union{
PBYTE ForwarderString;
PDWORD Functions;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
}u1;
}
IMAGE_IMPORT_BY_NAME結構保存一個輸入函數的相關信息:
IMAGE_IMPORT_BY_NAME struct
Hint WORD ? ;本函數在其所駐留DLL的輸出表中的序號
Name BYTE ? ;輸入函數的函數名,以NULL結尾的ASCII字符串
IMAGE_IMPORT_BY_NAME ends
OriginalFirstThunk(Characteristics):這是一個IMAGE_THUNK_DATA數組的RVA(相對于PE文件
起始處)。其中每個指針都指向IMAGE_IMPORT_BY_NAME結構。
TimeDateStamp:一個32位的時間標志,可以忽略。
ForwarderChain:正向鏈接索引,一般為0。當程序引用一個DLL中的API,而這個API又引用別的
DLL的API時使用。
NameLL名字的指針。是個以00結尾的ASCII字符的RVA地址,如"KERNEL32.DLL"。
FirstThunk:通常也是一個IMAGE_THUNK_DATA數組的RVA。如果不是一個指針,它就是該功能在
DLL中的序號。
OriginalFirstThunk與FirstThunk指向兩個本質相同的數組IMAGE_THUNK_DATA,但名稱不同,
分別是輸入名稱表(Import Name Table,INT)和輸入地址表(Import Address Table,IAT)。
IMAGE_THUNK_DATA結構是個雙字,在不同時刻有不同的含義,當雙字最高位為1時,表示函數以
序號輸入,低位就是函數序號。當雙字最高位為0時,表示函數以字符串類型的函數名
方式輸入,這時它是指向IMAGE_IMPORT_BY_NAME結構的RVA。
三個結構關系如下圖:
IMAGE_IMPORT_DESCRIPTOR INT IMAGE_IMPORT_BY_NAME IAT
-------------------- /-->---------------- ---------- ---------------- <--\
| OriginalFirstThunk |--/ |IMAGE_THUNK_DATA|-->|01| 函數1 |<--|IMAGE_THUNK_DATA| |
|--------------------| |----------------| |----------| |----------------| |
| TimeDateStamp | |IMAGE_THUNK_DATA|-->|02| 函數2 |<--|IMAGE_THUNK_DATA| |
|--------------------| |----------------| |----------| |----------------| |
| ForwarderChain | | ... |-->| n| ... |<--| ... | |
|--------------------| ---------------- ---------- ---------------- |
| Name |------>"USER32.dll" |
|--------------------| |
| FirstThunk |---------------------------------------------------------------/
--------------------
在PE文件中對DLL輸出函數的調用,主要以這種形式出現:
call dword ptr[xxxxxxxx] 或
jmp [xxxxxxxx]
其中地址xxxxxxxx就是IAT中一個IMAGE_THUNK_DATA結構的地址,[xxxxxxxx]取值為IMAGE_THUNK_DATA
的值,即IMAGE_IMPORT_BY_NAME的地址。在操作系統加載PE文件的過程中,通過IID中的Name加載相應
的DLL,然后根據INT或IAT所指向的IMAGE_IMPORT_BY_NAME中的輸入函數信息,在DLL中確定函數地址,
然后將函數地址寫到IAT中,此時IAT將不再指向IMAGE_IMPORT_BY_NAME數組。這樣[xxxxxxxx]取到的
就是真正的API地址。
從以上分析可以看出,要攔截API的調用,可以通過改寫IAT來實現,將自己函數的地址寫到IAT中,
達到攔截目的。
另外一種方法的原理更簡單,也更直接。我們不是要攔截嗎,先在內存中定位要攔截的API的地址,
然后改寫代碼的前幾個字節為 jmp xxxxxxxx,其中xxxxxxxx為我們的API的地址。這樣對欲攔截API的
調用實際上就跳轉到了咱們的API調用去了,完成了攔截。不攔截時,再改寫回來就是了。
三、實現前的準備
兩種攔截方法,最終目的都是使程序對欲攔截API的調用跳轉到自己的API。所以我們的API代碼對
欲攔截進程必須是可見的,即我們的代碼要映射到欲攔截進程的地址空間中。
在《隱藏進程》一文中我介紹了遠程線程注入代碼的技術,這里我們可以采用這種方法向欲攔截進
程中注入我們的API代碼。同樣有兩種注入方式,一種是,直接將代碼WriteProcessMemory到欲攔截進
程中,寫入的代碼包括我們的API代碼和遠程線程的入口函數代碼。這種的缺點是有一些細節問題要解
決,如參數傳遞、寫入代碼大小的確定等并且由于多了一個遠程線程效率不是很高,如果要攔截所有的
進程,即必須在每個進程中注入代碼、插入線程。另一種是,注入DLL,遠程線程入口函數為LoadLirary,
當然也存在效率的問題,但免去了一些麻煩。
這里我們主要介紹通過設置鉤子(Hook)來自動注入DLL到欲攔截進程。先簡單說明一下鉤子是怎么回事。
Hook指出了系統消息處理機制。利用Hook,可以在應用程序中安裝子程序監視系統和進程之間的消息
傳遞,這個監視過程是在消息到達目的窗口過程之前。系統支持很多不同類型的Hooks,不同的hook提供不
同的消息處理機制。比如,應用程序可以使用WH_MOUSE_hook來監視鼠標消息的傳遞。系統為不同類型的
Hook提供單獨的Hook鏈。Hook鏈是一個指針列表,這個列表的指針指向指定的,應用程序定義的,被hook
過程調用的回調函數。當與指定的Hook類型關聯的消息發生時,系統就把這個消息傳遞到Hook過程。一些
Hook過程可以只監視消息,或者修改消息,或者停止消息的前進,避免這些消息傳遞到下一個Hook過程或
者目的窗口。
為了利用特殊的Hook類型,可由開發者提供了Hook過程,使用SetWindowsHookEx函數來把Hook過程安
裝到關聯的Hook鏈。Hook過程必須按照以下的語法:
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam);
HookProc是應用程序定義的名字,nCode參數是Hook代碼,Hook過程使用這個參數來確定任務。這個參
數的值依賴于Hook類型,每一種Hook都有自己的Hook代碼特征字符集。wParam和lParam參數的值依賴于
Hook代碼,但是它們的典型值是包含了關于發送或者接收消息的信息。
SetWindowsHookEx函數總是在Hook鏈的開頭安裝Hook過程。當指定類型的Hook監視的事件發生時,系統
就調用與這個Hook關聯的Hook鏈的開頭的Hook過程。每一個Hook鏈中的Hook過程都決定是否把這個事件傳遞
到下一個Hook過程。Hook過程傳遞事件到下一個Hook過程需要調用CallNextHookEx函數。有些類型Hook的
Hook過程只能監視消息,不管是否調用了CallNextHookEx函數,系統都把消息傳遞到每一個Hook過程。全局
hook監視同一桌面的所有線程。而特定線程的Hook只能監視單獨的線程。全局Hook過程可以被同一桌面的任
何應用程序調用,就象調用線程一樣,所以這個過程必須和DLL模塊分開。特定線程Hook過程只可以被相關
線程調用。只有在有調試目的的時候才使用全局Hook,應該避免使用,全局Hook損害了系統性能。
本文使用全局的WH_GETMESSAGE Hook,它也是經常用到的Hook,應用程序使用WH_GETMESSAGE Hook來監
視從GetMessage or PeekMessage函數返回的消息。你可以使用WH_GETMESSAGE Hook去監視鼠標和鍵盤輸入,
以及其他發送到消息隊列中的消息。關于Hook的詳細信息請參考MSDN。
使用SetWindowsHookEx設置全局的WH_GETMESSAGE Hook,傳入DLL的映射到內存時的模塊句柄(HANDLE)
和Hook過程,這樣系統不但會將此DLL映射到當前所有進程的地址空間,并調用DllMain函數,而且也會將
此DLL映射到新創建的進程的地址空間了。也就是自動完成了代碼的注入工作,省了很多力氣,調用
UnhookWindowsHookEx卸載鉤子。
四、具體實現
兩種實現模式:一是由一個第三方進程負責鉤子的設置和卸載,DLL導出設置和卸載函數;二是由一個
第三方進程向某一個進程插入遠程線程、注入DLL,然后由DLL負責鉤子的設置和卸載,第三方進程退出。
兩種模型的DLL實現差別不大,封裝了鉤子設置和卸載函數,自己的API的函數等。
先說改寫IAT方法:
定義一個保存攔截信息的結構APIHOOK32_ENTRY:
typedef struct _APIHOOK32_ENTRY
{
LPCTSTR pszAPIName; //欲攔截API的函數名
LPCTSTR pszCalleeModuleName;//API所在模塊的模塊名
PROC pfnOriginApiAddress;//欲攔截API的函數地址
PROC pfnDummyFuncAddress;//我們自己的API的函數地址
HMODULE hModCallerModule; //調用此API的模塊名
}APIHOOK32_ENTRY, *PAPIHOOK32_ENTRY;
/////////////////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "apihook32.h"
HMODULE hModDLL;
HHOOK hHook;
APIHOOK32_ENTRY hkA;
//鉤子過程,直接調用CallNextHookEx,而不做任何處理
//因為我們只是利用設置鉤子來映射DLL
LRESULT CALLBACK GetMsgProc(int code,WPARAM wParam,LPARAM lParam)
{
return CallNextHookEx(hHook,code,wParam,lParam);
}
//我們自己的API函數
int WINAPI MyMessageBoxA(HWND hwnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType)
{
return MessageBoxA(hwnd,"It's coming from MyMessageBoxA",lpCaption,uType);
}
//設置全局鉤子,自動映射DLL到當前所有進程和新創建的進程
HHOOK InsertDll ()
{
hHook = SetWindowsHookEx(WH_GETMESSAGE,&GetMsgProc,hModDLL,0);
return hHook;
}
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
{
hModDLL = (HMODULE)hModule;//在32位windows系統中,DLL的hModule和hHandle是一回事
hkA.hModCallerModule = NULL;
hkA.pszAPIName = "MessageBoxA"; //攔截user32.dll中的MessageBoxA函數
hkA.pszCalleeModuleName = "user32.dll";
hkA.pfnDummyFuncAddress = (PROC) & MyMessageBoxA;
hkA.pfnOriginApiAddress = GetProcAddress(GetModuleHandle("user32.dll","MessageBoxA";
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// InsertDll();
SetWindowsAPIHook(&hkW);
SetWindowsAPIHook(&hkA);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_PROCESS_DETACH:
UnhookWindowsAPIHooks(hkW);
UnhookWindowsAPIHooks(hkA);
// UnhookWindowsHookEx(hHook);
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////////////////
//有了一上對PE文件輸入表的分析,我們將可以很好的理解下面的代碼
void _SetApiHookUp(PAPIHOOK32_ENTRY phk)
{
ULONG size;
//獲取指向PE文件中的Import表中IMAGE_DIRECTORY_DESCRIPTOR數組的指針
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
ImageDirectoryEntryToData(phk->hModCallerModule,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&size);
if (pImportDesc == NULL)
return;
//查找記錄,看看有沒有我們想要的DLL
//pImportDesc->Name為空說明IID數組結束
for (;pImportDesc->Name;pImportDesc++)
{
//pImportDesc->Name是DLL名字字符串的RVA,加上Module的基址獲得有效指針
LPSTR pszDllName = (LPSTR)((PBYTE)phk->hModCallerModule+pImportDesc->Name);
if (lstrcmpiA(pszDllName,phk->pszCalleeModuleName) == 0)
break;
}
if (pImportDesc->Name == NULL)
{
return;
}
//尋找我們想要的函數
//首先獲得IID的FirstThunk指向的IAT
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA) ((PBYTE)phk->hModCallerModule+pImportDesc->FirstThunk);
for (;pThunk->u1.Function;pThunk++)
{
//ppfn記錄了與IAT表項相應的函數的地址
PROC * ppfn= (PROC *)&pThunk->u1.Function;
if (*ppfn == phk->pfnOriginApiAddress)
{
//如果地址相同,也就是找到了我們想要的函數,進行改寫,將其指向我們所定義的函數
WriteProcessMemory(GetCurrentProcess(),ppfn,&(phk->pfnDummyFuncAddress),sizeof(phk->pfnDummyFuncAddress),NULL);
return;
}
}
}
//***************************************************************************************//
// SetWindowsAPIHook 掛接WindowsAPI函數 當phk->hModCallerModule == NULL //
// 會在整個系統內掛接函數 //
// 仿照SetWindowsHookEx 建立 //
//***************************************************************************************//
BOOL SetWindowsAPIHook(PAPIHOOK32_ENTRY phk)
{
if (phk->pszAPIName == NULL)
{
return FALSE;
}
if (phk->pszCalleeModuleName == NULL)
{
return FALSE;
}
if (phk->pfnOriginApiAddress == NULL)
{
return FALSE;
}
if (phk->hModCallerModule == NULL)
{
MEMORY_BASIC_INFORMATION mInfo;
HMODULE hModHookDLL;
HANDLE hSnapshot;
MODULEENTRY32 me = {sizeof(MODULEENTRY32)};
//根據_SetApiHookUp函數在內存中的位置,確定DLL的映射基址,即hModule
VirtualQuery(_SetApiHookUp,&mInfo,sizeof(mInfo));
hModHookDLL=(HMODULE)mInfo.AllocationBase;
//遍歷本進程中的所有Module,除了本DLL模塊
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,0);
BOOL bOk = Module32First(hSnapshot,&me);
while (bOk)
{
if (me.hModule!=hModHookDLL)
{
phk->hModCallerModule = me.hModule;
_SetApiHookUp(phk);
}
bOk = Module32Next(hSnapshot,&me);
}
return TRUE;
}
else
{
_SetApiHookUp(phk);
return TRUE;
}
return FALSE;
}
//攔截某個某塊的API調用
void SetMyHooksHere(APIHOOK32_ENTRY hk,HMODULE hMod)
{
hk.hModCallerModule = hMod;
_SetApiHookUp(&hk);
}
//卸載API Hook,就是往IAT中寫入原地址
BOOL UnhookWindowsAPIHooks(APIHOOK32_ENTRY & hk)
{
PROC temp;
temp = hk.pfnOriginApiAddress;
hk.pfnOriginApiAddress = hk.pfnDummyFuncAddress;
hk.pfnDummyFuncAddress = temp;
return SetWindowsAPIHook(&hk);
}
///////////////////////////////////////////////////////////////////////////////////////
第三方進程調用InsertDll注入DLL(設置鉤子),調用UnhookWindowsHookEx取消攔截。
下面簡單介紹jmp xxxxxxxx方法攔截API:
攔截user32.dll中的MessageBoxA函數
FARPROC g_pfMessageBoxA = NULL;
BYTE g_OldMessageBoxACode[5] = {0}, g_NewMessageBoxACode[5] = {0};
DWORD g_dwNewProcessId = 0, g_dwOldProcessId = 0;
BOOL g_bHook = FALSE;
HMODULE g_hDll = FALSE;
HHOOK g_hHook = NULL;
BOOL WINAPI Initialize();
BOOL WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
void WINAPI HookOn();
void WINAPI HookOff();
LRESULT WINAPI Hook(int nCode, WPARAM wParam, LPARAM lParam);
BOOL WINAPI InstallHook();
BOOL WINAPI UnInstallHook();
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
{
//char szProcessID[64];
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// _itoa(GetCurrentProcessId(), szProcessID, 10);
// MessageBox(NULL, szProcessID, "Remote Dll", MB_OK);
if(!g_bHook)
{
g_hDll = (HMODULE)hModule;
InstallHook();
Initialize();
// MessageBox(NULL, "Succeeded!", "Hook On", MB_OK);
}
//
MessageBox(NULL, "Process Attach", "Remote Dll", MB_OK);
break;
case DLL_THREAD_ATTACH:
// _itoa(GetCurrentProcessId(), szProcessID, 10);
// MessageBox(NULL, szProcessID, "Remote Dll", MB_OK);
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
// if(g_bHook)
// {
// HookOff();
// MessageBox(NULL, "Off!", "Hook Off", MB_OK);
// UnInstallHook();
// }
// break;
MessageBox(NULL, "Process Detach", "Remote Dll", MB_OK);
break;
}
return TRUE;
}
//獲得MessageBoxA的地址,然后保存代碼起始處的5個字節,并生成跳轉代碼jmp xxxxxxxx
BOOL WINAPI Initialize()
{
HMODULE hDll = LoadLibrary("user32.dll";
g_pfMessageBoxA = GetProcAddress(hDll, "MessageBoxA";//獲得MessageBoxA地址
if(g_pfMessageBoxA == NULL)
return FALSE;
_asm
{
lea edi, g_OldMessageBoxACode
mov esi, g_pfMessageBoxA
cld
movsd //將MessageBoxA地址起始的4個字節(dword)寫入g_OldMessageBoxACode
movsb //將MessageBoxA+4地址起始處的1個字節(byte)寫入g_OldMessageBoxACode+4
}
//jmp xxxxxxxx的機器碼為e9xxxxxxxx,其中e9后的xxxxxxxx為相對跳轉偏移,共5個字節
g_NewMessageBoxACode[0] = 0xe9;
_asm
{
lea eax, MyMessageBoxA
mov ebx, g_pfMessageBoxA
sub eax, ebx
sub eax, 5 //獲得相對跳轉偏移
mov dword ptr [g_NewMessageBoxACode + 1], eax
}
g_dwNewProcessId = GetCurrentProcessId();
g_dwOldProcessId = g_dwNewProcessId;
HookOn();
return TRUE;
}
int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
int nRet = 0;
char szText[128];
strcpy(szText, lpText);
strcat(szText, "\nYou have been Hooked!";
HookOff();
nRet = MessageBoxA(hWnd, szText, lpCaption, uType);
HookOn();
return nRet;
}
void WINAPI HookOn()
{
g_dwOldProcessId = g_dwNewProcessId;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, g_dwOldProcessId);
if(hProcess == NULL)
return ;
//申請MessageBoxA地址處的寫權限,然后寫入跳轉代碼,然后恢復權限
VirtualProtectEx(hProcess, g_pfMessageBoxA, 5, PAGE_READWRITE, &g_dwOldProcessId);
WriteProcessMemory(hProcess, g_pfMessageBoxA, g_NewMessageBoxACode, 5, NULL);
VirtualProtectEx(hProcess, g_pfMessageBoxA, 5, g_dwOldProcessId, &g_dwOldProcessId);
g_bHook = TRUE;
}
void WINAPI HookOff()
{
g_dwOldProcessId = g_dwNewProcessId;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, g_dwOldProcessId);
if(hProcess == NULL)
return ;
//寫入原MessageBoxA的5個字節代碼
VirtualProtectEx(hProcess, g_pfMessageBoxA, 5, PAGE_READWRITE, &g_dwOldProcessId);
WriteProcessMemory(hProcess, g_pfMessageBoxA, g_OldMessageBoxACode, 5, NULL);
VirtualProtectEx(hProcess, g_pfMessageBoxA, 5, g_dwOldProcessId, &g_dwOldProcessId);
g_bHook = FALSE;
}
LRESULT WINAPI Hook(int nCode, WPARAM wParam, LPARAM lParam)
{
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
BOOL WINAPI InstallHook()
{
g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)Hook, g_hDll, 0);
if(!g_hHook)
{
MessageBoxA(NULL, "Set Error!", "ERROR", MB_OK);
return FALSE;
}
return TRUE;
}
BOOL WINAPI UnInstallHook()
{
return UnhookWindowsHookEx(g_hHook);
}
如果讓DLL來負責鉤子的設置和卸載,就必須設置映射到所有進程的DLL共享的數據段,因為DLL中的
全局數據,每個映射的DLL副本都有自己的副本,互不干擾。為了同步鉤子的設置和卸載工作,我們
可以在DLL中設置一個共享段,如下:
#pragma data_seg("shared" //定義段名為shared
BOOL g_bHooked = FALSE;
DWORD g_dwParentProcessID = 0;
//……
#pragma data_seg()
#pragma commnet(lib, "/Section:shared, rws";//設置段屬性為read,write and shared
這個共享段在所有的DLL映射副本中共享,完成同步工作。
第三方進程向某個進程中注入遠程線程,遠程線程注入DLL,然后第三方進程退出。同步代碼如下:
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
{
DWORD dwProcessId = GetCurrentProcessId();
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
if(!g_bHooked)
{
g_bHooked = TRUE;
g_dw_ParentProcessID = dwProcessId;
...//在這里設置鉤子,hMoudle即為DLL的Handle
}
...//在這里完成API攔截工作
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
...//在這里完成API的恢復工作
if(g_bHooked && (g_dwParentProcessID == dwProcessId))
{
g_bHooked = FALSE;
g_dwParentProcessID = 0;
...//在這里卸載鉤子
}
break;
}
return TRUE;
}
以上關于遠程線程注入技術請參考我的另一篇《隱藏進程》的文章,以上代碼在Win2k Professional+SP2
+Visual C++6.0上測試通過,API的具體參數請參考MSDN。
上面的代碼大多是自己機子上的一些零碎代碼,加上自己的分析和實際的應用調試,一直找不到方法
卸載DLL,無論是用設置鉤子注入DLL,還是用注入遠程線程的方法注入DLL。