0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

安全开发之应用层Hook技术

蛇矛实验室 来源:蛇矛实验室 作者:蛇矛实验室 2022-10-31 14:32 次阅读

本环境是蛇矛实验室基于"火天网演攻防演训靶场"进行搭建,通过火天网演中的环境构建模块,可以灵活的对目标网络进行设计和配置,并且可以快速进行场景搭建和复现验证工作。

前言

Hook中文译为“钩子”或“挂钩”,这很容易联想到钓东西,好比钓鱼,但将其比作“网”更合适,在安全开发的过程中,Hook技术主要用于对程序的运行流程进行控制和拦截,对特定的消息或动作进行过滤。

Hook原理

在真正执行原始API之前,对程序流程进行拦截,使其先执行自定义的代码后,再执行原始API调用流程。

e58f24f4-56c7-11ed-a3b6-dac502259ad0.png

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
#pragmacomment (lib, "dbghelp.lib")

// 1. 定义基于User32!MessageBoxA的函数原型的函数指针
usingMessageBoxT = int(WINAPI*)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

// 2. 获取User32!MessageBoxA的函数地址并保存;
MessageBoxT OriginalMessageBox = MessageBoxA;

// 3. 创建HookedMessageBoxA函数(函数原型同User32!MessageBoxA一样),以拦截程序对User32!MessageBoxA的调用:
intWINAPI HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
// 3.1 先执行自定义的代码
MessageBoxW(0, L"HookedMessageBox() called", L"IAT Hook", 0);

// 3.2 再执行原始User32!MessageBoxA函数
returnOriginalMessageBox(hWnd, lpText, lpCaption, uType);
}

/*
4. 解析导入表,并在IAT中定位User32!MessageBoxA的位置;
5. 使用HookedMessageBoxA函数的地址替换IAT中User32!MessageBoxA的地址。
*/
boolSetHook(std::stringdllName, std::stringorigFunc, 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
boolfound = false;
for(i = 0; i < size; i++)
  {
    importDllName = (LPCSTR)importDescTab[i].Name + (DWORD_PTR)imageBase; // 获取导入DLL名称
    if (_stricmp(dllName.c_str(), importDllName) == 0)
    {
      found = true;
      break;
    }
  }

  // 没找到目标DLL,返回
  if (!found)
    return false;

  // 找到目标dll
  importDllImageBase = GetModuleHandleA(importDllName);
  if (!importDllImageBase) return false;

  PIMAGE_THUNK_DATA originalFirstThunk = (PIMAGE_THUNK_DATA)((ULONG_PTR)imageBase + importDescTab[i].OriginalFirstThunk); // 定位导入名称表INT
  PIMAGE_THUNK_DATA firstThunk = (PIMAGE_THUNK_DATA)((ULONG_PTR)imageBase + importDescTab[i].FirstThunk); // 定位导入地址表IAT

  // 寻找Hook的API
  while (originalFirstThunk->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;
}

returntrue;
}

intmain()
{
// Hook前
MessageBoxA(0, "Before Hooking", "IAT HOOKS", 0);

// 进行IAT Hook
SetHook("user32.dll", "MessageBoxA", (PROC)HookedMessageBox);

// Hook后
MessageBoxA(0, "After Hooking", "IAT Hook", 0);

return0;
}

上述代码第一次调用MessageBoxA时,正常弹出,为了之后在调用MessageBoxA时,先执行自定义的函数代码(HookedMessageBox),首先在进程的IAT中定位到MessageBoxA的地址,这个过程是先通过进程的导入表找到MessageBoxA所在的DLL模块(user32.dll),找到之后,通过INT(导入名称表)得到MessageBoxA函数地址在IAT中的下标,此时使用自定义函数的地址替换掉IAT中MessageBoxA函数的地址,即可达到IAT Hook的效果,Hook之后在调用MessageBoxA时,程序会先执行HookedMessageBox函数,在执行原始的MessageBoxA函数(需要提前获取MessageBoxA的地址)。

效果

e5a47dc2-56c7-11ed-a3b6-dac502259ad0.gif

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

