《Windows Internals》第三章_映像加载器

映像加载器

         当系统中的进程被启动的时候,内核创建一个内核对象表示该进程,并执行各种和内核相关的初始化任务。绝大部分的初始化任务是在内核之外完成的,这些工作是由映像加载器完成的。映像加载器驻留在用户模式下的Ntdll.dll中,映像加载器完成以下的初始化工作:

  • 1.初始化其他用户模式,包括堆栈的初始化,TLS和FLS的初始化
  • 2.解析执行文件的IAT,DLL的导出表
  • 3.加载卸载DLL,维护模块数据库
  • 4.启用API集和API重定向          进程创建以后,映像加载器调用特殊的原生API,在一个基于栈中的环境帧中执行。由于加载器并不使用标准的调用进入正在运行的应用程序中,所以,在一个线程的栈痕迹中,永远不会看到加载器的初始化函数出现在调用中。
             通过进行“观察映像加载器”这个实验,我们可以知道,加载器不一定在程序开始的时候运行,也可以在后续运行过程中,会对一些涉及到延迟加载其他模块的线程请求进行响应。

进程初始化早期工作

         加载器完成早期初始化工作后,开始解析IAT,以及加载Dll,并且根据IAT和导出表,解析导入导出函数,构建模块数据库。

DLL名称解析

         原始搜索dll目录的顺序

  • 应用程序被激发的目录
  • 当前目录(可以通过SetCurrentDirection修改)
  • windows系统目录
  • windows子系统目录(16位)
  • windows目录
  • %PATH%指定的目录(通过SetEnvironmentVariabe修改)

         更加安全的dll搜索路径

  • knowndll注册表指定的dll路径
  • 应用程序被激发的目录
  • windows系统目录
  • windows子系统目录(16位)
  • windows目录
  • 当前目录(可以通过SetCurrentDirection修改)
  • %PATH%指定的目录(通过SetEnvironmentVariabe修改)

已加载的模块数据库

         加载器维护了一个关于进程加载模块的数据库,存放在PEB中,该数据库被存放在PEB_LDR-DATA结构中,在此结构中,记载器维护了3个双向链表。
         以下是关于PEB_LDR_DATA的结构体。

1
2
3
4
5
6
7
8
9
typedef struct _PEB_LDR_DATA
{
 ULONG Length; // +0x00
 BOOLEAN Initialized; // +0x04
 PVOID SsHandle; // +0x08
 LIST_ENTRY InLoadOrderModuleList; // +0x0c
 LIST_ENTRY InMemoryOrderModuleList; // +0x14
 LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24

         其中包含了LIST_ENTRY类型三个数据结构,分别是模块加载顺序,内存加载顺序,初始化模块顺序。

1
2
3
 LIST_ENTRY InLoadOrderModuleList; // +0x0c
 LIST_ENTRY InMemoryOrderModuleList; // +0x14
 LIST_ENTRY InInitializationOrderModuleList;// +0x1c

         LIST_ENTRY是一个双向链表,这个双链表指向进程装载的模块,结构中的每个指针,指向了一个LDR_DATA_TABLE_ENTRY 的结构

1
2
3
4
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

         利用windbg,进行以下操作

  • !peb:显示peb信息
  • 根据上述知识,我们知道LIST_ENTRY的指针指向的是LDR_DATA_TABLE_ENTRY结构体,由上图,dd 00251ee0,内存数据251f48,指向的是下一个LDR_DATA_TABLE_ENTRY结构体,251eac指向的上一个结构体。
  • 不断的dd 内存,发现,最后一个结构体的下一个结构体,是第一个结构体,这样形成了一个双向循环链表。

导入信息解析

         根据IAT和模块数据库装载Dll,然后进行重定位检测,如果进行了重定位,则解析dll中的重定位信息,当每个dll装载完成后,解析IAT,查找每个导入函数。

导入过程初始化的后期处理

         dll等依赖文件被加载入进程后,则应该执行以下操作:

  • 检查是否为NET应用
  • 检查程序自身是否要求重定位
  • 是否使用TLS
  • 兼容性检测。