MiVerifyImageHeader是一个较小的函数。不过,恰恰由于它的较简单,是一个很好的示范,教会我们怎么从逆向的角度获取新知:大部人学习PE文件格式,是根据公开的文档以及头文件winnt.h中所提供的相关结构体。然而,PE中的有些字段,可能只是微软预留的位置,在当下操作系统中并没有实际作用。而随着微软操作系统的更新,有些字段是否检验,在不同版本(如WIN XP与WIN 7)策略是不同的。文档不可能完全跟得上操作系统的变更,winnt.h中的结构体也没法告诉我们哪些字段有意义哪些是预留占位的。 而逆向相关代码,可以得到关于PE校验的最可靠答案。 如前所述,MiVerifyImageHeader用于完成PE文件头的解析及校验工作,MiVerifyImageHeader的函数原型如下:
第一个传出参数,指向一个IMAGE_INFO结构体,它在MiVerifyImageHeader校验完成后,把后续还会使用的PE头字段,保存起来并传出。它的格式在微软提供的头文件中是找不到的,不过借助于IDA及分析相关代码,我们可以还原得到它的结构: 各字段的意义从字段名上很容易猜到,就不再多说明。 MiVerifyImageHeader的三个传入参数也很容易理解:指向IMAGE_NT_HEADER的指针、指向IMAGE_DOS_HEADER的指针以及NT头的大小。 其中pDosHeader与ntHeaderSize是对于NE格式(Win3.x 所引入的16位可执行文件格式,我们将不做讨论。 整个MiVerifyImageHeader的流程如下: 在函数的开始,会对第二个参数(pNtHeader)进行检验,若该参数所指向地址不是以4字节对齐的话,则直接返回错误码、结束流程: 接着,会检查NT头在Signature字段(以上代码中的cmp ‘EP’),以检测是否是PE文件,如上所述,NE文件及非PE文件不在我们讨论范围。我们直接看流程为’PE’后所做的事情。 首先,是对NtHeader中的FileHeader相关字段进行校验: 不过,对FileHeader的校验,并不全在在MiVerifyImageHeader中完成,结合静态分析及文件修改、试错。我们得出了FileHeader的必检字段,大家可在附件的头文件中下载: 接着,会校验OptionalHeader中的Magic字段,它决定着之后对IMAGE_INFO的填充方式,字段值为0x20B时,对应着64位的格式,这里我们只讨论64位的格式的填充。 对于IMAGE_INFO中非数据目录相关的字段,MiVerifyImageHeader简单地将NT头中对应的字段填充上去: 而对于数据目录字段,则是受NumberOfRvaAndSizes决定的: 因为我们知道,在Winnt.h的IMAGE_OPTIONAL_HEADER64结构体中,有一个数组成员,称为数据目录(DataDirectory)。IMAGE_NUMBEROF_DIRECTORY_ENTIRES的值为16:这个数据目录就是我们学习PE结构体据悉的各种“表”,如导入表、导出表等等。各个表所对应的序号在winnt.h头中也查得到: 但是,我们也知道,一个PE文件,并不一定要有这所有的表项才能正常运行。那哪些表项是必须的,哪些是可选的呢,以及操作系统怎么去判断一个PE文件里有几个表呢? 事实上,从代码中可以看出,MiVerifyImageHeader通过判断NumberofRvaAndSizes这个字段,来判断某些可选项数据表项是否存在。比如,若NumberofRvaAndSizes的值大于6,则说明Debug Directory一定存在(它对应的宏IMAGE_DIRECTORY_ENTRY_DEBUG的值为6),因此,会置IMAGE_INFO的相关字段(isHaveEntryDebug)为true。 IMAGE_INFO中的其它与数据目录有关的字段也是同样的道理,大家看附件中的IDA文件即可知,在此不再赘述。 这些在IMAGE_INFO中的数据,在MiVerifyImageHeader调用返回后还会被系统用到。 在填充完IMAGE_INFO结构体之后,函数返回前,MiVerifyImageHeader还会检查文件对齐值、内存对齐值、镜像大小等进行相应的检测: 文件对齐值、内存对齐值相关检测: 检测文件对齐值是否0x200的倍数,不是则检测是否和内存对齐值相等,不是0x200的倍数且和内存对齐值不相等则出错返回。 检查内存对齐值和文件对齐值是否2的倍数,不是则出错返回。 检查内存对齐值是否小于文件对齐值,小于则出错返回。 文件映像大小检测: 检查映像装入内存后的总大小,大于77000000h则出错返回,也就是说,一个PE文件的最大限制是1904MB,这与StackOverflow上一实际测试的哥们的结果是一致的: 相关标志的检测: 检查OptionalHeader.Magic和FileHeader.Machine值是否合法,这分为64位系统与32位系统两种情况。 当64为系统时,要求OptionalHeader.Magic为020Bh,且FileHeader.Machine为0200h或者8664h。 当32为系统时,OptionalHeader.Magic为010Bh,且FileHeader.Machine为014Ch。 以上检测若有不通过的项,均返回STATUS_INVALID_IMAGE_FORMAT。 PE结构体中需要校验字段 要注意,PE格式的检查,并不全部在MiVerifyImageHeader函数中完成。我们通过分析系统代码及反复试错,确认了64位PE头结构中必需检验的字段。在此分享给大家,代码如下,大家也可在附件的头文件中查看。 一并附上32位的相关结构体,大家在学习PE时,可以对照: 应用--畸形PE 研究PE文件的校验函数,得出其校验流程,是很有实践意义的。其最直接的例子,就是可以通过写出畸形PE来躲过反病毒分析系统的检测:只要分析系统的校验逻辑与Windows有一个地方不致,就可能被利用并躲过。 附件中是我们所写的一个畸形PE,它只有0x118个字节,在Windows 7 系统上可正常运行,弹出一个对话框:[ATTACH][/ATTACH] 而我们将它上传到几个知名的文件行为分析系统进行测试: 腾讯的哈勃系统,未能检测出程序行为: Anubis系统认定该文件不能正常执行 金山的火眼系统认定不是一个有效的PE文件: 这说明,我们所写的畸形PE,在物理机上能正常执行的前提下,成功躲避了以上各个自动检测系统。IDB文件: ntoskrnl.7z.001.rar ntoskrnl.7z.002.rar ntoskrnl.7z.003.rar 请将IDB文件的后缀名“.rar”删除后用7z解压
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)