Hook-两种Inline Hook

selph
selph
发布于 2020-10-18 / 1184 阅读
0
0

Hook-两种Inline Hook

前言

这个章节学习的主要内容是Inline Hook,在我正准备照着书敲代码的时候,我突然意识到了一个问题,这里的代码不是完整的,只是关键部分,作者也不是要读者照着源代码敲一遍,核心在于理解其中的技术原理,这里作者实现的功能是进程隐藏,这个功能我们再后面学习下一本书的时候再去实现,这里的重点是学习和实践两种Inline Hook:5字节和7字节

原理介绍:5字节的Inline Hook

Inline Hook 是 API Hook 的一种,目的是改变函数的执行结果,32位程序下,根据两种场景,分为5字节和7字节的Inline Hook,准确的来说,不一定是5字节或者7字节这么准确,前者指的是修改API前面的几个字节来进行跳转,然后再改回来进行执行,后者指的是,利用API前面的空间(Windows API很多都自带7字节的无用空间)一次修改,不再需要反复Hook和UnHook了

通常情况下,5字节的Inline Hook都适用:

  1. 通过修改目标API开头的5个字节为JMP XXXXXXXX,跳转到我们自己的函数上
  2. 跳转过去之后,再把API开头5字节改回来(UnHook)
  3. 然后调用这个API(这个时候栈帧还是原来的样子)
  4. API执行完毕后,返回到我们自己的函数上
  5. 根据需求修改API执行的结果
  6. 然后再进行Hook(将开头5字节改回来)
  7. 最后返回到最初函数调用的地方,等待下次调用

因为我们的函数需要在目标进程的内存空间中,所以用DLL注入的方式最方便

书上的例子是进程隐藏,但这里我们的主要目的是实践Inline Hook,所以选择一个简单的功能来实现

之前做过一次通过调试API在目标API调用的时候中断,修改栈中的参数来实现改变API的执行结果;那这次就通过Inline Hook来实现这个功能吧

没错,还是找notepad.exe下手哈哈哈

突然想到,010 editor 的验证过程中,只要能控制3个验证函数的返回值,那么就可以直接验证通过,挖个坑,下次单独拿出来实践写一篇笔记吧

代码分析:5字节的Inline Hook

由于都是通过dll注入来实现功能,这里也就只分析DLL的源代码,DLL注入写太多遍了,不多写了

MyWriteFile()

extern "C" __declspec(dllexport) 
BOOL WINAPI MyWriteFile(
    _In_ HANDLE hFile,
    _In_reads_bytes_opt_(nNumberOfBytesToWrite) LPCVOID lpBuffer,
    _In_ DWORD nNumberOfBytesToWrite,
    _Out_opt_ LPDWORD lpNumberOfBytesWritten,
    _Inout_opt_ LPOVERLAPPED lpOverlapped
) {
    //使用前先UnHook
    UnHook_By_Code("kernel32.dll", "WriteFile");

    BOOL bRet = 1;
    if(MessageBoxA(0, "是:保存\r\n否:清空", 0, MB_YESNO)==IDYES)
        bRet = WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);

    Hook_By_Code("kernel32.dll","WriteFile",(PROC)MyWriteFile);

    return bRet;
}

MyWriteFile就是在保存的时候弹框,点是则保存,点否则不保存任何东西,为了形式上保持完整,这里就把这个函数导出了

dllMain()

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH: {
        Hook_By_Code("kernel32.dll", "WriteFile", (PROC)MyWriteFile);
        MessageBoxA(0, "YesOK", 0, 0);
        break;
    }
    case DLL_PROCESS_DETACH:
        UnHook_By_Code("kernel32.dll", "WriteFile");
        break;
    }
    return TRUE;
}

DLL加载成功之后,进行Hook,修改API前面5个字节跳转到我们的函数里去,卸载之后就把API还原

接下来是我们这次的重点了,如何修改&还原API的代码

Hook_By_Code()

//保存原始5字节,全局变量
BYTE pOrgBytes[5] = {0};

