发这份笔记时,比较惶恐,因为连我自己都觉得,这个东西没用。但是事实上,在很多偏门或机密的领域里,它仍然是非常有价值的。鉴于国内这方面资料比较少,所以写了一篇笔记,与大家共享。

Analyze Hibernation File

Introduction
   What’s Hibernation File?
Hibernation File也就是系统的休眠文件Hiberfil.sys,位于系统卷的根目录下。windows在系统休眠时,将物理内存中的数据(包括系统运行时的状态数据)dump到Hiberfil.sys,并生成一个有效的文件头。下次系统开机的时候,利用hiberfil.sys文件中的数据恢复系统。

   How to generate a valid Hiberfil.sys?
从用户的角度来说,系统停机时,按下休眠按钮,则会自动生成一个有效的Hiberfil.sys。
从电源管理器的角度来说,只有当系统电源状态从S0àS4的时候,才会生成一个有效的休眠文件。
注:当系统正常运行时,电源状态为S0;完全关闭时,电源状态S5;休眠状态时,电源状态S4,此时只有电源电路和唤醒电路才有点滴电流;中间的几个状态S1,S2,S3,都是不同程度的睡眠状态。
  从S4àS0的过程,也就是利用Hiberfil.sys恢复系统的过程。

Valuation of Hibernation File
   It is quick and easy
恢复系统比重启系统要快。不解释(除特殊情况)。
   A new method to dump physical memory
  利用hibernation技术dump系统物理内存中的数据。这些数据包括处理器状态,当前EIP, IDT table, GDT table, SSDT table,当前状态的可执行代码和数据…
  利用这些数据可以分析系统状态。

   Leak Information?
  Yes。正常情况下,利用Hiberfil.sys恢复系统,虽然恢复后会清除掉Hiberfil的Header数据(one page),使之无效,但是Header之后的数据还会保留。利用外部工具,构造一个Header,就可以读取该Hiberfil.sys的所有信息了。

   Defensive uses
kernel land malwares detection
分析hibernation file, 可以通过检查SSDT, IDT, GDT表的完整性来判断系统是否被修改。虽然在内核层有了更为轻量级的方法去检测系统关键表的完整性,但是存在anti对抗问题,hibernation提供了一种终极检测方式。
  根据hibernation恢复原理,不在hibernation file中的代码,将不会被恢复执行。基于此,可以对抗SMM rootkit。(不做介绍)
   Offensive uses
可以获取敏感数据,如:password, keys.
修改hibernation file, 提高某个进程的执行权限;绕过系统登录密码…

Hibernation file internals
   Headline of hibernation process
当磁盘被挂起时(S0àS4),Windows内核(ntoskrnl.exe)执行体创建一个hibernation file,并将物理内存中的数据用LZ77算法压缩后,写入文件。
系统恢复时(S4àS0),OSLoader.exe读取hibernation file,加载解压后的数据到物理内存中,使系统以一种快捷方式恢复到休眠时的状态。
  注:恢复过程的分析,见appendix 1。

   Hibernation file structure
先直观的看下文件结构图:(左侧,域列表,是文件结构的组成域)
 



File Header
位于休眠文件的第一页(0x1000 bytes)数据空间。该结构PO_MEMORY_IMAGE由内核调试符号导出。结构中主要包含了休眠文件的创建日期,版本号,校验和,有效标志,物理页数量等信息。
注:用休眠文件恢复系统时,只有第一页数据被清0,也就是file header,其他数据保留不变。

FreeMap Page
包含一个ulong数组,保存了空闲内存页的映射信息。

Processor State
处理器状态,由内核函数KiSaveProcessorControlState保存。包括控制寄存器CRX,GDT, IDT, EIP… 
结构定义如下:
typedef struct _KPROCESSOR_STATE32
{
    CONTEXT ContextFrame;        //
    KSPECIAL_REGISTERS SpecialRegisters;  //
} KPROCESSOR_STATE32, *PKPROCESSOR_STATE32;

  注:从vista版本以后,字段2和3的位置就交换过来了。

Memory Range Array
存储物理内存页的压缩数据。
typedef struct _MEMORY_RANGE_ARRAY
{
    MEMORY_RANGE_ARRAY_LINK MemArrayLink;
    MEMORY_RANGE_ARRAY_RANGE MemArrayRange[MAX_ARRAY_ENTRY];
} MEMORY_RANGE_ARRAY, *PMEMORY_RANGE_ARRAY;

