1、课程目标
- 掌握线程创建回调的注册和使用
- 实现线程创建和退出监控
- 学会识别远程线程注入
- 理解线程监控在安全防护中的作用
2、名词解释
| 术语 |
解释 |
| PsSetCreateThreadNotifyRoutine |
注册线程通知回调 |
| PsSetCreateThreadNotifyRoutineEx |
扩展版线程回调 |
| PETHREAD |
线程执行体对象指针 |
| 远程线程 |
在其他进程中创建的线程 |
3、使用工具
| 工具 |
用途 |
| Process Explorer |
查看进程线程 |
| WinDbg |
调试线程回调 |
| API Monitor |
监控CreateRemoteThread |
4、技术原理
4.1、线程回调机制
┌─────────────────────────────────────────────────────────────┐
│ 线程创建回调流程 │
│ │
│ CreateThread() / CreateRemoteThread() │
│ │ │
│ ↓ │
│ NtCreateThreadEx() │
│ │ │
│ ↓ │
│ PspInsertThread() │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ 调用线程通知回调 │ │
│ │ PsSetCreateThreadNotifyRoutine │ │
│ │ │ │
│ │ 参数: │ │
│ │ - ProcessId (目标进程ID) │ │
│ │ - ThreadId (新线程ID) │ │
│ │ - Create (TRUE=创建, FALSE=退出) │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 远程线程检测:当前进程 != 目标进程 → 可能是注入 │
└─────────────────────────────────────────────────────────────┘
5、代码实现
5.1、示例1:基础线程监控
// ThreadMonitor.c - 基础线程监控
#include <ntddk.h>
// 线程通知回调
VOID ThreadNotifyCallback(
HANDLE ProcessId,
HANDLE ThreadId,
BOOLEAN Create
) {
if (Create) {
DbgPrint("[Thread] Created: TID=%d in PID=%d\n",
(ULONG)(ULONG_PTR)ThreadId,
(ULONG)(ULONG_PTR)ProcessId);
// 获取当前进程ID(创建者)
HANDLE currentPid = PsGetCurrentProcessId();
// 检测远程线程
if (currentPid != ProcessId && ProcessId != (HANDLE)4) {
// 当前进程与目标进程不同,可能是远程线程注入
PEPROCESS currentProcess, targetProcess;
if (NT_SUCCESS(PsLookupProcessByProcessId(currentPid, ¤tProcess))) {
PUCHAR currentName = PsGetProcessImageFileName(currentProcess);
if (NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &targetProcess))) {
PUCHAR targetName = PsGetProcessImageFileName(targetProcess);
DbgPrint("[Thread] !!! REMOTE THREAD DETECTED !!!\n");
DbgPrint("[Thread] Creator: %s (PID=%d)\n",
currentName, (ULONG)(ULONG_PTR)currentPid);
DbgPrint("[Thread] Target: %s (PID=%d)\n",
targetName, (ULONG)(ULONG_PTR)ProcessId);
ObDereferenceObject(targetProcess);
}
ObDereferenceObject(currentProcess);
}
}
} else {
DbgPrint("[Thread] Exited: TID=%d in PID=%d\n",
(ULONG)(ULONG_PTR)ThreadId,
(ULONG)(ULONG_PTR)ProcessId);
}
}
// 注册回调
NTSTATUS RegisterThreadCallback() {
return PsSetCreateThreadNotifyRoutine(ThreadNotifyCallback);
}
// 注销回调
VOID UnregisterThreadCallback() {
PsRemoveCreateThreadNotifyRoutine(ThreadNotifyCallback);
}
5.2、示例2:扩展线程监控
// ThreadMonitorEx.c - 扩展线程监控
#include <ntddk.h>
// 线程信息记录
typedef struct _THREAD_INFO {
HANDLE ProcessId;
HANDLE ThreadId;
HANDLE CreatorProcessId;
PVOID StartAddress;
BOOLEAN IsRemote;
LARGE_INTEGER CreateTime;
LIST_ENTRY ListEntry;
} THREAD_INFO, *PTHREAD_INFO;
LIST_ENTRY g_ThreadList;
KSPIN_LOCK g_ThreadLock;
VOID InitThreadList() {
InitializeListHead(&g_ThreadList);
KeInitializeSpinLock(&g_ThreadLock);
}
// 获取线程起始地址
PVOID GetThreadStartAddress(HANDLE ThreadId) {
PETHREAD thread;
PVOID startAddress = NULL;
if (NT_SUCCESS(PsLookupThreadByThreadId(ThreadId, &thread))) {
// 从ETHREAD获取StartAddress
// 注意:偏移量因Windows版本而异
// Win10 x64: ETHREAD + 0x620 (Win32StartAddress)
// 或使用ZwQueryInformationThread
ObDereferenceObject(thread);
}
return startAddress;
}
// 扩展线程回调
VOID ThreadNotifyCallbackEx(
HANDLE ProcessId,
HANDLE ThreadId,
BOOLEAN Create
) {
if (Create) {
KIRQL oldIrql;
HANDLE creatorPid = PsGetCurrentProcessId();
BOOLEAN isRemote = (creatorPid != ProcessId) && (ProcessId != (HANDLE)4);
// 记录线程信息
PTHREAD_INFO info = (PTHREAD_INFO)ExAllocatePoolWithTag(
NonPagedPool, sizeof(THREAD_INFO), 'irhT');
if (info) {
info->ProcessId = ProcessId;
info->ThreadId = ThreadId;
info->CreatorProcessId = creatorPid;
info->IsRemote = isRemote;
KeQuerySystemTime(&info->CreateTime);
KeAcquireSpinLock(&g_ThreadLock, &oldIrql);
InsertTailList(&g_ThreadList, &info->ListEntry);
KeReleaseSpinLock(&g_ThreadLock, oldIrql);
}
if (isRemote) {
// 记录远程线程注入事件
DbgPrint("[ThreadEx] Remote thread injection detected!\n");
DbgPrint("[ThreadEx] Creator PID: %d -> Target PID: %d, TID: %d\n",
(ULONG)(ULONG_PTR)creatorPid,
(ULONG)(ULONG_PTR)ProcessId,
(ULONG)(ULONG_PTR)ThreadId);
}
} else {
// 移除线程记录
KIRQL oldIrql;
PLIST_ENTRY entry;
KeAcquireSpinLock(&g_ThreadLock, &oldIrql);
for (entry = g_ThreadList.Flink;
entry != &g_ThreadList;
entry = entry->Flink) {
PTHREAD_INFO info = CONTAINING_RECORD(entry, THREAD_INFO, ListEntry);
if (info->ThreadId == ThreadId && info->ProcessId == ProcessId) {
RemoveEntryList(entry);
ExFreePoolWithTag(info, 'irhT');
break;
}
}
KeReleaseSpinLock(&g_ThreadLock, oldIrql);
}
}
// 获取远程线程列表
ULONG GetRemoteThreadCount() {
KIRQL oldIrql;
ULONG count = 0;
PLIST_ENTRY entry;
KeAcquireSpinLock(&g_ThreadLock, &oldIrql);
for (entry = g_ThreadList.Flink;
entry != &g_ThreadList;
entry = entry->Flink) {
PTHREAD_INFO info = CONTAINING_RECORD(entry, THREAD_INFO, ListEntry);
if (info->IsRemote) {
count++;
}
}
KeReleaseSpinLock(&g_ThreadLock, oldIrql);
return count;
}
// 清理线程列表
VOID CleanupThreadList() {
KIRQL oldIrql;
PLIST_ENTRY entry;
KeAcquireSpinLock(&g_ThreadLock, &oldIrql);
while (!IsListEmpty(&g_ThreadList)) {
entry = RemoveHeadList(&g_ThreadList);
PTHREAD_INFO info = CONTAINING_RECORD(entry, THREAD_INFO, ListEntry);
ExFreePoolWithTag(info, 'irhT');
}
KeReleaseSpinLock(&g_ThreadLock, oldIrql);
}
5.3、示例3:远程线程防护
// ThreadProtect.c - 远程线程防护
#include <ntddk.h>
// 保护的进程列表
ULONG g_ProtectedPids[64];
ULONG g_ProtectedCount = 0;
KSPIN_LOCK g_ProtectLock;
BOOLEAN g_BlockRemoteThreads = TRUE;
// 检查进程是否受保护
BOOLEAN IsProtected(HANDLE ProcessId) {
KIRQL oldIrql;
BOOLEAN result = FALSE;
KeAcquireSpinLock(&g_ProtectLock, &oldIrql);
for (ULONG i = 0; i < g_ProtectedCount; i++) {
if (g_ProtectedPids[i] == (ULONG)(ULONG_PTR)ProcessId) {
result = TRUE;
break;
}
}
KeReleaseSpinLock(&g_ProtectLock, oldIrql);
return result;
}
// 线程回调 - 阻止远程线程
VOID ThreadProtectCallback(
HANDLE ProcessId,
HANDLE ThreadId,
BOOLEAN Create
) {
if (!Create) return;
HANDLE creatorPid = PsGetCurrentProcessId();
// 检测远程线程
if (creatorPid != ProcessId && ProcessId != (HANDLE)4) {
// 检查目标进程是否受保护
if (IsProtected(ProcessId) && g_BlockRemoteThreads) {
// 终止远程线程
PETHREAD thread;
if (NT_SUCCESS(PsLookupThreadByThreadId(ThreadId, &thread))) {
DbgPrint("[Protect] Terminating remote thread: TID=%d\n",
(ULONG)(ULONG_PTR)ThreadId);
// 注意:这种方式可能导致问题
// 更安全的方式是使用ObRegisterCallbacks
ObDereferenceObject(thread);
}
}
}
}
// 使用对象回调阻止线程句柄创建
OB_PREOP_CALLBACK_STATUS ThreadObjectPreCallback(
PVOID RegistrationContext,
POB_PRE_OPERATION_INFORMATION OperationInfo
) {
UNREFERENCED_PARAMETER(RegistrationContext);
// 只处理线程对象
if (OperationInfo->ObjectType != *PsThreadType) {
return OB_PREOP_SUCCESS;
}
PETHREAD thread = (PETHREAD)OperationInfo->Object;
HANDLE targetPid = PsGetThreadProcessId(thread);
HANDLE currentPid = PsGetCurrentProcessId();
// 检查是否跨进程访问受保护进程的线程
if (currentPid != targetPid && IsProtected(targetPid)) {
// 当前进程尝试访问受保护进程的线程
if (OperationInfo->Operation == OB_OPERATION_HANDLE_CREATE) {
// 移除危险权限
OperationInfo->Parameters->CreateHandleInformation.DesiredAccess &=
~(THREAD_TERMINATE | THREAD_SUSPEND_RESUME |
THREAD_SET_CONTEXT | THREAD_SET_INFORMATION);
}
}
return OB_PREOP_SUCCESS;
}
// 添加保护进程
BOOLEAN AddProtectedProcess(ULONG Pid) {
KIRQL oldIrql;
BOOLEAN result = FALSE;
KeAcquireSpinLock(&g_ProtectLock, &oldIrql);
if (g_ProtectedCount < 64) {
g_ProtectedPids[g_ProtectedCount++] = Pid;
result = TRUE;
}
KeReleaseSpinLock(&g_ProtectLock, oldIrql);
return result;
}
5.4、示例4:线程注入检测用户模式组件
// ThreadInjectDetect.c - 用户模式检测组件
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
typedef struct _THREAD_INJECTION_INFO {
DWORD TargetPid;
DWORD TargetTid;
DWORD InjectorPid;
CHAR InjectorName[MAX_PATH];
CHAR TargetName[MAX_PATH];
} THREAD_INJECTION_INFO;
// 获取进程名
BOOL GetProcessName(DWORD pid, CHAR* name, DWORD size) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return FALSE;
PROCESSENTRY32 pe = { sizeof(pe) };
if (Process32First(hSnapshot, &pe)) {
do {
if (pe.th32ProcessID == pid) {
strncpy_s(name, size, pe.szExeFile, _TRUNCATE);
CloseHandle(hSnapshot);
return TRUE;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
return FALSE;
}
// 从驱动获取注入事件
BOOL GetInjectionEvents(HANDLE hDevice, THREAD_INJECTION_INFO* events, DWORD maxCount, DWORD* count) {
DWORD bytesReturned;
// 假设驱动实现了相应的IOCTL
#define IOCTL_GET_INJECTIONS CTL_CODE(0x8000, 0x810, METHOD_BUFFERED, FILE_ANY_ACCESS)
return DeviceIoControl(
hDevice,
IOCTL_GET_INJECTIONS,
NULL, 0,
events, maxCount * sizeof(THREAD_INJECTION_INFO),
&bytesReturned,
NULL
);
}
// 用户模式监控线程
DWORD WINAPI MonitorThread(LPVOID lpParam) {
HANDLE hDevice = (HANDLE)lpParam;
THREAD_INJECTION_INFO events[100];
DWORD count;
while (TRUE) {
if (GetInjectionEvents(hDevice, events, 100, &count)) {
for (DWORD i = 0; i < count; i++) {
printf("[ALERT] Thread Injection Detected!\n");
printf(" Injector: %s (PID: %d)\n",
events[i].InjectorName, events[i].InjectorPid);
printf(" Target: %s (PID: %d, TID: %d)\n",
events[i].TargetName, events[i].TargetPid, events[i].TargetTid);
}
}
Sleep(1000);
}
return 0;
}
int main() {
printf("=== Thread Injection Monitor ===\n\n");
HANDLE hDevice = CreateFileA("\\\\.\\ThreadMonitor",
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("Driver not loaded.\n");
return 1;
}
printf("Monitoring for thread injections...\n");
HANDLE hMonitor = CreateThread(NULL, 0, MonitorThread, hDevice, 0, NULL);
printf("Press Enter to exit...\n");
getchar();
TerminateThread(hMonitor, 0);
CloseHandle(hMonitor);
CloseHandle(hDevice);
return 0;
}
6、课后作业
- 实现线程起始地址分析,检测异常线程
- 添加线程活动日志记录
- 实现基于规则的远程线程拦截
- 结合进程监控构建完整的注入防护系统
7、扩展阅读
- Windows线程调度机制
- DLL注入检测技术
- APC注入防护