羽夏逆向指引—— Hook
写在前面
此系列是本人一个字一个字码出来的,包括示例和实验截图。可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 羽夏逆向指引——序 ,方便学习本教程。
简述
在软件安全对抗方面,还是在外挂和反外挂方面,你可能经常听到Hook这个名词,中文翻译就是钩子。所谓挂钩子就是试图改变代码程序的原有流程,执行到自己的代码区域,这就是挂钩子的作用。在看本篇介绍之前,最好先来阅读 羽夏笔记——Hook攻防基础 和 羽夏笔记——硬编码(32位),以防下面相关操作可能有些看不懂,本篇讨论32位的,64位的实现是一样的。
Hook实现无非常用如下类似组合:
//组合1
jmp 0x4001200;
//组合2
push 0x4001200;
ret;
//组合3
sub esp,4;
mov [esp],0x4001200;
ret;
//组合4
call 0x4001200;
//组合5
mov eax,0x4001200;
jmp eax;
//组合6
mov eax,0x4001200;
call eax;
上面的地址都是我假设的我让流程跑到0x4001200这个地址。
但是,程序存的并不是上面的汇编代码,而是实实在在的硬编码。jmp分为长跳和短跳,它的汇编指令编码方式是不同的。如果直接跳转到目标地址,还需要特定的算法进行转化,由于在 羽夏笔记——硬编码(32位) 介绍了,这里我就不赘述了,我们来看看几个在3环比较常见的Hook以及它们的示例。
InlineHook
内联钩子,就是直接在程序不用的空间直接写硬编码。因为程序不可能是紧凑的,每一个函数都有一定的空间可以利用,我们可以看如下图:

如果你注入别人的程序需要保证有充分的空间来存储你的内联钩子代码。仅展示一下原理,我们就偷懒就Hook自己进程的函数,实验思路如下:
先看看我们Hook的地址的汇编情况:

我们用jmp来实现Hook,先看看受影响的汇编代码:

