浅谈内联钩取原理与实现

news2024/11/25 0:47:21

前言

导入地址表钩取的方法容易实现但是存在缺陷,若需要钩取的函数不存在导入地址表中,那么我们就无法进行钩取,出现以下几种情况时,导入函数是不会存储在导入地址表中的。

  • 延迟加载:当导入函数还没调用时,导入函数还未写入到导入地址表中。
  • 动态链接:使用LoadLibraryGetProcAddress函数时,程序是显式获取函数地址的,因此不会写入到导入地址表中。
  • 手动解析导入函数:即程序自身实现一套导入方法,那么此时也不会将导入函数写入到导入地址表中。

有一种钩取方法解决上述问题即内联钩取(inline hook)。

内联钩取(inline hook)

内联钩取实际是找到需要钩取的函数地址,这里与导入地址表钩取不同的是我们不在局限于导入地址表,而是程序中所有的函数地址都能够作为钩取的对象。

这里以CreateProcessW函数为例,在CreateProcessW函数中,第一条指令是mov edi,edi

image-20240506224301537

那么根据钩取的思路,我们将mov edi,edi这条指令修改为jmp xxxxxx为我们自定义函数的地址),那么在执行CreateaProcessW函数时即可跳转到我们的自定义函数中。

我们获取mov edi,edi指令的地址,并且将该指令篡改为jmp指令,并且把mov edi,edi指令的数据进行存储,那么在执行到CreateProcessW函数时就会执行jmp指令跳转到自定义函数中,在钩取操作时需要将指令写回,还原CreateProcessW函数的执行逻辑,就可以在钩取的同时无碍的执行程序。

image-20240506225530172

那么总结一下内联钩取函数的流程

  • 找到需要钩取的函数的指令地址,这个指令并不仅限于函数起始的指令。
  • 将该指令篡改成跳转指令,跳转的目的就是自定义的函数。
  • 在自定义函数内需要还原被钩取函数的指令。

因此内联钩取的实际就是修改程序执行逻辑,劫持程序的执行流程。由于32位程序与64位程序的汇编语言与寻址方式有些许差异,因此不同机器位数的程序的内联钩取方式不同。

机器码的获取

由于在篡改内存时需要将jmp xxx的机器码填写到内存中,因此做内联钩取时需要获取指令对应的机器码。在C语言中支持内联汇编,因此可以使用内联汇编然后查看对应的机器码即可。

但是直接使用visual studio编译64位程序的内联汇编代码会出错,这是因为visual studio自带的编译工具不支持x64的内联汇编。

image-20240507131700203

因此需要先安装clang编译器

image-20240507131840623

在项目的编译工具选择clang即可

image-20240507131913336

在反汇编窗口中就有机器码了。

image-20240507132015250

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

32位的内联钩取

首先第一步是确定在32位程序下是如何进行跳转的,在32位情况使用跳转指令是根据偏移获取目的地址,偏移的计算公式如下

跳转偏移 = 跳转目的地址 - 当前指令地址 - 指令长度

因此jmp xxx中,xxx是偏移值而不是目的函数的绝对地址。

紧接着需要确定在32位下跳转指令的机器码是多少,用下面例子看看

void MyCreateProcess()
{

}

int main()
{

	__asm {
		jmp MyCreateProcess;
	};


}

可以看到对应的机器码为E9 EB FF FF FF

image-20240507134049072

可以看到目标函数的地址为0xA71000,使用上述公式计算一下偏移为0xA71000 - 0x0A71010 - 5 = 0xffffffeb,因此E9jmp的机器码

因此需要将待钩取函数的第一条指令修改为E9 XX XX XX XX XX,长度为5个字节

然后选择一个目标函数,这里还是使用CreateProcessW函数作为例子,需要先获取CreateProcessW函数的地址

    ...
    hMoudle = GetModuleHandleA(szDllName); //获取Kernel32.dll模块的地址
    if (hMoudle == NULL)
    {
        GetLastError();
    }
    
    pfnOld = GetProcAddress(hMoudle, funName);//获取CreateProcessW函数地址
    if (pfnOld == NULL)
    {
        GetLastError();
    }
	...

