通用hook拦截所有API的实现

Posted `

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通用hook拦截所有API的实现相关的知识,希望对你有一定的参考价值。

通用hook拦截所有API的实现

实现一个通用hook,可以对当前进程的所有函数调用进行拦截查看和统计。 类似程序:API Monitor

可以对某一进程的所有api调用进行拦截,可以获得的调用函数返回值,获得详细的参数值

现在尝试在windows 32位下实现一个类似API Monitor的程序。

当要hook一个函数,通常我们会创建一个参数相符的detour fake函数,当然还有跳板函数,但这样有一个问题,程序内部的api几百几千,甚至可能上万,不可能每一个函数都去写一个对应的参数相同调用方式相同的detour函数,那样做太蠢了。

但可以尝试实现一个通用hook,这个通用hook关键在于实现一个通用的detour函数,所有的api函数的跳转都指向这个通用的detour函数,通用的detour函数既能获取到所有参数,也能去调用原函数,同时还能获取到原调用函数的返回值。

话不多说,直接上核心代码。


当任意hook目标的函数执行跳转到这个`通用detour function`函数。

=========通用 detour function================

pushad//pushad压入当前8个寄存器值到栈  EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI

//寻找ip 这个ip是call压栈的ip,就是pushad上一条指令,所以栈esp+32
mov eax, dword ptr[esp + 0x20]//0x20 =》32

//开始调用GetCallParameter32

push eax//|GetCallParameter32 参数4
push esp//这时候esp已近加了个8个压栈 所以栈顶是esp +32  |GetCallParameter32 参数3
push 0xff //fun_index |GetCallParameter32 参数2
push 0xff //module_index |GetCallParameter32 参数1

call GetCallParameter32//e8   4b bc fd ff


popad//从栈中还原由pushad压入的8个寄存器,这时候寄存器与原调用函数时候无异

add esp, 4//类似 pop ip,pop ip出去 抵消一个call,直接用原来的push参数

call trampoline//此时再调用跳板函数  执行 原函数

push eax//将原函数return值得到的结果压栈一份
push edx//将原函数return值压栈一份 32位下的64位返回值也要支持,所以需要edx


sub esp, 8
fstp qword ptr[esp] //将浮点数寄存器压栈一份 因为有可能原函数返回浮点数 SetLastCallResult32参数3

push eax//再将得到的结果传参 SetLastCallResult32参数2
push edx//再将得到的结果传参 SetLastCallResult32参数1

call SetLastCallResult32//调用SetLastCallResult32 获取函数执行返回值

mov ecx, eax//SetLastCallResult325返回值是32位跳转地址,存放在ecx


//恢复目标函数的参数返回

sub esp, 8//尝试用原来压栈的浮点数??
fld qword ptr[esp]//取出浮点数
add esp, 8

pop edx //获取压栈的eax
pop eax //获取压栈的eax

push ecx //返回的是ret ip 还原到原来的ret
ret//ret 会跳转到刚存的ecx地址

这个通用的detour函数在内存中动态创建,这样就能指定fun_index module_index trampoline的任意值,还需要两个c++函数GetCallParameter32 SetLastCallResult32去辅助完成功能。

通用detour逻辑流程梳理===

pushad保存当前主要寄存器。寻找ret ip

调用GetCallParameter32这个函数,GetCallParameter32会去通过esp获取原函数调用的所有参数。

通过popad恢复寄存器,再恢复栈到参数水平,call 原函数

保存原函数返回值。调用SetLastCallResult32SetLastCallResult32就得到了原函数的返回值了。

再重新设置返回值到寄存器,然后通过ret ip回到原调用地址。

struct CallContext
{
	UINT_PTR ret_ip;
	HookEntryInfo* hook_entry = NULL;
};

//使用thread_local是为了存储调用对象,GetCallParameter32开始SetLastCallResult32结束,
//使用stack是为了某些函数在互相调用,用stack可以保证顺序
thread_local std::stack<CallContext*> hook_stack_list;

//在这里获取目标函数的调用信息,可以从module_index和fun_index,确切知道是那个函数在调用,
//可以从_stack这个esp栈指针,获得函数调用的参数信息
void __stdcall GetCallParameter32(uint32_t module_index, uint32_t fun_index, void* _stack, UINT_PTR ret_ip)
{
	unsigned char* esp_stack = (unsigned char*)_stack;

	ModuleInfo* minfo = module_list[module_index];

	HookEntryInfo* einfo = minfo->entry_list[fun_index];

	log_printA("GetCallParameter32: \\n module_index [%u] %s fun_index [%u] %s \\n_stack [%08x] ret_ip: 0x%08x \\n",
		module_index, minfo->dll_name.c_str(),
		fun_index, einfo->func_name.c_str(),
		_stack,
		ret_ip);


	CallContext* tls_hook_context = new CallContext();
	tls_hook_context->ret_ip = ret_ip;
	tls_hook_context->hook_entry = einfo;

	hook_stack_list.push(tls_hook_context);

	return;

}

//目标函数执行完会执行这个函数,可以获取函数执行返回值eax,当返回int64edx也有值,当返回浮点数,值就在return_sto
UINT_PTR __stdcall SetLastCallResult32(int edx, int eax, double return_sto)
{
	CallContext* tls_hook_context = hook_stack_list.top();
	hook_stack_list.pop();

	log_printA("SetLastCallResult32: %s eax: %d edx: %d sto: %f",
		tls_hook_context->hook_entry->func_name.c_str(),
		eax, edx, return_sto);

	UINT_PTR ret_ip = tls_hook_context->ret_ip;

	delete tls_hook_context;

	return ret_ip;
}

不多说,直接上完整的核心代码:=


<此程序只支持32位>

#include "ZEncode.h"//wchar char转换
#include "MinHook.h"

#include <TlHelp32.h>
#include <stack>
#include <unordered_map>

#if defined _M_X64

#elif defined _M_IX86
#pragma comment(lib, "libMinHook.x86.lib")
#endif

void log_printW(const wchar_t* format, ...) {
	const int logsize = 2 * 1024;
	wchar_t buffer[logsize], * p = buffer;
	va_list args;
	va_start(args, format);
	p += _vsnwprintf_s(buffer, logsize, format, args);
	va_end(args);
	while (*--p == L\'\\n\') {
	}
	*++p = L\'\\n\';
	*++p = L\'\\0\';

	fputws(buffer, stdout);
}


void log_printA(const char* format, ...) {
	const int logsize = 2 * 1024;
	char buffer[logsize], * p = buffer;
	va_list args;
	va_start(args, format);
	p += _vsnprintf_s(buffer, logsize, format, args);
	va_end(args);
	while (*--p == L\'\\n\') {
	}
	*++p = L\'\\n\';
	*++p = L\'\\0\';

	puts(buffer);
}

class QPCtime {
public:
	QPCtime() {
		if (!QueryPerformanceFrequency(&Frequency)) {
			log_printA("QueryPerformanceFrequency false");
		}
	}

	~QPCtime() {
	}

	int64_t now_us() {
		return now_ns() / 1000;
	}

	int64_t now_ms() {
		return now_ns() / 1000000;
	}

	int64_t now_ns() {
		LARGE_INTEGER li;
		QueryPerformanceCounter(&li);

		const long long _Whole = (li.QuadPart / Frequency.QuadPart) * 1000000000;
		const long long _Part = (li.QuadPart % Frequency.QuadPart) * 1000000000 / Frequency.QuadPart;

		return _Whole + _Part;
	}

private:
	LARGE_INTEGER Frequency;


};


void __stdcall GetCallParameter32(uint32_t module_index, uint32_t fun_index, void* _stack, UINT_PTR ret_ip);
UINT_PTR __stdcall SetLastCallResult32(int edx, int eax, double return_sto);

struct HookAsmCodeSell
{
	unsigned char* asm_code = NULL;

	int asm_code_size = 0;

	void SetValue(uint32_t module_index, uint32_t fun_index, UINT_PTR TrampolineCall)
	{
		if (asm_code == NULL) {
			log_printA("HookAsmCodeSell SetValue hook_shell_code==null");
			return;
		}

		uint32_t _getcallparameter = (uint32_t)&GetCallParameter32;
		uint32_t _setlastcallresult32 = (uint32_t)&SetLastCallResult32;

		//偏移地址 = 目的地址 - 跳转基地址(的下一条指令的地址)

		*(uint32_t*)(asm_code + 8) = fun_index;
		*(uint32_t*)(asm_code + 13) = module_index;
		*(uint32_t*)(asm_code + 18) =
			_getcallparameter - (uint32_t)(asm_code + 18 + 4);//GetCallParameter

		*(uint32_t*)(asm_code + 27) =
			TrampolineCall - (uint32_t)(asm_code + 27 + 4);;//old fun func_ttttt

		*(uint32_t*)(asm_code + 42) =
			_setlastcallresult32 - (uint32_t)(asm_code + 42 + 4);//SetLastCallResult32

	}

	bool AllocShell()
	{
		unsigned char shellcode_32[] =
		{

			0x60,                  // pushad  


			0x8B, 0x44, 0x24 ,0x20,         // mov         eax,dword ptr [esp+20h]  
			0x50,                 // push        eax   这是ret ip

			0x54,                  // push        esp  
			0x68,  0x00, 0x00, 0x00, 0x00,      // push       fun_index
			0x68,  0x00, 0x00, 0x00, 0x00,      // push       module_index
			0xE8, 0x00, 0x00, 0x00, 0x00,     // call        GetCallParameter (04F2CBAh)  
			0x61,                  // popad  

			//

			0x83, 0xC4 ,0x04 ,           // add         esp,4   抵消一个call,直接用原来的push参数 

			0xE8, 0x00, 0x00, 0x00, 0x00,      // call        func_ttttt (04F2D55h)  

			//保存返回值到栈
			0x50,                  // push        eax  
			0x52,                  // push        edx  
			0x83, 0xEC ,0x08,            // sub         esp,8  
			0xDD, 0x1C ,0x24,            // fstp        qword ptr [esp]  

			0x50,                  // push        eax  
			0x52,                  // push        edx  
			0xE8, 0x00, 0x00, 0x00, 0x00,      // call        SetLastCallResult32 (04F2D19h)  

			0x8B, 0xC8  ,             // mov         ecx,eax  

			//尝试获取原来压栈的8字节浮点数
			0x83, 0xEC, 0x08 ,           // sub         esp,8  
			0xDD, 0x04, 0x24 ,           // fld         qword ptr [esp]  
			0x83, 0xC4 ,0x08 ,           // add         esp,8  

			//原来的eax edx返回值
			0x5A,                  // pop         edx  
			0x58,                  // pop         eax  

			//原来的返回ip
			0x51,                  // push        ecx  
			0xC3,                  // ret  
		};

		*(uint32_t*)(&shellcode_32[8]) = 0x0;
		*(uint32_t*)(&shellcode_32[13]) = 0x0;
		*(uint32_t*)(&shellcode_32[18]) = 0xffffffff;//GetCallParameter
		*(uint32_t*)(&shellcode_32[27]) = 0xffffffff;//old fun func_ttttt
		*(uint32_t*)(&shellcode_32[42]) = 0xffffffff;//SetLastCallResult32

		asm_code_size = sizeof(shellcode_32);
		asm_code = (unsigned char*)VirtualAlloc(NULL, asm_code_size + 4,
			MEM_COMMIT, PAGE_EXECUTE_READWRITE);
		if (asm_code == NULL) {
			log_printA("VirtualAlloc shellcode_32 NULL!!");
			return false;
		}

		memcpy(asm_code, shellcode_32, asm_code_size);
		return true;
	}

	void Free()
	{
		if (asm_code != NULL)
			VirtualFree(asm_code, 0, MEM_RELEASE);
		asm_code = NULL;
	}

};


struct HookEntryInfo
{
	int index = 0;
	int module_index = 0;
	std::string func_name;

	UINT_PTR func_addr = 0;
	UINT_PTR rva_addr = 0;
	UINT_PTR trampoline = 0;

	bool enable_hook = false;

	HookAsmCodeSell hook_shell;


};

struct ModuleInfo
{
	int index = 0;
	UINT_PTR base_addr = 0;
	std::string dll_name;
	std::string dll_name_path;

	std::vector<HookEntryInfo*> entry_list;

	std::unordered_map<std::string, HookEntryInfo*>  entry_list_dic;//存一份dic 加速查找
};

std::vector<ModuleInfo*> module_list;//存储所有module信息

std::unordered_map<std::string, ModuleInfo*>   module_list_dic;//为了加速查找的


struct CallContext
{
	UINT_PTR ret_ip;
	HookEntryInfo* hook_entry = NULL;
	int64_t start_time;
	int64_t end_time;
};

QPCtime qpc;

//使用thread_local是为了存储调用对象,GetCallParameter32开始SetLastCallResult32结束,
//使用stack是为了某些函数在互相调用,用stack可以保证顺序
thread_local std::stack<CallContext*> hook_stack_list;

//在这里获取目标函数的调用信息,可以从module_index和fun_index,确切知道是那个函数在调用,
//可以从_stack这个esp栈指针,获得函数调用的参数信息
void __stdcall GetCallParameter32(uint32_t module_index, uint32_t fun_index, void* _stack, UINT_PTR ret_ip)
{
	unsigned char* esp_stack = (unsigned char*)_stack;
	esp_stack = esp_stack + 8 * 4 + 4 + 4;//pushad  +push [ret_ip] and call

	ModuleInfo* minfo = module_list[module_index];

	HookEntryInfo* einfo = minfo->entry_list[fun_index];

	log_printA("GetCallParameter32: \\n module_index [%u] %s fun_index [%u] %s \\n_stack [%08x] ret_ip: 0x%08x ",
		module_index, minfo->dll_name.c_str(),
		fun_index, einfo->func_name.c_str(),
		_stack,
		ret_ip);

	//通过esp获取参数的demo
	if (einfo->func_name.compare("sendto") == 0) {

		/*int PASCAL FAR sendto (
					   _In_ SOCKET s,
					   _In_reads_bytes_(len) const char FAR * buf,
					   _In_ int len,
					   _In_ int flags,
					   _In_reads_bytes_opt_(tolen) const struct sockaddr FAR *to,
					   _In_ int tolen);*/


					   //sendto是__stdcall,从右到左压栈,所以,栈顶是最左边的参数,为顺序

		char* send_data = *(char**)(esp_stack + 1 * 4);//顺数第2个参数

		int send_data_len = *(int*)(esp_stack + 2 * 4);//顺数第3个参数

		std::string send_data_str(send_data, send_data_len);

		log_printA("sendto parameter: send data:%s data len:%d", send_data_str.c_str(), send_data_len);

	}

	//通过esp获取参数的demo
	if (einfo->func_name.compare("MessageBoxW") == 0) {

		/*int
WINAPI
MessageBoxW(
	_In_opt_ HWND hWnd,
	_In_opt_ LPCWSTR lpText,
	_In_opt_ LPCWSTR lpCaption,
	_In_ UINT uType);*/


	//MessageBoxW是__stdcall,从右到左压栈,所以,栈顶是最左边的参数,为顺序

		wchar_t* lpText = *(wchar_t**)(esp_stack + 1 * 4);//顺数第2个参数

		wchar_t* lpCaption = *(wchar_t**)(esp_stack + 2 * 4);//顺数第3个参数

		log_printW(L"MessageBoxW parameter: lpText:%s  lpCaption:%s", lpText, lpCaption);

		lpCaption[0] = L\'A\';
		lpCaption[1] = L\'B\';
		lpCaption[2] = L\'C\';

		lpText[0] = L\'=\';
		lpText[1] = L\'=\';
		lpText[2] = L\'=\';
	}


	CallContext* tls_hook_context = new CallContext();
	tls_hook_context->ret_ip = ret_ip;
	tls_hook_context->hook_entry = einfo;

	hook_stack_list.push(tls_hook_context);

	tls_hook_context->start_time = qpc.now_ns();
	return;

}

//目标函数执行完会执行这个函数,可以获取函数执行返回值eax,当返回int64edx也有值,当返回浮点数,值就在return_sto
UINT_PTR __stdcall SetLastCallResult32(int edx, int eax, double return_sto)
{
	CallContext* tls_hook_context = hook_stack_list.top();
	hook_stack_list.pop();
	tls_hook_context->end_time = qpc.now_ns();

	log_printA("SetLastCallResult32: %s eax: %d edx: %d sto: %f use time: %I64d ns",
		tls_hook_context->hook_entry->func_name.c_str(),
		eax, edx, return_sto, tls_hook_context->end_time - tls_hook_context->start_time);

	UINT_PTR ret_ip = tls_hook_context->ret_ip;

	delete tls_hook_context;

	return ret_ip;
}

//加载模块的所有导出函数
void LoadModuleExportFuncs(ModuleInfo* dll_module)
{
	const char* lpImage = (const char*)dll_module->base_addr;

	PIMAGE_DOS_HEADER imDH = (PIMAGE_DOS_HEADER)lpImage;
	PIMAGE_NT_HEADERS imNH = (PIMAGE_NT_HEADERS)((char*)lpImage + imDH->e_lfanew);
	DWORD exportRVA = imNH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
	PIMAGE_EXPORT_DIRECTORY imED = (PIMAGE_EXPORT_DIRECTORY)(lpImage + exportRVA);
	//long pExportSize = imNH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;

	PWORD lpOrdinals = imED->AddressOfNameOrdinals ? (PWORD)(lpImage + imED->AddressOfNameOrdinals) : 0;
	PDWORD lpNames = imED->AddressOfNames ? (PDWORD)(lpImage + imED->AddressOfNames) : 0;
	PDWORD lpRvas = (PDWORD)(lpImage + imED->AddressOfFunctions);

	PIMAGE_SECTION_HEADER ish = (PIMAGE_SECTION_HEADER)(imNH + 1);
	int nsec = imNH->FileHeader.NumberOfSections;

	for (DWORD i = 0; i < imED->NumberOfFunctions; ++i)
	{
		DWORD rvafunc = lpRvas[i];

		DWORD oftName = 0;
		if (lpNames && lpOrdinals)
		{
			for (DWORD k = 0; k < imED->NumberOfNames; ++k)
			{
				if (lpOrdinals[k] == i)
				{
					oftName = lpNames[k];
					break;
				}
			}
		}

		std::string fun_name;
		UINT_PTR fun_addr = (UINT_PTR)lpImage + rvafunc;

		if (oftName) {
			fun_name = (char*)(lpImage + oftName);
		}
		else {
			//没名字的不要了
			continue;
		}

		HookEntryInfo* hook_entry = new HookEntryInfo();
		hook_entry->func_name = fun_name;
		hook_entry->func_addr = fun_addr;
		hook_entry->rva_addr = rvafunc;

		dll_module->entry_list.push_back(hook_entry);

		hook_entry->index = dll_module->entry_list.size() - 1;

		hook_entry->module_index = dll_module->index;

		dll_module->entry_list_dic[hook_entry->func_name] = hook_entry;
	}
}

//加载模块
void LoadModule(UINT_PTR base_addr, WCHAR* dll_name, WCHAR* dll_path)
{
	std::wstring w_dll_name = dll_name;
	std::wstring w_dll_path = dll_path;

	for (auto& str : w_dll_name) {
		str = ::towlower(str);
	}
	for (auto& str : w_dll_path) {
		str = ::towlower(str);
	}

	ModuleInfo* add_module_info = new ModuleInfo();

	add_module_info->base_addr = base_addr;
	add_module_info->dll_name = WCHAR_TO_ANSI(w_dll_name);
	add_module_info->dll_name_path = WCHAR_TO_ANSI(w_dll_path);

	module_list.push_back(add_module_info);
	add_module_info->index = module_list.size() - 1;

	module_list_dic[add_module_info->dll_name] = add_module_info;

	LoadModuleExportFuncs(add_module_info);
}

//扫描进程现在所有的模块
void ScanModule()
{
	HANDLE hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
	if (hModuleSnap == INVALID_HANDLE_VALUE) {
		log_printA("CreateToolhelp32Snapshot failed.");
		return;
	}

	MODULEENTRY32  me32;
	me32.dwSize = sizeof(MODULEENTRY32);
	if (!Module32First(hModuleSnap, &me32)) {
		log_printA("Module32First failed.");
		return;
	}

	do
	{
		LoadModule((UINT_PTR)me32.hModule, me32.szModule, me32.szExePath);
		log_printW(L"0x%08lX %s %s", me32.hModule, me32.szModule, me32.szExePath);

	} while (Module32Next(hModuleSnap, &me32));

	CloseHandle(hModuleSnap);
}


void MonitorAPI(const char* _dll_name, const char* func_name)
{
	std::string dll_name = _dll_name;
	for (auto& str : dll_name) {
		str = ::tolower(str);
	}

	auto find_module_iter = module_list_dic.find(dll_name);
	if (find_module_iter == module_list_dic.end()) {
		log_printA("not find dll %s", dll_name);
		return;
	}

	ModuleInfo* minfo = find_module_iter->second;

	auto find_api_iter = minfo->entry_list_dic.find(func_name);
	if (find_api_iter == minfo->entry_list_dic.end()) {
		log_printA("not find func_name %s", func_name);
		return;
	}

	HookEntryInfo* einfo = find_api_iter->second;

	einfo->hook_shell.AllocShell();

	if (MH_CreateHook((LPVOID)einfo->func_addr, einfo->hook_shell.asm_code,
		reinterpret_cast<LPVOID*>(&einfo->trampoline)) != MH_OK) {
		log_printA("MH_CreateHook false");
		return;
	}

	einfo->hook_shell.SetValue(einfo->module_index,
		einfo->index,
		(UINT_PTR)einfo->trampoline);

	einfo->enable_hook = true;

	log_printA("MonitorAPI %s success", func_name);
}

//测试用
extern "C" int64_t _declspec(dllexport) int64test(int64_t a, int64_t b)
{
	return a + b;
};

extern "C" float _declspec(dllexport) floattest(float a, float b)
{
	return a + b;
};

extern "C" double _declspec(dllexport) doubletest(double a, double b)
{
	return a + b;
};


int main()
{
	ScanModule();

	int value = 0;
	if (MH_Initialize() != MH_OK) {
		log_printA("MH_Initialize false");
		return 0;
	}

	//====随便添加要监控的任意函数============

	MonitorAPI("user32.dll", "MessageBoxW");

	MonitorAPI("kernel32.dll", "CreateFileA");
	MonitorAPI("kernel32.dll", "WriteFile");

	MonitorAPI("ws2_32.dll", "socket");
	MonitorAPI("ws2_32.dll", "sendto");
	MonitorAPI("ws2_32.dll", "closesocket");

    //myapp.exe是当前程序名称,这里测试上面那几个导出函数
	MonitorAPI("myapp.exe", "int64test");
	MonitorAPI("myapp.exe", "floattest");
	MonitorAPI("myapp.exe", "doubletest");


	//启用所有hook
	if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK) {
		log_printA("MH_EnableHook(MH_ALL_HOOKS) false");
		return 1;
	}

	//==========开始测试,执行下面的函数,都会被拦截监控到===============
	wchar_t lpText[50] = L"888--this messagebox text.";
	wchar_t lpCaption[30] = L"XXX-caption";

	MessageBoxW(NULL, lpText, lpCaption, MB_OK);

	HANDLE hFile = CreateFileA("one.txt",
		GENERIC_READ,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);
	if (hFile != INVALID_HANDLE_VALUE) {

		char buff[256] = "this WriteFile context.";
		DWORD dwWrite;
		WriteFile(hFile, &buff, strlen(buff), &dwWrite, NULL);
		CloseHandle(hFile);
	}


#pragma comment(lib,"ws2_32.lib")

	SOCKET m_sock = socket(AF_INET, SOCK_DGRAM, 0);

	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));

	char sendstr[256] = "this sendto data.";
	sendto(m_sock, sendstr, strlen(sendstr), 0, (struct sockaddr*)&addr, sizeof(addr));
	closesocket(m_sock);

	int64_t i64v = int64test(UINT32_MAX, 123);
	float fv = floattest(1.123, 2.00);
	double dv = doubletest(1.123, 2.00);


	//=======结束,移除所有hook========
	MH_DisableHook(MH_ALL_HOOKS);

	MH_Uninitialize();
}


输出:

0x00520000 myapp.exe D:\\Arma3_Read\\Debug\\myapp.exe
0x77E20000 ntdll.dll C:\\Windows\\SYSTEM32\\ntdll.dll
0x77430000 KERNEL32.DLL C:\\Windows\\System32\\KERNEL32.DLL
0x75CD0000 KERNELBASE.dll C:\\Windows\\System32\\KERNELBASE.dll
0x76CF0000 USER32.dll C:\\Windows\\System32\\USER32.dll
0x77B70000 win32u.dll C:\\Windows\\System32\\win32u.dll
0x77260000 GDI32.dll C:\\Windows\\System32\\GDI32.dll
0x77180000 gdi32full.dll C:\\Windows\\System32\\gdi32full.dll
0x779F0000 msvcp_win.dll C:\\Windows\\System32\\msvcp_win.dll
0x77880000 ucrtbase.dll C:\\Windows\\System32\\ucrtbase.dll
0x77810000 WS2_32.dll C:\\Windows\\System32\\WS2_32.dll
0x77730000 RPCRT4.dll C:\\Windows\\System32\\RPCRT4.dll
0x79890000 MSVCP140D.dll C:\\Windows\\SYSTEM32\\MSVCP140D.dll
0x796F0000 VCRUNTIME140D.dll C:\\Windows\\SYSTEM32\\VCRUNTIME140D.dll
0x79710000 ucrtbased.dll C:\\Windows\\SYSTEM32\\ucrtbased.dll
0x77700000 IMM32.DLL C:\\Windows\\System32\\IMM32.DLL
MonitorAPI MessageBoxW success

MonitorAPI CreateFileA success

MonitorAPI WriteFile success

MonitorAPI socket success

MonitorAPI sendto success

MonitorAPI closesocket success

MonitorAPI int64test success

MonitorAPI floattest success

MonitorAPI doubletest success

GetCallParameter32:
 module_index [4] user32.dll fun_index [645] MessageBoxW
_stack [003ef3e4] ret_ip: 0x00553b65

MessageBoxW parameter: lpText:888--this messagebox text.  lpCaption:XXX-caption
GetCallParameter32:
 module_index [2] kernel32.dll fun_index [1559] WriteFile
_stack [0683ef3c] ret_ip: 0x101266ae

SetLastCallResult32: WriteFile eax: 1 edx: 109309756 sto: -nan(ind) use time: 407400 ns

GetCallParameter32:
 module_index [2] kernel32.dll fun_index [1559] WriteFile
_stack [0683ef3c] ret_ip: 0x101266ae

SetLastCallResult32: WriteFile eax: 1 edx: 109309756 sto: -nan(ind) use time: 417800 ns

GetCallParameter32:
 module_index [2] kernel32.dll fun_index [1559] WriteFile
_stack [0659ef0c] ret_ip: 0x101266ae

SetLastCallResult32: WriteFile eax: 1 edx: 106557196 sto: -nan(ind) use time: 349500 ns

GetCallParameter32:
 module_index [2] kernel32.dll fun_index [1559] WriteFile
_stack [0659ef0c] ret_ip: 0x101266ae

SetLastCallResult32: WriteFile eax: 1 edx: 106557196 sto: -nan(ind) use time: 139900 ns

SetLastCallResult32: MessageBoxW eax: 1 edx: 10420224 sto: -nan(ind) use time: 3345317900 ns

GetCallParameter32:
 module_index [2] kernel32.dll fun_index [200] CreateFileA
_stack [003ef3d8] ret_ip: 0x00553b8b

SetLastCallResult32: CreateFileA eax: -1 edx: 10420224 sto: -nan(ind) use time: 515400 ns

GetCallParameter32:
 module_index [10] ws2_32.dll fun_index [22] socket
_stack [003ef3e8] ret_ip: 0x00553c58

SetLastCallResult32: socket eax: -1 edx: 0 sto: -nan(ind) use time: 1633900 ns

GetCallParameter32:
 module_index [10] ws2_32.dll fun_index [19] sendto
_stack [003ef3dc] ret_ip: 0x00553cf1

sendto parameter: send data:this sendto data. data len:17

SetLastCallResult32: sendto eax: -1 edx: 0 sto: -nan(ind) use time: 490700 ns

GetCallParameter32:
 module_index [10] ws2_32.dll fun_index [2] closesocket
_stack [003ef3f0] ret_ip: 0x00553cfd

SetLastCallResult32: closesocket eax: -1 edx: 0 sto: -nan(ind) use time: 29600 ns

GetCallParameter32:
 module_index [0] myapp.exe fun_index [2] int64test
_stack [003ef3e4] ret_ip: 0x00553d0a

SetLastCallResult32: int64test eax: 122 edx: 1 sto: -nan(ind) use time: 13100 ns

GetCallParameter32:
 module_index [0] myapp.exe fun_index [1] floattest
_stack [003ef3ec] ret_ip: 0x00553d3a

SetLastCallResult32: floattest eax: 5677115 edx: 1 sto: 3.123000 use time: 12300 ns

GetCallParameter32:
 module_index [0] myapp.exe fun_index [0] doubletest
_stack [003ef3e4] ret_ip: 0x00553d68

SetLastCallResult32: doubletest eax: 5677115 edx: 1 sto: 3.123000 use time: 11100 ns

** 很可能你很快发现了问题,[GetCallParameter32 MessageBoxW]之后隔了好几个WriteFile,然后才出现,[SetLastCallResult32: MessageBoxW],这是正常的,因为MessageBoxW内部就在WriteFile,所以就体现了,thread_local std::stack<CallContext*> hook_stack_list;std::stack必要性。 **

函数的参数通过esp栈指针去动态获取。函数的返回值通过eax edx 和浮点数sto去获取。此外我们还可以得到目标函数的调用整个纳秒时间。

关于参数获取

    esp_stack = esp_stack + 8 * 4 + 4 + 4;//pushad  +push [ret_ip] and call

	//通过esp获取参数的demo
	if (einfo->func_name.compare("sendto") == 0) {

		/*int PASCAL FAR sendto (
					   _In_ SOCKET s,
					   _In_reads_bytes_(len) const char FAR * buf,
					   _In_ int len,
					   _In_ int flags,
					   _In_reads_bytes_opt_(tolen) const struct sockaddr FAR *to,
					   _In_ int tolen);*/


					   //sendto是__stdcall,从右到左压栈,所以,栈顶是最左边的参数,为顺序

		char* send_data = *(char**)(esp_stack + 1 * 4);//顺数第2个参数

		int send_data_len = *(int*)(esp_stack + 2 * 4);//顺数第3个参数

		std::string send_data_str(send_data, send_data_len);

		log_printA("sendto parameter: send data:%s data len:%d", send_data_str.c_str(), send_data_len);

	}

参数获取,必须要知道传参的具体类型,上面的sendto函数,每个参数都是4字节,如果遇到int64的参数,那么一个参数就是8字节,就需要注意偏移。

问题来了,如何才能像API Monitor那样去获取任何api的完整的参数?就需要准备一个函数的资料库,这个资料里面有函数的每个参数类型,参数名称,返回值类型,结构体类型。

API Monitor有准备好了,参考API Monitor文件夹里面的API文件夹,里面的xml文件都是windows主要api的参数和返回值类型,很多类型是别名,需要继续去寻找最根本的类型,这样就能获取到大小了。

下面是我通过自己的程序和API Monitor的xml文件去配合完成的参数读取结果。

    module_name:kernel32.dll
    function name:CreateFileW
 function addr: 0x6f800558
      call addr:0x1039a95
    description:文件操作
       [return]: 0xe70([FILE_HANDLE])
          param:
                   [0] lpFileName  LPCTSTR
            value:   0x0753F644  (wstring)\\\\?\\D:\\xxx\\bin_110\\3dmgame\\data\\fonts\\yaheib17-01.paa
                   [1] dwDesiredAccess  [FILE_ACCESS_MASK]
            value:   0x80000000  ( GENERIC_READ ) 
                   [2] dwShareMode  [FILE_SHARE_MODE]
            value:   0x1  ( FILE_SHARE_READ ) 
                   [3] lpSecurityAttributes  LPSECURITY_ATTRIBUTES
            value:   0x0753F650  (struct)SECURITY_ATTRIBUTES||[DWORD nLength]  0x3||[PSECURITY_DESCRIPTOR lpSecurityDescriptor]  0x€||[BOOL bInheritHandle]  0x0
                   [4] dwCreationDisposition  [CreationDisposition]
            value:   0x3 (OPEN_EXISTING)
                   [5] dwFlagsAndAttributes  [FlagsAndAttributes]
            value:   0x60000080  ( FILE_ATTRIBUTE_NORMAL  FILE_FLAG_NO_BUFFERING  FILE_FLAG_OVERLAPPED ) 
                   [6] hTemplateFile  HANDLE
            value:   0x0 (NULL)


    module_name:ws2_32.dll
    function name:recv
 function addr: 0x6f8001f8
      call addr:0x7432c157
    description:网络访问
       [return]: 0x1([SocketCode-int])
          param:
                   [0] s  SOCKET
            value:   0x1218(peer:213.192.54.31:443)(local:22.62.0.114:13560)
                   [1] buf  [LPVOID|char*]
            value:   0x02E0FCA4  
                   [2] len  int
            value:   0x1
                   [3] flags  [SendRecvFlags]
            value:   0x2  ( MSG_PEEK ) 
         

32位的核心代码逻辑已给出了。朋友们可以去自己发挥了。关于64位的是同样的道理,但是64位传参主要都是通过寄存器来的了,所以很多不一样的处理,需要的就自己去研究吧。

<完> 2021年5月26日 qq: base64(MTcxMjgzNjQ0)

以上是关于通用hook拦截所有API的实现的主要内容,如果未能解决你的问题,请参考以下文章

拦截http协议封包hook哪些

易语言Hook拦截某进程所有信息

内核级HOOK的几种实现与应用

进程动态拦截注入API HOOK

Js 拦截全局ajax请求

Keyboard Hook API函数 参数说明