首页
社区
课程
招聘
[原创]利用 API Set 实现无DLL注入的API重定向与劫持
发表于: 2025-5-13 21:04 8478

[原创]利用 API Set 实现无DLL注入的API重定向与劫持

2025-5-13 21:04
8478

本文将深入探讨一种利用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继续执行。
#include <iostream>      // 用于标准输入输出,例如 printf (虽然这里主要用Windows API的printf)
#include <Windows.h>     // Windows API核心头文件
#include <intrin.h>      // 包含内联函数,例如 __readgsqword 获取TEB
#include <winternl.h>    // 包含Windows NT内部结构和函数声明,例如 PEB, TEB, UNICODE_STRING, NtQueryInformationProcess
 
// 声明外部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函数返回
}
#include <iostream>      // 用于标准输入输出,例如 printf (虽然这里主要用Windows API的printf)
#include <Windows.h>     // Windows API核心头文件
#include <intrin.h>      // 包含内联函数,例如 __readgsqword 获取TEB
#include <winternl.h>    // 包含Windows NT内部结构和函数声明,例如 PEB, TEB, UNICODE_STRING, NtQueryInformationProcess
 
// 声明外部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条目本身也有一个名称 (通常用于别名或重定向)

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2025-5-17 12:55 被S极客编辑 ,原因: 增加例子
收藏
免费 166
支持
分享
最新回复 (141)
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
mark
2025-5-13 23:28
0
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2025-5-14 00:31
0
雪    币: 1648
活跃值: (976)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
mark
2025-5-14 00:36
0
雪    币: 502
活跃值: (970)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
看到未公开的东西自己也有冲动想发表下未公开的东西,想想这样太危险
2025-5-14 01:13
0
雪    币: 292
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
2025-5-14 03:04
0
雪    币: 7146
活跃值: (5238)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
7
感谢分享
2025-5-14 07:36
0
雪    币: 4561
活跃值: (4056)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
8
回复来看看,先谢过楼主!
2025-5-14 08:10
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
学习学习
2025-5-14 08:35
0
雪    币: 7115
活跃值: (7795)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
10
感谢分享!
2025-5-14 08:39
0
雪    币: 655
活跃值: (707)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
学习学习
2025-5-14 08:51
0
雪    币: 3577
活跃值: (3235)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
学习学习
2025-5-14 09:59
0
雪    币: 2779
活跃值: (4483)
能力值: ( LV6,RANK:81 )
在线值:
发帖
回帖
粉丝
13
学习学习
2025-5-14 13:43
0
雪    币: 2850
活跃值: (7448)
能力值: ( LV7,RANK:102 )
在线值:
发帖
回帖
粉丝
14
注入新姿势吗
2025-5-14 13:57
0
雪    币: 144
活跃值: (708)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
不错
2025-5-14 14:17
0
雪    币: 1347
活跃值: (5063)
能力值: ( LV9,RANK:220 )
在线值:
发帖
回帖
粉丝
16
2025-5-14 14:23
0
雪    币: 3616
活跃值: (3405)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
学习学习
2025-5-14 15:09
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
不错
2025-5-14 15:21
0
雪    币: 9382
活跃值: (6037)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
19
学海无崖
2025-5-14 15:54
0
雪    币: 45
活跃值: (533)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
学习
2025-5-14 16:25
0
雪    币: 5176
活跃值: (5317)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
学习了
2025-5-14 16:40
0
雪    币: 94
活跃值: (565)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
学习了
2025-5-14 16:49
0
雪    币: 226
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23
m
2025-5-14 16:53
0
雪    币: 2839
活跃值: (2300)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
看下是新技术不
2025-5-14 16:58
0
雪    币: 364
活跃值: (2142)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
25
666666
2025-5-14 17:01
0
游客
登录 | 注册 方可回帖
返回