PE格式是Windows系统下最常用的可执行文件格式,有些应用必须建立在了解PE文件格式的基础之上,如可执行文件的加密与解密,文件型病毒的查杀等,熟练掌握PE文件结构,有助于软件的分析.

在PE文件中,代码,已初始化的数据,资源和重定位信息等数据被按照属性分类放到不同的Section(节区/或简称为节)中,而每个节区的属性和位置等信息用一个IMAGE_SECTION_HEADER结构来描述,所有的IMAGE_SECTION_HEADER结构组成了一个节表(Section Table),节表数据在PE文件中被放在所有节数据的前面.

在Win32系统中,当我们执行了可执行文件之后,可执行文件会被映射到内存,并且以4kb的粒度进行对齐,这个4kb也就是一个页面的大小,而每个页面又分别具有,可执行,可读写等属性.

PE格式中的DOS部分由MZ格式的文件头和可执行代码部分组成,可执行代码被称为DOS块(DOS stub).MZ格式的文件头由IMAGE_DOS_HEADER结构定义,以下就是DOS头部分的关键属性.

		mov esi,lpMemory
assume esi:ptr IMAGE_DOS_HEADER
movzx eax,[esi].e_magic ; 读取DOS的头部
movzx eax,[esi].e_ss ; DOS代码段的初始堆栈段
movzx eax,[esi].e_sp ; DOS代码段的初始堆栈指针
movzx eax,[esi].e_cs ; DOS代码的入口地址
movzx eax,[esi].e_ip ; DOS代码的入口IP
movzx eax,[esi].e_lfanew ; 指向了PE文件的开头(重要)

第一个字段e_magic被定义为MZ,标志着DOS文件的开头部分,最后一个字段e_lfanew则指明了PE文件的开头位置,现在来说除了第一个字段和最后一个字段有些用处,其他的字段几乎已经废弃了,这里也不再介绍了.

解析PE头结构

从DOS文件头的e_lfanew字段(文件头偏移003ch),PE文件格式排列在DOS头的后面,也就是e_lfanew指针所指向的地址,而PE文件的第一个字节就是PE这两个字符,有了这些信息,我们就可以写一个小工具,来检测指定一个程序是否是可执行文件啦.

.data
szFileName db "lyshark.exe",0h
hFile dd ?
hMapFile dd ?
lpMemory dd ?
szText db "这是一个PE可执行文件 !",0h
.code
main PROC
; 打开文件,并创建内存映射镜像
invoke CreateFile,addr szFileName,GENERIC_READ,FILE_SHARE_READ or \
FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL
mov hFile,eax
invoke CreateFileMapping,hFile,NULL,PAGE_READONLY,0,0,NULL
mov hMapFile,eax
invoke MapViewOfFile,eax,FILE_MAP_READ,0,0,0
mov lpMemory,eax
; -------------------------------------------------------------------
; 检测PE文件是否有效,是否是一个正常的PE
mov esi,lpMemory
assume esi:ptr IMAGE_DOS_HEADER
; 判断是否为DOS文件头部
.if [esi].e_magic == IMAGE_DOS_SIGNATURE
add esi,[esi].e_lfanew ; 递增指针
assume esi:ptr IMAGE_NT_HEADERS
; 判断是否为PE可执行文件
.if [esi].Signature == IMAGE_NT_SIGNATURE
invoke MessageBox,NULL,addr szText,0,MB_OK
.endif
.endif
; -------------------------------------------------------------------
invoke UnmapViewOfFile,addr lpMemory
invoke ExitProcess,NULL
main ENDP
END main

上面的核心代码原理也非常的简单,过程:读入文件,判断第一个字符是不是MZ,如果是MZ,则在判断e_lfanew指针指向的地址是不是PE如果是,则说明这是PE文件.

解析各区块信息

下面的代码,则用于读取PE文件的一些关键区块信息.

	.386
