Windows内核安全

9、线程监控

1、课程目标

  1. 掌握线程创建回调的注册和使用
  2. 实现线程创建和退出监控
  3. 学会识别远程线程注入
  4. 理解线程监控在安全防护中的作用

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, &currentProcess))) {
                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、课后作业

  1. 实现线程起始地址分析,检测异常线程
  2. 添加线程活动日志记录
  3. 实现基于规则的远程线程拦截
  4. 结合进程监控构建完整的注入防护系统

7、扩展阅读

  • Windows线程调度机制
  • DLL注入检测技术
  • APC注入防护