首页 > 其他分享 >逆向学习之Hook技术

逆向学习之Hook技术

时间:2022-08-24 19:45:10浏览次数:81  
标签:逆向 函数 HOOK int 学习 Hook 地址 DWORD NULL

一、什么是HOOK

Hook是用来获取或者更改程序执行时的某些数据,或者更改程序执行流程的一种技术

Hook还有一种说法叫做“挂钩子”


二、HOOK的两种形式

修改函数代码

  • Inline Hook

修改函数地址

  • IAT HOOK
  • SSDT HOOK
  • IDT HOOK
  • EAT HOOK
  • IRP HOOK

三、IAT HOOK

简介

IAT表:全称Import Address Table,中文名为导入地址表

每一个进程都有这样一个IAT表,而这个IAT表存储着当前这个模块所用到的所有api函数地址

将程序拖入OD里,随便找一个调用系统api的函数,然后数据跟随内存地址,会发现这些api函数在IAT表里的位置都是挨在一起的

只要将这些函数的地址修改成指向我们自行设置的函数地址,就能实现所谓的IAT Hook

请添加图片描述

DLL代码

// dllmain.cpp : 定义 DLL 应用程序的入口点。"
#include <windows.h>


//创建与Messagebox相同的函数指针,注意要设置相同的函数参数
typedef int (WINAPI *PfnMsgA)(
	_In_opt_ HWND hWnd,
	_In_opt_ LPCSTR lpText,
	_In_opt_ LPCSTR lpCaption,
	_In_ UINT uType);

PfnMsgA g_OldPfnMsgA = nullptr;  //定义一个指向原先messagebox函数的空指针(nullptr)


//解析PE文件结构
HMODULE hModImageBase = GetModuleHandle(NULL); //获取当前的ImagBase(基址)
PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)(DWORD)hModImageBase; //获取DOS头
DWORD dwTemp = (DWORD)pDosHead + (DWORD)pDosHead->e_lfanew;
PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)dwTemp;  //获取NT头
PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)& pNtHead->FileHeader;  //获取标准PE头
PIMAGE_OPTIONAL_HEADER pOptHead = (PIMAGE_OPTIONAL_HEADER)& pNtHead->OptionalHeader;  //获取扩展PE头
DWORD dwExportLocal = pOptHead->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; //找到导入表的偏移(RVA)
PIMAGE_IMPORT_DESCRIPTOR   pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)GetModuleHandle(NULL) + dwExportLocal);  //获取导入表


//定义自己设置的Messagebox函数
int WINAPI MyMessageBox(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType)
{

	char szHookText[] = "Hook成功";  
	if (g_OldPfnMsgA != nullptr)
	{
		return g_OldPfnMsgA(hWnd, szHookText, lpCaption, uType);//调用以前的
	}
	return 0;
}

//设置IATHook
void SetIatHook()
{
	MessageBoxA(NULL, "开始进行HOOK", NULL, NULL);
	PVOID pHookAddress = nullptr;  //定义一个指向hook地址的空指针
	pHookAddress = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA"); //将要hook的地址指向Messagebox函数
	
	if (nullptr == pHookAddress)  
	{
		OutputDebugString(TEXT("获取函数地址失败"));   
		MessageBoxA(NULL, "获取函数地址失败HOOK", NULL, NULL);

		return;
	}

	g_OldPfnMsgA = (PfnMsgA)pHookAddress; //指向旧函数的指针.  
	
	

	//寻找IAT表的位置.
	
	PIMAGE_IMPORT_DESCRIPTOR   pCurrent = pImport;   
	DWORD *pFirstThunk; //导入表子表,也就是IAT存储函数地址的表.
	
	//遍历导入表
	while (pCurrent->Characteristics && pCurrent->FirstThunk != NULL)
	{
		dwTemp = pCurrent->FirstThunk + (DWORD)GetModuleHandle(NULL);//找到IAT表的偏移地址
		pFirstThunk = (DWORD *)dwTemp; //指向IAT表的指针
		while (*pFirstThunk != NULL)
		{	
			//遍历IAT表里的子表,若指针指向的是就函数的地址,则将其修改成我们的函数地址
			if (*pFirstThunk == (DWORD)g_OldPfnMsgA)
			{	
				DWORD oldProtected;
				VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected); //设置该内存区域属性为可写可读可执行
				dwTemp = (DWORD)MyMessageBox;
				memcpy(pFirstThunk, (DWORD *)&dwTemp, 4); //将旧函数地址修改成自己的函数地址
				VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected);
			}
			pFirstThunk++; //遍历IAT表
		}
		pCurrent++; //遍历导入表
	}

}


