安全开发之应用层Hook技术

本环境是蛇矛实验室基于火天网演攻防演训靶场进行搭建,通过火天网演中的环境构建模块,可以灵活的对目标网络进行设计和配置,并且可以快速进行场景搭建和复现验证工作。
前言
hook中文译为“钩子”或“挂钩”,这很容易联想到钓东西,好比钓鱼,但将其比作“网”更合适,在安全开发的过程中,hook技术主要用于对程序的运行流程进行控制和拦截,对特定的消息或动作进行过滤。
hook原理
在真正执行原始api之前,对程序流程进行拦截,使其先执行自定义的代码后,再执行原始api调用流程。
hook分类
hook根据其作用的权限,可分为应用层(r3)钩子和内核(r0)钩子,本文主要讲解应用层钩子。
    从代码实现角度,可将r3 hook分为以下几类:
    基于地址修改,比如iat hook。
    基于代码修改,比如inline hook。
    基于异常或调试,比如veh hook。
由于篇幅有限,本文只包含部分hook技术。
iat hook
iat(import address table,导入地址表)是指pe文件格式中的一个表结构,说到iat就离不开导入表,在实际的开发过程中,难免会使用到windows api,这些api的代码保存在windows提供的不同的dll(动态链接库)文件中,dll将这些api导出,在可执行程序中使用到其他dll的代码或数据时,编译器会将这些导入的信息填充到可执行程序的导入表中。当可执行程序运行时,系统会将可执行程序和其依赖的dll加载到内存中,其中windows加载器会定位所有导入函数的地址并将定位到的地址填充到iat中供其使用,windows加载器定位这些函数的地址需要依赖pe文件中的导入表,其中导入表存放了所使用到的dll文件和导入的函数名称和序号信息。
实现原理
通过替换iat表中函数的原始地址从而实现hook。
实现步骤
以hook user32!messageboxa为例:
1. 定义基于user32!messageboxa的函数原型的函数指针;
2. 获取user32!messageboxa的函数地址并保存;
3. 创建hookedmessageboxa函数(函数原型同user32!messageboxa一样),以拦截程序对user32!messageboxa的调用:
先执行自定义的代码;再执行原始user32!messageboxa函数。  
4. 解析导入表,并在iat中定位user32!messageboxa的位置;
5. 使用hookedmessageboxa函数的地址替换iat中user32!messageboxa的地址。
实现代码
#include #include #include #include #pragma comment (lib, dbghelp.lib)// 1. 定义基于user32!messageboxa的函数原型的函数指针using messageboxt = int (winapi*)(hwnd hwnd, lpcstr lptext, lpcstr lpcaption, uint utype);// 2. 获取user32!messageboxa的函数地址并保存;messageboxt originalmessagebox = messageboxa;// 3. 创建hookedmessageboxa函数(函数原型同user32!messageboxa一样),以拦截程序对user32!messageboxa的调用:int winapi hookedmessagebox(hwnd hwnd, lpcstr lptext, lpcstr lpcaption, uint utype){  // 3.1 先执行自定义的代码  messageboxw(0, lhookedmessagebox() called, liat hook, 0);  // 3.2 再执行原始user32!messageboxa函数  return originalmessagebox(hwnd, lptext, lpcaption, utype);}/*4. 解析导入表,并在iat中定位user32!messageboxa的位置;5. 使用hookedmessageboxa函数的地址替换iat中user32!messageboxa的地址。*/bool sethook(std::string dllname, std::string origfunc, proc hookingfunc){  ulong size;  dword i;  lpcstr importdllname = null;  hmodule importdllimagebase = null;  // 获取主模块句柄  hmodule imagebase = getmodulehandle(null);  // 定位主模块的导入表  pimage_import_descriptor importdesctab = (pimage_import_descriptor)imagedirectoryentrytodataex(imagebase, true, image_directory_entry_import, &size, null);  // 寻找目标dll  bool found = false;  for (i = 0; i u1.addressofdata)  {    pimage_import_by_name functionname = (pimage_import_by_name)((ulong_ptr)imagebase + originalfirstthunk->u1.addressofdata);    if (_stricmp(origfunc.c_str(), functionname->name) == 0)    {      // 确保内存可写      dword oldprotect = 0;      virtualprotect((lpvoid)(&firstthunk->u1.function), 4096, page_readwrite, &oldprotect);      // 替换iat中的函数地址      firstthunk->u1.function = (ulong_ptr)hookingfunc;      // 恢复内存属性      virtualprotect((lpvoid)(&firstthunk->u1.function), 4096, oldprotect, &oldprotect);    }    ++originalfirstthunk;    ++firstthunk;  }  return true;}int main(){  // hook前  messageboxa(0, before hooking, iat hooks, 0);    // 进行iat hook  sethook(user32.dll, messageboxa, (proc)hookedmessagebox);  // hook后  messageboxa(0, after hooking, iat hook, 0);  return 0;}  
上述代码第一次调用messageboxa时,正常弹出,为了之后在调用messageboxa时,先执行自定义的函数代码(hookedmessagebox),首先在进程的iat中定位到messageboxa的地址,这个过程是先通过进程的导入表找到messageboxa所在的dll模块(user32.dll),找到之后,通过int(导入名称表)得到messageboxa函数地址在iat中的下标,此时使用自定义函数的地址替换掉iat中messageboxa函数的地址,即可达到iat hook的效果,hook之后在调用messageboxa时,程序会先执行hookedmessagebox函数,在执行原始的messageboxa函数(需要提前获取messageboxa的地址)。
效果
inline hook
inline hook实际上是一种通过修改机器码的方式来实现hook的技术。
实现原理
通过直接修改api函数在内存中对应的二进制代码,通过跳转指令将其代码的执行执行流程改变从而执行用户编写的代码进而进行inline hook。
实现步骤
以hook user32!messageboxa为例:
1. 在指定进程中内存中找到messageboxa函数地址,并保存函数头部若干字节(用于后续unpatch);
2. 创建hookedmessageboxa函数(函数原型同user32!messageboxa一样),以拦截程序对user32!messageboxa的调用:
先执行自定义的代码;恢复先前保存的messageboxa原始字节;执行原函数再次对messageboxa进行hook;  
3. 构造跳转指令,用于后续替换messageboxa函数代码头部字节;
4. 修改messageboxa函数首地址代码为跳转指令。
实现代码
#include #include #if defined(_win64)#define orig_bytes_size 14#else#define orig_bytes_size 7#endifbyte originalbytes[orig_bytes_size]{}; // 用于保存messageboxa的部分原始代码字节byte patchbytes[orig_bytes_size]{}; // 构造的跳转指令using messageboxat = int (winapi*)(hwnd hwnd, lpcstr lptext, lpcstr lpcaption, uint utype);messageboxat originalmessagebox = nullptr;int winapi hookedmessagebox(hwnd hwnd, lpcstr lptext, lpcstr lpcaption, uint utype){  // 执行自定义的代码  size_t bytesout = 0;  messageboxw(0, lhookedmessagebox() called, linline hook, 0);  // unpatch messageboxa  writeprocessmemory(getcurrentprocess(), (lpvoid)originalmessagebox, originalbytes, sizeof(originalbytes), &bytesout);  // 调用原来的messageboxa  int result = messageboxa(null, lptext, lpcaption, utype);  // 再次patch messageboxa  writeprocessmemory(getcurrentprocess(), originalmessagebox, patchbytes, sizeof(patchbytes), &bytesout);  return result;}bool sethook(std::string dllname, std::string origfunc, farproc hookingfunc){  size_t bytesin = 0;  size_t bytesout = 0;  // 保存messageboxa原始地址  originalmessagebox = (messageboxat)getprocaddress(getmodulehandlea(dllname.c_str()), origfunc.c_str());  // 保存messageboxa的部分原始代码字节  readprocessmemory(getcurrentprocess(), originalmessagebox, originalbytes, orig_bytes_size, &bytesin);  memset(patchbytes, 0, sizeof(patchbytes));#if defined(_win64)  /*  jmp [rip+0];  xffx25x00x00x00x00  x00x11x22x33x44x55x66x77  */  memcpy(patchbytes, xffx25, 2);  memcpy(patchbytes + 6, &hookingfunc, 8);#else  /*  mov eax, &hookingfunc  jmp eax  */  memcpy(patchbytes, xb8, 1);  memcpy(patchbytes + 1, &hookingfunc, sizeof(ulong_ptr));  memcpy(patchbytes + 5, xffxe0, 2);#endif  // patch the messageboxa  writeprocessmemory(getcurrentprocess(), originalmessagebox, patchbytes, sizeof(patchbytes), &bytesout);  return true;}int main(){  // hook前  messageboxa(0, before hooking, inline hook, 0);  // 进行inline hook  sethook(user32.dll, messageboxa, (farproc)hookedmessagebox);  // hook后  messageboxa(0, after hooking, inline hook, 0);  return 0;}  
程序中调用了2次messageboxa,第一次调用时未被挂钩,之后inline hook方式使用对messageboxa进行挂钩,当之后再次调用messageboxa时,程序会首先进入自写函数(hookedmessagebox)中,在该函数中,自定义的代码部分使用messageboxw弹出内容,随后修复messageboxa被修改的字节代码后,开始执行原始messageboxa代码。
效果
veh hook
veh hook是一种基于异常处理的hook手段,通过主动触发异常从在获取程序控制权来达到hook的手段。其中veh(vectored exception handler,向量化异常处理)是windows中处理异常的一种方式。
实现原理
由于veh的异常处理发生在之前,所以通过`主动抛出异常,使程序触发异常,进而使控制权交给异常处理例程的这一系列操作来实现hook。
实现步骤
以hook user32!messageboxa为例:
1. 获取messageboxa地址,并保存;
2. 安装veh异常处理程序,编写vehhandler(veh的异常处理函数);
3. 设置钩子:人为在hook点构造异常(比如修改目标函数第一个字节位0xcc等),并保存触发异常的地址等信息;
4. 在vehhandler函数内部修改目标函数原始流程,并在执行完毕后主动修复异常。
实现代码
#include #include // 获取目标函数的地址ulong_ptr originalmessagebox = null;struct exception_hook{  ulong_ptr address; // 用来记录异常产生的地址,后面将用来确保是我们人为构造的异常  byte originalbytes; // 用来记录原始目标函数的第一个字节};exception_hook hookinfo;// 异常处理函数// 用来修改目标函数原始流程,并在执行完我们功能后修复异常long ntapi vehhandler(exception_pointers* exceptioninfo){  if (exceptioninfo->exceptionrecord->exceptioncode == exception_breakpoint && // 异常类型为断点异常    (ulong_ptr)exceptioninfo->exceptionrecord->exceptionaddress == hookinfo.address) // 发生异常的地址为我们主动构造的异常地址  {    // 在这里编写自定义的代码,或者修改hook api的相关参数    messageboxw(0, lvehhandler() called, lveh hook, 0);    // 解除钩子    dword oldprotect = 0;    virtualprotect((lpvoid)hookinfo.address, 1, page_execute_readwrite, &oldprotect);    *(byte*)hookinfo.address = hookinfo.originalbytes;    virtualprotect((lpvoid)hookinfo.address, 1, oldprotect, &oldprotect);    return exception_continue_execution; // 回到异常发生的地方,由于已经修复了异常问题,所以之后能够正确执行  }  return exception_continue_search; // 向上继续寻找异常处理程序}void sethook(ulong_ptr address){  addvectoredexceptionhandler(1, vehhandler); // 添加veh的异常处理函数  hookinfo.address = address; // 保存目标函数发生异常的地址  hookinfo.originalbytes = *(byte*)address; // 保存目标函数原始的第一个字节  dword oldprotect = 0;  virtualprotect((lpvoid)address, 1, page_execute_readwrite, &oldprotect);  *(uchar*)address = 0xcc; // 人为构造异常,将目标函数代码处的第一个字节改为0xcc  virtualprotect((lpvoid)address, 1, oldprotect, &oldprotect);}int main(){  // 保存messageboxa原始地址  originalmessagebox = (ulong_ptr)getprocaddress(getmodulehandlea(user32.dll), messageboxa);  messageboxa(0, before hooking, veh hook, 0);  sethook(originalmessagebox);// 安装钩子用以触发异常  messageboxa(0, after hooking, veh hook, 0);  return 0;}  
在上述代码中,先保存了messageboxa函数在当前进程中的地址,之后第一次调用messageboxa,此时该函数还没有被hook,随后为了人为构造异常,将messageboxa函数代码的第一个字节修改为0xcc,当之后再次调用messageboxa后,程序将会触发0xcc异常,由于程序中添加了veh异常处理,那么程序将跳转到vehhandler(自写的异常处理函数中),在该函数代码中,首先过滤得到主动触发的异常,满足的条件下,开始执行自定义的代码,这里为了说明,使用messageboxw弹出对话框,由于异常被程序接管,所以在异常处理函数中,执行完自定义的代码后,需要修复异常,进而返回原始触发异常的位置继续执行。
效果
ps:通常来说,我们将hook的功能代码编写进一个dll文件中,在将该dll文件通过进程注入的方式注入到需要改变程序流程的进程中来达到目的。
丈八网安蛇矛实验室成立于2020年,致力于安全研究、攻防解决方案、靶场对标场景仿真复现及技战法设计与输出等相关方向。团队核心成员均由从事安全行业10余年经验的安全专家组成,团队目前成员涉及红蓝对抗、渗透测试、逆向破解、病毒分析、工控安全以及免杀等相关领域。


PS5与Xbox到底谁更好?
ds18b20测温程序,ds18b20温度测量c51单片机程序
红鹏全国经销商新品发布会召开 小金牛正式亮相
芯粤能半导体碳化硅芯片制造项目通过广东省能源局节能审查
如何解决激光电视散热问题
安全开发之应用层Hook技术
霍尔开关的主要分类有哪些
iphone8什么时候时候上市?价涨到2000美元就不是开玩笑
Bittele公司拥有最先进的自动化印刷电路板组装设备
openharmony刷机教程 源码获取概述
第三届红帽杯网络安全攻防大赛启动会在广州召开
企业组织如何通过云计算实施远程办公?
新唐科技W567C300控制器介绍
镭神智能入选国家级专精特新“小巨人”企业名单
AWE 2019:“8K+5G”成最佳拍档,在对的时间遇到了对的人
特斯拉首次承认通过车内摄像头来监视驾驶员
马克·扎克伯格为何致力于虚拟现实行业的发展 ?
三款大众化低价位数字卫星接收机评介(之二)
超声波测井的井下数据采集与传输系统的实现
德州仪器正式完成收购美国国家半导体