分享
 
 
 

深入浅出PE文件格式---自己动手打造PE Show

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

深入浅出PE文件格式---自己动手打造PE Show

作者:WiNrOOt

////////////////////////////////////////////////////////////////////

// 开篇 //

///////////////////////////////////////////////////////////////////

大家好!我一位菜鸟,学习加密解密已经有一段时间了,可是对于脱壳总是似懂非懂,心中甚是不爽。

于是就从PE结构开始,在学习的过程中我发现要真正了解PE文件结构就必须动手,只有动手您才能看懂他,

感受他,直到你应用他………………这是我的一点废话希望大家不要嫌烦。下面是是我的学习笔记,希望能给大家

带来一点提示,文中有不对的地方请各位大虾指正。谢谢!

//////////////////////////////////////////////////////////////////

// 准备 //

/////////////////////////////////////////////////////////////////

开篇之前我想大家起码要有几点准备:

1。Iczelion's Win32 Assembly的教程我们主要是围绕他的PE教程来实现我们的函数功能。

(其实Win32ASM Tutorial Resource Kit v1.00 Collected and packed by dREAMtHEATER就包含这些

还是翻译过的。下载地址WWW.PEDIY.COM)

下载回来希望您能看一下,这样大家就好交流:-)

2。一个开发环境。(我用的是VC++6.0)

3。一个适合你研究东西的环境。

////////////////////////////////////////////////////////////////

// 正文 //

///////////////////////////////////////////////////////////////

“ PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格

式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。"portable executable"

(可移植的执行体)意味着此文件格式是跨win32平台的 :

即使Windows运行在非Intel的CPU上,任何win32平台的PE装载器都能识别和使用该文件格式。当然,移植到不

同的CPU上PE执行体必然得有一些改变。所有 win32执行体

(除了VxD和16位的Dll)都使用PE文件格式,包括NT的内核模式驱动程序(kernel mode

drivers)。因而研究PE文件格式给了我们洞悉Windows结构的良机。“

好了,上面这段话就是我们为什么要研究PE文件结构。

看图1,

这张图我相信大家不陌生,第一块是DOS MZ header这是什么呢?

这张图的每一块都是什么意思呢?

从编程这方面说图中的每一块都代表着一个结构体,这些小块都包含有不同的子块,也是些结构体。

每个小块都有他自己的功能,我相信Iczelion's的教程中已经表达得很明白了。

那么我们就来开始动手。

我们要设计我们自己的PE TOOLS----PE Show

主要功能:1判断文件是否是PE文件。

2显示pe文件的相关信息。

1.打开文件代码如下:

代码

if(FALSE==PEfile.Open(m_filename,CFile::typeBinary&line;CFile::shareDenyNone))

&leftsign;

MessageBox("文件打不开!");

return;

&rightsign;

CFile类的使用方法希望大及自己去查找msdn

2。文件我们打开了而且是以Binary方式打开的,下面我们该干什么了?

编写第一个功能-----检验PE文件的有效性

在Iczelion's的教程中有这样一段话:

“1。首先检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE,是则 DOS MZ header 有效。

2。一旦证明文件的 DOS header 有效后,就可用e_lfanew来定位 PE header 了。

3。比较 PE header 的第一个字的值是否等于 IMAGE_NT_HEADER。

如果前后两个值都匹配,那我们就认为该文件是一个有效的PE文件。

这就是检验PE文件有效性的流程。

从上面那段话我们看出判断的关键是PE header 的第一个字的值是否等于 IMAGE_NT_HEADER

直到这些我们就倒着找。

PE header 的第一个字的值是什么?

我么就来看一下IMAGE_NT_HEADERS的结构:(查看WINNT.H就找到了)

typedef struct _IMAGE_NT_HEADERS &leftsign;

DWORD Signature;

IMAGE_FILE_HEADER FileHeader;

IMAGE_OPTIONAL_HEADER32 OptionalHeader;

&rightsign; IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Signature 一dword类型,值为50h, 45h, 00h, 00h(PE\0\0)。 本域为PE标记,我们可以此识别给定文件是否为有效PE文件。

FileHeader 该结构域包含了关于PE文件物理分布的信息, 比如节数目、文件执行机器等。

OptionalHeader 该结构域包含了关于PE文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。

我们目的很明确。如果IMAGE_NT_HEADERS的signature域值等于"PE\0\0",那么就是有效的PE文件。实际上,为了比较方便,Microsoft已定义了常量IMAGE_NT_SIGNATURE供我们使用。

