首先声明,我是一个初学者,这篇文章只是我学习编程过程中的一个记录 ,仅供大家参考。文章中涉及的源代码是我在Windows核心编程一书中和网上的一些资料里学习到的,在此向那些前辈表示感谢。本源代码没有版权,网友可随意复制修改,但请注明引用出处 http://blog.csdn.net/scienwu。此代码仅供学习参考之用,用此代码修改而成的程序所造成的损失作者ScienWu不承担任何责任。
由于作者水平有限,难免有疏忽错误之处,欢迎各位同道给予批评指正,也希望能和各位多交流,谢谢。
这篇文章介绍我在学习Windows核心编程时的一些心得,主要内容是关于使用远程线程进行Dll注入。
关于使用远程线程进行DLL注入和API挂接并不是什么新鲜技术,网上的介绍有很多,但或是粗略带过或是比较高深,让我这样的初学者学习起来有些吃力,最近在看Windows核心编程这本书,虽然是本老书,但却讲得由浅入深而且很详实,还有例程,让我学习起来省力不少。为了使向我这样的初学者少走弯路,在此把使用远程线程进行DLL注入方法做一个的详细介绍,供大家参考交流。高手若觉得此篇文章肤浅请不要扔西红柿,因为我的确是一个菜鸟 。
详细的实现过程。我使用的是VS.NET2005,如果您使用的是其他IDE,流程是一样的,但因为不同编译器之间的细微差别,请自行做相应的调整。
使用远程线程进行DLL注入的目的在于要在目标程序的领空内执行你自己的程序代码,主要用于监控修改目标程序的某些消息或数据(如汉化补丁、外挂、插件等),而且也是进行API挂接的前提条件。
要进行远程线程的DLL注入首先要有一个目标程序(1),一个包含你要执行代码的DLL文件(2)和一个将你的DLL文件注入到目标程序的引导程序(3)。将这三个文件放在同一个目录下执行引导程序。过程是这样的:引导程序使用CreateProcess建立一个新的目标程序的进程,这样我们可以很容易的得到进程句柄,或者用FindWindow获取已执行进程的进程句柄,有了进程句柄后就可以对进程进行操作了。然后使用VirtualAllocEx和WriteProcessMemory将必要的参数信息等写入到目标进程的内存空间,最后使用CreateRemoteThread在目标程序的进程中建立远程线程,并让远程线程执行LoadLibraryA载入我们自己的DLL,使得代码得以在目标进程的领空内执行。因为Kernel32.dll是每一个应用程序都必须加载的系统DLL,所以LoadLibrary函数可以在任何一个程序的进程中被调用,因此我们选择让远程线程启动时执行LoadLibrary来加载我们自己的DLL是再合适不过的了。
(1)目标程序:当然这个目标程序可以而且绝大部分情况不是你写的程序,你可能没有源代码。在这个例子中,我所使用的目标程序是一个已经编译好的空白MFC程序Test.exe,仅仅显示一个Windows窗口没有其他作用。
(2)包含代码的DLL文件myDll.dll:接下来你要是用IDE建立一个包含你自己要执行的代码的DLL项目。打开IDE-〉File-〉New-〉Project,在打开的对话框里选择Visual C++ 下Win32类型下的Win32 Project程序,命名为myDll,选择打开,在设置向导中的Application Settings处将Application Type选成DLL,其他使用默认选项,完成,这样会建立一个空的DLL项目,我们就在这个项目中书写需要注入进目标程序执行的程序代码。
在myDll.cpp中已经有Dll文件的启动函数 DllMain。
可能因为VS.NET2005默认使用的是UNICODE,为了适应UNICODE和ANSI我们需要在Stdafx.h文件中加入:#include <tchar.h> 或者使用所有WindowsAPI的ANSI版本。
这里我们完成一个简单的功能,就是在目标程序执行前弹出一个对话框。所以我们在myDll.cpp中添加如下的代码:
void myTestFun()
{
MessageBoxEx(NULL,_TEXT("测试文本"),_TEXT("测试标题"),MB_OK,0);
}
一个做测试用的非常简单的函数。为了能让我们自己的代码获得执行机会,我们把这个函数添加到DllMain中:
BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
}
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
myTestFun();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
这样只要我们的Dll被加载就会执行我们写的这段代码。这样DLL工程中的工作就完成了。编译会生成一个myDll.dll文件。
(3)将你的DLL注入到目标程序的引导程序DllLoader.exe:建立一个新的工程,因为只是一个引导程序,为了简单我选择了Win32 console application,命名为DllLoader,完成,这样我们就建立了一个控制台程序。
需要在StdAfx.h文件中加入
#include <tchar.h>
接着我们在_tmain函数里书写引导代码。
// 我们自己的DLL文件的文件名,如果不在同一目录下,需要指定完成路径
TCHAR * pcFileName = _TEXT("TestDll.dll");
// 计算文件名字符串的长度
int iSize = ( _tcslen(pcFileName) + 1 ) * sizeof(TCHAR);
// 要写入目标进程的字符串参数的地址
TCHAR * pcLibFileRemote = NULL;
// 目标程序的文件名
TCHAR * pcTargetFileName=_TEXT("ollyDbgTest.exe");
// 创建进程所需的进程信息结构体
PROCESS_INFORMATION ProcessInfo;
// 创建进程所需的启动信息结构体
STARTUPINFO StartupInfo;
ZeroMemory(&StartupInfo, sizeof(StartupInfo)); // 初始化工作
StartupInfo.cb = sizeof StartupInfo; // 计算大小
// 开始为目标程序建立进程
if(CreateProcess(pcTargetFileName, NULL,NULL,NULL,FALSE,0,NULL,NULL,&StartupInfo,&ProcessInfo))
{
// 打开进程,并获得所有权限
HANDLE handleTarget=OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcessInfo.dwProcessId);
// 在目标进程中分配iSize大小的空间以便将我们的DLL的文件名作为参数写入目标进程
pcLibFileRemote=(TCHAR*)VirtualAllocEx(handleTarget,NULL,iSize,MEM_COMMIT | MEM_RESERVE,PAGE_READWRITE);
// 分配成功
if(NULL != pcLibFileRemote)
{
// 把我们的DLL文件名写入目标进程的内存中
if(WriteProcessMemory(handleTarget,pcLibFileRemote,(LPVOID)pcFileName,iSize,NULL))
{
// 我们要在内存中执行的LoadLibrary函数所在的系统DLL
HMODULE hmoduleKernel32 = LoadLibrary(_TEXT("Kernel32"));
// 获取Kernel32.dll中的Loadlibrary函数的地址
#ifdef UNICODE // 为了适应UNICODE和ANSI两种编码方式
PTHREAD_START_ROUTINE pStart = (PTHREAD_START_ROUTINE)GetProcAddress(hmoduleKernel32,"LoadLibraryW");
#else
PTHREAD_START_ROUTINE pStart = (PTHREAD_START_ROUTINE)GetProcAddress(hmoduleKernel32,"LoadLibraryA");
#endif
/*
建立远程线程进行注入。这里多说几句CreateRemoteThread函数是在我们的引导程序DllLoader.exe中执行的,如果lpParamter参数直接传递字符串的话 这个字符串的内存是在DllLoader.exe中的,目标进程是无法访问这个地址的,这就是之所以前面很麻烦的把我们的DLL的文件名用WriteProcessMemory写入到目标进程的内存中的原因,只有这样目标进程才能正确的得到文件名参数。
*/
CreateRemoteThread(handleTarget,NULL,0,pStart,pcLibFileRemote,0,NULL);
}
}
}
这段代码就能在建立的目标进程中创建远程线程,并让远程线程一启动就执行LoadLibrary函数加载我们的myDll.dll,使得我们写的代码得以执行。其效果就是在目标程序启动之前,先弹出了一个对话框,确定了之后才启动目标程序。某些汉化补丁或打包程序的LOGO界面就是用类似的方法实现的。此时我们的DLL就注入到了目标程序的进程中,可以访问目标进程中的任意内存,这样进行API的挂接和监视修改目标进程中内存的工作也变得非常简单了,可以用于制作外挂程序、插件、或者游戏的修改器等等,这些属于以后讨论的内容,这篇文章暂不涉及。
ScienWu
2006-11-22
附:主要文件源程序代码
DLL工程myDll
头文件myDll.cpp
// myDll.cpp开头
#include "stdafx.h"
#ifdef _MANAGED
#pragma managed(push, off)
#endif
void myTestFun();
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
myTestFun();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
#ifdef _MANAGED
#pragma managed(pop)
#endif
void myTestFun()
{
MessageBoxEx(NULL,_TEXT("测试文本"),_TEXT("测试标题"),MB_OK,0);
}
// myDll.cpp结尾
Exe工程DllLoader.exe
// DllLoader.cpp开头
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
// 我们自己的DLL文件的文件名,如果不在同一目录下,需要指定完成路径
TCHAR * pcFileName = _TEXT("myDll.dll");
// 计算文件名字符串的长度
int iSize = ( _tcslen(pcFileName) + 1 ) * sizeof(TCHAR);
// 要写入目标进程的字符串参数的地址
TCHAR * pcLibFileRemote = NULL;
// 目标程序的文件名
TCHAR * pcTargetFileName=_TEXT("ollyDbgTest.exe");
// 创建进程所需的进程信息结构体
PROCESS_INFORMATION ProcessInfo;
// 创建进程所需的启动信息结构体
STARTUPINFO StartupInfo;
ZeroMemory(&StartupInfo, sizeof(StartupInfo)); // 初始化工作
StartupInfo.cb = sizeof StartupInfo; // 计算大小
// 开始为目标程序建立进程
if(CreateProcess(pcTargetFileName, NULL,NULL,NULL,FALSE,0,NULL,NULL,&StartupInfo,&ProcessInfo))
{
// 打开进程,并获得所有权限
HANDLE handleTarget=OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcessInfo.dwProcessId);
// 在目标进程中分配iSize大小的空间以便将我们的DLL的文件名作为参数写入目标进程
pcLibFileRemote=(TCHAR*)VirtualAllocEx(handleTarget,NULL,iSize,MEM_COMMIT | MEM_RESERVE,PAGE_READWRITE);
// 分配成功
if(NULL != pcLibFileRemote)
{
// 把我们的DLL文件名写入目标进程的内存中
if(WriteProcessMemory(handleTarget,pcLibFileRemote,(LPVOID)pcFileName,iSize,NULL))
{
// 我们要在内存中执行的LoadLibrary函数所在的系统DLL
HMODULE hmoduleKernel32 = LoadLibrary(_TEXT("Kernel32"));
// 获取Kernel32.dll中的Loadlibrary函数的地址
#ifdef UNICODE // 为了适应UNICODE和ANSI两种编码方式
PTHREAD_START_ROUTINE pStart = (PTHREAD_START_ROUTINE)GetProcAddress(hmoduleKernel32,"LoadLibraryW");
#else
PTHREAD_START_ROUTINE pStart = (PTHREAD_START_ROUTINE)GetProcAddress(hmoduleKernel32,"LoadLibraryA");
#endif
/* 建立远程线程进行注入。这里多说几句CreateRemoteThread函数是在我们的引导程序DllLoader.exe中执行的,如果lpParamter参数直接传递字符串的话这个字符串的内存是在DllLoader.exe中的,目标进程是无法访问这个地址的,这就是之所以前面很麻烦的把我们的DLL的文件名用WriteProcessMemory写入到目标进程的内存中的原因,只有这样目标进程才能正确的得到文件名参数。 */
CreateRemoteThread(handleTarget,NULL,0,pStart,pcLibFileRemote,0,NULL);
}
}
}
return 0;
}
// DllLoader.cpp结尾