#ifdefined(_WIN64)
#defineORIG_BYTES_SIZE 14
#else
#defineORIG_BYTES_SIZE 7
#endif

BYTE OriginalBytes[ORIG_BYTES_SIZE]{}; // 用于保存MessageBoxA的部分原始代码字节
BYTE PatchBytes[ORIG_BYTES_SIZE]{}; // 构造的跳转指令

usingMessageBoxAT = int(WINAPI*)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
MessageBoxAT OriginalMessageBox = nullptr;

intWINAPI HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
// 执行自定义的代码
SIZE_T bytesOut = 0;

MessageBoxW(0, L"HookedMessageBox() called", L"Inline Hook", 0);

// unpatch MessageBoxA
WriteProcessMemory(GetCurrentProcess(), (LPVOID)OriginalMessageBox, OriginalBytes, sizeof(OriginalBytes), &bytesOut);

// 调用原来的MessageBoxA
intresult = MessageBoxA(NULL, lpText, lpCaption, uType);

// 再次patch MessageBoxA
WriteProcessMemory(GetCurrentProcess(), OriginalMessageBox, PatchBytes, sizeof(PatchBytes), &bytesOut);

returnresult;
}

boolSetHook(std::stringdllName, std::stringorigFunc, 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));
#ifdefined(_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);

returntrue;
}

intmain()
{
// Hook前
MessageBoxA(0, "Before Hooking", "Inline Hook", 0);

// 进行Inline Hook
SetHook("user32.dll", "MessageBoxA", (FARPROC)HookedMessageBox);

// Hook后
MessageBoxA(0, "After Hooking", "Inline Hook", 0);

return0;
}

程序中调用了2次MessageBoxA,第一次调用时未被挂钩,之后Inline Hook方式使用对MessageBoxA进行挂钩,当之后再次调用MessageBoxA时,程序会首先进入自写函数(HookedMessageBox)中,在该函数中,自定义的代码部分使用MessageBoxW弹出内容,随后修复MessageBoxA被修改的字节代码后,开始执行原始MessageBoxA代码。

效果

e5d414ba-56c7-11ed-a3b6-dac502259ad0.gif

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;

structEXCEPTION_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, L"VEHHandler() called", L"VEH 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);

returnEXCEPTION_CONTINUE_EXECUTION; // 回到异常发生的地方,由于已经修复了异常问题,所以之后能够正确执行
}
returnEXCEPTION_CONTINUE_SEARCH; // 向上继续寻找异常处理程序
}

voidSetHook(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);
}

intmain()
{
// 保存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);

return0;
}

在上述代码中,先保存了MessageBoxA函数在当前进程中的地址,之后第一次调用MessageBoxA,此时该函数还没有被Hook,随后为了人为构造异常,将MessageBoxA函数代码的第一个字节修改为0xCC,当之后再次调用MessageBoxA后,程序将会触发0xCC异常,由于程序中添加了VEH异常处理,那么程序将跳转到VEHHandler(自写的异常处理函数中),在该函数代码中,首先过滤得到主动触发的异常,满足的条件下,开始执行自定义的代码,这里为了说明,使用MessageBoxW弹出对话框,由于异常被程序接管,所以在异常处理函数中,执行完自定义的代码后,需要修复异常,进而返回原始触发异常的位置继续执行。

效果

e5f6a836-56c7-11ed-a3b6-dac502259ad0.gif

PS:通常来说,我们将Hook的功能代码编写进一个DLL文件中,在将该DLL文件通过进程注入的方式注入到需要改变程序流程的进程中来达到目的。

丈八网安蛇矛实验室成立于2020年,致力于安全研究、攻防解决方案、靶场对标场景仿真复现及技战法设计与输出等相关方向。团队核心成员均由从事安全行业10余年经验的安全专家组成,团队目前成员涉及红蓝对抗、渗透测试、逆向破解、病毒分析、工控安全以及免杀等相关领域。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • API
    API
    +关注

    关注

    2

    文章

    1499

    浏览量

    61964
  • 应用层
    +关注

    关注

    0

    文章

    46

    浏览量

    11500
  • HOOK
    +关注

    关注

    0

    文章

    15

    浏览量

    8382

