API HOOK,就是截獲API調用的技術,在程序對一個API調用之前先執行你的函數,然后根據你的需要可以執行缺省的API調用或者進行其他處理,假設如果想截獲一個進程對網絡的訪問,一般是幾個socket API : recv,recvfrom, send, sendto等等,當然你可以用網絡抓包工具,這里只介紹通過API HOOK的方式來實現, 主要原理是在程序運行中動態修改目標函數地址的內存數據,使用jmp語句跳轉到你的函數地址,執行完后再恢復內存數據, 匯編代碼是:
mov eax, pNewAddr[/size][size=3] jmp eax
讀寫進程內存方法:
1.讀進程內存:
VirtualProtect(lpAddress, nSize, PAGE_READONLY, &dwOldProtect); ReadProcessMemory(hProcess, lpAddress, lpBuffer, nSize, &dwRead); VirtualProtect(lpAddress, nSize, dwOldProtect, &dwOldProtect);
2.寫進程內存:
VirtualProtect(lpAddress, nSize, PAGE_READWRITE, &dwOldProtect); WriteProcessMemory(hProcess, lpAddress, lpBuffer, nSize, &dwWrite); VirtualProtect(lpAddress, nSize, dwOldProtect, &dwOldProtect);
在很多年前這種技術非常的流行,有各種各樣的工具和SDK,我自己也實現了一個C++ class,名為 CAdHookApi, 主要幾個函數是:
class CAdHookApi { public: // 指定DLL的某個函數進行HOOK HANDLE Add(LPCTSTR lpszModule, LPCSTR lpcFuncName, void *pNewAddr, DWORD dwData = 0); // 給定一個函數地址進行HOOK 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(); };
舉例說明使用方法:
假設一個軟件是試用軟件,試用7天,最笨的辦法就是改本機時間,但如果用API HOOK技術就可以很容易做到,可以先用CFF Explorer或者Dependency查看一下該軟件是調用哪個函數來獲取系統當前時間的,假如是GetLocalTime函數(當然獲取時間的函數還有很多API),那么我就可以截獲GetLocalTime,返回一個永不過期的時間.
1.首先,聲明一個全局變量:
static CAdHookApi gHooks;
2.確定要截獲API的參數,API GetLocalTime對應的DLL是KERNEL32.DLL, API定義為:
void WINAPI GetLocalTime(LPSYSTEMTIME lpSystemTime);
寫一個新的函數,定義和原函數保持一致:
void WINAPI my_GetLocalTime(LPSYSTEMTIME lpSystemTime) { #if 1 // 執行缺省調用 CAdAutoHookApi autoHook(&gHooks, my_GetLocalTime); GetLocalTime(lpSystemTime); #else // 改變函數的行為,返回固定的時間 // 2012-12-28 10:00:00 lpSystemTime->wYear = 2012; lpSystemTime->wMonth = 12; lpSystemTime->wDayOfWeek = 0; lpSystemTime->wDay = 28; lpSystemTime->wHour = 10; lpSystemTime->wMinute = 0; lpSystemTime->wSecond = 0; lpSystemTime->wMilliseconds = 0; #endif }
3.直接HOOK已知的函數地址:
如果已知函數地址和函數定義,可以直接對地址進行HOOK,在HOOK之前還可以先對內存數據進行檢驗,只有數據一致才HOOK.
// 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; } 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.\r\n")); } else { logOutput(formatString("ApiDebugger - hook sub_4026B0 failed.\r\n")); }
4.函數首次HOOK是在DLL加載時完成的,DLL入口增加代碼:
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { // 截獲KERNEL32.DLL的API GetLocalTime到你的函數地址my_GetLocalTime gHooks.Add(_T("KERNEL32.DLL"), "GetLocalTime", my_GetLocalTime); // 開始HOOK所有的 gHooks.BeginAll(); } break ; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break ; case DLL_PROCESS_DETACH: { gHooks.EndAll(); } break; } return TRUE; }
這樣就完成了,只要你的DLL加載到一個進程中,相應的函數就被你截獲了.
下面談一下如何讓一個程序加載你的DLL,一般有兩種方式:
1.修改原程序的Import Table,增加導入你的DLL(靜態加載): 使用工具:CFF Explorer,是Explorer Suite(http://www.ntcore.com/)中的一個工具 用於PE文件的修改,下面這個操作就是讓notepad.exe加載rand.dll的操作:
只要Rebuild Import Table,然后再Save/Save As就可以保存新的文件,這樣你的dll就自動的被加載了,DLL加載的時候也就實現了API HOOK。
這種方式因為對原程序進行了修改,如果程序有CRC校驗,運行肯定就不正確了,就需要通過破解去除CRC校驗部分的判斷.
2.動態DLL加載:
在原程序運行之后,通過API CreateRemoteThread 把自己的DLL注入到另一個進程.使用DLL注入工具,這個工具是我多年前寫的:
這種方式最大的好處是不需要對原程序進行修改,可以躲避程序CRC校驗.
最后例舉一些應用場景:
1.加密狗的通用破解方法,僅針對固定數據讀取的有效(有算法的加密狗無效):
1)HOOK幾個API,加密狗一般最終都是使用CreateFile打開設備,調用API DeviceIoControl與加密狗進行數據交互:
gHooks.Add(_T("KERNEL32.DLL"), "CreateFileA", my_CreateFileA); gHooks.Add(_T("KERNEL32.DLL"), "CreateFileW", my_CreateFileW); gHooks.Add(_T("KERNEL32.DLL"), "DeviceIoControl", my_DeviceIoControl); static int gCallCounter = 0; BOOL WINAPI my_DeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped ) { BOOL ret = TRUE; CAdAutoHookApi autoHook(&gHooks, my_DeviceIoControl); #if 1 ret = DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped); if(ret) { // 帶狗時記錄數據 WriteDataToFile(formatstring(L"1\\%d.in", gCallCounter).c_str(), lpInBuffer, nInBufferSize); gCallCounter ++; } #else { // 拔掉后狗直接從已保存的文件中返回數據,實現狗數據的模擬 int nRet = 0; *lpBytesReturned = ReadDataFromFile(formatstring(L"1\\%d.in", gCallCounter).c_str(), lpOutBuffer, nOutBufferSize); gCallCounter ++; } #endif return ret; }
2)使用剛才提到的方法進行DLL導入
3)帶狗保存數據,數據記錄完成后,用保存的數據進行狗的模擬
2. 360 CrackMe加密API的截獲,這部分在我的一個帖子中有提到
http://www.52pojie.cn/thread-257062-1-1.html
1)HOOK以個API:
// HOOK IsDebuggerPresent可以讓函數直接返回FALSE gHooks.Add(_T("KERNEL32.DLL"), "IsDebuggerPresent", my_IsDebuggerPresent); 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); 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.\r\n")); int ret = CompareStringW(Locale, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2); logOutput(formatString("ApiDebugger - CompareStringW(%S, %S).\r\n", 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.\r\n", (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.\r\n", (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.\r\n", (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.\r\n", (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.\r\n", (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.\r\n", (int)hKey, (int)hHash, Final ? L"TRUE" : L"FALSE", dwFlags, hexData1.c_str(), hexData2.c_str(), ret ? L"TRUE" : L"FALSE" )); return ret; }
3. 程序網絡訪問抓包,前面提到的幾個api : recv,recvfrom, send, sendto, ...
當然API HOOK的功能還遠不止這些,可以分析目標程序的特點做更多的處理,有時間我再寫一個用這種方法破解HASP SRM AES-128加密狗的經驗吧。
總結:雖然API HOOK是相對比較老的一門技術,但很多時候如果能結合這種方法就不需要花很大的精力去脫五花八門的殼和分析反匯編代碼,直接鎖定核心的API的調用,能夠快速的進行數據分析,而且也不用對原有程序進行Patch, 簡單有效.另外,本人很少發帖,難免有錯字和問題,歡迎批評指正。
更多細節和代碼請下載附件源程序(VS2012的工程).