VB,你也想HOOK API吗?那跟我来!!

        本文 不是介绍怎样用纯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,&params,&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也就能够正常地工作了。

VB拦截Windows Explorer删除进程,内含API HOOK,源代码:倒霉蛋儿,程序有时候也会窗口勾挂失败!   勾住了SHFileOperation等函数,DLL用Delphi写的C会的太少,查了半天才知道原来explorer是用SHFileOperation删除文件,经过测试很稳定,没有出现崩溃的情况,由于只勾住了SHFileOperation函数,所以别的程序要是调用DeleteFile删除文件,拦截不到,要是拦截DeleteFile自己接着写吧。      mod_Inject.bas类的注释摘录:   Dim MyAddr As Long ‘执行远程线程代码的起始地址。这里等于LoadLibraryA的地址   ‘dll文件路径   MyDllFileLength = LenB(StrConv(MyDllFileName, vbFromUnicode)) + 1    ‘这里把dll文件名从Unicode转换成Ansi,否则英文字母是2个字节。 _   顺便说一下,学过C的应该知道字符串要以/0标志结尾,所以dll文件名长度要加上1个字节存放Chr(0)   ‘得到进程的句柄   在目标进程中申请分配一块空白内存区域。内存的起始地址保存在MyDllFileBuffer中。 _   这块内存区域我们用来存放dll文件路径,并作为参数传递给LoadLibraryA。   在分配出来的内存区域中写入dll路径径。注意第二个参数传递的是MyDllFileBuffer的内容, _   而不是MyDllFileBuffer的内存地址?   If MyReturn = 0 Then Inject = False   MyAddr = GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA")   ‘得到LoadLibraryA函数的起始地址。他的参数就是我们刚才写入的dll路径。但是LoadLibraryA本身是不知道参数在哪里的。 _   接下来我们就用CreateRemoteThread函数告诉他参数放在哪里了? If MyAddr = 0 Then Inject = False   MyResult = CreateRemoteThread(ProcessHandle, 0, 0, MyAddr, MyDllFileBuffer, 0, 0)   好了,现在用CreateRemoteThread在目标进程创建一个线程,线程起始地址指向LoadLibraryA, _   参数就是MyDllFileBuffer中保存的dll路径?    If MyResult = 0 Then    Inject = False    Else    Inject = True    End If    ‘接下来你可以使用WaitForSingleObject等待线程执行完毕。 _    并用GetExitCodeThread得到线程的退出代码,用来判断时候正确执行了dll中的代码。    CloseHandle MyResult    CloseHandle ProcessHandle    ‘扫地工作   End Function
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值