1.简介
从根本上来说,DLL注入技术要求目标进程中的一个线程调用LoadLibrary来载入我们想要的DLL。由于我们不能轻易地控制别人进程中的线程,因此这种方法要求我们在目标进程中创建一个新的线程,Windows提供了创建远程线程的函数。
HANDLE CreateRemoteThread(
[in] HANDLE hProcess,
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in] LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out] LPDWORD lpThreadId
);
- hProcess:用于创建线程的进程的句柄。
- lpThreadAttributes:指向SECURITY_ATTRIBUTES结构的指针,该结构确定返回的句柄是否可以被子进程继承。
- dwStackSize:堆栈的初始大小,以字节为单位。如果此参数为零,则新线程使用默认大小。
- lpStartAddress:指向线程要执行的应用程序定义函数的指针。
- lpParameter:指向要传递给线程的变量的指针。
- dwCreationFlags:控制线程创建的标志。
- lpThreadId:指向接收线程标识符的变量的指针。如果该参数为NULL,则不返回线程标识符。
2.步骤
- 用VirtualAllocEx函数在远程进程的地址空间中分配一块内存。
- 用WriteProcessMemory函数把DLL的路径名复制到第一步分配的内存中。
- 用GetProcAddress函数来得到LoadLibraryW或LoadLibraryA函数的实际地址(在Kernel32.dll中)
- 用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的LoadLibrary函数并在参数中传入第一步分配的内存地址。这时,DLL已经被注入到远程进程的地址空间中,DLL的DllMain函数会收到DLL_PROCESS_ATTACH通知并且执行我们想要执行的代码。
- VirtualFreeEx来释放第1步分配的内存。
- 用GetProcAddress函数来得到FreeLibrary函数的实际地址(在Kernel32.dll中)
- 用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用FreeLibrary函数并在参数中传入远程DLL的HMODULE。
步骤1、2、3原理简介:
字符串“D://remoteDll.dll”,位于调用进程的空间地址中,我们把这个地址传给新创建的远程线程,远程线程再把它传给LoadLibrary,当LoadLibrary去访问这个内存地址的时候,DLL的路径字符串并不在那里,远程进程的线程就很可能引发访问违规。为了解决这个问题,我们需要把DLL的字符串存放到远程进程的地址空间中去,所以会使用VirtualAllocEx和WriteProcessMemory方法。
3.示例
3.1先做一个动态库remoteDll.dll
此库的功能是,一旦被注入到进程的地址空间中,就报告该进程正在使用的所有DLL,并将打印信息写入到本地文件D://out.txt中。
const char* filepath = "D://out.txt";
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
FILE* fp = freopen(filepath, "w", stdout);
PBYTE pb = NULL;
MEMORY_BASIC_INFORMATION mbi;
while (VirtualQuery(pb, &mbi, sizeof(mbi)) == sizeof(mbi))
{
int nlen;
char szModName[MAX_PATH];
if (mbi.State == MEM_FREE)
mbi.AllocationBase = mbi.BaseAddress;
if ((mbi.AllocationBase == hModule) || (mbi.AllocationBase != mbi.BaseAddress) || (mbi.AllocationBase == NULL))
nlen = 0;
else
{
nlen = GetModuleFileNameA((HINSTANCE)mbi.AllocationBase, szModName, _countof(szModName));
}
if (nlen > 0)
{
char szBuf[MAX_PATH] = { 0 };
wsprintfA(szBuf, "\n%p-%s", mbi.AllocationBase, szModName);
printf("%s\n", szBuf);
}
pb += mbi.RegionSize;
}
fclose(fp);
}
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
3.2写一个测试程序
新建一个带界面的程序
改了一下窗口界面,这里加了一个菜单项注入。
点击注入出现弹窗,如下图所示。
输入进程ID,点击确定即可,下面是注入的回调函数。
// “注入”框的消息处理程序。
INT_PTR CALLBACK Inject(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
{
return (INT_PTR)TRUE;
}
case WM_COMMAND:
if (LOWORD(wParam) == IDOK)
{
//获取ID值
DWORD processID = GetDlgItemInt(hDlg, IDC_EDIT1, NULL, FALSE);
if (processID == 0)
{
processID = GetCurrentProcessId();
}
if (InjectLib(processID, TEXT("D://remoteDll.dll")))
{
printf("inject ok");
EjectLib(processID, TEXT("D://remoteDll.dll"));
}
else
{
printf("inject failed");
}
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
else if (LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
InjectLib函数如下:
BOOL WINAPI InjectLib(DWORD processID, PCWSTR pszLibFile)
{
BOOL bOk = FALSE;
HANDLE hProcess = NULL, hThread = NULL;
PWSTR pszLibFileRemote = NULL;
__try
{
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, processID);
if (hProcess == NULL)
__leave;
int cch = 1 + lstrlenW(pszLibFile);
int cb = cch * sizeof(wchar_t);
pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);
if (pszLibFileRemote == NULL)
__leave;
if (!WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, cb, NULL))
__leave;
FARPROC pfnThreadRtn = GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
if (pfnThreadRtn == NULL)
__leave;
hThread = CreateRemoteThread(hProcess, NULL, 0, (PTHREAD_START_ROUTINE)pfnThreadRtn, pszLibFileRemote, 0, NULL);
if (hThread == NULL)
__leave;
WaitForSingleObject(hThread, INFINITE);
bOk = TRUE;
}
__finally
{
if (pszLibFileRemote != NULL)
VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE);
if (hThread != NULL)
CloseHandle(hThread);
if (hProcess != NULL)
CloseHandle(hProcess);
}
return bOk;
}
EjectLib函数如下:
BOOL WINAPI EjectLib(DWORD processID, PCWSTR pszLibFile)
{
BOOL bOk = FALSE;
HANDLE hthSnapshot = NULL;
HANDLE hProcess = NULL, hThread = NULL;
PWSTR pszLibFileRemote = NULL;
__try
{
//获取指定进程的快照,以及这些进程使用的堆、模块和线程。
hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processID);
if (hthSnapshot == INVALID_HANDLE_VALUE)
__leave;
MODULEENTRY32W me = { sizeof(me) };
BOOL bFound = FALSE;
BOOL bMoreMods = Module32FirstW(hthSnapshot, &me); //检索与进程关联的第一个模块的信息。
for (; bMoreMods; bMoreMods = Module32NextW(hthSnapshot, &me)) //检索与进程或线程关联的下一个模块的信息
{
bFound = (_wcsicmp(me.szModule, pszLibFile) == 0) || (_wcsicmp(me.szExePath, pszLibFile));
if (bFound)
break;
}
if (!bFound)
__leave;
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION, FALSE, processID);
if (hProcess == NULL)
__leave;
FARPROC pfnThreadRtn = GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
if (pfnThreadRtn == NULL)
__leave;
//me.modBaseAddr 在所属进程的上下文中模块的基地址。
hThread = CreateRemoteThread(hProcess, NULL, 0, (PTHREAD_START_ROUTINE)pfnThreadRtn, me.modBaseAddr, 0, NULL);
if (hThread == NULL)
__leave;
WaitForSingleObject(hThread, INFINITE);
bOk = TRUE;
}
__finally
{
if (hthSnapshot != NULL)
CloseHandle(hthSnapshot);
if (hThread != NULL)
CloseHandle(hThread);
if (hProcess != NULL)
CloseHandle(hProcess);
}
return bOk;
}
3.3执行程序
打开一个记事本程序,找到PID,如下图所示,PID = 20752。
运行3.2中的程序,输入这个PID,点击确定注入成功。
3.4运行结果
注入之后,在本地文件out.txt中,可以看见正在该记事本程序正在使用的DLL。