Windows下API Hook 技术

1         前言

         在Micrisoft Windows中, 每一个进程都有本身的私有地址空间。当咱们用指针来引用内存的时候,指针的值表示的是进程本身的自制空间的一个内存地址。进程不能建立一个指针来引用属于其余进程的内存。编程

         独立的地址控件对开发人员和用户来讲都是很是有利的。对开发人员来讲,系统更有可能捕获错误的内存读\写。对用户而言, 操做系统变得更加健壮。固然这样的健壮性也是要付出代价的,由于它使咱们很难编写可以与其余进程通讯的应用程序或对其余进程进行操控的应用程序。windows

         在《Windows 核心编程》第二十二章《DLL注入和API拦截》中讲解了多种机制,他们能够将一个DLL注入到另外一个进程地址的空间中。一旦DLL代码进入另外一个地址空间,那么咱们就能够在那个进程中为所欲为了。api

         本文主要介绍了如何实现替换Windows上的API函数, 实现Windows API HOOK。API HOOK的实现方法大概不下五六种。本位主要介绍了其中的一种,即如何使用WIndows挂钩来注入DLL。函数

 

2         使用Windows挂钩来注入DLL

2.1       简单windows消息HOOK

2.1.1      SetWindowsHookEx

         这个是安装狗子的函数声明以下:工具

        HHOOK WINAPI SetWindowsHookEx(操作系统

                                    __in int idHook, \\钩子类型线程

                                    __in HOOKPROC lpfn, \\回调函数地址指针

                                    __in HINSTANCE hMod, \\实例句柄orm

                                    __in DWORD dwThreadId); \\线程ID,0表示全部继承

         让咱们经过一个例子来学些下这个API。进程A(一个游戏改建工具,须要接获键盘消息),安装了WH_KEYBOARD_LL挂钩,以下所示:

         HOOK hHook = SetWindowsHookEx(WH_KEYBOARD_LL,  LowLevelKeyboardProc, hInstDll, 0); 第一个参数表示挂钩类型, 是一个低级键盘钩子。第二个参数是一个函数地址(在咱们的地址空间内),在窗口即将有键盘消息的时候,系统应该调用这个函数。第三个参数hInstDll标识一个DLL, 这个DLL中包含了LowLevelKeyboardProc函数。在windows中, hInstDll的值就是就是DLL被映射到的虚拟内存地址。最后一个参数表示要给哪一个线程安装钩子,0表示给全部GUI线程安装。

         如今让咱们看一看接下来会发生什么。

         1)进程B中的一个线程准备向一个窗口发送一个键盘消息

         2)系统检查该线程是否已经安装了WH_KEYBOARD_LL钩子。

         3)系统检查LowLevelKeyboardProc所在的dll是否已经被映射到进程B的地址空间中。

         4)若是DLL还没有被映射,那么系统会强制将该DLL映射到进程B的地址空间中,并将进程B中该DLL的锁计数器(lock count)递增。

         5)因为DLL的hInstDll是在进程B中映射的, 所以系统会对它进行检查,看它与该DLL在进程A中的位置是否相同。若是hInstDll相同,那么在两个进程地址空间中,LowLevelKeyboardProc函数位于相同的位置,在这种状况下, 系统能够直接在进程A的地址空间中调用LowLevelKeyboardProc。若是hInstDll不一样,那么系统必须肯定LowLevelKeyboardProc函数在进程B地址空间中的虚拟地址,这个地址经过下面的公式得出

         LowLevelKeyboardProc B = hInstDll B + (LowLevelKeyboardProc A  - hInstDll A);

经过把LowLevelKeyboardProc A减去hInstDll A, 咱们获得LowLevelKeyboardProc函数的偏移量,以字节为单位, 再把这个偏移量於hInstDll B相加就获得LowLevelKeyboardProc在B地址空间中位置。

         6)系统在进程B中递增该DLL的锁计数器(为什么再次递增? 核心编程中这样写的,不是很明白)

         7)系统在B的地址空间中调用LowLevelKeyboardProc函数

         8)当LowLevelKeyboardProc返回的时候,系统减去DLL在进程B中的锁计数器。

 

         注意, 当系统把挂钩过滤函数所在的DLL注入或映射到地址空间中时,会映射整个DLL,而不只仅是挂钩过滤函数。这意味着,DLL内全部函数存在于进程B中,可以为进程B中全部线程所调用。

        

         此处参考两个简单消息钩子的demo, 一个是相似於改建的键盘钩子demo,这个demo展现了如何简单的作一个游戏改建; 一个是在MFC中使用钩子勾取菜单的相关消息, MFC的菜单不继承CWnd, 因此菜单相关的建立、销毁、绘制等消息咱们都捕获不到,因此重绘的时候要改变菜单的一些属性就须要用到钩子,。

