首页
社区
课程
招聘
[旧帖] [原创][原创]Windows PE文件格式解析 0.00雪花
发表于: 2012-9-23 10:31 2009

[旧帖] [原创][原创]Windows PE文件格式解析 0.00雪花

2012-9-23 10:31
2009
在接下来的时间里我们将会了解我们身边最常使用的.EXE的格式。只要是存储在计算机内的数据都是要按一定的格式存储。即使是可执行程序也一样。当你运行程序的时候并不是程序把自己运行了,而是系统内有一个叫PE加载器的程序进行读取该应用程序的文件,获得必要的数据然后才分配内存,线程等。这样程序才可以运行。
   说到这里大家可能又要以为什么大神等等,其实我只是一个普通的人,怀着一个理想去学习,在普通点我只是一个职高的综合高中的学生,成绩并不咋样。而且还总是被这种教育所遏制我喜欢的计算机。现实就是现实,怎么说呢。
现在让我们认识一个要认识的结构体:
typedef struct IMAGE_DOS_HEADER
{
 WORD magic;
 WORD cblp;
 WORD cp;
 WORD crlc;
 WORD cparhdr;
 WORD minalloc;
 WORD maxalloc;
 WORD ss;
 WORD sp;
 WORD csum;
 WORD ip;
 WORD cs;
 WORD lfarlc;
 WORD ovno;
 WORD res[4];
 WORD oemid;
 WORD oeminfo;
 WORD res2[10];
 DWORD lfanew;
} IMAGE_DOS_HEADER;

其实如果按实际来说,这个大个结构体其实只有一个参数是对我们有用的,因为我相信现在没有人会继续十六位的程序设计了吧。那样就只有 lfanew 这个参数我们会使用到,大家可能会奇怪为什么还要使用这个呢,其实也是 windows 为了兼容过去,毕竟你的机器上还是有DOS子系统,所以这个结构体自然就没有除去。
这个结构体就在exe文件的第一个,你知道使用文本打开你的任何一个exe会发现第一个都是“MZ”这个英文(是个人名,只是为了纪念这个人),那么你就找到结构体的第一个参数的值 magic ,修改这个值只会影响在DOS下的运行,并不会影响到我们正常的运行。(但是不能增加数据,或者删减数据,只可以修改除了最后和最前的其他数据项)
为什么最后一个参数很重要呢,你一看它的类型是 DWORD 为4个字节(2个字)里面存放的是从映射基地址头到另一个重要的结构体头的偏移。
  偏移:说的简单点就是距离一个位置有多远,那么这个位置的地址加上那个偏移就是这个偏移所指的地址。比如在内存中映射基地址的开始位置为 00400000h 然后 lfanew 的偏移值为:F8 。那么这个结构体头的地址为  0x00400000 + F8 = 0x004000F8 h ;
  映射基地址:关于这个地址要从执行程序加载进内存(准确说是虚拟内存),并不是你所想像的放在从 0x00000000 地址开始然后顺序往下,而是根据后面一个结构体中的数据来决定加载到哪个地址,大多数程序都是从 0x00400000 地址开始。当然这个是可以改变的,但是关于 dll 的映射到后面会讲述。
  从 lfanew 参数到另一个结构体头之间的数据只是在 DOS 下才有用。如果你想探究,可以通过 ollydbg 查看。
  不能光说不写,我们一步一步开始.从定位IMAGE_DOS_HEADER 开始,现在看以下代码:

HANDLE hFile = NULL;
HANDLE MapFile = NULL;
LPVOID lpMapOfFile = NULL;
DWORD dwLowFileSize = 0;
hFile = CreateFile(
 FileName,   //程序所在位置
 GENERIC_READ | GENERIC_WRITE, //设置为可写可读
 NULL,
 NULL,
 OPEN_EXISTING, //打开存在的文件
 NULL,
 NULL
);
if( hFile == INVALID_HANDLE_VALUE ) //如果文件句柄为错误则返回
  return 0;