IMAGE_DOS_SIGNATURE equ 5A4Dh

IMAGE_OS2_SIGNATURE equ 454Eh

IMAGE_OS2_SIGNATURE_LE equ 454Ch

IMAGE_VXD_SIGNATURE equ 454Ch

IMAGE_NT_SIGNATURE equ 4550h

判断的问题我们解决了,新的问题又来了我们如何定位IMAGE_NT_HEADERS结构的位置。

MS肯定有办法,PE文件的开头是什么?DOS MZ header结构,我们来看一下他的定义:代码

typedef struct _IMAGE_DOS_HEADER &leftsign; // DOS .EXE header

WORD e_magic; // Magic number

WORD e_cblp; // Bytes on last page of file

WORD e_cp; // Pages in file

WORD e_crlc; // Relocations

WORD e_cparhdr; // Size of header in paragraphs

WORD e_minalloc; // Minimum extra paragraphs needed

WORD e_maxalloc; // Maximum extra paragraphs needed

WORD e_ss; // Initial (relative) SS value

WORD e_sp; // Initial SP value

WORD e_csum; // Checksum

WORD e_ip; // Initial IP value

WORD e_cs; // Initial (relative) CS value

WORD e_lfarlc; // File address of relocation table

WORD e_ovno; // Overlay number

WORD e_res[4]; // Reserved words

WORD e_oemid; // OEM identifier (for e_oeminfo)

WORD e_oeminfo; // OEM information; e_oemid specific

WORD e_res2[10]; // Reserved words

LONG e_lfanew; // File address of new exe header

&rightsign; IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

看一下最后一项!!!!!

发现了吗?他指向的就是PE header

那么DOS MZ header的位置怎么确定呢?呵呵,他就是文件的开始

看代码:代码

PEfile.Read(&stPEDosHeader,sizeof(_IMAGE_DOS_HEADER));

if(stPEDosHeader.e_magic!=IMAGE_DOS_SIGNATURE)//"MZ"

&leftsign;

MessageBox("DOS MZ header无效!");

PEfile.Close();

return;

&rightsign;

else

&leftsign;

//-----------------------

//显示DOS Header

//-----------------------

UpdateData(true);

m_Magicnumber.Format(_T("0x%.4X"),stPEDosHeader.e_magic);

m_cblp.Format(_T("0x%.4X"),stPEDosHeader.e_cblp);

m_cp.Format(_T("0x%.4X"),stPEDosHeader.e_cp);

m_crlc.Format(_T("0x%.4X"),stPEDosHeader.e_crlc);

m_cparhdr.Format(_T("0x%.4X"),stPEDosHeader.e_cparhdr);

m_minalloc.Format(_T("0x%.4X"),stPEDosHeader.e_minalloc);

m_maxalloc.Format(_T("0x%.4X"),stPEDosHeader.e_maxalloc);

m_ss.Format(_T("0x%.4X"),stPEDosHeader.e_ss);

m_sp.Format(_T("0x%.4X"),stPEDosHeader.e_sp);

m_csum.Format(_T("0x%.4X"),stPEDosHeader.e_csum);

m_ip.Format(_T("0x%.4X"),stPEDosHeader.e_ip);

m_cs.Format(_T("0x%.4X"),stPEDosHeader.e_cs);

m_lfarlc.Format(_T("0x%.4X"),stPEDosHeader.e_lfarlc);

m_ovno.Format(_T("0x%.4X"),stPEDosHeader.e_ovno);

m_oemid.Format(_T("0x%.4X"),stPEDosHeader.e_oemid);

m_oeminfo.Format(_T("0x%.4X"),stPEDosHeader.e_oeminfo);

m_lfanew.Format(_T("0x%.8X"),stPEDosHeader.e_lfanew);

UpdateData(false);

&rightsign;

buf=stPEDosHeader.e_lfanew; //确定_IMAGE_DOS_HEADER偏移

try&leftsign;PEfile.Seek(buf,CFile::begin);&rightsign;

catch(...)

&leftsign;

MessageBox("_IMAGE_DOS_HEADER.e_lfanew不对!");

PEfile.Close();

return;

&rightsign;

PEfile.Read(&stPEHeader,sizeof(_IMAGE_NT_HEADERS));//----------NT头

if(stPEHeader.Signature!=IMAGE_NT_SIGNATURE)//"PE\0\0"

