自制简单的pe信息查看器

来源:互联网 发布:网络电视选择哪个信源 编辑:程序博客网 时间:2024/06/11 17:53

刚开始学逆向,刚好学到 pe 结构的部分,想着如果可以自己做一个自动帮我解析pe头信息的程序出来自己
的操作会方便很多,于是就开始了这个小项目

首先是 pe 文件的结构

从总体上看基本是这样子的

==============dos头==============dos存根==============NT头==============节区头(.text)    代码==============节区头(.data)     数据==============节区头(.rsrc)     资源==============     null==============     节区(.text)==============     null==============     节区(.data)==============     null==============     节区(.rsrc)==============     null==============

从 dos头 到 节区头 这一段叫做 pe头, 节区头用于定义 对应的节区的信息,在文件或内存中的大小,位置
属性等,想想也是,你跟我说这是个 pe 文件,那你总得要给出对应的信息给我吧,不然要怎么读取文件
呢, pe 头 还有 节区头 干的大概就是这样一件事。接下来一边解析 pe 文件的格式 一边来编写自己的 pe 文件信息查看器
使用python3 来进行编写,要解析 pe 文件,那么我首先要 读取 文件的内容吧,因为 pe文件里面存储的是二
进制数据,所以可以读取出来 改为对应的 十六进行形式,像 hex editor 等 16 进制编辑器一样

#!/usr/bin/env python#coding:utf-8__author__="luojiaqs"'''pe 文件 解析'''import sysif __name__=='__main__':    if len(sys.argv)==1:   #控制台输入文件名 python pe.py xx.exe        print("请输入一个pe文件")        exit(0)    pefile=sys.argv[1]    with open(pefile,'rb') as f:        f.seek(0)        t='%8s' % 'offset'        for i in range(16):            t+=("%2s%02x" % (' ',i)) # 添加输出前面的标签        print(t)        s=f.read()#读取二进制文件        t=''        #每行 16 个字节 进行输出        for i in range(len(s)):            if i%16==0:                if i!=0:                    print(t)                t=''                t+=str("%08x" % (i))            t+=str("%2s%02s" % (' ',s[i].encode('hex')))

运行结果大概像这个样子
这里写图片描述
很好,可以用16进制读取 二进制文件了,接下来就边解析 pe 文件格式 边完善这个代码

Dos头

这里引用 逆向工程核心原理里面的话,dos头的存在,是因为微软在创建 pe 文件格式的时候,人们正广泛
使用 dos 文件, 所以 微软充分考虑 pe 文件对 dos 文件的 兼容性,结果是在 pe 头 的最前面添加了一个
IMAGE_DOS_HEADER 结构体,用来扩展已有的 dos exe 头
该结构体大小为 40 个字节 ,网上找的 dos 头的注释,不过主要就是 第一个 e_magic 还有最后一个
e_lfanew 这两个

