自制简单的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> 其他功能
- 自制简单的pe信息查看器
- 取简单PE信息
- 32位PE文件信息查看器(WIN32控制台)
- 自制一个简单的音乐播放器
- PE学习(五)导出表,编写DLL及查看DLL的导出信息
- 系统启动不了的情况下用PE查看网络IP配置信息(Windows系列系统)
- 修改PE文件版本信息(简单演示)
- cutePE:自己写的PE文件结构查看器
- 自制控件的简单例程
- 自制简单的Lambert光照
- java 自制类加载器的简单实现
- 查看电脑硬件信息很简单
- 系统信息查看简单脚本
- 获取PE文件信息的封装
- 实验: 得PE文件的版本信息
- 获取PE文件信息的封装
- 判断PE文件的数字签名信息
- 判断PE文件的数字签名信息
- 51nod-1785:数据流中的算法
- 搜索衬线字体和无衬线字体的区别
- 文章标题
- matlab错误:Variable 'a' cannot be saved to a MAT-file whose version is older than 7.3.
- [kernel] Linux 4.10.0+ 内核编译(Mac10.12+VM+Ubuntu16)
- 自制简单的pe信息查看器
- 斐波那契数列
- OpenCV角点检测器测试和比较
- 九度OJ-1042:Coincidence(最长公共子序列)
- Eclipse-Eclispe导入正确工程后出现xml等文件报错
- jvm系列四:JVM监测&工具
- 利用线性拟合模型发现测试环境性能隐患
- 插入Mysql字段,数据长度过长
- [AHK]探测关闭脚本窗口事件