字段MemArrayLink作为链接表指针,将多个这样的结构链接维护起来;
字段MemArrayRange指向数据区,最多包含255个入口。这也就是为什么要存在一个指向下一个MEMORY_RANGE_ARRAY结构的指针MemArrayLink,因为当休眠文件很大的时候,一个MEMORY_RANGE_ARRAY存储不了所有数据。

结构MEMORY_RANGE_ARRAY_RANGE定义如下:

typedef struct _MEMORY_RANGE_ARRAY_RANGE
{
    ULONG PageNo;       // ???
    ULONG StartPage;    // Block start (physical address)
    ULONG EndPage;      // Block stop (physical address)
    ULONG CheckSum;     // Always zero, but used under Windows 2000.
} MEMORY_RANGE_ARRAY_RANGE, *PMEMORY_RANGE_ARRAY_RANGE;
指定了页开始地址和页结束地址,在这些地址范围内的数据都是经过压缩算法来压缩后存储的,这个结构所对应的整块数据就是一个压缩块PageCompressedData,压缩快的起始部分是一个结构体,如下:
struct IMAGE_XPRESS_HEADER 

     CHAR Signature[8] = 81h, 81h, "xpress"; 
     BYTE UncompressedPages = 15; 
     UINT32 CompressedSize; 
     BYTE Reserved[19] = 0; 
};
也就是说,压缩块的标识是:\x81\x81xpress;之后就是压缩数据了。

  注:每个压缩快所对应的解压后的块大小为64KB(0x10 pages)。
   compression algorithm
This algorithm has been publicly documented since recent Microsoft Interoperability initiative (February 2008)
压缩块算法使用LZ77+DIRECT2;
LZ77用于压缩内存数据,DIRECT2对字节的位置进行编码和解码。


Project and Application 
   Sandman Project
该开源项目的主要特点是定义了休眠文件的文件结构,并给出了解析休眠文件的代码。
不过,这份代码中存在问题。在虚拟地址转换成物理地址时,作者将页面映射的PAE部分解析错了。
如果开启了PAE页面映射机制(实事上,几乎所有多核计算机都开启了PAE),那么MMU将会用三级表来实现地址转译,分别是:页目录指针表,页目录索引,页表索引,页字节偏移,其中前三项都是8字节长,而作者解析时将其作为4字节处理了。
具体改法:在mm.c文件à MmGetPhysicalAddress函数中,将处理PAE的偏移,改为8;

注:4KB分页时,地址转译只需二级表,页目录索引,页表索引,每项4字节长。
  
  Tiamo NTLDR Project
作者Tiamo基本实现了一个完整的NTLDR,可以替换系统NTLDR,主要包括了利用休眠文件hiberfil.sys恢复系统的C++实现。不过我没有测试过可行性。
项目包括两部分:
16位实模式代码;
32位模式代码,即OsLoader.exe,是windows真正的32位入口程序。作者这部分代码参考了NT4代码,但是NT4代码中并没有实现恢复hiberfil的代码,估计作者是通过逆向写的。

   Useful application
利用Sandman,开发了若干免费小工具,一般开发人员用不上。
可访问:http://www.moonsols.com

解析hiberfil.sys,获取系统版本号,休眠文件的创建时间,从休眠文件dump物理内存内容(见附件)。

ANTI SMM Rootkit应用;

其他方面,取证分析,以及未知的应用…


APPENDIX
   简述用休眠文件恢复系统的过程

开机过程中,BIOS代码首先获得控制权,执行完硬件检测后跳转到MBR,MBR代码部分主要读取分区表信息,然后将控制权交给系统卷的引导扇区DBR,DBR执行一些初始化后,读取文件NTLDR,NTLDR中的16位模式代码开始执行,同样进行一些必要的初始化工作,然后将处理器切换到32位保护模式。
控制权交给osloader.exe, 此时处理器虽然已经工作在保护模式下,但是它的虚拟地址转译机制尚未开启,所以,处理器仍然直接使用物理地址。
Osloader 在入口函数NtProcessStartup里,调用全局初始化内存函数:
DoGlobalInitialization()-> InitializeMemorySubsystem() ->
实现的功能是: 用一个内存描述符数组把每一段内存的大小和用途记录下来,然后构造页目录和页表,使得16M一下的内存能够通过页面映射(paging)机制进行访问,在loader阶段是不使用高于16M以上的物理内存的,在设置好页目录寄存器,并打开页面映射机制。
之后,osloader 继续执行其他的初始化工作,包括IO设备的初始化。
分配PCR的页面-> 分配TSS的页面-> 初始化内存描述符-> 初始化IO系统BlIoInitialize。