原文标题:安全开发之应用层Hook技术

文章出处:【微信号:蛇矛实验室,微信公众号:蛇矛实验室】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Android安全开发WebView中的地雷

    `Android安全开发WebView中的地雷0X01 About WebView在Android开发中,经常会使用WebView来实现WEB页面的展示,在Activiry中启动自己的浏览器,或者
    发表于 09-09 19:35

    应用层通信安全

    `欢迎工程师的我们!电子发烧友E2E技术沙龙-北京站,本期讨论主题是关于“应用层通信安全”E2E活动北京站活动链接~~http://url.elecfans.com/u/e524cb1b33
    发表于 03-18 17:55

    学习嵌入式Linux应用层开发

    1 应用层与驱动要想学习嵌入式Linux应用层开发,首先要区分好应用层和驱动之间的关系。我
    发表于 11-02 10:20

    嵌入式应用层开发通常有哪些问题

    嵌入式应用层开发通常有哪些问题?
    发表于 12-24 06:54

    【学习打卡】OpenHarmony的应用层说明

    以增加额外的网络安全;3.确保存在必要的通信接口,例如发送方计算机中是否有以太网或Wi-Fi接口;4.确保双方就错误恢复程序、数据完整性和隐私达成一致;5.在应用层确定协议和数据语法规则;6.将接收
    发表于 07-14 08:44

    讲讲Hook技术的攻防对抗思路

    1、论Hook技术的攻防对抗  首先,简单认识下Hook 技术。  Hook技术是一门广泛用于计
    发表于 09-28 11:12

    嵌入式开发系列课程五:Windows CE安全开发

    嵌入式开发系列课程五:Windows CE安全开发与配置
    发表于 03-25 08:58 22次下载

    应用层和后台分析便携式电子产品的节能技术

    应用层和后台分析便携式电子产品的节能技术  便携式电子产品的节能技术基本上可以按照其执行方式分为应用层技术及后台
    发表于 11-30 10:03 614次阅读
    从<b class='flag-5'>应用层</b>和后台分析便携式电子产品的节能<b class='flag-5'>技术</b>

    SIP应用层网关技术

    本文提出了“SIP应用层网关”技术,并将其应用于网络通信中来建立相对合理、完善的SIP网络,以解决SIP私网远程控制中穿越NAT/FireWall的难题
    发表于 04-20 11:37 5744次阅读

    Zigbee应用层规范

    本内容介绍了Zigbee应用层规范
    发表于 05-24 11:37 85次下载
    Zigbee<b class='flag-5'>应用层</b>规范

    电子功能安全开发及汽车EPS电机控制设计

    实现认证并开始你的功能安全开发
    的头像 发表于 08-14 00:15 4917次阅读

    认知无线电MAC应用层仿真软件

    认知无线电MAC应用层仿真软件(澳莱特电源技术有限公司)-该文档为认知无线电MAC应用层仿真软件总结文档,是一份很不错的参考资料,具
    发表于 09-15 11:40 11次下载
    认知无线电MAC<b class='flag-5'>层</b>与<b class='flag-5'>应用层</b>仿真软件

    嵌入式Linux应用层开发教程(一)基本概念

    1 应用层与驱动要想学习嵌入式Linux应用层开发,首先要区分好应用层和驱动之间的关系。我
    发表于 11-01 17:59 14次下载
    嵌入式Linux<b class='flag-5'>应用层</b><b class='flag-5'>开发</b>教程(一)基本概念

    什么是SEooC?SEooC和正常功能安全开发有什么不同?

    在功能安全开发过程中,很多时候我们会遇到独立于环境的安全要素开发(Safety Element out of Context, SEooC)
    的头像 发表于 04-27 16:52 9303次阅读
    什么是SEooC?SEooC和正常功能<b class='flag-5'>安全开发</b>有什么不同?

    物联网的技术架构及应用层是什么?

    物联网的技术架构包括感知、网络、平台应用层应用层是物联网的顶层,它的主要功能是将感知
    的头像 发表于 07-15 08:56 3709次阅读