將托管dll注入到非托管進程中
Binhua Liu
為什么要寫這篇文章
1,如果你想注入帶窗體的dll,C#寫界面比C++容易的多;
2,或許你想利用.net的某些功能,比如利用.Net Remoting從外部控制被注入的dll;
3,或許你是一個C#程序員,使用C#的時候總感覺更舒適些,比如筆者。同時,你希望必要時也能在宿主中調用C++函數,提供更大的靈活性,本文的方法也能做到。
注入托管dll的不同之處
首先,為什么托管dll 不能像非托管dll那樣用LoadLibrary注入? 我們知道,.net語言,如C#,VB.net等,都是運行在CLR(公共語言運行時)上的,也就是我們通常所說的虛擬機,而我們所說的非托管進程是沒有加載虛擬機的。那為什么托管dll一定要在CLR上運行?托管dll雖然符合windows的PE格式規范,但是代碼是以IL的形式保存在.Text 區的,而不是機器碼,CLR會在運行時JIT編譯成機器碼再交給操作系統執行,這也就為什么托管代碼稱之為”托管”的意義。
所以,要想注入托管dll,首先需要在目標進程中啟動CLR,然后讓CLR來加載managed dll。
注入的方式
首先,我們注入一個非托管的dll,再通過它加載CLR并加載托管dll。所以工程需要3個模塊:注入器,一個注入的非托管dll和注入的托管dll。
我們首先看如何注入非托管dll,這里是通過遠程線程來實現的,如果你已經熟悉這個技術,可以跳過:
InjectDemo.cpp:
int _tmain(int argc, _TCHAR* argv[])
{
int pid;
void *pNativeDllRemote;
FARPROC pLoadLibrary;
TCHAR szNativeDllPath[_MAX_PATH]=_T("D:\\Code\\InjectDemo\\Debug\\NativeDll.dll");
cout<<"input the process id to inject"<<endl;
cin>>pid;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,0,pid);
if(hProcess==0)
return 1;
HMODULE hKernel32 = ::GetModuleHandle(_T("Kernel32"));
if(sizeof(TCHAR)==2)
pLoadLibrary= ::GetProcAddress(hKernel32,"LoadLibraryW"); //if path is unicode, use "LoadLibraryW"
else
pLoadLibrary= ::GetProcAddress(hKernel32,"LoadLibraryA");
pNativeDllRemote=VirtualAllocEx(hProcess,NULL,sizeof(szNativeDllPath),MEM_COMMIT,PAGE_READWRITE);
::WriteProcessMemory(hProcess,pNativeDllRemote,(void*)szNativeDllPath,sizeof(szNativeDllPath),NULL);
HANDLE hThread = CreateRemoteThread(hProcess,NULL,0,(LPTHREAD_START_ROUTINE)pLoadLibrary,pNativeDllRemote,0,NULL);
::WaitForSingleObject(hThread,INFINITE);
//DWORD exitcode;
//GetExitCodeThread(hThread,&exitcode);
::CloseHandle(hThread);
return 0;
}
這段代碼通過遠程線程注入Native.dll,網上這方面文章很多,也可以根據不同的需要采用服務輸入法或者Hook等方式注入。需要注意的是,我們通過GetProcAddress獲取LoadLibrary的函數地址,事實上獲取的是注入器中該函數的虛擬地址,而不是宿主的。由于LoadLibrary函數位于系統dll中,在每個進程中都被加載到相同的虛擬地址上,所以我們才能這么做。LoadLibrary函數的參數,dll的路徑字符串需要通過VirtualAllocEx和WriteProcessMemory在宿主進程上創建,而不能把注入器上的字符串地址傳給LoadLibrary。總之,必須記住的一點是,遠程線程是在另一個虛擬地址空間上執行的,遠程執行的函數體本身或者他引用的虛擬地址都不能是注入器進程中的虛擬地址,而必須是宿主進程的虛擬地址。
再來看被注入的非托管的NativeDll.dll的代碼:
NativeDll.cpp:
#include <windows.h>
#include "stdafx.h"
#include "NativeDll.h"
#include "MSCorEE.h"
#include "metahost.h"
DWORD CALLBACK StartTheDotNetRuntime(LPVOID lp)
{
HRESULT hr = S_OK;
ICLRMetaHost *m_pMetaHost = NULL;
ICLRRuntimeInfo *m_pRuntimeInfo = NULL;
ICLRRuntimeHost *pClrHost = NULL;
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*) &m_pMetaHost);
if (hr != S_OK)
return hr;
hr = m_pMetaHost->GetRuntime (L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*) &m_pRuntimeInfo);
if (hr != S_OK)
return hr;
hr = m_pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*) &pClrHost );
if (FAILED(hr)) return hr;
HRESULT hrStart = pClrHost->Start();
DWORD dwRet = 0;
hr = pClrHost->ExecuteInDefaultAppDomain(
L"d:\\Code\\InjectDemo\\Debug\\ManagedDll.dll",
L"ManagedDll.Class1", L"Start", L"nothing to post", &dwRet);
hr = pClrHost->Stop();
pClrHost->Release();
return S_OK;
}
BOOL WINAPI DllMain(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
CreateThread(0,0,StartTheDotNetRuntime,0,0,0);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
default:
break;
}
return true;
}
這段代碼是本文的重點,我們在NativeDll.dll的DllMain函數中創建一個線程來加載加載CLR,CLR事實上是一組COM服務器,為了可以調用它們,我們需要引用頭文件 "MSCorEE.h"和"metahost.h",并在鏈接器中添加對MSCorEE.lib的鏈接。 GetRuntime函數用于指定加載的CLR的版本,需要填寫完整的版本號,根據我的測試,2.0和4.0可以成功加載,但是3.5似乎不行。在成功啟動CLR后,執行ExecuteInDefaultAppDomain加載指定的托管dll并執行它的靜態方法。執行結束后,停止并釋放CLR。
然后,在托管的ManagedDll.dll中,我們可以打開一個窗口:
Class1.cs:
using System.Windows.Forms;
namespace ManagedDll
{
public class Class1
{
public static int Start(string argument)
{
Application.Run(new MainForm());
return 0;
}
}
}
或者開啟一個.net Remoting服務,這樣注入器就可以在外部控制該進程了:
using System.Threading;
using System.Runtime.Remoting;
namespace ManagedDll
{
public class Class1
{
public static int Start(string argument)
{
RemotingConfiguration.Configure("ManagedDll.dll.config");
while (true)
{
Thread.Sleep(1000);
}
}
}
}
需要注意的是,使用.net Remoting,你必須把托管dll放在和宿主同一個目錄下,否則反射機制會失敗。這種注入方式提供了很大的靈活性,你可以把邏輯代碼寫在ManagedDll.dll中,也可以寫在NativeDll.dll中并導出,在ManagedDll.dll中引用,再通過窗口或者.net Remoting調用。
參考
http://www.codingthewheel.com/archives/how-to-inject-a-managed-assembly-dll
http://windows-internals.blogspot.com/2009/02/injecting-code-using-createremotethread.html
Binhua Liu原創,寫于2011/8/4。
浙公網安備 33010602011771號