dwLowFileSize = GetFileSize(hFile,NULL); //获得文件的尺寸(不能超过4GB,一般都不会超过)
MapFile = CreateFileMapping(
 hFile,  //打开的文件句柄
 NULL,
 PAGE_READWRITE, //设置为可读可写
 NULL,
 dwLowFileSize, //为文件尺寸
 NULL
);
If( MapFile == NULL ) //如果映射文件句柄为空返回
 return 0;
lpMapOfFile = MapViewOfFile(
 MapFile, //创建的映射文件句柄
 FILE_MAP_ALL_ACCESS, //拥有所有访问权限
 NULL,
 NULL,
 dwLowFileSize //文件尺寸
);
TCHAR *lpDos;
DWORD dwlfanew;
lpDos = (THCAR*)lpMapOfFile;
Dwlfanew = reinterpret_cast<IMAGE_DOS_HEADER*>(lpDos)->e_lfanew; //获得PE头的偏移

接下来我们来介绍这个 lfanew 所指向的位置。
其实  lfanew 指向的是一个 DWORD 类型的值,为ASCII的“PE\0\0”是PE文件头的标志,你或许会问上面不是写是一个结构体吗,等后面你就会知道了。这里的 PE 文件头的标志,不是绝对了,如果是其他的文件可能是其他的标示,所以以后编程了,一定要检测下是不是 PE\0\0 ,如果不是就按对应的文件格式读取。
在 "PE\0\0" 后面就是一个重要的结构体了:
 typedef struct IMAGE_FILE_HEADER{
 WORD Machine;  //运行平台
 WORD NumberOfSections;  //PE中节的数量
 DWORD TimeDateStamp;  //文件创建日期和时间
 DWORD PointerToSymbols;  //指向符号表(调试用)
 DWORD NumberOfSymbols;  //符号表中的符号数量(调试用)
 WORD SizeOfOptionalHeader;  //扩展头结构的长度
 WORD Characteristics;  //文件属性
 } IMAGE_FILE_HEADER

来详细的介绍各个成员的具体意思:

 Machine :
取值  常量符号  含义
0x0  IMAGE_FILE_MACHINE_UNKNOWN  适应于任何处理器
0x8664  IMAGE_FILE_MACHINE_AMD64  X64处理器
0x1c0  IMAGE_FILE_MACHINE_ARM  ARM小尾处理器
0x1c4  IMAGE_FILE_MACHINE_ARMV7  ARMV7(或更高级)处理器的tHUMB模式
0x14c  IMAGE_FILE_MACHINE_I386  Intel 389 处理或后续兼容处理器(基本都是这个)
0x200  IMAGE_FILE_MACHINE_IA64  Intel Itanium 处理器系列
当然如果你修改这个值那么就会报 “不是有效的WIN32应用程序”的错误。

NumberOfSections :
 文件中存在的节的总数。可以存在0个节,但数值不能小于1,也不能超过96个。TimeDateStamp :
编译器创建此文件时的时间截。存放的是自 1970 年 1 月 1 日 00:00时开始到创建时间为止的总秒数。当然这个数值可以随意更改,对程序没有任何影响。

PointerToSymbolTable :
COFF 符号表的文件偏移。如果不存在COFF符号表,则为0

NumberOfSymbols :
符号表中元素的数目。

SizeOfOptionalHeader :
IMAGE_OPTIONAL_HEADER32 的大小,如果为 32 位则大小为 00e0 ,为 64 位则大小为 00f0 。

