Win32汇编学习笔记06.APIHook-C/C++基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net

api hook 称为 api 钩子,也称为 内联apihook

我们程序使用时,有时候需要获得程序的一些其他信息,例如,网络发包是获得包数据,打开文件时,获得文件信息等,这是,我们就可以给这些api下个钩子,当他操作时可以把它的数据抓下来,保存

钩子类似起监控作用

网络,文件,注册表自己实现需要进内核

API Hook

简介:API Hook 是通过拦截 Api 的调用,使其流程转移到我们指定的地方,同时为了保持原有程序的健壮性,需要执行完我们流程后再转移会自身的流程。

原理:修改api函数入口的地方,让他调转到我们函数入口点。API在入口处特意留了位置。

汇编思路:jmp 地址 -> 我们代码 -> ret返回 jmp 下一条地址继续执行。

使用场景:

1通常为某api 的功能做扩展或则改变api所

2为大型程序打补丁,拦截api并成功跳转

3执行api前修改api的参数

实现api hook

创建一个桌面应用程序

把点击关于的弹窗改成 MessageBox

接下来我们需要实现改变 MessageBox 的标题,实际就是改 api 的参数那么就需要修改 MessageBox 的代码

查看 MessageBox 在 debug 模式中 是无法 通过 MessageBox 的 api 直接 找到的,因为 有跳表,但是可以在 user32中招

但是我们修改代码不能修改它由原本自己功能的代码,需要先跳走,实现我们的代码再跳回来,那问题是在那里跳呢?

一个 jmp 的 机器码是 5个字节,一般是不能随便选位置,否则的话可能会覆盖其他代码,还可能截断其他指令

我们一般放在下图位置

上面3条指令,有一条是没用的 (第一条),而且加起来就是5个字节,而且很多api 开头都是这3条指令,刚好一条 jmp

这3条指令功能是固定的,我们恢复的时候也很简单

综上所述:想实现 api hook

思路:修改messagebox api 的执行流程,观察messagebox 的汇编代码如下。

我们都知道,每个进程都有自己的地址空间,指针的值表示的是自己的地址空间,进程不能创建一个指针来引用属于其他进程的内存。所以首选DLL的,而不是远程线程注入代码。因为一旦DLL进入另一个地址空间,那么我们就可以在这个进程中为所欲为。选择线程代码注入的话。需要重定位API ,且如果注入的代码需要调用其他api,还要单独计算重定位。

jmp 偏移的计算:跳转到的目标地址 - 当前地址 - 5(API开头的位置)

流程:

1将 dll 注入到目标程序。执行DLL函数的功能,DLL功能如下:

2获取MessageBox 地址,修改MessageBox入口地址为jmp + 偏移 ---》自定义代码

3保存系统函数入口处的代码

4替换掉进程中的而系统函数入口指向我们的函数

5当系统函数被调用,立即跳转到我们的函数

6我们的函数处理

7恢复系统函数入口的代码

8调用原来的系统函数

9再修改系统函数入口指向我们的函数 ->返回

代码实现:

.386
.model flat, stdcall ;32 bit memory model
option casemap :none ;case sensitive ;include HookApi.inc include windows.inc
include kernel32.inc
include user32.inc
include Comctl32.inc
include shell32.inc
include msvcrt.inc includelib kernel32.lib
includelib user32.lib
includelib Comctl32.lib
includelib shell32.lib
includelib msvcrt.lib .data
g_szUser32 db "user32",0 ; 用于根据模块名获取模块地址
g_szMessageBoxA db "MessageBoxA",0 ; 用于根据函数名获取函数地址
g_dwAddrMsgBox dd 0 ; MessageBox地址 g_szNewTitel db "这是修改后的标题",0 .code MYMESSAGE: ;这块代码是调用MessageBox 时才执行
;修改标题 mov dword ptr [esp+ 0CH], offset g_szNewTitel ;把原先标题位置的值换成新标题的位置 ;执行原来代码 mov edi,edi
push ebp
mov ebp,esp ;跳回去
mov eax,g_dwAddrMsgBox
add eax,5 ;上面3条指令长度
jmp eax InstallHook proc ;这块代码是加载dll时就执行
LOCAL @dwOldProc:DWORD ;修改之前的内存属性 ;获取MessageBox地址,用过 user32 动态获取
invoke GetModuleHandle,offset g_szUser32 ;根据模块名获取模块地址 invoke GetProcAddress,eax,offset g_szMessageBoxA ;获取模块中指定函数名的地址
mov g_dwAddrMsgBox,eax ;保存函数地址 ;添加 jmp 到自己代码
;获取跳转偏移
mov ebx,offset MYMESSAGE ;要跳转的目标地址
sub ebx,g_dwAddrMsgBox ;获取 到标地址的偏移
sub ebx,5 ;减去jmp指定的长度(下一条指令地址) ;修改内存属性
invoke VirtualProtect, g_dwAddrMsgBox, 1, PAGE_EXECUTE_READWRITE, addr @dwOldProc ;修改指令
mov eax, g_dwAddrMsgBox
;改指令位跳转指令
mov byte ptr [eax],0e9H ;jmp
mov dword ptr [eax+1 ],ebx ;偏移值 ;还原内存属性
invoke VirtualProtect, g_dwAddrMsgBox, 1, @dwOldProc, addr @dwOldProc ret
InstallHook endp DllMain proc hinstDLL:HINSTANCE, fdwReason:DWORD, lpvReserved:LPVOID .if fdwReason == DLL_PROCESS_ATTACH ;如果dll被映射
;安装 dll
invoke InstallHook .endif mov eax, TRUE
ret
DllMain endp end DllMain

