内容简介:.NET是一个强大的编程语言,可以用来快速可靠地开发软件,然而.NET也有其不适用的一些场景。本文重点讲解了DLL注入的一个案例,当未加载.NET Runtime时,无法向一个远程进程中注入.NET DLL(托管DLL)。此外,假设.NET Runtime已经在目标进程中加载,那么应该如何调用.NET DLL中的方法?其结构是什么样的?针对64位进程的操作是否与32位进程不同?阅读本文后,你将会了解如何使用官方文档中记录的API实现这些任务。我们的目标是:1、在32位和64位系统上,在任意进程中启动.NE
源代码
一、概述
.NET是一个强大的编程语言,可以用来快速可靠地开发软件,然而.NET也有其不适用的一些场景。本文重点讲解了DLL注入的一个案例,当未加载.NET Runtime时,无法向一个远程进程中注入.NET DLL(托管DLL)。此外,假设.NET Runtime已经在目标进程中加载,那么应该如何调用.NET DLL中的方法?其结构是什么样的?针对64位进程的操作是否与32位进程不同?阅读本文后,你将会了解如何使用官方文档中记录的API实现这些任务。我们的目标是:
1、在32位和64位系统上,在任意进程中启动.NET CLR(公共语言运行库);
2、在任意进程中,加载自定义.NET程序集;
3、在任意进程的上下文中,执行托管代码。
二、拆分步骤,逐步击破
要实现最终的目标,需要进行几项不同的工作。为了使思路更加清晰,我们将其拆分成几个步骤,并在最后进行组合利用。要解决这个难题,需要进行的步骤是:
1、加载CLR(基础):如何在非托管进程中启动.NET框架。
2、加载CLR(高级):如何加载自定义.NET程序,并从非托管代码调用托管方法。
3、DLL注入(基础):如何在远程进程中执行非托管代码。
4、DLL注入(高级):如何在远程进程中执行任意导出的函数。
5、组合利用:将上述方法组合使用,实现我们最终的目标。
注意:我们在引用C++函数和C#函数时,所使用的都是函数的标准方式。我们所说的“远程”进程,是指除当前进程之外的其他任意进程。
三、加载CLR
我们实现目标的第一步,是编写可以加载.NET Runtime和任意程序集的非托管应用程序。
3.1 基础篇
以下示例程序,展示了C++应用程序如何将.NET Runtime加载到其自身:
#include <metahost.h> #pragma comment(lib, "mscoree.lib") #import "mscorlib.tlb" raw_interfaces_only high_property_prefixes("_get","_put","_putref") rename("ReportEvent", "InteropServices_ReportEvent") int wmain(int argc, wchar_t* argv[]) { char c; wprintf(L"Press enter to load the .net runtime..."); while (getchar() != 'n'); HRESULT hr; ICLRMetaHost *pMetaHost = NULL; ICLRRuntimeInfo *pRuntimeInfo = NULL; ICLRRuntimeHost *pClrRuntimeHost = NULL; // build runtime hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost)); hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo)); hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pClrRuntimeHost)); // start runtime hr = pClrRuntimeHost->Start(); wprintf(L".Net runtime is loaded. Press any key to exit..."); while (getchar() != 'n'); return 0; }
在上述代码中,有4个调用比较重要,其作用如下:
1、CLRCreateInstance – 指定CLSID_CLRMetaHost,获取指向ICLRMetaHost实例的指针;
2、ICLRMetaHost::GetRuntime – 获取指向特定.NET Runtime的ICLRRuntimeInfo类型指针;
3、ICLRRuntimeInfo::GetInterface – 将CLR加载到当前进程,并获取ICLRRuntimeHost指针;
4、ICLRRuntimeHost::Start – 显示启动CLR,在首次加载托管代码时隐式调用。
在撰写本文时,ICLRMetaHost::GetRuntime的有效版本值为NULL、v1.0.3705、v1.1.4322、v2.0.50727和v4.0.30319,其中NULL加载最新版本的Runtime。需要根据操作系统的实际需要,安装相应版本的Runtime,其版本值保存在%WinDir%Microsoft.NETFramework和%WinDir%Microsoft.NETFramework64中。
编译并运行上述代码后,得到输出结果如下:
一旦按下回车键,就可以通过Process Hacker观察到.NET Runtime已被加载。请注意属性窗口上引用.NET的其他选项卡:
上述示例代码不包含在源代码压缩包中,建议读者自行编写并运行示例。
3.2 进阶篇
在将第一块拼图放好之后,接下来就要将任意.NET程序集加载到进程中,并调用该.NET程序集中的方法。
我们在上述示例的基础之上,继续进行下一步工作。在此之前,CLR被加载到进程中,这一过程是通过获取指向CLR接口的指针来实现的,该指针存储在变量pClrRuntimeHost中。使用pClrRuntimeHost调用ICLRRuntimeHost::Start,以实现将CLR初始化到进程之中。
现在,已经完成CLR的初始化工作,pClrRuntimeHost就可以调用ICLRRuntimeHost::ExecuteInDefaultAppDomain来加载和调用任意.NET程序集中的方法。该函数具有如下签名(Signature):
HRESULT ExecuteInDefaultAppDomain ( [in] LPCWSTR pwzAssemblyPath, [in] LPCWSTR pwzTypeName, [in] LPCWSTR pwzMethodName, [in] LPCWSTR pwzArgument, [out] DWORD *pReturnValue );
其中,各参数的简要说明如下:
1、pwzAssemblyPath – .NET程序集的完整路径,可以是exe文件,也可以是dll文件;
2、pwzTypeName – 需调用的方法的完全限定类型名;
3、pwzMethodName – 需调用的方法的名称;
4、pwzArgument – 传递给方法的可选参数;
5、pReturnValue – 方法的返回值。
并非.NET程序集中的每个方法都可以通过ICLRRuntimeHost::ExecuteInDefaultAppDomain调用。有效的.NET方法必须具有如下签名:
static int pwzMethodName (String pwzArgument);
顺便提一句,访问控制修饰符(例如:public、protected、private和internal)不会影响方法的可见性,因此它们被排除在签名之外。
下面的.NET应用程序,适用于所有示例,将托管.NET程序集注入到进程内部:
using System; using System.Windows.Forms; namespace InjectExample { public class Program { static int EntryPoint(String pwzArgument) { System.Media.SystemSounds.Beep.Play(); MessageBox.Show( "I am a managed app.nn" + "I am running inside: [" + System.Diagnostics.Process.GetCurrentProcess().ProcessName + "]nn" + (String.IsNullOrEmpty(pwzArgument) ? "I was not given an argument" : "I was given this argument: [" + pwzArgument + "]")); return 0; } static void Main(string[] args) { EntryPoint("hello world"); } } }
上述代码可以通过ICLRRuntimeHost::ExecuteInDefaultAppDomain调用,也可以独立运行,两种运行方式都可以产生相似的行为。注入非托管远程进程,最终使上述应用程序在该进程的上下文中执行,并弹出一个显示远程进程名称的消息框。
在基础篇的示例代码基础上,我们编写了以下C++程序,用于加载上述.NET程序集并执行EntryPoint方法:
#include <metahost.h> #pragma comment(lib, "mscoree.lib") #import "mscorlib.tlb" raw_interfaces_only high_property_prefixes("_get","_put","_putref") rename("ReportEvent", "InteropServices_ReportEvent") int wmain(int argc, wchar_t* argv[]) { HRESULT hr; ICLRMetaHost *pMetaHost = NULL; ICLRRuntimeInfo *pRuntimeInfo = NULL; ICLRRuntimeHost *pClrRuntimeHost = NULL; // build runtime hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost)); hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo)); hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pClrRuntimeHost)); // start runtime hr = pClrRuntimeHost->Start(); // execute managed assembly DWORD pReturnValue; hr = pClrRuntimeHost->ExecuteInDefaultAppDomain( L"T:\FrameworkInjection\_build\debug\anycpu\InjectExample.exe", L"InjectExample.Program", L"EntryPoint", L"hello .net runtime", &pReturnValue); // free resources pMetaHost->Release(); pRuntimeInfo->Release(); pClrRuntimeHost->Release(); return 0; }
应用程序输出如下:
至此,我们已经拼好了两块拼图。现在,我们已经理解了如何加载CLR,以及如何从非托管代码中执行任意方法。但是,如何在任意进程中实现呢?
四、DLL注入
通过DLL注入,可以在远程进程中加载DLL,从而实现远程进程内代码执行。许多DLL注入方法,都在试图实现DllMain内部代码执行。但是,如果尝试从DllMain中启动CLR,将会导致Windows加载程序的死锁。我们可以编写一个尝试从DllMain中启动CLR的示例DLL,来验证这一点,而验证过程就留给感兴趣的读者作为练习。关于.NET初始化、Windows加载程序和加载程序锁定的更多信息,请阅读以下MSDN文章:
http://msdn.microsoft.com/en-us/library/ms172219(v=VS.100).aspx
http://msdn.microsoft.com/en-us/library/ms173266.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/dd744765(v=vs.85).aspx
当Windows加载程序对另一个模块进行初始化时,将会不可避免地造成CLR无法启动。每个锁都是特定于进程的,由Windows来管理。请注意,任何模块如果已经获得过一个加载程序锁,当其尝试继续获取下一个加载程序锁时,都会导致死锁的发生。
4.1 基础篇
要搞定Windows加载程序,也需要拆分成多个步骤。一开始,我们先尝试如何在远程进程中注入一个非托管DLL,示例代码如下:
#define WIN32_LEAN_AND_MEAN #include <windows.h> BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
上述代码实现了一个简单的DLL。要在远程进程中注入以下DLL,需要用到以下Windows API:
1、OpenProcess – 获取进程的句柄;
2、GetModuleHandle – 获取指定模块的句柄;
3、LoadLibrary – 在调用进程的地址空间中加载库;
4、GetProcAddress – 从库中获取导出函数的VA(虚拟地址);
5、VirtualAllocEx – 在指定进程中分配空间;
6、WriteProcessMemory – 将字节写入指定地址的指定进程;
7、CreateRemoteThread – 在远程进程中生成一个线程。
接下来,在已经加载DLL的远程进程上,执行一个导出函数:
DWORD_PTR Inject(const HANDLE hProcess, const LPVOID function, const wstring& argument) { // allocate some memory in remote process LPVOID baseAddress = VirtualAllocEx(hProcess, NULL, GetStringAllocSize(argument), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // write argument into remote process BOOL isSucceeded = WriteProcessMemory(hProcess, baseAddress, argument.c_str(), GetStringAllocSize(argument), NULL); // make the remote process invoke the function HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)function, baseAddress, NULL, 0); // wait for thread to exit WaitForSingleObject(hThread, INFINITE); // free memory in remote process VirtualFreeEx(hProcess, baseAddress, 0, MEM_RELEASE); // get the thread exit code DWORD exitCode = 0; GetExitCodeThread(hThread, &exitCode); // close thread handle CloseHandle(hThread); // return the exit code return exitCode; }
在这里需要明确,我们的目标是在远程进程中加载一个库。接下来要考虑的是,如何使用上述函数在远程进程中注入DLL。我们将目光放在了kernel32.dll映射到每个进程的地址空间内。LoadLibrary是kernel32.dll的导出函数,它具有与LPTHREAD_START_ROUTINE相匹配的函数签名,因此可以作为启动例程传递给CreateRemoteThread。回想一下,LoadLibrary的目的是在调用进程的地址空间中加载库,而CreateRemoteThread的目的是在远程进程中生成一个线程。下面的部分代码说明了如何在ID为3344的进程中加载示例DLL:
// 使用PID 3344获取远程进程的句柄 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 3344); // 获取LoadLibrary的地址 FARPROC fnLoadLibrary = GetProcAddress(GetModuleHandle(L"Kernel32"), "LoadLibraryW"); // 将test.dll注入到远程进程中 Inject(hProcess, fnLoadLibrary, L"T:\test\test.dll");
一旦CreateRemoteThread被调用,紧接着就会调用WaitForSingleObject以等待线程退出。随后,会调用GetExitCodeThread来获取结果。巧合的是,当LoadLibrary传递到CreateRemoteThread时,成功的调用将导致GetExitCodeThread的lpExitCode在远程进程的上下文中返回已加载库的基址。这一点适用于32位应用程序,但不适用于64位应用程序。原因在于,即使在64位计算机上,GetExitCodeThread的IpExitCode也是DWORD值,因此该地址将会被截断。
这就是我们想要的第三块拼图。总结一下,目前已经解决了以下问题:
1、使用非托管代码加载CLR;
2、从非托管代码执行任意.NET程序集;
3、注入DLL。
4.2 进阶篇
现在,我们已经清楚如何在远程进程中加载DLL。那么,可以继续讨论如何在远程进程中启动CLR。
当LoadLibrary返回时,加载程序上的锁是空闲的。使用远程进程地址空间中的DLL,可以通过后续对CreateRemoteThread的调用来调用导出函数,其函数签名与LPTHREAD_START_ROUTINE相匹配。然而,这里就又产生了一些问题。如何在远程进程内调用导出函数?如何获取指向这些函数的指针?考虑到GetProcAddress没有相匹配的LPTHREAD_START_ROUTINE签名,那么应该如何获取DLL内部函数的地址?此外,即使可以调用GetProcAddress,也仍然需要远程DLL的句柄,那么针对64位系统,如何获取到句柄呢?
我们需要再次分解这些问题,并逐一突破。通过下面的函数,可以在x86和x64系统上可靠地返回特定进程中特定模块的句柄(在这里,意外地获得了基址):
DWORD_PTR GetRemoteModuleHandle(const int processId, const wchar_t* moduleName) { MODULEENTRY32 me32; HANDLE hSnapshot = INVALID_HANDLE_VALUE; // get snapshot of all modules in the remote process me32.dwSize = sizeof(MODULEENTRY32); hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processId); // can we start looking? if (!Module32First(hSnapshot, &me32)) { CloseHandle(hSnapshot); return 0; } // enumerate all modules till we find the one we are looking for // or until every one of them is checked while (wcscmp(me32.szModule, moduleName) != 0 && Module32Next(hSnapshot, &me32)); // close the handle CloseHandle(hSnapshot); // check if module handle was found and return it if (wcscmp(me32.szModule, moduleName) == 0) return (DWORD_PTR)me32.modBaseAddr; return 0; }
掌握远程进程中DLL的基址,是朝着正确方向迈出的第一步。接下来,要找到一种可以获取任意导出函数地址的策略。回顾一下,目前已经知道如何在远程进程中调用LoadLibrary并获取已加载模块的句柄。在此基础上,在本地(调用过程中)调用LoadLibrary并获取已加载模块的句柄就非常简单了。这一句柄,可能与远程进程中的句柄相同,也可能不同,即使它是相同的库。通过一些基本的数学运算,就能够获得任何想要的导出函数的地址。我们的思路是,尽管模块的基址因进程而异,但特定函数相对于模块基址的偏移量都是不变的。例如,在Bootstrap DLL项目中,可以找到如下导出函数:
__declspec(dllexport) HRESULT ImplantDotNetAssembly(_In_ LPCTSTR lpCommand)
在可以远程调用此函数之前,必须首先在远程进程中加载Bootstrap.dll模块。使用Process Hacker,可以看到Windows加载 工具 在注入Firefox时将Bootstrap.dll模块放入内存的过程:
下面是一个示例程序,用于在本地(调用进程中)加载Bootstrap.dll模块:
#include <windows.h> int wmain(int argc, wchar_t* argv[]) { HMODULE hLoaded = LoadLibrary( L"T:\FrameworkInjection\_build\release\x86\Bootstrap.dll"); system("pause"); return 0; }
下面是运行上述程序过程中,Windows加载Bootstrap.dll模块的截图:
接下来,要在wmain内调用GetProcAddress,来获取ImplantDotNetAssembly函数的地址:
#include <windows.h> int wmain(int argc, wchar_t* argv[]) { HMODULE hLoaded = LoadLibrary( L"T:\FrameworkInjection\_build\debug\x86\Bootstrap.dll"); // get the address of ImplantDotNetAssembly void* lpInject = GetProcAddress(hLoaded, "ImplantDotNetAssembly"); system("pause"); return 0; }
模块中函数的地址始终大于模块的基址,现在就是基础数学该上场的时候了。以下表格有助于说明这一过程:
Firefox.exe | Bootstrap.dll @ 0x50d0000 | ImplantDotNetAssembly @ ? test.exe | Bootstrap.dll @ 0xf270000 | ImplantDotNetAssembly @ 0xf271490 (lpInject)
Test.exe显示Bootstrap.dll在地址0xf270000处加载,同时ImplantDotNetAssembly可以在内存中地址为0xf271490的位置找到。使用Bootstrap.dll的地址减去ImplantDotNetAssembly的地址,就获得特定函数相对于模块基址的偏移量。经过计算,ImplantDotNetAssembly是在模块中 (0xf271490 – 0xf270000) = 0x1490的位置。然后,就可以将此偏移量添加到远程进程中Bootstrap.dll模块的基址上,从而可靠地提供ImplantDotNetAssembly相对于远程进程的地址。同样,通过计算,得到Firefox.exe的ImplantDotNetAssembly地址为(0x50d0000 + 0x1490) = 0x50d1490。通过下面的函数,可以计算出特定模块中特定函数的偏移量:
DWORD_PTR GetFunctionOffset(const wstring& library, const char* functionName) { // load library into this process HMODULE hLoaded = LoadLibrary(library.c_str()); // get address of function to invoke void* lpInject = GetProcAddress(hLoaded, functionName); // compute the distance between the base address and the function to invoke DWORD_PTR offset = (DWORD_PTR)lpInject - (DWORD_PTR)hLoaded; // unload library from this process FreeLibrary(hLoaded); // return the offset to the function return offset; }
需要注意的是,ImplantDotNetAssembly特意匹配了LPTHREAD_START_ROUTINE的签名,并且将所有方法传递给CreateRemoteThread。由此,Bootstrap.dll中的ImplantDotNetAssembly函数已经具备了在远程DLL中执行任意函数和初始化CLR的能力。在远程进程加载Bootstrap.dll后,可以计算出远程实例中ImplantDotNetAssembly的地址,然后通过CreateRemoteThread调用。这就是整个过程中的最后一块拼图。接下来,是时候把这些拼图组合到一起了。
五、组合利用
使用非托管DLL(Bootstrap.dll)加载CLR的主要原因是,如果CLR没有在远程进程中运行,那么唯一方法就是从非托管代码中使用。在这里,不能使用 Python 等脚本语言,因为它们自身就带有一系列依赖项。
另外,如果在每次应用发生变化时都重新进行一次编译,会非常繁琐。因此,我们Inject应用程序能灵活地接受命令行输入,具体可以接受如下选项:
- m 要执行的托管方法名称,例如:IntryPoint。
- i 要在远程进程内部注入的托管程序集的完全限定路径,例如:C:InjectExample.exe。
- l 托管程序集的完全限定类型名称,例如:InjectExample.Program。
- a 传递给托管函数的可选参数。
n 要注入的进程ID或进程名称,例如:1500或notepad.exe。
Inject的wmain方法如下:
int wmain(int argc, wchar_t* argv[]) { // parse args (-m -i -l -a -n) if (!ParseArgs(argc, argv)) { PrintUsage(); return -1; } // enable debug privileges EnablePrivilege(SE_DEBUG_NAME, TRUE); // get handle to remote process HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, g_processId); // inject bootstrap.dll into the remote process FARPROC fnLoadLibrary = GetProcAddress(GetModuleHandle(L"Kernel32"), "LoadLibraryW"); Inject(hProcess, fnLoadLibrary, GetBootstrapPath()); // add the function offset to the base of the module in the remote process DWORD_PTR hBootstrap = GetRemoteModuleHandle(g_processId, BOOTSTRAP_DLL); DWORD_PTR offset = GetFunctionOffset(GetBootstrapPath(), "ImplantDotNetAssembly"); DWORD_PTR fnImplant = hBootstrap + offset; // build argument; use DELIM as tokenizer wstring argument = g_moduleName + DELIM + g_typeName + DELIM + g_methodName + DELIM + g_Argument; // inject the managed assembly into the remote process Inject(hProcess, (LPVOID)fnImplant, argument); // unload bootstrap.dll out of the remote process FARPROC fnFreeLibrary = GetProcAddress(GetModuleHandle(L"Kernel32"), "FreeLibrary"); CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)fnFreeLibrary, (LPVOID)hBootstrap, NULL, 0); // close process handle CloseHandle(hProcess); return 0; }
下面是调用Inject.exe应用程序的屏幕截图,该应用程序将.NET程序集InjectExample.exe与使用的命令行一起注入notepad.exe:
"T:FrameworkInjection_buildreleasex64Inject.exe" -m EntryPoint -i "T:FrameworkInjection_buildreleaseanycpuInjectExample.exe" -l InjectExample.Program -a "hello inject" -n "notepad.exe"
在这里,重点关注下针对x86、x64和AnyCPU所构建的DLL之间的差异。理论上,应该使用x86版本的Inject.exe和Bootstrap.dll来注入x86进程,使用x64版本的相应文件注入x64进程。调用者需要确保使用了正确的二进制文件。AnyCPU是.NET中提供的平台,AnyCPU会将正确的体系结构传递给CLR JIT程序集。这样一来,我们就可以将相同的InjectExample.exe程序集注入x86或x64进程。
六、实际测试
接下来,我们进行实际测试。首先,有一些环境上的要求。
编译代码的环境:
Visual Studio 2012 Express +
Visual Studio 2012 Express Update 1 +
运行代码的环境:
.Net Framework 4.0 +
Visual C++ Redistributable for Visual Studio 2012 Update 1 +
Windows XP SP3 +
为了简化编译代码的过程,在下载的源代码中,有一个名为build.bat的文件。如果已经部署好了所需环境,运行该脚本即可自动化进行。该脚本将同时编译调试版本和发布版本,以及相应的x86、x64和AnyCPU版本。每个项目可以独立创建,并通过Visual Studio进行编译。脚本build.bat会将二进制文件放入名为_build的文件夹中。
在所有代码中,都加入了注释。此外,我们选择了C++ 11.0和.NET 4.0版本,上述版本支持Windows XP SP3到Windows 8 x64之间的所有Windows操作系统。顺便提一句,Microsoft从VS 2012 U1版本开始,为C++ 11.0 Runtime提供了对XP SP3的支持。
七、总结
一般情况下,总结部分都是回顾整个过程,并且思考其中的技术,提出需要改进的地方。但在本文的总结中,我们打算写一些不同的内容,向大家提供一些彩蛋。
正如文章开头所说的,.NET是一个强大的语言。举例来说,可以利用.NET中的Reflection API来获取有关程序集的类型信息。这意味着,.NET实际上可以用于扫描程序集,并且能够返回可用于注入的有效方法。在源代码压缩包中,有一个名为InjectGUI的.NET项目。该项目是一个针对我们的非托管Inject应用程序的托管包装器(Managed Wrapper)。InjectGUI可以显示正在运行的进程列表,决定是调用Inject的32位还是64位版本,并且扫描.NET程序集以获取有效的注入方法。在InjectGUI中,有一个名为InjectWrapper.cs的文件,其中包含包装器逻辑。
此外,还有一个名为MethodItem的辅助类,定义如下:
public class MethodItem { public string TypeName { get; set; } public string Name { get; set; } public string ParameterName { get; set; } }
ExtractInjectableMethods方法的以下部分,将获得List<MethodItem>类型的Collection,它与所需的方法签名匹配:
“`
// find all methods that match:
// static int pwzMethodName (String pwzArgument)
private void ExtractInjectableMethods()
{
// …
// open assembly Assembly asm = Assembly.LoadFile(ManagedFilename); // get valid methods InjectableMethods = (from c in asm.GetTypes() from m in c.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.ReturnType == typeof(int) && m.GetParameters().Length == 1 && m.GetParameters().First().ParameterType == typeof(string) select new MethodItem { Name = m.Name, ParameterName = m.GetParameters().First().Name, TypeName = m.ReflectedType.FullName }).ToList(); // ...
}
既然我们已经获得了有效(可以注入)的方法,此时UI也应该了解要注入的进程是32位还是64位。为了实现这一点,需要Windows API提供一些帮助:
[DllImport(“kernel32.dll”, SetLastError = true, CallingConvention =
CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWow64Process([In] IntPtr process,
[Out] out bool wow64Process);
IsWow64Process只在64位操作系统定义,如果应用程序为32位,那么就会返回True。在.NET 4.0中,引入了Environment.Is64BitOperatingSystem属性。该属性可以帮助确定是否定义了IsWow64Process函数,如这一包装函数中所示:
private static bool IsWow64Process(int id)
{
if (!Environment.Is64BitOperatingSystem)
return true;
IntPtr processHandle; bool retVal; try { processHandle = Process.GetProcessById(id).Handle; } catch { return false; // access is denied to the process } return IsWow64Process(processHandle, out retVal) && retVal;
}
“`
InjectGUI项目所使用的逻辑非常简单。要理解UI,首先需要了解WPF和依赖属性。然而,与注入相关的逻辑都位于InjectWrapper类中。UI使用了Modern UI for WPF构建,其中的图标都源自Modern UI Icons。这两个项目都是开源的。以下是InjectGUI的操作截图:
以上是关于如何将.NET程序注入到非托管进程的讨论。引用莎士比亚的一句名言:“不要因为结束而哭泣,微笑吧,因为你曾经拥有过”。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Donut:将.NET程序集注入Windows进程
- 进程:进程生命周期
- Python 知识巩固:通过主进程带起多个子进程实现多进程执行逻辑
- Python 中子进程与父进程
- 什么是僵尸进程,如何找到并杀掉僵尸进程?
- Python第十二章-多进程和多线程01-多进程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。