Characteristics :
文件属性标志字段:
数据位  常量符号  为1的含义
0  IMAGE_FILE_RELOCS_STRIPPED  文件中不存在重定位信息
1  IMAGE_FILE_EXECUTABLE_IMAGE  文件是可执行的
2  IMAGE_FILE_LINE_NUMS_STRIPPED  不存在行信息
3  IMAGE_FILE_LOCAL_SYMS_STRIPPED  不存在符号信息
4  IMAGE_FILE_AGGRESIVE_WS_TRIM  调整工作集
5  IMAGE_FILE_LARGE_ADDRESS_AWARE  应用程序可处理大于2GB的地址
6    保留
7  IMAGE_FILE_BYTES_REVERSED_LO  小尾方式
8  IMAGE_FILE_32BIT_MACHINE  只在32位平台上运行
9  IMAGE_FILE_DEBUG_STRIPPED  不包含调试信息
10  IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP  不能从可移动盘运行
11  IMAGE_FILE_NET_RUN_FROM_SWAP  不能从网络运行
12  IMAGE_FILE_SYSTEM  系统文件,不能直接运行
13  IMAGE_FILE_DLL  这是一个dll文件
14  IMAGE_FILE_UP_SYSTEM_ONLY  文件不能在多处理器计算机上运行
15  IMAGE_FILE_BYTES_REVERSED_HI  大尾方式

现在教你如何过滤这些信息:

bool GetFileCharacteristics( DWORD dwCharacteristics , std::string &strCharacteristics )
{
  if( dwCharacteristics == 0 )
  return 0;
  if( dwCharacteristics & IMAGE_FILE_RELOCS_STRIPPED )
    strCharacteristics+="无重定位;";
  if( dwCharacteristics & IMAGE_FILE_EXECUTABLE_IMAGE )
    strCharacteristics+="可执行;";
  if( dwCharacteristics & IMAGE_FILE_LINE_NUMS_STRIPPED )
    strCharacteristics+="不存在行信息;";
  if( dwCharacteristics & IMAGE_FILE_LOCAL_SYMS_STRIPPED )
    strCharacteristics+="不存在符号信息;";
  if( dwCharacteristics & IMAGE_FILE_AGGRESIVE_WS_TRIM )
    strCharacteristics+="调整工作集;";
  if( dwCharacteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE )
    strCharacteristics+="可处理大于2GB内存;";
  if( dwCharacteristics & IMAGE_FILE_BYTES_REVERSED_LO )
    strCharacteristics+="小尾方式;";
  if( dwCharacteristics & IMAGE_FILE_32BIT_MACHINE )
    strCharacteristics+="32位;";
  if( dwCharacteristics & IMAGE_FILE_DEBUG_STRIPPED )
    strCharacteristics+="不含调试信息;";
  if( dwCharacteristics & IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP )
    strCharacteristics+="不可在移动盘运行;";
  if( dwCharacteristics & IMAGE_FILE_NET_RUN_FROM_SWAP )
    strCharacteristics+="不可从网络运行;";
  if( dwCharacteristics & IMAGE_FILE_SYSTEM )
    strCharacteristics+="系统文件;";
  if( dwCharacteristics & IMAGE_FILE_DLL )
  {
    strCharacteristics+="DLL文件;";
    m_IsDll = 1;  // 是本人用来特意识别dll文件设置的
  }
  if( dwCharacteristics & IMAGE_FILE_UP_SYSTEM_ONLY )
    strCharacteristics+="不能在多处理器上运行;";
  if( dwCharacteristics & IMAGE_FILE_BYTES_REVERSED_HI )
    strCharacteristics+="大尾方式;";
  return true;
}

  这样一个简单的过滤函数就完成了。可以获得这个文件的属性,当然我建议你最好特别为dll文件的设置一个位,这样方便以后你要特别对待特定文件进行特别的处理,为将来留下一定的空间,可以更好的扩展。

上面的只是一个功能函数,但是我们还是要接着主体的函数进行,上面我已经定位到了PE头,那么我现在就要定位到这个结构体。但在这之前我还要做一件事,那就是辨别那个定位后的头是否是 PE 头,如果不是那么本书的结构将不一定适用,可能会出现让人难以理解的错误等等。所以建议必须检测这个头,避免恶意。

 if( *(DWORD*)lpDos != IMAGE_NT_SIGNATURE )
  return 0;
 lpDos += sizeof(DWORD);
 IMAGE_FILE_HEADER  *lpIFH;
 lpIFH = (IMAGE_FILE_HEADER*)lpDos;  //到这里,lpIFH 就指向你需要的结构体了,可以直接使用指针来使用里面的成员。