//恢复导入表
void UnIatHook()
{
	MessageBoxA(NULL, "开始进行HOOK", NULL, NULL);
	PVOID pHookAddress = nullptr;
	pHookAddress = MyMessageBox;
	if (nullptr == pHookAddress)
	{
		OutputDebugString(TEXT("获取函数地址失败"));
		MessageBoxA(NULL, "恢复函数地址失败HOOK", NULL, NULL);
		return;
	}

	PIMAGE_IMPORT_DESCRIPTOR   pCurrent = pImport;
	DWORD* pFirstThunk; //指向
	
	//遍历导入表
	while (pCurrent->Characteristics && pCurrent->FirstThunk != NULL)
	{
		dwTemp = pCurrent->FirstThunk + (DWORD)GetModuleHandle(NULL);
		pFirstThunk = (DWORD*)dwTemp; 
		while (*pFirstThunk != NULL)
		{
			//遍历子表
			if (*pFirstThunk == (DWORD)MyMessageBox) //如果是我们的函数地址.则
			{
				//找到要修改的导入表了,修改内存保护属性.写入我们新的函数地址.
				DWORD oldProtected;
				VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected);
				dwTemp = (DWORD)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
				memcpy(pFirstThunk, (DWORD*)& dwTemp, 4); //将变量中保存的函数地址拷贝到导入表中.
				VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected);
			}
			pFirstThunk++; //继续遍历.
		}
		pCurrent++; //每次是加一个导入表结构.
	}

}


BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		SetIatHook();
		break;
	case DLL_THREAD_ATTACH:
		break;
	case DLL_THREAD_DETACH:

		break;
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

四、Inline Hook

简介

Inline Hook又称为超级Hook, 比起IAT Hook, 它的实用性更强以及更加容易过检测

Inline Hook为什么被称为是修改函数代码的Hook呢? 简单来说Hook就是jmp, 之所以InlineHook能够实现任意地址的hook, 是因为可以通过jmp指令实现来回的跳转

例如, 你要在某个地址进行Hook, 那就将此地址的汇编指令修改成jmp, 跳转到你要执行的函数, 然后再jmp回到hook地址的下一行地址

下面有一个简单的实例给给大伙演示

一个简单的实例

首先用C语言创建一个简单的加法程序

#include<Windows.h>
#include<cstdio>

int ADD(int a,int b) {
	return a + b;
}


int main() {
	int a, b;
	scanf("%d,%d",&a, &b);
	printf("结果是:%d", ADD(a, b));
}

将程序拖入OD中,并且找到Add函数的call的地址

请添加图片描述

再此call处下个断点,然后运行程序输入数据,这里我们输入1,2,然后F7进入此call
请添加图片描述

进入call后查看右下角的堆栈窗口可以查看到我们输入的函数实参

请添加图片描述

如果将此call堆栈里的参数值给修改了,那么就可以实现修改函数返回值

首先在函数头部地址添加一个JMP汇编指令,跳转的地址是任意的,只要这个地址周围没有指令,这里以跳转到401045为例

请添加图片描述

红色部分的汇编指令就是我们自行创建的,以此来实现修改堆栈中的函数参数值

这里也要注意平衡堆栈,原先的call是有push ebpmov ebp,esp指令的,由于添加了jmp指令导致它们被覆盖,所以这里要添上

最后的jmp指令再跳转回到401003

请添加图片描述

最后程序运行结果为9,因为我们把参数从原先的1和2, 修改成了4和5

请添加图片描述


任意地址Hook

代码下载地址

链接:https://pan.baidu.com/s/1M2rPk2aNs74vIOyRl44n8A
提取码:c5ff

C++代码

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include <Windows.h>
#include <stdio.h>
#define BEA_ENGINE_STATIC 
#define BEA_USE_STDCALL
#include "BeaEngine.h"   //反汇编引擎
#include<mmsystem.h>
#pragma comment(lib,"winmm.lib")
#pragma comment(lib,"BeaEngine.lib")
#pragma comment(lib, "legacy_stdio_definitions.lib")


int gJmpRetAddress;  //跳转返回到的地址

int gHookAddress;  //要被HOOK的地址

int gLen;  //HOOK代码写入后被覆盖掉的字节数

byte gOldArray[256] = { 0 };    //用于存储HOOK代码写入后被覆盖掉的字节数组


void __declspec(naked) MyFunction()
{


	//打开浏览器
	ShellExecute(NULL, "open", "https://www.baidu.com", NULL, NULL, SW_SHOWNORMAL);
	
	//恢复原始字节
	for (size_t i = 0; i < gLen; i++)
	{
		*((byte*)(gHookAddress + i)) = gOldArray[i];
	}
	_asm jmp gHookAddress
}

int CalcJmpAddress(int targetAddress, int hookAddress)
{
	return targetAddress - hookAddress - 5;
}



