EASYHOOK_NT_EXPORT DbgAttachDebugger() { /* Description: Attaches a debugger to the current process. This is currently only necessary for RIP-relocation and disassembling. Multiple calls will do nothing. */ NTSTATUS NtStatus; if(hDbgEng != NULL) RETURN; #ifdef _M_X64 if((hDbgEng = LoadLibraryW(L"dbgeng.dll")) == NULL) THROW(STATUS_NOT_SUPPORTED, L"Unable to load 'dbgeng.dll'."); if((CreateDebugEngine = (DebugCreate_PROC*)GetProcAddress(hDbgEng, "DebugCreate")) == NULL) THROW(STATUS_NOT_SUPPORTED, L"'dbgeng.dll' does not export 'DebugCreate'."); // initialize microsoft debugging engine if(!RTL_SUCCESS(CreateDebugEngine(__uuidof(IDebugClient), (void**)&DebugClient))) THROW(STATUS_NOT_SUPPORTED, L"Unable to obtain IDebugClient interface."); if(!RTL_SUCCESS(DebugClient->QueryInterface(__uuidof(IDebugControl), (void**)&DebugControl))) THROW(STATUS_NOT_SUPPORTED, L"Unable to obtain IDebugControl interface."); // attach to current process if(!RTL_SUCCESS(NtStatus = DebugClient->AttachProcess(0, GetCurrentProcessId(), DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND))) { NtStatus = NtStatus; THROW(STATUS_NOT_SUPPORTED, L"Unable to attach debugger to current process."); } // wait for completion if(!RTL_SUCCESS(DebugControl->WaitForEvent(0, 5000))) THROW(STATUS_INTERNAL_ERROR, L"Unable to wait for debugger."); #else hDbgEng = (HMODULE)-1; #endif RETURN; THROW_OUTRO: { DbgDetachDebugger(); } FINALLY_OUTRO: { return NtStatus; } }
EASYHOOK_NT_EXPORT AssociateCustomCLRHost(ICLRRuntimeHost* pClr, RhAssemblyInfo* pRhAssemblies, long assembliesCount) { ULONG ErrorCode = ERROR_SUCCESS; AssemblyManager* pAsmManager = NULL; HostControl* pHostControl = NULL; // Copy pRhAssemblies to a list<AssemblyInfo> list<AssemblyInfo>* pAssemblies = new list<AssemblyInfo>(); for (ULONG assemblyIndex = 0; assemblyIndex < assembliesCount; assemblyIndex++) { RhAssemblyInfo rhAssembly = pRhAssemblies[assemblyIndex]; AssemblyInfo* assembly = new AssemblyInfo(rhAssembly.FullName, rhAssembly.AssemblyLoadPath, rhAssembly.AssemblyDebugInfoPath); pAssemblies->push_back(*assembly); } // Create an instance of the custom CLR Host, with the custom CLR Assembly Loader. pAsmManager = new AssemblyManager(pAssemblies); if (NULL == pAsmManager) UNMANAGED_ERROR(14); pHostControl = new HostControl((IHostAssemblyManager *)pAsmManager); if (NULL == pHostControl) UNMANAGED_ERROR(14); // Associate the custom CLR Host with the CLR. if (!RTL_SUCCESS(pClr->SetHostControl((IHostControl *)pHostControl))) UNMANAGED_ERROR(14); ABORT_ERROR: // Release Resources if (NULL != pAsmManager) pAsmManager->Release(); if (NULL != pHostControl) pHostControl->Release(); return ErrorCode; }
EASYHOOK_NT_INTERNAL LhRelocateRIPRelativeInstruction( ULONGLONG InOffset, ULONGLONG InTargetOffset, BOOL* OutWasRelocated) { /* Description: Check whether the given instruction is RIP relative and relocates it. If it is not RIP relative, nothing is done. Only applicable to 64-bit processes, 32-bit will always return FALSE. Parameters: - InOffset The instruction pointer to check for RIP addressing and relocate. - InTargetOffset The instruction pointer where the RIP relocation should go to. Please note that RIP relocation are relocated relative to the offset you specify here and therefore are still not absolute! - OutWasRelocated TRUE if the instruction was RIP relative and has been relocated, FALSE otherwise. */ #ifndef _M_X64 return FALSE; #else NTSTATUS NtStatus; CHAR Buf[MAX_PATH]; ULONG AsmSize; ULONG64 NextInstr; CHAR Line[MAX_PATH]; LONG Pos; LONGLONG RelAddr; LONGLONG MemDelta = InTargetOffset - InOffset; ULONGLONG RelAddrOffset = 0; LONGLONG RelAddrSign = 1; ASSERT(MemDelta == (LONG)MemDelta,L"reloc.c - MemDelta == (LONG)MemDelta"); *OutWasRelocated = FALSE; // test field... /* BYTE t[10] = {0x8b, 0x05, 0x12, 0x34, 0x56, 0x78}; // udis86 outputs: 0000000000000000 8b0512345678 mov eax, [rip+0x78563412] InOffset = (LONGLONG)t; MemDelta = InTargetOffset - InOffset; */ // Disassemble the current instruction if(!RTL_SUCCESS(LhDisassembleInstruction((void*)InOffset, &AsmSize, Buf, sizeof(Buf), &NextInstr))) THROW(STATUS_INVALID_PARAMETER_1, L"Unable to disassemble entry point. "); // Check that the address is RIP relative (i.e. look for "[rip+") Pos = RtlAnsiIndexOf(Buf, '['); if(Pos < 0) RETURN; if (Buf[Pos + 1] == 'r' && Buf[Pos + 2] == 'i' && Buf[Pos + 3] == 'p' && (Buf[Pos + 4] == '+' || Buf[Pos + 4] == '-')) { /* Support negative relative addresses https://easyhook.codeplex.com/workitem/25592 e.g. Win8.1 64-bit OLEAUT32.dll!VarBoolFromR8 Entry Point: 66 0F 2E 05 DC 25 FC FF ucomisd xmm0, [rip-0x3da24] IP:ffc46d4 Relocated: 66 0F 2E 05 10 69 F6 FF ucomisd xmm0, [rip-0x996f0] IP:100203a0 */ if (Buf[Pos + 4] == '-') RelAddrSign = -1; Pos += 4; // parse content if(RtlAnsiSubString(Buf, Pos + 1, RtlAnsiIndexOf(Buf, ']') - Pos - 1, Line, MAX_PATH) <= 0) RETURN; // Convert HEX string to LONG RelAddr = strtol(Line, NULL, 16); if (!RelAddr) RETURN; // Apply correct sign RelAddr *= RelAddrSign; // Verify that we are really RIP relative (i.e. must be 32-bit) if(RelAddr != (LONG)RelAddr) RETURN; /* Ensure the RelAddr is equal to the RIP address in code https://easyhook.codeplex.com/workitem/25487 Thanks to Michal for pointing out that the operand will not always be at *(NextInstr - 4) e.g. Win8.1 64-bit OLEAUT32.dll!GetVarConversionLocaleSetting Entry Point: 83 3D 71 08 06 00 00 cmp dword [rip+0x60871], 0x0 IP:ffa1937 Relocated: 83 3D 09 1E 0B 00 00 cmp dword [rip+0xb1e09], 0x0 IP:ff5039f */ for (Pos = 1; Pos <= NextInstr - InOffset - 4; Pos++) { if (*((LONG*)(InOffset + Pos)) == RelAddr) { if (RelAddrOffset != 0) { // More than one offset matches the address, therefore we can't determine correct offset for operand RelAddrOffset = 0; break; } RelAddrOffset = Pos; } } if (RelAddrOffset == 0) { THROW(STATUS_INTERNAL_ERROR, L"The given entry point contains a RIP-relative instruction for which we can't determine the correct address offset!"); } /* Relocate this instruction... */ // Adjust the relative address RelAddr = RelAddr - MemDelta; // Ensure the RIP address can still be relocated if(RelAddr != (LONG)RelAddr) THROW(STATUS_NOT_SUPPORTED, L"The given entry point contains at least one RIP-Relative instruction that could not be relocated!"); // Copy instruction to target RtlCopyMemory((void*)InTargetOffset, (void*)InOffset, (ULONG)(NextInstr - InOffset)); // Correct the rip address *((LONG*)(InTargetOffset + RelAddrOffset)) = (LONG)RelAddr; *OutWasRelocated = TRUE; } RETURN; THROW_OUTRO: FINALLY_OUTRO: return NtStatus; #endif }
EASYHOOK_NT_INTERNAL CompleteManagedInjection(LPREMOTE_INFO InInfo) { /* Description: Loads the NET runtime into the calling process and invokes the managed injection entry point (EasyHook.InjectionLoader.Main). If InInfo->UserLibrary provides Load/Close exports then these will be called to initiate .NET rather than manually loading the framework. This provides a more reliable approach and greater control over the .NET Framework version that is loaded. The EasyLoad32 and EasyLoad64 .NET assemblies provide these exports. */ ICLRMetaHost* MetaClrHost = NULL; ICLRRuntimeInfo* RuntimeInfo = NULL; ICLRRuntimeHost* RuntimeClrHost = NULL; DWORD ErrorCode = 0; WCHAR ParamString[MAX_PATH]; REMOTE_ENTRY_INFO EntryInfo; // Support for loading EasyLoad32/64.dll HMODULE userLib = GetModuleHandleW(InInfo->UserLibrary); PROC_UserLibraryLoad* userLibLoad = NULL; PROC_UserLibraryClose* userLibClose = NULL; HMODULE hMsCorEE = NULL; PROC_CorBindToRuntime* CorBindToRuntime = NULL; // .NET 2.0/3.5 framework creation method PROC_CLRCreateInstance* CLRCreateInstance = NULL; // .NET 4.0+ framework creation method DWORD RetVal; bool UseCorBindToRuntime = false; // invoke user defined entry point EntryInfo.HostPID = InInfo->HostProcess; EntryInfo.UserData = InInfo->UserData; EntryInfo.UserDataSize = InInfo->UserDataSize; // If library is not already loaded (GetModuleHandleW) then try to load it if (userLib == NULL) { userLib = LoadLibraryW(InInfo->UserLibrary); } // Attempt to load userLib Load export for preparing the .NET environment // NOTE: the framework version that userLib (usually EasyLoad32/64.dll) is compiled // with is the version that will be initialised. if (userLib != NULL) { userLibLoad = (PROC_UserLibraryLoad*)GetProcAddress(userLib, "Load"); userLibClose = (PROC_UserLibraryClose*)GetProcAddress(userLib, "Close"); } if (userLibLoad != NULL) { // The userLib provides the Load export so use it to initialise .NET // NOTE: The first call to userLibLoad simply intialises the IPC channel // used by EasyHook to confirm the injection. The target assembly // is not loaded here, but within the second call. __try { RtlLongLongToUnicodeHex((LONGLONG)&EntryInfo, ParamString); if(!RTL_SUCCESS(RetVal = userLibLoad(ParamString))) { DEBUGOUT("Failed to call Loader.Load"); UNMANAGED_ERROR(15); } // Set and close event, the host will now stop waiting for the injection to complete if(!SetEvent(InInfo->hRemoteSignal)) UNMANAGED_ERROR(22); CloseHandle(InInfo->hRemoteSignal); InInfo->hRemoteSignal = NULL; // This is the second call that providing the IPC channel was successfully // initiated, will load the target assembly and call the matching Run method // on the IEntryPoint. userLibLoad(ParamString); // If the library implements a clean-up method, call it if (userLibClose != NULL) userLibClose(); } __except(EXCEPTION_EXECUTE_HANDLER) { goto ABORT_ERROR; } }
EASYHOOK_NT_EXPORT HookCompleteInjection(LPREMOTE_INFO InInfo) { /* Description: This method is directly called from assembler code. It will update the symbols with the ones of the current process, adjust the PATH variable and invoke one of two above injection completions. */ WCHAR* PATH = NULL; ULONG ErrorCode = 0; HMODULE hMod = GetModuleHandleA("kernel32.dll"); ULONG DirSize; ULONG EnvSize; /* To increase stability we will now update all symbols with the real local ones... */ InInfo->LoadLibraryW = GetProcAddress(hMod, "LoadLibraryW"); InInfo->FreeLibrary = GetProcAddress(hMod, "FreeLibrary"); InInfo->GetProcAddress = GetProcAddress(hMod, "GetProcAddress"); InInfo->VirtualFree = GetProcAddress(hMod, "VirtualFree"); InInfo->VirtualProtect = GetProcAddress(hMod, "VirtualProtect"); InInfo->ExitThread = GetProcAddress(hMod, "ExitThread"); InInfo->GetLastError = GetProcAddress(hMod, "GetLastError"); /* Make directory of user library path available to current process... This is to find dependencies without copying them into a global directory which might cause trouble. */ DirSize = RtlUnicodeLength(InInfo->PATH); EnvSize = GetEnvironmentVariableW(L"PATH", NULL, 0) + DirSize; if((PATH = (wchar_t*)RtlAllocateMemory(TRUE, EnvSize * 2 + 10)) == NULL) UNMANAGED_ERROR(1); GetEnvironmentVariableW(L"PATH", PATH, EnvSize); // add library path to environment variable if(!RtlMoveMemory(PATH + DirSize, PATH, (EnvSize - DirSize) * 2)) UNMANAGED_ERROR(1); RtlCopyMemory(PATH, InInfo->PATH, DirSize * 2); if(!SetEnvironmentVariableW(L"PATH", PATH)) UNMANAGED_ERROR(2); if(!RTL_SUCCESS(RhSetWakeUpThreadID(InInfo->WakeUpThreadID))) UNMANAGED_ERROR(3); // load and execute user library... if(InInfo->IsManaged) ErrorCode = CompleteManagedInjection(InInfo); else ErrorCode = CompleteUnmanagedInjection(InInfo); ABORT_ERROR: // release resources if(PATH != NULL) RtlFreeMemory(PATH); if(InInfo->hRemoteSignal != NULL) CloseHandle(InInfo->hRemoteSignal); return ErrorCode; }
EASYHOOK_NT_INTERNAL CompleteManagedInjection(LPREMOTE_INFO InInfo) { /* Description: Loads the NET runtime into the calling process and invokes the managed injection entry point (EasyHook.InjectionLoader.Main). */ ICLRMetaHost* MetaClrHost = NULL; ICLRRuntimeInfo* RuntimeInfo = NULL; ICLRRuntimeHost* RuntimeClrHost = NULL; DWORD ErrorCode = 0; WCHAR ParamString[MAX_PATH]; REMOTE_ENTRY_INFO EntryInfo; HMODULE hMsCorEE = LoadLibraryA("mscoree.dll"); PROC_CorBindToRuntime* CorBindToRuntime = (PROC_CorBindToRuntime*)GetProcAddress(hMsCorEE, "CorBindToRuntime"); // .NET 2.0/3.5 framework creation method PROC_CLRCreateInstance* CLRCreateInstance = (PROC_CLRCreateInstance*)GetProcAddress(hMsCorEE, "CLRCreateInstance"); // .NET 4.0+ framework creation method DWORD RetVal; bool UseCorBindToRuntime = false; if(CorBindToRuntime == NULL && CLRCreateInstance == NULL) UNMANAGED_ERROR(10); // mscoree.dll does not exist or does not expose either of the framework creation methods UseCorBindToRuntime = CLRCreateInstance == NULL; // invoke user defined entry point EntryInfo.HostPID = InInfo->HostProcess; EntryInfo.UserData = InInfo->UserData; EntryInfo.UserDataSize = InInfo->UserDataSize; if (!UseCorBindToRuntime) { // Attempt to use .NET 3.5/4 runtime object creation method rather than deprecated .NET 2.0 CorBindToRuntime if (FAILED(CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&MetaClrHost))) { // Failed to get a MetaHost instance DEBUGOUT("Failed to retrieve CLRMetaHost instance"); UseCorBindToRuntime = true; } else { /* It is possible to create a ICLRRuntimeInfo object based on the runtime required by InInfo->UserLibrary however this requires that the assembly containing the "EasyHook.InjectionLoader" (usually EasyHook.dll) must be targetting the same framework version as the assembly(ies) to be injected. This is because the first CLR assembly injected into the target process in this case is usually the EasyHook.dll assembly. So instead we are providing a specific .NET version (for now v4.0.30319), this will need to be passed as a parameter in the future. */ // TODO: add documentation about what happens when injecting into a managed process where the .NET framework is already loaded LPCWSTR frameworkVersion = L"v4.0.30319"; // TODO: .NET version string to be passed in "InInfo" if (FAILED(MetaClrHost->GetRuntime( frameworkVersion, IID_ICLRRuntimeInfo, (LPVOID*)&RuntimeInfo))) { // .NET version requested is not available DEBUGOUT("Failed to retrieve runtime info for framework version: %s", frameworkVersion); UseCorBindToRuntime = true; } else { if (FAILED(RuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&RuntimeClrHost))) { // Failed to create the requested CLR host version // TODO: add documentation about why this might happen - e.g. does this happen if an older framework version is already loaded by the target? DEBUGOUT("Failed to create CLR host for framework version: %s", frameworkVersion); UseCorBindToRuntime = true; } else { RuntimeClrHost->Start(); // Enable support for running older .NET v2 assemblies within .NET v4 (e.g. EasyHook.dll is .NET 2.0) if (FAILED(RuntimeInfo->BindAsLegacyV2Runtime())) DEBUGOUT("Unable to BindAsLegacyV2Runtime"); } } } } if (UseCorBindToRuntime) { // load NET-Runtime and execute user defined method if(!RTL_SUCCESS(CorBindToRuntime(NULL, NULL, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (void**)&RuntimeClrHost))) { DEBUGOUT("CorBindToRuntime failed"); UNMANAGED_ERROR(11); } RuntimeClrHost->Start(); } /* Test library code. This is because if once we have set the remote signal, there is no way to notify the host about errors. If the following call succeeds, then it will also do so some lines later... If not, then we are still able to report an error. The EasyHook managed injection loader will establish a connection to the host, so that further error reporting is still possible after we set the event! */ RtlLongLongToUnicodeHex((LONGLONG)&EntryInfo, ParamString); if(!RTL_SUCCESS(RuntimeClrHost->ExecuteInDefaultAppDomain( InInfo->UserLibrary, L"EasyHook.InjectionLoader", L"Main", ParamString, &RetVal))) { if (UseCorBindToRuntime) UNMANAGED_ERROR(12); // We already tried the CorBindToRuntime method and it has failed // Running the assembly in the .NET 4.0 Runtime did not work; // Stop and attempt to run it in the .NET 2.0/3.5 Runtime if(RuntimeClrHost != NULL) RuntimeClrHost->Release(); if(!RTL_SUCCESS(CorBindToRuntime(NULL, NULL, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (void**)&RuntimeClrHost))) UNMANAGED_ERROR(11); RuntimeClrHost->Start(); RtlLongLongToUnicodeHex((LONGLONG)&EntryInfo, ParamString); if(!RTL_SUCCESS(RuntimeClrHost->ExecuteInDefaultAppDomain(InInfo->UserLibrary, L"EasyHook.InjectionLoader", L"Main", ParamString, &RetVal))) UNMANAGED_ERROR(14); // Execution under both .NET 4 and .NET 2/3.5 failed (new ErrorCode: 14, for EasyHook 2.7) } if(!RetVal) UNMANAGED_ERROR(13); // set and close event if(!SetEvent(InInfo->hRemoteSignal)) UNMANAGED_ERROR(22); CloseHandle(InInfo->hRemoteSignal); InInfo->hRemoteSignal = NULL; // execute library code (no way for error reporting, so we dont need to check) RuntimeClrHost->ExecuteInDefaultAppDomain( InInfo->UserLibrary, L"EasyHook.InjectionLoader", L"Main", ParamString, &RetVal); ABORT_ERROR: // release resources if(MetaClrHost != NULL) MetaClrHost->Release(); if(RuntimeInfo != NULL) RuntimeInfo->Release(); if(RuntimeClrHost != NULL) RuntimeClrHost->Release(); if(hMsCorEE != NULL) FreeLibrary(hMsCorEE); return ErrorCode; }
EASYHOOK_NT_EXPORT RhInjectLibrary( ULONG InTargetPID, ULONG InWakeUpTID, ULONG InInjectionOptions, WCHAR* InLibraryPath_x86, WCHAR* InLibraryPath_x64, PVOID InPassThruBuffer, ULONG InPassThruSize) { /* Description: Injects a library into the target process. This is a very stable operation. The problem so far is, that only the NET layer will support injection through WOW64 boundaries and into other terminal sessions. It is quite complex to realize with unmanaged code and that's why it is not supported! If you really need this feature I highly recommend to at least look at C++.NET because using the managed injection can speed up your development progress about orders of magnitudes. I know by experience that writing the required multi-process injection code in any unmanaged language is a rather daunting task! Parameters: - InTargetPID The process in which the library should be injected. - InWakeUpTID If the target process was created suspended (RhCreateAndInject), then this parameter should be set to the main thread ID of the target. You may later resume the process from within the injected library by calling RhWakeUpProcess(). If the process is already running, you should specify zero. - InInjectionOptions All flags can be combined. EASYHOOK_INJECT_DEFAULT: No special behavior. The given libraries are expected to be unmanaged DLLs. Further they should export an entry point named "NativeInjectionEntryPoint" (in case of 64-bit) and "_NativeInjectionEntryPoint@4" (in case of 32-bit). The expected entry point signature is REMOTE_ENTRY_POINT. EASYHOOK_INJECT_MANAGED: The given user library is a NET assembly. Further they should export a class named "EasyHook.InjectionLoader" with a static method named "Main". The signature of this method is expected to be "int (String)". Please refer to the managed injection loader of EasyHook for more information about writing such managed entry points. EASYHOOK_INJECT_STEALTH: Uses the experimental stealth thread creation. If it fails you may try it with default settings. EASYHOOK_INJECT_HEART_BEAT: Is only used internally to workaround the managed process creation bug. For curiosity, NET seems to hijack our remote thread if a managed process is created suspended. It doesn't do anything with the suspended main thread, - InLibraryPath_x86 A relative or absolute path to the 32-bit version of the user library being injected. If you don't want to inject into 32-Bit processes, you may set this parameter to NULL. - InLibraryPath_x64 A relative or absolute path to the 64-bit version of the user library being injected. If you don't want to inject into 64-Bit processes, you may set this parameter to NULL. - InPassThruBuffer An optional buffer containg data to be passed to the injection entry point. Such data is available in both, the managed and unmanaged user library entry points. Set to NULL if no used. - InPassThruSize Specifies the size in bytes of the pass thru data. If "InPassThruBuffer" is NULL, this parameter shall also be zero. Returns: */ HANDLE hProc = NULL; HANDLE hRemoteThread = NULL; HANDLE hSignal = NULL; UCHAR* RemoteInjectCode = NULL; LPREMOTE_INFO Info = NULL; LPREMOTE_INFO RemoteInfo = NULL; ULONG RemoteInfoSize = 0; BYTE* Offset = 0; ULONG CodeSize; BOOL Is64BitTarget; NTSTATUS NtStatus; LONGLONG Diff; HANDLE Handles[2]; ULONG UserLibrarySize; ULONG PATHSize; ULONG EasyHookPathSize; ULONG EasyHookEntrySize; ULONG Code; SIZE_T BytesWritten; WCHAR UserLibrary[MAX_PATH+1]; WCHAR PATH[MAX_PATH + 1]; WCHAR EasyHookPath[MAX_PATH + 1]; #ifdef _M_X64 CHAR* EasyHookEntry = "HookCompleteInjection"; #else CHAR* EasyHookEntry = "_HookCompleteInjection@4"; #endif // validate parameters if(InPassThruSize > MAX_PASSTHRU_SIZE) THROW(STATUS_INVALID_PARAMETER_7, L"The given pass thru buffer is too large."); if(InPassThruBuffer != NULL) { if(!IsValidPointer(InPassThruBuffer, InPassThruSize)) THROW(STATUS_INVALID_PARAMETER_6, L"The given pass thru buffer is invalid."); } else if(InPassThruSize != 0) THROW(STATUS_INVALID_PARAMETER_7, L"If no pass thru buffer is specified, the pass thru length also has to be zero."); if(InTargetPID == GetCurrentProcessId()) THROW(STATUS_NOT_SUPPORTED, L"For stability reasons it is not supported to inject into the calling process."); // open target process if((hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, InTargetPID)) == NULL) { if(GetLastError() == ERROR_ACCESS_DENIED) THROW(STATUS_ACCESS_DENIED, L"Unable to open target process. Consider using a system service.") else THROW(STATUS_NOT_FOUND, L"The given target process does not exist!"); } /* Check bitness... After this we can assume hooking a target that is running in the same WOW64 level. */ #ifdef _M_X64 FORCE(RhIsX64Process(InTargetPID, &Is64BitTarget)); if(!Is64BitTarget) THROW(STATUS_WOW_ASSERTION, L"It is not supported to directly hook through the WOW64 barrier."); if(!GetFullPathNameW(InLibraryPath_x64, MAX_PATH, UserLibrary, NULL)) THROW(STATUS_INVALID_PARAMETER_5, L"Unable to get full path to the given 64-bit library."); #else FORCE(RhIsX64Process(InTargetPID, &Is64BitTarget)); if(Is64BitTarget) THROW(STATUS_WOW_ASSERTION, L"It is not supported to directly hook through the WOW64 barrier."); if(!GetFullPathNameW(InLibraryPath_x86, MAX_PATH, UserLibrary, NULL)) THROW(STATUS_INVALID_PARAMETER_4, L"Unable to get full path to the given 32-bit library."); #endif /* Validate library path... */ if(!RtlFileExists(UserLibrary)) { #ifdef _M_X64 THROW(STATUS_INVALID_PARAMETER_5, L"The given 64-Bit library does not exist!"); #else THROW(STATUS_INVALID_PARAMETER_4, L"The given 32-Bit library does not exist!"); #endif } // import strings... RtlGetWorkingDirectory(PATH, MAX_PATH - 1); RtlGetCurrentModulePath(EasyHookPath, MAX_PATH); // allocate remote information block EasyHookPathSize = (RtlUnicodeLength(EasyHookPath) + 1) * 2; EasyHookEntrySize = (RtlAnsiLength(EasyHookEntry) + 1); PATHSize = (RtlUnicodeLength(PATH) + 1 + 1) * 2; UserLibrarySize = (RtlUnicodeLength(UserLibrary) + 1 + 1) * 2; PATH[PATHSize / 2 - 2] = ';'; PATH[PATHSize / 2 - 1] = 0; RemoteInfoSize = EasyHookPathSize + EasyHookEntrySize + PATHSize + InPassThruSize + UserLibrarySize; RemoteInfoSize += sizeof(REMOTE_INFO); if((Info = (LPREMOTE_INFO)RtlAllocateMemory(TRUE, RemoteInfoSize)) == NULL) THROW(STATUS_NO_MEMORY, L"Unable to allocate memory in current process."); Info->LoadLibraryW = (PVOID)GetProcAddress(hKernel32, "LoadLibraryW"); Info->FreeLibrary = (PVOID)GetProcAddress(hKernel32, "FreeLibrary"); Info->GetProcAddress = (PVOID)GetProcAddress(hKernel32, "GetProcAddress"); Info->VirtualFree = (PVOID)GetProcAddress(hKernel32, "VirtualFree"); Info->VirtualProtect = (PVOID)GetProcAddress(hKernel32, "VirtualProtect"); Info->ExitThread = (PVOID)GetProcAddress(hKernel32, "ExitThread"); Info->GetLastError = (PVOID)GetProcAddress(hKernel32, "GetLastError"); Info->WakeUpThreadID = InWakeUpTID; Info->IsManaged = InInjectionOptions & EASYHOOK_INJECT_MANAGED; // allocate memory in target process CodeSize = GetInjectionSize(); if((RemoteInjectCode = (BYTE*)VirtualAllocEx(hProc, NULL, CodeSize + RemoteInfoSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) == NULL) THROW(STATUS_NO_MEMORY, L"Unable to allocate memory in target process."); // save strings Offset = (BYTE*)(Info + 1); Info->EasyHookEntry = (char*)Offset; Info->EasyHookPath = (wchar_t*)(Offset += EasyHookEntrySize); Info->PATH = (wchar_t*)(Offset += EasyHookPathSize); Info->UserData = (BYTE*)(Offset += PATHSize); Info->UserLibrary = (WCHAR*)(Offset += InPassThruSize); Info->Size = RemoteInfoSize; Info->HostProcess = GetCurrentProcessId(); Info->UserDataSize = 0; Offset += UserLibrarySize; if((ULONG)(Offset - ((BYTE*)Info)) > Info->Size) THROW(STATUS_BUFFER_OVERFLOW, L"A buffer overflow in internal memory was detected."); RtlCopyMemory(Info->EasyHookPath, EasyHookPath, EasyHookPathSize); RtlCopyMemory(Info->PATH, PATH, PATHSize); RtlCopyMemory(Info->EasyHookEntry, EasyHookEntry, EasyHookEntrySize); RtlCopyMemory(Info->UserLibrary, UserLibrary, UserLibrarySize); if(InPassThruBuffer != NULL) { RtlCopyMemory(Info->UserData, InPassThruBuffer, InPassThruSize); Info->UserDataSize = InPassThruSize; } // copy code into target process if(!WriteProcessMemory(hProc, RemoteInjectCode, GetInjectionPtr(), CodeSize, &BytesWritten) || (BytesWritten != CodeSize)) THROW(STATUS_INTERNAL_ERROR, L"Unable to write into target process memory."); // create and export signal event> if((hSignal = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) THROW(STATUS_INSUFFICIENT_RESOURCES, L"Unable to create event."); // Possible resource leck: the remote handles cannt be closed here if an error occurs if(!DuplicateHandle(GetCurrentProcess(), hSignal, hProc, &Info->hRemoteSignal, EVENT_ALL_ACCESS, FALSE, 0)) THROW(STATUS_INTERNAL_ERROR, L"Failed to duplicate remote event."); // relocate remote information RemoteInfo = (LPREMOTE_INFO)(RemoteInjectCode + CodeSize); Diff = ((BYTE*)RemoteInfo - (BYTE*)Info); Info->EasyHookEntry = (char*)(((BYTE*)Info->EasyHookEntry) + Diff); Info->EasyHookPath = (wchar_t*)(((BYTE*)Info->EasyHookPath) + Diff); Info->PATH = (wchar_t*)(((BYTE*)Info->PATH) + Diff); Info->UserLibrary = (wchar_t*)(((BYTE*)Info->UserLibrary) + Diff); if(Info->UserData != NULL) Info->UserData = (BYTE*)(((BYTE*)Info->UserData) + Diff); Info->RemoteEntryPoint = RemoteInjectCode; if(!WriteProcessMemory(hProc, RemoteInfo, Info, RemoteInfoSize, &BytesWritten) || (BytesWritten != RemoteInfoSize)) THROW(STATUS_INTERNAL_ERROR, L"Unable to write into target process memory."); if((InInjectionOptions & EASYHOOK_INJECT_STEALTH) != 0) { FORCE(RhCreateStealthRemoteThread(InTargetPID, (LPTHREAD_START_ROUTINE)RemoteInjectCode, RemoteInfo, &hRemoteThread)); } else { if(!RTL_SUCCESS(NtCreateThreadEx(hProc, (LPTHREAD_START_ROUTINE)RemoteInjectCode, RemoteInfo, FALSE, &hRemoteThread))) { // create remote thread and wait for injection completion if((hRemoteThread = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)RemoteInjectCode, RemoteInfo, 0, NULL)) == NULL) THROW(STATUS_ACCESS_DENIED, L"Unable to create remote thread."); } } /* * The assembler codes are designed to let us derive extensive error information... */ Handles[1] = hSignal; Handles[0] = hRemoteThread; Code = WaitForMultipleObjects(2, Handles, FALSE, INFINITE); if(Code == WAIT_OBJECT_0) { // parse error code GetExitCodeThread(hRemoteThread, &Code); SetLastError(Code & 0x0FFFFFFF); switch(Code & 0xF0000000) { case 0x10000000: THROW(STATUS_INTERNAL_ERROR, L"Unable to find internal entry point."); case 0x20000000: THROW(STATUS_INTERNAL_ERROR, L"Unable to make stack executable."); case 0x30000000: THROW(STATUS_INTERNAL_ERROR, L"Unable to release injected library."); case 0x40000000: THROW(STATUS_INTERNAL_ERROR, L"Unable to find EasyHook library in target process context."); case 0xF0000000: // error in C++ injection completion { switch(Code & 0xFF) { #ifdef _M_X64 case 20: THROW(STATUS_INVALID_PARAMETER_5, L"Unable to load the given 64-bit library into target process."); case 21: THROW(STATUS_INVALID_PARAMETER_5, L"Unable to find the required native entry point in the given 64-bit library."); case 12: THROW(STATUS_INVALID_PARAMETER_5, L"Unable to find the required managed entry point in the given 64-bit library."); #else case 20: THROW(STATUS_INVALID_PARAMETER_4, L"Unable to load the given 32-bit library into target process."); case 21: THROW(STATUS_INVALID_PARAMETER_4, L"Unable to find the required native entry point in the given 32-bit library."); case 12: THROW(STATUS_INVALID_PARAMETER_4, L"Unable to find the required managed entry point in the given 32-bit library."); #endif case 13: THROW(STATUS_DLL_INIT_FAILED, L"The user defined managed entry point failed in the target process. Make sure that EasyHook is registered in the GAC. Refer to event logs for more information."); case 1: THROW(STATUS_INTERNAL_ERROR, L"Unable to allocate memory in target process."); case 2: THROW(STATUS_INTERNAL_ERROR, L"Unable to adjust target's PATH variable."); case 10: THROW(STATUS_INTERNAL_ERROR, L"Unable to load 'mscoree.dll' into target process."); case 11: THROW(STATUS_INTERNAL_ERROR, L"Unable to bind NET Runtime to target process."); case 22: THROW(STATUS_INTERNAL_ERROR, L"Unable to signal remote event."); default: THROW(STATUS_INTERNAL_ERROR, L"Unknown error in injected C++ completion routine."); } }break; case 0: THROW(STATUS_INTERNAL_ERROR, L"C++ completion routine has returned success but didn't raise the remote event."); default: THROW(STATUS_INTERNAL_ERROR, L"Unknown error in injected assembler code."); } } else if(Code != WAIT_OBJECT_0 + 1) THROW(STATUS_INTERNAL_ERROR, L"Unable to wait for injection completion due to timeout. "); RETURN; THROW_OUTRO: FINALLY_OUTRO: { // release resources if(hProc != NULL) CloseHandle(hProc); if(Info != NULL) RtlFreeMemory(Info); if(hRemoteThread != NULL) CloseHandle(hRemoteThread); if(hSignal != NULL) CloseHandle(hSignal); return NtStatus; } }
EASYHOOK_NT_INTERNAL DbgRelocateRIPRelative( ULONGLONG InOffset, ULONGLONG InTargetOffset, BOOL* OutWasRelocated) { /* Description: Check whether the given instruction is RIP relative and relocates it. If it is not RIP relative, nothing is done. Parameters: - InOffset The instruction pointer to check for RIP addressing and relocate. - InTargetOffset The instruction pointer where the RIP relocation should go to. Please note that RIP relocation are relocated relative to the offset you specify here and therefore are still not absolute! - OutWasRelocated TRUE if the instruction was RIP relative and has been relocated, FALSE otherwise. */ #ifndef _M_X64 return FALSE; #else NTSTATUS NtStatus; CHAR Buf[MAX_PATH]; ULONG AsmSize; ULONG64 NextInstr; CHAR Line[MAX_PATH]; LONG Pos; LONGLONG RelAddr; LONGLONG MemDelta = InTargetOffset - InOffset; ASSERT(MemDelta == (LONG)MemDelta); *OutWasRelocated = FALSE; // test field... /*BYTE t[10] = {0x8b, 0x05, 0x12, 0x34, 0x56, 0x78}; InOffset = (LONGLONG)t; MemDelta = InTargetOffset - InOffset; */ if(!RTL_SUCCESS(DebugControl->Disassemble(InOffset, 0, Buf, sizeof(Buf), &AsmSize, &NextInstr))) THROW(STATUS_INVALID_PARAMETER_1, L"Unable to disassemble entry point. "); Pos = RtlAnsiIndexOf(Buf, '['); if(Pos < 0) RETURN; // parse content if(RtlAnsiSubString(Buf, Pos + 1, RtlAnsiIndexOf(Buf, ']') - Pos - 1, Line, MAX_PATH) != 16) RETURN; if(!RtlAnsiHexToLongLong(Line, 16, &RelAddr)) RETURN; // verify that we are really RIP relative... RelAddr -= NextInstr; if(RelAddr != (LONG)RelAddr) RETURN; if(*((LONG*)(NextInstr - 4)) != RelAddr) RETURN; /* Just relocate this instruction... */ RelAddr = RelAddr - MemDelta; if(RelAddr != (LONG)RelAddr) THROW(STATUS_NOT_SUPPORTED, L"The given entry point contains at least one RIP-Relative instruction that could not be relocated!"); // copy instruction RtlCopyMemory((void*)InTargetOffset, (void*)InOffset, (ULONG)(NextInstr - InOffset)); *((LONG*)(InTargetOffset + (NextInstr - InOffset) - 4)) = (LONG)RelAddr; *OutWasRelocated = TRUE; RETURN; THROW_OUTRO: FINALLY_OUTRO: return NtStatus; #endif }
EASYHOOK_NT_INTERNAL LhUpdateModuleInformation() { NTSTATUS NtStatus; PSYSTEM_MODULE_INFORMATION NativeList = NULL; ULONG RequiredSize = 0; ULONG i; PSYSTEM_MODULE Mod; MODULE_INFORMATION* List = NULL; if(!LhModuleListChanged) return STATUS_SUCCESS; LhModuleListChanged = FALSE; if(ZwQuerySystemInformation(11, NULL, 0, &RequiredSize) != STATUS_INFO_LENGTH_MISMATCH) THROW(STATUS_INTERNAL_ERROR, L"Unable to enumerate system modules."); if((NativeList = RtlAllocateMemory(TRUE, RequiredSize)) == NULL) THROW(STATUS_NO_MEMORY, L"Unable to allocate memory."); if(!RTL_SUCCESS(ZwQuerySystemInformation(11, NativeList, RequiredSize, &RequiredSize))) THROW(STATUS_INTERNAL_ERROR, L"Unable to enumerate system modules."); if((List = RtlAllocateMemory(FALSE, sizeof(MODULE_INFORMATION) * NativeList->Count)) == NULL) THROW(STATUS_NO_MEMORY, L"Unable to allocate memory."); for(i = 0; i < NativeList->Count; i++) { Mod = &NativeList->Modules[i]; List[i].BaseAddress = Mod->ImageBaseAddress; List[i].ImageSize = Mod->ImageSize; List[i].ModuleName = Mod->Name + Mod->NameOffset; memcpy(List[i].Path, Mod->Name, 256); if(i + 1 < NativeList->Count) List[i].Next = &List[i + 1]; else List[i].Next = NULL; } RtlAcquireLock(&GlobalHookLock); { if(LhNativeModuleArray != NULL) RtlFreeMemory(LhNativeModuleArray); if(LhModuleArray != NULL) RtlFreeMemory(LhModuleArray); LhNativeModuleArray = NativeList; LhModuleArray = List; LhModuleCount = NativeList->Count; } RtlReleaseLock(&GlobalHookLock); RETURN; THROW_OUTRO: { if(NativeList != NULL) RtlFreeMemory(NativeList); if(List != NULL) RtlFreeMemory(List); } FINALLY_OUTRO: return NtStatus; }
EASYHOOK_NT_INTERNAL CompleteManagedInjection(LPREMOTE_INFO InInfo) { /// Region: Temporary workaround for InInfo->Assemblies not being received correctly InInfo->AssembliesCount = 3; InInfo->Assemblies = new RhAssemblyInfo[InInfo->AssembliesCount]; InInfo->Assemblies[0].FullName = L"ProcMonInject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d34a061f079be347, ProcessorArchitecture=MSIL"; InInfo->Assemblies[0].AssemblyLoadPath = L"D:/Documenten/Documenten/Projects/EasyHook/EasyHook - CLRHostingAPI - vs2008/Debug/x64/ProcMonInject.dll"; InInfo->Assemblies[0].AssemblyDebugInfoPath = NULL; InInfo->Assemblies[1].FullName = L"ProcessMonitor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d34a061f079be347, ProcessorArchitecture=MSIL"; InInfo->Assemblies[1].AssemblyLoadPath = L"D:/Documenten/Documenten/Projects/EasyHook/EasyHook - CLRHostingAPI - vs2008/Debug/x64/ProcessMonitor.exe"; InInfo->Assemblies[1].AssemblyDebugInfoPath = NULL; InInfo->Assemblies[2].FullName = L"EasyHook, Version=2.5.0.0, Culture=neutral, PublicKeyToken=4b580fca19d0b0c5, ProcessorArchitecture=MSIL"; InInfo->Assemblies[2].AssemblyLoadPath = L"D:/Documenten/Documenten/Projects/EasyHook/EasyHook - CLRHostingAPI - vs2008/Debug/x64/EasyHook.dll"; InInfo->Assemblies[2].AssemblyDebugInfoPath = NULL; /// Endregion /* Description: Loads the NET runtime into the calling process and invokes the managed injection entry point. */ #ifdef _NET4 ICLRMetaHost* pMetaClrHost = NULL; ICLRRuntimeInfo* pRuntimeInfo = NULL; #endif REMOTE_ENTRY_INFO EntryInfo; ICLRRuntimeHost* pClr = NULL; WCHAR ParamString[MAX_PATH]; DWORD ErrorCode = 0; HMODULE hMsCorEE = LoadLibraryA("mscoree.dll"); PROC_CorBindToRuntime* CorBindToRuntime = (PROC_CorBindToRuntime*)GetProcAddress(hMsCorEE, "CorBindToRuntime"); if(CorBindToRuntime == NULL) UNMANAGED_ERROR(10); // Invoke user defined entry point EntryInfo.HostPID = InInfo->HostProcess; EntryInfo.UserData = InInfo->UserData; EntryInfo.UserDataSize = InInfo->UserDataSize; RtlLongLongToUnicodeHex((LONGLONG)&EntryInfo, ParamString); // Load the Common Language Runtime if(!RTL_SUCCESS(CorBindToRuntime(NULL, NULL, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (void**)&pClr))) UNMANAGED_ERROR(11); if (!RTL_SUCCESS(AssociateCustomCLRHost(pClr, InInfo->Assemblies, InInfo->AssembliesCount))) UNMANAGED_ERROR(14); pClr->Start(); /* Test library code. This is because if once we have set the remote signal, there is no way to notify the host about errors. If the following call succeeds, then it will also do so some lines later... If not, then we are still able to report an error. The EasyHook managed injection loader will establish a connection to the host, so that further error reporting is still possible after we set the event! */ RtlLongLongToUnicodeHex((LONGLONG)&EntryInfo, ParamString); HRESULT result = pClr->ExecuteInDefaultAppDomain( InInfo->UserLibrary, L"EasyHook.InjectionLoader", L"Main", ParamString, &ErrorCode); if(!RTL_SUCCESS(result)) { // Test failed! #ifdef _NET4 // Target CLR might be .NET 4.0, try to load this CLR instead. if(NULL != pClr) pClr->Release(); if(FAILED(CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (void**)&pMetaClrHost))) UNMANAGED_ERROR(11); if(FAILED(pMetaClrHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (void**)&pRuntimeInfo))) UNMANAGED_ERROR(11); if(FAILED(pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (void**) &pClr))) UNMANAGED_ERROR(11); AssociateCustomCLRHost(pClr); pClr->Start(); RtlLongLongToUnicodeHex((LONGLONG)&EntryInfo, ParamString); if(!RTL_SUCCESS(RuntimeClrHost->ExecuteInDefaultAppDomain(InInfo->UserLibrary, L"EasyHook.InjectionLoader", L"Main", ParamString, &RetVal))) #endif UNMANAGED_ERROR(12); } if(!ErrorCode) UNMANAGED_ERROR(13); // set and close event if(!SetEvent(InInfo->hRemoteSignal)) UNMANAGED_ERROR(22); CloseHandle(InInfo->hRemoteSignal); InInfo->hRemoteSignal = NULL; // Execute library code (no way for error reporting, so we dont need to check) pClr->ExecuteInDefaultAppDomain( InInfo->UserLibrary, L"EasyHook.InjectionLoader", L"Main", ParamString, &ErrorCode); ABORT_ERROR: // Release Resources if(NULL != pClr) pClr->Release(); #ifdef _NET4 if (NULL != pMetaClrHost) pMetaClrHost->Release(); if (NULL != pRuntimeInfo) pRuntimeInfo->Release(); #endif return ErrorCode; }