这样关于IMAGE_FILE_HEADER 结构体的介绍就到此结束了。

现在我们要继续接下来介绍紧跟在这个结构体之后的另一个更大的结构体,虽然微软文档说它是可选的,但似乎看来根本就是必须的,而且包含的信息比介绍过的还要大的多,数据更丰富的多,可以提供给我们更多我们所想要的信息。
当然我们要小心使用这个地方,如果不小心你的应用程序就玩完了。这个责任我可不负责的哦。所以建议你在映射的时候将 GENERIC_WRITE 还有 PAGE_READWRITE , FILE_MAP_ALL_ACCESS 这些改成只能读,这样就避免你乱修改了( 后面的编程可能需要有写入的权限,如果你要设置的尽量为以后作好一些必要的准备 )

现在就开始看最重要的结构体:

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;  //魔术字
    BYTE    MajorLinkerVersion; //链接器版本号
    BYTE    MinorLinkerVersion; 
    DWORD   SizeOfCode;         //代码节的尺寸
    DWORD   SizeOfInitializedData; //所有含已初始化代码的节的总大小
    DWORD   SizeOfUninitializedData; //所有含未初始化代码的节的总大小
    DWORD   AddressOfEntryPoint; //程序执行入口相对虚拟地址
    DWORD   BaseOfCode; //代码节的相对虚拟地址
    DWORD   BaseOfData; //数据节的相对虚拟地址

    //
    // NT additional fields.
    //

    DWORD   ImageBase; //程序的建议装载地址
    DWORD   SectionAlignment; //内存堆砌粒度
    DWORD   FileAlignment;  //文件对齐粒度
    WORD    MajorOperatingSystemVersion; //操作系统版本号
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion; //该PE的版本号
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion; //所需子系统的版本号
    WORD    MinorSubsystemVersion;
  DWORD   Win32VersionValue;  //未用
    DWORD   SizeOfImage; //内存中整个PE映像尺寸
    DWORD   SizeOfHeaders; //所有头加节表的大小
    DWORD   CheckSum; //校验和
    WORD    Subsystem; //文件的子系统
    WORD    DllCharacteristics; //dll文件特性
    DWORD   SizeOfStackReserve; //初始化时的栈大小
    DWORD   SizeOfStackCommit;  //初始化时实际提交的栈大小
    DWORD   SizeOfHeapReserve;  //初始化时的堆大小
    DWORD   SizeOfHeapCommit;   //初始化时实际提交的堆大小
    DWORD   LoaderFlags;        //加载标志
    DWORD   NumberOfRvaAndSizes; //数据目录项的数量
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录项
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

Magic : 说明文件类型
值  意思
IMAGE_NT_OPTIONAL_HDR_MAGIC  这个文件是可执行的。
IMAGE_NT_OPTIONAL_HDR32,MAGIC  这个文件是可执行的( 32 位)。
IMAGE_NT_OPTIONAL_HDR64_MAGIC  这个文件是可执行的(64 位)。
IMAGE_ROM_OPTIONAL_HDR_MAGIC  这个文件是一个 ROM 映像。

MajorLinkerVersion :
  链接器的版本号
MinorLinkerVersion:
  链接器的版本号

SizeOfCode:
  代码节的尺寸,单位字节。或者所有这样的节,比如多个代码节。

SizeOfInitializedData:
  已经初始化的数据尺寸

SizeOfUninitializedData:
  未初始化的数据尺寸