&leftsign;

MessageBox("该文件不是PE格式!");

PEfile.Close();

return;

&rightsign;

else

&leftsign;

MessageBox("该文件是PE格式!");

PEfile.Close();

return;

&rightsign;

好了现在我们已经确定了文件是否是有效的PE文件。顺便我们把IMAGE_DOS_HEADER的结构成员都显示出来了。

我们已经写了一个功能。

下面我们继续顺着往下看:

IMAGE_DOS_HEADER结构结束那么就是IMAGE_NT_HEADERS开始。结构体的成员情况前面我们已经介绍过了

下面我们来提取他们:代码

//---------------------------

//显示IMAGE_FILE_HEADER结构

//---------------------------

UpdateData(true);

m_Machine.Format(_T("0x%.4X"),stPEHeader.FileHeader.Machine);

m_NumberOfSections.Format(_T("0x%.4X"),stPEHeader.FileHeader.NumberOfSections);

m_TimeDateStamp.Format(_T("0x%.8X"),stPEHeader.FileHeader.TimeDateStamp);

m_PointerToSymbolTable.Format(_T("0x%.8X"),stPEHeader.FileHeader.PointerToSymbolTable);

m_NumberOfSymbols.Format(_T("0x%.8X"),stPEHeader.FileHeader.NumberOfSymbols);

m_SizeOfOptionalHeader.Format(_T("0x%.4X"),stPEHeader.FileHeader.SizeOfOptionalHeader);

m_Characteristics.Format(_T("0x%.4X"),stPEHeader.FileHeader.Characteristics);

UpdateData(false);

这是IMAGE_FILE_HEADER FileHeader的成员,我们已经将他们提取出来。

看教程我们继续寻找IMAGE_OPTIONAL_HEADER的成员,他是结构体中的结构体我们就顺着找。代码

typedef struct _IMAGE_OPTIONAL_HEADER &leftsign;

//

// 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;

WORD MinorImageVersion;

WORD MajorSubsystemVersion;

WORD MinorSubsystemVersion;

DWORD Win32VersionValue;

DWORD SizeOfImage;

DWORD SizeOfHeaders;

DWORD CheckSum;

WORD Subsystem;

WORD DllCharacteristics;

DWORD SizeOfStackReserve;

DWORD SizeOfStackCommit;

DWORD SizeOfHeapReserve;

DWORD SizeOfHeapCommit;

DWORD LoaderFlags;

DWORD NumberOfRvaAndSizes;

IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

&rightsign; IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

看到这些成员将他们全部显示出来

代码

//--------------------------------

//显示IMAGE_OPTIONAL_HEADER

//--------------------------------

UpdateData(true);

m_Magic.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.Magic);

m_MajorLinkerVersion.Format(_T("0x%.2X"),stPEHeader.OptionalHeader.MajorLinkerVersion);

m_MinorLinkerVersion.Format(_T("0x%.2X"),stPEHeader.OptionalHeader.MinorLinkerVersion);

m_SizeOfCode.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfCode);

m_SizeOfInitializedData.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfInitializedData);

m_SizeOfUninitializedData.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfUninitializedData);

m_AddressOfEntryPoint.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.AddressOfEntryPoint);

m_BaseOfCode.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.BaseOfCode);

m_BaseOfData.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.BaseOfData);

m_ImageBase.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.ImageBase);

m_SectionAlignment.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SectionAlignment);

m_FileAlignment.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.FileAlignment);

m_MajorOperatingSystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MajorOperatingSystemVersion);

m_MinorOperatingSystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MinorOperatingSystemVersion);

m_MajorImageVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MajorImageVersion);

m_MinorImageVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MinorImageVersion);

m_MajorSubsystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MajorSubsystemVersion);

m_MinorSubsystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MinorSubsystemVersion);

m_Win32VersionValue.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.Win32VersionValue);

m_SizeOfImage.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfImage);

m_SizeOfHeaders.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfHeaders);

m_CheckSum.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.CheckSum);

m_Subsystem.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.Subsystem);

m_DllCharacteristics.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.DllCharacteristics);

m_SizeOfStackReserve.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfStackReserve);

m_SizeOfStackCommit.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfStackCommit);

m_SizeOfHeapReserve.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfHeapReserve);

m_SizeOfHeapCommit.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfHeapCommit);

m_LoaderFlags.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.LoaderFlags);

