目录
三个小工具的编写
这三个小工具分别是:
口 PEDump: PE 文件字节码查看器
口 PEComp: PE 文件比较器
口 PEInfo: PE 文件结构查看器
构造基本窗口程序:
本节我们将构造一个具有基本窗口元素(含标题栏、菜单栏、工作区域)的窗口程序,后续大部分的程序开发都将以这个基本窗口程序作为基础进行扩展。
构造窗口界面:
要构造的窗口程序具备窗口图形界面的大部分元素,包含窗口、菜单、图标、工作区域等。
通常的做法是:
首先,根据程序功能对程序的界面进行构思; 然后,在纸上画出大致的结构图 , 最后,通过资源脚本来定义并实现界面中的每一部分。当然,读者也可以使用一些辅助的软件(如 RADAsm 中的资源编辑器,或者 VS 中的资源编辑器)根据构思好的界面,在所见即所得的资源编辑器图形界面中直接构造程序窗口界面。
该程序最终显示的效果如图 2-1 所示:
编写相关的资源文件:
构造完窗口界面以后,需要依据界面编写对应的资源文件,一般以 “.rc” 为扩展名; 资源文件编写完成后,还必须通过资源编译器对资源文件实施编译,以生成资源目标文件。资源文件是文本文件,由定义资源的一些脚本语句组成,可以使用文本编辑软件(如记事本)查看和修改。资源目标文件是对这些脚本的一种再组织,根据脚本描述将脚本涉及的所有资源编译到一起,形成二进制字节码。资源目标文件无法通过文本编辑软件查看。
整个过程分为两个阶段:
口 创建资源文件 pe.rc
口 生成资源目标文件 pe.res
1. 创建资源文件 pe.rc:
在编写资源文件时,需要定义图形中出现的所有菜单项、对话框、图标等。资源文件的详细编码如代码清单 2-1 所示。
2. 生成资源目标文件 pe.res:
接下来编译资源文件,生成资源目标文件(扩展名为 res)。在命令提示符下输入以下命令:
如果执行编译时没有错误发生(如资源脚本中定义的相关文件不存在就会抛出错误提示),则命令执行后会在当前目录下生成资源目标文件 pe.res。该资源目标文件最终要被链接程序嵌入到 PE 文件中,构成 PE 资源表所描述的数据的一部分。
通用程序框架的实现:
资源目标文件生成以后,接下来的工作就是实现通用程序框架。主要分为三个阶段:
口 编写源程序 pe.asm
口 编译生成目标文件 pe.obj
口 链接生成可执行文件 pe.exe
1. 编写源程序 pe.asm:
代码清单 2-2 中的第 98 行通过调用 DialogBoxParam 函数创建了一个弹出式窗口作为整个程序的主窗口,并将内部函数 _ProcDlgMain 的地址当成该函数的参数之一传入该函数。
函数 _ProcDlgMain 是弹出窗口的回调函数,如果要对发生在窗口中的消息进行捕获,则需要在此函数中设置对不同的消息进行响应的代码。
由于本实例只是一个基本程序框架,所以回调函数只对菜单中的 “退出” 选项做了响应,如下所示:
2. 编译生成目标文件 pe.obj
在编写较大的程序时,通常会根据功能将源代码分别写到不同的文件里。有时为了分工合作,同一个项目中还会出现使用不同语言编写的源代码。这些源代码文件都需要在各自独立的环境中被编译成各自的目标文件。目标文件符合通用对象文件格式 (COFF),该格式的定制主要是为了方便混合编程。生成目标文件的过程是处理源代码中可能会出现错误 (如引入外部符号错误、源代码语法错误等)的过程,生成的目标文件最终会被链接程序拼接到最终的可执行文件中,当然,除了编译源代码生成的目标文件外,可执行文件还包含资源目标文件、外部引入的符号等信息。
在命令提示符下输入以下命令,编译源文件 pe.asm:
如果设有错误,则会在当前目录下生成目标文件 pe.obj 。
3. 链接生成可执行文件 pe.exe
上述命令指定了最终生成的 EXE 文件的运行平台为 Windows,链接程序将根据 pe.obj 中的描述构造 PE 文件,并将相关资源内容附加到 PE 文件里,最终生成可执行的 pe.exe。
在命令提示符下输入“pe”,然后回车,即可看到最终的运行效果,总图如下:
PEDump 的实现:
PEDump 是 PE 文件字节码查看器,利用它可以查看和阅读指定 PE 文件的十六进制字节码,帮助我们更好地分析 PE 结构。
编程思路:
编写PEDump 的重点在于显示功能,首先来看一看最终可能的输出效果,如图 2-2 所示:
如图所示,最终输出包含三列内容:
第一列:是地址,地址的值是第 (n*16+1) 个字节在文件中的位置。
第二列:是由空格分隔符分隔的 16 个字节的十六进制显示。
第三列:是这 16 个字节对应的 ASCII 码值。如果 ASCII 码中无对应值,或者这些值是一些功能键,则以 “.” 代替。
注意:有的查看器 (如 FlexHex ) 还增加了 Unicode 字符一列,用来显示字节码中包含的 Unicode 字符。
编写流程如下所示:
编程时要考虑到最后一行可能会少于 16 个字节,这时候第二列和第三列不足的地方就可以使用空格补足。
步骤1:打开 PE 文件。需要说明的是,打开的 PE 文件会被映射为内存文件。因为内存文件中的内容是线性存放的,存取方便,速度也快,并且操作起来比在文件中使用指针定位要更容易些。
步骤2:使用 API 函数 GetFileSize 得到该 PE 文件的大小。
步骤3一5:将第2步获取的值与 16 相除,商作为循环计数,余数则是字节码查看器最后一行的字节个数。在程序中构造一个循环,用来显示 PE 文件除最后一行外其他行的字节内容。
PEDump 代码中的数据结构:
为了帮助大家更好地阅读 PEDump 的实现代码,在此分别列出本程序中用到的全局变量和局部变量:
(1) 程序中用到的全局变量:
(2) 程序中用到的局部变量:
PEDump 编码:
前面简单了解了程序的开发流程,接下来进入编码阶段。此处将会用到 2.1 节中的源程序文件 pe.asm。
PEDump.asm 在 pe.asm 的基础上增加了对菜单项 IDM_OPEN 的响应代码,如下所示:
函数 _openFile 的实现如代码清单 2-3 所示:
第 50 ~177 行是对内存映像文件的处理过程。这个过程如果太复杂,可以继续使用子程序 ,如果不是很复杂,则可以直接在此编写处理代码。
十六进制字节码查看器的主要代码如下:
完整的源代码请查看随书文件 chapter2\pedump.asm。在该部分代码中,每取一个字节,都会将其 ASCII 码的值写入 bufDisplay。如果字节的值在 20h 和 7eh 之间,则显示相应的 ASCIl 码,否则显示 “.” 。每计数 16 个字节,就会重新初始化 bufDisplay 。
每取一个字节,都会将该字节的十六进制字符表示形式加上后面的空格作为一个完整单位,附加到 lpServicesBuffer。每计数 16 个字节,就会将 lpServicesBuffer 中存放的完整的一行内容写入到富文本框中。
运行PEDump:
打开命令提示符窗口,在 D:\masm32\source\chapter2 目录下执行如下命令:
口 rc -r pedump.rc (编译资源脚本文件)
口 ml -c -coff pedump.asm (编译 PEDump.asm)
口 link -subsystem:windows pedump.res pedump.obj (链接生成可执行程序)
运行PEDump.exe,最终效果如图 2-4 所示:
假死现象的处理:
在测试多个打开的 PE 文件后你会发现程序存在一个问题,当程序在打开比较大的 PE 文件显示字节码时界面会发生假死,这是由于程序主线程的循环造成了系统消息堵塞,从而无法完成界面更新所致。要解决这个问题,应该把事件响应代码 _openFile 单独放到一个开启的线程中执行。
处理方法如下,将以下菜单响应代码:
前面说过 PEDump.asm 在 pe.asm 的基础上增加了对菜单项 IDM_OPEN 的响应代码
更改为:
为了能随时终止滚动显示,可以在主程序中增加一个标志字节,然后在查看菜单中增加一个 “停止 dump...” 菜单选项:
加了资源后根据对应的 ID 来响应事件,该选项的响应代码如下
线程函数 _openFile 的循环中也有对应的检测 dwStope的代码,如下所示:
经过如上设计,在程序显示字节码的过程中,任意拖拽运行窗口都不会出现界面假死现象。同时,用户也可以通过菜单项 "查看" | "停止 Dump..." 随时终止字节码的显示,退出循环。
PEComp 的实现:
PEComp 是 PE 文件比较器。功能是按照 PE 文件格式的数据结构按字段对两个指定的 PE 文件进行比对,以获取两个 PE 文件结构中的不相同的信息。在实际应用中,我们可以通过对比病毒感染前后 PE 文件在相关字段上发生的变化来判断病毒的感染方式,从而确定清理病毒的方法。
编程思路:
PEComp 的功能是在通用框架 pe.asm 的基础上,实现两个 PE 文件的对比,并通过图形界面将不同之处形象地表示出来。
编码的大致思路如下:
步骤1:打开要比较的两个文件,分别进行文件的内存映射,获取内存起始地址。
步骤2:线性搜索,根据文件头部内容确定该文件是否为 PE 文件,不是则退出。
步骤3:将 esi 指向要操作的第一个文件的相关字段处,将 edi 指向第二个要操作的文件的相同字段处,同时获取该位置的指定个数的字节到内存,比较并显示,如果不同,则显示时使用红色背景以示区别。
定义资源文件:
将 2.1.2 节中的资源文件 pe.rc 复制到 pecomp.rc,并在 pecomp.rc 文件中增加一个对话框,该对话框中包括用户选择的参与对比的两个 PE 文件文本框 ID_TEXT1 和 ID_TEXT2、两个浏览按钮、一个显示结果用的表格 IDC_MODULETABLE 和一个执行按钮。
增加的对话框脚本定义如下所示:
PEComp 编码:
复制 pe.asm 到 PEComp.asm,并从代码中的窗口回调函数的菜单项响应部分开始编码。
1. 菜单项响应代码
在主窗口的回调函数中,定义对鼠标点击菜单项 “文件” |“打开” 所引发的消息处理程序,添加如下代码:
当用户选择了“打开”菜单选项时,会弹出资源文件里定义的对话框 RESULT_MODULE。通过定义该对话框的回调函数,可以处理对话框控件发出的消息。
该对话框的回调函数是 _resultProcMain,其代码如代码清单 2-4 所示:
2. _openFile 函数:
_openFile 函数的功能是把两个 PE 文件数据结构中的相关字段的值取出,分别放到表格的第 1 列和第 2 列,判断两个值是否相等的代码在回调函数 _resultProcMain 中已经给出。
与前面的思路一样,程序还是使用了内存映射函数来操作参与对比的两个 PE 文件,所不同的是这里需要定义两个指针。一个指针指向第一个文件的内存映射函数的起始位置,另一个指针指向第二个文件的内存映射函数的起始位置。假设该工作已经完成,接下来就是把两个PE 文件按照第 3 章里描述的所有字段的值取出来显示到表格中。
完成该功能的代码如代码清单 2-5 所示:
-
Header1 函数
_Header1 函数完成了 DOS 头部分的字段比较,此部分的详细代码如代码清单 2-6 所示:
DOS 头结构中字段 e_lpanew 的处理在第 59 一61行。首先,将 esi 和 edi 指向该字段所在内存位置; 然后调用 _addLine 函数将两个 PE 文件对应字段的值加入到表格中。
invoke _addLine,paral,para2,para3,Para4
_addLine 的参数摘述如下:
-
_addLine 函数:
该函数完成了在表格中增加一行的操作,具体定义如代码清单 2-7 所示。
显示字段值的同时,会在销息处理函数中调用字节比对函数 _MemCmp 以确定值是否相同,如果不相同则将表格行的背景色设置为红色以示区别。其他字段的处理方式与字段 IMAGE_DOS_HEADER.e_lpanew 的处理方式类似,不再一一陈述。
运行 PEComp:
编译资源文件 PEComp.rc,编译链接 PEComp.asm 生成最终的 PEComp.exe 程序;将随书文件中目录 chapter2 下的两个测试用文件 peinfoNorbin 和 peinfoVirbin 重命名,其扩展名都改为 exe,然后运行 PEComp.exe 程序。
注意:请不要运行以上两个文件,因为 peinfoVir.exe 是个病毒文件。测试完以后请将扩展名改回原来的“bin”。
通过对比可以看出,在两个 PE 文件的文件头部结构中,字段不相同的部分来自最后一个节的描述。可以初步断定,该病毒程序是通过修改正常程序的最后一个节的相关字段的值来实现病毒代码携带的,应该是在字节码中直接写入机器指令码。
PElnfo 的实现:
PEJnfo 是 PE 文件结构查看器,它将 PE 中的字节码以形象的描述语言显示出来,塑造一个整体的 PE 形象。通过编写 PEInfo,可以锻炼我们使用数据结构定位特定 PE 信息的能力。
编程思路:
步骤1:
打开文件,判断是否为PE 文件。判断方法非常简单,首先查看 IMAGE_DOS_HEADER.e_magic 字段,然后查看 IMAGE_NT_HEADER.Signature 字段 , 如果符合 PE 文件定义,则视为合法 PE 文件。事实上,操作系统在装载 PE 文件时,对 PE 文件的检测远比此方法复杂得多。
步骤2:
将指针定位到相关数据结构,获取字段内容并以更人性化的方式显示相关内容。
PElnfo 编码:
编写 PEInfo 不需要额外的资源文件,复制一份 pe.rc 到 PEInfo.rc 即可,源代码依然来自 pe.asm。与 pe.asm 不同的是,我们需要在 PEInfo.asm 的窗口回调函数中,为菜单项 “文件” | “打开” 的消息响应代码加入调用 _openFile 函数的代码。
如下所示:
1._openFile 函数:
_openFile 函数完成了显示 PE 结构的所有功能,该部分代码如代码清单 2-8 所示:
2. _getMainlnfo 函数:
该函数接收三个参数:
该函数获取 PE 文件的头部信息并显示:
运行 PElnfo:
编译链接生成 PEInfo.exe,然后运行。用该程序打开第 1 章生成的 HelloWorld.exe 程序,运行效果如图:
小结:
本章主要学习了如何通过汇编语言来编写基于 PE 操作的三个小工具,在后面对 PE 文件的分析中会经常使用这三个小工具。后面会讲到如何利用 PEInfo 遍历 PE 文件的导入表和导出表,那时还会用到本章的源代码。大家也可以对这些小工具进行扩展或整合,编写属于自己的 PE 分析工具。
值得一提的是,随编译器分发的可执行程序中有一个基于命令行的PE 文件结构分析工具 dumpbin.exe,它是公认的最好的 PE 分析工具之一,如果你喜欢,也可以用它来代替我们的小程序。