您的位置:首页 > 编程语言

将代码注入其它进程的三种方法

2009-09-28 12:20 405 查看
在互连网络飞速发展的今天,网络的充分利用虽然方便了人们的生产和生活,但也随之带来了严重的网络安全问题。许多黑客可以利用各种操作系统或者应用软件的漏洞进入到一台联入互连网络的主机中,并且利用木马软件实现对该主机的长期控制。一些木马为了实现自身运行的隐蔽,往往会将自身代码注入到其它进程中来运行。有道是“知己知彼,百战不殆”,下面随本文的介绍来认识一下将代码注入其它进程的三种方法。
将代码注入其它进程,则意味着在另一个存在的进程地址空间中执行用户编写的代码。实现这一目标的方法通常有一下三种,即:
1、 将实现代码编写为DLL,然后通过Windows钩子来将DLL映射到另一进程中。
2、 将实现代码编写为DLL,然后通过CreateRemoteThread和LoadLibrary技术将DLL映射到另一进程中。
3、 利用WriteProcessMemory技术将代码直接拷贝到其它进程中,然后通过CreateRemoteThread函数来执行编写的代码。
下面逐一介绍上述三种方法的具体实现过程。

I、Windows 钩子技术

钩子是Windows的一个主要特性之一,它就主要认为是在系统中监视一些线程间的消息传输。Windows具有两种类型的钩子
1、 局部钩子,用来监视所有往来于系统和本进程之间消息。
2、 远程钩子,它可以用于:
a、 Thread-specific,监视指定进程和其它线程间的消息。
b、 System-wide,坚持系统中当前运行的任何线程间传输的消息。
如果钩子线程从属于另一进程,则钩子程序必须编写为DLL,借着系统将包含钩子程序的DLL映射到钩子线程的地址空间中。此时Windows将映射整个DLL,而不仅仅是钩子程序,这就是为什么我们可以利用Windows钩子来实现将代码注入另一进程地址空间中的原因。
限于篇幅原因,本文将不会详细讨论关于钩子的更多细节(细节参见MSDN中有关SetWindowsHookEx API函数相关文档),仅介绍实现代码注入的关键细节。
1、当成功调用SetWindowsHookEx函数以后,系统将自动映射DLL钩子线程的地址空间中,但映射不会马上起作用。这是因为,Windows钩子同样是基于消息机制的,因此,当一个适当事件Event发生以前,映射都不会起作用。比如,当安装一个用来监视某些线程的所有非队列消息时,DLL在收到一个真实的消息以前是不会映射到其它进程中的。为了使映射立刻产生作用,则必须在成功调用了SetWindowsHookEx之后,再向钩子线程发送一个适当的事件。同样,如果调用UnhookWindowsHookEx来卸载钩子,也需要做同样的工作。
2、当安装了一个钩子以后,钩子可能会影响到整个系统中的所有消息事件,为了避免不必要的钩子功能,可以选择使用thread-specific钩子。比如下面这段代码:
BOOL APIENTRY DllMain ( HANDLE hModule , DWORD ul_reason_for_call , LPVOID lpReserved )

{

if ( ul_reason_for_call == DLL_PROCESS_ATTACH )

{

char lib_name[MAX_PATH];

::GetModuleFileName ( hModule , lib_name , MAX_PATH );

::LoadLibrary ( lib_name );

::UnhookWindowsHookEx ( g_hHook );

}

return TRUE;

}

上述代码进行了如下的工作。首先,使用Windows钩子DLL映射到了另一进程中,此时,DLL实际上已经完成了映射工作,一般来说,一旦第一个发往钩子的消息到达,DLL就将解除映射。使用LoadLibrary正是为了避免映射的解除。那么,一旦完成了代码的执行工作,怎样卸载DLL呢?这时候,UnhookWindowsHookEx不会做任何事情,因为钩子线程已经卸载了,因此,只能通过下面的来完成DLL的卸载。

在想要下载DLL以前,安装另一钩子

发送一个指定的信息给另一进程(注入代码的进程);

钩子中捕捉这一消息,然后调用FreeLibrary和UnhookWindowsHookEx。

现在,已经可以使用DLL来实现将代码注入到其它进程中(或者从其它进程中卸载)。该方法适用于Win9x和WinNT操作系统。

II、CreateRemoteThread和LoadLibrary技术

一般来说,任何进程都可以通过使用LoadLibrary API函数来动态载入DLL。那么,自己编写的DLL怎么样才能使其它进程载入呢?也就是说,怎样才能强迫一个外部的进程去调用LoadLibrary函数呢?方法是,利用CreateRemoteThread。

先来认识一下LoadLibrary和FreeLibrary:

HMODULE LoadLibrary(

LPCTSTR lpFileName // file name of module

);

BOOL FreeLibrary(

HMODULE hModule // handle to DLL module

);

将上述两个函数与ThreadProc(CreateRemoteThread创建的线程入口函数)的声明做比较:
DWORD WINAPI ThreadProc(
LPVOID lpParameter   // thread data

);

通过比较可以看出,所有函数都使用同样的调用方式,且都接受32位的参数。返回值的尺寸大小也相同。这表明,可以传送一个指针给LoadLibrary(FreeLibrary)来作为CreateRemoteThread构造的线程线程序。
再来看CreateRemoteThread的定义:
HANDLE CreateRemoteThread(

HANDLE hProcess,                          // handle to process

LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD

DWORD dwStackSize,                        // initial stack size

LPTHREAD_START_ROUTINE lpStartAddress,    // thread function

LPVOID lpParameter,                       // thread argument

DWORD dwCreationFlags,                    // creation option

LPDWORD lpThreadId                        // thread identifier

);

