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回调
使用未公开函数IoAllocateMiniCompletionPacket和IoSetIoCompletionEx设置回调:
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对应的服务类型。
注意事项
- 不同Windows版本需要适配不同的内存布局和结构
- 特征码搜索需要针对具体版本进行调整
- 解码ALPC消息内容需要根据具体服务协议进行
- 内核开发需注意稳定性问题
参考函数
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消息内容的解码工作。