2.2     API Hook

2.2.1      原理

         api hook并非什么特别不一样的hook,它也须要经过基本的hook提升本身的权限,跨越不一样进程间访问的限制,达到修改api函数地址的目的。对于自身进程空间下使用到的api函数地址的修改,是不须要用到api hook技术就能够实现的。

     咱们知道,系统函数都是以DLL封装起来的,应用程序应用到系统函数时,应首先把该DLL加载到当前的进程空间中,调用的系统函数的入口地址,能够经过 GetProcAddress函数进行获取。当系统函数进行调用的时候,首先把所必要的信息保存下来(包括参数和返回地址,等一些别的信息),而后就跳转到函数的入口地址,继续执行。其实函数地址,就是系统函数“可执行代码”的开始地址。那么怎么才能让函数首先执行咱们的函数呢?实际上就是把开始的那段可执行代码替换为咱们本身定制的一小段可执行代码,这样系统函数调用时,不就按咱们的意图乖乖行事了吗? 简单的说,就能够修改系统函数入口的地方,让他调转到咱们的函数的入口点就好了。采用汇编代码就能简单的实现Jmp XXXX, 其中XXXX就是要跳转的相对地址。而Jmp后面要求的是相对偏移,也就是咱们的函数入口地址到系统函数入口地址之间的差别,再减去咱们这条指令的大小。用公式表达以下:

         (1)int nDelta = UserFunAddr – SysFunAddr - (咱们定制的这条指令的大小);

         (2)Jmp nDleta;

为了保持原程序的健壮性,咱们的函数里作完必要的处理后,要回调原来的系统函数,而后返回。 不然会发生本身调用本身的死循环。

         那么说下程序执行过程。

         1) 咱们的dll“注射”入被hook的进程 (这一步只须要安装一个钩子就能实现)

         2)保存系统函数入口处的代码

         3)替换掉进程中的系统函数入口指向咱们的函数

         4)当系统函数被调用,当即跳转到咱们的函数

         5)咱们函数处理

         6)恢复系统函数入口的代码

         7)调用原来的系统函数

         8)再修改系统函数入口指向咱们的函数(为了下次hook)-> 返回

 

来看咱们HooK自定义的Add函数、

         首先咱们建立一个AddFunc的dll工程, 这个dll只有一个导出函数:

                            int WINAPI add(int a, int b);

这个add函数就是咱们稍后须要拦截的函数。有了dll后咱们就能能够直接新建一个MFC工程调用Add函数 主要代码以下:

         // HOOK 个人Add方法

voidCHookDemoDlg::OnBnClickedButton7()

{

 //函数原型定义

    typedefint(WINAPI*AddProc)(inta,intb);

    AddProcadd;

    staticHINSTANCEs_instadd=NULL;

    s_instadd=LoadLibrary(s_path+_T("\\AddFunc.dll"));//加载dll文件

    if(s_instadd==NULL)

    {

       AfxMessageBox(_T("no AddFunc.dll!"));

       return;

    }

 

    add=  (AddProc)::GetProcAddress(s_instadd,"add");//获取函数地址

    intnRet=add(1,1);

    CStringcstr;

    cstr.Format(_T("%d + %d = %d"),1,1,nRet);

    ::MessageBoxW(NULL,cstr,NULL,MB_OK);

}

    接下来, 咱们来进行HOOK即便Hook咱们AddFunc.dll中的add函数。新建一个win32的Dll工程HookDll。首先在头部声明以下变量:

       //全局共享变量

#pragmadata_seg("MySec")

staticHINSTANCEg_hInstance=NULL;

staticHHOOKg_hook=NULL;

#pragmadata_seg()

#pragmacomment(linker,"/section:MySec,rws")

