实现DLL注入的思路是让进程运行LoadLibrary函数,把我们的DLL装载进去,怎样让进程调用LoadLibrary函数呢?首先想到的办法就是在目标进程中创建一个新的线程来调用。比如:
HANDLE WINAPI CreateRemoteThread(
__in HANDLE hProcess, //线程被创建的进程句柄
__in LPSECURITY_ATTRIBUTES lpThreadAttributes, //可设置为0,表示取默认值
__in SIZE_T dwStackSize, //可设置为0,表示取默认值
__in LPTHREAD_START_ROUTINE lpStartAddress, //线程函数指针,必须存在于远程进程地址空间中
__in LPVOID lpParameter, //传给线程函数的参数
__in DWORD dwCreationFlags, //可设置为0,表示线程创建后立刻执行
__out LPDWORD lpThreadId //返回线程ID,可设置为0,表示不返回
);
通过函数的参数可以看出,我们需要得到三个参数来创建线程以达到注入DLL的目的。下面一一介绍。
一.hProcess进程的句柄
要获得进程的句柄,有几种方法可以选择1) 使用FindWindow获得窗口句柄,接着调用GetWindowThreadProcessId来获得进程ID,最后调用OpenProcess来获得进程句柄
2) 使用EnumWindows来枚举所有窗口,并通过GetWindowText来获得窗口标题,从而找到自己需要的窗口句柄,接下来的操作与1)相同。
3) 使用CreateToolhelp32Snapshot获得进程快照,使用Process32First和Process32Next来检索每个具体的进程,并获得我们需要的进程ID,最后调用OpenProcess来获 得进程句柄。
上述几个函数的功能和参数都很简单,可以参考MSDN说明。我们采用的是第3中方式,因为有些程序会屏蔽掉前两种方法中的API。获得进程句柄的代码如下:
if(!(ProcessID=GetGameProcessName("notepad.exe")))
{
cout<<"进程没用启动"<<endl;
return 0;
}
if(!(ProcessHandle=OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcessID)))
{
cout<<"打开进程失败"<<endl;
return 0;
}
int GetGameProcessName(char *GameName)
{
PROCESSENTRY32 stProcess;
HANDLE hProcessShot;
hProcessShot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
stProcess.dwSize=sizeof(PROCESSENTRY32);
Process32First(hProcessShot,&stProcess);
do
{
if (!strcmp(GameName,stProcess.szExeFile))
{
return stProcess.th32ProcessID;
}
} while (Process32Next(hProcessShot,&stProcess));
CloseHandle(hProcessShot);
return FALSE;
}
二、线程函数
这里线程函数我们使用LoadLibrary函数,但是这里有个问题,我们调用LoadLibrary时,并不是直接调用kernel32.dll中的LoadLibry函数,而要经过一个跳转。这样我们调用的LoadLibrary函数在不同的进程中地址空间可能不一样,因为不能直接把LoadLibrary函数地址传递给CreateRometeThread函数,但是可以把kernel32.dll中的LoadLibrary函数地址传递给CreateRometeThread函数,因为几乎所有进程都会加载kernel32.dll,而且会映射到同一地址。但是这里又有新的问题,怎样才能获得kernel32.dll中的函数呢?很容易想到GetProcAddress函数,我们看关于这个函数的说明:
FARPROC GetProcAddress(
HMODULE hModule, //DLL模块句柄
LPCWSTR lpProcName //函数名称
);
第一个参数需要我们获得模块句柄,这里肯定不能用LoadLibrary来获得了,但是还有一个函数可以获得GetModuleHandle。DLL名称作为参数传递给他即可。
第二个参数不能使用“LoadLibrary”,而应该使用“LoadLibraryA”(ANSI类型)或者“LoadLibraryW”(unicode类型)。
代码如下:
hKernelModule=GetModuleHandle("kernel32.dll");
LPTHREAD_START_ROUTINE start_routine=(LPTHREAD_START_ROUTINE)GetProcAddress(hKernelModule,"LoadLibraryA");
三、线程参数
LoadLibrary函数的参数是Dll的名字,Dll的名字是字符串,它也必须存在于远程进程的地址空间中,所以我们不能直接使用字符串作为参数传递给线程,这里可以使用函数WriteProcessMemory来通过向指定的进程写内存将Dll的路径名字写进远程进程的地址空间中,我们看一看这个函数的参数:BOOL WriteProcessMemory(
HANDLE hProcess, //进程句柄,由OpenProcess获得
LPVOID lpBaseAddress, //指定进程的内存起始地址
LPVOID lpBuffer, //提供数据的缓冲区指针,它将被写入指定进程的地址空间中
DWORD nSize, //写入的字节数
LPDWORD lpNumberOfBytesWritten //输出参数,指向实际输出的字节数,这里可设置为NULL,表示忽略
);
通过函数的参数我们可以看到,这里还有一个问题,我们怎么获得指定进程的起始地址呢?这里肯定不能用new或者malloc,因为他们分配的是当前进程的内存,这就需要另外一个函数VirtualAllocEx,该函数在指定进程的虚拟地址空间中提交或保留一个区域的内存,对于的函数为VirtualFreeEx释放回收指定进程的地址空间。代码如下:
addrStart=VirtualAllocEx(ProcessHandle,0,1024,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
if (!addrStart)
{
cout<<"申请内存失败"<<endl;
return 0;
}
WriteProcessMemory(ProcessHandle,addrStart,dll_name,1024,0);
四、函数返回
以上几个参数获得成功之后,就可以通过CreateRemoteThread函数实现DLL注入的目的了,但是远程线程什么时候结束呢?即LoadLibrary函数什么时候返回呢?我们可能需要等到LoadLibrary返回之后才能进行下一步操作。这里可以使用WaitForSingleObject来检测信号对象的情况DWORD WaitForSingleObject(
HANDLE hHandle, //对象句柄,这里为CreateRemoteThread的返回值
DWORD dwMilliseconds //等待时间,单位ms,设置为INFINITE表示一直等待
);
五、注入总结
以上几个函数就是实现Dll注入需要的API函数,下面进行一下总结,按函数执行的流程进行说明,并直接设置好需要的参数:1.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0)获得进程快照
2.Process32First(hProcessShot,&stProcess)、Process32Next(hProcessShot,&stProcess)查找获取我们需要进程的ID
3.ProcessHandle=OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcessID)获得进程句柄
4.addrStart=VirtualAllocEx(ProcessHandle,0,1024,MEM_COMMIT,PAGE_EXECUTE_READWRITE)申请指定进程的内存空间
5.WriteProcessMemory(ProcessHandle,addrStart,dll_name,1024,0)向指定空间中写入dll名字
6.hModule=GetModuleHandle("kernel32.dll")获得模块的模块句柄
7.start_routine=(LPTHREAD_START_ROUTINE)GetProcAddress(hModule,"LoadLibraryA")获得模块中LoadLibrary函数地址
8.ThreadHandle=CreateRemoteThread(ProcessHandle,0,0,start_routine,addrStart,0,0)创建远程线程
9.WaitForSingleObject(ThreadHandle,INFINITE)等待线程结束
10.VirtualFreeEx(ProcessHandle,addrStart,0,MEM_RELEASE)释放进程地址空间
11.CloseHandle(ProcessHandle);关闭句柄
六、DLL卸载
我们将Dll成功注入到远程进程中了,现在来说说卸载的问题。
关于Dll的卸载需要函数Freelibrary,现在存在两个问题:
1.FreeLibrary需要以Dll的模块句柄作为参数,我们怎么获得Dll的模块句柄?
2.FreeLibrary不支持卸载本程序以外的Dll,该怎么办呢?
关于第二个问题很好解决,像LoadLibrary那样将FreeLibrary也注入到指定进程中就可以了。
现在考虑第一个问题,怎么获得Dll的模块句柄呢?我们还记得在注入Dll时使用GetModuleHandle来获得kernel32.dll的模块句柄,很容易想到这里依然使用GetModuleHandle函数,但是我们程序中并没有加载我们的DLL,不能直接调用GetModuleHandle来获得模块句柄,那怎么办?同样的将GetModuleHandle注入到指定进程中,但是怎么获得GetModuleHandle的返回值,也就是怎么获得DLL的模块句柄呢?这里要接受一个新的函数GetExitCodeThread,从指定的线程中获得退出的状态码,该函数的说明如下:
BOOL GetExitCodeThread(
HANDLE hThread, //线程句柄
LPDWORD lpExitCode //输出返回值
);
实现源码如下:
// RegeditDllOK.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <Windows.h>
#include "Tlhelp32.h"
#include <iostream>
using namespace std;
DWORD ProcessID;
HANDLE ProcessHandle;
LPVOID addrStart;
HANDLE ThreadHandle;
HANDLE hDll;
HMODULE hKernelModule;
char *dll_name="RegeditDll.dll";
int GetGameProcessName(char *GameName);
void UnRegisterDll()
{
LPTHREAD_START_ROUTINE start_routine=(LPTHREAD_START_ROUTINE)GetProcAddress(hKernelModule,"GetModuleHandleA");
ThreadHandle=CreateRemoteThread(ProcessHandle,0,0,start_routine,addrStart,0,0);
WaitForSingleObject(ThreadHandle,INFINITE);
GetExitCodeThread(ThreadHandle,(LPDWORD)(&hDll));
start_routine=(LPTHREAD_START_ROUTINE)GetProcAddress(hKernelModule,"FreeLibrary");
ThreadHandle=CreateRemoteThread(ProcessHandle,0,0,start_routine,hDll,0,0);
WaitForSingleObject(ThreadHandle,INFINITE);
}
void RegisterDll()
{
LPTHREAD_START_ROUTINE start_routine=(LPTHREAD_START_ROUTINE)GetProcAddress(hKernelModule,"LoadLibraryA");
ThreadHandle=CreateRemoteThread(ProcessHandle,0,0,start_routine,addrStart,0,0);
WaitForSingleObject(ThreadHandle,INFINITE);
}
int _tmain(int argc, _TCHAR* argv[])
{
if(!(ProcessID=GetGameProcessName("notepad.exe")))
{
cout<<"进程没用启动"<<endl;
return 0;
}
if(!(ProcessHandle=OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcessID)))
{
cout<<"打开进程失败"<<endl;
return 0;
}
addrStart=VirtualAllocEx(ProcessHandle,0,1024,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
if (!addrStart)
{
cout<<"申请内存失败"<<endl;
return 0;
}
WriteProcessMemory(ProcessHandle,addrStart,dll_name,1024,0);
hKernelModule=GetModuleHandle("kernel32.dll");
RegisterDll();
UnRegisterDll();
VirtualFreeEx(ProcessHandle,addrStart,0,MEM_RELEASE);
CloseHandle(ProcessHandle);
return 0;
}
int GetGameProcessName(char *GameName)
{
PROCESSENTRY32 stProcess;
HANDLE hProcessShot;
hProcessShot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
stProcess.dwSize=sizeof(PROCESSENTRY32);
Process32First(hProcessShot,&stProcess);
do
{
if (!strcmp(GameName,stProcess.szExeFile))
{
return stProcess.th32ProcessID;
}
} while (Process32Next(hProcessShot,&stProcess));
CloseHandle(hProcessShot);
return FALSE;
}