首先,假设您希望了解我是怎样用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也就能够正常地工作了。