汇编语言(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. impala 四舍五入后转换成string后又变成一个double的数值解决(除不尽的情况)

    impala 四舍五入后转换成string后又变成一个double的数值解决(除不尽的情况)例如Query: select cast(round(2 / 3, 4)*100 as string)+-- ...

  2. Error creating bean with name 'persistenceExceptionTranslationPostProcessor' defined in class path resource [org/springframework/boot/autoconfigure/dao/PersistenceExceptionTranslationAutoConfiguration

    dubbo 包和SpringBoot 冲突,注释就可以正常启动

  3. Java-Unsafe

    Unsafe 是 sun.misc 包下的一个类,可以直接操作堆外内存,可以随意查看及修改 JVM 中运行时的数据,使 Java 语言拥有了类似 C 语言指针一样操作内存空间的能力. Unsafe 的 ...

  4. [Java]简单计算下一段Java代码段运行了多少秒

    long startTime = System.currentTimeMillis(); ...... long endTime = System.currentTimeMillis(); logge ...

  5. springboot 获取控制器参数的几种方式

    这里介绍springboot 获取控制器参数有四种方式 1.无注解下获取参数 2.使用@RequestParam获取参数 3.传递数组 4.通过URL传递参数 无注解下获取参数无注解下获取参数,需要控 ...

  6. 【Makefile】Makefile中的常用函数简介

    1. subst函数 格式:$(subst <from>, <to>, <text>)功能:把字串<text>中的<from>字符串替换成& ...

  7. Springboot将mybatis替换为mybatis-plus

    知识点: 1.Mybatis-plus相比mybatis,功能更加强大,简而言之,不需要我们去写mapper.xml配置,但是对于特殊需求的sql语句,还是需要写mapper.xml文件中的sql语句 ...

  8. itchat库微信自动回复祝福语

    过年了,之前看到一些python文章介绍用itchat自动回复微信,我自己就写了一个. 官方文档https://itchat.readthedocs.io/zh/latest/,这个库挺简洁的,对着接 ...

  9. MySQLdb User's Guide

    MySQLdb MySQLdb-1.2.2 API documentation http://mysql-python.sourceforge.net/MySQLdb-1.2.2/ MySQLdb U ...

  10. centos6 安装tensorflow

    1.升级python2.6.6 至 python2.7.12+ 升级时./configure --prefix=/usr/local/python27 --enable-unicode=ucs4 2. ...