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注入防护