前言
这个章节学习的主要内容是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都适用:
- 通过修改目标API开头的5个字节为JMP XXXXXXXX,跳转到我们自己的函数上
- 跳转过去之后,再把API开头5字节改回来(UnHook)
- 然后调用这个API(这个时候栈帧还是原来的样子)
- API执行完毕后,返回到我们自己的函数上
- 根据需求修改API执行的结果
- 然后再进行Hook(将开头5字节改回来)
- 最后返回到最初函数调用的地方,等待下次调用
因为我们的函数需要在目标进程的内存空间中,所以用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之前:
Hook之后:
notepad点击保存的时候弹框:
原理介绍:7字节的Inline Hook
所谓7字节Inline Hook,其实就是热补丁(相比5字节Inline Hook就是一次修改,一直使用),微软给一些库的API前面设置了7个字节的无用空间,可通过热补丁实现二次跳转来实现对特定API的Hook:
比如WriteFile API:
API正式运行前有7个字节的空间
我们假设我们要跳转到0x10001000这个位置,进行远距离跳转,那么就可以这么修改这7字节无用空间:
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之前:
Hook之后:
函数调用跳转回来的地方:
notepad的弹框:
总结
绝了,同样的代码,怎么运行都不成功,调试了半天之后,最后还是原来的代码,一运行居然成功了,唉,不管了
这一章节主要学习的内容是两种Inline Hook,一种是通过反复Hook和UnHook来修改API代码实现对API的Hook,另一种是通过热补丁的方式进行API Hook
抱怨一句,照这样折腾下去,下次再写这个代码基本都不用查书了。。。