代码注入之远程线程篇
引子
前些日子由于项目要求,在网上到处找资料,于无意中发现了 CodeProject 上的一篇很老的文章,文章标题为:
Three Ways to Inject Your Code into Another Process
这篇文章呢,出来很久咯,还是 03 年的文章了,可惜我弄底层弄得时间不久哦,不然应该早就看过这篇大作了,
由于是大作,而且出来的又久了,自然在网上也就到处流传咯,
所以也有人将这篇文章翻译成了中文版的,下面给出这篇大作的两个链接:
中文版:http://www.vckbase.com/document/viewdoc/?id=1886
英文版:http://www.codeproject.com/KB/threads/winspy.aspx
然后呢,这边由于老大给弄了蛮多好书过来了,其中一本就是所谓的骇客之类的东西,
虽然是繁体的,但是知识点都很不错哦,所以也拿过来看了看,就发现其中对这个远程线程的注入有很多的介绍,
而且貌似前些年的很多病毒或者木马就是通过这屁东西来隐藏的,
看着看着就来劲了,而后呢,自己就根据书中的思路,
然后再结合自己的理解,将理解整理出了代码,然后就出了这篇文章咯 !
然后注意一点的是,在 CodeProject 上的那篇文章中介绍了三种注入代码技术,
第一种就是众所周知的 Hook 了;
第二种是直接将所要执行的代码全部拷贝到宿主进程中,即代码远程注入技术;
第三种则是 DLL 的远程注入技术了,其通过在宿主进程加载自己写的另外的一个 DLL 来实现注入;
然后在我的这篇博文中,我也只是总结前人的思想,然后再加入我自己的立即,
同时由于 Hook 太常见了,常见得不行了,所以我并不会介绍 Hook 了,而只介绍后面的两种方式。
代码远程注入技术
Demo 的效果:
创建的项目为 RemoteThreadCode,即远程注入代码,其实现的功能是当运行 RemoteThreadCode.exe 时,
会在 Explorer.exe 进程中创建一个线程,而这个创建的线程功能实现很简单,
就是弹出一个消息框即 OK !
Demo 的效果展示:
当双击执行 RemoteThreadCode.exe 时,则会注入一个线程到 Explorer.exe 中
当点击确定后,注入到 Explorer.exe 中的线程执行完毕,从而 WaitForSingleObject 等待成功 !
基本思路以及所对应的代码:
1. 提升进程权限,如果权限不够的话,很容易造成 OpenProcess 失败;
1: //=====================================================================================//
2: //Name: bool AdjustProcessTokenPrivilege() //
3: // //
4: //Descripion: 提升当前进程权限 //
5: //=====================================================================================//
6: bool AdjustProcessTokenPrivilege()
7: {
8: LUID luidTmp;
9: HANDLE hToken;
10: TOKEN_PRIVILEGES tkp;
11:
12: if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
13: {
14: OutputDebugString("AdjustProcessTokenPrivilege OpenProcessToken Failed ! \n");
15:
16: return false;
17: }
18:
19: if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luidTmp))
20: {
21: OutputDebugString("AdjustProcessTokenPrivilege LookupPrivilegeValue Failed ! \n");
22:
23: CloseHandle(hToken);
24:
25: return FALSE;
26: }
27:
28: tkp.PrivilegeCount = 1;
29: tkp.Privileges[0].Luid = luidTmp;
30: tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
31:
32: if(!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL))
33: {
34: OutputDebugString("AdjustProcessTokenPrivilege AdjustTokenPrivileges Failed ! \n");
35:
36: CloseHandle(hToken);
37:
38: return FALSE;
39: }
40: return true;
41: }
最好是选择系统要想运行,则必须开启的那种进程,比如资源管理器进程 Explorer.exe,
Windows 子系统进程 csrss.exe 等等,但是这里注意的是,我注入 System 进程的时候造成了失败哦,
所以最好还是别拿 System 做实验,而且如果你注入失败了的话,是会造成宿主进程崩溃的,
等下一不小心把 System 进程给弄崩溃了就不好了;
1: //=====================================================================================//
2: //Name: bool ProcessIsExplorer(DWORD dwProcessId) //
3: // //
4: //Descripion: 判定一个进程是否为 Explorer 进程 //
5: //=====================================================================================//
6: bool ProcessIsExplorer(DWORD dwProcessId)
7: {
8: HANDLE hProcess;
9:
10: hProcess = NULL;
11:
12: hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, dwProcessId);
13: if(NULL == hProcess)
14: {
15: OutputErrorMessage("ProcessIsExplorer - OpenProcess Failed , Error Code Is %d , Error Message Is %s !");
16:
17: return FALSE;
18: }
19:
20: DWORD dwNameLen;
21: TCHAR pathArray[MAX_PATH];
22: ZeroMemory(pathArray, MAX_PATH);
23:
24: dwNameLen = 0;
25: dwNameLen = GetModuleFileNameEx(hProcess, NULL, pathArray, MAX_PATH);
26: if(dwNameLen == 0)
27: {
29: CloseHandle(hProcess);
30:
31: return FALSE;
32: }
33:
34: TCHAR exeNameArray[MAX_PATH];
35: ZeroMemory(exeNameArray, MAX_PATH);
36: _tsplitpath(pathArray, NULL, NULL, exeNameArray, NULL);
37:
38: string str1 = exeNameArray;
39: if((str1.compare("Explorer") == 0) || (str1.compare("explorer") == 0))
40: {
41: CloseHandle(hProcess);
42:
43: return TRUE;
44: }
45:
46: return FALSE;
47: }
3. 打开宿主进程了(我这里打开的是 Explorer.exe 进程),思路是首先变量当前系统下运行的所有的进程,
然后遍历获取到得所有的进程的 PID,再调用 ProcessIsExplorer 函数来判断这个进程是否为 Explorer.exe 进程,
如果是则记录下这个进程的 PID 就可以了,这样就获得了 Explorer.exe 进程的 PID 了,
再通过 OpenProcess 来打开这个 Explorer.exe 进程就 OK 了;
1: //提升当前进程的权限
2: AdjustProcessTokenPrivilege();
3:
4: //第一个参数为用来保存所有的进程 ID
5: //第二个参数则是第一个参数的字节数
6: //第三个参数则是写入 dwProcess 数组的字节数
7: EnumProcesses(dwProcess, sizeof(dwProcess), &dwNeeded);
8:
9: //找到 explorer.exe 进程的 ID
10: dwExplorerId = 0;
11: for(int i = 0; i < dwNeeded / sizeof(DWORD); i++)
12: {
13: if(0 != dwProcess[i])
14: {
15: if(ProcessIsExplorer(dwProcess[i]))
16: {
17: dwExplorerId = dwProcess[i];
18: break;
19: }
20: }
21: }
22:
23: hProcess = NULL;
24: hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwExplorerId);
25: if(NULL == hProcess)
26: {
27: OutputErrorMessage("main - OpenProcess Failed , Error Code Is %d , Error Message Is %s !");
28: }
4. 在宿主进程中分配好存储空间,这个存储空间是用来存放我们将要创建的远程线程的线程处理例程的,
这里需要注意的是:我们分配的内存必须标记必须带有 EXECUTE,因为分配的这块内存是用来存放线程处理例程的,
而线程处理例程必须得执行,所以必须得带有 EXECUTE 标记,而至于 WRITE 标记的话,很明显是需要的,
因为我们在后面的代码中还必须调用 WriteProcessMemory 来将线程处理例程写入到这块内存中,自然其必须可写;
1: //在 hProcess 所代表的进程内部分配虚拟内存来容纳我们将要创建的远程线程
2: PVOID pRemoteThread = VirtualAllocEx(hProcess, NULL, THREAD_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
3: if(NULL == pRemoteThread)
4: {
5: OutputErrorMessage("main - VirtualAllocEx Failed , Error Code Is %d , Error Message Is %s !");
6:
7: //关闭进程句柄
8: CloseHandle(hProcess);
9: }
5. 将远程线程处理例程写入到 4 中在宿主进程中所分配的内存中,这个可以直接调用 WriteProcessMemory 来实现;
1: //往我们在 hProcess 进程中分配的虚拟内存里面写入数据,这里主要是将整个线程都写进去
2: if(WriteProcessMemory(hProcess, pRemoteThread, &RemoteThreadProc, THREAD_SIZE, 0) == FALSE)
3: {
4: OutputErrorMessage("main - WriteProcessMemory Failed , Error Code Is %d , Error Message Is %s !");
5:
6: //释放 VirtualAllocEx 分配的内存
7: VirtualFreeEx(hProcess, pRemoteThread, 0, MEM_RELEASE);
8: CloseHandle(hProcess);
9: }
6. 在宿主进程中分配好存储空间,这个存储空间是用来存放我们将要传递给远程线程线程处理例程的参数,
从下面的结构体中可以看出,其由三个参数组成,第一个参数代表要在对话框中显示的内容,
第二个参数代表要在对话框中显示的标题,第三个参数则是 MessageBox 这个 API 的地址,
因为在 Explorer.exe 中 MessageBox 的地址会发生重定向,所以需要将其地址通过参数传递给线程处理例程;
1: typedef struct _REMOTE_PARAMETER
2: {
3: CHAR m_msgContent[MAX_PATH];
4: CHAR m_msgTitle[MAX_PATH];
5: DWORD m_dwMessageBoxAddr;
6:
7: }RemotePara, * PRemotePara;
1: //=====================================================================================//
2: //Name: void GetMessageBoxParameter(PRemotePara pRemotePara) //
3: // //
4: //Descripion: 获得 MessageBox 这个 API 的地址以及填充的参数 //
5: //=====================================================================================//
6: void GetMessageBoxParameter(PRemotePara pRemotePara)
7: {
8: HMODULE hUser32 = LoadLibrary("User32.dll");
9:
10: pRemotePara->m_dwMessageBoxAddr = (DWORD)GetProcAddress(hUser32, "MessageBoxA");
11: strcat(pRemotePara->m_msgContent, "Hello, Zachary.XiaoZhen !\0");
12: strcat(pRemotePara->m_msgTitle, "Hello\0");
13:
14: //注意要释放掉 User32
15: FreeLibrary(hUser32);
16: }
1: RemotePara remotePara;
2: ZeroMemory(&remotePara, sizeof(RemotePara));
3: GetMessageBoxParameter(&remotePara);
4:
5: //在 hProcess 所代表的进程中分配虚拟内存来容纳线程的参数部分
6: PRemotePara pRemotePara = (PRemotePara)VirtualAllocEx(hProcess, NULL, sizeof(RemotePara), MEM_COMMIT, PAGE_READWRITE);
7: if(NULL == pRemotePara)
8: {
9: OutputErrorMessage("main - VirtualAllocEx Failed , Error Code Is %d , Error Message Is %s !");
10:
11: //释放 VirtualAllocEx 分配的内存
12: VirtualFreeEx(hProcess, pRemoteThread, 0, MEM_RELEASE);
13: CloseHandle(hProcess);
14: }
7. 将参数写入到 6 中在宿主进程中所分配的内存中,同样是调用 WriteProcessMemory 来完成;
1: //往在 hProcess 进程中分配的虚拟内存中写入参数数据
2: if(WriteProcessMemory(hProcess, pRemotePara, &remotePara, sizeof(RemotePara), 0) == FALSE)
3: {
4: OutputErrorMessage("main - WriteProcessMemory Failed , Error Code Is %d , Error Message Is %s !");
5: //释放 VirtualAllocEx 分配的内存
6: VirtualFreeEx(hProcess, pRemoteThread, 0, MEM_RELEASE);
7: VirtualFreeEx(hProcess, pRemotePara, 0, MEM_RELEASE);
8:
9: CloseHandle(hProcess);
10: }
8. 调用 CreateRemoteThread 在 Explorer.exe(宿主进程)中创建远程线程;
注意,当远程线程没有执行完时,不能够通过 VirtualFreeEx 来将远程进程中的内存释放掉,
你想啊,我他妈的线程都还在 Explorer.exe 里面执行,你他妈的在外面把我占的内存给释放掉了,我还执行个屁啊 !
所以这里调用了 WaitForSingleObject 来等待这个远程线程执行完毕,
其执行完毕后再释放在 Explorer.exe 中所分配的存储空间 !
1: HANDLE hThread;
2: DWORD dwThreadId;
3:
4: hThread = NULL;
5: dwThreadId = 0;
6:
7: //将已经写入到 hProcess 进程中的线程以及线程的参数作为 CreateRemoteThread 的参数,从而创建远程线程
8: hThread = CreateRemoteThread(hProcess, NULL, 0, (DWORD (WINAPI *)(LPVOID))pRemoteThread, pRemotePara, 0, &dwThreadId);
9: if(NULL == hThread)
10: {
11: OutputErrorMessage("main - CreateRemoteThread Failed , Error Code Is %d , Error Message Is %s !");
12: }
13: else
14: {
15: OutputSuccessMessage("Code Inject Success !");
16: }
17:
18: //等待远程线程结束
19: WaitForSingleObject(hThread, INFINITE);
20: CloseHandle(hThread);
21:
22: //必须等到远程线程结束后才能释放宿主进程中所分配的内存,否则宿主进程会直接崩溃
23: //释放 VirtualAllocEx 分配的内存
24: VirtualFreeEx(hProcess, pRemoteThread, 0, MEM_RELEASE);
25: VirtualFreeEx(hProcess, pRemotePara, 0, MEM_RELEASE);
26:
27: CloseHandle(hProcess);
9. 编写好远程线程的线程处理例程即可;
1: //=====================================================================================//
2: //Name: bool RemoteThreadProc(LPVOID lpParameter) //
3: // //
4: //Descripion: 远程线程处理例程 //
5: //=====================================================================================//
6: DWORD WINAPI RemoteThreadProc(PRemotePara pRemotePara)
7: {
8: //这个 MessageBox 的地址必须由外部参数传入,因为在其他进程中需要重定向
9: typedef int (WINAPI *MESSAGEBOXA)(HWND, LPCSTR, LPCSTR, UINT);
10:
11: MESSAGEBOXA MessageBoxA;
12: MessageBoxA = (MESSAGEBOXA)pRemotePara->m_dwMessageBoxAddr;
13:
14: //调用 MessageBoxA 来打印消息
15: MessageBoxA(NULL, pRemotePara->m_msgContent, pRemotePara->m_msgTitle, MB_OK);
16:
17: return 0;
18: }
DLL 远程注入技术
Demo 的效果:
创建的项目为 RemoteThreadDll,即远程注入 DLL,其实现的功能是当运行 RemoteThreadDll.exe 时,
会在 Explorer.exe 进程中创建一个线程,而这个创建的线程功能实现则相对于上面的远程注入代码来说复杂一点,
在线程的处理例程中,首先是由线程参数传递过来的 LoadLibrary 的地址
和 GetProcAddress 的地址来找到 LoadLibrary 和 GetProcAddress API,
然后再通过 LoadLibrary(“MyDllName”) 来加载到我自己的 DLL,
然后再通过 GetProcAddress 在这个 DLL 中找到我的 DLL 所公开的函数,
再就是调用这个公开的函数了,我新建的 DLL 项目为 – ZacharyDll.dll,
该 DLL 中导出了两个函数,一个函数用来弹出一个对话框,一个函数则是用来打印出调试信息;
Demo 的效果展示:
当双击执行 RemoteThreadCode.exe 时,则会注入一个线程到 Explorer.exe 中,
而后注入的这个线程就会调用我自己的 ZacharyDll.dll,
再调用 ZacharyDll.dll 中导出的两个函数了,一个输出调试信息,一个弹出对话框:
当点击确定后,注入到 Explorer.exe 中的线程执行完毕,从而 WaitForSingleObject 等待成功 !
基本思路以及所对应的代码:
1. 提升进程权限,如果权限不够的话,很容易造成 OpenProcess 失败;
这一部分的代码同上面的远程注入代码是一样的;
2. 确定你的宿主进程,即你所要注入代码的进程,这个其实很好办,你要是不想你的木马或者病毒被别个一下子就结束了的话,
最好是选择系统要想运行,则必须开启的那种进程,比如资源管理器进程 Explorer.exe,
Windows 子系统进程 csrss.exe 等等,但是这里注意的是,我注入 System 进程的时候造成了失败哦,
所以最好还是别拿 System 做实验,而且如果你注入失败了的话,是会造成宿主进程崩溃的,
等下一不小心把 System 进程给弄崩溃了就不好了;
这一部分的代码同上面的远程注入代码是一样的;
3. 打开宿主进程了(我这里打开的是 Explorer.exe 进程),思路是首先变量当前系统下运行的所有的进程,
然后遍历获取到得所有的进程的 PID,再调用 ProcessIsExplorer 函数来判断这个进程是否为 Explorer.exe 进程,
如果是则记录下这个进程的 PID 就可以了,这样就获得了 Explorer.exe 进程的 PID 了,
再通过 OpenProcess 来打开这个 Explorer.exe 进程就 OK 了;
这一部分的代码同上面的远程注入代码是一样的;
4. 在宿主进程中分配好存储空间,这个存储空间是用来存放我们将要创建的远程线程的线程处理例程的,
这里需要注意的是:我们分配的内存必须标记必须带有 EXECUTE,因为分配的这块内存是用来存放线程处理例程的,
而线程处理例程必须得执行,所以必须得带有 EXECUTE 标记,而至于 WRITE 标记的话,很明显是需要的,
因为我们在后面的代码中还必须调用 WriteProcessMemory 来将线程处理例程写入到这块内存中,自然其必须可写;
这一部分的代码同上面的远程注入代码是一样的;
5. 将远程线程处理例程写入到 4 中在宿主进程中所分配的内存中,这个可以直接调用 WriteProcessMemory 来实现;
这一部分的代码同上面的远程注入代码是一样的;
6. 在宿主进程中分配好存储空间,这个存储空间是用来存放我们将要传递给远程线程线程处理例程的参数,
从下面的结构体中可以看出,其由六个参数组成,
第一个参数代表 ZacharyDll.dll 中导出的 PrintMessageBox 的名称,
第二个参数代表 ZacharyDll.dll 中导出的 PrintDbgStr 的名称题,
第三个参数则是 ZacharyDll.dll 所在的路径,
第四个参数则是代表 LoadLibrary 的地址,
第五个参数代表 FreeLibrary 的地址,
第六个参数代表 GetProcAddress 的地址;
1: #define DLLNAME "\\ZacharyDll.dll\0"
2:
3: typedef struct _REMOTE_PARAMETER
4: {
5: CHAR m_printMsgBox[MAX_PATH];
6: CHAR m_printDbgStr[MAX_PATH];
7: CHAR m_strDllPath[MAX_PATH];
8: DWORD m_dwLoadLibraryAddr;
9: DWORD m_dwFreeLibraryAddr;
10: DWORD m_dwGetProcAddrAddr;
11:
12: }RemotePara, * PRemotePara;
1: HMODULE hKernel32 = GetModuleHandle("Kernel32");
2: if(NULL == hKernel32)
3: {
4: OutputErrorMessage("main - GetModuleHandle Failed , Error Code Is %d , Error Message Is %s !");
5:
6: //释放 VirtualAllocEx 分配的内存
7: VirtualFreeEx(hProcess, pRemoteThread, 0, MEM_RELEASE);
8: CloseHandle(hProcess);
9: }
10: else
11: {
12: RemotePara remotePara;
13: ZeroMemory(&remotePara, sizeof(RemotePara));
14:
15: //将 LoadLibraryA、FreeLibrary 和 GetProcAddress 三个 Kernel32 API 的地址保存到 remotePara 中
16: remotePara.m_dwLoadLibraryAddr = (DWORD)GetProcAddress(hKernel32, "LoadLibraryA");
17: remotePara.m_dwFreeLibraryAddr = (DWORD)GetProcAddress(hKernel32, "FreeLibrary");
18: remotePara.m_dwGetProcAddrAddr = (DWORD)GetProcAddress(hKernel32, "GetProcAddress");
19:
20: string strMsgBox = "PrintMessageBox";
21: string strBbgStr = "PrintDebugString";
22:
23: CHAR tmpArray[MAX_PATH];
24: CHAR * pTmpMsgBoxArray = "PrintMessageBox";
25: CHAR * pTmpDbgStrArray = "PrintDebugString";
26:
27: //将 ZacharyDll.dll 中导出的 API 的名称保存到 remotePara 中
28: strcpy(remotePara.m_printMsgBox, pTmpMsgBoxArray);
29: strcpy(remotePara.m_printDbgStr, pTmpDbgStrArray);
30:
31: ZeroMemory(tmpArray, MAX_PATH);
32:
33: //获取到当前路径
34: GetCurrentDirectory(MAX_PATH, tmpArray);
35:
36: //路径加上 DLL 名称(从而可以将 DLL 和 Loader EXE 放在同一个目录下运行了)
37: //免去了将 DLL 复制到系统目录下的麻烦
38: string strDllPath = tmpArray;
39: strDllPath += DLLNAME;
40:
41: //将 DLL 的路径完整的复制到 remotePara 中
42: strcpy(remotePara.m_strDllPath, strDllPath.c_str());
43: //free(tmpArray);
44:
45: //在宿主进程中分配虚拟内存来容纳远程线程所需要的参数
46: PVOID pRemotePara = VirtualAllocEx(hProcess, NULL, sizeof(RemotePara), MEM_COMMIT, PAGE_READWRITE);
47: if(NULL == pRemotePara)
48: {
49: OutputErrorMessage("main - VirtualAllocEx Failed , Error Code Is %d , Error Message Is %s !");
50:
51: //释放 VirtualAllocEx 分配的内存
52: VirtualFreeEx(hProcess, pRemoteThread, 0, MEM_RELEASE);
53: CloseHandle(hProcess);
54: }
7. 将参数写入到 6 中在宿主进程中所分配的内存中,同样是调用 WriteProcessMemory 来完成;
1: //将远程线程所携带的参数写入到宿主进程中所分配的虚拟内存
2: if(NULL == WriteProcessMemory(hProcess, pRemotePara, &remotePara, sizeof(RemotePara), 0))
3: {
4: OutputErrorMessage("main - WriteProcessMemory Failed , Error Code Is %d , Error Message Is %s !");
5:
6: //释放 VirtualAllocEx 分配的内存
7: VirtualFreeEx(hProcess, pRemoteThread, 0, MEM_RELEASE);
8: VirtualFreeEx(hProcess, pRemotePara, 0, MEM_RELEASE);
9: CloseHandle(hProcess);
10: }
8. 调用 CreateRemoteThread 在 Explorer.exe(宿主进程)中创建远程线程;
注意,当远程线程没有执行完时,不能够通过 VirtualFreeEx 来将远程进程中的内存释放掉,
你想啊,我他妈的线程都还在 Explorer.exe 里面执行,你他妈的在外面把我占的内存给释放掉了,我还执行个屁啊 !
所以这里调用了 WaitForSingleObject 来等待这个远程线程执行完毕,
其执行完毕后再释放在 Explorer.exe 中所分配的存储空间 !
这一部分的代码同上面的远程注入代码是类似的;
9. 编写好远程线程的线程处理例程即可;
1: //=====================================================================================//
2: //Name: bool RemoteThreadProc(LPVOID lpParameter) //
3: // //
4: //Descripion: 远程线程处理例程 //
5: //=====================================================================================//
6: DWORD WINAPI RemoteThreadProc(PRemotePara pRemotePara)
7: {
8: //对于从参数 pRemotePara 中传过来的 API 都需要重新声明
9: typedef HMODULE (WINAPI *LOADLIBRARY_ZACHARY)(LPCSTR);
10: typedef BOOL (WINAPI *FREELIBRARY_ZACHARY)(HMODULE);
11: typedef FARPROC (WINAPI *GETPROCADDRESS_ZACHARY)(HMODULE hModule, LPCSTR lpProcName);
12:
13: //这两个 API 是由 ZacharyDLL.dll 导出的,也需要重新声明
14: typedef void (* PRINTMESSAGEBOX_ZACHARY)();
15: typedef void (* PRINTDEBUGSTRING)();
16:
17: LOADLIBRARY_ZACHARY LoadLibrary_Zachary;
18: FREELIBRARY_ZACHARY FreeLibrary_Zachary;
19: GETPROCADDRESS_ZACHARY GetProcAddress_Zachary;
20: PRINTMESSAGEBOX_ZACHARY PrintMessageBox_Zachary;
21: PRINTDEBUGSTRING PrintDebugString_Zachary;
22:
23: //在参数 pRemotePara 中保存了 LoadLibray,FreeLibrary 和 GetProcAddress 这三个 API 的地址
24: LoadLibrary_Zachary = (LOADLIBRARY_ZACHARY)pRemotePara->m_dwLoadLibraryAddr;
25: FreeLibrary_Zachary = (FREELIBRARY_ZACHARY)pRemotePara->m_dwFreeLibraryAddr;
26: GetProcAddress_Zachary = (GETPROCADDRESS_ZACHARY)pRemotePara->m_dwGetProcAddrAddr;
27:
28: //获得 DLL 所在的地址
29: PCHAR pDllPath = pRemotePara->m_strDllPath;
30:
31: //加载我们自己的 DLL - ZacharyDLL.dll
32: HMODULE hMyDll = LoadLibrary_Zachary(pDllPath);
33:
34: if(NULL != hMyDll)
35: {
36: //从 ZacharyDll.dll 中通过 GetProcAddress 获取 DLL 导出的 API 的地址
37: PrintDebugString_Zachary = (PRINTDEBUGSTRING)GetProcAddress_Zachary(hMyDll, pRemotePara->m_printDbgStr);
38: PrintMessageBox_Zachary = (PRINTMESSAGEBOX_ZACHARY)GetProcAddress_Zachary(hMyDll, pRemotePara->m_printMsgBox);
39:
40: //执行 DLL 中所导出的 API
41: PrintDebugString_Zachary();
42: PrintMessageBox_Zachary();
43: //释放所加载的 DLL
44: FreeLibrary_Zachary(hMyDll);
45: }
46: return 0;
47: }
10. 编写好要注入的 DLL – ZacharyDll.dll;
1: BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
2: {
3: switch (dwReason)
4: {
5: case DLL_PROCESS_ATTACH:
6: break;
7: case DLL_PROCESS_DETACH:
8: break;
9: case DLL_THREAD_ATTACH:
10: break;
11: case DLL_THREAD_DETACH:
12: break;
13: }
14:
15: return TRUE;
16: }
17:
18: //弹出一个对话框
19: void PrintMessageBox()
20: {
21: MessageBox(NULL, MESSAGE_CONTENT, MESSAGE_TITLE, MB_OK);
22: }
23:
24: //打印语句
25: void PrintDebugString()
26: {
27: //直接打印出 5 条同样的消息
28: for(int i=0; i<5; i++)
29: {
30: OutputDebugString("In ZacharyDll - PrintDebugString !");
31: }
32: }
两种注入技术的优点和缺点总结
使用代码远程注入技术的话,其相对于使用 DLL 远程注入技术来说,
有一个优点,就是其不会无缘无故的让宿主进程加载其他的 DLL,
为什么说这是一个优点 ? 那是因为很多的监控软件或者杀软都在实时监控着每个进程,
当一个进程中加载了其他的 DLL 时(加载 DLL 的动作相对来说是比较大的)很容易被发觉,
而且当一个 DLL 被加载到进程中以后,可以利用很多的工具,
比如 Process Explorer 之类的将该进程所加载的 DLL 枚举出来,这样你所注入的 DLL 也就暴露无遗了 !
而如果你使用的是代码注入,那相对来说会安静很多(毕竟是小动作),而且也不会让宿主进程加载其他的 DLL,
所以相对来说,其成功的可能性会更高,但是使用代码远程注入有一个致命的弱点,
那就是你所注入的代码中所使用的 API 都必须要重定向,
而且如果是自定义的函数的话,则必须将这个函数也全部拷贝到宿主进程中,
注入的代码中所使用的全局变量以及所使用的字符串都必须重新拷贝到宿主进程中,
听起来貌似没什么,但是事实上,这屁东西会搞得很复杂,就比如你在注入的代码中所使用的一个字符串,
你也必须先在宿主进程中分配虚拟内存,然后将这个字符串写入到这个新分配的虚拟内存中,
如果你字符串多的话,这样的操作会烦死人去,而且这还仅仅是字符串,对于你所需要使用的一些 Win32 API,
你也都得先获取好这些 API 的地址,然后又重复上面的操作,分配虚拟内存,写入地址,最后才能够在远程线程中调用,
所以如果你所注入的代码需要完成很复杂的功能的话,还是使用 DLL 的远程注入技术比较好,使用远程注入代码会搞死人的 !
使用 DLL 的远程注入技术的病毒或者木马通常都位于一个 DLL 中,
在系统启动时,通过另外的一个 EXE 程序来在另外的一个进程(宿主进程,比如使用 Explorer.exe)中创建一个远程线程,
然后再在这个远程线程的线程处理函数中将这个带有病毒或者木马主体的 DLL 加载到宿主进程中,
这样的话,这个带有病毒或者木马主体的 DLL 就会被宿主进程加载,从而得以在宿主进程中执行,
这样,即使我们自己的 EXE(这个进程通常被称之为 Loader)进程被关闭了也不会影响到病毒或者木马主体代码的执行,
因为这些主体代码位于 Explorer.exe 进程中执行,而 Explorer.exe 基本上不会被关闭的。
并且使用这种方法,你的恶意代码也很难被什么任务管理器之类的发现,因为只要宿主进程没有终止运行,
那么这个 DLL 也就不会在内存中卸载(当然被卸载还是可能的,杀软就可以利用这点来将这个 DLL 卸载掉)。
Demo 展望:
上面的这种注入技术用来实现木马或者其他的恶意程序其实是比较方便的,
首先由一个 Loader.exe (这个 Loader 可以通过其他的方式来设置为随机器自动启动,
这可以通过修改 system.ini 或者注册表或者服务之类的来实现)
来通过 CreateRemoteThread 来在 Explorer.exe 进程中创建一个远程线程,
而后由这个远程线程神不知鬼不觉的调用一个另外的 DLL,
然后在另外的那个 DLL 中,我们就可以做很多的事情了啊,比如最简单的,设个全局键盘钩子,用来捕获键盘的记录,
再进行一定的解析就有可能获取用户有效的密码之类的信息,同时,既然 DLL 都注入到 Explorer.exe 中了,
那么还可以做很多邪恶的事情,比如在其中悄悄的扫描用户磁盘上的文件,只要是 .jpg, .png 之类的就全部给记录下来,
或者再在 DLL 中创建个什么 SOCKET 之类的,并且将这些什么 .png 啊,.jpg 啊之类的图片数据悄悄的传出去,
然后说不准第二个艳照门就出来了 ~
当然这些都是后话了,不过有了上面的这几个技术,要实现这种简单的木马功能还是很容易了哦 !
当然,这里再提一下的是,这样做不道德 ! 针对女朋友的属于合法行为 ! 哈哈哈 !
然后还有一个要提一下的是,现在这种注入方式,杀软或者安全卫士是能够捕捉到了,也就是过不了杀软或安全卫士这一关了哦 !
毕竟 03 年就有大牛弄这种东西了,这么些年了,人家做反病毒做安全的也不是吃饭的 !
不过如果将上面的技术和 Rootkit 做个结合,可能效果会很不一样了哦,尚待研究 ~