从中可以发现两个问题,首先,lpStartAddress参数必须是在远程进程中线程程序的开始地址。其次,如果lpParameter参数作为普通的32-bit值传送给ThreadProc,当然没有任何问题,但如果参数lpParameter作为一个指针比如char字符串进行传送的话,则该参数一定指向了在远程进程中的一些数据。
对于上述两个问题,第一个实际上系统已经帮助我们解决了。这是因为,LoadLibrary和FreeLibrary函数都是系统Kernel32.dll中的函数,对于任何一个“Normal”级的进程来说,Kernel32.dll确保了他们使用相同的地址。每一个进程中的LoadLibrary和FreeLibray也一样。这就保证了传递给远程进程的指针是有效的。
对于第二个问题,可以使用WriteProcessMemory函数来拷贝DLL模块名称来解决。
下面介绍使用CreateRemoteThread和LoadLibrary的具体实现步骤。
1、 获取远程进程的HANDLE句柄(OpenProcess)。
2、 在远程进程中为DLL名称分配内存空间(VirtualAllocEx)。
3、 写入DLL名称,包括完整路径,到以分配的存储空间中(WriteProcessMemory)。
4、 通过CreateRemoteThread和LoadLibrary映射DLL到远程进程中。
5、 等待远程线程终止(WaitForSingleObject),直到LoadLibrary调用返回。
6、 返回远程线程的退出代码(GetExitCodeThread),注意这个值已经由LoadLibrary返回了,它是映射DLL的基址HMODULE。
7、 释放第2步中申请的内存(VirtualFreeEx)。
8、 使用CreateRemoteThread和FreeLibrary将DLL从远程进程中卸载。这里会用到步骤6中HMODULE,它将会作为参数lpParameter的值传递给CreateRemoteThread。
9、 等待直到线程终止(WaitForSingleObject)。
本文给出下面的代码片段,以说明具体的实现方法。
HANDLE hThread;

char
szLibPath[_MAX_PATH]; // The name of our "LibSpy.dll" module

// (including full path!);

void
* pLibRemote; // The address (in the remote process) where

// szLibPath will be copied to;

DWORD hLibModule;
// Base address of loaded module (==HMODULE);

HMODULE hKernel32 = ::GetModuleHandle("Kernel32");

// initialize szLibPath

//...

// 1. Allocate memory in the remote process for szLibPath

// 2. Write szLibPath to the allocated memory

pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath),

MEM_COMMIT, PAGE_READWRITE );

::WriteProcessMemory( hProcess, pLibRemote, (void*)szLibPath,

sizeof(szLibPath), NULL );

// Load "LibSpy.dll" into the remote process

// (via CreateRemoteThread & LoadLibrary)

hThread = ::CreateRemoteThread( hProcess, NULL, 0,

(LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,

"LoadLibraryA" ),

pLibRemote, 0, NULL );

::WaitForSingleObject( hThread, INFINITE );

// Get handle of the loaded module

::GetExitCodeThread( hThread, &hLibModule );

// Clean up

::CloseHandle( hThread );

::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );

//(代码完毕)

此时,代码实际上已经注入到了远程进程中。这些代码存在于DllMain中,且已经执行。这时,可以通过下面的代码将Dll从目标进程中卸载。

// Unload "LibSpy.dll" from the target process
// (via CreateRemoteThread & FreeLibrary)
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
"FreeLibrary" ),
(void*)hLibModule, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );

// Clean up
::CloseHandle( hThread );

III、CreateRemoteThread和WriteProcessMemory技术

第三种方法的基本思想是拷贝一些代码到另一进程的地址空间中然后执行。这种方法代替了编写独立的DLL,而是直接将代码使用WriteProcessMemory API函数拷贝到远程进程中。然后通过CreateRemoteThread来执行它。

本文前面已经介绍过CreateRemoteThread的函数原型,注意以下几点:

1、 hProcess参数是CreateRemoteThread的附加参数,它是进程决定创建哪个线程的句柄。

2、 lpStartAddress参数是线程在远程进程地址空间中的开始地址。它必须存在于远程进程中,因此不能简单的拷贝一个地址句柄传递给本地的ThreadFunc,而必须先将代码拷贝到远程进程中。

3、 对于lpParameter参数也一样,也必须将他拷贝到远程进程中。

下面概括一下使用CreateRemoteThread和WriteProcessMemory的具体步骤。

1、 返回远程进程的句柄HANDLE(OpenProcess)

2、 在远程进程的地址空间中为待注入的代码分配内存(VirtualAllocEX)

3、 将已初始化后的INJDATA结构的一个拷贝写入分配的内存(WriteProcessMemory)

4、 在远程进程的地址空间中为注入的代码分配内存

5、 将ThreadPunc的一个拷贝写入分配的内存

6、 使用CreateRemoteThread来执行远程的ThreadFunc拷贝

7、 等待远程线程的终止(WaitForSingleObject)

8、 从远程进程中返回执行结果(ReadProcessMemory或GetExitCodeThread)

9、 释放已申请的内存,包括步骤2和步骤4中的申请(VirtualFreeEx)

10、 关闭返回的句柄,包括步骤1和步骤6中的HANDLE(CloseHandle)

上述步骤中需要定义INJDATA结构:

typedef struct {

HWND hwnd;

SENDMESSAGE fnSendMessage; // pointer to user32!SendMessage

BYTE pbText[128 * sizeof(TCHAR)];

} INJDATA, *PINJDATA;

构造ThreadFunc时将INJDATA作为传递的参数:

static DWORD WINAPI ThreadFunc (INJDATA *pData)

{

return 0;

}

这样,就可以在ThreadFunc中加入一些想做的事情,并且注入到其它进程中来执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: