DLL注入浅析(下)
关于DLL注入有注册表注入和远程线程注入两种方式。
此文主讲远程线程注入(即CreateRemoteThread)。
1. LoadLibrary载入DLL
2. CreateRemoteThread远程注入及遇到的问题
3. VirtualAllocEx开辟内存
4. WriteProcessMemory写入字符串
1.LoadLibrary从 根本上说,DLL注入技术就是要求目标进程中的一个线程调用LoadLibrary来载入我们想要的DLL(上篇文章已经讲到了LoadLibrary的 用法)。但是我们不能轻易的控制别人进程中的线程,否则整个操作系统还不乱成一团。幸运的是操作系统给出了一种方式让我们在一个进程中创建自己的线程,这 样我们就可以在这个线程中调用LoadLibrary载入我们的DLL。这种方法就是调用CreateRemoteThread函数。
2.CreateRemoteThread参数hProcess:这个参数表示创建的线程归哪个进程所有。
参数lpThreadAttributes:这个参数为NULL,线程就会得到默认的安全描述符,并且句柄不能被继承。更多情况自行查询MSDN(https://msdn.microsoft.com/zh-cn/vstudio/ms682437%28v=vs.95%29.aspx)
参数dwStackSize:为线程分配的栈大小,如果是0,就分配默认大小。
参数lpStartAddress:这个参数是线程函数的内存地址。
参数lpParameter:线程参数地址。
参数dwCreationFlags:这个标记会控制线程的创建
参数lpThreadId:用来储存新建线程的ID,一般设置为0,代表我们对它没兴趣╭(╯^╰)╮
接下来就是远程线程注入最巧妙的环节:
我们来查看下CreateRemoteThread的第四个参数,这个参数要求一个线程函数的内存地址,线程函数的创建使用ThreadProc,其定义如下:
我们在查看下LoadLibrary的定义:
虽 然二者并非完全一样,但是已经足够接近了,二者都接收一个参数,返回一个值,都使用相同的调用约定WINAPI。所以我们现在要做的事就是创建一个线程, 并且把线程函数地址设置为LoadLibraryW(今天LoadLibrary宏多数被扩展为LoadLibraryW)
那我们实际上只需要一行代码:
HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, LoadLibrary, L”C:\\MyLib.dll”, 0, NULL)
我们用CreateRemoteThread在目标进程创建线程,并用线程调用LoadLibrary,并向LoadLibrary内传入C:\\MyLib.dll(需要再入的DLL地址)参数,载入我们的DLL。
一切都是这么美好,然而问题来了。
第 一个问题是我们并不能直接把LoadLibraryW当作第四个参数传给CreateRemoteThread,原因有点难以理解。在编译和链接一个程序 的时候,生成的二进制文件中会包含一个导入段。这个段由一系列转换函数构成,这些转换函数用来跳转到导入的函数。因此,当我们直接用 CreateRemoteThread调用LoadLibraryW时,该引用会被解析为我们模块中的LoadLibraryW地址,如果把这个转换函数 的地址作为远程线程的初始地址传入,我们就根本不知道会执行什么代码。
所以我们需要用到GetProcAddress(上一篇文章已经讲到了)来获取LoadLibrary的准确地址。
GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryW");
第二个是DLL路径的问题。字符串“C:\\MyLib.dll位于调用进程的地址空间中,我们把这个地址传给新创建的远程线程,但是当LoadLibraryW访问的时候会发生违规访问。所以我们必须把要传入的地址字符串放入远程进程的地址空间中去。
我们需要用到VirtualAllocEx函数
3.VirtualAllocExVirtualAllocEx函数可以让一个进程在另一个进程的地址空间中分配一块内存。
参数hProcess:分配内存的进程句柄。
参数lpAddress:指向分配内存的起始地址,如果为NULL,函数将自己决定在哪里分配内存。
参数dwSize:需要分配的内存大小。
参数flAllocationType:分配类型
参数flProtect:这个参数给区域指定的保护属性
一旦为字符串分配了一块内存,我们还需要一种方法来把字符串从进程地址空间中复制到远程地址空间中去,这里我们就需要用到WriteProcessMemory函数、
4.WriteProcessMemory参数hProcess:指向远程进程
参数lpBaseAddress:表示远程进程中的地址
参数lpBuffer:将写入远程进程内存中的包含数据的本地内存地址
参数nSize:传输字节数
参数*lpNumberOfBytesWritten:表示实际传输字节数,如果为NULL,表示忽略。
(1).用VirtualAllocEx函数为我们在远程地址空间中分配一块内存。
(2).用WriteProcessMemory函数把我们的DLL路径复制到第1步分配的内存中去。
(3).用GetProcAddress函数来得到LoadLibraryW在kernel32.dll中的实际地址。
(4).用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的LoadLibrary函数并传入第1步写有DLL路径的内存地址。此时DLL已经被注入到了远程进程的地址空间中了。
(5).使用VirtualFreeEx函数来释放内存。
// RemoteMain.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <Windows.h> #include <tchar.h> intSetDebugPrivileges(void) { TOKEN_PRIVILEGES priv = { 0 }; HANDLE hToken = NULL; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,&hToken)) { priv.PrivilegeCount= 1; priv.Privileges[0].Attributes= SE_PRIVILEGE_ENABLED; if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid)) { if (AdjustTokenPrivileges(hToken, FALSE,&priv, 0, NULL, NULL) ==0) { printf("AdjustTokenPrivilege Error![%u]\n",GetLastError()); } } CloseHandle(hToken); } return GetLastError(); } //注入的主函数 BOOLInjectDll(DWORD dwPID, LPCTSTR szDllPath) { HANDLE hProcess = NULL, hThread = NULL; HMODULE hMod = NULL; LPVOID pRemoteBuf = NULL; DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR); LPTHREAD_START_ROUTINE pThreadProc; //取得进程句柄 if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) { _tprintf(L"OpenProcess(%d)failed!!! [%d]\n", dwPID, GetLastError()); return FALSE; } _tprintf(L"hProcess:%x\n", hProcess); //分配内存 pRemoteBuf =VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE); _tprintf(L"pRemoteBuf:%x\n", pRemoteBuf); //把DLL地址写入内存 WriteProcessMemory(hProcess,pRemoteBuf, (LPVOID)szDllPath,dwBufSize, NULL); //获取LoadLibrary具体位置 pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryW");\ _tprintf(L"pThreadProc:%x\n", pThreadProc); //创建远程线程,实现DLL注入 hThread =CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); WaitForSingleObject(hThread,INFINITE); CloseHandle(hThread); CloseHandle(hProcess); return TRUE; } //分别传入2个参数,第一个是远程进程的PID码,第二个是我们的DLL地址 int _tmain(int argc, TCHAR *argv[]) { if (argc != 3) { _tprintf(L"USAGE: %s pid dll_path\n", argv[0]); return 1; } //调整访问令牌,提升权限 _tprintf(L"[+]Setting Debug Privileges [%ld]\n",SetDebugPrivileges()); if (InjectDll((DWORD)_tstol(argv[1]), argv[2])) _tprintf(TEXT("InjectDll(\"%s\") success!!!\n"), argv[2]); else _tprintf(L"InjectDll(\"%s\")failed!!!\n", argv[2]); system("pause"); return 0; }
仔细分析下这段代码,很容易理解的。
注意:请保证要被注入的程序与注入程序和注入DLL是同位的,不要尝试使用32位程序注入64位(或64位注入32位),建议以管理员权限运行
我们接下来实际看一下DLL注入的作用(接下讲API Hook和进程隐藏的文章都以DLL注入为基础):
笔者最近在玩一款游戏,我将通过演示用DLL注入来修改游戏中储存金币的内存,其中用CE找金币内存一项步骤已省去,读者有兴趣还可以去查找基址,不过这里都不是我们讨论的内容,省去不表。
用CE找到储存金币的内存为2C3F0E08(当然用CE直接修改此处内存也是可以的,不过我们目地是演示DLL注入的作用)
我此时拥有的金币为
(原谅手残党的我已经修改过无数次了。。。。。。)
编写注入程序YunxueMain.cpp(是不是暴露什么游戏了。。。)和DLL程序RemoteInjection.cpp
YunxueMain.cpp代码同RemoteMain.cpp代码一样
RemoteInject.cpp代码如下:
// RemoteInjection.cpp : 定义DLL 应用程序的导出函数。 // #include "stdafx.h" #include <Windows.h> #include <tchar.h> #include <iostream> using namespacestd; HINSTANCEg_hInstance = NULL; DWORD WINAPIThreadProc(LPVOID lParam) { //修改金币 int *gold = (int*)0x2C3F0E08; *gold = 9999999; MessageBoxA(NULL, "注入成功!修改金币!", "注入成功!",MB_OK); return 0; } BOOL APIENTRYDllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { HANDLE hThread = NULL; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: g_hInstance = hModule; //使用DebugView可以看到我们注入成功并输出了<RemoteHook.dll> Injection!!!一句话 OutputDebugString(L"<RemoteHook.dll>Injection!!!"); hThread = CreateThread(NULL,0, ThreadProc, NULL, 0, NULL); CloseHandle(hThread); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
然后我们在命令行里打开,用任务管理器查看游戏PID码
命令行里执行程序,输入PID码和DLL路径
回车后显示注入并修改成功
接下来的文章会以DLL注入技术为基础,实现API Hook和进程隐藏一系列功能