引子
前些日子由于项目要求,在网上到处找资料,于无意中发现了 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 进程给弄崩溃了就不好了;
4: //Descripion: 判定一个进程是否为 Explorer 进程 //
6: bool ProcessIsExplorer(DWORD dwProcessId)
8: HANDLE hProcess;
9:
10: hProcess = NULL;
12: hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_informatION,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,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,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 36: _tsplitpath(pathArray,exeNameArray,NULL);
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 了;
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])
15: if(ProcessIsExplorer(dwProcess[i]))
16: {
17: dwExplorerId = dwProcess[i];
18: break;
19: }
20: }
21: }
23: hProcess = NULL;
24: hProcess = OpenProcess(PROCESS_ALL_ACCESS,dwExplorerId);
25: if(NULL == hProcess)
26: {
27: OutputErrorMessage("main - OpenProcess Failed,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 28: }
4. 在宿主进程中分配好存储空间,这个存储空间是用来存放我们将要创建的远程线程的线程处理例程的,
这里需要注意的是:我们分配的内存必须标记必须带有 EXECUTE,因为分配的这块内存是用来存放线程处理例程的,
而线程处理例程必须得执行,所以必须得带有 EXECUTE 标记,而至于 WRITE 标记的话,很明显是需要的,
因为我们在后面的代码中还必须调用 WriteProcessMemory 来将线程处理例程写入到这块内存中,自然其必须可写;
2: PVOID pRemoteThread = VirtualAllocEx(hProcess,THREAD_SIZE,MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE);
3: if(NULL == pRemoteThread)
4: {
5: OutputErrorMessage("main - VirtualAllocEx Failed,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 6:
7: //关闭进程句柄
8: CloseHandle(hProcess);
9: }
5. 将远程线程处理例程写入到 4 中在宿主进程中所分配的内存中,这个可以直接调用 WriteProcessMemory 来实现;
6. 在宿主进程中分配好存储空间,这个存储空间是用来存放我们将要传递给远程线程线程处理例程的参数,
从下面的结构体中可以看出,其由三个参数组成,第一个参数代表要在对话框中显示的内容,
第二个参数代表要在对话框中显示的标题,第三个参数则是 MessageBox 这个 API 的地址,
因为在 Explorer.exe 中 MessageBox 的地址会发生重定向,所以需要将其地址通过参数传递给线程处理例程;
4: //Descripion: 获得 MessageBox 这个 API 的地址以及填充的参数 //
6: void GetMessageBoxParameter(PRemotePara pRemotePara)
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: }
2: ZeroMemory(&remotePara,sizeof(RemotePara));
3: GetMessageBoxParameter(&remotePara);
4:
5: //在 hProcess 所代表的进程中分配虚拟内存来容纳线程的参数部分
6: PRemotePara pRemotePara = (PRemotePara)VirtualAllocEx(hProcess,sizeof(RemotePara),MEM_COMMIT,PAGE_READWRITE);
7: if(NULL == pRemotePara)
8: {
9: OutputErrorMessage("main - VirtualAllocEx Failed,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 10:
11: //释放 VirtualAllocEx 分配的内存
12: VirtualFreeEx(hProcess,MEM_RELEASE);
13: CloseHandle(hProcess);
14: }
7. 将参数写入到 6 中在宿主进程中所分配的内存中,同样是调用 WriteProcessMemory 来完成;
5: //释放 VirtualAllocEx 分配的内存
6: VirtualFreeEx(hProcess,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 9: CloseHandle(hProcess);
10: }
8. 调用 CreateRemoteThread 在 Explorer.exe(宿主进程)中创建远程线程;
注意,当远程线程没有执行完时,不能够通过 VirtualFreeEx 来将远程进程中的内存释放掉,
你想啊,我他妈的线程都还在 Explorer.exe 里面执行,你他妈的在外面把我占的内存给释放掉了,我还执行个屁啊 !
所以这里调用了 WaitForSingleObject 来等待这个远程线程执行完毕,
其执行完毕后再释放在 Explorer.exe 中所分配的存储空间 !
2: DWORD dwThreadId;
4: hThread = NULL;
5: dwThreadId = 0;
7: //将已经写入到 hProcess 进程中的线程以及线程的参数作为 CreateRemoteThread 的参数,从而创建远程线程
8: hThread = CreateRemoteThread(hProcess,(DWORD (WINAPI *)(LPVOID))pRemoteThread,&dwThreadId);
9: if(NULL == hThread)
10: {
11: OutputErrorMessage("main - CreateRemoteThread Failed,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 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,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 25: VirtualFreeEx(hProcess,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 26:
27: CloseHandle(hProcess);
9. 编写好远程线程的线程处理例程即可;
4: //Descripion: 远程线程处理例程 //
6: DWORD WINAPI RemoteThreadProc(PRemotePara pRemotePara)
8: //这个 MessageBox 的地址必须由外部参数传入,因为在其他进程中需要重定向
9: typedef int (WINAPI *MESSAGEBoxA)(HWND,LPCSTR,UINT);
11: MESSAGEBoxA MessageBoxA;
12: MessageBoxA = (MESSAGEBoxA)pRemotePara->m_dwMessageBoxAddr;
13:
14: //调用 MessageBoxA 来打印消息
15: MessageBoxA(NULL,pRemotePara->m_msgContent,pRemotePara->m_msgTitle,MB_OK);
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 进程),
再通过 OpenProcess 来打开这个 Explorer.exe 进程就 OK 了;
4. 在宿主进程中分配好存储空间,这个存储空间是用来存放我们将要创建的远程线程的线程处理例程的,自然其必须可写;
5. 将远程线程处理例程写入到 4 中在宿主进程中所分配的内存中,这个可以直接调用 WriteProcessMemory 来实现;
6. 在宿主进程中分配好存储空间,这个存储空间是用来存放我们将要传递给远程线程线程处理例程的参数,
从下面的结构体中可以看出,其由六个参数组成,
第一个参数代表 ZacharyDll.dll 中导出的 PrintMessageBox 的名称,
第二个参数代表 ZacharyDll.dll 中导出的 PrintDbgStr 的名称题,
第三个参数则是 ZacharyDll.dll 所在的路径,
第四个参数则是代表 LoadLibrary 的地址,
第五个参数代表 FreeLibrary 的地址,
第六个参数代表 GetProcAddress 的地址;
2: if(NULL == hKernel32)
4: OutputErrorMessage("main - GetModuleHandle Failed,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 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");
20: string strMsgBox = "PrintMessageBox";
21: string strBbgStr = "PrintDebugString";
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);
31: ZeroMemory(tmpArray,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 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,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 47: if(NULL == pRemotePara)
48: {
49: OutputErrorMessage("main - VirtualAllocEx Failed,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 50:
51: //释放 VirtualAllocEx 分配的内存
52: VirtualFreeEx(hProcess,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 53: CloseHandle(hProcess);
54: }
7. 将参数写入到 6 中在宿主进程中所分配的内存中,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 1: //将远程线程所携带的参数写入到宿主进程中所分配的虚拟内存
2: if(NULL == WriteProcessMemory(hProcess,0))
8: VirtualFreeEx(hProcess,monospace; direction:ltr; border-top-style:none; color:black; font-size:8pt; border-left-style:none; overflow:visible; padding-top:0px"> 10: }