windows | 虚表hook

类中有虚函数就会存在虚表。

主要是一种间接call:
call dword ptr [xxxxxxx];

因此就可以修改间接跳转的地址实现hook。

主要步骤:

  1. 找到虚表
  2. 修改虚表的物理页属性可写
  3. 修改虚函数地址

代码如下:

#include <stdio.h>
#include <windows.h>


class Base{
public:
	virtual void output(){
		printf("i am base \n");
	}

};


void hook_print(){
	printf("你被hook了\n");
}


int main(int argc, char* argv[]){
	Base *pb = new Base();     // 此对象的第一个DWORD指向虚表

	DWORD *pVtAddr = (DWORD *)(*(DWORD*)pb);        // 强转类型指向虚表
	// 由于虚表的属性是只读不可写,所以要改成可写
	DWORD dwOldProct = 0;
	VirtualProtect(pVtAddr, 4, PAGE_READWRITE, &dwOldProct);
	*pVtAddr = (DWORD)hook_print;        // hook函数的地址

	pb->output();
	delete pb;
	return 0;
}

下面是2023/11/23补充:
上面的情况过于简单,事实上,对于类函数的调用通常是thiscall的调用约定,因此参数和寄存器的处理至关重要,下面给出我最新调试好的一个示例:

// injecteddll.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

extern "C" __declspec(dllexport) void __stdcall hi()   // 导出一个函数,并没有实际作用
{
	printf("hi from dll \n ");
}


// 保存原来的函数指针
void (__cdecl *g_pFunc)(DWORD*, int);

char s[] = "[dll] arg number: %d \n";
// 自定义的hook函数
_declspec(naked) void my_get_money(){
	_asm{
		push ecx           ; 保存this指针
		mov ecx, dword ptr [esp+8]   ; 获取原先的参数值
		push ecx
		lea ecx, s
		push ecx
		call printf                     ;输出参数值
		add     esp, 8
	}
	_asm{
		mov eax, dword ptr [esp+8]    ; 获取原先的参数值
		pop ecx            ; 恢复this指针
		add eax, 100000     ; 修改参数值
		push eax        ; 调用原函数
		call g_pFunc
		retn 4
	}
}

// 用于进行hook的线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter){
	Sleep(1000);    // 延迟等待exe完成加载
    // 进行虚表hook
	DWORD* vtable = (DWORD*)0x42801C;
	printf("%x %x \n", vtable[0], vtable[1]);     // 读取虚表中的两个虚函数地址(用于验证)
	// 由于虚表位于.rodate section,节属性为只读,因此需要对节属性进行修改
	DWORD dwOldProct = 0;
	VirtualProtect(vtable, 4, PAGE_READWRITE, &dwOldProct);   // 节属性修改成可读可写
	g_pFunc = (void (__cdecl *)(DWORD*, int))vtable[1];    // 保存原来的函数地址
	vtable[1] = (DWORD)&my_get_money;   // 将虚表中的地址改成我们定义的函数地址
    return 0;
}

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
	if (ul_reason_for_call == DLL_PROCESS_ATTACH){    // dll被加载时调用
		hi();
		CreateThread(NULL, 0,(LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL);    //创建新线程执行代码
	}
    return TRUE;
}


被hook程序的源代码:

#include <stdio.h>
class Game{
public:
	Game(){
		this->money = 0;
	}
	virtual void show_menu(){
		puts("\nmake a choice:");
		puts("  1. get money");
		puts("  2. buy flag");
		puts("  3. quit");
		printf("your choice: ");
	}
	virtual void get_money(int number){
		this->money += number;
		printf("you get %d$ \n", number);
	}
	void loop(){
		int choice = 0;
		while(1){
			show_menu();
			scanf("%d", &choice);
			if (choice == 3){
				break;
			}else if (choice == 2){
				if (this->money > 1000000){
					puts("you win! ");
					break;
				}else{
					printf("you only have %d$, more to get flag. \n", this->money);
				}
			}else if (choice == 1){
				get_money(1);
			}
		}
	}
private:
	int money;
};

int main(){
	Game* game = new Game;
	game->loop();
	return 0;
}

posted @ 2021-08-19 21:59  Mz1  阅读(180)  评论(0编辑  收藏  举报