UEFI开发探索08 – PE/COFF

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

前几天有人问起我,对UEFI的格式文件的理解。

嗯,我基本上快把PE格式忘光光了,那是很多年前,搞反汇编,混看雪学院时搞的事情。当时花了几个月,专门学习软件加密、加壳、扰乱反汇编等等。然后,又花了几周,破解了几个dll,扔给工程师去用,就再也没有接触过了。

闲言少叙。既然忘记了,就花点时间去回忆一下吧。上午的会议开完,下午有两个小时空档,找找以前的日志和资料。

1 DOS下的exe文件

这是PE格式的起源,有必要了解一下。我曾经做过一个小程序,为了直接加载exe,自己实现加载器。这是为了在Foxdisk中,能够复用我以前的exe程序而做的。

DOS下的exe结构非常简单:

图1 DOS下EXE文件结构

DOS下的加载,分为exe文件的加载和com文件的加载。它们加载后是这样的:

A  exe文件加载
1) DS 和 ES 指向 PSP 的段地址;
2) CS 指向代码段的绝对段址;
3) SS 指向堆栈段的绝对段址;
4) IP 指向代码段入口时第一条指令的偏移地址;
5) SP 指向堆栈段如口时深度,此值由文件头位移 10H 的字域决定;
6) BX, CX 是加载程序的字节长度。

B  com文件加载
1) CS,DS,ES 和 SS 指向 PSP 的段地址;
2) IP 固定为 100H;
3) SP 位 FFFEH ,并在栈顶出压入一全 0 字;
4) BX, CX 是 COM 文件的字节长度。

需要注意的是重定向的过程,加载程序会将重定向表的段和地址偏移加上起始段和偏移的,这个很好理解。这都是在实模式下处理,非常简单。保护模式下,有非常多的信息,GDT、LDT、IDT,虚拟地址和物理地址的换算,比这个复杂多了。

另外,DOS加载的时候会在程序段之前设置一个具有256字节的信息区,比如命令行参数就存在这个里面。我们自己写加载程序就不用管这些了,我主要的目的是让程序运行起来。当然,我的程序也不会去调用int 21h之类的dos中断,否则foxdisk加载起来也无法运行。

代码放在百度云上,有兴趣可以瞧瞧:
百度云链接:https://pan.baidu.com/s/1gccSosw8_UAGTI5gZPnLCA
提取码:dx23
代码在 X1 Run Dos exe下

2 PE/Coff

我手上有微软以前发布的《Microsoft Portable Executable and Common Object File Format Specification》Revision 8.2,2010年发布。最新的版本不知道是什么,估计变化不大,也不用找了

如何去理解PE结构,我以为,最好自己写一个PE格式分析软件,或者读一读别人写的分析软件。正好,以前看过的《软件加密技术内幕》(看雪学院),提供了这么一个工具:

图2 PEInfo

PE文件一个方便的特点是磁盘上的数据结构和加载到内存中的数据结构是相同的。加载一个可执行文件到内存中 (例如,通过调用LoadLibrary)主要就是映射一个PE文件中的几个确定的区域到地址空间中。这就使得分析起来很方便,都是些机械性的动作。

PE的结构如下图。

图3 PE文件架构

开始的时候,看得我有点糊涂。我的第一个反应就是,这些到底怎么定位?我用UE以bin文件的方式打开一个exe文件,对照书上的数据结构算了很久,才有点眉目。

记下我的一些感想:
1)      MS-DOS头部的偏移0x3c处的内容,为PE文件签名的偏移地址(也即PE文件头起始);
2)      PE文件头的结构由三个字段组成:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
对照上图可以有个直观的了解;
3)      OptionalHeader最后包含了16个数据目录表,以数组方式存储,第一项和第二项分别为输出表和输入表;
4)      紧随PE文件头的是区块表,其数目在PE文件头的FileHeader中给出了。我测试的这个程序其值为6;
5)      区块表规定了各个区块的信息,包括我很关心的大小和属性。区块表后面就是各种区块了;
6)      调试符号的位置和大小也在PE文件头的FileHeader中给出了(windbg用的pdb也在此处?)。

大致就是这样的框架,其余的细节可以参照spec一点点看。

需要注意的是,在可执行文件中,有许多地方需要指定内存中的地址。比如全局变量的地址,必须定位准确。PE有一个首选载入地址,但是它有可能载入到任何进程空间,所以不能依赖这个。

由此出现了相对虚拟地址(Relative Virtual Address, RVA)概念。这是一个偏移位置,方便exe载入的时候,从基地址加上它,把相应的磁盘文件载入。

图4 Stud_PE和PEInfo测试图

我找到了一个不错的,可以查看PE文件的软件Stud_PE。可惜的是它只能支持exe和dll文件名的载入。

PEInfo没有这个限制,可以看efi的信息。什么时候改造一下,符合我自己的使用习惯。

这篇博客我不是很满意,时间只有两个小时,很多细节不够深入。最好的方法就是仿照PEInfo,自己编写有个查看efi的程序,估计理解就深刻了。以后找时间重写一次。

今天就先这样吧。

3,411 total views, 1 views today

发表评论

电子邮件地址不会被公开。