//任意地址HOOK函数
void SupperHook(int hookAddress)
{	
	DISASM disam;  //定义一个反汇编引擎
	disam.EIP = hookAddress;  //将当前EIP设置成hook的地址
	gLen = Disasm(&disam);  //当前EIP的硬编码字节数
	while (gLen < 5)  
	{
		disam.EIP += gLen;
		gLen += Disasm(&disam);
	}

	memcpy_s(gOldArray, gLen, (void*)hookAddress, gLen);  //
	
	//计算HOOK执行完毕后jmp返回的地址
	gJmpRetAddress = hookAddress + gLen;
	//得到HOOK需要跳转到的函数地址
	int targetAddress = (int)&MyFunction;
	//计算jmp xxxxxxxx这个指令中的xxxxxxxx的值
	//计算公式:目标地址-源地址-5
	int value = CalcJmpAddress(targetAddress, hookAddress);

	//修改内存页属性为可读可写可执行
	DWORD oldProtect;
	VirtualProtect((LPVOID)hookAddress, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtect);
	//将要HOOK的地址第一个字节改成Jmp
	*((byte*)hookAddress) = 0xE9;
	//将跳转目标地址写入
	*((int*)(hookAddress + 1)) = value;
}



DWORD WINAPI Thread(_In_ LPVOID lpParameter) {
	int hookAddress;
	AllocConsole();  //创建控制台
	freopen("CONIN$", "r+t", stdin); //将stdin设备输入的数据重定向至控制台输入
	freopen("CONOUT$", "w+t", stdout); //将stdin设备输出的数据重定向至控制台输出
	while (true)
	{
		printf("请输入要HOOK的地址:");
		scanf("%08X", &hookAddress);
		//执行HOOK
		SupperHook(hookAddress);
		printf("HOOK执行完毕\n\n");
	}
	return true;
	SupperHook(hookAddress);
	return true;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
	case DLL_PROCESS_ATTACH: {
		HANDLE hTread = CreateThread(NULL, 0, Thread, (LPVOID)NULL, 0, NULL);
		
		
	}
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}



易语言代码

请添加图片描述

操作演示

这里就用C++代码的hook来作演示

首先新建一个测试dll注入的易语言程序, , 并将此程序放到InlineHook.dll的所处目录

请添加图片描述

请添加图片描述

将testdll.exe拖入OD中, 跳转到按钮时间特征码处, 这里就用按钮时间特征码的起始地址来作Hook的地址

请添加图片描述

运行testdll.exe程序, 输入要hook的地址后, 再返回OD查看hook地址的汇编指令变化,由原先的push ebp 变成了 jmp指令

请添加图片描述

请添加图片描述

返回testdll.exe程序界面, 点击按钮后触发Hook并执行我们自己的函数, 这里我的函数是打开浏览器

请添加图片描述

标签:逆向,函数,HOOK,int,学习,Hook,地址,DWORD,NULL
From: https://www.cnblogs.com/henry666/p/16621344.html

相关文章

  • 【Go学习】Ubuntu18.04执行localedef -i en_US -f UTF-8 en_US.UTF-8
    Ubuntu18.04执行localedef-ien_US-fUTF-8en_US.UTF-8报如下错误:[error]charactermapfile`UTF-8'notfound:Nosuchfileordirectory[error]cannotreadcha......
  • 2022-08-24 第四小组 王星苹 学习笔记
    学习心得自动类型推断数字numbervarv1=10; varv2=1.5;字符串stringvarv3="你好";varv4='我好';布尔型boolean   ......
  • 2022-08-24 第二组刘禹彤 学习笔记
    打卡39天  ###学习内容JavaScript脚本语言,解释型,主要用来给HTML网页增加动态功能,通常的JS是运行在浏览器环境下的JS的两种模型DOM文档对象模型(document)BOM浏......
  • 线性代数学习笔记
    线性代数学习笔记前置知识向量:既有大小又有方向的量称为向量。1.矩阵1.1定义和性质:由$n\timesm$个数字构成的n行m列的数表$$\begin{pmatrix}a_{11}&a_{12}......
  • MySQL学习(3)---MySQL常用命令
    ps:此随笔基于mysql5.7.*版本。已知root账户密码进行登录格式:mysql[-h地址][-p端口]-u用户名-p密码省略不写地址或端口则自动使用默认。(地址:localhost;端口:3306)......
  • Git基础学习
    Git安装与配置安装好之后打开bash.exe配置用户名和邮箱用户名gitconfig--globaluser.name"LLY"邮箱[email protected]......
  • ArrayList学习
    核心源码packagejava.util;importjava.util.function.Consumer;importjava.util.function.Predicate;importjava.util.function.UnaryOperator;publiccla......
  • Android 学习笔记1
    Android学习笔记1需求:1.按钮响应、文本更新2.动态注册广播,实现接收系统分钟广播,跳转界面3.在子线程中实现倒计时1分钟4.将Activity与Service绑定、解绑,开关Service服......
  • [ROS学习]4.ROS命令行工具
    笔记参考:【ROS学习笔记】4.玩转小海龟——ROS命令行工具基于B站ROS公开课:【古月居】古月·ROS入门21讲基于Ubuntu20.04.1、Noetic版本1回顾第一个小海龟程序打开小海......
  • Ladon代码学习
    代码地址https://github.com/k8gege/LadonGo按照Ladon的readme功能介绍来学习win环境需要先安装gcc https://blog.csdn.net/yvge669/article/details/124564622一,信息......