.model flat,stdcall
option casemap:none include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include masm32.inc
includelib masm32.lib .data
szFileName db "lyshark.exe",0h
hFile dd ?
hMapFile dd ?
lpMemory dd ?
lpBuffer db 2048 dup(?)
.const
szMsg db "----------------------------------------",0dh,0ah
db "运行平台: 0x%04X",0dh,0ah
db "节区数量: %d",0dh,0ah
db "文件属性: 0x%04X",0dh,0ah
db "时间标记: %d",0dh,0ah
db "镜像装入基址: 0x%08X",0dh,0ah
db "程序的入口RVA: 0x%08X",0dh,0ah
db "代码节起始RVA: 0x%08X",0dh,0ah
db "数据节起始RVA: 0x%08X",0dh,0ah
db "----------------------------------------",0dh,0ah,0
.code
main PROC
; 打开文件,并创建内存映射镜像
invoke CreateFile,addr szFileName,GENERIC_READ,FILE_SHARE_READ or \
FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL
mov hFile,eax
invoke CreateFileMapping,hFile,NULL,PAGE_READONLY,0,0,NULL
mov hMapFile,eax
invoke MapViewOfFile,eax,FILE_MAP_READ,0,0,0
mov lpMemory,eax
mov esi,lpMemory assume esi:ptr IMAGE_DOS_HEADER
add esi,[esi].e_lfanew
assume esi:ptr IMAGE_NT_HEADERS
invoke wsprintf,addr lpBuffer,addr szMsg,\
[esi].FileHeader.Machine, \ ; 运行平台
[esi].FileHeader.NumberOfSections, \ ; 节区数目
[esi].FileHeader.Characteristics, \ ; 文件属性
[esi].FileHeader.TimeDateStamp, \ ; 时间标记
[esi].OptionalHeader.ImageBase, \ ; 镜像基址
[esi].OptionalHeader.AddressOfEntryPoint, \ ; 入口RVA地址
[esi].OptionalHeader.BaseOfCode, \ ; 代码节起始RVA
[esi].OptionalHeader.BaseOfData
invoke StdOut,addr lpBuffer
invoke ExitProcess,NULL
main ENDP
END main

解析节与节表

系统装载可执行文件并不等同于内存映射,内存映射是将整个磁盘文件原封不动的搬到内存中去,而PE的加载则会处理一些其他数据,例如预处理,重定位等,装入以后页面位置,偏移等都会随之发生改变,Windows装载器在装载DOS部分,PE文件头部分和节表部分时不进行任何处理,而装载节的时候将根据节的属性做不同的处理.

	.386
.model flat,stdcall
option casemap:none include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include masm32.inc
includelib masm32.lib .data
szFileName db "lyshark.exe",0h
hFile dd ?
hMapFile dd ?
lpMemory dd ?
lpBuffer db 2048 dup(?)
.const
szMsg db "----------------------------------------------------------",0dh,0ah
db "节区名称 节区大小 虚拟地址 Raw_尺寸 Raw_偏移 节区属性",0dh,0ah
db "----------------------------------------------------------",0dh,0ah,0
szFmt db "%s %08X %08X %08X %08X %08X",0dh,0ah,0
.code
main PROC
invoke CreateFile,addr szFileName,GENERIC_READ,FILE_SHARE_READ or \
FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL
mov hFile,eax
invoke CreateFileMapping,hFile,NULL,PAGE_READONLY,0,0,NULL
mov hMapFile,eax
invoke MapViewOfFile,eax,FILE_MAP_READ,0,0,0
mov lpMemory,eax
mov esi,lpMemory assume esi:ptr IMAGE_DOS_HEADER ; 指向DOS开头
add esi,[esi].e_lfanew ; 递增指针到PE结构开头
assume esi:ptr IMAGE_NT_HEADERS invoke StdOut,addr szMsg ; 输出提示信息
movzx ecx,[esi].FileHeader.NumberOfSections ; 取出节的数量,作为循环条件
add esi,sizeof IMAGE_NT_HEADERS ; 指向.text节
assume esi:ptr IMAGE_SECTION_HEADER ; 指向节中的SECTION
.repeat
push ecx ; wsprintf影响ecx寄存器,所以这里必须压栈保存数据
mov eax,[esi].VirtualAddress invoke wsprintf,addr lpBuffer,addr szFmt,esi, \ ; 节区名称
[esi].Misc.VirtualSize, \ ; 节区大小
[esi].VirtualAddress, \ ; 虚拟地址
[esi].SizeOfRawData, \ ; Raw_尺寸
[esi].PointerToRawData, \ ; Raw_偏移
[esi].Characteristics ; 节区属性
invoke StdOut,addr lpBuffer ; 打印节区信息
pop ecx
add esi,sizeof IMAGE_SECTION_HEADER
.untilcxz
invoke ExitProcess,NULL
main ENDP
END main

