WINDOWS上的 API HOOK 技术

预备知识

  1. 每一个进程都拥有独立的地址空间
  2. dll动态连接库是全部进程共享的,可是须要注意,这里有个前提。前提是,全部的进程都不会去修改dll的内容,若是有进程A修改了dll的内容,则该dll就变成了进程A私有的了(系统使用copy-on-write方法,复制一份dll到进程A的私有地址空间,而后修改dll内容)。试想,若是任何状况都是共享的,一个进程只须要本身加载的dll中的内容,那么其他的全部使用了该dll的进程都会受到影响。
  3. 若是进程A加载的dll和进程B加载的dll是同一个dll(不是同一个的拷贝,必须是同一个路径下的同一个dll),那么dll加载后映射到进程A地址空间和进程B地址空间的虚拟地址是相同的

API HOOK

API HOOK 有不少方式,本文只介绍最经常使用的dll注入方式进程API HOOK。
dll注入方式大致分为两步:一、dll注入,二、修改API入口git

dll注入

假设注入进程为A,被注入的进程为B,注入的dll名为virus.dll。则进程A注入dll到进程B的大致步骤以下github

  • 调整当前进程(A进程)权限
if ( OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken) )
    {
        TOKEN_PRIVILEGES tkp;

        LookupPrivilegeValue( NULL,SE_DEBUG_NAME,&tkp.Privileges[0].Luid );//修改进程权限
        tkp.PrivilegeCount=1;
        tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
        AdjustTokenPrivileges( hToken,FALSE,&tkp,sizeof tkp,NULL,NULL );//通知系统修改进程权限
        CloseHandle(hToken);
    }
  • 打开B进程
OpenProcess( PROCESS_CREATE_THREAD |    //容许远程建立线程
            PROCESS_VM_OPERATION |                //容许远程VM操做
            PROCESS_VM_WRITE|                    //容许远程VM写
            PROCESS_ALL_ACCESS,
            FALSE, dwRemoteProcessId ) )
  • 在B进程中申请一块内存,申请的区域假设为REDATA
pszLibFileRemote = (char *) VirtualAllocEx( hRemoteProcess, NULL,         
            lstrlen(DllFullPath)+1,MEM_COMMIT, PAGE_READWRITE);
  • 将virus.dll的路径写入到REDATA区域
WriteProcessMemory(hRemoteProcess,pszLibFileRemote,
             (void *) DllFullPath, lstrlen(DllFullPath)+1, NULL)
  • 计算Kernel32.dll中的LoadLibraryA API的地址,记为pfnStartAddr
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)
            GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
  • 建立远程线程,传入pfnStartAddr的地址做为线程执行的函数,REDATA的地址做为参数
hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0, 
            pfnStartAddr, pszLibFileRemote, 0, NULL)

通过了上述步骤,virus.dll就能被成功注入到进程B的地址空间中,上面的步骤就是为了干一件事情,让B进程调用LoadLibraryA("virus.dll"),为了作这件事情须要上述6个步骤。,此处须要有几点说明:编程

  1. 提权是为了有权限访问远程线程,读写远程线程
  2. 为何要申请空间,而后将virus.dll路径写入到B进程空间中,不能直接传一个dll路径的字符串呢?由于远程空间调用LoadLibraryA时,须要读取参数,A进程中写的dll路径的字符串存储在A进程的地址空间中,A把这个地址告诉B,B在本身的进程空间中找这个地址是找不到内容的,因此,须要将申请的空间地址告诉B。
  3. 你要问了,既然路径的地址须要B进程内存空间的地址,pfnStartAddr的地址是A进程空间相对位置,这个地址直接给B有问题吗?问得好,这个是没有问题的,这里有一个编程经验:
a、任何应用进程将Kerner32.dll加载到内存的虚拟地址位置都是同样
   b、dll中的方法在dll中的相对位置是固定的

因此,A进程找到的函数地址位置和在B进程中的位置是同样的。
至此,咱们将virus.dll加载到了B进程中,接下来就是B进程中的virus.dll怎么对本身进程空间中的API函数作HOOK的问题了。这里涉及到两个问题:api

  • 何时执行API HOOK这件事情
  • API HOOK以后,何时能够执行HOOK以后的内容(不知道大家听懂我在说什么没有。。)

