ImportTableHook(Also Called "FunctionRedirection")

什么是ImportTableHook(Also Called "FunctionRedirection")?

ImportTableHook(Also Called "FunctionRedirection")允许我们将任意模块(一般是没有源代码的模块,可以是系统模块)中对任意函数名已知的导入函数的调用重定向到自定义的实现,本文会对如何在Win32、Linux、Mach平台上实现ImportTableHook进行介绍。

由于时间有限,先给出Mach和Win32下的实现代码,以后再填坑

由于时间有限,先给出Mach和Win32下的实现代码,以后再填坑

Mach平台

#include <mach-o>
#include <mach-o>

#if defined(__x86_64__)||defined(__aarch64__)
	typedef mach_header_64 PT_MACH_HEADER;
	#define PT_MH_MAGIC MH_MAGIC_64
	typedef load_command PT_LOAD_COMMAND;
	#define PT_LC_SEGMENT LC_SEGMENT_64
	typedef segment_command_64 PT_SEGMENT_COMMAND;
	typedef section_64 PT_SECTION;
	#define PT_LC_SYMTAB LC_SYMTAB
	typedef symtab_command PT_SYMTAB_COMMAND;
	typedef nlist_64 PT_NLIST;
	#define PT_LC_DYSYMTAB LC_DYSYMTAB
	typedef dysymtab_command PT_DYSYMTAB_COMMAND;
#elif defined(__i386__)||defined(__arm__)
	typedef mach_header PT_MACH_HEADER;
	#define PT_MH_MAGIC MH_MAGIC
	typedef load_command PT_LOAD_COMMAND;
	#define PT_LC_SEGMENT LC_SEGMENT
	typedef segment_command PT_SEGMENT_COMMAND;
	typedef section PT_SECTION;
	#define PT_LC_SYMTAB LC_SYMTAB_64
	typedef symtab_command PT_SYMTAB_COMMAND;
	typedef nlist PT_NLIST;
	#define PT_LC_DYSYMTAB LC_DYSYMTAB
	typedef dysymtab_command PT_DYSYMTAB_COMMAND;
#else
	#error Unknown Architecture
#endif

#include <string.h>
#include <assert.h>