调试程序,发现原来的3行代码已经变成了 jmp 了

Hook注意点

1写DLL的时候千万不要忘记 返回TRUE,否则DLL会加载失败

2因为MessageBox加载到进程中是属于代码段,可读,不可写,需要修改内存属性。

●计算注入代码偏移,以便正确拿到注入代码中携带的变量
●保存寄存器环境
●执行覆盖的指令
●跳转回覆盖指令的下一条指令

API重入

当我们我们需要调用 hook 后 的 api的话 ,会产生 调 api --- 调 hook --- 调 api -- 调hook

无限套娃的情况, 导致栈空间 或者 内存空间 被撑爆

例如: 上面代码,我自在自己代码中 调 MessageBox ,就会出现

解决上面问题有3种方法

1.自己调之前先把之前修改的代码恢复,调完之后再修改

自己使用前 先卸载 hook ,使用后在安装

.386
.model flat, stdcall ;32 bit memory model
option casemap :none ;case sensitive ;include HookApi.inc include windows.inc
include kernel32.inc
include user32.inc
include Comctl32.inc
include shell32.inc
include msvcrt.inc includelib kernel32.lib
includelib user32.lib
includelib Comctl32.lib
includelib shell32.lib
includelib msvcrt.lib InstallHook proto
UninstallHook proto .data
g_szUser32 db "user32",0 ; 用于根据模块名获取模块地址
g_szMessageBoxA db "MessageBoxA",0 ; 用于根据函数名获取函数地址
g_dwAddrMsgBox dd 0 ; MessageBox地址 g_szNewTitel db "这是修改后的标题",0 g_szTitel db "重入",0
g_szTxt db "这这是重入弹窗",0 .code MYMESSAGE: ;这块代码是调用MessageBox 时才执行
;修改标题 mov dword ptr [esp+ 0CH], offset g_szNewTitel ;把原先标题位置的值换成新标题的位置 invoke UninstallHook ;卸载hook
invoke MessageBoxA,NULL,offset g_szTxt,offset g_szTitel ,MB_OK
invoke InstallHook ;安装hook
;执行原来代码
mov edi,edi
push ebp
mov ebp,esp ;跳回去
mov eax,g_dwAddrMsgBox
add eax,5 ;上面3条指令长度
jmp eax InstallHook proc uses ebx ;这块代码是加载dll时就执行
LOCAL @dwOldProc:DWORD ;修改之前的内存属性 ;获取MessageBox地址,用过 user32 动态获取
invoke GetModuleHandle,offset g_szUser32 ;根据模块名获取模块地址 invoke GetProcAddress,eax,offset g_szMessageBoxA ;获取模块中指定函数名的地址
mov g_dwAddrMsgBox,eax ;保存函数地址 ;添加 jmp 到自己代码
;获取跳转偏移
mov ebx,offset MYMESSAGE ;要跳转的目标地址
sub ebx,g_dwAddrMsgBox ;获取 到标地址的偏移
sub ebx,5 ;减去jmp指定的长度(下一条指令地址) ;修改内存属性
invoke VirtualProtect, g_dwAddrMsgBox, 1, PAGE_EXECUTE_READWRITE, addr @dwOldProc ;修改指令
mov eax, g_dwAddrMsgBox
;改指令位跳转指令
mov byte ptr [eax],0e9H ;jmp
mov dword ptr [eax+1 ],ebx ;偏移值 ;还原内存属性
invoke VirtualProtect, g_dwAddrMsgBox, 1, @dwOldProc, addr @dwOldProc ret
InstallHook endp UninstallHook proc ;卸载hook,还原之前修改的3行代码
LOCAL @dwOldProc:DWORD ;修改之前的内存属性 ;修改内存属性
invoke VirtualProtect, g_dwAddrMsgBox, 1, PAGE_EXECUTE_READWRITE, addr @dwOldProc ;还原指令
;8B FF 55 8B EC
mov eax, g_dwAddrMsgBox
mov byte ptr [eax], 08Bh
mov byte ptr [eax+1], 0FFh
mov byte ptr [eax+2], 055h
mov byte ptr [eax+3], 08Bh
mov byte ptr [eax+4], 0ECh ;还原内存属性
invoke VirtualProtect, g_dwAddrMsgBox, 1, @dwOldProc, addr @dwOldProc ret ret
UninstallHook endp DllMain proc hinstDLL:HINSTANCE, fdwReason:DWORD, lpvReserved:LPVOID .if fdwReason == DLL_PROCESS_ATTACH ;如果dll被映射
;安装 dll
invoke InstallHook .endif mov eax, TRUE
ret
DllMain endp end DllMain
2. 加状态标记(推荐)

