汇编语言(assembly language)是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言.在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数的地址.在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令,普遍地说,特定的汇编语言和特定的机器语言指令集是相互对应的,不同平台之间不可直接移植.

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

PE 结构概述

在操作系统中,可执行的代码在被最终装载进内存执行之前是以文件的方式存放在磁盘中的,早期DOS操作系统中,是以.com文件的格式存储的,com文件限制了只能使用代码段,堆栈寻址也被限制在了64KB的段中,这样极大的限制了软件的发展.

为了应对这种局面,出现了另一种可执行文件,那就是我们所熟悉的exe文件,exe文件在代码前面加了一个文件头,文件头中包括各种说明数据,如程序的入口地址,堆栈的位置,重定位表等,显然可执行文件的格式是操作系统工作方式的写照,不同的系统之间文件格式千差万别,从而导致不同系统中的可执行文件无法跨平台运行.

当Windows3x出现的时候,可执行文件出现了32位代码,程序运行时会转到保护模式执行,所以Windows3x使用了新的LE格式的可执行文件(Linear Executable/线性可执行文件),而在Windows NT系统中可执行文件则使用微软设计的新的文件格式,也就是现在还在使用的PE格式(Portable Executable File Format/可移植的执行体).

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

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

在PE文件中将同样属性的数据分类放在一起是为了统一描述这些数据装入内存后的页面属性,由于数据是按照属性在节中放置的,不同用途但是属性相同的数据可能被放在同一个节中,PE文件头被放置在节和节表的前面,上面介绍的是真正的PE文件,为了兼容以前的DOS系统,所以保留了DOS的文件格式,接下来将依次介绍这几种数据结构.

◆DOS头结构◆

从上面的PE结构图中,PE文件的开头部分包括了一个标准的DOS可执行文件结构,这看上去有些奇怪,但是这对于可执行文件的向下兼容性来说却是不可缺少的.

操作系统识别可执行文件的方法是按照文件格式而不是扩展名来识别的,就是因为其识别文件看的是文件格式,所以就算你将exe可执行文件改成bat,scr等其他格式,PE文件加载器依然可以识别出这是一个可执行文件,但是,如果不去兼容DOS结构,那么在DOS下运行PE文件的话,则系统一定会崩盘,为了避免这一情况的发生,PE文件的头部依然包括一个标准的DOS_MZ格式的可执行部分,这样万一在DOS下执行PE文件,那么系统会弹出一个提示This program cannot be run in DOS mode.,这样不至于崩溃.

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并不在一开始就将整个文件读入内存,而是采用与内存映射文件类似的机制,Windows会事先建立好虚拟地址和PE文件之间的映射关系,只有真正执行到某个内存页中的指令或者访问某一页中的数据时,这个页面才会被提交到内存,这种机制加快了程序的运行效率,同时使文件的装入速度与文件大小没有关系.

系统装载可执行文件并不等同于内存映射,内存映射是将整个磁盘文件原封不动的搬到内存中去,而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

参考文献:《Intel 汇编语言程序设计》,《琢石成器-Win32汇编语言程序设计》,《汇编语言-王爽》

Win32汇编-编写PE结构解析工具的更多相关文章

  1. 手写PE结构解析工具

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

  2. innodb结构解析工具---innodb_ruby

    1.下载ruby并安装ruby: ftp://ftp.ruby-lang.org/pub/ruby/ ftp://ftp.ruby-lang.org/pub/ruby/ruby-2.3-stable. ...

  3. 编写自定义PE结构的程序(如何手写一个PE,高级编译器都是编译好的PE头部,例如MASM,TASM等,NASM,FASM是低级编译器.可以自定义结构)

    正在学PE结构...感谢个位大哥的文章和资料...这里先说声谢谢 一般高级编译器都是编译好的PE头部,例如MASM,TASM等一直都说NASM,FASM是低级编译器.可以自定义结构但是苦于无人发布相关 ...

  4. Golang Json文件解析为结构体工具-json2go

    代码地址如下:http://www.demodashi.com/demo/14946.html 概述 json2go是一个基于Golang开发的轻量json文件解析.转换命令行工具,目前支持转换输出到 ...

  5. 【转】pe结构详解

    (一)基本概念 PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等, 事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是 ...

  6. 修改记事本PE结构弹计算器Shellcode

    目录 修改记事本PE结构弹计算器Shellcode 0x00 前言 0x01 添加新节 修改节数量 节表位置 添加新节表信息 0x02 添加弹计算器Shellcode 修改代码 0x03 修改入口点 ...

  7. Win32汇编环境配置

    放假了,发现自己知识面窄,趁有时间就打算折腾下Win32汇编.其实在学校也上过汇编课,是基于dos的.那时老师不务正业,老跟我们讲政治经济文化,唯独不怎么讲课;再加上自己的问题,导致了dos汇编学得好 ...

  8. PE文件解析 基础篇

    PE文件解析 基础篇 来源 https://bbs.pediy.com/thread-247114.htm 前言 之前学习了PE格式,为了更好的理解,决定写一个类似LoadPE的小工具. 编译器是VS ...

  9. 逆向-PE头解析

    目录 PE头解析 数据结构 IMAGE_DOS_HEADER IMAGE_NT_HEADERS 区块 PE头解析 PE 格式是Windows系统下组织可执行文件的格式.PE文件由文件头和对应的数据组成 ...

随机推荐

  1. 如何知道,当前redis实例是处于阻塞状态?

    随便get一个key,然后卡着不动就行,简单粗暴.优雅一点是看latency的延迟,blocked_clients的数量,rejected_connections的数量等 或者 方法一:登录 Redi ...

  2. 整个系统禁用复制功能下,js实现部分数据的复制功能

    需求背景:整个系统禁止复制,列表页操作栏新增按钮来复制数据列的手机号功能 感受下是怎么回事?看下效果 (GIF有点点烂)

  3. 使用 usb 调试的时候,连接上电脑没反应

    使用 usb 调试的时候,连接上电脑没反应 原因: 手机上没有信任本计算机的授权,请在手机上信任该授权 解决方法: 原因就是手机上会有一个弹话框,让我们信任该计算机,我们才可以进行 usb调试 我们的 ...

  4. P1115 最大子段和&P1719 最大加权矩形

    上接:DP&图论 DAY 1 上午 这两个题本质是一个亚子,所以放一起啦 DPDPDPDPDPDPDPDP P1115 最大子段和 题解 因为题目要求的是一段连续的区间,所以前缀和搞暴力??? ...

  5. excel导出显示默认格子

    <html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:o ...

  6. hibernate中session的管理方式

    package loaderman.c_session; import loaderman.b_second_cache.Dept; import loaderman.b_second_cache.E ...

  7. 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_2-7.接口配置文件自动映射到属性和实体类配置

    笔记 7.接口配置文件自动映射到属性和实体类配置     简介:使用@value注解配置文件自动映射到属性和实体类 1.添加 @Component或者Configuration 注解:        ...

  8. Java排序方法

    下面是用JAVA代码实现的数据结构中的7种基本排序算法,希望对你有所帮助. (1)直接插入排序 /** 直接插入排序 **/ /** 数组是引用类型,元素值将被改变 **/ public static ...

  9. Linux命令集锦:scp命令

    scp命令用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的,可能会稍微影响一下速度.当你服务器硬盘变为只读 read on ...

  10. Spring Boot 2.0 集成 Druid 数据源

    一.Maven项目依赖 <!-- 开发者工具(热部署 修改classpath下的文件springboot自动重启) --> <dependency> <groupId&g ...