深入剖析 api-ms-* 系列动态链接库
字数 1342 2025-08-24 20:49:31
深入剖析 api-ms-* 系列动态链接库
概述
api-ms-* 系列动态链接库是Windows操作系统中一种特殊的DLL文件,它们实际上并不包含实际的函数实现,而是作为API转发器存在。这些DLL文件的主要作用是为Windows API提供版本控制和兼容性支持。
核心机制
API转发机制
api-ms-* DLL并不包含实际代码,而是将API调用转发到其他系统DLL中。这种设计允许微软在不破坏向后兼容性的情况下更新API实现。
ApiSetSchema.dll 的作用
所有api-ms-* DLL的转发关系都记录在ApiSetSchema.dll中,该文件位于System32目录下。它包含了一个完整的转发关系索引表,即使某些api-ms-* DLL文件不存在于磁盘上,系统也能通过这个索引表找到正确的转发目标。
内核实现细节
初始化过程
- 内核在初始化阶段使用
nt!MiInitializeApiSets函数 - 通过
nt!PsLoadedModuleList函数在ldr双链表中搜索ApiSetSchema.dll - 获取其访问控制权后,将其视为PE结构解析
- 提取
.apiset节区数据并映射到内核空间 - 映射完成后卸载
ApiSetSchema模块
PEB中的ApiSetMap
在PEB(进程环境块)偏移0x68处存在一个ApiSetMap结构体指针,它指向ApiSetSchema.dll中.apiset节区的数据。
数据结构解析
主要结构体
typedef struct _API_SET_NAMESPACE {
uint32_t Version;
uint32_t Size;
uint32_t Flags;
uint32_t Count; // API_SET_NAMESPACE_ENTRY数组数量
uint32_t EntryOffset; // API_SET_NAMESPACE_ENTRY入口偏移
uint32_t HashOffset; // API_SET_HASH_ENTRY入口偏移
uint32_t HashFactor;
} API_SET_NAMESPACE, *PAPI_SET_NAMESPACE;
typedef struct _API_SET_HASH_ENTRY {
uint32_t Hash;
uint32_t Index;
} API_SET_HASH_ENTRY, *PAPI_SET_HASH_ENTRY;
typedef struct _API_SET_NAMESPACE_ENTRY {
uint32_t Flags;
uint32_t NameOffset; // 原始DLL名偏移
uint32_t NameLength; // 原始DLL名长度
uint32_t HashedLength;
uint32_t ValueOffset; // API_SET_VALUE_ENTRY入口偏移
uint32_t ValueCount; // 转发目标DLL数量
} API_SET_NAMESPACE_ENTRY, *PAPI_SET_NAMESPACE_ENTRY;
typedef struct _API_SET_VALUE_ENTRY {
uint32_t Flags;
uint32_t NameOffset;
uint32_t NameLength;
uint32_t ValueOffset; // 转发目标DLL名偏移
uint32_t ValueLength; // 转发目标DLL名长度
} API_SET_VALUE_ENTRY, *PAPI_SET_VALUE_ENTRY;
解析流程
- 首先解析
API_SET_NAMESPACE结构体 - 根据
EntryOffset定位到API_SET_NAMESPACE_ENTRY数组 - 遍历数组,通过
NameOffset获取原始DLL名 - 通过
ValueOffset定位到API_SET_VALUE_ENTRY结构体 - 通过
ValueOffset获取转发目标DLL名
实践解析方法
使用010 Editor模板解析
提供了一个010 Editor模板,可以直观地解析.apiset节区数据:
// 模板代码见原文
使用步骤:
- 从
ApiSetSchema.dll中提取.apiset节区数据 - 在010 Editor中应用模板
- 查看解析结果
使用C++代码解析
完整C++解析代码示例:
#include <LIEF/LIEF.hpp>
#include <Windows.h>
// 结构体定义见上文
int main() {
// 1. 加载ApiSetSchema.dll
std::string path = "apisetschema.dll";
std::unique_ptr<LIEF::PE::Binary> bin = LIEF::PE::Parser::parse(path);
// 2. 获取.apiset节区数据
LIEF::PE::Section sec = bin->get_section(".apiset");
std::vector<uint8_t> sec_data = sec.content();
// 3. 解析API_SET_NAMESPACE结构
PAPI_SET_NAMESPACE pnamespace = (PAPI_SET_NAMESPACE)(sec_data.data());
UINT_PTR namespace_addr = (UINT_PTR)pnamespace;
PAPI_SET_NAMESPACE_ENTRY pnamespace_entry =
(PAPI_SET_NAMESPACE_ENTRY)(namespace_addr + pnamespace->EntryOffset);
// 4. 遍历所有条目并打印转发关系
UNICODE_STRING origin_name, forward_name;
for (uint32_t i = 0; i < pnamespace->Count; i++) {
origin_name.Buffer = (wchar_t*)(namespace_addr + pnamespace_entry->NameOffset);
origin_name.Length = pnamespace_entry->NameLength;
origin_name.MaximumLength = pnamespace_entry->NameLength;
printf("%wZ.dll -> ", &origin_name);
PAPI_SET_VALUE_ENTRY pvalue_entry =
(PAPI_SET_VALUE_ENTRY)(namespace_addr + pnamespace_entry->ValueOffset);
for (uint32_t j = 0; j < pnamespace_entry->ValueCount; j++) {
forward_name.Buffer = (wchar_t*)(namespace_addr + pvalue_entry->ValueOffset);
forward_name.Length = pvalue_entry->ValueLength;
forward_name.MaximumLength = pvalue_entry->ValueLength;
printf("%wZ", &forward_name);
if ((j + 1) != pnamespace_entry->ValueCount) {
printf(", ");
}
if (pvalue_entry->NameLength != 0) {
origin_name.Buffer = (wchar_t*)(namespace_addr + pvalue_entry->NameOffset);
origin_name.Length = pvalue_entry->NameLength;
origin_name.MaximumLength = pvalue_entry->NameLength;
printf(" [%wZ]", &origin_name);
}
pvalue_entry++;
}
printf("\n");
pnamespace_entry++;
}
return 0;
}
注意事项
- 字符串使用
UNICODE_STRING结构而非普通的宽字符字符串,因为DLL名称不以\0结尾 - 一个api-ms-* DLL可能转发到多个目标DLL,由
ValueCount字段指定 - 哈希表(
API_SET_HASH_ENTRY)用于系统快速检索转发关系
实际应用场景
- 逆向分析:理解API调用链,追踪函数实际实现位置
- 兼容性研究:分析不同Windows版本间的API变化
- 安全研究:检测API调用劫持或挂钩
- 软件开发:解决DLL依赖问题,理解Windows API版本控制机制