然后需要保存原始指令,然后修改区域为可写权限,紧接着计算一下偏移把完整的指令写进到待钩取函数即可。

	...
	//修改权限
    VirtualProtect(pfnOld, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    //存储原始的5个字节
    memcpy(pOrgBytes, pfnOld, 5);
    //计算需要跳转到的地址
    //跳转偏移 = 跳转目的地址 - 当前指令地址 - 指令长度
    dwAddress = (ULONGLONG)pfnNew - (ULONGLONG)pfnOld - 5;
	//将目标函数的地址写入到指令中
    memcpy(&pBuf[1], &dwAddress, 4);
	//篡改为跳转指令
    memcpy(pfnOld, pBuf, 5);
	//还原权限
    VirtualProtect(pfnOld, 5, dwOldProtect, &dwOldProtect);
	...

64位的内联钩取

64位下的规则会与32位有差异,但是总体思路是一致的。在32位下我们采用了偏移的方式找到目标函数,在64位下可以换种方式,采用mov rax, xxx; jmp rax,将函数的绝对地址写入寄存器,然后跳转到指定寄存器的方式。

如下例子,我们首先获取自定义函数的绝对地址,紧接着将它存放于寄存器中,紧接着跳转即可。

int main()
{
	__asm {
		mov rax, 0x1122334455667788;
		jmp rax;
	};

}

可以看到mov rax, xxx; jmp rax指令的机器码为48 B8 xx xx xx xx xx xx xx xx FF E0,其中由于64位地址都是8字节的,因此需要xx需要填充8字节

image-20240507153239222

因此总体代码与32位区别不大,这里需要注意的是篡改的指令长度需要根据实际进行更改。

    /*
    * 48 B8 88 77 66 55 44 33 22 11 mov rax, 0x1122334455667788
    * FF E0                         jmp rax
    * 需要12个字节进行跳转
    */

    //修改区域权限
    VirtualProtect((LPVOID)pfnOrg, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    //保存原有的12字节数据
    memcpy(pOrgBytes, pfnOrg, 12);
    //将HOOK函数的地址填进缓冲区
    //将目标地址拷贝到指令中
    memcpy(&pBuf[2], &pfnNew, 8);
    //篡改待钩取函数
    memcpy(pfnOrg, pBuf, 12);
    //恢复权限
    VirtualProtect((LPVOID)pfnOrg, 12, dwOldProtect, &dwOldProtect);

因此任意可以修改函数执行流程的汇编指令实际都可以例如push xxx; ret

完整代码可以参考https://github.com/h0pe-ay/HookTechnology/tree/main/Hook-InlineHook

总结

优势

  • 内联钩取相较于导入表钩取的选择性更广,可以选择任意的函数及函数内的任意指令地址。

劣势

  • 每次都需要脱钩后再进行挂钩,影响效率
  • 多线程写入时可能会出错

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1817336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Rust 实战丨通过实现 json! 掌握声明宏

在 Rust 编程语言中,宏是一种强大的工具,可以用于在编译时生成代码。json! 是一个在 Rust 中广泛使用的宏,它允许我们在 Rust 代码中方便地创建 JSON 数据。 声明宏(declarative macros)是 Rust 中的一种宏&#xff0…

debug调试_以Pycharm为例

文章目录 作用步骤打断点调试调试窗口 作用 主要是检查逻辑错误,而非语法错误。 步骤 打断点 在需要调试的代码行前打断点,执行后会停顿在断点位置(不运行) 调试 右键“debug”,或者直接点击右上角的小虫子 调试…

2-2 基于matlab的变邻域

基于matlab的变邻域,含变惯性权重策略的自适应离散粒子群算法,适应函数是多式联运路径优化距离。有10城市、30城市、75城市三个案例。可直接运行。 2-2 路径规划 自适应离散粒子群算法 - 小红书 (xiaohongshu.com)

Vue基本使用-02

上节我们讲了什么是mvvm模型,以及我们vue的一些常用指令,今天给大家讲一下vue的基本使用,在将之前我们需要重点讲解我们的一个指令,v-model指令 v-model v-model 可以在组件上使用以实现双向绑定,什么是双向绑定呢?意思就是当我们…

【Ubuntu双系统】两块硬盘分别安装系统,一块硬盘安装Ubuntu 一块安装Windows

【Ubuntu双系统】两块硬盘分别安装双系统,一块硬盘安装Ubuntu 一块安装Windows 前言安装Ubuntu前置操作安装过程参考文献 前言 机器情况:两块1T的硬盘,其中一块已安装Windows 11现需在另一块硬盘上安装Ubuntu,该硬盘还未初始化Ub…

SQL聚合函数---汇总数据

此篇文章内容均来自与mysql必知必会教材,后期有衍生会继续更新、补充知识体系结构 文章目录 SQL聚集函数表:AGV()count()根据需求可以进行组合处理 max()min()max()、min()、avg()组…

Mac下载了docker,在终端使用docker命令时用不了

问题:在mac使用docker的时候,拉取docker镜像失败 原因:docker是需要用app使用的 ,所以在使用的时候必须打开这个桌面端软件才可以在终端上使用docker命令!!!

【PL理论】(21) 函数式语言:支持匿名函数 fun x → E | 设计递归函数 | 支持递归函数:let rec ...

💭 写在前面:本章我们将讲解支持匿名函数,先回顾一下 F# 语言表示函数的方法,然后引出它。随后我们讲解一下如何设计递归函数,最后让我们的 F- 语言支持递归函数。 目录 0x00 回顾:F# 语言 0x01 支持匿名…

深度学习笔记: 最详尽Airbnb租赁搜索排名设计

欢迎收藏Star我的Machine Learning Blog:https://github.com/purepisces/Wenqing-Machine_Learning_Blog。如果收藏star, 有问题可以随时与我交流, 谢谢大家! Airbnb租赁搜索排名 1. 问题陈述 Airbnb用户在特定地点搜索可用房源。系统应在搜索结果中对多个房源进…

Qt飞机大战小游戏

Gitee地址 :plane-game: 基于Qt的飞机大战小游戏 GitHub地址: https://github.com/a-mo-xi-wei/plane-game

Vue25-内置指令02:v-text指令

一、v-html对比v-text v-html支持结构的解析,v-text不支持结构的解析。 二、v-html的安全性问题 2-1、cookie的原理(node.js) 7天免登录,cookie实现。 cookie的本质就是类似于json的字符串,格式是:key-va…

图片导入AutoCAD建立草图—CAD图像导入插件

插件介绍 CAD图像导入插件可将PNG,JPG等格式图片导入到AutoCAD软件内建立图像边缘的二维线条模型。插件可以提取图像黑色或白色区域的边界,并可绘制原状边界或平滑边界两种样式。 模型说明 边界提取,黑色或白色边界的提取根据原图类型选择…

【云原生| K8S系列】Kubernetes Daemonset,全面指南

Kubernetes中的DaemonSet是什么? Kubernetes是一个分布式系统,Kubernetes平台管理员应该有一些功能可以在所有节点上运行特定于平台的应用程序。例如,在所有Kubernetes节点上运行日志代理。 这就是Daemonset发挥作用的地方。 Daemonset是一个原生的K…

查询满足条件的元组-WHRER子句(运算符、BETWEEN 、LIKE、IN、NULL)

一、WHERE子句(筛选出使选择表达式为真的元组) 1、SELECT-FROM子句可以实现数据的查询(会查询出所有元组),加上WHERE子句之后可以实现数据的筛选(会查询出满足条件的元组) SELECT 【ALL|DISTI…

windows 下 基于 WSL2安装DeepSpares进行YOLOV8 v5 的加速推理

文章大纲 简介软硬件限制安装安装 WSL2 基础环境WSL2 手动安装安装 miniconda 环境本地USB 摄像头使用:Windows 无延迟视频流本地USB 摄像头使用:WSL2 挂载 本地 USB 摄像头WSL2更新报错: 离线安装 wsl --update安装 DeepSpares测试打开本地USB 摄像头进行测试测试结果参考文…

50.Python-web框架-Django中引入静态的bootstrap样式

目录 Bootstrap 官网 特性 下载 在线样例 Bootstrap 入门 Bootstrap v5 中文文档 v5.3 | Bootstrap 中文网 在django中使用bootstrap 新建static\bootstrap5目录,解压后的Bootstrap文件,拷贝项目里就好。 在template文件里引用css文…

Nginx+KeepAlived高可用负载均衡集群的部署

目录 一.KeepAlived补充知识 1.一个合格的群集应该具备的特点 2.健康检查(探针)常用的工作方式 3.相关面试问题 问题1 问题2 二.Keepealived脑裂现象 1.现象 2.原因 硬件原因 运用配置原因 3.解决 4.预防 方法1 方法2 方法3 方法4 三.…

VUE之重定向redirect

VUE之路由和重定向redirect 这个小知识点是在学习做项目的时候遇到的一个问题,借鉴了一个他人的项目,是一个酒店管理系统,拿到源码之后导到我的vscode里。 参考链接 导的过程比较顺利,正常安装,加依赖,没有…

禁渔期水域监管:EasyCVR视频智能监控方案

一、背景与需求分析 根据农业部印发的《中国渔政亮剑2024系列专项执法行动方案》,我国将持续推进长江十年禁渔、海洋伏季休渔、黄河等内陆重点水域禁渔等专项行动。根据四川省相关规定,每年3月1日至6月30日为禁渔期,在此期间,四川…

坚持每天学编程的有多少?聊聊有多少人躺平了,工作生活压力大吗

以前刚开始学编程的时候,一晚上就能看完一本Frontpage网页编程的书,就像是WORD一样简单,第二天就敢去找工作。工作后学习VB6SQL SERVER数据库,几百页的大部头书,基本上一个月也能看完,后面还买了2个大书柜&…