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

    文章

    1383

    浏览量

    60994
  • 应用层
    +关注

    关注

    0

    文章

    43

    浏览量

    11440
  • HOOK
    +关注

    关注

    0

    文章

    15

    浏览量

    8309

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

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

收藏 人收藏

    评论

    相关推荐

    Android安全开发WebView中的地雷

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

    can应用层协议

    有写过can应用层的协议吗,我正在搞nmea2000的can协议,网上没有资料,求帮助!!!!!!!!!
    发表于 04-09 19:03

    应用层通信安全

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

    Linux安全相关技术及项目设计简介

    Linux安全相关技术SELinuxAppArmoropenSSLTPMSGXLSM linux上六种常用的安全加固技术:1 安全的编码(S
    发表于 07-08 08:12

    学习嵌入式Linux应用层开发

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

    C语言在Linux应用层开发的使用率

    好,C在Linux应用层开发的使用率正在并将继续被象Python 或Java这类更“高级”的面向对象语言蚕食,就如以前的汇编语言使用率被C蚕食一样。2.2 Socket通信、智能家居或物联网
    发表于 11-04 07:51

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

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

    如何跳转到应用层

    如何跳转到应用层
    发表于 01-20 07:03

    【学习打卡】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次下载

    Web安全开发指南(图文完整版)

    Web安全开发指南 文字完整版
    发表于 09-08 10:50 0次下载

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

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

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

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

    贸泽开售面向安全应用的英飞凌OPTIGA Trust M物联网安全开发套件

    2023 年 5 月 11 日 – 专注于引入新品的全球半导体和电子元器件授权代理商贸泽电子 (Mouser Electronics) 即日起供货英飞凌的OPTIGA™ Trust M物联网安全开发
    发表于 05-12 17:05 474次阅读
     贸泽开售面向<b class='flag-5'>安全</b>应用的英飞凌OPTIGA Trust M物联网<b class='flag-5'>安全开发</b>套件