在PE文件中插入一个新节
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在PE文件中插入一个新节相关的知识,希望对你有一定的参考价值。
这篇文章写如何在exe文件中插入一个新节,并且让它还能继续运行。这个节里保存的是导入表信息,指向一个自己写的动态库。在PE头中修改导入表地址位自己新构建的导入表。
能够实现这些需要对PE文件结构有着熟悉的掌握,可参考《Windows PE权威指南》。当初我看这本书的时候觉得很枯燥,结构信息不太好记。但是经过这个项目和一个自己实现LoadLibrary函数的项目后对PE文件结构就有了较熟悉的掌握。
首先,通过内存映射将目标EXE文件映射到内存中,保存原始结构,然后将原来的PE头部写入新文件。
之后就是构建新的节表,在新节表的最后插入自己的节头。这里就要注意,如果写入节表项造成节表的文件偏移,那么就要对原先的节表进行逐一修正。
1 DWORD dwMiniPointer = 0; 2 DWORD dwSizeDelta = 0; 3 BOOL bNeedModify = FALSE; 4 for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++) 5 { 6 if (pUpdatedSection[i].Name) 7 { 8 if (0 == dwMiniPointer) 9 dwMiniPointer = (DWORD)(pUpdatedSection[i].PointerToRawData); 10 if ((pUpdatedSection[i].PointerToRawData) < dwMiniPointer) 11 dwMiniPointer = (DWORD)pUpdatedSection[i].PointerToRawData; 12 } 13 } 14 15 //写入文件的PE头部的总大小,可能会造成全部节表的文件偏移的抬高 16 //这种情况发生在第一个节的文件偏移 小于 新写入PE头部的总大小 的时候 17 //如果发生了,操作也很简单,保留一些空白区域以满足文件粒度,还有别忘了要更新节表的文件偏移 18 //值得注意的是,对文件的操作基本上不用到VirtualAddress,只有在算地址的时候才用到RVA 19 DWORD dwTmp = (pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + dwSectionSize); 20 21 //写入的PE头在预计范围内就不需要对节表的文件偏移进行修正 22 if (dwMiniPointer >= dwTmp) 23 dwSizeDelta = dwMiniPointer - dwTmp; 24 //写入的PE头超出了预计范围内就需要对节表的文件偏移进行修正 25 else 26 { 27 // 比如说,文件粒度20,写入的PE头部总大小25,这个时候超出了预计范围就要修正,但是由于粒度的原因,后面要留够一定字节,这里就是 20 - 25 % 20 = 15 ----> 25+15 = 40 28 // 如果正好是整数倍就不做任何处理。比如像 PE头部总大小为40,文件粒度20. 29 dwSizeDelta = dwTmp % (pNTHeader->OptionalHeader.FileAlignment); 30 if (dwSizeDelta != 0) 31 dwSizeDelta = pNTHeader->OptionalHeader.FileAlignment - dwSizeDelta; 32 else dwSizeDelta = 0; 33 34 //指示每个节表的文件偏移需要重新计算 35 bNeedModify = TRUE; 36 } 37 38 BYTE* pDelta = new BYTE[dwSizeDelta]; 39 memset(pDelta, 0, dwSizeDelta); 40 dwMiniPointer = dwTmp; 41 dwMiniPointer += dwSizeDelta; 42 43 if (bNeedModify) 44 { 45 for (int i = 0; i < (UINT)(pNTHeader->FileHeader.NumberOfSections + 1); i++) 46 { 47 if (0 != i) 48 pUpdatedSection[i].PointerToRawData = 49 pUpdatedSection[i - 1].PointerToRawData + 50 pUpdatedSection[i - 1].SizeOfRawData; 51 else 52 pUpdatedSection[i].PointerToRawData = dwMiniPointer; 53 } 54 }
写入节表和块的内容。注意着中间还要写入空白数据满足文件粒度。
然后就是对导入表的操作了。这里同样是先将原来的导入表项写到新的节中,然后在末尾添加自己构建的导入表项,这个新构建的就是指向自己的动态库。别忘了还要将数据目录项中导入表的地址修改为新的。
1 DWORD dwBytesWritten = 0; 2 DWORD dwBuffer = pNewSection->VirtualAddress; 3 4 5 // 修改IMAGE_DIRECTOR_ENTRY_IMPORT中VirtualAddress的地址, 6 // 使其指向新的导入表的位置 7 int nRet = 0; 8 lDistanceToMove = (long)&(pNTHeader->OptionalHeader.DataDirectory 9 [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress) - ulBaseAddress; 10 11 SetFilePointer(hTargetFile, lDistanceToMove, NULL, FILE_BEGIN); 12 13 printf("OrigEntryImport: %x\r\n", pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 14 nRet = WriteFile(hTargetFile, (PVOID)&dwBuffer, 4, &dwBytesWritten, NULL); 15 if (!nRet) 16 { 17 printf("WriteFile(ENTRY_IMPORT) failed. --err: %d\r\n", GetLastError()); 18 return FALSE; 19 } 20 printf("NewEntryImport: %x\r\n", dwBuffer); 21 22 // 修改导入表长度 23 nRet = WriteFile(hTargetFile, (PVOID)&dwIATDESCSize, 4, &dwBytesWritten, NULL); 24 if (!nRet) 25 { 26 printf("WriteFile(Entry_import.size) failed. --err: %d\n", GetLastError()); 27 return FALSE; 28 }
到这里我们的主要工作就基本完成了。然后就是对各种大小的重造,最后,对于很多系统文件,像什么calc.exe notepad.exe存在绑定输入表这些内容存在于PE文件头之后,第一个节之前的那段空白区,我们在写入新文件的时候并没有写入这部分的数据,是按0填充的因此,这里必须要将绑定输入表的RVA,Size置成0。不过一般而言自己用vs编译的exe都不带绑定输入表的。
还有一点要注意,这个程序编译成32位就去搞32位目标,64位就去搞64位目标,千万不能搞混。
项目地址:https://github.com/HollyDi/InsertSection
以上是关于在PE文件中插入一个新节的主要内容,如果未能解决你的问题,请参考以下文章