本文不是介紹怎樣用純VB實現HOOK API的,如果您想瞭解細節那麼您必須懂得一些vc和很少的彙編知識,並且參考我的其它文章,如果您是忠實VB使用者那麼您完全可以不去瞭解內部的原理,因為那些是vc乾的事情(不要不愛聽,我沒有種族歧視的傾向,因為我不知道怎樣用VB實現它,我完全不會使用VB,為了本文才研究了幾天,本文中涉及VB的代碼也是非常非常的少,本文中涉及VB的代碼都是前幾天在CSDN上法帖問到的),您可以直接使用我提供的一個標準dll檔案,並且像調用普通API一樣調用幾個簡單的函數,並且在您的VB代碼中實現您自己的攔截函數就可以了。好了,讓我詳細的說一下吧。
首先,假設您希望瞭解我是怎樣用VB HOOK API的,否則您應該繼續滾屏,直到介紹怎樣使用的那個段落。其次,如果您沒有看我的另外的幾篇關於VC下HOOK API的文章,那麼您趕快去看,因為我下面要講的內容假設您已經閱讀過這幾篇文章了,那個系列文章第一篇的地址是:HOOK API 跳轉大法。
在系列文章中請注意一下關於通用函數的問題,因為這是本文實現的關鍵。我們回顧一下那個通用函數的功能。我們知道我們攔截到的任何API函數,都將進入到我們的通用函數中去,而通用函數是無參數無傳回值類型的。我們是用彙編通過對堆棧的操作在不知道參數類型的情況下建構函式調用的堆棧並且調用使用者的攔截函數。也就是說,你要想攔截一個API,那麼,你告訴我你要攔截的API的名字,還有你處理這個API的替換函數,那麼當我攔截到一個API的時候,就會調用你告訴我的處理這個API的替換函數。現在要解決的問題是,這個處理API的替換函數是在VB裡面的!經過我不懈努力,終於知道VB裡面怎樣把函數地址傳遞出來了!(其實就是一個addressof操作符)。好了,現在HOOK API要工作的所有條件都具備了,開始工作吧。
修改《防火牆原理及實現》裡面的代碼,其實只要修改初始化的代碼,原來是從一個VC寫的dll檔案裡獲得要攔截的API的資訊,主要是通過GetHookApiInfo函數獲得HOOKAPIINFO結構,然後把HOOKAPIINFO結構轉化成APIINFO結構就可以了,其實關鍵點還是怎樣獲得APIINFO結果。
在VB裡面,建立一個ActiveX DLL項目。這樣您就建立了一個Com組件,預設的項目就為您建立了一個類別模組。這個類別模組中您必須添加一個方法,叫做GetHookApiInfo,它的傳回值是APIINFO_VB類型的數組,這個類型的VB定義如下:
Public Type HOOKAPIINFO_VB
MyApiFuncAddr As Long '我們自己的函數地址
OrgModuleName As String '要攔截的API所在的模組名稱
OrgApiName As String '要攔截的API名稱
ParamCount As Long '要攔截的API的參數個數
End Type
還要提供一個獲得函數地址的方法,很簡單,如下:
Private Function GetAddr(ByVal address As Long) As Long
GetAddr = address
End Function
那麼,GetHookApiInfo方法結構大致如下
Public Function GetHookApiInfo()
Dim Param(1) As HOOKAPIINFO_VB
Param(0).MyApiFuncAddr = GetAddr(AddressOf message)
Param(0).OrgApiName = "MessageBoxA"
Param(0).OrgModuleName = "User32.dll"
Param(0).ParamCount = 4
GetHookApiInfo = Param
End Function
這裡只是要攔截MessageBoxA函數。當然,我們還要添加一個模組,並定義攔截函數,message,當目標進程執行要攔截的函數,此處是MessageBoxA的時候,將調用VB代碼的message方法。在c++的CHooApi類的初始化函數中我們將獲得APIINFO_VB結構數組。與vb的APIINFO_VB結構對應的c++結構是_HOOKAPIINFO_VB結構,定義如下:
typedef struct _HOOKAPIINFO_VB
{
// 我們自己的函數地址
DWORD dwMyApiFuncAddr;
// 要攔截的API所在的模組名稱
BSTR bstrOrgModuleName;
// 要攔截的API名稱
BSTR bstrOrgApiName;
// 要攔截的API的參數個數
long ParamCount;
} HOOKAPIINFO_VB;
為了調用VB代碼中的GetHookApiInfo函數來獲得HOOKAPIINFO_VB結構,我們CHookApi的初始化代碼這樣寫:
CoInitialize(NULL);
CLSID clsid = {0x2ED7F30B,0x2135,0x4DE1,{0x99,0x22,0xEE,0xB7,0x02,0x1E,0x8F,0xB4}};
HRESULT hr = CoGetClassObject(clsid,CLSCTX_INPROC_SERVER,NULL, IID_IClassFactory, (void**)&m_pCF);
m_pCF->CreateInstance(NULL,IID_IDispatch,(void**)&m_pDsp);
OLECHAR *szGetHookApiInfo[]={L"GetHookApiInfo"};
DISPID dispID;
hr = m_pDsp->GetIDsOfNames(IID_NULL,szGetHookApiInfo,1,LOCALE_SYSTEM_DEFAULT,&dispID);
DISPPARAMS params = {NULL,NULL,0,0};
VARIANT vResult;
hr = m_pDsp->Invoke(dispID,IID_NULL,LOCALE_SYSTEM_DEFAULT,DISPATCH_METHOD,¶ms,&vResult,NULL,NULL);
m_pDsp->Release();
m_pCF->Release();
當然這裡省略了錯誤處理。上面這段代碼是通過VB的IDispatch介面調用GetHookApiInfo方法,這裡我們需要你的VB寫的Com組件的CLSID的值,不用我告訴你怎麼得到這個值了吧?這裡其實可以通過設定檔獲得這個值,那麼會更有通用性。到此HOOKAPIINFO_VB結構數組就在vResult裡面了,下面我們把它拿出來,並轉化為APIINFO結構,代碼如下:
for(ULONG i = 0; i < vResult.parray->rgsabound->cElements; i++)
{
HOOKAPIINFO_VB *phai = (HOOKAPIINFO_VB*)vResult.parray->pvData;
if(phai == NULL)
continue;
char szOrgModuleName[100];
char szOrgApiName[50];
if(!WideCharToMultiByte(CP_ACP,0,phai->bstrOrgModuleName,-1,szOrgModuleName,99,NULL,NULL))
continue;
if(!WideCharToMultiByte(CP_ACP,0,phai->bstrOrgApiName,-1,szOrgApiName,99,NULL,NULL))
continue;
APIINFO *pai = new APIINFO;
// 儲存參數個數
pai->ParamCount = phai->ParamCount;
// 設定此API是否已經被HOOK
pai->bIsHooked = FALSE;
// 記憶體保護標誌
pai->dwOldProtectFlag = 0;
// 我們的函數地址
pai->lfMyApiAddr = (CMAPIFUNC)phai->dwMyApiFuncAddr;
// 要攔截的函數地址
HMODULE hmod = GetModuleHandle(szOrgModuleName);
if(hmod == NULL)
{
delete pai;
continue;
}
pai->lfOrgApiAddr = (CMAPIFUNC)GetProcAddress(hmod,szOrgApiName);
if(pai->lfMyApiAddr == NULL)
{
delete pai;
continue;
}
strcpy(pai->szOrgApiName,szOrgApiName);
m_vpApiInfo.push_back(pai);
}
到這裡我們已經填充了m_vpApiInfo容器,其他的HOOK操作就和二、防火牆原理及實現 (1)裡面寫的差不多了。
下面我說一下二、防火牆原理及實現 (1)一文當中為了符合我們的VB代碼所要修改和注意的地方。
1.關於呼叫慣例
我在開始實現這個程式的時候遇到的各種彈窗的情況,當時的情景那叫一個壯烈。後來才知道原來VB模組裡面的函數的呼叫慣例是__stdcall的,也就是說,堆棧由函數自己平衡,不需要調用者幹預,而我在二、防火牆原理及實現 (1)一文當中的CommonFunc的代碼是回調調用c類型的函數,所以調用VB的函數會有問題,解決辦法是把堆棧平衡的代碼刪除,修改後的代碼如下:
void CHookApi::CommonFunc(void)
{
DWORD dwlpFunc; // 替換函數的地址 __stdcall
DWORD* pdwCall;// 儲存被調用前壓在棧中的返回地址,也就是Call XXXX 的地址
DWORD* pdwESP;// 儲存ESP內容
DWORD* pdwParam; // 第一個參數的地址
DWORD dwParamSize; // 所有參數所佔用的大小應該=4* dwParamCount
DWORD dwRt; // 儲存傳回值
DWORD dwRtAddr; // 我們的函數真正要返回的地址
PROCESS_INFORMATION *pPi;// 進程資訊
// 得到棧中第一個參數的位置
_asm
{
mov EAX,[EBP+8]
mov [dwRtAddr],EAX
lea EAX,[EBP+4] // call XXXX所在的地址
mov [pdwCall],EAX
lea EAX, [EBP+12] // 第一個參數所在地址
mov [pdwParam],EAX
}
(*pdwCall) -= 5;
vector<APIINFO*>::iterator it;
APIINFO* pai = NULL;
for(it = m_vpApiInfo.begin(); it != m_vpApiInfo.end(); it++)
{
APIINFO* papiinfo = *it;
if((DWORD)papiinfo->lfOrgApiAddr == *pdwCall)
{
pai = *it;
break;
}
}
if(pai == NULL)
return;
BYTE* pbtapi = (BYTE*)pai->lfOrgApiAddr;
dwParamSize = 4*pai->ParamCount;
EnterCriticalSection(&pai->cs);
// 恢複被修改的5個位元組
memcpy(pbtapi,pai->OrgApiBytes,5);
pai->bIsHooked = FALSE;
dwlpFunc = (DWORD)pai->lfMyApiAddr;
// 構造參數
_asm
{
sub esp,[dwParamSize]
mov [pdwESP],esp
}
memcpy(pdwESP, pdwParam, dwParamSize);
//COMMONFUNC myapifunc = (COMMONFUNC)pai->lfMyApiAddr;
//pai->lfMyApiAddr();// 調用替換API的函數
_asm
{
call dwlpFunc
// 儲存傳回值
mov [dwRt],eax
}
// 如果是CreateProcess,那麼繼續hook它
pPi = (PROCESS_INFORMATION*)pdwParam[9];
if(strcmpi(pai->szOrgApiName,"CreateProcessA") == 0 || strcmpi(pai->szOrgApiName,"CreateProcessW") == 0)
{
InjectDll(pPi->dwProcessId,m_szDllPathName);
}
// 再修改5位元組
pbtapi[0] = CALLCODE;//jmp
DWORD* pdwapi = (DWORD*)&(pbtapi[1]);
pdwapi[0] = (DWORD)CommonFunc - (DWORD)pbtapi - 5;// 我的api的地址位移
pai->bIsHooked = TRUE;
LeaveCriticalSection(&pai->cs);
// 下面準備返回的操作
_asm
{
//add esp,[dwParamSize] // 清理我們為了調用真正的替換函數而分配的堆棧裡的參數
mov EDX,[dwRtAddr] // 儲存返回地址
mov EAX,dwRt // 設定傳回值
mov ECX,[dwParamSize] // 獲得參數的大小
// 下面彈出所有儲存的寄存器值(按照入棧的逆順序)
pop EDI // 恢複EDI
pop ESI // 恢複ESI
pop EBX // 恢複EBX
// 我們沒有改動過EBP的值,所以EBP指向堆棧中OldEBP的位置
mov ESP,EBP
pop EBP // 恢複EBP
// 由於堆棧中還剩下參數和兩個返回地址(我們真正要返回的地址和原始API中的第6個位元組的地址),所以我們把這些資料也清除出堆棧
add ESP,8 // 清除兩個返回地址
add ESP,ECX // 清除參數
// 由於調用ret返回時,程式先從堆棧中取出返回地址,所以我們把要真正返回的地址壓入堆棧中
push EDX
ret // 返回
}
}
2.關於VB的字串
為瞭解決字串問題,我也想過了很多種方法,但是都失敗了,造成這種麻煩的主要原因是VB的字串都是BSTR類型的,而****A類型的API都是普通的ANSI類型的字串。因為在我的通用函數中,不知道那個參數或者傳回值是字串。所以我把這個麻煩扔給了VB的使用者,讓你們來處理這個問題。比如攔截MessageBoxA這個函數,那麼VB的攔截函數應該這樣聲名:
Public Function message(ByVal h As Long, ByVal msg As Long, ByVal title As Long, ByVal nType As Long) As Integer
Dim mymsg As String
Dim strlen As Long
mymsg = SysAllocString(msg)
MsgBox mymsg, vbOKOnly, "liutao hooked"
End Function
也就是第二個和第三個參數,用兩個long類型的變數儲存LPCTSTR類型的字串指標,然後通過SysAllocString來構造BSTR進而把它轉化成VB的字串類型。SysAllocString的聲名如下:
Private Declare Function SysAllocString Lib "oleaut32" (ByVal OLECHAR As Long) As String
到此,我們解決了在VC代碼中回調VB寫的DLL的所有問題,那麼我的HOOK API也就能夠正常地工作了。