void PTS_ImportTableHook //Also Called "Function Redirection"
(
	void const *pLibraryImportBaseAddress
	, void const *pLibraryImportSlideAddress //Only Used By Mach To Support Shared Cache 
	, char const * //pLibraryExportName //Only Used By Win32 To Distinguish Different (Export)Library
	, char const *const *pFuntionToHookNameVector //SOA(Structure Of Array)
	, uintptr_t const *pFuntionToHookNewAddressVector //SOA(Structure Of Array)
	, size_t FuntionToHookVectorCount
)
{
	uintptr_t pLibraryFilePointer = reinterpret_cast<uintptr_t>(pLibraryImportBaseAddress);

	//MachHeader
	PT_MACH_HEADER const *const pMachHeader = reinterpret_cast<pt_mach_header>(pLibraryFilePointer);
	pLibraryFilePointer += sizeof(PT_MACH_HEADER);

	//Even If The Mach-O Has FAT_HEADER, The dyld Will Only Load The Part That Matches The Current Architecture.
	assert(pMachHeader->magic == PT_MH_MAGIC);


	//LoadCommand

	//To Support Shared Cache
	//Reference The Source Code: Function"LinkEditOptimizer<a>::updateLoadCommands" In SourceFile"update_dyld_shared_cache.cpp" In Project"dyld"(opensource-apple/dyld on github)
	uintptr_t fLinkEditStartAddress = ~0U;
	uintptr_t fLinkEditFileOffset = ~0U;
	uintptr_t fDataStartAddress = ~0U;
	uintptr_t fDataFileOffset = ~0U;

	//We Borrow The Name From The GUI Of "MachOView".

	//SymbolTable/Symbols
	uintptr_t SymbolTableFileOffset = ~0U;

	//StringTable
	uintptr_t StringTableFileOffset = ~0U;

	//
	uint32_t iDynamicSymbolBeginInSymbolTable = ~0U;
	uint32_t iDynamicSymbolEndInSymbolTable = ~0U;

	//DynamicSymbolTable/IndirectSymbols
	uintptr_t IndirectSymbolTableFileOffset = ~0U;
	uint32_t cIndirectSymbolTable = ~0U;

	//In Fact IndirectSymbol Include NonLazySymbol And LazySymbol.
	//But MachOView Only Show The Part Start From "pIndirectSymbolTable+iLazySymbolPointersBegin" In The "DynamicSymbolTable/IndirectSymbols".
	uint32_t iLazySymbolPointersBegin = ~0U;

	//Section(__DATA,__la_symbol_ptr)/LazySymbolPointers
	uintptr_t LazySymbolPointersFileOffset = ~0U;
	uint32_t cLazySymbolPointers = ~0U;


	for (uint32_t icmd = 0; icmd < pMachHeader->ncmds; ++icmd)
	{
		PT_LOAD_COMMAND const *const pLoadCommand = reinterpret_cast<pt_load_command>(pLibraryFilePointer);
		pLibraryFilePointer += pLoadCommand->cmdsize;

		switch (pLoadCommand->cmd)
		{
		case PT_LC_SEGMENT:
		{
			assert(pLoadCommand->cmd == PT_LC_SEGMENT);
			PT_SEGMENT_COMMAND const *const pSegmentCommand = reinterpret_cast<pt_segment_command const="">(pLoadCommand);
			if (::strcmp(pSegmentCommand->segname, SEG_DATA) == 0)
			{
				PT_SECTION const *const pSectionVector = reinterpret_cast<pt_section>(reinterpret_cast<uintptr_t>(pSegmentCommand) + sizeof(PT_SEGMENT_COMMAND));

				for (uint32_t isect = 0; isect <= pSegmentCommand->nsects; ++isect)
				{
					PT_SECTION const *const pSection = pSectionVector + isect;
					if (::strcmp(pSection->sectname, "__la_symbol_ptr") == 0)
					{
						fDataStartAddress = reinterpret_cast<uintptr_t>(pLibraryImportSlideAddress) + pSegmentCommand->vmaddr;
						fDataFileOffset = pSegmentCommand->fileoff;
						LazySymbolPointersFileOffset = pSection->offset;
						iLazySymbolPointersBegin = pSection->reserved1;
						assert((pSection->size % sizeof(uintptr_t)) == 0U);
						cLazySymbolPointers = pSection->size / sizeof(uintptr_t);
						break;
					}
				}
			}
			else if (::strcmp(pSegmentCommand->segname, SEG_LINKEDIT) == 0)
			{
				fLinkEditStartAddress = reinterpret_cast<uintptr_t>(pLibraryImportSlideAddress) + pSegmentCommand->vmaddr;
				fLinkEditFileOffset = pSegmentCommand->fileoff;
				assert(pSegmentCommand->nsects == 0U);
			}
		}
		break;
		case PT_LC_SYMTAB:
		{
			assert(pLoadCommand->cmd == PT_LC_SYMTAB);
			PT_SYMTAB_COMMAND const *const pSymTabCommand = reinterpret_cast<pt_symtab_command const="">(pLoadCommand);
			SymbolTableFileOffset = pSymTabCommand->symoff;
			StringTableFileOffset = pSymTabCommand->stroff;
		}
		break;
		case PT_LC_DYSYMTAB:
		{
			assert(pLoadCommand->cmd == PT_LC_DYSYMTAB);
			PT_DYSYMTAB_COMMAND const *const pDySymTabCommand = reinterpret_cast<pt_dysymtab_command const="">(pLoadCommand);
			iDynamicSymbolBeginInSymbolTable = pDySymTabCommand->iundefsym;
			iDynamicSymbolEndInSymbolTable = pDySymTabCommand->iundefsym + pDySymTabCommand->nundefsym;
			IndirectSymbolTableFileOffset = pDySymTabCommand->indirectsymoff;
			cIndirectSymbolTable = pDySymTabCommand->nindirectsyms;
		}
		break;
		}
	}

	//To Support Shared Cache
	//Reference The Code: Function"LinkEditOptimizer</pt_dysymtab_command></pt_symtab_command></uintptr_t></uintptr_t></uintptr_t></pt_section></pt_segment_command></pt_load_command></a>
<a>::updateLoadCommands" In SourceFile"update_dyld_shared_cache.cpp" In Project"dyld"(opensource-apple/dyld on github)
	bool bIsMachOFileFormatValid = (fLinkEditStartAddress != (~0U))
		&& (fLinkEditFileOffset != (~0U))
		&& (fLinkEditStartAddress > fLinkEditFileOffset)
		&& (SymbolTableFileOffset != (~0U))
		&& (StringTableFileOffset != (~0U))
		&& (IndirectSymbolTableFileOffset != (~0U))
		&& (fDataStartAddress != (~0U))
		&& (fDataFileOffset != (~0U))
		&& (fDataStartAddress > fDataFileOffset)
		&& (LazySymbolPointersFileOffset != (~0U));
	assert(bIsMachOFileFormatValid);

	if (bIsMachOFileFormatValid)
	{
		uintptr_t const pLinkEditFileOffsetBaseAddress = fLinkEditStartAddress - fLinkEditFileOffset;
		PT_NLIST const * const pSymbolTable = reinterpret_cast<pt_nlist const="">(pLinkEditFileOffsetBaseAddress + SymbolTableFileOffset);
		char const * const pStringTable = reinterpret_cast<char const="">(pLinkEditFileOffsetBaseAddress + StringTableFileOffset);
		uint32_t const * const pIndirectSymbolTable = reinterpret_cast<uint32_t const="">(pLinkEditFileOffsetBaseAddress + IndirectSymbolTableFileOffset);
		uintptr_t const pDataFileOffsetBaseAddress = fDataStartAddress - fDataFileOffset;
		uintptr_t * const pLazySymbolPointers = reinterpret_cast<uintptr_t>(pDataFileOffsetBaseAddress + LazySymbolPointersFileOffset);

		for (size_t iFuntionToHook = 0U; iFuntionToHook < FuntionToHookVectorCount; ++iFuntionToHook)
		{
			uint32_t iFuntionToHookInSymbolTable = ~0U;
			{
				for (uint32_t iDynamicSymbol = iDynamicSymbolBeginInSymbolTable; iDynamicSymbol < iDynamicSymbolEndInSymbolTable; ++iDynamicSymbol)
				{
					PT_NLIST const *const pDynamicSymbol = pSymbolTable + iDynamicSymbol;
					char const *pDynamicSymbolName = (pDynamicSymbol->n_un.n_strx != 0) ? (pStringTable + pDynamicSymbol->n_un.n_strx) : "";
					if (::strcmp(pDynamicSymbolName, pFuntionToHookNameVector[iFuntionToHook]) == 0)
					{
						iFuntionToHookInSymbolTable = iDynamicSymbol;
						break;
					}
				}
			}

			if (iFuntionToHookInSymbolTable != (~0U))
			{
				uint32_t iFuntionToHookInLazySymbolPointers = ~0U;
				{
					uint32_t const *const pLazySymbolTable = pIndirectSymbolTable + iLazySymbolPointersBegin;
					for (uint32_t iLazySymbolPointer = 0; iLazySymbolPointer < cLazySymbolPointers; ++iLazySymbolPointer)
					{
						if (pLazySymbolTable[iLazySymbolPointer] == iFuntionToHookInSymbolTable)
						{
							iFuntionToHookInLazySymbolPointers = iLazySymbolPointer;
							break;
						}
					}
				}

				if (iFuntionToHookInLazySymbolPointers != (~0U))
				{
					pLazySymbolPointers[iFuntionToHookInLazySymbolPointers] = pFuntionToHookNewAddressVector[iFuntionToHook];
				}
			}
		}
	}
}

