[原创]Pe解析总结
前言
一个操作系统的可执行文件格式在很多方面是这个系统的一面镜子。虽然学习一个可执行文件格式通常不是一个程序员的首要任务,但是你可以从这其中学到大量的知识。在可预知的未来,包括, PE文件格式在 MicroSoft 的操作系统中扮演一个重要的角色。如果你在使用 Win32 或 Winnt ,那么你已经在使用 PE 文件了。甚至你只是在 Windows3.1 下使用 Visual C++编程,你使用的仍然是 PE 文件(Visual C++ 的 32 位扩展组件用这个格式)。简而言之,PE 格式已经普遍应用,并且在不短的将来仍是不可避免的。PE结构如此重要,我们可以通过写一个PE_EDIT 工具来理解pe文件,这是本文的目的(15pb项目)
总体设计
以简洁,直观的设计理念。我以树控件为主要控件,如下图:
[图片:emp.gif]
当然,树控件总体展现能力强,具体数据展示能力比较弱。为了弥补这一缺陷,我加了右键菜单功能。鼠标点击树控件哪一项,来展示具体数据。
为了数据和界面分离,可以写个数据类,这里是CPeTool。所有的数据组织都在这个类里面,当然这个pe工具所有的逻辑都在这个类里面
class PeTool
{
private:
MAP_FILE_STRUCT m_filestruct;
public:
vector<LPVOID> m_importVector;
vector<DOSHEADER*> m_DosHeaderVector;
vector<DOSHEADER*> m_FileHeaderVector;
vector<DOSHEADER*> m_OptionHeaderVector;
vector<SECTION*> m_SectionVector;
vector<EXPORT_FUNC_DESCRIPTOR> m_ExportFucDecVector;
vector<IMPORT_TABLE> m_ImportTableVector;
IMAGE_IMPORT_DESCRIPTOR m_Image_Import;
IMAGE_EXPORT_DIRECTORY m_Image_Export;
DWORD m_exportOffset;
bool m_isHasExport;
bool m_isHaxImport;
public:
PeTool();
~PeTool();
TREENODE* m_pHeadNode;
TREENODE* m_pResourceHeadNode;
bool LoadFile(LPSTR lpFileName);
//是否为PE
bool IsPe();
//我用的是树形框,构造树,初始化后,此数据初始化树形框
void CreateTree();
//创建资源数
void CreateResourceTree();
void AddNodeToResourceTree(TREENODE*,PIMAGE_RESOURCE_DIRECTORY,int depath);
void PreAddNode(TREENODE*,int&);
//释放空间
void DeleteTree(TREENODE*);
//分析dosheader
void DosHeaderAnalysis();
//分析Fileheader
void FileHeaderAnalysis();
//分析Optionheader
void OptionHeaderAnalysis();
//分析Section
void SectionAnalysis();
//分析EXport_Table
void ExportTableAnalysis();
//分析Import_table
void ImportTableAnalysis();
//得到资源指针
DWORD GetResourceOffset();
PIMAGE_DOS_HEADER GetDosHeaderPt();
//地址转换
DWORD RSAtoOffset(DWORD);
//得到NT指针
PIMAGE_NT_HEADERS GetNTheaderPt();
//得到数据目录地址 根据INDEX
PIMAGE_DATA_DIRECTORY GetDataDirectoryEnty(int index);
//得到第一个节的指针
PIMAGE_SECTION_HEADER GetFirstSectionHeader();
//地址转换
LPVOID RvaToOffet(LPVOID Rva);
//得到可选头指针
PIMAGE_OPTIONAL_HEADER32 GetOptionHeader();
//得到文件头指针
PIMAGE_FILE_HEADER GetFileHeader();
};工具函数
/*********************************************************************/
/*
此函数最先调用 保存文件句柄,内存文件地址
*/
bool PeTool::LoadFile(LPSTR lpFileName)
{
HANDLE hFile;
HANDLE hMapping;
LPVOID ImageBase;
memset(&m_filestruct,0,sizeof(MAP_FILE_STRUCT));
hFile=CreateFile(lpFileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);
if (!hFile)
return false;
hMapping=CreateFileMapping(hFile,NULL,PAGE_READONLY,0,0,NULL);
if(!hMapping)
{
CloseHandle(hFile);
return FALSE;
}
ImageBase=MapViewOfFile(hMapping,FILE_MAP_READ,0,0,0);
if(!ImageBase)
{
CloseHandle(hFile);
CloseHandle(hMapping);
return false;
}
m_filestruct.hFile=hFile;
m_filestruct.hMapping=hMapping;
m_filestruct.ImageBase=ImageBase;
}
/*********************************************************************/
/*
把pe内存文件地址转换成dos header pointer
*/
PIMAGE_DOS_HEADER PeTool::GetDosHeaderPt()
{
PIMAGE_DOS_HEADER pDosHeader;
pDosHeader=(PIMAGE_DOS_HEADER)m_filestruct.ImageBase;
return pDosHeader;
}
/*********************************************************************/
/*
根据 dos header 中的e_lfanew字段 求出文件的NT headers 的指针
*/
PIMAGE_NT_HEADERS PeTool::GetNTheaderPt()
{
PIMAGE_DOS_HEADER pDosHeader=NULL;
pDosHeader=GetDosHeaderPt();
PIMAGE_NT_HEADERS pNtHeader=NULL;
pNtHeader=(PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew+(DWORD)m_filestruct.ImageBase);
return pNtHeader;
}
/*********************************************************************/
/*
根据NT header FileHeader 字段得出PIMAGE_FILE_HEADER
*/
PIMAGE_FILE_HEADER PeTool::GetFileHeader()
{
PIMAGE_NT_HEADERS pNTHeader;
PIMAGE_FILE_HEADER pFileHeader;
pNTHeader=GetNTheaderPt();
pFileHeader=&(pNTHeader->FileHeader);
return pFileHeader;
}
/*********************************************************************/
/*
根据RVA 求出文件地址
*/
LPVOID PeTool::RvaToOffet(LPVOID Rva)
{
PIMAGE_FILE_HEADER pFileHeader;
// get file pointer of fileheader
pFileHeader=GetFileHeader();
int NumberOfSections=0;
NumberOfSections=pFileHeader->NumberOfSections;
PIMAGE_SECTION_HEADER pSectionHeader;
// get the first section pointer
pSectionHeader=GetFirstSectionHeader();
for(int i=0;i<NumberOfSections;i++)
{
int virtualsize;
DWORD VirtualAddress;
virtualsize=pSectionHeader->Misc.VirtualSize;
VirtualAddress=pSectionHeader->VirtualAddress;
if((DWORD)Rva>=VirtualAddress&&(DWORD)Rva<(VirtualAddress+virtualsize))
{
return LPVOID((DWORD)Rva-VirtualAddress+pSectionHeader->PointerToRawData+(DWORD)m_filestruct.ImageBase);
}
pSectionHeader++;
}
return 0;
}
/*********************************************************************/
/*
根据索引值求的相应数据的数据目录表
*/
PIMAGE_DATA_DIRECTORY PeTool::GetDataDirectoryEnty(int index)
{
PIMAGE_OPTIONAL_HEADER pOptionHeader;
pOptionHeader=GetOptionHeader();
return &(pOptionHeader->DataDirectory[index]);
}
/*********************************************************************/
/*
得到第一个节表的文件地址
*/
PIMAGE_SECTION_HEADER PeTool::GetFirstSectionHeader()
{
PIMAGE_NT_HEADERS pNtH=NULL;
PIMAGE_SECTION_HEADER pSH=NULL;
pNtH=GetNTheaderPt();
pSH=IMAGE_FIRST_SECTION(pNtH);
return pSH;
}
/*********************************************************************/
/*
得到OptionHeader 的文件地址
*/
PIMAGE_OPTIONAL_HEADER32 PeTool::GetOptionHeader()
{
PIMAGE_NT_HEADERS pNTHeader;
PIMAGE_OPTIONAL_HEADER pOptionalHeader;
pNTHeader=GetNTheaderPt();
pOptionalHeader=&(pNTHeader->OptionalHeader);
return pOptionalHeader;
}导入表

