这是我觉得一种非常好的Hook技术,自己也非常喜欢,我先引用Windows核心编程里的讲解,最后在文后附上一个封装好的类,这种方法非常适合在别人的程序里隐藏自己的程序,因此得到了广大木马爱好者的青眯。
插入D L L的第三种方法是使用远程线程。这种方法具有更大的灵活性。它要求你懂得若干个Wi n d o w s特性、如进程、线程、线程同步、虚拟内存管理、D L L和U n i c o d e等(如果对这些特性不清楚,请参阅本书中的有关章节)。Wi n d o w s的大多数函数允许进程只对自己进行操作。这是很好的一个特性,因为它能够防止一个进程破坏另一个进程的运行。但是,有些函数却允许一个进程对另一个进程进行操作。这些函数大部分最初是为调试程序和其他工具设计的。不过任何函数都可以调用这些函数。
这个D L L插入方法基本上要求目标进程中的线程调用L o a d L i b r a r y函数来加载必要的D L L。由于除了自己进程中的线程外,我们无法方便地控制其他进程中的线程,因此这种解决方案要求我们在目标进程中创建一个新线程。由于是自己创建这个线程,因此我们能够控制它执行什么代码。幸好,Wi n d o w s提供了一个称为C r e a t e R e m o t e T h r e a d的函数,使我们能够非常容易地在另一个进程中创建线程:
HANDLE CreateRemoteThread(
HANDLE hProcess,
PSECURITY_ATTRIBUTES psa,
DWORD dwStackSize,
PTHREAD_START_ROUTINE pfnStartAddr,
PVOID pvParam,
DWORD fdwCreate,
PDWORD pdwThreadId);
注意在Windows 2000中,更常用的函数CreateThread是在内部以下面的形式来实现的:
HANDLE CreateThread(PSECURITY_ATTRIBUTES psa, DWORD dwStackSize,
PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam,
DWORD fdwCreate, PDWORD pdwThreadID)
{
return(CreateRemoteThread(GetCurrentProcess(), psa, dwStackSize,
pfnStartAddr, pvParam, fdwCreate, pdwThreadID));
}
好了,现在你已经知道如何在另一个进程中创建线程了,但是,如何才能让该线程加载我们的D L L呢?答案很简单,那就是需要该线程调用L o a d L i b r a r y函数:
HINSTANCE LoadLibrary(PCTSTR pszLibFile);
HINSTANCE WINAPI LoadLibraryA(LPCSTR pszLibFileName);
HINSTANCE WINAPI LoadLibraryW(LPCWSTR pszLibFileName);
#ifdef UNICODE
#define LoadLibrary LoadLibraryW
#else
#define LoadLibrary LoadLibraryA
#endif // !UNICODE
幸好L o a d L i b r a r y函数的原型与一个线程函数的原型是相同的。下面是一个线程函数的原型:
DWORD WINAPI ThreadFunc(PVOID pvParam);
HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0,
LoadLibraryA, "C://MyLib.dll", 0, NULL);
HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0,
LoadLibraryW, L"C://MyLib.dll", 0, NULL);
第一个问题是,不能像我在上面展示的那样,将L o a d L i b r a r y A或L o a d L i b r a r y W作为第四个参数传递给C r e a t e R e m o t e T h r e a d。原因很简单。当你编译或者链接一个程序时,产生的二进制代码包含一个输入节(第1 9章中做了介绍)。这一节由一系列输入函数的形式替换程序(t h u n k)组成。所以,当你的代码调用一个函数如L o a d L i b r a r y A时,链接程序将生成一个对你模块的输入节中的形实替换程序的调用。接着,该形实替换程序便转移到实际的函数。
如果在对C r e a t e R e m o t e T h r e a d的调用中使用一个对L o a d L i b r a r y A的直接引用,这将在你的模块的输入节中转换成L o a d L i b r a r y A的形实替换程序的地址。将形实替换程序的地址作为远程线程的起始地址来传递,会导致远程线程开始执行一些令人莫名其妙的东西。其结果很可能造成访问违规。若要强制直接调用L o a d L i b r a r y A函数,避开形实替换程序,必须通过调用G e t P r o c A d d r e s s函数,获取L o a d L i b r a r y A的准确内存位置。
对C r e a t e R e m o t e T h r e a d进行调用的前提是,K e r n e l 3 2 . d l l已经被同时映射到本地和远程进程的地址空间中。每个应用程序都需要K e r n e l 3 2 . d l l,根据我的经验,系统将K e r n e l 3 2 . d l l映射到每个进程的同一个地址。因此,必须调用下面的C r e a t e R e m o t e T h r e a d函数:
// Get the real address of LoadLibraryA in Kernel32.dll.
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0,
pfnThreadRtn, "C://MyLib.dll", 0, NULL);
// Get the real address of LoadLibraryW in Kernel32.dll.
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0,
pfnThreadRtn, L"C://MyLib.dll", 0, NULL);
为了解决这个问题,必须将D L L的路径名字符串放入远程进程的地址空间中。然后,当C r e a t e R e m o t e T h r e a d函数被调用时,我们必须将我们放置该字符串的地址(相对于远程进程的地址)传递给它。同样,Wi n d o w s提供了一个函数,即Vi r t u a l A l l o c E x,使得一个进程能够分配另一个进程的地址空间中的内存:
PVOID VirtualAllocEx(
HANDLE hProcess,
PVOID pvAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect);
BOOL VirtualFreeEx(
HANDLE hProcess,
PVOID pvAddress,
SIZE_T dwSize,
DWORD dwFreeType);
一旦为该字符串分配了内存,我们还需要一种方法将该字符串从我们的进程的地址空间拷贝到远程进程的地址空间中。Wi n d o w s提供了一些函数,使得一个进程能够从另一个进程的地址空间中读取数据,并将数据写入另一个进程的地址空间。
BOOL ReadProcessMemory(
HANDLE hProcess,
PVOID pvAddressRemote,
PVOID pvBufferLocal,
DWORD dwSize,
PDWORD pdwNumBytesRead);
BOOL WriteProcessMemory(
HANDLE hProcess,
PVOID pvAddressRemote,
PVOID pvBufferLocal,
DWORD dwSize,
PDWORD pdwNumBytesWritten);
既然已经知道了要进行操作,下面让我们将必须执行的操作步骤做一个归纳:
1) 使用Vi r t u a l A l l o c E x函数,分配远程进程的地址空间中的内存。
2) 使用Wr i t e P r o c e s s M e m o r y函数,将D L L的路径名拷贝到第一个步骤中已经分配的内存中。
3) 使用G e t P r o c A d d r e s s函数,获取L o a d L i b r a r y A或L o a d L i b r a t y W函数的实地址(在K e r n e l 3 2 . d l l中)。
4) 使用C r e a t e R e m o t e T h r e a d函数,在远程进程中创建一个线程,它调用正确的L o a d L i b r a r y函数,为它传递第一个步骤中分配的内存的地址。
这时, D L L已经被插入远程进程的地址空间中,同时D L L的D l l M a i n函数接收到一个D L L _ P R O C E S S _ AT TA C H通知,并且能够执行需要的代码。当D l l M a i n函数返回时,远程线程从它对L o a d L i b r a r y的调用返回到B a s e T h r e a d S t a r t 函数(第6 章中已经介绍)。然后B a s e T h r e a d S t a r t调用E x i t T h r e a d,使远程线程终止运行。
现在远程进程拥有第一个步骤中分配的内存块,而D L L则仍然保留在它的地址空间中。若要将它删除,需要在远程线程退出后执行下面的步骤:
5) 使用Vi r t u a l F r e e E x函数,释放第一个步骤中分配的内存。
6) 使用G e t P r o c A d d r e s s函数,获得F r e e L i b r a r y函数的实地址(在K e r n e l 3 2 . d l l中)。
7) 使用C r e a t e R e m o t e T h r e a d函数,在远程进程中创建一个线程,它调用F r e e L i b r a r y函数,传递远程D L L的H I N S TA N C E。
附一个封装好的类:
// RemThreadInjector.h文件
#include <windows.h>
class CRemThreadInjector
{
public:
CRemThreadInjector(LPCTSTR pszDllName);
~CRemThreadInjector();
// 注入DLL到指定的进程空间
BOOL InjectModuleInto(DWORD dwProcessId);
// 从指定的进程空间卸载DLL
BOOL EjectModuleFrom(DWORD dwProcessId);
protected:
char m_szDllName[MAX_PATH];
// 调整特权级别
static BOOL EnableDebugPrivilege(BOOL bEnable);
};
// RemThreadInjector.cpp文件
#include "RemThreadInjector.h"
#include <tlhelp32.h>
CRemThreadInjector::CRemThreadInjector(LPCTSTR pszDllName)
{
strncpy(m_szDllName, pszDllName, MAX_PATH);
EnableDebugPrivilege(TRUE);
}
CRemThreadInjector::~CRemThreadInjector()
{
EnableDebugPrivilege(FALSE);
}
BOOL CRemThreadInjector::EnableDebugPrivilege(BOOL bEnable)
{
// 附给本进程特权,以便访问系统进程
BOOL bOk = FALSE;
HANDLE hToken;
// 打开一个进程的访问令牌
if(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
// 取得特权名称为“SetDebugPrivilege”的LUID
LUID uID;
::LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID);
// 调整特权级别
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = uID;
tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
::AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
bOk = (::GetLastError() == ERROR_SUCCESS);
// 关闭访问令牌句柄
::CloseHandle(hToken);
}
return bOk;
}
BOOL CRemThreadInjector::InjectModuleInto(DWORD dwProcessId)
{
if(::GetCurrentProcessId() == dwProcessId)
return FALSE;
// 首先查看目标进程是否加载了这个模块
BOOL bFound = FALSE;
MODULEENTRY32 me32 = { 0 };
HANDLE hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
me32.dwSize = sizeof(MODULEENTRY32);
if(::Module32First(hModuleSnap, &me32))
{
do
{
if(lstrcmpiA(me32.szExePath, m_szDllName) == 0)
{
bFound = TRUE;
break;
}
}
while(::Module32Next(hModuleSnap, &me32));
}
::CloseHandle(hModuleSnap);
// 如果能够找到,就不重复加载了(因为重复加载没有用,Windows只将使用计数加1,其它什么也不做)
if(bFound)
return FALSE;
// 试图打开目标进程
HANDLE hProcess = ::OpenProcess(
PROCESS_VM_WRITE|PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION, FALSE, dwProcessId);
if(hProcess == NULL)
return FALSE;
// 在目标进程中申请空间,存放字符串pszDllName,作为远程线程的参数
int cbSize = (strlen(m_szDllName) + 1);
LPVOID lpRemoteDllName = ::VirtualAllocEx(hProcess, NULL, cbSize, MEM_COMMIT, PAGE_READWRITE);
::WriteProcessMemory(hProcess, lpRemoteDllName, m_szDllName, cbSize, NULL);
// 取得LoadLibraryA函数的地址,我们将以它作为远程线程函数启动
HMODULE hModule=::GetModuleHandle("Kernel32.dll");
LPTHREAD_START_ROUTINE pfnStartRoutine =
(LPTHREAD_START_ROUTINE)::GetProcAddress(hModule, "LoadLibraryA");
// 启动远程线程
HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnStartRoutine, lpRemoteDllName, 0, NULL);
if(hRemoteThread == NULL)
{
::CloseHandle(hProcess);
return FALSE;
}
// 等待目标线程运行结束,即LoadLibraryA函数返回
::WaitForSingleObject(hRemoteThread, INFINITE);
::CloseHandle(hRemoteThread);
::CloseHandle(hProcess);
return TRUE;
}
BOOL CRemThreadInjector::EjectModuleFrom(DWORD dwProcessId)
{
if(::GetCurrentProcessId() == dwProcessId)
return FALSE;
// 首先查看目标进程是否加载了这个模块
BOOL bFound = FALSE;
MODULEENTRY32 me32 = { 0 };
HANDLE hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
me32.dwSize = sizeof(MODULEENTRY32);
if(::Module32First(hModuleSnap, &me32))
{
do
{
if(lstrcmpiA(me32.szExePath, m_szDllName) == 0)
{
bFound = TRUE;
break;
}
}
while(::Module32Next(hModuleSnap, &me32));
}
::CloseHandle(hModuleSnap);
// 如果找不到就返回出错处理
if(!bFound)
return FALSE;
// 试图打开目标进程
HANDLE hProcess = ::OpenProcess(
PROCESS_VM_WRITE|PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION, FALSE, dwProcessId);
if(hProcess == NULL)
return FALSE;
// 取得LoadLibraryA函数的地址,我们将以它作为远程线程函数启动
HMODULE hModule=::GetModuleHandle("kernel32.dll");
LPTHREAD_START_ROUTINE pfnStartRoutine =
(LPTHREAD_START_ROUTINE)::GetProcAddress(hModule, "FreeLibrary");
// 启动远程线程
HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnStartRoutine, me32.hModule, 0, NULL);
if(hRemoteThread == NULL)
{
::CloseHandle(hProcess);
return FALSE;
}
// 等待目标线程运行结束,即FreeLibrary函数返回
::WaitForSingleObject(hRemoteThread, INFINITE);
::CloseHandle(hRemoteThread);
::CloseHandle(hProcess);
return TRUE;
}