AddressOfEntryPoint:
  在 Windows 中,可执行程序运行在虚拟地址空间中,由于4GB空间对于程序是唯一的,所以这里的虚拟空间可以简单的理解为真实的地址。该字段的值到底有多少个字节。
  如果在一个可执行文件中附加了一段自己的代码,并且想让折断代码首先被执行,一般都要修改这里的值使之指向自己的代码。入口点对于dll来说是可选的,如果不存在入口点,这个字段必须设置为 0。

BaseOfCode:
  代码节的相对虚拟地址

BaseOfData:
  数据节的相对虚拟地址

ImageBase:
  程序优先装入的地址。

SectionAlignment:
  内存对齐粒度

FileAlignment:
  文件对齐粒度

MajorOperatingSystemVersion:
  操作系统版本号

MinorOperatingSystemVersion:
   操作系统版本号

MajorImageVersion:
  PE文件映像版本号

MinorImageVersion:
  PE文件映像版本号

MajorSubsystemVersion:
  运行所需的子系统版本号

MinorSubsystemVersion:
  运行所需的子系统版本号

Win32VersionValue:
  未被使用,

SizeOfImage:
  PE映射文件的尺寸,但是必须是内存对齐整倍数。

SizeOfHeaders:
  所有头和节的尺寸

CheckSum:
  校验和,在内核模式中的程序和系统dll此值必须存在,并且必须要正确。一般的PE程序一般都是为0,但是也可以存在,而且也必须要正确。当然如果要自己计算可以使用 IMAGEHLP.DLL 中的 CheckSumMappedFile 计算文件头的校验和, MapFileAndCheckSum 计算整个PE文件校验和。

Subsystem:
  制定使用界面的子系统,在链接的时候使用 -subsystem:xxx 选项来制定这个值。
取值  常量符号  含义
0  IMAGE_SUBSYSTEM_UNKNOWN  未知的子系统
1  IMAGE_SUBSYSTEM_NATIVE  设备驱动程序和Native Windows 进程
2  IMAGE_SUBSYSTEM_WINDOWS_GUI  Windows 图形用户界面
3  IMAGE_SUBSYSTEM_WINDOWS_CUI  Windows 字符模式
7  IMAGE_SUBSYSTEM_POSIX_CUI  POSIX字符模式
9  IMAGE_SUBSYSTEM_WINDOWS_CE_GUI  Windows CE 图形界面
10  IMAGE_SUBSYSTEM_EFI_APPLICATION  可扩展固件接口应用程序
11  IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER  带引导服务的EFI驱动程序
12  IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER  带运行时服务的EFI驱动程序
13  IMAGE_SUBSYSTEM_EFI_ROM  EFI ROM映像
14  IMAGE_SUBSYSTEM_XBOX  XBOX

Characteristics:
  Dll文件属性,当然这个属性并不是像它的名字那样只是用来描述dll文件的,也是描述exe文件的关键之一。

取值  常量符号  含义
6  IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE  DLL可以在加载时候被重新定位
7  IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY  强制代码实施完整性验证
8  IMAGE_DLLCHARACTERISTICS_NX_COMPAT  该映像兼容DEP
9  IMAGE_DLLCHARACTERISTICS_NO_ISOLATION  可以隔离,但并不隔离此映像
10  IMAGE_DLLCHARACTERISTICS_NO_SEH  映像不使用SEH
11  IMAGE_DLLCHARACTERISTICS_NO_BIND  不绑定映像
13  IMAGE_DLLCHARACTERISTICS_WDM_DRIVER  该映像为一个WDM 驱动
15  IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE  可用于终端服务器
接下来既然是介绍如何写识别这些功能的函数:
bool GetDllCharacteristics( DWORD dwType , std::string &strType )
{
    if( dwType & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE )
    strType+="Dll可重定位;";
  if( dwType & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY )
    strType+="完整性验证;";
  if( dwType & IMAGE_DLLCHARACTERISTICS_NX_COMPAT )
    strType+="兼容DEP;";
  if( dwType & IMAGE_DLLCHARACTERISTICS_NO_ISOLATION )
    strType+="可隔离,不隔离此映像;";
  if( dwType & IMAGE_DLLCHARACTERISTICS_NO_SEH )
    strType+="不使用SEH;";
  if( dwType & IMAGE_DLLCHARACTERISTICS_NO_BIND )
    strType+="不绑定对象;";
  if( dwType & IMAGE_DLLCHARACTERISTICS_WDM_DRIVER )
    strType+="WDM;";
  if( dwType & IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE )
    strType+="终端设备";
     return 1;
}

