windows-API劫持(API-HOOK)

API Hook

    ApiHook又叫做API劫持,也就是如果A程序调用了B.cll里面的C函数,我们可以做到当A调用C函数执行的时候,直接执行我们自己事先准备好的函数,之后我们在执行真正的C,当然我们可以不执行C或者更改C的参数等等,实现的核心思路就是:

                                           mov eax, pNewAddr[/size][size=3] jmp eax

    解释下具体原理:我们首先获取要劫持函数的地址,然后我们在自己组装一个数据结构,数据结构的内容是 执行汇编:把新函数地址拷到寄存器里,然后再jmp到新函数地址位置执行新函数,然后我们把自己组装这个数据结构拷贝到之前获取的需要劫持的函数地址指向的内存的位置,这样当我们再次调用该函数的时候,程序走到函数地址处发现是执行我们刚刚写好的汇编命令,直接jmp到了我们自己定义的函数地址的位置,也就相当于直接运行了我们自己写好的函数地址了,当然自己写的函数必须要和原函数参数和返回值一样,自己写的函数里面也可以调用原函数(达到过滤的目的),但是前提是调用之前要先关闭劫持,也就是把我们替换的内容给人家替换回去,执行完之后再次替换我们的地址,如果不替换回去我们就会进入无限递归了这个不解释,当然我们也可以修改参数什么的,给调用者进行一个“加工”。还有就是刚开始看的时候有一个地方不理解就是:DLL注入能成功的原理是,某些系统DLL里面的函数所有程序调用都会执行同一个地址,而APIHook中有一步是更改函数地址,但是如果我们更改了某些公用的函数地址,那么并不会影响其他的程序继续调用,这个地方小纠结了一会,后来弄清楚了,A加载了系统B.DLL获取里面的API函数C, A2也同样加载并且获取了里面的API函数C2,CC2是相等的(当然并不是所有都相等),但是我们在A程序里进行C函数的Hook,并没有影响A2的调用,原因是他们只是指向同一个地址不是就是同一个地址,可以理解成是中间有过度变量。我们修改的只是自己内存那部分”本分而已”,这样的话,我们想要Hook某个继承的某些API的话通常可以采取DLL注入的方式,DLL注入之前我总结过并且上传过相关的实现代码。这里不解释。然后就是在网上找到了一个写的不错的APIHook的代码,分享下(注意,下面代码是方便理解的,可以通过代码好好理解,但是实际开发不要用下面的代码,因为下面没考虑64位等其他问题,我平时开发常用的一个比较稳定的代码我上传在:http://download.csdn.net/detail/u013761036/9603063):

AdHookApi.h
#ifndef __ADHOOKAPI_H__
#define __ADHOOKAPI_H__
#include <windows.h>
#include <tchar.h>
#include <vector>
using namespace std;

// class CAdAutoHookApi
class CAdHookApi;
class CAdAutoHookApi
{
public:
	CAdAutoHookApi(CAdHookApi *pHookApi, void *pAddr);
	virtual ~CAdAutoHookApi();

private:
	CAdHookApi *m_pHookApi;
	void       *m_pAddr;
};

// class CAdAutoHook
class CAdHookApi  
{
public:
	CAdHookApi();
	virtual ~CAdHookApi();
	
protected:
	struct HookMap
	{
		HANDLE hProcess;
		void  *pOldAddr;
		void  *pNewAddr;
		BYTE   chOldCode[8];
		BYTE   chNewCode[8];
		BOOL   bHooked;
		DWORD  dwData;
	};
public:
	HANDLE Add(LPCTSTR lpszModule, LPCSTR lpcFuncName, void *pNewAddr, DWORD dwData = 0);
	HANDLE Add(void *pOldAddr, void *pNewAddr, const BYTE *verifyData = NULL, DWORD verifySize = 0, DWORD dwData = 0);
	BOOL   Remove(HANDLE hHook);
	BOOL   Begin(HANDLE hHook);
	BOOL   End(HANDLE hHook);
	BOOL   Begin2(void *pNewAddr);
	BOOL   End2(void *pNewAddr);
	int    BeginAll();
	int    EndAll();
	int    GetCount();
	void  *OldAddr2NewAddr(void *pOldAddr);
	void  *NewAddr2OldAddr(void *pNewAddr);

public:
    static BOOL VerifyAddress(void *pAddr, const BYTE *verifyData, DWORD verifySize);

    static BOOL PatchCode(void *pAddr, const BYTE *pCode, DWORD dwCode, 
        const BYTE *verifyData = NULL, DWORD verifySize = 0);
	
protected:
	CAdHookApi::HookMap *FromNewAddr(void *pNewAddr);
	CAdHookApi::HookMap *FromOldAddr(void *pOldAddr);
	BOOL HasHook(HANDLE hHook);

protected:
	vector<HookMap *> m_obHooks;
};

#endif // __ADHOOKAPI_H__


AdHookApi.cpp

//#include "stdafx.h"
#include "AdHookApi.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <Windows.h>
#include "Common.h"

static BOOL gUseAPI = TRUE;

static BOOL WINAPI myReadMemory(HANDLE hProcess, LPVOID lpAddress, LPVOID lpBuffer, SIZE_T nSize)
{
    BOOL bRet = FALSE;
    DWORD dwOldProtect = 0;
    bRet = VirtualProtect(lpAddress, nSize, PAGE_READONLY, &dwOldProtect);
    if(gUseAPI)
    {
        DWORD dwRead = 0;
        bRet = ReadProcessMemory(hProcess, lpAddress, lpBuffer, nSize, &dwRead);
    }
    else
    {
        memcpy(lpBuffer, lpAddress, nSize);
    }
    VirtualProtect(lpAddress, nSize, dwOldProtect, &dwOldProtect);
    assert(bRet);
    return bRet;
}

static BOOL WINAPI myWriteMemory(HANDLE hProcess, LPVOID lpAddress, LPCVOID lpBuffer, SIZE_T nSize)
{
    BOOL bRet = FALSE;
    DWORD dwOldProtect = 0;
    bRet = VirtualProtect(lpAddress, nSize, PAGE_READWRITE, &dwOldProtect);
    if(gUseAPI)
    {
        DWORD dwWrite = 0;
        bRet = WriteProcessMemory(hProcess, lpAddress, lpBuffer, nSize, &dwWrite);
    }
    else
    {
        memcpy(lpAddress, lpBuffer, nSize);
    }
    VirtualProtect(lpAddress, nSize, dwOldProtect, &dwOldProtect);
    assert(bRet);
    return bRet;
}

// class CAdAutoHookApi
CAdAutoHookApi::CAdAutoHookApi(CAdHookApi *pHookApi, void *pAddr)
{
	m_pHookApi = pHookApi;
	m_pAddr    = pAddr;

	assert(m_pHookApi != NULL);

	if(m_pHookApi != NULL)
    {
		m_pHookApi->End2(m_pAddr);
    }
}

CAdAutoHookApi::~CAdAutoHookApi()
{
	if(m_pHookApi != NULL)
    {
		m_pHookApi->Begin2(m_pAddr);
    }
}

// class CAdHookApi
CAdHookApi::CAdHookApi()
{
}

CAdHookApi::~CAdHookApi()
{
	EndAll();
}

BOOL CAdHookApi::VerifyAddress(void *pAddr, const BYTE *verifyData, DWORD verifySize)
{
    BOOL isPassed = FALSE;
    if((verifyData != NULL) && (verifySize > 0))
    {
        BYTE *addrData = new BYTE[verifySize];
        if(myReadMemory(GetCurrentProcess(), pAddr, addrData, verifySize))
        {
            if(memcmp(addrData, verifyData, verifySize) == 0)
            {
                isPassed = TRUE;
            }
        }
        delete []addrData;
    }
    else
    {
        isPassed = TRUE;
    }
    return isPassed;
}

BOOL CAdHookApi::PatchCode(void *pAddr, const BYTE *pCode, DWORD dwCode, 
                           const BYTE *verifyData, DWORD verifySize)
{
    if(!VerifyAddress(pAddr, verifyData, verifySize))
    {
        return FALSE;
    }
    BOOL bRet = myWriteMemory(GetCurrentProcess(), pAddr, pCode, dwCode);
    return bRet;
}

HANDLE CAdHookApi::Add(LPCTSTR lpszModule, LPCSTR lpcFuncName, void *pNewAddr, DWORD dwData)
{
    HMODULE hModule = LoadLibrary(lpszModule);
    if(hModule == NULL)
    {
        return NULL;
    }

    void *pOldAddr = (void *)GetProcAddress(hModule, lpcFuncName);
    if(pOldAddr == NULL)
    {
        return NULL;
    }

    return Add(pOldAddr, pNewAddr, NULL, 0, dwData);
}

HANDLE CAdHookApi::Add(void *pOldAddr, void *pNewAddr, const BYTE *verifyData, DWORD verifySize, DWORD dwData)
{
	BOOL bRet = FALSE;
	HookMap *pHook = new HookMap;
	do
	{
		ZeroMemory(pHook, sizeof(HookMap));
		
		pHook->hProcess = GetCurrentProcess();

		pHook->pOldAddr = pOldAddr;
		if(pHook->pOldAddr == NULL)
        {
			break ;
        }

		DWORD dwRead = 8;
        if((verifyData != NULL) && (verifySize > 0) && (verifySize > dwRead))
        {
            dwRead = verifySize;
        }
        BYTE *addrData = new BYTE[dwRead];
		if(!myReadMemory(pHook->hProcess, pHook->pOldAddr, addrData, dwRead))
        {
            delete []addrData;
			break ;
        }
        if((verifyData != NULL) && (verifySize > 0) && (memcmp(addrData, verifyData, verifySize) != 0))
        {
            delete []addrData;
            break ;
        }
        memcpy(pHook->chOldCode, addrData, 8);
        delete []addrData;

		DWORD dwTemp = (DWORD)pNewAddr;
		pHook->pNewAddr = pNewAddr;

		// mov eax, pNewAddr
		// jmp eax
		pHook->chNewCode[0] = 0xB8;
		memcpy(pHook->chNewCode + 1, &dwTemp, sizeof(DWORD));
		pHook->chNewCode[5] = 0xFF;
		pHook->chNewCode[6] = 0xE0;			

		pHook->bHooked = FALSE;

		pHook->dwData = dwData;

		m_obHooks.push_back(pHook);

		bRet = TRUE;
	}while(0);
	if(!bRet)
	{
		delete pHook;
		pHook = NULL;
	}

	return (HANDLE)pHook;
}

BOOL CAdHookApi::Remove(HANDLE hHook)
{
	BOOL bRet = FALSE;
	HookMap *pHook = (HookMap *)hHook;
	for(int i = 0; i < (int)m_obHooks.size(); i ++)
	{
		HookMap *pTemp = m_obHooks[i];
		if(pTemp == pHook)
		{
			End((HANDLE)pTemp);
			delete pHook;
			m_obHooks.erase(m_obHooks.begin() + i);
			bRet = TRUE;
			break ;
		}
	}

	return bRet;
}

BOOL CAdHookApi::Begin(HANDLE hHook)
{
	if(!HasHook(hHook))
    {
		return FALSE;
    }
	HookMap *pHook = (HookMap *)hHook;
	if(pHook->bHooked)
    {
		return TRUE;
    }
	DWORD dwWrite = 8;    
    BOOL bRet = myWriteMemory(pHook->hProcess, pHook->pOldAddr, pHook->chNewCode, dwWrite);
	if(bRet)
    {
		pHook->bHooked = TRUE;
    }
	return bRet;
}

BOOL CAdHookApi::End(HANDLE hHook)
{
	if(!HasHook(hHook))
    {
		return FALSE;
    }
	HookMap *pHook = (HookMap *)hHook;
	if(!pHook->bHooked)
    {
		return FALSE;
    }
	DWORD dwWrite = 8;
    BOOL bRet = myWriteMemory(pHook->hProcess, pHook->pOldAddr, pHook->chOldCode, dwWrite);
	if(bRet)
    {
		pHook->bHooked = FALSE;
    }
	return bRet;
}

BOOL CAdHookApi::Begin2(void *pNewAddr)
{
	HookMap *pHook = FromNewAddr(pNewAddr);
	if(pHook == NULL)
    {
		return FALSE;
    }

	return Begin((HANDLE)pHook);
}

BOOL CAdHookApi::End2(void *pNewAddr)
{
	HookMap *pHook = FromNewAddr(pNewAddr);
	if(pHook == NULL)
    {
		return FALSE;
    }
	
	return End((HANDLE)pHook);
}

void *CAdHookApi::OldAddr2NewAddr(void *pOldAddr)
{
	HookMap *pHook = FromOldAddr(pOldAddr);
	if(pHook == NULL)
    {
		return NULL;
    }
	
	return pHook->pNewAddr;
}

void *CAdHookApi::NewAddr2OldAddr(void *pNewAddr)
{
	HookMap *pHook = FromNewAddr(pNewAddr);
	if(pHook == NULL)
    {
		return NULL;
    }
	
	return pHook->pOldAddr;
}

CAdHookApi::HookMap *CAdHookApi::FromNewAddr(void *pNewAddr)
{
	HookMap *pHook = NULL;
	for(int i = 0; i < (int)m_obHooks.size(); i ++)
	{
		HookMap *pTemp = m_obHooks[i];
		if(pTemp->pNewAddr == pNewAddr)
		{
			pHook = pTemp;
			break ;
		}
	}
	
	return pHook;
}

CAdHookApi::HookMap *CAdHookApi::FromOldAddr(void *pOldAddr)
{
	HookMap *pHook = NULL;
	for(int i = 0; i < (int)m_obHooks.size(); i ++)
	{
		HookMap *pTemp = m_obHooks[i];
		if(pTemp->pOldAddr == pOldAddr)
		{
			pHook = pTemp;
			break ;
		}
	}
	
	return pHook;
}

BOOL CAdHookApi::HasHook(HANDLE hHook)
{
	BOOL bRet = FALSE;
	HookMap *pHook = (HookMap *)hHook;
	for(int i = 0; i < (int)m_obHooks.size(); i ++)
	{
		HookMap *pTemp = m_obHooks[i];
		if(pTemp == pHook)
		{
			bRet = TRUE;
			break ;
		}
	}
	
	return bRet;
}

int CAdHookApi::BeginAll()
{
	int nRet = 0;
	for(int i = 0; i < (int)m_obHooks.size(); i ++)
	{
		HookMap *pTemp = m_obHooks[i];
		BOOL bRet = Begin((HANDLE)pTemp);
		if(bRet)
        {
			nRet ++;
        }
	}

	return nRet;
}

int CAdHookApi::EndAll()
{
	int nRet = 0;
	for(int i = 0; i < (int)m_obHooks.size(); i ++)
	{
		HookMap *pTemp = m_obHooks[i];
		BOOL bRet = End((HANDLE)pTemp);
		delete pTemp;
		if(bRet)
        {
			nRet ++;
        }
	}
	m_obHooks.clear();

	return nRet;
}

int CAdHookApi::GetCount()
{
	return (int)m_obHooks.size();
}

User1 自己注入自己(测试用)
#include "stdafx.h"
#include "AdHookApi.h"
#include <windows.h>
using namespace std;

static CAdHookApi AdHookApi;

int WINAPI mYMessageBoxW( __in_opt HWND hWnd, __in_opt LPCWSTR lpText, __in_opt LPCWSTR lpCaption, __in UINT uType)
{
	//如果是做过滤,记得先关闭掉当前的劫持,然后调用原API(给调用者看,当然这个地方也可以进行参数修改),
	//然后再改成劫持的地址 也就是 end(a) do happythings begin(a)
	MessageBoxA(NULL ,"B" ,"B" ,MB_OK);
	return 0;
}

int main()
{
	AdHookApi.Add(_T("User32.dll") ,"MessageBoxW" ,mYMessageBoxW);
	::MessageBoxW(NULL ,L"A" ,L"A" ,MB_OK);
	AdHookApi.BeginAll();
	::MessageBoxW(NULL ,L"A" ,L"A" ,MB_OK);
	AdHookApi.EndAll();
	::MessageBoxW(NULL ,L"A" ,L"A" ,MB_OK);
	return 0;
}


User2 DLL
#include "stdafx.h"
#include "ApiDebugger.h"
#include "AdHookApi.h"
#include <tlhelp32.h>
#include <wincrypt.h>
#include "Common.h"

static const char *   gCopyright = "ApiDebugger by CodeLive, email : dongfa@yeah.net";
static CAdHookApi     gHooks;

bool gEnableLogOutput = true;

extern "C" APIDEBUGGER const char * ApiDebugger()
{
    return gCopyright;
}

///////////////////////////////////////////////////////////////////////////////

BOOL WINAPI my_IsDebuggerPresent(VOID)
{
    return FALSE;
}

int WINAPI my_CompareStringW(LCID Locale, DWORD dwCmpFlags, PCNZWCH lpString1, int cchCount1, 
                          PCNZWCH lpString2,int cchCount2)
{
    CAdAutoHookApi autoHook(&gHooks, my_CompareStringW);
    logOutput(formatString("ApiDebugger - CompareStringW.
"));
    int ret = CompareStringW(Locale, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2);
    logOutput(formatString("ApiDebugger - CompareStringW(%S, %S).
", lpString1, lpString2));
    return ret;
}

BOOL WINAPI my_CryptAcquireContextW(HCRYPTPROV *phProv, LPCWSTR szContainer, LPCWSTR szProvider, 
                                 DWORD dwProvType, DWORD dwFlags)
{
    CAdAutoHookApi autoHook(&gHooks, my_CryptAcquireContextW);
    BOOL ret = CryptAcquireContextW(phProv, szContainer, szProvider, dwProvType, dwFlags);
    logOutput(formatString("ApiDebugger - CryptAcquireContextW(0x%08X, %S, %S, 0x%08X, 0x%08X) : %S.
",
        (int)(*phProv),
        (szContainer != NULL) ? szContainer : L"NULL",
        (szProvider != NULL) ? szProvider : L"NULL",
        dwProvType, dwFlags,
        ret ? L"TRUE" : L"FALSE"
        ));

    return ret;
}

BOOL WINAPI my_CryptImportKey(HCRYPTPROV hProv, CONST BYTE *pbData, DWORD dwDataLen, HCRYPTKEY hPubKey,
                           DWORD dwFlags, HCRYPTKEY *phKey)
{
    CAdAutoHookApi autoHook(&gHooks, my_CryptImportKey);

    BOOL ret = CryptImportKey(hProv, pbData, dwDataLen, hPubKey, dwFlags, phKey);

    string hexData = toHexString((const char *)pbData, dwDataLen);
    logOutput(formatString("ApiDebugger - CryptImportKey(0x%08X, %s, 0x%08X, 0x%08X, 0x%08X) : %S.
",
        (int)hProv, hexData.c_str(), (int)hPubKey, dwFlags, (int)(*phKey), 
        ret ? L"TRUE" : L"FALSE"
        ));

    return ret;
}

BOOL WINAPI my_CryptCreateHash(HCRYPTPROV hProv, ALG_ID Algid, HCRYPTKEY hKey, DWORD dwFlags, HCRYPTHASH *phHash)
{
    CAdAutoHookApi autoHook(&gHooks, my_CryptCreateHash);
    BOOL ret = CryptCreateHash(hProv, Algid, hKey, dwFlags, phHash);
    logOutput(formatString("ApiDebugger - CryptCreateHash(0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X) : %S.
",
        (int)hProv, (int)Algid, (int)hKey, dwFlags, (int)phHash,
        ret ? L"TRUE" : L"FALSE"
        ));
    return ret;
}

BOOL WINAPI my_CryptHashData(HCRYPTHASH hHash, CONST BYTE *pbData, DWORD dwDataLen, DWORD dwFlags)
{
    CAdAutoHookApi autoHook(&gHooks, my_CryptHashData);
    BOOL ret = CryptHashData(hHash, pbData, dwDataLen, dwFlags);
    string hexData = toHexString((const char *)pbData, dwDataLen);
    logOutput(formatString("ApiDebugger - CryptHashData(0x%08X, %s, 0x%08X) : %S.
",
        (int)hHash, hexData.c_str(), dwFlags,
        ret ? L"TRUE" : L"FALSE"
        ));
    return ret;
}

BOOL WINAPI my_CryptDeriveKey(HCRYPTPROV hProv, ALG_ID Algid, HCRYPTHASH hBaseData, DWORD dwFlags, HCRYPTKEY *phKey)
{
    CAdAutoHookApi autoHook(&gHooks, my_CryptDeriveKey);
    BOOL ret = CryptDeriveKey(hProv, Algid, hBaseData, dwFlags, phKey);
    logOutput(formatString("ApiDebugger - CryptDeriveKey(0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X) : %S.
",
        (int)hProv, (int)Algid, (int)hBaseData, dwFlags, (int)phKey,
        ret ? L"TRUE" : L"FALSE"
        ));
    return ret;
}

BOOL WINAPI my_CryptDecrypt(HCRYPTKEY hKey, HCRYPTHASH hHash, BOOL Final, DWORD dwFlags,
                            BYTE *pbData, DWORD *pdwDataLen)
{
    CAdAutoHookApi autoHook(&gHooks, my_CryptDecrypt);

    string hexData1 = toHexString((const char *)pbData, *pdwDataLen);
    writeDataToFile("CryptDec_IN.bin", pbData, *pdwDataLen);
    BOOL ret = CryptDecrypt(hKey, hHash, Final, dwFlags, pbData, pdwDataLen);
    string hexData2 = toHexString((const char *)pbData, *pdwDataLen);
    writeDataToFile("CryptDec_OUT.bin", pbData, *pdwDataLen);

    logOutput(formatString("ApiDebugger - CryptDecrypt(0x%08X, 0x%08X, %S, 0x%08X, %s=>%s) : %S.
",
        (int)hKey, (int)hHash, Final ? L"TRUE" : L"FALSE",
        dwFlags, hexData1.c_str(), hexData2.c_str(), 
        ret ? L"TRUE" : L"FALSE"
        ));
    return ret;
}

typedef int (__cdecl *sub_4026B0_func)(BYTE *pbData);

// 004026B0 ; 
static int my_sub_4026B0(BYTE *pbData)
{
    CAdAutoHookApi autoHook(&gHooks, my_sub_4026B0);
    sub_4026B0_func sub_4026B0 = (sub_4026B0_func)(0x004026B0);
    string hexData1 = toHexString((const char *)pbData, strlen((const char *)pbData));
    int ret = sub_4026B0(pbData);
    string hexData2 = toHexString((const char *)pbData, strlen((const char *)pbData));

    logOutput(formatString("ApiDebugger - sub_4026B0(%s=>%s)",
        hexData1.c_str(), hexData2.c_str()));

    return ret;
}

///////////////////////////////////////////////////////////////////////////////


void ApiDebugferShutdown()
{
    gHooks.EndAll();

    logOutput("ApiDebugger Shutdown.
");
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
        {
//            gHooks.Add(_T("KERNEL32.DLL"),      "IsDebuggerPresent",        my_IsDebuggerPresent);
//            gHooks.Add(_T("KERNEL32.DLL"),      "CompareStringW",           my_CompareStringW);
            gHooks.Add(_T("ADVAPI32.DLL"),      "CryptAcquireContextW",     my_CryptAcquireContextW);
            gHooks.Add(_T("ADVAPI32.DLL"),      "CryptImportKey",           my_CryptImportKey);
            gHooks.Add(_T("ADVAPI32.DLL"),      "CryptCreateHash",          my_CryptCreateHash);
            gHooks.Add(_T("ADVAPI32.DLL"),      "CryptHashData",            my_CryptHashData);
            gHooks.Add(_T("ADVAPI32.DLL"),      "CryptDeriveKey",           my_CryptDeriveKey);
            gHooks.Add(_T("ADVAPI32.DLL"),      "CryptDecrypt",             my_CryptDecrypt);
/*
            const BYTE verifyData[] = { 0x55, 0x8B, 0xEC, 0x81, 0xEC, 0x2C, 0x01, 0x00, 0x00 };
            void *addr = (void *)0x004026B0;
            if(gHooks.Add(addr, my_sub_4026B0, verifyData, sizeof(verifyData), 0) != NULL)
            {
                logOutput(formatString("ApiDebugger - hook sub_4026B0 ok.
"));
            }
            else
            {
                logOutput(formatString("ApiDebugger - hook sub_4026B0 failed.
"));
            }
*/
            gHooks.BeginAll();

            logOutput(formatString("ApiDebugger - %s.
", gCopyright));
            logOutput("ApiDebugger Loaded.
");
       }
        break ;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
        {
        }
        break ;
	case DLL_PROCESS_DETACH:
        {
            ApiDebugferShutdown();
            logOutput("ApiDebugger Unloaded.
");
        }
		break;
	}
	return TRUE;
}


原文地址:https://www.cnblogs.com/csnd/p/12062290.html