zoukankan      html  css  js  c++  java
  • Win32汇编-编写PE结构解析工具

    汇编语言(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汇编语言程序设计》,《汇编语言-王爽》

  • 相关阅读:
    设置材质球的材质,是第几个
    转载渲染。
    系统的时间调不错,就是界面躁动太多,要是允许话还是在自己的界面中加入比较薄, 不过这个很方便。
    清除poly修改器的脚本,效果还好。
    不用string了用getFilenameFile 函数 索引名字更快
    判断平pickbutton 节点是否被删除, 这个事件放在点击事件之内。
    字符串加入到数组的号办法。
    收集每个mesh 面的id 号, 这个很有用,可以用来查找物体共有几个id 效果好。
    Evervolv android 源码编译
    zoj 2112 Dynamic Rankings(SBT in SegTree)
  • 原文地址:https://www.cnblogs.com/LyShark/p/11136342.html
Copyright © 2011-2022 走看看