windows ALPC内核拦截的方法
字数 1156 2025-08-24 07:48:33

Windows ALPC内核拦截方法详解

前言

ALPC(Advanced Local Procedure Call)是Windows系统中一种复杂的进程间通信机制。目前大多数安全软件都是在用户态(R3)通过hook services来实现拦截。本文将详细介绍一种内核态的ALPC拦截方法。

方法概述

本文介绍的方法是通过挂钩ALPC的IO完成回调来实现内核拦截。该方法最初是通过逆向ALPC时偶然发现的,后来发现国外研究者zer0mem也发现了类似方法(http://www.zer0mem.sk/?p=542)。

技术原理

NtAlpcSetInformation->AlpcpInitializeCompletionList->AlpcpAllocateCompletionPacketLookaside调用链中,可以发现ALPC使用了IO回调机制。我们可以利用微软允许的IOComplateCallback来进行拦截操作。

关键实现步骤

1. 定位ALPC全局链表

内核中存在一个未公开的全局双向链表nt!alpclist。在不同Windows版本中,定位方法有所不同:

  • Windows 7及之前版本:可以通过公开结构AlpcPortObjectType来间接定位alpclist

  • Windows 8及之后版本:由于内存布局改变,必须使用特征码搜索方法。特征码示例:

    \x48\xCC\xCC\xCC\xCC\xCC\xCC\x48\xCC\xCC\xCC\xCC\xCC\xCC\x48\xCC\xCC\xCC\xCC\xCC\xCC\x48\xCC\xCC\xCC\xCC\xCC\xCC\xCC
    

2. 遍历ALPC端口链表

首先需要获取锁:

if (_interlockedbittestandset64((__int64*)&gAlpcpPortListLock, 0))
    ExfAcquirePushLockShared((PULONG_PTR)&gAlpcpPortListLock);

然后遍历链表:

PLIST_ENTRY pLink = NULL;
for (pLink = gAlpcpPortList->Flink; pLink != (PLIST_ENTRY)&gAlpcpPortList->Flink; pLink = pLink->Flink) {
    _ALPC_PORT* alpcPort = CONTAINING_RECORD(pLink, _ALPC_PORT, PortListEntry);

3. ALPC_PORT结构

ALPC_PORT结构随Windows版本变化而变化,以下是部分关键字段:

struct _ALPC_PORT {
    struct _LIST_ENTRY PortListEntry;          //0x0
    struct _ALPC_COMMUNICATION_INFO* CommunicationInfo; //0x10
    struct _EPROCESS* OwnerProcess;           //0x18
    VOID* CompletionPort;                     //0x20
    VOID* CompletionKey;                       //0x28
    struct _ALPC_COMPLETION_PACKET_LOOKASIDE* CompletionPacketLookaside; //0x30
    VOID* PortContext;                         //0x38
    struct _SECURITY_CLIENT_CONTEXT StaticSecurity; //0x40
    // ... 其他字段省略
};

4. 判断目标进程

if (!alpcPort->OwnerProcess || 
    PsGetProcessId((PEPROCESS)alpcPort->OwnerProcess) != TargetPrcessId || 
    !alpcPort->CompletionPort)
    continue;

5. 设置IO回调

使用未公开函数IoAllocateMiniCompletionPacketIoSetIoCompletionEx设置回调:

void* IoMiniCompletPtr = IoAllocateMiniCompletionPacket(ALPC_NotifyCallback, alpcPort);
if (IoMiniCompletPtr == NULL) {
    __debugbreak();
    return;
}
IoSetIoCompletionEx(alpcPort->CompletionPort, alpcPort->CompletionKey, 
                   nullptr, NULL, NULL, FALSE, IoMiniCompletPtr);

6. IO_MINI_COMPLETION_PACKET_USER结构

通过逆向分析得到的结构:

struct _IO_MINI_COMPLETION_PACKET_USER {
    struct _LIST_ENTRY ListEntry;      //0x0
    ULONG PacketType;                 //0x10
    VOID* KeyContext;                 //0x18
    VOID* ApcContext;                 //0x20
    LONG IoStatus;                    //0x28
    ULONGLONG IoStatusInformation;    //0x30
    VOID(*MiniPacketCallback)(struct _IO_MINI_COMPLETION_PACKET_USER* arg1, VOID* arg2); //0x38
    VOID* Context;                    //0x40
    UCHAR Allocated;                  //0x48
};

7. 回调函数原型

typedef void(__fastcall* MINIPACKETCALLBACK)(
    __in _IO_MINI_COMPLETION_PACKET_USER* miniPacket,
    __inout void* context
);

解码ALPC_PORT信息

ALPC只是一个标准协议,不同服务(如创建服务、创建账号、搜索系统信息等)的具体内容各不相同,需要手动解码。其中keycontext包含一个SubProcessTag,可以识别RPC对应的服务类型。

注意事项

  1. 不同Windows版本需要适配不同的内存布局和结构
  2. 特征码搜索需要针对具体版本进行调整
  3. 解码ALPC消息内容需要根据具体服务协议进行
  4. 内核开发需注意稳定性问题

参考函数

extern "C" {
    NTKERNELAPI void* NTAPI IoAllocateMiniCompletionPacket(
        __in MINIPACKETCALLBACK miniPacketCallback,
        __in const void* context
    );
    
    NTKERNELAPI void NTAPI IoSetIoCompletionEx(
        __inout void* completitionPort,
        __in const void* keyContext,
        __in const void* apcContext,
        __in ULONG_PTR ioStatus,
        __in ULONG_PTR ioStatusInformation,
        __in bool allocPacketInfo,
        __in const void* ioMiniCoompletitionPacketUser
    );
    
    NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation(
        IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
        OUT PVOID SystemInformation,
        IN ULONG SystemInformationLength,
        OUT PULONG ReturnLength OPTIONAL
    );
    
    NTSYSAPI PIMAGE_NT_HEADERS NTAPI RtlImageNtHeader(_In_ PVOID Base);
    
    NTKERNELAPI void* NTAPI IoFreeMiniCompletionPacket(__in const void* miniPacket);
}

总结

本文详细介绍了通过挂钩ALPC的IO完成回调来实现内核拦截的方法。相比用户态hook services的方法,这种方法更加底层和高效。但需要注意不同Windows版本的适配问题,以及ALPC消息内容的解码工作。

Windows ALPC内核拦截方法详解 前言 ALPC(Advanced Local Procedure Call)是Windows系统中一种复杂的进程间通信机制。目前大多数安全软件都是在用户态(R3)通过hook services来实现拦截。本文将详细介绍一种内核态的ALPC拦截方法。 方法概述 本文介绍的方法是通过挂钩ALPC的IO完成回调来实现内核拦截。该方法最初是通过逆向ALPC时偶然发现的,后来发现国外研究者zer0mem也发现了类似方法(http://www.zer0mem.sk/?p=542)。 技术原理 在 NtAlpcSetInformation->AlpcpInitializeCompletionList->AlpcpAllocateCompletionPacketLookaside 调用链中,可以发现ALPC使用了IO回调机制。我们可以利用微软允许的 IOComplateCallback 来进行拦截操作。 关键实现步骤 1. 定位ALPC全局链表 内核中存在一个未公开的全局双向链表 nt!alpclist 。在不同Windows版本中,定位方法有所不同: Windows 7及之前版本 :可以通过公开结构 AlpcPortObjectType 来间接定位 alpclist 。 Windows 8及之后版本 :由于内存布局改变,必须使用特征码搜索方法。特征码示例: 2. 遍历ALPC端口链表 首先需要获取锁: 然后遍历链表: 3. ALPC_ PORT结构 ALPC_ PORT结构随Windows版本变化而变化,以下是部分关键字段: 4. 判断目标进程 5. 设置IO回调 使用未公开函数 IoAllocateMiniCompletionPacket 和 IoSetIoCompletionEx 设置回调: 6. IO_ MINI_ COMPLETION_ PACKET_ USER结构 通过逆向分析得到的结构: 7. 回调函数原型 解码ALPC_ PORT信息 ALPC只是一个标准协议,不同服务(如创建服务、创建账号、搜索系统信息等)的具体内容各不相同,需要手动解码。其中 keycontext 包含一个 SubProcessTag ,可以识别RPC对应的服务类型。 注意事项 不同Windows版本需要适配不同的内存布局和结构 特征码搜索需要针对具体版本进行调整 解码ALPC消息内容需要根据具体服务协议进行 内核开发需注意稳定性问题 参考函数 总结 本文详细介绍了通过挂钩ALPC的IO完成回调来实现内核拦截的方法。相比用户态hook services的方法,这种方法更加底层和高效。但需要注意不同Windows版本的适配问题,以及ALPC消息内容的解码工作。