遇到了需要 Hook 一些函数的情况,WinAPI 的 __stdcall
或是 IDA 分析出来的 __usercall
,留此纪录。测试环境为 Win32,编译器为 MSVC。
-
普通
JMP
(不推荐)
Hook 一个函数,最简单的思路就是在它开头放一个 JMP
指令到我们的地址。
而需要调用回原函数时(比如只是截取参数信息),必须先还原它的开头字节才能正常调用,这在多线程中可能会出现短暂的 Hook 失效,甚至执行到非预期的汇编指令,因此一般不采用此方法。
首先定义一个 JMPCODE
,大小为 5 字节。(注意对齐问题)
1 2 3 4 5 6 7 |
#pragma pack(push, 1) typedef struct _JMPCODE { BYTE jmp = 0xE9; // JMP instruction DWORD addr = 0; // Target address offset }JMPCODE, *PJMPCODE; #pragma pack(pop) |
有一个函数 HOOK::patch
用来写入某块内存区域。(注意更改页面保护信息)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/* patch(addr, arr, size) => bool, whether succeeds addr (DWORD_PTR) address to patch arr (const BYTE*) data size (DWORD) bytes to patch */ bool HOOK::patch(DWORD_PTR addr, const BYTE* arr, DWORD size) { DWORD oldProtect = 0; // make memory page writable if (!VirtualProtect((LPVOID)addr, size, PAGE_READWRITE, &oldProtect)) return false; // overwrite target address BYTE* pad = (BYTE*)addr; for (unsigned i = 0; i < size; i++) pad[i] = arr[i]; // recover page protection if (!VirtualProtect((LPVOID)addr, size, oldProtect, &oldProtect)) return false; return true; } |
以 Hook WinAPI OutputDebugStringA
为例,只需要把它的前 5 个字节替换为 JMPCODE
即可。(注意备份前 5 个字节,及 JMP
的偏移计算)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
JMPCODE hookCode, orgCode; /* hook(myF, orgF) => bool myF (LPVOID) my function address orgF (LPVOID) original function address */ bool HOOKJMP::hook(LPVOID myF, LPVOID orgF) { // backup original 5 bytes if (!ReadProcessMemory(GetCurrentProcess(), orgF, &orgCode, 5, NULL)) return false; // overwrite original function hookCode.addr = (DWORD)myF - (DWORD)orgF - 5; // calculate offset if (!HOOK::patch((DWORD)orgF, (BYTE*)&hookCode, 5)) return false; return true; } HOOKJMP::hook(myOutputDebugStringA, OutputDebugStringA); |
其中 Hook 到的我们的 myOutputDebugStringA
函数实现如下:(注意还原前 5 个字节)
1 2 3 4 5 6 7 8 9 10 |
void WINAPI myOutputDebugStringA(LPCSTR lpOutputString) { MessageBoxA(NULL, lpOutputString, "DEBUG", 0); // recover bytes HOOK::patch(OutputDebugStringA, (BYTE*)&orgCode, 5); // call original function OutputDebugStringA(lpOutputString); // re-hook HOOK::patch(OutputDebugStringA, (BYTE*)&hookCode, 5)); } |
这是用 JMP
实现的一个最简单的 Hook。
-
带
Gadget
的JMP
基于上述普通 JMP
在多线程环境下的问题,如果能避免每次都重新写入(hook, recover, re-hook, recover, re-hook, ……)原函数的前几个字节就好了。
所以自然的一种想法就是将原函数的前几个字节拷到一个地方,每次只需先转到这个地方执行原函数的前几条指令,而后跳回原函数继续执行后面的指令就好了。
这就是带 Gadget
的 JMP
思想,首先把原函数的前几条完整的指令拷到 Gadget
上,Gadget
最后 JMP
回原函数后面的指令继续执行,这样就可以避免更改原函数的前几个字节(Hook 完后始终保持为 JMP
)而调用回真正的原函数了。
主要流程如下图:
需要注意的是这里必须 patch 完整的指令字节(至少是 5 bytes,多余的部分用 NOP
填充),所以对于不同函数需要 patch 的字节数也不尽相同。
以 Hook 上图中 base + 0x75E90
处的函数为例,需要替换它的前 6 个字节(分别为完整的
sub esp, C 和
push esi 和
push 2C)为
JMP offset 和
NOP 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
LPVOID gadAddress; /* hook(myF, orgF, size) => bool myF (LPVOID) my function address orgF (LPVOID) original function address size (DWORD) size of complete commands to patch */ bool HOOKJMP::hook(LPVOID myF, LPVOID orgF, DWORD size) { // copy size bytes for original command, extra 5 for JMPCODE BYTE* arr = (BYTE*)VirtualAlloc(NULL, size + 5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!arr) return false; gadAddress = arr; // gadget address if (!ReadProcessMemory(GetCurrentProcess(), orgF, arr, size, NULL)) return false; // overwrite gadget address JMPCODE code; code.addr = (DWORD)orgF - (DWORD)arr - 5; // from gadget to original function if (!WriteProcessMemory(GetCurrentProcess(), &arr[size], &code, 5, NULL)) return false; // overwrite original function code.addr = (DWORD)myF - (DWORD)orgF - 5; // from original function to mine if (!HOOK::patch((DWORD)orgF, (BYTE*)&code, 5)) return false; BYTE NOPs[1024] = { 0x90 }; if (!HOOK::patch((DWORD)orgF + 5, NOPs, this->size - 5)) return false; // finished return true; } hook(myFunc, (LPVOID)(base + 0x75E90), 6); |
其中 Hook 到的我们的 myFunc
函数实现如下:(假设该函数为 __cdecl
,接受一个参数)
1 2 3 4 5 6 |
typedef void (__cdecl* tpOrgFunc)(int); void __cdecl myFunc(int a1) { tpOrgFunc orgFunc = static_cast<tpOrgFunc>(gadAddress); return orgFunc(a1); } |
这样就完成了一个带 Gadget
的 JMP
Hook。
-
替换 IAT(仅适用 PE)
可以发现,上述方法也存在一个明显的缺点:被调用函数最好至少为 5 字节,且前几条指令最好不要是 JMP
。
比如 kernel32.dll
中的 OutputDebugStringA
只是一个简单的 JMP
到 kernelbase.dll
中的 OutputDebugStringA
。
而要 Hook WinAPI (或是其它由 DLL 导入的函数),还可以通过 patch IAT 中对应函数的位置。
IAT ( Import Address Table ),详见 https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#import-address-table
在一个 PE 文件里,可以很容易地找到 Import Table 的位置。
里面包含了导入的各种 DLL 函数。
在 PE 文件被加载的时候,系统会自动填充此表为正确的地址,而要 Hook 某个导入的函数,只需其后将 IAT 中它的地址替换为我们的就行了。
以 Hook WinAPI OutputDebugStringA
为例,实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
LPVOID orgFunc; /* hook(myF, orgName) => bool, whether succeeds myF (LPVOID) my function address orgName (LPCSTR) original function name */ bool HOOKIAT::hook(LPVOID myF, LPCSTR orgName) { bool suc = false; // base address of current process LPVOID imageBase = GetModuleHandleA(NULL); PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)imageBase; PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)imageBase + dosHeaders->e_lfanew); // jump to imports directory PIMAGE_IMPORT_DESCRIPTOR importDescriptor = NULL; IMAGE_DATA_DIRECTORY importsDirectory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(importsDirectory.VirtualAddress + (DWORD_PTR)imageBase); LPCSTR libraryName = NULL; HMODULE library = NULL; PIMAGE_IMPORT_BY_NAME functionName = NULL; // loop import modules while (importDescriptor->Name != NULL) { // get handle of this imported module libraryName = (LPCSTR)((DWORD_PTR)imageBase + importDescriptor->Name); if (library = LoadLibraryA(libraryName)) { PIMAGE_THUNK_DATA originalFirstThunk = NULL, firstThunk = NULL; originalFirstThunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)imageBase + importDescriptor->OriginalFirstThunk); firstThunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)imageBase + importDescriptor->FirstThunk); // loop import functions while (originalFirstThunk->u1.AddressOfData != NULL && !IMAGE_SNAP_BY_ORDINAL(originalFirstThunk->u1.Ordinal)) { functionName = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)imageBase + originalFirstThunk->u1.AddressOfData); // find function by name if (strcmp(this->orgName, functionName->Name) == 0) { if (!orgFunc) orgFunc = (LPVOID)(firstThunk->u1.Function); // patch this if (!HOOK::patch((DWORD_PTR)&firstThunk->u1.Function, (BYTE*)&myF, sizeof(DWORD_PTR))) return false; suc = true; } ++originalFirstThunk; ++firstThunk; } } ++importDescriptor; } return suc; } HOOKIAT::hook(myOutputDebugStringA, "OutputDebugStringA"); |
其中 Hook 到的我们的 myOutputDebugStringA
函数实现如下:
1 2 3 4 5 6 7 |
typedef void (WINAPI* tpOutputDebugStringA)(LPCSTR); void WINAPI myOutputDebugStringA(LPCSTR lpOutputString) { MessageBoxA(NULL, lpOutputString, "DEBUG", 0); tpOutputDebugStringA ODA = static_cast<tpOutputDebugStringA>(orgFunc); ODA(lpOutputString); } |
这样就完成了一个简单的 IAT Hook。
可以发现 PE 中的 IAT 与 ELF 中的 GOT/PLT 存在相似之处。
-
__usercall
的转换
至此,我们已经可以很好地 Hook 一些常规的函数了。
而有些函数在 IDA 里会被分析成下图的 __usercall
调用方式:
1 |
char __usercall sub_475E90@<al>(_DWORD *a1@<eax>, _DWORD *a2@<edi>, int a3) |
__usercall
意味着区别于常规的 __stdcall
(参数自右向左依次入栈,由被调用函数清理)或是 __cdecl
(参数自右向左以此入栈,由调用者自行清理),它先把一些参数存在寄存器里,使用寄存器传参,而后才会入栈,是一种编译器优化而成的非标准的调用方法。
这就意味着我们无法使用纯 C/++ 代码来实现此函数的 Hook(甚至是调用),不过内联汇编使转换函数(__usercall
<-> __stdcall
)成为了可能。
关于函数调用栈操作的粗浅的理解,在先前的文章 “栈溢出 —— 初级 ROP 学习记录” 已有所提及。
在这里 Hook 可以完全仿照先前带 Gadget
的 JMP
方法,应当把注意力集中在两种调用方法的转换上。
以 base + 0x75E90
处的函数 sub_475E90
为例,它接受三个参数,分别由 eax
,edi
和栈传递,最后用 eax
传递返回值。
在实现上,__usercall
会更相似于 __cdecl
,因为前者可以被看做是后者的一种优化。所以把原函数 Hook 至我们的 sub_475E90
,实现如下:(在这里进行 enter
/leave
操作是为了避免 push
参数时可能会出现溢出)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
__declspec(naked) char __cdecl sub_475E90(int a3) { __asm { push ebp // save stack frame mov ebp, esp // create new stack frame push a3 // push arguments push edi // push arguments push eax // push arguments call mysub_475E90 // converted to __stdcall leave // recover stack frame ret // finished } } |
而 mysub_475E90
已经可以被当做是一个普通的 __stdcall
函数了。(转换成功!)
1 2 3 4 5 |
char __stdcall mysub_475E90(DWORD* a1, DWORD* a2, int a3) { // Do whatever wants here return orgsub_475E90(a1, a2, a3); } |
orgsub_475E90
实现了从 __cdecl
到 __usercall
的转换。(我在这里使用了 HOOKJMP::get
函数,功能是返回原函数(或者是 Gadget
)的地址,ecx
作为 this
指针传进去)(调用完原函数后要记得平衡堆栈,orgsub_475E90
选择了 __cdecl
也是为了避免需要在 __declspec(naked)
里手动平衡堆栈)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
__declspec(naked) char __cdecl orgsub_475E90(DWORD* a1, DWORD* a2, int a3) { __asm { lea ecx, hookJMP // store class pointer in ecx call HOOKJMP::get // __thiscall mov ecx, eax // original function address // or just mov ecx, orgFunc push a3 // push arguments mov edi, a2 // __usercall mov eax, a1 // __usercall call ecx // call original function add esp, 0x4 // recover stack for a3 ret // finished } } |
这样就可以 Hook 一个 __usercall
函数,包括正常调用原函数了。
(一个隐藏的问题,使用这种方法 Hook 时,va_list
可能无法被正确地传递。)
-
附:Reflective PE Loader
从内存中加载 DLL 或是运行 EXE 是一个难题,这里列出几次尝试。
加载一个 DLL 时,LoadLibrary
其实做了两件事:修复 reloc,导入 IAT。
在本进程加载 DLL 时,只需在内存中手动解析 PE 头结构,修复 reloc 段,构建 IAT 表,然后转到 DllMain
就可以了。
本进程的 Reflective DLL Loader 是成功的,只是 DllMain
需要保持阻塞否则调用 DLL 的导出函数时会发生错误(或许可以用 GetProcAddress
避免?)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
typedef struct BASE_RELOCATION_BLOCK { DWORD PageAddress; DWORD BlockSize; } BASE_RELOCATION_BLOCK, * PBASE_RELOCATION_BLOCK; typedef struct BASE_RELOCATION_ENTRY { WORD Offset : 12; WORD Type : 4; } BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY; typedef BOOL(WINAPI* tpDllMain)(HMODULE, DWORD, LPVOID); void PLoadDll(LPVOID imageBase) { // pe headers PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)imageBase; PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)imageBase + dosHeaders->e_lfanew); DWORD_PTR deltaImageBase = (DWORD_PTR)imageBase - (DWORD_PTR)ntHeaders->OptionalHeader.ImageBase; // perform image base relocation IMAGE_DATA_DIRECTORY relocations = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; DWORD_PTR relocationTable = relocations.VirtualAddress + (DWORD_PTR)imageBase; DWORD relocationsProcessed = 0; while (relocationsProcessed < relocations.Size) { PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)(relocationTable + relocationsProcessed); relocationsProcessed += sizeof(BASE_RELOCATION_BLOCK); DWORD relocationsCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY); PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)(relocationTable + relocationsProcessed); for (DWORD i = 0; i < relocationsCount; i++) { relocationsProcessed += sizeof(BASE_RELOCATION_ENTRY); if (relocationEntries[i].Type == 0) continue; DWORD_PTR relocationRVA = relocationBlock->PageAddress + relocationEntries[i].Offset; DWORD_PTR writePosition = (DWORD_PTR)imageBase + relocationRVA; DWORD oldProtect = 0; VirtualProtect((LPVOID)writePosition, sizeof(DWORD_PTR), PAGE_READWRITE, &oldProtect); DWORD_PTR *addressToPatch = (DWORD_PTR*)writePosition; *addressToPatch += deltaImageBase; VirtualProtect((LPVOID)writePosition, sizeof(DWORD_PTR), oldProtect, &oldProtect); } } // resolve import address table PIMAGE_IMPORT_DESCRIPTOR importDescriptor = NULL; IMAGE_DATA_DIRECTORY importsDirectory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(importsDirectory.VirtualAddress + (DWORD_PTR)imageBase); LPCSTR libraryName = NULL; HMODULE library = NULL; PIMAGE_IMPORT_BY_NAME functionName = NULL; while (importDescriptor->Name != NULL) { libraryName = (LPCSTR)(importDescriptor->Name + (DWORD_PTR)imageBase); library = LoadLibraryA(libraryName); if (library) { PIMAGE_THUNK_DATA originalFirstThunk = NULL, firstThunk = NULL; originalFirstThunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)imageBase + importDescriptor->OriginalFirstThunk); firstThunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)imageBase + importDescriptor->FirstThunk); while (originalFirstThunk->u1.AddressOfData != NULL) { DWORD oldProtect = 0; VirtualProtect((LPVOID)(&firstThunk->u1.Function), sizeof(DWORD_PTR), PAGE_READWRITE, &oldProtect); // import by ordinal or name if (IMAGE_SNAP_BY_ORDINAL(originalFirstThunk->u1.Ordinal)) { LPCSTR functionOrdinal = (LPCSTR)IMAGE_ORDINAL(originalFirstThunk->u1.Ordinal); firstThunk->u1.Function = (DWORD_PTR)GetProcAddress(library, functionOrdinal); } else { PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)imageBase + originalFirstThunk->u1.AddressOfData); DWORD_PTR functionAddress = (DWORD_PTR)GetProcAddress(library, functionName->Name); firstThunk->u1.Function = functionAddress; } VirtualProtect((LPVOID)(&firstThunk->u1.Function), sizeof(DWORD_PTR), oldProtect, &oldProtect); ++originalFirstThunk; ++firstThunk; } } ++importDescriptor; } // entering DllMain tpDllMain DllMain = (tpDllMain)((DWORD_PTR)lp->imageBase + ntHeaders->OptionalHeader.AddressOfEntryPoint); (*DllMain)((HMODULE)lp->imageBase, DLL_PROCESS_ATTACH, NULL); } |
而远程 Reflective DLL Loader 一个显而易见的思路就是把这个 Loader(修复 reloc,IAT)功能注入到远程进程中执行。(如下代码)
然而这么做有一定失败的机率,推测原因为 ASLR 导致 kernel32.dll 中 API 的地址发生了变化,一种可行的改进方法为不借助其它 WinAPI 而找到这些函数的地址(待坑)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
typedef HMODULE(WINAPI* tpLoadLibraryA)(LPCSTR); typedef FARPROC(WINAPI* tpGetProcAddress)(HMODULE, LPCSTR); typedef BOOL(WINAPI* tpVirtualProtect)(LPVOID, SIZE_T, DWORD, PDWORD); typedef BOOL(WINAPI* tpDllMain)(HMODULE, DWORD, LPVOID); struct PLoaderParam { LPVOID imageBase; tpLoadLibraryA fnLoadLibraryA; tpGetProcAddress fnGetProcAddress; tpVirtualProtect fnVirtualProtect; }; // injected thread DWORD WINAPI PFixDll(LPVOID param) { PLoaderParam* lp = (PLoaderParam*)param; // same as above ... } // used to compute offset DWORD WINAPI _p2stub() { return 0; } bool PLoadDll(HANDLE hProcess, LPVOID lpBuffer, DWORD dwLength) { // load dll into remote process PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)lpBuffer; PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)lpBuffer + dosHeaders->e_lfanew); SIZE_T imageSize = ntHeaders->OptionalHeader.SizeOfImage; LPVOID lpRemoteBuffer = VirtualAllocEx(hProcess, NULL, imageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (lpRemoteBuffer == NULL) return false; // copy header/section if(!WriteProcessMemory(hProcess, lpRemoteBuffer, lpBuffer, ntHeaders->OptionalHeader.SizeOfHeaders, NULL)) return false; PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders); for (size_t i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) { if (!WriteProcessMemory(hProcess, (LPVOID)((DWORD_PTR)lpRemoteBuffer + section->VirtualAddress), (LPVOID)((DWORD_PTR)lpBuffer + section->PointerToRawData), section->SizeOfRawData, NULL)) return false; static const ULONG mapping[] = { PAGE_NOACCESS, PAGE_EXECUTE, PAGE_READONLY, PAGE_EXECUTE_READ, PAGE_READWRITE, PAGE_EXECUTE_READWRITE, PAGE_READWRITE, PAGE_EXECUTE_READWRITE }; // proper page protection DWORD oldProtect = 0; VirtualProtectEx(hProcess, (LPVOID)((DWORD_PTR)lpRemoteBuffer + section->VirtualAddress), section->SizeOfRawData, mapping[section->Characteristics >> 29], &oldProtect); ++section; } PLoaderParam lp{}; lp.imageBase = lpRemoteBuffer; lp.fnLoadLibraryA = LoadLibraryA; lp.fnGetProcAddress = GetProcAddress; lp.fnVirtualProtect = VirtualProtect; // load remote thread DWORD szRLoader = (DWORD_PTR)_p2stub - (DWORD_PTR)PFixDll; LPVOID lpRLoader = VirtualAllocEx(hProcess, NULL, szRLoader, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); LPVOID lpRLoaderParam = VirtualAllocEx(hProcess, NULL, sizeof(lp), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (lpRLoader == NULL || lpRLoaderParam == NULL) return false; if (!WriteProcessMemory(hProcess, lpRLoader, PFixDll, szRLoader, NULL)) return false; if (!WriteProcessMemory(hProcess, lpRLoaderParam, &lp, sizeof(lp), NULL)) return false; HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRLoader, lpRLoaderParam, 0, NULL); if (hThread == NULL) return false; //WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); return true; } |
而本地加载 EXE 同理,修复 reloc,构建 IAT,然后转到入口点执行。代码基本同上。
然而加载大多数程序时却失败了(有个例能成功),原因未知。
远程加载 EXE 更不用说,有看到一种写法是先 ZwUnmapViewOfSection
掉原来的区块,手动修复 reloc、IAT 后用 SetThreadContext
覆盖上去,加载大多数程序时也合理地失败了。
以上为一些尝试。