如何将.NET程序注入到非托管进程

栏目: ASP.NET · 发布时间: 7年前

内容简介:.NET是一个强大的编程语言,可以用来快速可靠地开发软件,然而.NET也有其不适用的一些场景。本文重点讲解了DLL注入的一个案例,当未加载.NET Runtime时,无法向一个远程进程中注入.NET DLL(托管DLL)。此外,假设.NET Runtime已经在目标进程中加载,那么应该如何调用.NET DLL中的方法?其结构是什么样的?针对64位进程的操作是否与32位进程不同?阅读本文后,你将会了解如何使用官方文档中记录的API实现这些任务。我们的目标是:1、在32位和64位系统上,在任意进程中启动.NE

如何将.NET程序注入到非托管进程

源代码

一、概述

.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中。

编译并运行上述代码后,得到输出结果如下:

如何将.NET程序注入到非托管进程 如何将.NET程序注入到非托管进程

一旦按下回车键,就可以通过Process Hacker观察到.NET Runtime已被加载。请注意属性窗口上引用.NET的其他选项卡:

如何将.NET程序注入到非托管进程 如何将.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;
}

应用程序输出如下:

如何将.NET程序注入到非托管进程

至此,我们已经拼好了两块拼图。现在,我们已经理解了如何加载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模块放入内存的过程:

如何将.NET程序注入到非托管进程

下面是一个示例程序,用于在本地(调用进程中)加载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模块的截图:

如何将.NET程序注入到非托管进程

接下来,要在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"

如何将.NET程序注入到非托管进程 在这里,重点关注下针对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程序注入到非托管进程

以上是关于如何将.NET程序注入到非托管进程的讨论。引用莎士比亚的一句名言:“不要因为结束而哭泣,微笑吧,因为你曾经拥有过”。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Art of Computer Programming, Volume 3

The Art of Computer Programming, Volume 3

Donald E. Knuth / Addison-Wesley Professional / 1998-05-04 / USD 74.99

Finally, after a wait of more than thirty-five years, the first part of Volume 4 is at last ready for publication. Check out the boxed set that brings together Volumes 1 - 4A in one elegant case, and ......一起来看看 《The Art of Computer Programming, Volume 3》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具