UseCase -- Hook "malloc" and "free" from "libobjc(.A).dylib"

传统的C运行时库的malloc和free在并发访问时会串行化,从而降低效率;Intel TBB提供了内存分配器——TBBMalloc(论文中的名称是McRT-Malloc),以允许被高效并发访问。 macOS/iOS平台上Objective-C运行时库(libobjc.dylib)仍调用传统的C运行时库的malloc和free,显然在并发访问时会严重降低效率。 一个非常自然的想法是Hook住Objective-C运行时库中对malloc和free的调用,并用TBBMalloc中的实现代替,ImportTableHook技术可以帮助我们做到这一点。

//To Allow Custom Allocator (For Example The TBB Malloc By Intel To Avoid Lock In Multi-Thread Envrionment)
//We Try To Hook The "malloc" And "free" In "libobjc.dylib"

#include <stdlib.h>

static void *Hooked_Malloc(size_t size)
{
	return malloc(size); //For Example, We Can Replace It By Intel Tbb Malloc
}

static void Hooked_Free(void *ptr)
{
	free(ptr); //For Example, We Can Replace It By Intel Tbb Malloc
}

#include <mach-o>

extern "C" CMTLAPI_ATTR void CMTLAPI_CALL CMetal_Hook_Allocation(void *(CMTLAPI_PTR *pfnAlloc)(size_t size, size_t alignment), void (CMTLAPI_PTR *pfnFree)(void *ptr))
{
	void const *pLibObjCBaseAddress = NULL;
	void const *pLibObjCSlideAddress = NULL;
	{
		uint32_t Imagecount = ::_dyld_image_count();
		for (uint32_t iImage = 0U; iImage < Imagecount; ++iImage)
		{
			char const *dylibname = ::_dyld_get_image_name(iImage);
			if (::strstr(dylibname, "/libobjc"))
			{
				pLibObjCBaseAddress = static_cast<void const="">(::_dyld_get_image_header(iImage));
				pLibObjCSlideAddress = reinterpret_cast<void const="">(_dyld_get_image_vmaddr_slide(iImage));
				break;
			}
		}
	}

	//Because Of The Name Decoration Of The Compiler, The Actual Name Of The Function In The Mach-O File Has "_" Prefix.
	char const *FuntionToHookNameVector[] = {
		"_malloc"
		,"_free"
	};

	uintptr_t FuntionToHookAddressVector[] = {
		reinterpret_cast<uintptr_t>(&Hooked_Malloc)
		,reinterpret_cast<uintptr_t>(&Hooked_Free)
	};

	assert((sizeof(FuntionToHookNameVector) / sizeof(FuntionToHookNameVector[0])) == (sizeof(FuntionToHookAddressVector) / sizeof(FuntionToHookAddressVector[0])));

	PTS_ImportTableHook(
		pLibObjCBaseAddress,
		pLibObjCSlideAddress,
		NULL,
		FuntionToHookNameVector,
		FuntionToHookAddressVector,
		sizeof(FuntionToHookNameVector) / sizeof(FuntionToHookNameVector[0])
	);
}