SizeOfStackReserve:
  初始化时保留的栈大小。默认是1MB

SizeOfStackCommit:
  初始化时实际提交的栈大小。默认是 1KB
              
SizeOfHeapReserve:
  初始化时保留的堆大小。

SizeOfHeapCommit:
  初始化时实际提交的堆大小。

LoaderFlags:
  加载标志

NumberOfRvaAndSizes:
  数据目录的数目,一般为16。

DataDirectory:
   为16个IMAGE_DATA_DIRECTORY 结构体组成,为数据目录项
序号  名称
0  导出表
1  导入表
2  资源表
3  异常表
4  属性证书数据
5  基地重定位
6  调试表
7  预留
8  全局指针数据描述
9  县城本地存储
10  加载配置信息
11  绑定导入表
12  IAT导入地址表
13  延迟加载导入表
14  CLR表
15  系统预留

这十六个结构体的结构都是一直的,而且都是顺序的排列的中间没有任何分割等等。而且这个结构很简单,但是对于后面我们定位这些数据目录项很重要,而且我们需要的很多数据都是来自这个数据目录项里的,如果你没有搞懂怎么使用这个结构体那么后面的章节你将会感觉很苦恼。
现在我们先来看下这个结构体的结构:
 
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress; // 这个数据目录项的RVA(相对虚拟地址)
    DWORD   Size;  //这个数据目录项的尺寸
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

现在你看到了这个结构体是不是很简单,根本不存在不懂的这个可能性,但是在实际的使用过程中,我们都是这样使用的,假设之前的 lpDos 是指向 IMAGE_FILE_HEADER 头的.
那么在这里我们这么写:
IMAGE_OPTIONAL_HEADER32* lpIOH = NULL;  
lpDos += sizeof(IMAGE_FILE_HEADER);
lpIOH = (IMAGE_OPTIONAL_HEADER32*)lpDos;
lpIOH->DataDirectiory[x].VirtualAddress;  //这里的 x 表示要访问第几个数据目录项范围为0~15 不能超过也不能低于.

这个结构体有很多的参数,所以笔者为什么说它比 IMAGE_FILE_HEADER 还要重要,它所包含的信息要远远超过前者,所以读者必须要掌握这个结构体里参数,后面有很多都需要使用这里的,所以编程的时候一定要单独保存一个指针或者变量存储这些数据,否则等你要使用的时候还要再一次寻找比较麻烦,而且也消耗资源。

跟在  IMAGE_OPTIONAL_HEADER32 后面的为节表,这个结构体决定哪部分是存放的代码,哪部分存放的是数据,当然不是代码节只有一个,可以有多个的.比如加壳的软件,一般会多一个节.而且会修改 IMAGE_OPTIONAL_HEADER32 的 AddressOfEntryPoint 这个参数的值,从而指向自己的代码,如果读者是新手那么你应该会很高兴,因为当你学习完这所有的知识之后,然后会使用 shellcode 那么你就可以自己在任何符合PE的文件中增加自己的程序,这样就可以伪装自己.当然 360等杀毒软件还是可以检测到的,所以我在这里还要建议你再学习 汇编 (笔者不是为了写汇编程序,而是读懂反汇编,对于shellcode 也有一定的帮助),另外就是“看雪”的《加密与解密》第三版,笔者十分喜欢这本书,它的价格很大众,而且内容丰富.我相信市面上好一点的都要70~100远以上吧.这本只要 59元.