自己调用前先把状态改成 true,用完之后就改成 false

.386
.model flat, stdcall ;32 bit memory model
option casemap :none ;case sensitive ;include HookApi.inc include windows.inc
include kernel32.inc
include user32.inc
include Comctl32.inc
include shell32.inc
include msvcrt.inc includelib kernel32.lib
includelib user32.lib
includelib Comctl32.lib
includelib shell32.lib
includelib msvcrt.lib InstallHook proto .data
g_szUser32 db "user32",0 ; 用于根据模块名获取模块地址
g_szMessageBoxA db "MessageBoxA",0 ; 用于根据函数名获取函数地址
g_dwAddrMsgBox dd 0 ; MessageBox地址 g_szNewTitel db "这是修改后的标题",0 g_szTitel db "重入",0
g_szTxt db "这这是重入弹窗",0 g_bIsSelfCall db FALSE ;判断是否自己调用 .code MYMESSAGE: ;这块代码是调用MessageBox 时才执行
;修改标题 .if !g_bIsSelfCall
mov g_bIsSelfCall,TRUE mov dword ptr [esp+ 0CH], offset g_szNewTitel ;把原先标题位置的值换成新标题的位置
;这下面可以放我们自己的api
invoke MessageBoxA,NULL,offset g_szTxt,offset g_szTitel ,MB_OK mov g_bIsSelfCall,FALSE
.endif ;执行原来代码
mov edi,edi
push ebp
mov ebp,esp ;跳回去
mov eax,g_dwAddrMsgBox
add eax,5 ;上面3条指令长度
jmp eax InstallHook proc uses ebx ;这块代码是加载dll时就执行
LOCAL @dwOldProc:DWORD ;修改之前的内存属性 ;获取MessageBox地址,用过 user32 动态获取
invoke GetModuleHandle,offset g_szUser32 ;根据模块名获取模块地址 invoke GetProcAddress,eax,offset g_szMessageBoxA ;获取模块中指定函数名的地址
mov g_dwAddrMsgBox,eax ;保存函数地址 ;添加 jmp 到自己代码
;获取跳转偏移
mov ebx,offset MYMESSAGE ;要跳转的目标地址
sub ebx,g_dwAddrMsgBox ;获取 到标地址的偏移
sub ebx,5 ;减去jmp指定的长度(下一条指令地址) ;修改内存属性
invoke VirtualProtect, g_dwAddrMsgBox, 1, PAGE_EXECUTE_READWRITE, addr @dwOldProc ;修改指令
mov eax, g_dwAddrMsgBox
;改指令位跳转指令
mov byte ptr [eax],0e9H ;jmp
mov dword ptr [eax+1 ],ebx ;偏移值 ;还原内存属性
invoke VirtualProtect, g_dwAddrMsgBox, 1, @dwOldProc, addr @dwOldProc ret
InstallHook endp DllMain proc hinstDLL:HINSTANCE, fdwReason:DWORD, lpvReserved:LPVOID .if fdwReason == DLL_PROCESS_ATTACH ;如果dll被映射
;安装 dll
invoke InstallHook .endif mov eax, TRUE
ret
DllMain endp end DllMain
3.自己调的话 第二次 就不执行 自己调的函数,绕过 直接执行下面的 或者直接执行下面的代码,因为那里才是 真正入口
.386
.model flat, stdcall ;32 bit memory model
option casemap :none ;case sensitive ;include HookApi.inc include windows.inc
include kernel32.inc
include user32.inc
include Comctl32.inc
include shell32.inc
include msvcrt.inc includelib kernel32.lib
includelib user32.lib
includelib Comctl32.lib
includelib shell32.lib
includelib msvcrt.lib InstallHook proto .data
g_szUser32 db "user32",0 ; 用于根据模块名获取模块地址
g_szMessageBoxA db "MessageBoxA",0 ; 用于根据函数名获取函数地址
g_dwAddrMsgBox dd 0 ; MessageBox地址 g_szNewTitel db "这是修改后的标题",0 g_szTitel db "重入",0
g_szTxt db "这这是重入弹窗",0 .code ;我们自己的MessageBox MYMESSAGE:
push MB_OK
push offset g_szTitel
push offset g_szTxt
push NULL
call NWEMSGBOX
;.....
;.....
;修改标题
mov dword ptr [esp+0ch], offset g_szNewTitel NWEMSGBOX: ;这块代码是调用MessageBox 时才执行 ;执行原来代码
mov edi,edi
push ebp
mov ebp,esp ;跳回去
mov eax,g_dwAddrMsgBox
add eax,5 ;上面3条指令长度
jmp eax InstallHook proc uses ebx ;这块代码是加载dll时就执行
LOCAL @dwOldProc:DWORD ;修改之前的内存属性 ;获取MessageBox地址,用过 user32 动态获取
invoke GetModuleHandle,offset g_szUser32 ;根据模块名获取模块地址 invoke GetProcAddress,eax,offset g_szMessageBoxA ;获取模块中指定函数名的地址
mov g_dwAddrMsgBox,eax ;保存函数地址 ;添加 jmp 到自己代码
;获取跳转偏移
mov ebx,offset MYMESSAGE ;要跳转的目标地址
sub ebx,g_dwAddrMsgBox ;获取 到标地址的偏移
sub ebx,5 ;减去jmp指定的长度(下一条指令地址) ;修改内存属性
invoke VirtualProtect, g_dwAddrMsgBox, 1, PAGE_EXECUTE_READWRITE, addr @dwOldProc ;修改指令
mov eax, g_dwAddrMsgBox
;改指令位跳转指令
mov byte ptr [eax],0e9H ;jmp
mov dword ptr [eax+1 ],ebx ;偏移值 ;还原内存属性
invoke VirtualProtect, g_dwAddrMsgBox, 1, @dwOldProc, addr @dwOldProc ret
InstallHook endp DllMain proc hinstDLL:HINSTANCE, fdwReason:DWORD, lpvReserved:LPVOID .if fdwReason == DLL_PROCESS_ATTACH ;如果dll被映射
;安装 dll
invoke InstallHook .endif mov eax, TRUE
ret
DllMain endp end DllMain