接下来判断控制启动的方式。
函数:BlStartup(BootPartitionName);
过程大概这样:打开启动分区,以便加载驱动;初始化屏幕; 读取hiberfil.sys,如果是一个有效的休眠文件,则系统以hiberfil.sys方式恢复系统。如果不是,打开boot.ini文件,并显示一个引导选择菜单。如果boot.ini只包含一个引导选项,那么,此菜单不显示,而是立即应用该引导选项。

如果检测到hiberfil.sys有效,osloader将控制权交给一段能恢复系统的代码。大致流程如下:
打开hiberfil,不成功则返回-->成功则继续恢复 --> 分配页面缓冲-->解析头文件,判断image signature,有三种标志,分别处理,如果判断标志出错,就直接返回-->进行一些其他的校验工作->接下来分配PTE,读取内存映射页面的数据-> 分配压缩数据的缓冲->解压缩数据到物理内存> 再经过一系列的恢复工作-> 读取处理器的状态......

   通过NT4代码,分析NTLDR的工作流
boot/bootcode/mbr/i386/x86mboot.asm  ->
这里执行MBR引导代码,读取分区表,获得引导分区,将引导分区的第一个扇区,即DBR读入到内存,然后将EIP调到DBR引导代码,执行。
boot/bootcode/ntfs/i386/ntfsboot.asm    ->
该函数主要用来读取卷上的数据,主要是将ntldr读入到内存,然后执行。(卷上的数据是根据文件系统格式存储和管理的,所以访问卷上的文件内容,需借助文件系统代码去读。)到目前为止,程序还是在实模式下,即16-bit模式。
boot/startup/i386/su.asm               ->
直接跳到 JMP RealStart,准备stack和segment,然后去执行suMain函数,该函数位于:
boot/startup/i386/main.c        ->
来到了C代码。这里实现一些初始化,包括video ,memory,foppy等。之后在回到su.asm,进行模式切换,至32-bit;
boot/lib/i386/entry.c          ->
入口函数NtProcessStartup,这里,进行一些初始化,调用DoGlobalInitialization 初始化16M以下的内存,分页机制开始启动。又进行了一些system memory和I/O system的初始化工作后,理解调用BlStartup函数,也就是说ntldr在BlStartup函数中执行完毕后,就返回了。该函数在:
boot/bldr/i386/initx86.c        -> 
这个函数打开boot.ini,获取启动选项,继续调用osloader.c;
\boot\bldr\osloader.c  

注:NT4不支持hiberfil休眠机制,在XP以后的系统中,恢复hiberfil的过程在 BlStartup过程中被调用。

   通过逆向分析NTLDR,分析hibernation file
这里只说下分析方法,省去细节。
恢复Hiberfil的工作是由osloader完成的,所以,通过逆向分析osloader.exe,可以了解恢复hiberfil的一些具体细节。

NTLDR位于系统盘根目录下,是一个具有隐藏只读属性的binary系统文件,不能直接使用IDA进行分析。
NTLDR由两个image组成,一个是16-bit下的binary image,就像是一个.com 文件;另一个是32-bit 下的PE file image,主要包含加载任务,这个PE image对应的文件就是osloader.exe。

提取osloader.exe的方法
用一个16进制编辑器(e.g. WinHex),打开NTLDR, 查找'MZ'或'PE'标志,然后从'MZ'标志开始,将剩下的hex data全部复制并粘贴到另外一个空文件,重命名为:osloader.exe。用winhex的话,会很简单(找到'MZ'标志,ALT+1,然后拖到文件末尾,ALT+2,这样就选中的从'MZ'到文件末尾的区块,然后右键'edit'->'copy block'->'into a new file')。
提取出osloader之后,用IDA加载,加载的时候会提示从MS官方下载'未公开'的符号,选择YES或NO,等待分析,可能要花2分钟左右时间。

分析osloader.exe文件
常理来说,osloader应该是一个native app,其入口函数是:NtProcessStartup。但是IDA分析后,入口函数为DriverEntry,当成驱动程序来分析了。但是,事实上,native app本质上就是一个驱动。
接下来就是利用各种资源,找自己关注的代码区分析了。

References
1, http://sandman.msuiche.net/docs/SandMan_Project.pdf
2, http://www.blackhat.com/presentations/bh-usa-06/BH-US-06-Burdach.pdf
3, https://www.volatilesystems.com/default/volatility
4, http://sandman.msuiche.net/
5, http://www.msuiche.net/con/euro2008/Exploiting_Windows_Hibernation_File.pdf

code and docs:
ShareHiberfil.rar