首页 > 代码库 > 在PE文件中插入一个新节

在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 =
http://www.mamicode.com/49                 pUpdatedSection[i - 1].PointerToRawData +
50                 pUpdatedSection[i - 1].SizeOfRawData;
51             else
52                 pUpdatedSection[i].PointerToRawData =http://www.mamicode.com/ 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文件中插入一个新节