detours

微软自己有一套 hook api 的库

https://blog.csdn.net/fuhanghang/article/details/118309429

 

Win32汇编学习笔记06.APIHook的更多相关文章

  1. 汇编学习笔记(11)int指令和端口

    格式 int指令也是一种内中断指令,int指令的格式为int n,n是中断类型码.也就是说,使用int指令可以调用任意的中断例程,例如我们可以显示的调用0号中断例程,还记得在汇编学习笔记(10)中我们 ...

  2. Win32汇编学习(4):绘制文本

    这次,我们将学习如何在窗口的客户区"绘制"字符串.我们还将学习关于"设备环境"的概念. 理论: "绘制"字符串 Windows 中的文本是一 ...

  3. 机器学习实战(Machine Learning in Action)学习笔记————06.k-均值聚类算法(kMeans)学习笔记

    机器学习实战(Machine Learning in Action)学习笔记————06.k-均值聚类算法(kMeans)学习笔记 关键字:k-均值.kMeans.聚类.非监督学习作者:米仓山下时间: ...

  4. iOS学习笔记06—Category和Extension

    iOS学习笔记06—Category和Extension 一.概述 类别是一种为现有的类添加新方法的方式. 利用Objective-C的动态运行时分配机制,Category提供了一种比继承(inher ...

  5. 汇编学习笔记(3)[bx]和loop

    本文是<汇编语言>一书的学习笔记,对应书中的4-6章. 汇编程序的执行 要想将源代码变为可执行的程序需经过编译.连接两个步骤,WIN7操作系统下需要MASM程序来进行编译连接工作.将MAS ...

  6. Win32汇编学习(5):绘制文本2

    这次我们将学习有关文本的诸多属性如字体和颜色等. 理论: Windows 的颜色系统是用RGB值来表示的,R 代表红色,G 代表绿色,B 代表蓝色.如果您想指定一种颜色就必须给该颜色赋相关的 RGB ...

  7. Win32汇编学习(1):基本概念

    背景知识 Windows 把每一个 Win32 应用程序放到分开的虚拟地址空间中去运行,也就是说每一个应用程序都拥有其相互独立的 4GB 地址空间,当然这倒不是说它们都拥有 4GB 的物理地址空间,而 ...

  8. 罗云彬win32汇编教程笔记 子函数的声明, 定义与调用

    在主程序中用call指令来调用子程序. Win32汇编中的子程序也采用堆栈来传递参数,这样就可以用invoke伪指令来进行调用和语法检查工作. 一. 子程序的定义 子程序的定义方式如下所示. 子程序名 ...

  9. [Golang学习笔记] 06 程序实体3 类型断言和类型转换

    类型断言: 语法:<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 ) // 安全类型断言<目标类型的值> := <表达式>. ...

  10. stm32寄存器版学习笔记06 输入捕获(ETR脉冲计数)

    STM32外部脉冲ETR引脚:TIM1-->PA12;TIMER2-->PA0:TIMER3-->PD2;TIMER4-->PE0… 1.TIM2 PA0计数 配置步骤 ①开启 ...