m_NumberOfRvaAndSizes.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.NumberOfRvaAndSizes);

UpdateData(false);

我们已经学了许多关于 DOS header 和 PE header 的知识。接下来就该轮到 section table(节表)了。

节表其实就是紧挨着 PE header 的一结构数组。该数组成员的数目由 file header (IMAGE_FILE_HEADER)

结构中 NumberOfSections 域的域值来决定。节表结构又命名为 IMAGE_SECTION_HEADER。代码

typedef struct _IMAGE_SECTION_HEADER &leftsign;

BYTE Name[IMAGE_SIZEOF_SHORT_NAME];

union &leftsign;

DWORD PhysicalAddress;

DWORD VirtualSize;

&rightsign; Misc;

DWORD VirtualAddress;

DWORD SizeOfRawData;

DWORD PointerToRawData;

DWORD PointerToRelocations;

DWORD PointerToLinenumbers;

WORD NumberOfRelocations;

WORD NumberOfLinenumbers;

DWORD Characteristics;

&rightsign; IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER

Name 这儿的节名长不超过8字节。记住节名仅仅是个标记而已,我们选择任何名字甚至空着也行,注意这里不用null结束。命名不是一个ASCIIZ字符串,所以不用null结尾。

VirtualAddress 本节的RVA(相对虚拟地址)。PE装载器将节映射至内存时会读取本值,因此如果域值是1000h,而PE文件装在地址400000h处,那么本节就被载到401000h。

SizeOfRawData 经过文件对齐处理后节尺寸,PE装载器提取本域值了解需映射入内存的节字节数。(译者注: 假设一个文件的文件对齐尺寸是0x200,如果前面的 VirtualSize域指示本节长度是0x388字节,则本域值为0x400,表示本节是0x400字节长)。

PointerToRawData 这是节基于文件的偏移量,PE装载器通过本域值找到节数据在文件中的位置。

Characteristics 包含标记以指示节属性,比如节是否含有可执行代码、初始化数据、未初始数据,是否可写、可读等。

现在我们已知晓 IMAGE_SECTION_HEADER 结构,再来模拟一下 PE装载器的工作吧:

1 读取 IMAGE_FILE_HEADER 的 NumberOfSections域,知道文件的节数目。

2 SizeOfHeaders 域值作为节表的文件偏移量,并以此定位节表。

3 遍历整个结构数组检查各成员值。

4 对于每个结构,我们读取PointerToRawData域值并定位到该文件偏移量。然后再读取SizeOfRawData域值来决定映射内存的字节数。将VirtualAddress域值加上ImageBase域值等于节起始的虚拟地址。然后就准备把节映射进内存,并根据Characteristics域值设置属性。

5 遍历整个数组,直至所有节都已处理完毕。

代码如下:代码

//------------------------------

//显示Section结构

//------------------------------

nSection=stPEHeader.FileHeader.NumberOfSections;

stSectionHeader=new _IMAGE_SECTION_HEADER[nSection];

m_ListCtrl.DeleteAllItems();

for(int i=0;i &leftsign;

PEfile.Read(&stSectionHeader[i],sizeof(_IMAGE_SECTION_HEADER));//-----节表

//NO

szTemp.Format(_T("%.2d"), i+1);

m_ListCtrl.InsertItem(i,szTemp,i);

//SectionName

strcpy(chSectionName,(LPCSTR)stSectionHeader[i].Name);

m_ListCtrl.SetItemText(i,1,chSectionName);

//VirtualSize

szTemp.Format(_T("0x%.8X"),stSectionHeader[i].Misc.VirtualSize);

m_ListCtrl.SetItemText(i,2,szTemp);

//VirtualAddress

szTemp.Format(_T("0x%.8X"),stSectionHeader[i].VirtualAddress );

m_ListCtrl.SetItemText(i,3,szTemp);

//SizeOfRawData

szTemp.Format(_T("0x%.8X"),stSectionHeader[i].SizeOfRawData );

m_ListCtrl.SetItemText(i,4,szTemp);

//SizeOffset

szTemp.Format(_T("0x%.8X"),stSectionHeader[i].PointerToRawData );

m_ListCtrl.SetItemText(i,5,szTemp);

//Characteristics

szTemp.Format(_T("0x%.8X"),stSectionHeader[i].Characteristics );

m_ListCtrl.SetItemText(i,6,szTemp);

&rightsign;

delete stSectionHeader;

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有