Определение

Каждый программист, работающий со библиотеками знает, что при компиляции приложения/библиотеки, к которой была прилинкована библиотека, создаётся таблица импорта, содержащая адреса импортируемых функций той библиотеки. Данный алгоритм позволяет как просто перехватить аргументы функции, её вызов, так и полностью заменить её на свою.
Алгоритм был подсмотрен с JNGP, а после немного доработан.

Свойства

Алгоритм состоит из класса с методом для указания модуля, в котором будут проходить манипуляции, методом для выбора текущего модуля, содержащего функции, вызов которых мы собираемся перехватывать и модулем, производящим замену адреса в таблице импорта. Все методы содержат перегрузки для простоты работы.
Возвращаемое значение каждого метода true/false в случае удачи/неудачи соответственно
Составляющие класса Redirector:
  • setModule(HMODULE hModule) - определение модуля, в котором совершаются действия. Перегрузка setModule(LPCSTR lpModuleName)
  • setApiModule(HMODULE hApiModule) - указание модуля, содержащий функции, которые мы собираемся перехватывать. Перегрузка setApiModule(LPCSTR lpApiModuleName)
  • redirect(LPCSTR lpProcName, LPVOID lpNewProcAddress) - подмена функции в таблице импорта. Перегрузка redirect(int nProcOrdinal, LPVOID lpNewProcAddress)

Реализация

Redirector.h:
#pragma once

#include <Windows.h>

class Redirector
{
private:
	HMODULE m_hModule = NULL;
	HMODULE m_hApiModule = NULL;
	PIMAGE_THUNK_DATA m_pThunk = NULL;
public:
	Redirector() {};
	bool setModule(HMODULE hModule);
	bool setModule(LPCSTR lpModuleName);

	bool setApiModule(HMODULE hApiModule);
	bool setApiModule(LPCSTR lpApiModuleName);

	bool redirect(LPCSTR lpProcName, LPVOID lpNewProcAddress);
	bool redirect(int nProcOrdinal, LPVOID lpNewProcAddress);
};
Redirector.cpp:
#include "Redirector.h"

// Устанавливаем наш модуль
bool Redirector::setModule(HMODULE hModule)
{
	if (!hModule)
		return false;

	m_hModule = hModule;

	return true;
}

bool Redirector::setModule(LPCSTR lpModuleName)
{
	return setModule(GetModuleHandle(lpModuleName));
}

// Выбираем модуль, функции которого будем подменивать
bool Redirector::setApiModule(HMODULE hApiModule)
{
	if (!hApiModule)
		return false;

	m_hApiModule = hApiModule;

	if (!m_hModule)
		return false;

	// Проверка, dll/exe это или нет
	if (PIMAGE_DOS_HEADER(m_hModule)->e_magic != IMAGE_DOS_SIGNATURE)
		return false;

	// Точно ли это исполняемый файл?
	PIMAGE_NT_HEADERS pNTHeader = PIMAGE_NT_HEADERS(PBYTE(m_hModule) + PIMAGE_DOS_HEADER(m_hModule)->e_lfanew);
	if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
		return false;

	// Получаем адрес таблицы импорта
	DWORD dwImportRVA = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
	PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((ULONG_PTR)(m_hModule) + (ULONG_PTR)(dwImportRVA));

	// Ищем нашу таблицу импорта
	while (pImportDesc->FirstThunk)
	{
		if (GetModuleHandle((PSTR)((ULONG_PTR)(m_hModule)+(ULONG_PTR)(pImportDesc->Name))) == hApiModule)
			break;

		pImportDesc++;
	}

	if (!pImportDesc->FirstThunk)
		return false;

	// Сохраняем адрес нашей таблицы импорта
	m_pThunk = (PIMAGE_THUNK_DATA)((PBYTE)m_hModule + (DWORD)pImportDesc->FirstThunk);

	return true;
}

bool Redirector::setApiModule(LPCSTR lpApiModuleName)
{
	return setApiModule(GetModuleHandle(lpApiModuleName));
}

// Меняем адрес в таблице импорта на наш
bool Redirector::redirect(LPCSTR lpProcName, LPVOID lpNewProcAddress)
{
	// Получаем адреса функций
	ULONG_PTR uNewProcAddress = (ULONG_PTR)lpNewProcAddress;
	ULONG_PTR uRoutineAddress = (ULONG_PTR)GetProcAddress(m_hApiModule, lpProcName);
	PIMAGE_THUNK_DATA pThunk = m_pThunk;

	if (!uRoutineAddress)
		return false;

	if (!pThunk)
		return false;

	// Ищим нашу функцию
	while (pThunk->u1.Function)
	{
		// Получаем адрес
		ULONG_PTR* uAddress = (ULONG_PTR*)&pThunk->u1.Function;
		if (*uAddress == uRoutineAddress)
		{
			DWORD dwOldProtect = NULL;

			// Подмениваем адрес
			VirtualProtect((LPVOID)uAddress, 4, PAGE_WRITECOPY, &dwOldProtect);
			*uAddress = uNewProcAddress;
			VirtualProtect((LPVOID)uAddress, 4, dwOldProtect, NULL);

			return true;
		}

		pThunk++;
	}

	return false;
}

bool Redirector::redirect(int nProcOrdinal, LPVOID lpNewProcAddress)
{
	return redirect((LPCSTR)nProcOrdinal, lpNewProcAddress);
}

Пример использования

Для примера, я хочу получить название иконки Warcraft III, чтобы потом гладенько заменить её на свою. Т.к Resource Hacker не отображает название ресурса, я, зная то, что Warcraft III использует функцию LoadImageA для получения её из ресурсов, переписываю её объявление, а после, помещаю в неё MessageBox, отображающий мне аргумент, содержащий название ресурса. В возврате указываю обычную функцию LoadImageA. Это выглядит примерно так:
HANDLE CALLBACK LoadImageAProxy(HINSTANCE hInst, LPCSTR name, UINT type, int cx, int cy, UINT fuLoad)
{
	MessageBox(NULL, name, name, MB_OK | MB_SYSTEMMODAL);

	return LoadImageA(hInst, name, type, cx, cy, fuLoad);
}
Теперь я пишу в нашем коде перехват оригинальной функции, для этого создаём класс и по очереди обращаемся к методам. Т.к LoadImageA - функция, импортируемая из user32.dll, то в setApiModule указываем именно его. В redirect пишем название заменяемой функции и ссылку на наш вариант. Это выглядит так:
redirector.setModule(GetModuleHandle("Game.dll"));
if (redirector.setApiModule("user32.dll"))
	redirector.redirect("LoadImageA", (LPVOID)LoadImageAProxy);
Дальше компилируем и подгружаем код к игре, я это делаю через свой лаунчер, но,скорее всего, можно обойтись и обычным .mix.
Теперь мы знаем название ресурса, содержащего иконку, и можем спокойно сделать подмену результата, для этого в том же самом коде сделаем проверку аргумента по названию и в случае удачи, возвратим наш ресурс. MessageBox можно убрать. Мой код:
HANDLE CALLBACK LoadImageAProxy(HINSTANCE hInst, LPCSTR name, UINT type, int cx, int cy, UINT fuLoad)
{
	if (!strcmpi(name, "war3x.ico"))
		return LoadImageA(hInst, "Krakazyava.ico", type, cx, cy, fuLoad);

	return LoadImageA(hInst, name, type, cx, cy, fuLoad);
}
Вот и всё! Код подгружен, функция поймана, аргументы получены, а иконка заменена.