截图留念!

Objective-C语言的实现方式是:在编译的时候会转成C语言,然后调用Objective-C运行时库中的函数。 已经成功Hook住了Objective-C运行时库中的malloc和free的引用,并允许转发到更高效的实现(比如Intel TBB Malloc)!

未完待续!

替换成IntelTBBMalloc后,程序会发生崩溃! 原因不明,可能是由于:在Hook之前,Objective-C已经调用了部分malloc和free,与Hook之后的IntelTBBMalloc不兼容! 以后有时间再解决!

Win32平台

void PTS_ImportTableHook //Also Called "Function Redirection"
(
	void *pLibraryImportBaseAddress
	, void const * //pLibraryImportSlideAddress //Only Used By Mach To Support Shared Cache 
	, char const *pLibraryExportName //Only Used By Win32 To Distinguish Different (Export)Library
	, char const *const *pFuntionToHookNameVector //SOA(Structure Of Array)
	, uintptr_t const *pFuntionToHookNewAddressVector //SOA(Structure Of Array)
	, size_t FuntionToHookVectorCount
)
{
	//On Win32, Functions Imported From Different (Export)Library Are Grouped Into Different ImportTable.
	//We Find The ImportTable That Matches The "pLibraryExportName" Argument .

	IMAGE_IMPORT_DESCRIPTOR *pImportDescriptor = NULL;
	{
		ULONG ImportDescriptorVectorSize;
		IMAGE_IMPORT_DESCRIPTOR *ImportDescriptorVector = static_cast<image_import_descriptor>(::ImageDirectoryEntryToData(pLibraryImportBaseAddress, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ImportDescriptorVectorSize));
		assert((ImportDescriptorVectorSize % sizeof(IMAGE_IMPORT_DESCRIPTOR)) == 0U);
		ULONG ImportDescriptorVectorCount = (ImportDescriptorVectorSize / sizeof(IMAGE_IMPORT_DESCRIPTOR));
		for (ULONG iImportDescriptor = 0U; iImportDescriptor < ImportDescriptorVectorCount; ++iImportDescriptor)
		{
			char *NameDLL = reinterpret_cast<char>(reinterpret_cast<ulong_ptr>(pLibraryImportBaseAddress) + ImportDescriptorVector[iImportDescriptor].Name);//RVA
			if (::stricmp(NameDLL, pLibraryExportName) == 0)
			{
				pImportDescriptor = ImportDescriptorVector + iImportDescriptor;
				break;
			}
		}
	}

	//The ImportTable Can Be Considered As A SOA(Structure Of Array), Which Constists Of Two Arrays - ImportNameTable And ImportAddressTable.
	//We Use The ImportNameTable To Find The Index Of The Function In The ImportTable And Write The New Address To The ImportAddressTable.

	if (pImportDescriptor != NULL)
	{
		for (size_t iFuntionToHook = 0U; iFuntionToHook < FuntionToHookVectorCount; ++iFuntionToHook)
		{
			uintptr_t *pFunctionAddressToWrite = NULL;
			{
				//OriginalFirstThunk Is The RelativeVirtualAddress From LibraryBaseAddress
				IMAGE_THUNK_DATA *ImportNameTable = reinterpret_cast<image_thunk_data>(reinterpret_cast<ulong_ptr>(pLibraryImportBaseAddress) + pImportDescriptor->OriginalFirstThunk);

				//FirstThunk Is The RelativeVirtualAddress From LibraryBaseAddress
				IMAGE_THUNK_DATA *ImportAddressTable = reinterpret_cast<image_thunk_data>(reinterpret_cast<ulong_ptr>(pLibraryImportBaseAddress) + pImportDescriptor->FirstThunk);

				for (size_t iImportTable = 0U; ImportNameTable[iImportTable].u1.AddressOfData != 0U; ++iImportTable) //“RVA = 0” Means End Of Table
				{
					//Name Is The RelativeVirtualAddress From LibraryBaseAddress
					char *FunctionName = reinterpret_cast<char>(reinterpret_cast<image_import_by_name>(reinterpret_cast<ulong_ptr>(pLibraryImportBaseAddress) + ImportNameTable[iImportTable].u1.AddressOfData)->Name);
					if (::strcmp(FunctionName, pFuntionToHookNameVector[iFuntionToHook]) == 0)
					{
						pFunctionAddressToWrite = &ImportAddressTable[iImportTable].u1.Function;
						break;
					}
				}
			}

			if (pFunctionAddressToWrite != NULL)
			{
				//We Use Write-Copy To Ensure The Write Will Success!
				DWORD flOldProtect1;
				::VirtualProtect(pFunctionAddressToWrite, sizeof(ULONG_PTR), PAGE_WRITECOPY, &flOldProtect1);

				*pFunctionAddressToWrite = pFuntionToHookNewAddressVector[iFuntionToHook];

				DWORD flOldProtect2;
				::VirtualProtect(pFunctionAddressToWrite, sizeof(ULONG_PTR), flOldProtect1, &flOldProtect2);
			}
		}
	}
}

</ulong_ptr></image_import_by_name></ulong_ptr></image_thunk_data></ulong_ptr></image_thunk_data></ulong_ptr></image_import_descriptor></uintptr_t></uintptr_t></stdlib.h></uintptr_t></uint32_t></pt_nlist> </pt_mach_header></uintptr_t></assert.h></string.h>