我们言归正传,开始介绍节表.直接上结构体,牛B的人一般看到结构体和解释就知道怎么编写了哈,如果读者想知道更详细可以阅读《Windows PE权威指南》(我不是卖书也不是他们的人,只是个人买到这些好书,想让大家一起阅读,但是价格有点高了 89元 —— 咋什么书都是9是末尾)。

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME]; //节名称
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;                           //节在没有对齐前的尺寸
    DWORD   VirtualAddress;           //相对虚拟地址
    DWORD   SizeOfRawData;             //文件对齐下的尺寸
    DWORD   PointerToRawData;          //文件偏移地址
    DWORD   PointerToRelocations;       //指向重定位
    DWORD   PointerToLinenumbers;       //指向符号表
    WORD    NumberOfRelocations;        //重定位表数量
    WORD    NumberOfLinenumbers;         //符号表数量
    DWORD   Characteristics;              //节属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

如果读者兴趣很广泛可以看 WinNt.h 里面包含了所有PE结构体和属性(非常多的东东).

Name :
 读者必须要注意这个参数,虽然它是以'\0'结尾的字符串,但是文档后面也说明了如果字符串已经达到8个字节,那么那个'\0'就不会存在,所以在写程序时一定要注意不要使用 strcpy 等没有控制数量的字符串复制函数,否则可能会出现严重的错误. strncpy .

Misc :
字节没有对齐前的尺寸,一般这个参数不怎么用到,而且文档说明这个参数的值并不是正确的,所以就不再多余的解释了.

VirtualAddress : 
相对虚拟地址,在后面的 相对虚拟地址 到 文件偏移地址 的转换中要经常使用到各个节的这个值。

SizeOfRawData :
文件对齐下的节尺寸,可以让你知道这个节在文件中是从哪到哪的.

PointerToRawData :
文件偏移地址,这个参数是和上面的参数匹配的。

PointerToRelocations :
指向重定位

PointerToLinenumbers :
指向符号表

NumberOfRelocations :
重定位表数量

NumberOfLinenumbers :
符号表数量

Characteristics :
节属性
数据位  常量符号  位为1时的含义
5  IMAGE_SCN_CNT_CODE  节中包含代码
6  IMAGE_SCN_CNT_INITIALIZED_DATA  节中包含已初始化数据
7  IMAGE_SCN_CNT_UNINITIALIZED_DATA  节中包含未初始化数据
8  IMAGE_SCN_LNK_OTHER  保留供将来使用
25  IMAGE_SCN_MEM_DISCARDABLE  节中的数据在进程开始以后将被丢弃
26  IMAGE_SCN_MEM_NOT_CACHED  节中的数据不会经过缓存
27  IMAGE_SCN_MEM_NOT_PAGED  节中的数据不会被交换到硬盘
28  IMAGE_SCN_MEM_SHARED  表示节中的数据将被不同的进程所共享
29  IMAGE_SCN_MEM_EXECUTE  映射到内存后的页面包含可执行属性
30  IMAGE_SCN_MEM_READ  映射到内存后的页面包含可读属性
31  IMAGE_SCN_MEM_WRITE  映射到内存后的页面包含可写属性

介绍完这个我们就要开始打代码了哈:
关于这个要写两个分别是根据属性获得对应的字符串,还有根据字符串获得属性.

第一个是根据属性获得字符串:

