原创:星绽紫辉(rawdata) http://www.cppblog.com/rawdata 2009-1-20
转载请注明出处
关键字: PE 增加 区段 section 文件格式
现在我要给一PE文件增加区段(section),但是增加区段后我不希望影响PE文件的正常使用,那么应该怎么
做呢?我写这个教程的目的,希望能帮学习PE格式,当然也作为我以后参考的笔记。
简单地说:PE文件和普通文件没有什么区别,只是存在格式上的差异。另外,当你双击某个.exe文件
时,Windows Shell 程序将会尝试解析文件并运行它的PE代码。所以第一步,你必须对PE格式比较熟悉。现在
网上的PE教程我认为最好的就是罗云斌主页上的汇编教程了,我在这里长话短说,只是讨论和我们要解决的
问题相关的方面。
一般来讲,PE由以下几个部分依次排列下来:
1. Dos 头
2. Dos stub (通常你不必关心它的内容,重建PE只需完全拷贝即可)
3. NT 头
4. 节表
5. 文件对齐间隙
6. 第一节
7. 第二节
8. ...
9. 第 n 节 (文件结尾)
对于PE文件,如果没有文件对齐和内存对齐,那将是非常简单了。(但是,太简单了就不安全了,不是
吗?对于这种格式,当初的设计者还是动了不少脑筋的,呵呵~)。我们可以用非常简单的读文件函数,读取
各部分结构,然后把它重组还原PE。如果你是初次接触,建议你这样实践一下。
然后我们讨论一下如何增加区段,我们将尽量保证维持原来的PE结构的数据,然后在此基础上增加我们
的数据。现在我把这个过程步骤化:
一、读取Dos头
二、读取Dos stub
三、读取NT头,根据Dos头定位到此
四、读取NT头
五、读取节表
六、遍历读取所有节块
现在,我们把一个PE文件读到缓冲区了。然后我们进行修改:试验证明,只需要某些特征项即可,而不
必对所有参数进行修改,这样PE文件(如.exe)还是能正常运行。对于具体的节块,我们必须给某些关键的
参数赋值,否则将破坏PE结构,导致不能运行。
在开始修改前,有一些非常关键的东西我们必须知道:文件对齐和内存对齐。实际上,所有的section区
段都是文件对齐的(你把每一节当成一个块,具有起始文件位置和块大小),比如:第一节的文件偏移为
1024KB,节块大小为1000KB,那么第二节的文件偏移将是2048KB,而不是2024KB(文件偏移即是节块的开始
位置)。但这其实不是一成不变的。你可以修改它,只要满足文件对齐,但是带来的麻烦是,你必须同时也
修改它的定位目录----->节表里的PointerToRawData文件指针,否则将由于找不到对应的节块而产生错误。
我常常思考这样一个问题,到底要不要把PE装载到内存,然后在内存中增加区段,然后把PE内存dump成
新的PE文件达到增加区段的目的。实际上,这是完全可行的。但是,直接修改PE文件也是可以的,可以不把
PE装载到内存。因为我们仅仅是增加区段,不需要修改引入表之类的东西。在我增加区段成功后,对比发
现,节表里面的Virtual Address 实际上等于PointerToRawData,节表里面的Virtual Size 实际上等于SizeofRawData
(呵呵,我的网名就是rawdata)。于是,我想,这样的设计实际上是为了简化PE程序的设计的复杂性。内存对
齐转化为文件对齐,一旦设计好文件对齐,那么内存对齐就设计好了。但是,你千万不要认为两者一定总是相
等的,实际它有很大的灵活性,你可以随意设计,只要满足NT头里面的指定的内存对齐值参数。
还有一个关键的要注意的地方:必须重新修改NT头里面的pINH->OptionalHeader.SizeOfImage值。即整个PE
在内存的全部的映像的大小。我们可以这样给它赋予新的值:增加1个新的区段,就在原来的SizeOfImage值基
础上再加上该节块大小的文件对齐值,增加了几个区段,就累加几次,你应该明白了吧?
如果你感觉我的语言表达很糟糕,请你谅解。不过,请你放心,我后面会给出源代码。其实主要做的工
作就是在文件尾加上一些数据,然后修改文件头的一些参数,仅此而已,没有什么神奇的地方,还等什么?
赶快去写一个PE加区工具吧~~~
代码清单如下: (平台: console)
把其中的PE文件名该为你想加区的PE文件名就可以了。
后记:
原来的程序是只使用于raw和rva相等的情况,正确的修改如下:
把第295行修改为:
//Rva与raw不匹配的情况。。。
ppISH[i]->VirtualAddress = ppISH[i-1]->VirtualAddress +\
((ppISH[i-1]->SizeOfRawData/pINH->OptionalHeader.SectionAlignment)+1)*\
(pINH->OptionalHeader.SectionAlignment);
1
/**//**************************************************************************
2
* 文件名: Main.cpp
3
* 日 期: 2009年1月13日
4
* 作 者: rawdata
5
* 描 述:
6
***************************************************************************/
7
8
#include <windows.h>
9
#include <windowsx.h>
10
#include <winnt.h>
11
#include <iostream>
12
using namespace std;
13
14
#pragma warning(disable:4312)
15
#pragma warning(disable:4311)
16
#pragma warning(disable:4244)
17
18
int main(int argc,char**argv)
19