这两个变量表示可以在全部调用该dll的进程中共享。若是不加#pragma data_seg()来声明,g_hInstance 和g_hook将会在每一个进程空间中都有一份独立的数据。编写鼠标钩子的安装卸载函数,注意两个函数导出。

 

//鼠标钩子过程,什么也不作,目录是注入dll到程序中

LRESULTCALLBACKMouseProc(intnCode,WPARAMwParam,LPARAMlParam)

{

    returnCallNextHookEx(hhk,nCode,wParam,lParam);

}

 

 

//鼠标钩子安装函数:

BOOLInstallHook()

{

    hhk=::SetWindowsHookEx(WH_MOUSE,MouseProc,g_hInstance,0);

      

    returnhhk!=NULL;

}

 

BOOLUninstallHook()

{

    if(hhk!=NULL)

    {

       ::UnhookWindowsHookEx(hhk);

       hhk=NULL;

    }

   

    //HookMessageBoxW::HookOff();

    //HookAddFuc::HookOff();

    HookTextOutW::HookOff();

 

    returnTRUE;

}

 

在DLL的入口处DLL_PROCESS_ATTACH添加初始化变脸和进行注入。

BOOLAPIENTRYDllMain(HMODULEhModule,

                       DWORD  ul_reason_for_call,

                       LPVOIDlpReserved

                   )

{

    g_hInstance=(HINSTANCE)hModule;

 

    switch(ul_reason_for_call)

    {

       caseDLL_PROCESS_ATTACH:

       {

           DWORDdwPid=::GetCurrentProcessId();

           HANDLEhProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);

           //HookAddFuc::Inject(hProcess);

           break;

       }

       caseDLL_THREAD_ATTACH:

       caseDLL_THREAD_DETACH:

       caseDLL_PROCESS_DETACH:

       {

           //HookAddFuc::HookOff();

           break;

       }

    }

    returnTRUE;

}

编写HookAddFuc::Inject(hProcess)注入函数

voidHookAddFuc::Inject(HANDLEh)

{

    hProcess=h;

 

    if(!bInjectedAdd)

    {

       bInjectedAdd=true;

 

       //获取add.dll中的add()函数

       HMODULEhmod=::LoadLibrary(s_path);

       add=(AddProc)::GetProcAddress(hmod,"add");

       pfadd=(FARPROC)add;

 

       if(pfadd==NULL)

       {

           MessageBoxW(NULL,L"cannot locate add()",NULL,MB_OK);;

       }

 

 

       // 将add()中的入口代码保存入OldCode[]

       _asm

       {

           leaedi,OldCode

              movesi,pfadd

              cld

              movsd

              movsb

       }

 

       NewCode[0]=0xe9;//实际上0xe9就至关于jmp指令

       //获取Myadd()的相对地址

       _asm

       {

           leaeax,Myadd

              movebx,pfadd

              subeax,ebx

              subeax,5

              movdwordptr[NewCode+1],eax

       }

 

       //填充完毕,如今NewCode[]里的指令至关于Jmp Myadd

       HookOn();//能够开启钩子了

    }

}

 

//恢复函数地址

voidHookAddFuc::HookOff()

{

 

    DWORDdwTemp=0;

    DWORDdwOldProtect;

 

    VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect);

    WriteProcessMemory(hProcess,pfadd,OldCode,5,0);

    VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp);

   

}

 

//修改函数地址

voidHookAddFuc::HookOn()

{

    DWORDdwTemp=0;

    DWORDdwOldProtect;

 

    //将内存保护模式改成可写,老模式保存入dwOldProtect

    VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect);

    //将所属进程中add()的前5个字节改成Jmp Myadd

    WriteProcessMemory(hProcess,pfadd,NewCode,5,0);

    //将内存保护模式改回为dwOldProtect

    VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp);

 

}

 

 

//而后,写咱们本身的Myadd()函数

intWINAPIMyadd(inta,intb)

{

    //截获了对add()的调用,咱们给a,b都加1

    a=a+1;

    b=b+1;

 

    HookAddFuc::HookOff();//关掉Myadd()钩子防止死循环

 

    intret;

    ret=add(a,b);

 

    HookAddFuc::HookOn();//开启Myadd()钩子

 

    returnret;

}

相关文章
相关标签/搜索