随机推荐

  1. Vuex:让状态管理不再头疼的“管家”

    如果你正在开发一个 Vue.js 应用程序,但发现自己被各种组件之间的状态共享问题搞得焦头烂额,那么 Vuex 就是你需要的"超级管家".Vuex 是专门为 Vue.js 设计的状 ...

  2. 又一国产AI爆火!Manus强势炸场,邀请码申请方法,看这一篇就够了!

    3月6日凌晨,一款名为Manus的国产AI产品横空出世,迅速霸榜社交平台热搜.其内测邀请码在二手交易平台被炒至5万元天价,甚至出现标价10万元的卖家,我的个乖乖啊. 究竟是什么让Manus如此火爆?今 ...

  3. 【MIPS】内存小端存储与MARS显示

    1.小端存储方式:按字节逆序 以4Byte一个字为单位,其内按字节逆序排列 概念上-数据真值 机器码-内存存储 Mars显示 (+0) (+1) (+2) (+3) 0x12345678 0x78 0 ...

  4. [Qt 基础内容-05] QDialogButtonBox

    QDialogButtonBox 本文主要根据QT官方帮助文档以及日常使用,简单的介绍一下QDialogButtonBox的功能以及使用 文章目录 QDialogButtonBox 简介 信号和槽 基 ...

  5. Golang 入门 : 包名与导入路径

    math/rand包有一个Intn函数,可以生成一个随机数,所以我们需要导入math/rand.然后调用rand.Intn生成随机数. 等一下!Intn来自math/rand包,那为什么我们调用包的时 ...

  6. VS 2022 WEB发布编译失败

    VS2022当安装在非默认路径时,每次更新后,在发布时,就会出来编译失败的提示,比如这样: C:\VS2022\Preview\MSBuild\Microsoft\VisualStudio\v17.0 ...

  7. Netty源码—2.Reactor线程模型一

    大纲 1.关于NioEventLoop的问题整理 2.理解Reactor线程模型主要分三部分 3.NioEventLoop的创建 4.NioEventLoop的启动 1.关于NioEventLoop的 ...

  8. python API 之 fastapi

    为什么选择 FastAPI? 高性能:基于 Starlette 和 Uvicorn,支持异步请求处理 开发效率:自动交互文档.类型提示.代码自动补全 现代标准:兼容 OpenAPI 和 JSON Sc ...

  9. SpringSecurity5(13-核心组件和认证流程)

    SecurityContextHolder SecurityContextHolder 持有的是安全上下文的信息,当前操作的用户是谁,用户是否已经被认证,他拥有哪些角色权限等,这些都被保存在 Secu ...

  10. JavaScript Library – Embla Carousel

    前言 2022 年 4 月,我写了一篇 Swiper 介绍. Swiper 是当时前端最多人使用的 Slider 库,没有之一,一骑绝尘. 但是!时过境迁,这两年已经有一匹神秘的黑马悄悄杀上来了. 它 ...