导入表的显示,我用列表框实现,那怎么组织数据呢,下面我用VECTOR来实现。结构如下
/*********************************************************************/
//此结构描述每一个DLL的信息
typedef struct _Import_table
{
char Dllname[20];
char OriginalFirstThunk[20];
char TimeDateStamp[20];
char ForwardChain[20];
char name[20];
char FirtThunk[20];
vector<IMPORT_FUNC> m_funcsvector;
}IMPORT_TABLE;
/*********************************************************************/
//此结构描述每一个导入函数
typedef struct _Import_Func_Des
{
char ThunkRva[20];
char ThunkOffset[20];
char ThunkValue[20];
char Hin[20];
char ApiName[100];
}IMPORT_FUNC;
/*********************************************************************/
//此结构用来描述每一个导入的dll,以及每一个导入函数,然后存入下面的vector中
vector<IMPORT_TABLE> m_ImportTableVector;
/*********************************************************************/
//分析Import_table
void ImportTableAnalysis();
具体代码如下:
void PeTool::ImportTableAnalysis()
{
//得到导入表RVA
PIMAGE_DATA_DIRECTORY pImportRva=GetDataDirectoryEnty(1);
//没有则返回
if(pImportRva->VirtualAddress==0)
return ;
//根据RVA得到文件偏移
PIMAGE_IMPORT_DESCRIPTOR
pImportOffet=(PIMAGE_IMPORT_DESCRIPTOR)RvaToOffet((LPVOID)pImportRva->VirtualAddress);
int i=0;
//描述函数结构体
IMPORT_FUNC funs;
//描述dll的结构体
IMPORT_TABLE importTable;
//循环遍历每一个DLL
while(pImportOffet[i].FirstThunk)
{
PIMAGE_IMPORT_DESCRIPTOR tempImport;
tempImport=pImportOffet+i;
char *pName=(char*)RvaToOffet((LPVOID)tempImport->Name);
//初始化IMPORT_TABLE结构体
sprintf(importTable.Dllname,"%s",pName);
sprintf(importTable.OriginalFirstThunk,"%0.8x",tempImport->OriginalFirstThunk);
sprintf(importTable.TimeDateStamp,"%0.8x",tempImport->TimeDateStamp);
sprintf(importTable.ForwardChain,"%0.8x",tempImport->ForwarderChain);
sprintf(importTable.name,"%0.8x",tempImport->Name);
sprintf(importTable.FirtThunk,"%0.8x",tempImport->FirstThunk);
i++;
//得到INT OFFSET
PIMAGE_THUNK_DATA pInt=(PIMAGE_THUNK_DATA)RvaToOffet((LPVOID)tempImport->FirstThunk);
PIMAGE_IMPORT_BY_NAME pbyName;
int j=0;
//循环遍历每一个函数,取得具体信息。
while(pInt[j].u1.Function)
{
sprintf(funs.ThunkRva,"%0.8x",tempImport->OriginalFirstThunk+j*4);
sprintf(funs.ThunkOffset,"%0.8x",(DWORD)pInt+j*4-(DWORD)m_filestruct.ImageBase);
sprintf(funs.ThunkValue,"%0.8x",pInt[j].u1.AddressOfData);
pbyName=(PIMAGE_IMPORT_BY_NAME)RvaToOffet((LPVOID)pInt[j].u1.AddressOfData);
//判断是否为序号导入的
if(pInt[j].u1.AddressOfData&0x80000000)
{
sprintf(funs.Hin,"%s","--");
sprintf(funs.ApiName,"ByOrdinal:%0.4x",pInt[j].u1.Function&0xFFFF);
}
else
{
sprintf(funs.Hin,"%0.4x",pbyName->Hint);
sprintf(funs.ApiName,"%s",(char*)pbyName->Name);
}
//把函数加入vector中
importTable.m_funcsvector.push_back(funs);
j++;
}
//把每一个DLL加入vector中
m_ImportTableVector.push_back(importTable);
importTable.m_funcsvector.clear();
m_isHaxImport=1;//是否有导入表标志位
}
}
/******************************************************************导出表

//导出表描述信息
/*********************************************************************/
typedef struct _Export_func_Descriptor
{
char Ordinal[50];
char Rva[50];
char Offset[50];
char name[200];
}EXPORT_FUNC_DESCRIPTOR;
用此结构存储导出函数的每一个信息
vector<EXPORT_FUNC_DESCRIPTOR> m_ExportFucDecVector;
/*********************************************************************/
下面是导出表的分析
void PeTool::ExportTableAnalysis()
{
//得到数据目录的文件地址
PIMAGE_DATA_DIRECTORY pData=GetDataDirectoryEnty(0);
if(pData->VirtualAddress==0)
{
return ;
}
//得到导出表的文件地址
PIMAGE_EXPORT_DIRECTORY pImexport=(PIMAGE_EXPORT_DIRECTORY )RvaToOffet((LPVOID)pData->VirtualAddress);
m_exportOffset=(DWORD)pImexport;
memcpy(&m_Image_Export,pImexport,sizeof(IMAGE_IMPORT_DESCRIPTOR));
int sizeoffun=pImexport->NumberOfFunctions;
int base=pImexport->Base;
int sizeofname=pImexport->NumberOfNames;
//得到导出函数文件地址
DWORD* pFunc=(DWORD*)RvaToOffet((LPVOID)pImexport->AddressOfFunctions);
//得到导出函数名字文件地址
DWORD* pName=(DWORD*)RvaToOffet((LPVOID)pImexport->AddressOfNames);
//得到ORdinal 文件地址
const WORD* pOrdinal=(WORD*)RvaToOffet((LPVOID)pImexport->AddressOfNameOrdinals);
DWORD Offset=0;
EXPORT_FUNC_DESCRIPTOR exportTable;
for (int i = 0; i < sizeoffun; i++)
{
if(!pFunc[i])
continue;
sprintf(exportTable.Ordinal,"%0.2x",base+i);
sprintf( exportTable.Rva,"%0.8x",pFunc[i]);
Offset=(DWORD)RvaToOffet((LPVOID)pFunc[i]);
sprintf(exportTable.Offset,"%0.8x",Offset-(DWORD)m_filestruct.ImageBase);
//是否按序号导出
int index=isByname(pOrdinal,i,sizeofname);
if(index==-1)
{
sprintf(exportTable.name,"%s","ByOrdinal");
}
else
{
char* name=(char*)RvaToOffet((LPVOID)pName[index]);
sprintf(exportTable.name,"%s",name);
}
m_ExportFucDecVector.push_back(exportTable);
}
m_isHasExport=1;//是否有导出表标志位
}
/*********************************************************************/资源
资源这一块,由于显示是树形控件,则数据以树的结构组织,显示更为方便。问题关键是怎么组织成树的结构,我这里定义这个兄弟树结构体
/*********************************************************************/
//树节点
typedef struct _TreeNode
{
char data[256];//节点数据
_TreeNode* pFirstChild; //第一个子节点
_TreeNode* pNextBrother;//兄弟节点
_TreeNode()
{
pFirstChild=pNextBrother=NULL;
}
/*********************************************************************/
/*
把资源数据以树的结构组织
*/
void PeTool::CreateResourceTree()
{
// 根节点
m_pResourceHeadNode=new TREENODE;
//资源地址
PIMAGE_RESOURCE_DIRECTORY pReDirectory=(PIMAGE_RESOURCE_DIRECTORY)GetResourceOffset();
//资源地址,根节点为参数 添加个个节点
AddNodeToResourceTree(m_pResourceHeadNode,pReDirectory,0);
}
/*********************************************************************/
/*
参数:
TREENODE*pNode, 父节点
PIMAGE_RESOURCE_DIRECTORY pDirectory, 资源头
int depath 深度
*/
void PeTool::AddNodeToResourceTree(TREENODE*pNode,PIMAGE_RESOURCE_DIRECTORY pDirectory,int depath )
{
//得到 PIMAGE_RESOURCE_DIRECTORY_ENTRY 地址
PIMAGE_RESOURCE_DIRECTORY_ENTRY pEntry=(PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)pDirectory+sizeof(IMAGE_RESOURCE_DIRECTORY));
WORD NumberOfNamedEntries;
WORD NumberOfIdEntries;
NumberOfNamedEntries=pDirectory->NumberOfNamedEntries;
NumberOfIdEntries=pDirectory->NumberOfIdEntries;
//根据每一个资源目录有多少个来申请多少个节点
_TreeNode* NodeArry=new _TreeNode[NumberOfNamedEntries+NumberOfIdEntries];
pNode->pFirstChild=NodeArry;
TREENODE* temp=NodeArry;
for(int i=1;i<NumberOfNamedEntries+NumberOfIdEntries;i++)
{
temp->pNextBrother=NodeArry+i;
temp=NodeArry+i;
}
temp=NodeArry;
//采用先序遍历 来添加节点
for (int i=0;i<NumberOfNamedEntries+NumberOfIdEntries;i++)
{
PIMAGE_RESOURCE_DIRECTORY_ENTRY pEntryitem=pEntry+i;
DWORD name=pEntryitem->Name;
if(pEntryitem->DataIsDirectory)
{
if(depath==0)//第一级目录 有id 标示和字符串标示
{
if(pEntryitem->NameIsString)//字符串标示
{
PIMAGE_RESOURCE_DIR_STRING_U pString=(PIMAGE_RESOURCE_DIR_STRING_U)RSAtoOffset(pEntryitem->NameOffset);
WideCharToMultiByte(CP_ACP,WC_COMPOSITECHECK |WC_DEFAULTCHAR ,
pString->NameString,pString->Length,
temp->data,
256,
NULL,
NULL);
temp->data[pString->Length]=0;
}
else //id标示
{
if(pEntryitem->Name>=17)
sprintf(temp->data,"%s","自定义");
else
strcpy(temp->data,szResourceType[pEntryitem->Name]);
}
}
else
sprintf(temp->data,"%d",pEntryitem->Id);
PIMAGE_RESOURCE_DIRECTORY dd=(PIMAGE_RESOURCE_DIRECTORY)RSAtoOffset(pEntryitem->OffsetToDirectory);
AddNodeToResourceTree(temp,dd,depath+1);
}
else
{
PIMAGE_RESOURCE_DATA_ENTRY pDataEntry=(PIMAGE_RESOURCE_DATA_ENTRY)RSAtoOffset(pEntryitem->OffsetToData);
sprintf(temp->data,"OffsetToData (RVA): 0x%x,Size: %d,CodePage: %d,Reserved: %d",
pDataEntry->OffsetToData,pDataEntry->Size,pDataEntry->CodePage,pDataEntry->Reserved);
}
temp=temp->pNextBrother;
}
}
/*********************************************************************/