{
20
//------------------- 主要缓冲区定义 ----------------------
21
22
BYTE* pDos = NULL; //Dos头和Stub区
23
DWORD dwSizeDos = 0; //Dos头 大小
24
DWORD dwSizeStub = 0; //Dos Stub区大小
25
26
BYTE* pNT = NULL; //NT头
27
DWORD dwSizeNT = 0; //该区大小
28
29
BYTE* pSecH = NULL; //节表
30
DWORD dwSizeSecH = 0; //该区大小
31
WORD dwSizeAdd = 3; //增加的节的个数
32
33
BYTE* pDelta = NULL; //文件对齐填充数据
34
DWORD dwSizeDelta = 0; //该区大小
35
36
BYTE* pPrevSec = NULL; //节块
37
DWORD dwSizePrevSec = 0;//该区大小
38
39
BYTE* pAddSec = NULL; //新增的节块
40
DWORD dwSizeAddSec = 0; //该区大小
41
42
//---------------------- 其他临时定义 --------------------
43
44
const char* pszFilePath = NULL; //文件路径
45
HANDLE hFile = NULL; //文件句柄
46
47
PIMAGE_DOS_HEADER pIDH = NULL; //Dos头
48
PIMAGE_NT_HEADERS pINH = NULL; //NT 头
49
PIMAGE_SECTION_HEADER* ppISH = NULL; //节表
50
51
DWORD dwRealRead = 0; //实际每次文件读取的字节数
52
DWORD dwMiniPointer = 0; //第一个section的位置
53
BOOL bNeedModify = FALSE; //是否有必要需要修改节块的文件指针
54
55
DWORD dwRawDataSize = 10*1024; //增加区段的数据大小
56
57
DWORD dwTmp=0,dwTmp2=0,dwTmp3 = 0;
58
59
//Dos 头
60
//===============================================================================
61
//打开文件、读取Dos头
62
63
pszFilePath = argv[0];
64
pszFilePath = "BeingInjued.exe";//Test
65
66
cout<<"PE FileName:"<<pszFilePath<<endl;
67
68
if(0 == lstrcmp(pszFilePath,""))
69
{
70
cout<<"文件路径不能为空!"<<endl;
71
return -1;
72
}
73
74
hFile = CreateFile(pszFilePath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,
75
FILE_ATTRIBUTE_NORMAL,NULL);
76
77
if(INVALID_HANDLE_VALUE == hFile)
78
{
79
cout<<"打开文件失败!"<<endl;
80
hFile = NULL;
81
goto Error;
82
}
83
84
dwSizeDos = sizeof(IMAGE_DOS_HEADER);
85
pDos = new BYTE[dwSizeDos];
86
memset(pDos,0,dwSizeDos);
87
88
if(!ReadFile(hFile,pDos,dwSizeDos,&dwRealRead,NULL))
89
{
90
cout<<"读取Dos头失败!"<<endl;
91
goto Error;
92
}
93
cout<<"Dos Header: "<<dwRealRead