确认好受影响的汇编指令后,我们就可以写代码了:
#include <iostream>
#include <Windows.h>
using namespace std;
UINT HookAddr = 0;
char shellcode[] = { 0xE9,0,0,0,0 };
void __stdcall HookProc(LPCWSTR Caption, LPCWSTR Text)
{
wcout << "Capiton : " << Caption << endl << "Text : " << Text << endl;
}
void __declspec(naked) HookDispatcher()
{
_asm
{
/*执行 Hook 处理*/
mov eax, [esp + 0xC]; //Caption
mov ebx, [esp + 0x8]; //Text
push ebx;
push eax;
call HookProc;
/*补充被损坏的硬编码*/
push ebp;
mov ebp, esp;
/*回去执行*/
mov eax, [HookAddr];
add eax, 5;
jmp eax;
}
}
int main(int argc, char* argv[])
{
HMODULE lib = LoadLibrary(L"user32.dll");
if (lib)
{
FARPROC msgboxW = GetProcAddress(lib, "MessageBoxW");
if (msgboxW)
{
HookAddr = (UINT)msgboxW;
//构造 ShellCode
UINT dest = (UINT)HookDispatcher - HookAddr - 5;
memcpy_s(&shellcode[1], 4, &dest, sizeof(UINT));
if (WriteProcessMemory((HANDLE)-1, (LPVOID)HookAddr, shellcode, sizeof(shellcode), NULL))
{
MessageBoxW(NULL, L"This is the text!!!", L"Caption", MB_ICONINFORMATION); //调用测试
}
}
}
system("pause");
return 0;
}
这个实现的功能就是拦截字符串参数,只要程序调用了MessageBoxW函数,就会被拦截。
IATHook
对于Windows的可执行程序,调用系统的API并不是直接调用对应的函数地址,而是通过间接的方式来进行的,如下是实际情况:
push 40h
push offset string L"Caption" (0405220h)
push offset string L"This is the tex\x4000\0\0\0" (0405230h)
push 0
call dword ptr [__imp__MessageBoxW@16 (04050B0h)]
如果我们修改了这个地址,我们就可以实现对该函数的挂钩,由于这里需要PE结构的知识,所以请详细学习之后再回来看看这部分代码:
#include <iostream>
#include <Windows.h>
using namespace std;
typedef int (*WINAPI MsgBoxW)(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
MsgBoxW msgboxw;
void WINAPI HookProc(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
{
wcout << "Capiton : " << lpCaption << endl << "Text : " << lpText << endl;
msgboxw(hWnd, lpText, lpCaption, uType);
}
int main(int argc, char* argv[])
{
HMODULE lib = LoadLibrary(L"user32.dll");
if (!lib)
{
cout << "LoadLibrary Error!" << endl;
system("pause");
return 0;
}
msgboxw = (MsgBoxW)GetProcAddress(lib, "MessageBoxW");
if (!msgboxw)
{
cout << "GetProcAddress Error!" << endl;
system("pause");
return 0;
}
UINT pdos = (UINT)GetModuleHandle(NULL);
PIMAGE_NT_HEADERS pfile = (PIMAGE_NT_HEADERS)(pdos + ((PIMAGE_DOS_HEADER)pdos)->e_lfanew);
UINT optHeaderSize = pfile->FileHeader.SizeOfOptionalHeader;
auto iat = pfile->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
auto importd = (PIMAGE_IMPORT_DESCRIPTOR)(pdos + iat.VirtualAddress);
IMAGE_IMPORT_DESCRIPTOR emptyImport = { 0 };
for (;memcmp(&emptyImport, &importd, sizeof(IMAGE_IMPORT_DESCRIPTOR)); importd++)
{
auto item = *importd;
if (_stricmp((char*)(pdos + item.Name), "user32.dll")) //不区分大小写比较
continue;
auto othunk = (PIMAGE_THUNK_DATA)(pdos + item.OriginalFirstThunk);
IMAGE_THUNK_DATA emptyThunk = { 0 };
for (int i = 0; ; i++)
{
auto iitem = othunk[i];
if (!memcmp(&othunk[i], &emptyThunk, sizeof(IMAGE_THUNK_DATA)))
break;
if (iitem.u1.Ordinal & 0x80000000)
continue;
auto byname = (PIMAGE_IMPORT_BY_NAME)(pdos + iitem.u1.AddressOfData);
if (!strcmp((char*)&byname->Name, "MessageBoxW"))
{
auto thunk = (PIMAGE_THUNK_DATA)(pdos + item.FirstThunk);
DWORD old;
if (VirtualProtect(&thunk[i].u1.Function, sizeof(UINT), PAGE_READWRITE, &old))
{
thunk[i].u1.Function = (UINT)HookProc;
VirtualProtect(&thunk[i].u1.Function, sizeof(UINT), old, &old);
MessageBoxW(NULL, L"This is the text!!!", L"Caption", MB_ICONINFORMATION); //调用测试
}
goto EndProc;
}
}
}
EndProc:
system("pause");
return 0;
}
针对于如上的Hook,我们可以有一些反制措施,比如加载``PE完毕后直接抹掉INT表,因为这个表就没啥用处了,调用函数都是IAT`表。
虚表 Hook
在C++面向对象使用带有虚函数的就会有这东西,我们可以通过调试窗口的局部变量来观察情况:

具体的测试代码如下所示:
#include <iostream>
#include <Windows.h>
using namespace std;
void HookProc()
{
cout << "HookProc" <<endl;
}
class MyClass
{
public:
virtual void Test()
{
cout << "MyClass" << endl;
}
private:
};
class MyClassSub :MyClass
{
public:
void Test()
{
cout << "MyClassSub" << endl;
}
private:
};
int main(int argc, char* argv[])
{
MyClassSub* cls = new MyClassSub();
cls->Test();
UINT* vfptr = (UINT*)*(UINT*)cls;
DWORD old;
if (VirtualProtect(vfptr,sizeof(UINT),PAGE_READWRITE,&old))
{
*vfptr = (UINT)HookProc;
VirtualProtect(vfptr, sizeof(UINT), old, &old);
cls->Test();
}
system("pause");
return 0;
}
如果第二次输出的是HookProc,说明我们的虚表钩子实现成功。
异常 Hook
在外挂补丁层面,有一些基于异常实现的钩子。在这里我们实现硬件断点配合VEH实现挂钩:
#include <iostream>
#include <Windows.h>
using namespace std;
void HookProc()
{
cout << "HookProc" << endl;
}
void Proc()
{
cout << "Proc" << endl;
}
LONG NTAPI VECTORED_EXCEPTION_HANDLER(struct _EXCEPTION_POINTERS* ExceptionInfo)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode== EXCEPTION_SINGLE_STEP &&
ExceptionInfo->ExceptionRecord->ExceptionAddress == (PVOID)Proc)
{
ExceptionInfo->ContextRecord->Eip = (DWORD)HookProc;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
PVOID pveh;
DWORD WINAPI THREAD_START_ROUTINE(LPVOID lpThreadParameter)
{
auto tid = (DWORD)lpThreadParameter; //获取主进程的线程 ID
Proc(); //测试函数
pveh = AddVectoredExceptionHandler(1, VECTORED_EXCEPTION_HANDLER);
HANDLE hthread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
if (hthread)
{
CONTEXT context;
context.ContextFlags = CONTEXT_ALL;
SuspendThread(hthread);
GetThreadContext(hthread, &context);
context.Dr0 = (DWORD)Proc;
context.Dr7 |= 1;
SetThreadContext(hthread, &context);
ResumeThread(hthread);
CloseHandle(hthread);
}
return 0;
}
int main(int argc, char* argv[])
{
HANDLE hthread = CreateThread(NULL, NULL, THREAD_START_ROUTINE, (LPVOID)GetCurrentThreadId(), 0, NULL);
if (hthread)
{
WaitForSingleObject(hthread, -1);
Proc(); //调用测试
CloseHandle(hthread);
}
if (pveh) RemoveVectoredExceptionHandler(pveh);
system("pause");
return 0;
}
有关基于异常的钩子,我就介绍这么多。
下一篇
羽夏逆向指引——注入
羽夏逆向指引—— Hook的更多相关文章
- 羽夏逆向破解日记簿——RunAsDate的实现原理分析
前言 RunAsDate是一个小工具,允许您在指定的日期和时间运行程序,不过有人用它来破解有时间限制了.此实用程序不会更改计算机的当前系统日期和时间,但只会将指定的日期/时间注入所需的应用程序.该 ...
- 羽夏看Win系统内核——SourceInsight 配置 WRK
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...
- 羽夏看Win系统内核——系统调用篇
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...
- 羽夏看Win系统内核—— APC 篇
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...
- 跟羽夏学 Ghidra ——工具
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...
- 跟羽夏学 Ghidra ——初识
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...
- 跟羽夏学 Ghidra ——窗口
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...
- 跟羽夏学 Ghidra ——引用
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...
- 跟羽夏学 Ghidra ——导航
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...
随机推荐
- 痞子衡嵌入式:对比MbedTLS算法库纯软件实现与i.MXRT上DCP,CAAM硬件加速器实现性能差异
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是MbedTLS算法库纯软件实现与i.MXRT上DCP,CAAM硬件加速器实现性能差异. 近期有 i.MXRT 客户在集成 OTA SBL ...
- traceroute实用命令总结
在工作中,我们经常会使用到traceroute进行追踪路由,从而判断网络的故障点,或者网络策略限制等原因.对于网络排查traceroute还是有很大用处的. 一.traceroute常用命令参数 用法 ...
- 操作系统实验一:进程管理(含成功运行C语言源代码)
目录 操作系统实验一:进程管理 1.实验目的 2.实验内容 3.实验准备 3.1.1进程的含义 3.1.2进程的状态 3.1.3进程状态之间的转换 3.2 进程控制块PCB 3.2.1进程控制块的作用 ...
- IDEA中使用Docker
开发环境 IDEA:2020.3.2 Docker:20.10.12 注意,如果没有开启Docker远程连接,请先开启Docker远程连接. 1. 打开或新建一个Web项目 可参考使用IDEA新建一个 ...
- 【Java分享客栈】我为什么极力推荐XXL-JOB作为中小厂的分布式任务调度平台
前言 大家好,我是福隆苑居士,今天给大家聊聊XXL-JOB的使用. XXL-JOB是本人呆过的三家公司都使用到的分布式任务调度平台,前两家都是服务于传统行业(某大型移动基地和某大型电网),现在 ...
- 【性能测试实战:jmeter+k8s+微服务+skywalking+efk】系列之:性能监控、分析、调优等
说明: 本文是基于虚拟机演示的,资源有限 skywalking中拓扑图 kubectl get po -A -owide 测试执行:单场景 查询礼品 jmeter -n -t gift.jmx -l ...
- 智能脚本工具(Smart scripts)测试应用
如果你是一位网络测试人员,您的工作中是否有出现过以下困扰呢? · 重复机械式的测试有时让你觉得工作是如此的枯燥乏味!· 只增不减的测试用例让你下班越来越晚!· 请求老板招人,人却永远不够用! 但值得庆 ...
- 强力推荐!五款能让你成为Excel“高手”的Excel插件
excel是大家日常生活中经常用到一款表格软件,虽然软件本身的功能已经非常齐全了,但是插件可以进一步加强软件的功能,为用户提供更好的体验,提高工作效率,需要的用户快来看看吧. 1.Smartbi 首推 ...
- 【C# 线程】IntPtr 类
IntPtr类由来 .NET提供了一个结构体System.IntPtr专门用来代表句柄或指针.句柄是对象的标识符,当调用这些API创建对象时,它们并不直接返回指向对象的指针,而是会返回一个32位或64 ...
- Linux经典100题及参考答案
转至:https://blog.csdn.net/yaoqiang2011/article/details/11908189 一.单选题 1. cron 后台常驻程序 (daemon) 用于: A. ...