DWORD SectionCharacteristics( DWORD dwType , std::string &strType )
{
  if( dwType & IMAGE_SCN_CNT_CODE )
    strType+="含有代码\r\n";
  if( dwType & IMAGE_SCN_CNT_INITIALIZED_DATA )
    strType+="含有已初始化数据\r\n";
  if( dwType & IMAGE_SCN_CNT_UNINITIALIZED_DATA )
    strType+="含有未初始化数据\r\n";
  if( dwType & IMAGE_SCN_LNK_OTHER )
    strType+="保留\r\n";
  if( dwType & IMAGE_SCN_MEM_DISCARDABLE )
    strType+="将被丢失\r\n";
  if( dwType & IMAGE_SCN_MEM_NOT_CACHED )
    strType+="不经过缓存\r\n";
  if( dwType & IMAGE_SCN_MEM_NOT_PAGED )
    strType+="不被交换到磁盘\r\n";
  if( dwType & IMAGE_SCN_MEM_SHARED )
    strType+="共享\r\n";
  if( dwType & IMAGE_SCN_MEM_EXECUTE )
    strType+="可执行属性\r\n";
  if( dwType & IMAGE_SCN_MEM_READ )
    strType+="可读属性\r\n";
  if( dwType & IMAGE_SCN_MEM_WRITE )
    strType+="可写属性\r\n";
  return 0;
}
这里之所以结尾为"\r\n"是为了在EDIT这个控件中实现换行显示.

这个函数的功能就是获得你需要查找的节的属性.因为这个节的属性非常重要而且不能随便乱个。但是以后随着我们深如的研究,这里的值可能必须要改,所以在这里我就让读者再把另一个函数写好可以根据字符串获得对应属性(为什么是字符串到属性,因为选择的时候是字符串,当然也可以是索引一样的)

DWORD CPFDlg::CharacteristicsToInt(CString& string)
{
  if( string == "含有代码" )
    return IMAGE_SCN_CNT_CODE;
  if( string == "含有已初始化数据" )
    return IMAGE_SCN_CNT_INITIALIZED_DATA;
  if( string == "含有未初始化数据" )
    return IMAGE_SCN_CNT_UNINITIALIZED_DATA;
  if( string == "将被丢失" )
    return IMAGE_SCN_MEM_DISCARDABLE;
  if( string == "不经过缓存" )
    return IMAGE_SCN_MEM_NOT_CACHED;
  if( string == "不被交换到磁盘" )
    return IMAGE_SCN_MEM_NOT_PAGED;
  if( string == "共享" )
    return IMAGE_SCN_MEM_SHARED;
  if( string == "可执行属性" )
    return IMAGE_SCN_MEM_EXECUTE;
  if( string == "可读属性" )
    return IMAGE_SCN_MEM_READ;
  if( string == "可写属性" )
    return IMAGE_SCN_MEM_WRITE;
  return 0;
}

到这里,我们只要再介绍如何遍历节,那么关于pe的重要的头就介绍完毕了.后面我就会介绍每个数据目录项的结构等等。

我们接着上面的程序,lpDos 已经指向了 IMAGE_OPTIONAL_HEADER32 .

IMAGE_SECTION_HEADER*  lpISH = NULL;
lpDos += sizeof(IMAGE_OPTIONAL_HEADER32);
lpISH = (IMAGE_SECTION_HEADER*)lpDos;
for(; lpISH->Name != NULL ; lpISH+=1 )
{
 /*处理每个节*/
}

笔者是采用 vector 和一个自定义的结构体 把节表中需要使用的参数放在自己定义的结构体中然后放在容器中,这样可以方面后面的使用,因为这里的数据后面都要经常的使用到,所以我建议读者最好使用适当的方式把你需要的数据保存起来,因为不是里面所有的数据都是需要使用到的,而且每次使用指针来使用还要定位实在是有点麻烦而且在速度上也不一定很好,最麻烦的是可能会很容易出现错误.

[课程]Linux pwn 探索篇!

收藏
免费 0
支持
分享
最新回复 (2)
雪    币: 17
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
厉害,学习了,感谢楼主分享
2012-9-23 11:30
0
雪    币: 478
活跃值: (50)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
好文章    只是可惜了    没人给楼主一个邀请码  (我注意到楼主还是临时用户)
2014-1-22 01:08
0
游客
登录 | 注册 方可回帖
返回
//