Win32汇编:汇编版PE结构解析器的更多相关文章

  1. Win32汇编-编写PE结构解析工具

    汇编语言(assembly language)是一种用于电子计算机.微处理器.微控制器或其他可编程器件的低级语言,亦称为符号语言.在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地 ...

  2. 手写PE结构解析工具

    PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如 ...

  3. C++PE文件格式解析类(轻松制作自己的PE文件解析器)

    PE是Portable Executable File Format(可移植的运行体)简写,它是眼下Windows平台上的主流可运行文件格式. PE文件里包括的内容非常多,详细我就不在这解释了,有兴趣 ...

  4. PE文件解析器的编写(二)——PE文件头的解析

    之前在学习PE文件格式的时候,是通过自己查看各个结构,自己一步步计算各个成员在结构中的偏移,然后在计算出其在文件中的偏移,从而找到各个结构的值,但是在使用C语言编写这个工具的时候,就比这个方便的多,只 ...

  5. 自制C#版3DS文件的解析器并用SharpGL显示3DS模型

    自制C#版3DS文件的解析器并用SharpGL显示3DS模型 我已经重写了3ds解析器,详情在此(http://www.cnblogs.com/bitzhuwei/p/CSharpGL-2-parse ...

  6. PE解析器的编写(四)——数据目录表的解析

    在PE结构中最重要的就是区块表和数据目录表,上节已经说明了如何解析区块表,下面就是数据目录表,在数据目录表中一般只关心导入表,导出表和资源这几个部分,但是资源实在是太复杂了,而且在一般的病毒木马中也不 ...

  7. PE解析器的编写(一)——总体说明

    之前自己学习了PE文件的格式,后来自己写了个PE文件的解析器,这段时间工作上刚好要用到它,老板需要能查看某个exe中加载的dll的一个工具,我在使用之前自己写的这个东西的时候,发现很多东西都忘记了,所 ...

  8. PE解析器与加载器编写指南

    PE解析器与加载器编写指南 最近准备去实习,看公司要求应该开发PE相关的查杀引擎,因此再回头复习一下PE格式,重新写一个PE解析器和PE加载器,再此记录下有关坑. PE解析器部分: 1)如何确定节区表 ...

  9. Xamarin版的C# SVG路径解析器

    原文:Xamarin版的C# SVG路径解析器 Xamarin版的C# SVG路径解析器,对SVG的Path路径进行解析,其中包括: 主程序SvgPathParser.cs, 相关接口定义:ISour ...

  10. PE头结构解析(代码实现)

    PE头结构解析(代码实现) 图表实现在:https://www.cnblogs.com/juicyhumberger/articles/17064764.html #include "std ...

随机推荐

  1. nginx安装 没有网络且缺少基础包的环境下

    一.安装 [root@oracle ~]# cd /etc/yum.repos.d/ [root@oracle yum.repos.d]# rm -rf * [root@oracle yum.repo ...

  2. JS上下文和作用域链

    开发中我们可能会不小心将写多个相同名称的变量,也经常会写一个递归调用的方法, 上述示例中程序执行顺序如下图,程序会按照顺序执行第一个子元素内部所有的程序,当最底层执行结束后,会逐渐抛出返回值,然后执行 ...

  3. 编写Java代码时应该避免的6个坑

    通常情况下,我们都希望我们的代码是高效和兼容的,但是实际情况下代码中常常含有一些隐藏的坑,只有等出现异常时我们才会去解决它.本文是一篇比较简短的文章,列出了开发人员在编写 Java 程序时常犯的错误, ...

  4. docker容器常用操作

    1.查看运行容器 docker ps:  2.查看所有容器 docker ps -a:  3.查看容器的日志 docker logs 容器名称/容器ID: 4.运行镜像 docker run --na ...

  5. 有n个整数,使前面各数顺序向后m个位置,最后m个数变成最前面m个数,见图 8.43。写一函数实现以上功能在主函数中输入个整数和输出调整后的n个数。

    4,有n个整数,使前面各数顺序向后m个位置,最后m个数变成最前面m个数,见图 8.43.写一函数实现以上功能在主函数中输入个整数和输出调整后的n个数. 我的代码: 1.使用双向链表 void Move ...

  6. 【TouchGFX】visual studio 工程中 SIMULATOR 宏定义位置

  7. [转帖]SQL Server数据库重建索引、更新统计信息

    https://vip.kingdee.com/article/183932?productLineId=8 SQL Server数据库有时由于长期未做索引重建,导致SQL执行效率下降,当表的索引碎片 ...

  8. [转帖]Oracle参数解析(parallel_force_local)

    https://www.modb.pro/db/122032 是否需要增加这个参数? 往期专题请查看www.zhaibibei.cn这是一个坚持Oracle,Python,MySQL原创内容的公众号 ...

  9. [转帖]记druid 连接池没满,但超时问题 GetConnectionTimeoutException active 5, maxActive 100

    记druid 连接池没满,但超时问题 GetConnectionTimeoutException active 5, maxActive 100 问题说明 线上服务突然出现报错,通过日志查找发现是因为 ...

  10. Mysql8.0.32 union all创建视图无法使用中文模糊查询的坑

    Mysql8.0.32 union all创建视图无法使用中文模糊查询的坑 摘要 本周研发同事反馈现场有一个问题. 客户使用mysql的数据库(Windows平台) 然后在多表union all 创建 ...