修改API入口代码

咱们先看怎么修改API函数的入口代码,再讨论执行时机的问题。这件事情是在virus.dll中作的,执行的步骤以下函数

  • 获取API函数所在的模块句柄
HMODULE hmod=::LoadLibrary(_TEXT("add.dll"));
  • 获取该模块中API函数的地址
add=(AddProc)::GetProcAddress(hmod,"add");
  • 修改API函数的汇编代码,让他跳转到咱们本身写的函数(这个函数才是真正的干坏事的地方)
// 将add()的入口代码保存到OldCode里
        _asm 
        { 
            lea edi,OldCode 
                mov esi,pfadd 
                cld 
                movsd 
                movsb 
        }

        NewCode[0]=0xe9;//第一个字节0xe9至关于jmp指令
        //获取Myadd()的相对地址
        _asm 
        { 
            lea eax,Myadd
                mov ebx,pfadd 
                sub eax,ebx 
                sub eax,5 
                mov dword ptr [NewCode+1],eax 
        } 
        //填充完毕,如今NewCode[]里面就至关于指令 jmp Myadd
        HookOn();
void HookOn() 
    { 
        ASSERT(hProcess!=NULL);
    
        DWORD dwTemp=0;
        DWORD dwOldProtect;
    
        //将内存保护模式改成可写,老模式保存入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);
    
        bHook=true; 
    }

这段代码将add函数(API函数)的前5个字节存储到OldCode里面(为了之后恢复add函数),而后生成一个jmp指令到NewCode中,都存储好以后,NewCode的5个字节的内容,替换到原来add函数入口处的5个字节内容。
注意几个问题:ui

  1. 为何是5个字节?

由于jmp XXX 指令占用5个字节(32位体系结构中).net

  1. jmp XXX 中的跳转目标是计算的

xxx = Myadd - pfadd - 5线程

为何是这个公式呢,先引用《深刻理解计算机系统-第三版》中一段话:code

当CPU计算跳转指令的目标地址时,程序计数器的值是当前指令的后一条指令的地址,而不是跳转指令的地址

举个例子blog

pfadd:
01FF0000 : ?? ?? ?? ??
01FF0004 : ?? ?? ?? ??
01FF0008 : ?? ?? ?? ??
...
Myadd:
01FF00A0 : ?? ?? ?? ??
01FF00A4 : ?? ?? ?? ??
01FF00A8 : ?? ?? ?? ??
...
relpalce the first 5 bytes pfadd as jmp(E9 xx xx xx xx),the content of pfadd:
pfadd:
01FF0000 : E9 xx xx xx
01FF0004 : xx ?? ?? ??
01FF0008 : ?? ?? ?? ??
...
satisfy :  01FF00A0 = PC + XXX
PC = 01FF0000 + 5
then the addr : XXX = 01FF00A0 - 01FF0000 - 5

以上就是修改api入口代码的方法
执行时机问题就比较简单了,咱们还记得进程A(起始就是注入器)建立了一个远程线程,让进程B执行了LoadLibraryA("virus.dll")函数。dll中有个入口函数

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

{

    HANDLE g_hModule;

    switch(dwReason)

    {

    case DLL_PROCESS_ATTACH:

       cout<<"Dll is attached!"<<endl;

       g_hModule = (HINSTANCE)hModule;

       break;

    case DLL_PROCESS_DETACH:

       cout<<"Dll is detached!"<<endl;

       g_hModule=NULL;

       break;

    }

    return true;

}

当dll被加载以后,就会执行DLL_PROCESS_ATTACH后面的代码,若是咱们将修改API入口代码的动做放到这里面作,则建立远程线程加载dll后,HooK的动做就会被执行,API函数的入口代码就会被修改。而当进程B再次调用该API时,该API就会跳转到咱们本身写的函数了。

总结

以上就是API HOOK 的dll远程注入技术的详细步骤,若是有不明白的地方,能够参考:
远程注入 : http://blog.csdn.net/ithzhang...
api修改 : http://blog.csdn.net/friendan...
还能够参考个人代码 : https://github.com/Jasey/hook...里面有大量的例子和解释。

相关文章
相关标签/搜索