BOOL Hook_By_Code(LPCSTR szDllName,LPCSTR szAPIName,PROC pfnNew) {
    BYTE pBuf[5] = {0xE9,0,};//E9在IA-32是JMP,EB是64位的

    //获取目标函数地址
    HMODULE hMod = GetModuleHandleA(szDllName);
    PROC pFunAddr = GetProcAddress(hMod, szAPIName);
    
    //如果已经修改过了,则直接返回
    if (((PBYTE)pFunAddr)[0] == 0xE9) {
        return FALSE;
    }

    //向目标添加写属性
    HANDLE hProcess = GetCurrentProcess();//获取当前进程伪句柄
    DWORD dwOldProtect = 0;
    VirtualProtectEx(hProcess,pFunAddr, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

    //读出来原本的5字节
    memcpy(pOrgBytes, pFunAddr, 5);

    //写入新的字节
    DWORD dwAddress = (DWORD)pfnNew - (DWORD)pFunAddr -  5;
    memcpy(&pBuf[1], &dwAddress, 4);
    memcpy(pFunAddr, pBuf, 5);

    //恢复内存属性
    VirtualProtectEx(hProcess,pFunAddr, 5, dwOldProtect, &dwOldProtect);

    return TRUE;
}

这个注释还是蛮清楚的,这个流程就是获取API的地址,改一下这个内存的读写权限,然后读取代码出来存起来,把我们的构造的代码写入进去,然后恢复读写权限

UnHook_By_Code()

BOOL UnHook_By_Code(LPCSTR szDllName, LPCSTR szAPIName) {
    //获取目标函数地址
    HMODULE hMod = GetModuleHandleA(szDllName);
    if (hMod == NULL) {
        return FALSE;
    }
    PROC pFunAddr = GetProcAddress(hMod, szAPIName);
    if (((PBYTE)pFunAddr)[0] != 0xE9) {
        return FALSE;
    }

    //向目标添加写属性
    HANDLE hProcess = GetCurrentProcess();//获取当前进程伪句柄
    DWORD dwOldProtect = 0;
    VirtualProtectEx(hProcess, pFunAddr, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

    //还原原本的5字节
    memcpy(pFunAddr,pOrgBytes, 5);

    //恢复内存属性
    VirtualProtectEx(hProcess, pFunAddr, 5, dwOldProtect, &dwOldProtect);
    return TRUE;
}

取消修改的函数更简单了,还是获取目标API的地址,然后修改内存属性,还原刚刚保存出来的原本的代码就行

效果展示:5字节的Inline Hook

Hook之前:

image-20201017204259447

Hook之后:

image-20201017204358363

notepad点击保存的时候弹框:

image-20201017204422564

原理介绍:7字节的Inline Hook

所谓7字节Inline Hook,其实就是热补丁(相比5字节Inline Hook就是一次修改,一直使用),微软给一些库的API前面设置了7个字节的无用空间,可通过热补丁实现二次跳转来实现对特定API的Hook:

比如WriteFile API:

image-20201017185929619

API正式运行前有7个字节的空间

我们假设我们要跳转到0x10001000这个位置,进行远距离跳转,那么就可以这么修改这7字节无用空间:

image-20201017190207687

E9是远距离跳转,EB是短距离跳转,虽然都是JMP,但解析出来是不一样的,以后学习到相关部分会好好进行分析

这种Inline Hook的特点就是二次跳转,且无需再API内部进行反复的Hook和UnHook操作

具体操作步骤同5字节Inline Hook

代码分析:7字节的Inline Hook

MyWriteFile()

typedef  BOOL(WINAPI* OrgWriteFile)(
    _In_ HANDLE hFile,
    _In_reads_bytes_opt_(nNumberOfBytesToWrite) LPCVOID lpBuffer,
    _In_ DWORD nNumberOfBytesToWrite,
    _Out_opt_ LPDWORD lpNumberOfBytesWritten,
    _Inout_opt_ LPOVERLAPPED lpOverlapped
    );

....
    
extern "C" __declspec(dllexport) 
BOOL WINAPI MyWriteFile(
    _In_ HANDLE hFile,
    _In_reads_bytes_opt_(nNumberOfBytesToWrite) LPCVOID lpBuffer,
    _In_ DWORD nNumberOfBytesToWrite,
    _Out_opt_ LPDWORD lpNumberOfBytesWritten,
    _Inout_opt_ LPOVERLAPPED lpOverlapped
) {
    MessageBoxA(NULL,"今天也是充满希望的一天!","保佑一次成功",MB_OK);

    PBYTE pByte = (PBYTE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
    OrgWriteFile pOrgWriteFile = (OrgWriteFile)(pByte + 2);
    return pOrgWriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
}

7字节的Inline Hook,也就是热补丁,再次调用函数的时候,需要跳过我们修改的API开头的两个字节,从两个字节之后开始调用,所以这里要定义这个函数,然后把地址+2进行调用

dllmain()

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        Hook_By_Code("kernel32.dll", "WriteFile", (PROC)MyWriteFile);
        break;
    case DLL_PROCESS_DETACH:
        UnHook_By_Code("kernel32.dll", "WriteFile");
        break;
    }
    return TRUE;
}

老样子

Hook_By_Code()

BYTE g_OrgBytes[7] = { 0 };//全局变量
BOOL Hook_By_Code(LPCSTR szDllName,LPCSTR szAPIName,PROC pNewFunAddr) {

    //获取目标API
    HMODULE hMod = GetModuleHandleA("kernel32.dll");
    if (hMod == NULL) {
        return FALSE;
    }
    PROC pOrgFunAddr = GetProcAddress(hMod, "WriteFile");
    PBYTE pByte = (PBYTE)pOrgFunAddr;

    //读取前7个字节
    HANDLE hProcess = GetCurrentProcess();
    DWORD dwOldProtect = 0;
    VirtualProtectEx(hProcess, pByte - 5, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    ReadProcessMemory(hProcess, pByte - 5, g_OrgBytes, 7, NULL);

    //构造新的7字节
    BYTE pBuffer[7] = { 0xE9,0,0,0,0,0xEB,0xF9 };
    DWORD dwAddr = (DWORD)pNewFunAddr - (DWORD)pOrgFunAddr;
    memcpy(&pBuffer[1], &dwAddr,4);

    //修改为新的7字节
    WriteProcessMemory(hProcess,pByte-5,pBuffer,7,NULL);
    VirtualProtectEx(hProcess, pByte - 5, 7, dwOldProtect, &dwOldProtect);

    return TRUE;
}

写法上同5字节,这里要注意的是,7字节是API开头的两个字节加上之前的5个字节,要注意函数指针的位置!

UnHook_By_Code()

BOOL UnHook_By_Code(LPCSTR szDllName, LPCSTR szAPIName) {
   //获取目标API
   HMODULE hMod = GetModuleHandleA("kernel32.dll");
   if (hMod == NULL) {
       return FALSE;
   }
   PROC pOrgFunAddr = GetProcAddress(hMod, "WriteFile");
   PBYTE pByte = (PBYTE)pOrgFunAddr;

   //修改访问权限
   HANDLE hProcess = GetCurrentProcess();
   DWORD dwOldProtect = 0;
   VirtualProtectEx(hProcess, pByte - 5, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);

   //还原7字节
   WriteProcessMemory(hProcess, pByte - 5, g_OrgBytes, 7, NULL);
   
   //还原访问权限
   VirtualProtectEx(hProcess, pByte - 5, 7, dwOldProtect, &dwOldProtect);

   return TRUE;
}

同理

效果展示:7字节的Inline Hook

Hook之前:

image-20201017205148773

Hook之后:

image-20201017205225703

函数调用跳转回来的地方:

image-20201017205316848

notepad的弹框:

image-20201017205340998

总结

绝了,同样的代码,怎么运行都不成功,调试了半天之后,最后还是原来的代码,一运行居然成功了,唉,不管了

这一章节主要学习的内容是两种Inline Hook,一种是通过反复Hook和UnHook来修改API代码实现对API的Hook,另一种是通过热补丁的方式进行API Hook

抱怨一句,照这样折腾下去,下次再写这个代码基本都不用查书了。。。


评论