遇到了需要 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 覆盖上去,加载大多数程序时也合理地失败了。
以上为一些尝试。