typedef struct _IMAGE_DOS_HEADER STRUCT {WORD e_magic  // DOS可执行文件标记,就是16进制时候的 4d 5a('MZ'),所有pe文件开始都有这个标志WORD e_cblp   // Bytes on last page of file  WORD e_cp   // Pages in fileWORD e_crlc   // RelocationsWORD e_cparhdr   // Size of header in paragraphsWORD e_minalloc   // Minimun extra paragraphs needsWORD e_maxalloc  // Maximun extra paragraphs needsWORD e_ss   // intial(relative)SS value   DOS代码的初始化堆栈SS WORD e_sp   // intial SP value   DOS代码的初始化堆栈指针SP WORD e_csum   // Checksum +WORD e_ip   //  intial IP value   DOS代码的初始化指令入口[指针IP] WORD e_cs   // intial(relative)CS value   DOS代码的初始堆栈入口 CSWORD 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  // 指示 NT 头 的 偏移,根据不同的文件有可变的值,有了这个偏移就可以找到NT头的位置了}IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;

主要就是这样子了,不过要读取一个 word 一个 word 什么的 还是有点麻烦的,还有就是 intel 的 cpu 都采用
小端标识法,即逆序存储,要转换过来还是需要花一点功夫的
总之就先写一个模板吧,之后有疏漏的地方再逐渐进行完善

#!/usr/bin/env python#coding:utf-8__author__="luojiaqs"'''pe 文件 解析'''import sys#将 二进制文件转换为一个连续的 列表 这个类用来处理数据的读取,#现在没有做太多的错误什么的处理,先完成基本功能,使用小端的读法class Hexhandle(object):    data=[]#简单传入一个 列表,然后就只是在列表中对数据进行操作就可以了    base=0#读取每个字节的基址    def __init__(self,data,base=0):        #判断是否是列表        self.data=data        self.base=base    def readbyte(self):#读取一个字节        return self.read(1)        pass    def readword(self):#读取一个word        return self.read(2)        pass    def readdword(self):#四个字节        return self.read(4)        pass    def readdd(self):#8个字节        return self.read(8)        pass    def read(self,num=1):        self.base+=num        hexnum=self.data[self.base-1]        for i in range(1,num):            hexnum=hexnum<<8            hexnum+=self.data[self.base-i-1]                return hexnum        passdef readBin(filename): #读取二进制文件,并转换成一个列表 返回    result=[]    with open(filename,'rb') as f:        f.seek(0)        s=f.read()        for i in range(len(s)):            result.append(int(s[i].encode('hex'),base=16))    return result    passif __name__=='__main__':    if len(sys.argv)==1:        print("输入一个pe文件")        exit(0)    #测试代码    pefile=sys.argv[1]    pehex=readBin(pefile)    hh=Hexhandle(pehex)    print(hex(hh.readbyte()))

基本上这样就可以比较轻松的读取到 一个 pe 文件的每个位置了,先看看其他的头,然后再进行每个头
的读取的功能的实现

Dos 存根(stub)

在dos 头的正下方,是一个可选项,而且大小不固定,即使没有这个程序也是可以运行的
大概就像这个样子
这里写图片描述
不过自己只是需要快速的定位信息,并且找出对自己有用的信息,所以这一个暂时没有什么用处

NT 头

NT头也是由一个结构体来进行定义的 叫 IMAGE_NT_HEADERS,大小为 f8 ,也就是 248个字节
dos头的最后一个 e_lfanew 所指向的位置就是这里

typedef struct _IMAGE_NT_HEADERS {    DWORD Signature;      //签名 00004550h 'PE..'    IMAGE_FILE_HEADER FileHeader;           //文件头    IMAGE_OPTIONAL_HEADER32 OptionalHeader; //可选头} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

文件头结构体 IMAGE_FILE_HEADER是这样子定义的

typedef struct _IMAGE_FILE_HEADER {    WORD    Machine; //每个cpu拥有唯一的 machine 码    WORD    NumberOfSections;  //用来指示 文件中所拥有的节区的数量,一定要大于0,且要和实际的节区数量一致    DWORD   TimeDateStamp;           //时间戳E72B4FA9    DWORD   PointerToSymbolTable;       //指向符号表    DWORD   NumberOfSymbols;        //符号表数量    WORD    SizeOfOptionalHeader;   //NT头可选头的 大小 ,一般为 E0 ,不过 PE32+的可选头大小不同,所以需要定义    WORD    Characteristics;    //用来标识文件的属性,是否是可运行状态,是否是dll等信息,用 | 的形式组合起来} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

很好,接下来就剩下一个可选头了,可选头是 PE头里面最大的一个,好吧,慢慢解析一下

typedef struct _IMAGE_OPTIONAL_HEADER {    WORD    Magic;  //为IMAGE_OPTIONAL_HEADER32时为10B IMAGE_OPTIONAL_HEADER64时为 20B    BYTE    MajorLinkerVersion;    //连接器主版本号    BYTE    MinorLinkerVersion;      //连接器小版本号    DWORD   SizeOfCode;  //代码节大小    DWORD   SizeOfInitializedData;       //已初始化数据大小    DWORD   SizeOfUninitializedData;//为初始化数据大小    DWORD   AddressOfEntryPoint;  //定义 EP 的 RVA值,即程序最先执行的代码的其实位置,十分重要    DWORD   BaseOfCode;      //程序段基地址    DWORD   BaseOfData;  //数据段基地址    DWORD   ImageBase;    //指出装载到用户内存时的优先装入地址,exe=>00400000  dll=>10000000    DWORD   SectionAlignment;  //节对齐,指定节区在 内存 中的最小单位    DWORD   FileAlignment;    //文件对齐,指定节区在 磁盘文件 中的最小单位    WORD    MajorOperatingSystemVersion;   //操作系统主版本号    WORD    MinorOperatingSystemVersion;  //操作系统小版本号    WORD    MajorImageVersion;     //镜像主版本号    WORD    MinorImageVersion;    //镜像小版本号    WORD    MajorSubsystemVersion;//子系统主版本号    WORD    MinorSubsystemVersion;  //子系统小版本号    DWORD   Win32VersionValue;      DWORD   SizeOfImage;    //指定了 PE 镜像装载到内存中所占的空间大小    DWORD   SizeOfHeaders;  //指出了PE头的大小,是FileAlignment的整数倍,第一节区所在的位置和                //SizeOfHeaders距文件开始的偏移量相同    DWORD   CheckSum;       WORD    Subsystem;    //用来区分系统驱动文件(*.sys)和普通的可执行文件    WORD    DllCharacteristics;                    DWORD   SizeOfStackReserve;//栈初始化大小    DWORD   SizeOfStackCommit;    DWORD   SizeOfHeapReserve;  //堆初始化大小    DWORD   SizeOfHeapCommit;      DWORD   LoaderFlags;         DWORD   NumberOfRvaAndSizes;   //用来指定DataDirectory数组的个数    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表                                        //数据目录表,包括输入输出表,资源等} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

IMAGE_DATA_DIRECTORY结构体定义如下

typedef struct _IMAGE_DATA_DIRECTORY{DWORD VirtualAddress  ;DWORDisize ;}IMAGE_DATA_DIRECTORY,PIMAGE_DATA_DIRECTORY;#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 // DataDirectory各项定义如下    #define IMAGE_DIRECTORY_ENTRY_EXPORT         0 导出表      #define IMAGE_DIRECTORY_ENTRY_IMPORT         1 导入表       #define IMAGE_DIRECTORY_ENTRY_RESOURCE       2 资源目录      #define IMAGE_DIRECTORY_ENTRY_EXCEPTION      3 异常目录      #define IMAGE_DIRECTORY_ENTRY_SECURITY       4 安全目录      #define IMAGE_DIRECTORY_ENTRY_BASERELOC          5 重定位基本表      #define IMAGE_DIRECTORY_ENTRY_DEBUG           6 调试目录      #define IMAGE_DIRECTORY_ENTRY_COPYRIGHT      7 描术字串      #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR      8 机器值      #define IMAGE_DIRECTORY_ENTRY_TLS        9 TLS目录      #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10 载入配值目录      #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT       11 绑定输入表      #define IMAGE_DIRECTORY_ENTRY_IAT        12 导入地址表      #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13 延迟载入描述      #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR     14 COM信息  15的位置预留

基本上就是这些了,一些作用不是很清楚的之后有用到在进行完善,现在之需要知道对应的字段有多少字
节就可以进行相应的字段的读取了
这里写图片描述

大概就是上面这样,从 PE 的位置 一直到 .text 的位置 都是 NT头的部分,好,NT头终于完了

接下来只要解析一下 节区头 基本的 PE 文件结构就清楚了

节区头

在winnt.h下如下定义#define IMAGE_SIZEOF_SHORT_NAME 8typedef struct _IMAGE_SECTION_HEADER {BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//节表名称,如“.text” ,节区的name仅供参考,不能保证其百分之百会用作某种信息union {DWORD PhysicalAddress;//物理地址DWORD VirtualSize;//内存中节区所占的大小} Misc;DWORD VirtualAddress;//内存中节区的起始位置 RVADWORD SizeOfRawData;//磁盘文件中街区所占的大小DWORD PointerToRawData;//磁盘文件中节区的起始位置,即想对于文件的偏移DWORD PointerToRelocations;//重定位的偏移DWORD PointerToLinenumbers;//行号表的偏移WORD NumberOfRelocations;//重定位项数目WORD NumberOfLinenumbers;//行号表的数目DWORD Characteristics;//节属性 如可读,可写,可执行等} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; 

好,基本的头信息都是可以了,现在就可以编写一个显示基本的读取头信息的程序了

。。。。最近比较忙,过几天有时间再把坑填上

完成之后,还需要继续的任务
1》地址的转换 RVA <⇒ RAW
2> 导入导出表 IAT EAT
3> 其他功能

0 0
原创粉丝点击