ImportTableHook(Also Called "FunctionRedirection")允许我们将任意模块(一般是没有源代码的模块,可以是系统模块)中对任意函数名已知的导入函数的调用重定向到自定义的实现,本文会对如何在Win32、Linux、Mach平台上实现ImportTableHook进行介绍。
由于时间有限,先给出Mach和Win32下的实现代码,以后再填坑
#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];
}
}
}
}
}
传统的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不兼容! 以后有时间再解决!
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>