#include "../controller_private.h" #include "../injection/injection.h" #include "ipc_internal.h" #include static constexpr UINT64 ControllerSr71WriteBlockedMarker = 0x5352373157424C4Bull; // SR71WBLK static constexpr UINT64 ControllerSr71ProtectBlockedMarker = 0x5352373150424C4Bull; // SR71PBLK enum { ControllerModuleOpLoadLibraryA = 0, ControllerModuleOpLoadLibraryW = 1, ControllerModuleOpLoadLibraryExA = 2, ControllerModuleOpLoadLibraryExW = 3, ControllerModuleOpLdrLoadDll = 4, ControllerModuleOpRtlAddFunctionTable = 5, ControllerModuleOpRtlInstallFunctionTableCallback = 6, ControllerModuleOpRtlDeleteFunctionTable = 7, ControllerModuleOpCoInitializeEx = 8, ControllerModuleOpCoInitializeSecurity = 9, ControllerModuleOpCoCreateInstance = 10, ControllerModuleOpEventRegister = 11, ControllerModuleOpEventUnregister = 12, ControllerModuleOpStartTraceW = 13, ControllerModuleOpEnableTraceEx2 = 14, ControllerModuleOpCreateJobObjectW = 15, ControllerModuleOpOpenJobObjectW = 16, ControllerModuleOpAssignProcessToJobObject = 17, ControllerModuleOpSetInformationJobObject = 18 }; enum CONTROLLER_IMAGE_TAMPER_KIND { ControllerImageTamperNone = 0, ControllerImageTamperIat, ControllerImageTamperEat, ControllerImageTamperNtdll }; struct CONTROLLER_IMAGE_TAMPER_CLASSIFICATION { CONTROLLER_IMAGE_TAMPER_KIND Kind; WCHAR ModulePath[MAX_PATH]; CHAR ModuleName[MAX_PATH]; UINT64 ModuleBase; UINT32 Rva; UINT32 DirectoryRva; UINT32 DirectorySize; }; typedef struct _BK_CONTROLLER_ANALYSIS_TEARDOWN { BOOL HadState; BOOL HadAnalysisLease; BOOL LaunchOwned; BOOL PendingLaunchArmed; DWORD RootProcessId; DWORD SubscriptionCount; DWORD OwnedRangeCount; ULONGLONG SessionId; } BK_CONTROLLER_ANALYSIS_TEARDOWN, *PBK_CONTROLLER_ANALYSIS_TEARDOWN; static volatile LONG64 g_ControllerAnalysisSessionSequence = 0; static VOID ControllerClearDriverPendingLaunchBestEffort(_In_z_ PCSTR Reason); static ULONGLONG ControllerNextAnalysisSessionId(VOID) { LONG64 value = InterlockedIncrement64(&g_ControllerAnalysisSessionSequence); return (value > 0) ? (ULONGLONG)value : 1ull; } static ULONGLONG ControllerClientBeginAnalysisSessionLocked(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ DWORD RootProcessId, _In_ BOOL LaunchOwned) { BOOL newSession; if (Client == NULL || RootProcessId == 0) { return 0; } newSession = (!Client->AnalysisActive || Client->AnalysisRootProcessId != RootProcessId || Client->AnalysisSessionId == 0); if (newSession) { Client->AnalysisSessionId = ControllerNextAnalysisSessionId(); Client->AnalysisStartedTick = GetTickCount64(); Client->AnalysisLaunchOwned = FALSE; } Client->AnalysisRootProcessId = RootProcessId; Client->AnalysisActive = TRUE; if (LaunchOwned) { Client->AnalysisLaunchOwned = TRUE; } return Client->AnalysisSessionId; } static BOOL ControllerClientStopAnalysisLocked(_Inout_ BK_CONTROLLER_CLIENT *Client, _Out_opt_ PBK_CONTROLLER_ANALYSIS_TEARDOWN Teardown) { BK_CONTROLLER_ANALYSIS_TEARDOWN local; ZeroMemory(&local, sizeof(local)); if (Client == NULL) { if (Teardown != NULL) { ZeroMemory(Teardown, sizeof(*Teardown)); } return FALSE; } local.HadAnalysisLease = (Client->AnalysisActive || Client->AnalysisRootProcessId != 0 || Client->AnalysisSessionId != 0 || Client->AnalysisLaunchOwned || Client->PendingLaunchArmed || Client->PendingLaunchPid != 0); local.HadState = (local.HadAnalysisLease || Client->SubscriptionCount != 0 || Client->OwnedRangeCount != 0); local.LaunchOwned = Client->AnalysisLaunchOwned; local.PendingLaunchArmed = Client->PendingLaunchArmed; local.RootProcessId = (Client->AnalysisRootProcessId != 0) ? Client->AnalysisRootProcessId : Client->PendingLaunchPid; local.SubscriptionCount = Client->SubscriptionCount; local.OwnedRangeCount = Client->OwnedRangeCount; local.SessionId = Client->AnalysisSessionId; Client->SubscriptionCount = 0; ZeroMemory(Client->Subscriptions, sizeof(Client->Subscriptions)); ControllerClientClearPendingLaunchLocked(Client); Client->AnalysisSessionId = 0; Client->AnalysisRootProcessId = 0; Client->AnalysisLaunchOwned = FALSE; Client->AnalysisActive = FALSE; Client->AnalysisStartedTick = 0; Client->OwnedRangeCount = 0; ZeroMemory(Client->OwnedRanges, sizeof(Client->OwnedRanges)); InterlockedExchange(&Client->HookReadyMask, 0); Client->HookReadyTick = 0; if (local.HadAnalysisLease || local.SubscriptionCount != 0) { ControllerMarkDriverSubscriptionsDirty(); } if (Teardown != NULL) { *Teardown = local; } return local.HadState; } static VOID ControllerCompleteAnalysisTeardown(_In_ const BK_CONTROLLER_CLIENT *Client, _In_ const BK_CONTROLLER_ANALYSIS_TEARDOWN *Teardown, _In_z_ PCSTR Reason) { PCSTR reason = (Reason != NULL && Reason[0] != '\0') ? Reason : "analysis-teardown"; if (Client == NULL || Teardown == NULL || !Teardown->HadState) { return; } if (Teardown->HadAnalysisLease) { ControllerLog("[IPC] analysis session stopped clientPid=%lu sessionId=%llu rootPid=%lu launchOwned=%u " "subscriptions=%lu ranges=%lu reason=%s\n", Client->ProcessId, (unsigned long long)Teardown->SessionId, Teardown->RootProcessId, Teardown->LaunchOwned ? 1u : 0u, Teardown->SubscriptionCount, Teardown->OwnedRangeCount, reason); } else if (Teardown->SubscriptionCount != 0 || Teardown->OwnedRangeCount != 0) { ControllerLog("[IPC] client analysis state cleared clientPid=%lu subscriptions=%lu ranges=%lu reason=%s\n", Client->ProcessId, Teardown->SubscriptionCount, Teardown->OwnedRangeCount, reason); } if (Teardown->PendingLaunchArmed) { ControllerClearDriverPendingLaunchBestEffort(reason); } } static VOID ControllerTerminateLaunchOwnedTeardown(_In_ const BK_CONTROLLER_ANALYSIS_TEARDOWN *Teardown, _In_z_ PCSTR Reason) { PCSTR reason = (Reason != NULL && Reason[0] != '\0') ? Reason : "analysis-teardown"; if (Teardown != NULL && Teardown->HadAnalysisLease && Teardown->LaunchOwned && Teardown->RootProcessId != 0) { ControllerInjectionTerminateProcessTreeBestEffort(Teardown->RootProcessId, reason); } } static BOOL ControllerRangeOverlaps32(_In_ UINT32 Start, _In_ UINT32 Size, _In_ UINT32 OtherStart, _In_ UINT32 OtherSize) { UINT64 end = (UINT64)Start + ((Size == 0u) ? 1u : Size); UINT64 otherEnd = (UINT64)OtherStart + ((OtherSize == 0u) ? 1u : OtherSize); return Start < otherEnd && OtherStart < end; } static PCSTR ControllerImageTamperDetectionName(_In_ CONTROLLER_IMAGE_TAMPER_KIND Kind) { switch (Kind) { case ControllerImageTamperIat: return "IAT_TAMPER_SUSPECT"; case ControllerImageTamperEat: return "EAT_TAMPER_SUSPECT"; case ControllerImageTamperNtdll: return "NTDLL_IMAGE_TAMPER_SUSPECT"; default: return NULL; } } static PCWSTR ControllerImageTamperKindLabel(_In_ CONTROLLER_IMAGE_TAMPER_KIND Kind) { switch (Kind) { case ControllerImageTamperIat: return L"IAT"; case ControllerImageTamperEat: return L"EAT"; case ControllerImageTamperNtdll: return L"ntdll image"; default: return L"image"; } } static BOOL ControllerProtectAllowsWrite(_In_ UINT32 Protect) { UINT32 baseProtect = Protect & 0xFFu; return baseProtect == PAGE_READWRITE || baseProtect == PAGE_WRITECOPY || baseProtect == PAGE_EXECUTE_READWRITE || baseProtect == PAGE_EXECUTE_WRITECOPY; } static BOOL ControllerProtectAllowsExecute(_In_ UINT32 Protect) { UINT32 baseProtect = Protect & 0xFFu; return baseProtect == PAGE_EXECUTE || baseProtect == PAGE_EXECUTE_READ || baseProtect == PAGE_EXECUTE_READWRITE || baseProtect == PAGE_EXECUTE_WRITECOPY; } static PCWSTR ControllerBaseNameW(_In_opt_z_ PCWSTR Path) { PCWSTR slash; PCWSTR altSlash; if (Path == NULL) { return L""; } slash = wcsrchr(Path, L'\\'); altSlash = wcsrchr(Path, L'/'); if (altSlash != NULL && (slash == NULL || altSlash > slash)) { slash = altSlash; } return (slash != NULL) ? slash + 1 : Path; } static BOOL ControllerPidImageNameEquals(_In_ DWORD ProcessId, _In_z_ PCWSTR ExpectedImageName) { HANDLE process; WCHAR imagePath[MAX_PATH * 2]; DWORD imageChars; HANDLE snapshot; PROCESSENTRY32W entry; if (ProcessId == 0 || ExpectedImageName == NULL || ExpectedImageName[0] == L'\0') { return FALSE; } imageChars = RTL_NUMBER_OF(imagePath); imagePath[0] = L'\0'; process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ProcessId); if (process != NULL) { if (QueryFullProcessImageNameW(process, 0, imagePath, &imageChars) && _wcsicmp(ControllerBaseNameW(imagePath), ExpectedImageName) == 0) { CloseHandle(process); return TRUE; } CloseHandle(process); } snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot == INVALID_HANDLE_VALUE) { return FALSE; } ZeroMemory(&entry, sizeof(entry)); entry.dwSize = sizeof(entry); if (!Process32FirstW(snapshot, &entry)) { CloseHandle(snapshot); return FALSE; } do { if (entry.th32ProcessID == ProcessId && _wcsicmp(entry.szExeFile, ExpectedImageName) == 0) { CloseHandle(snapshot); return TRUE; } } while (Process32NextW(snapshot, &entry)); CloseHandle(snapshot); return FALSE; } static BOOL ControllerQueryProcessMemoryBasic(_In_ DWORD ProcessId, _In_ UINT64 Address, _Out_ MEMORY_BASIC_INFORMATION *Mbi) { HANDLE process; SIZE_T queried; if (ProcessId == 0 || Address == 0 || Mbi == NULL) { return FALSE; } process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, ProcessId); if (process == NULL) { return FALSE; } ZeroMemory(Mbi, sizeof(*Mbi)); queried = VirtualQueryEx(process, (LPCVOID)(ULONG_PTR)Address, Mbi, sizeof(*Mbi)); CloseHandle(process); return queried == sizeof(*Mbi); } static BOOL ControllerFunctionTableBaseLooksAbusive(_In_ DWORD ProcessId, _In_ UINT64 BaseAddress, _Out_opt_ MEMORY_BASIC_INFORMATION *MbiOut, _Out_writes_z_(ReasonChars) PWSTR Reason, _In_ size_t ReasonChars) { MEMORY_BASIC_INFORMATION mbi; BOOL queried; BOOL executable; BOOL writable; if (Reason != NULL && ReasonChars != 0) { Reason[0] = L'\0'; } if (MbiOut != NULL) { ZeroMemory(MbiOut, sizeof(*MbiOut)); } queried = ControllerQueryProcessMemoryBasic(ProcessId, BaseAddress, &mbi); if (!queried) { if (Reason != NULL && ReasonChars != 0) { (void)StringCchCopyW(Reason, ReasonChars, L"unmapped function-table base"); } return TRUE; } if (MbiOut != NULL) { *MbiOut = mbi; } executable = ControllerProtectAllowsExecute((UINT32)mbi.Protect); writable = ControllerProtectAllowsWrite((UINT32)mbi.Protect); if (mbi.State != MEM_COMMIT) { if (Reason != NULL && ReasonChars != 0) { (void)StringCchPrintfW(Reason, ReasonChars, L"function-table base is not committed state=0x%lX", (unsigned long)mbi.State); } return TRUE; } if (executable && writable) { if (Reason != NULL && ReasonChars != 0) { (void)StringCchPrintfW(Reason, ReasonChars, L"function-table base is writable executable protect=0x%lX", (unsigned long)mbi.Protect); } return TRUE; } if (mbi.Type == MEM_PRIVATE && executable) { if (Reason != NULL && ReasonChars != 0) { (void)StringCchPrintfW(Reason, ReasonChars, L"function-table base is private executable protect=0x%lX", (unsigned long)mbi.Protect); } return TRUE; } return FALSE; } static BOOL ControllerFindModuleForAddress(_In_ DWORD ProcessId, _In_ UINT64 Address, _Out_ MODULEENTRY32W *Module) { HANDLE snapshot; MODULEENTRY32W entry; if (ProcessId == 0 || Address == 0 || Module == NULL) { return FALSE; } snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, ProcessId); if (snapshot == INVALID_HANDLE_VALUE) { return FALSE; } ZeroMemory(&entry, sizeof(entry)); entry.dwSize = sizeof(entry); if (!Module32FirstW(snapshot, &entry)) { CloseHandle(snapshot); return FALSE; } do { UINT64 base = (UINT64)(ULONG_PTR)entry.modBaseAddr; UINT64 end = base + entry.modBaseSize; if (Address >= base && Address < end) { *Module = entry; CloseHandle(snapshot); return TRUE; } } while (Module32NextW(snapshot, &entry)); CloseHandle(snapshot); return FALSE; } static BOOL ControllerReadRemoteExact(_In_ HANDLE Process, _In_ UINT64 Address, _Out_writes_bytes_(Size) PVOID Buffer, _In_ SIZE_T Size) { SIZE_T bytesRead = 0; return Process != NULL && Process != INVALID_HANDLE_VALUE && Address != 0 && Buffer != NULL && Size != 0 && ReadProcessMemory(Process, (LPCVOID)(ULONG_PTR)Address, Buffer, Size, &bytesRead) && bytesRead == Size; } static BOOL ControllerClassifyImageTamperTarget(_In_ DWORD ProcessId, _In_ UINT64 Address, _In_ UINT64 Size, _Out_ CONTROLLER_IMAGE_TAMPER_CLASSIFICATION *Out) { HANDLE process; MODULEENTRY32W module; IMAGE_DOS_HEADER dos; DWORD signature = 0; IMAGE_FILE_HEADER fileHeader; WORD optionalMagic = 0; IMAGE_DATA_DIRECTORY directories[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; UINT64 moduleBase; UINT32 rva; UINT32 span; BOOL isNtdll; if (Out == NULL) { return FALSE; } ZeroMemory(Out, sizeof(*Out)); Out->Kind = ControllerImageTamperNone; if (ProcessId == 0 || Address == 0 || !ControllerFindModuleForAddress(ProcessId, Address, &module)) { return FALSE; } moduleBase = (UINT64)(ULONG_PTR)module.modBaseAddr; if (Address < moduleBase || Address - moduleBase > 0xFFFFFFFFull) { return FALSE; } rva = (UINT32)(Address - moduleBase); span = (Size == 0 || Size > 0xFFFFFFFFull) ? 1u : (UINT32)Size; isNtdll = (_wcsicmp(module.szModule, L"ntdll.dll") == 0); process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, ProcessId); if (process == NULL) { return FALSE; } ZeroMemory(directories, sizeof(directories)); if (!ControllerReadRemoteExact(process, moduleBase, &dos, sizeof(dos)) || dos.e_magic != IMAGE_DOS_SIGNATURE || dos.e_lfanew <= 0 || dos.e_lfanew > 0x100000) { CloseHandle(process); return FALSE; } UINT64 ntBase = moduleBase + (UINT64)dos.e_lfanew; if (!ControllerReadRemoteExact(process, ntBase, &signature, sizeof(signature)) || signature != IMAGE_NT_SIGNATURE || !ControllerReadRemoteExact(process, ntBase + sizeof(signature), &fileHeader, sizeof(fileHeader)) || !ControllerReadRemoteExact(process, ntBase + sizeof(signature) + sizeof(fileHeader), &optionalMagic, sizeof(optionalMagic))) { CloseHandle(process); return FALSE; } UINT64 optionalBase = ntBase + sizeof(signature) + sizeof(fileHeader); if (optionalMagic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { IMAGE_OPTIONAL_HEADER64 optionalHeader; if (!ControllerReadRemoteExact(process, optionalBase, &optionalHeader, sizeof(optionalHeader))) { CloseHandle(process); return FALSE; } CopyMemory(directories, optionalHeader.DataDirectory, sizeof(directories)); } else if (optionalMagic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { IMAGE_OPTIONAL_HEADER32 optionalHeader; if (!ControllerReadRemoteExact(process, optionalBase, &optionalHeader, sizeof(optionalHeader))) { CloseHandle(process); return FALSE; } CopyMemory(directories, optionalHeader.DataDirectory, sizeof(directories)); } else { CloseHandle(process); return FALSE; } CloseHandle(process); Out->ModuleBase = moduleBase; Out->Rva = rva; (void)StringCchCopyW(Out->ModulePath, RTL_NUMBER_OF(Out->ModulePath), module.szExePath); WideCharToMultiByte(CP_ACP, 0, module.szModule, -1, Out->ModuleName, RTL_NUMBER_OF(Out->ModuleName), NULL, NULL); const IMAGE_DATA_DIRECTORY &iat = directories[IMAGE_DIRECTORY_ENTRY_IAT]; if (iat.VirtualAddress != 0 && iat.Size != 0 && ControllerRangeOverlaps32(rva, span, iat.VirtualAddress, iat.Size)) { Out->Kind = ControllerImageTamperIat; Out->DirectoryRva = iat.VirtualAddress; Out->DirectorySize = iat.Size; return TRUE; } const IMAGE_DATA_DIRECTORY &exports = directories[IMAGE_DIRECTORY_ENTRY_EXPORT]; if (exports.VirtualAddress != 0 && exports.Size != 0 && ControllerRangeOverlaps32(rva, span, exports.VirtualAddress, exports.Size)) { Out->Kind = ControllerImageTamperEat; Out->DirectoryRva = exports.VirtualAddress; Out->DirectorySize = exports.Size; return TRUE; } if (isNtdll) { Out->Kind = ControllerImageTamperNtdll; return TRUE; } return FALSE; } static BOOL ControllerApplyImageTamperDetection(_Inout_ BKIPC_ETW_EVENT *Mapped, _In_ const CONTROLLER_IMAGE_TAMPER_CLASSIFICATION *Classification, _In_z_ PCWSTR Action, _In_ UINT64 Address, _In_ UINT64 Size) { PCSTR detectionName; if (Mapped == NULL || Classification == NULL || Classification->Kind == ControllerImageTamperNone) { return FALSE; } detectionName = ControllerImageTamperDetectionName(Classification->Kind); if (detectionName == NULL) { return FALSE; } (void)StringCchCopyA(Mapped->DetectionName, RTL_NUMBER_OF(Mapped->DetectionName), detectionName); Mapped->Severity = (Classification->Kind == ControllerImageTamperNtdll) ? 7u : 8u; (void)StringCchPrintfW( Mapped->Reason, RTL_NUMBER_OF(Mapped->Reason), L"%ws tamper action=%ws module=%S base=0x%llX rva=0x%X size=0x%llX dirRva=0x%X dirSize=0x%X path=%ws", ControllerImageTamperKindLabel(Classification->Kind), Action, Classification->ModuleName[0] != '\0' ? Classification->ModuleName : "", (unsigned long long)Address, Classification->Rva, (unsigned long long)Size, Classification->DirectoryRva, Classification->DirectorySize, Classification->ModulePath[0] != L'\0' ? Classification->ModulePath : L""); return TRUE; } static BOOL ControllerValidatePacket(_In_ const BKIPC_PACKET *Packet, _In_ UINT16 ExpectedType) { if (Packet == NULL) { return FALSE; } if (Packet->Magic != BKIPC_MAGIC) { return FALSE; } if (Packet->Version != BKIPC_VERSION) { return FALSE; } if (Packet->PacketType != ExpectedType) { return FALSE; } return TRUE; } static VOID ControllerPrepareResponse(_In_ const BKIPC_PACKET *Request, _Out_ BKIPC_PACKET *Response) { ZeroMemory(Response, sizeof(*Response)); Response->Magic = BKIPC_MAGIC; Response->Version = BKIPC_VERSION; Response->PacketType = BlackbirdIpcPacketResponse; Response->Command = Request->Command; Response->Sequence = Request->Sequence; Response->Status = ERROR_SUCCESS; } static BOOL ControllerCommandAllowedForRole(_In_ DWORD ClientRole, _In_ UINT32 Command) { switch (ClientRole) { case BkctlrClientRoleHook: return (Command == BlackbirdIpcCommandHandshake || Command == BlackbirdIpcCommandPublishHookEvent || Command == BlackbirdIpcCommandNotifyHookReady || Command == BlackbirdIpcCommandRegisterInstrumentationRange || Command == BlackbirdIpcCommandRegisterHookPatch); case BkctlrClientRoleControl: return (Command != BlackbirdIpcCommandPublishHookEvent && Command != BlackbirdIpcCommandNotifyHookReady && Command != BlackbirdIpcCommandRegisterInstrumentationRange && Command != BlackbirdIpcCommandRegisterHookPatch); default: return FALSE; } } _Success_(return) static BOOL ControllerQueryProcessTokenUser(_In_ DWORD ProcessId, _Outptr_result_bytebuffer_(*TokenBytesOut) PTOKEN_USER *TokenUserOut, _Out_ DWORD *TokenBytesOut) { HANDLE process = NULL; HANDLE token = NULL; DWORD tokenBytes = 0; PTOKEN_USER tokenUser = NULL; if (ProcessId == 0 || TokenUserOut == NULL || TokenBytesOut == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } *TokenUserOut = NULL; *TokenBytesOut = 0; process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, ProcessId); if (process == NULL) { return FALSE; } if (!OpenProcessToken(process, TOKEN_QUERY, &token)) { DWORD err = GetLastError(); CloseHandle(process); SetLastError(err); return FALSE; } (void)GetTokenInformation(token, TokenUser, NULL, 0, &tokenBytes); if (tokenBytes == 0 || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { DWORD err = GetLastError(); CloseHandle(token); CloseHandle(process); SetLastError(err == ERROR_SUCCESS ? ERROR_BAD_LENGTH : err); return FALSE; } tokenUser = (PTOKEN_USER)calloc(1, tokenBytes); if (tokenUser == NULL) { CloseHandle(token); CloseHandle(process); SetLastError(ERROR_OUTOFMEMORY); return FALSE; } if (!GetTokenInformation(token, TokenUser, tokenUser, tokenBytes, &tokenBytes)) { DWORD err = GetLastError(); free(tokenUser); CloseHandle(token); CloseHandle(process); SetLastError(err); return FALSE; } CloseHandle(token); CloseHandle(process); *TokenUserOut = tokenUser; *TokenBytesOut = tokenBytes; return TRUE; } _Success_(return) static BOOL ControllerProcessesShareOwnerSid(_In_ DWORD ProcessIdA, _In_ DWORD ProcessIdB, _Out_ BOOL *SameOwner) { PTOKEN_USER tokenUserA = NULL; PTOKEN_USER tokenUserB = NULL; DWORD tokenUserABytes = 0; DWORD tokenUserBBytes = 0; if (SameOwner == NULL || ProcessIdA == 0 || ProcessIdB == 0) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } *SameOwner = FALSE; if (!ControllerQueryProcessTokenUser(ProcessIdA, &tokenUserA, &tokenUserABytes)) { return FALSE; } if (!ControllerQueryProcessTokenUser(ProcessIdB, &tokenUserB, &tokenUserBBytes)) { DWORD err = GetLastError(); free(tokenUserA); SetLastError(err); return FALSE; } *SameOwner = EqualSid(tokenUserA->User.Sid, tokenUserB->User.Sid) ? TRUE : FALSE; free(tokenUserA); free(tokenUserB); return TRUE; } static BOOL ControllerClientCanMonitorPid(_In_ const BK_CONTROLLER_CLIENT *Client, _In_ DWORD TargetPid, _Inout_opt_ BOOL *PrivilegeResolved, _Inout_opt_ BOOL *IsPrivileged) { BOOL privileged = FALSE; DWORD targetSessionId = 0; BOOL sameOwner = FALSE; if (Client == NULL || TargetPid == 0 || Client->ProcessId == 0) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } if (TargetPid == Client->ProcessId) { return TRUE; } if (PrivilegeResolved != NULL && IsPrivileged != NULL && *PrivilegeResolved) { privileged = *IsPrivileged; } else { if (!ControllerClientIsPrivileged(Client, &privileged)) { DWORD err = GetLastError(); if (err == ERROR_NO_IMPERSONATION_TOKEN) { privileged = TRUE; } else { return FALSE; } } if (PrivilegeResolved != NULL) { *PrivilegeResolved = TRUE; } if (IsPrivileged != NULL) { *IsPrivileged = privileged; } } if (privileged) { return TRUE; } if (!ProcessIdToSessionId(TargetPid, &targetSessionId)) { return FALSE; } if (targetSessionId != Client->SessionId) { SetLastError(ERROR_ACCESS_DENIED); return FALSE; } if (!ControllerProcessesShareOwnerSid(Client->ProcessId, TargetPid, &sameOwner)) { return FALSE; } if (!sameOwner) { SetLastError(ERROR_ACCESS_DENIED); return FALSE; } return TRUE; } static DWORD ControllerClientSubscribe(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ const BK_SUBSCRIBE_REQUEST *Request) { DWORD i; if (Client == NULL || Request == NULL || Request->ProcessId == 0 || !ControllerIsValidStreamMask(Request->StreamMask)) { return ERROR_INVALID_PARAMETER; } if (!ControllerClientCanMonitorPid(Client, Request->ProcessId, NULL, NULL)) { DWORD err = GetLastError(); return (err == ERROR_SUCCESS) ? ERROR_ACCESS_DENIED : err; } EnterCriticalSection(&Client->Lock); for (i = 0; i < Client->SubscriptionCount; ++i) { if (Client->Subscriptions[i].ProcessId == Request->ProcessId) { Client->Subscriptions[i].StreamMask |= Request->StreamMask; if (Client->Subscriptions[i].Dynamic) { Client->Subscriptions[i].Dynamic = FALSE; Client->Subscriptions[i].SourceProcessId = 0; Client->Subscriptions[i].Depth = 0; Client->Subscriptions[i].LastSeenTick = 0; } LeaveCriticalSection(&Client->Lock); ControllerLog("[IPC] subscribe update clientPid=%lu targetPid=%lu streamMask=0x%08lX\n", Client->ProcessId, Request->ProcessId, Request->StreamMask); if (!ControllerRequestDriverSubscriptionApply(TRUE, BK_CONTROLLER_SUBSCRIPTION_APPLY_SYNC_TIMEOUT_MS)) { return GetLastError(); } return ERROR_SUCCESS; } } if (Client->SubscriptionCount >= BK_CONTROLLER_MAX_CLIENT_SUBSCRIPTIONS) { LeaveCriticalSection(&Client->Lock); return ERROR_INSUFFICIENT_BUFFER; } Client->Subscriptions[Client->SubscriptionCount].ProcessId = Request->ProcessId; Client->Subscriptions[Client->SubscriptionCount].StreamMask = Request->StreamMask; Client->Subscriptions[Client->SubscriptionCount].Dynamic = FALSE; Client->Subscriptions[Client->SubscriptionCount].SourceProcessId = 0; Client->Subscriptions[Client->SubscriptionCount].Depth = 0; Client->Subscriptions[Client->SubscriptionCount].LastSeenTick = 0; Client->SubscriptionCount += 1; LeaveCriticalSection(&Client->Lock); ControllerLog("[IPC] subscribe add clientPid=%lu targetPid=%lu streamMask=0x%08lX\n", Client->ProcessId, Request->ProcessId, Request->StreamMask); if (!ControllerRequestDriverSubscriptionApply(TRUE, BK_CONTROLLER_SUBSCRIPTION_APPLY_SYNC_TIMEOUT_MS)) { return GetLastError(); } return ERROR_SUCCESS; } static DWORD ControllerClientUnsubscribe(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ const BK_UNSUBSCRIBE_REQUEST *Request) { DWORD i; DWORD removedPid = 0; BOOL changed = FALSE; if (Client == NULL || Request == NULL || Request->ProcessId == 0) { return ERROR_INVALID_PARAMETER; } EnterCriticalSection(&Client->Lock); for (i = 0; i < Client->SubscriptionCount; ++i) { if (Client->Subscriptions[i].ProcessId == Request->ProcessId) { removedPid = Client->Subscriptions[i].ProcessId; ControllerRemoveSubscriptionAtLocked(Client, i); changed = TRUE; if (removedPid != 0) { changed |= ControllerDropDynamicDescendantsLocked(Client, removedPid); } LeaveCriticalSection(&Client->Lock); ControllerLog("[IPC] unsubscribe clientPid=%lu targetPid=%lu\n", Client->ProcessId, Request->ProcessId); if (changed) { (void)ControllerRequestDriverSubscriptionApply(FALSE, 0); } return ERROR_SUCCESS; } } LeaveCriticalSection(&Client->Lock); return ERROR_NOT_FOUND; } static DWORD ControllerClientSetPids(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ const BK_SET_PIDS_REQUEST *Request) { DWORD i; DWORD primaryPid = 0; DWORD appliedCount = 0; DWORD replacedLaunchOwnedRootPid = 0; ULONGLONG sessionId = 0; BOOL privilegeResolved = FALSE; BOOL isPrivileged = FALSE; BK_CONTROLLER_ANALYSIS_TEARDOWN teardown; if (Client == NULL || Request == NULL || Request->ProcessCount > BK_MAX_PID_LIST || !ControllerIsValidStreamMask(Request->StreamMask)) { return ERROR_INVALID_PARAMETER; } if (Request->ProcessCount == 0) { EnterCriticalSection(&Client->Lock); ControllerClientStopAnalysisLocked(Client, &teardown); LeaveCriticalSection(&Client->Lock); ControllerCompleteAnalysisTeardown(Client, &teardown, "set-pids-empty"); if (teardown.HadAnalysisLease || teardown.SubscriptionCount != 0) { (void)ControllerRequestDriverSubscriptionApply(FALSE, 0); } ControllerTerminateLaunchOwnedTeardown(&teardown, "set-pids-empty"); return ERROR_SUCCESS; } for (i = 0; i < Request->ProcessCount; ++i) { DWORD pid = Request->ProcessIds[i]; if (pid == 0) { continue; } if (primaryPid == 0) { primaryPid = pid; } if (!ControllerClientCanMonitorPid(Client, pid, &privilegeResolved, &isPrivileged)) { DWORD err = GetLastError(); return (err == ERROR_SUCCESS) ? ERROR_ACCESS_DENIED : err; } } if (primaryPid == 0) { return ERROR_INVALID_PARAMETER; } EnterCriticalSection(&Client->Lock); if (Client->AnalysisActive && Client->AnalysisLaunchOwned && Client->AnalysisRootProcessId != 0 && Client->AnalysisRootProcessId != primaryPid) { replacedLaunchOwnedRootPid = Client->AnalysisRootProcessId; } Client->SubscriptionCount = 0; ZeroMemory(Client->Subscriptions, sizeof(Client->Subscriptions)); for (i = 0; i < Request->ProcessCount; ++i) { DWORD pid = Request->ProcessIds[i]; DWORD j; BOOL seen = FALSE; if (pid == 0) { continue; } for (j = 0; j < Client->SubscriptionCount; ++j) { if (Client->Subscriptions[j].ProcessId == pid) { Client->Subscriptions[j].StreamMask |= Request->StreamMask; Client->Subscriptions[j].Dynamic = FALSE; Client->Subscriptions[j].SourceProcessId = 0; Client->Subscriptions[j].Depth = 0; Client->Subscriptions[j].LastSeenTick = 0; seen = TRUE; break; } } if (!seen && Client->SubscriptionCount < BK_CONTROLLER_MAX_CLIENT_SUBSCRIPTIONS) { Client->Subscriptions[Client->SubscriptionCount].ProcessId = pid; Client->Subscriptions[Client->SubscriptionCount].StreamMask = Request->StreamMask; Client->Subscriptions[Client->SubscriptionCount].Dynamic = FALSE; Client->Subscriptions[Client->SubscriptionCount].SourceProcessId = 0; Client->Subscriptions[Client->SubscriptionCount].Depth = 0; Client->Subscriptions[Client->SubscriptionCount].LastSeenTick = 0; Client->SubscriptionCount += 1; } } appliedCount = Client->SubscriptionCount; if (appliedCount != 0 && (Client->AnalysisActive || Client->AnalysisRootProcessId != 0 || Client->AnalysisSessionId != 0 || Client->AnalysisLaunchOwned || Client->PendingLaunchPid != 0)) { sessionId = ControllerClientBeginAnalysisSessionLocked(Client, primaryPid, FALSE); } LeaveCriticalSection(&Client->Lock); if (appliedCount == 0) { return ERROR_INVALID_PARAMETER; } ControllerLog("[IPC] set-pids clientPid=%lu sessionId=%llu rootPid=%lu count=%lu streamMask=0x%08lX\n", Client->ProcessId, (unsigned long long)sessionId, primaryPid, appliedCount, Request->StreamMask); if (!ControllerRequestDriverSubscriptionApply(TRUE, BK_CONTROLLER_SUBSCRIPTION_APPLY_SYNC_TIMEOUT_MS)) { DWORD applyErr = GetLastError(); if (replacedLaunchOwnedRootPid != 0) { ControllerInjectionTerminateProcessTreeBestEffort(replacedLaunchOwnedRootPid, "analysis-replaced"); } return applyErr; } if (replacedLaunchOwnedRootPid != 0) { ControllerInjectionTerminateProcessTreeBestEffort(replacedLaunchOwnedRootPid, "analysis-replaced"); } return ERROR_SUCCESS; } static DWORD ControllerClientGetEvent(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ DWORD TimeoutMs, _Out_ BK_EVENT_RECORD *Record) { ULONGLONG startTick; if (Client == NULL || Record == NULL) { return ERROR_INVALID_PARAMETER; } startTick = GetTickCount64(); for (;;) { BOOL dequeued = FALSE; HANDLE dataEvent = NULL; HANDLE waitHandles[2]; DWORD waitCount = 0; DWORD waitMs = INFINITE; DWORD waitResult; ULONGLONG elapsed = 0; EnterCriticalSection(&Client->Lock); if (Client->SharedRingEnabled && Client->IoctlSharedDataEvent != NULL && Client->IoctlSharedDataEvent != INVALID_HANDLE_VALUE) { dataEvent = Client->IoctlSharedDataEvent; } else { dataEvent = Client->IoctlQueueDataEvent; } dequeued = ControllerClientDequeueRecordLocked(Client, Record); LeaveCriticalSection(&Client->Lock); if (dequeued) { return ERROR_SUCCESS; } if (ControllerShouldStop()) { return ERROR_OPERATION_ABORTED; } if (TimeoutMs != INFINITE) { elapsed = GetTickCount64() - startTick; if (elapsed >= TimeoutMs) { return ERROR_NO_MORE_ITEMS; } waitMs = (DWORD)((ULONGLONG)TimeoutMs - elapsed); } if (g_StopEvent != NULL) { waitHandles[waitCount++] = g_StopEvent; } if (dataEvent != NULL && dataEvent != INVALID_HANDLE_VALUE) { waitHandles[waitCount++] = dataEvent; } if (waitCount == 0) { if (waitMs == 0) { return ERROR_NO_MORE_ITEMS; } Sleep((waitMs == INFINITE || waitMs > 2u) ? 2u : waitMs); continue; } waitResult = WaitForMultipleObjects(waitCount, waitHandles, FALSE, waitMs); if (waitResult == WAIT_OBJECT_0 && g_StopEvent != NULL) { return ERROR_OPERATION_ABORTED; } if (waitResult == WAIT_TIMEOUT) { if (TimeoutMs != INFINITE) { return ERROR_NO_MORE_ITEMS; } continue; } if (waitResult == WAIT_FAILED) { DWORD err = GetLastError(); return (err == ERROR_SUCCESS) ? ERROR_GEN_FAILURE : err; } } } static DWORD ControllerClientGetEtwEvent(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ DWORD TimeoutMs, _Out_ BKIPC_ETW_EVENT *Event) { ULONGLONG startTick; if (Client == NULL || Event == NULL) { return ERROR_INVALID_PARAMETER; } startTick = GetTickCount64(); for (;;) { BOOL dequeued = FALSE; HANDLE dataEvent = NULL; HANDLE waitHandles[2]; DWORD waitCount = 0; DWORD waitMs = INFINITE; DWORD waitResult; ULONGLONG elapsed = 0; EnterCriticalSection(&Client->Lock); if (Client->SharedRingEnabled && Client->EtwSharedDataEvent != NULL && Client->EtwSharedDataEvent != INVALID_HANDLE_VALUE) { dataEvent = Client->EtwSharedDataEvent; } else { dataEvent = Client->EtwQueueDataEvent; } dequeued = ControllerClientDequeueEtwEventLocked(Client, Event); LeaveCriticalSection(&Client->Lock); if (dequeued) { return ERROR_SUCCESS; } if (ControllerShouldStop()) { return ERROR_OPERATION_ABORTED; } if (TimeoutMs != INFINITE) { elapsed = GetTickCount64() - startTick; if (elapsed >= TimeoutMs) { return ERROR_NO_MORE_ITEMS; } waitMs = (DWORD)((ULONGLONG)TimeoutMs - elapsed); } if (g_StopEvent != NULL) { waitHandles[waitCount++] = g_StopEvent; } if (dataEvent != NULL && dataEvent != INVALID_HANDLE_VALUE) { waitHandles[waitCount++] = dataEvent; } if (waitCount == 0) { if (waitMs == 0) { return ERROR_NO_MORE_ITEMS; } Sleep((waitMs == INFINITE || waitMs > 2u) ? 2u : waitMs); continue; } waitResult = WaitForMultipleObjects(waitCount, waitHandles, FALSE, waitMs); if (waitResult == WAIT_OBJECT_0 && g_StopEvent != NULL) { return ERROR_OPERATION_ABORTED; } if (waitResult == WAIT_TIMEOUT) { if (TimeoutMs != INFINITE) { return ERROR_NO_MORE_ITEMS; } continue; } if (waitResult == WAIT_FAILED) { DWORD err = GetLastError(); return (err == ERROR_SUCCESS) ? ERROR_GEN_FAILURE : err; } } } static DWORD ControllerClientPublishHookEvent(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ const BKIPC_HOOK_EVENT *HookEvent) { BKIPC_ETW_EVENT mapped; DWORD eventPid = 0; DWORD threadId = 0; CHAR apiName[BKIPC_MAX_HOOK_API_NAME]; CHAR moduleName[BKIPC_MAX_HOOK_MODULE_NAME]; PCSTR kindName; int wideChars; UINT32 argCount; UINT32 sampleSize; BOOL integrityTampered = FALSE; BOOL integrityAmsiPatch = FALSE; BOOL integrityEtwPatch = FALSE; BOOL memoryEvent = FALSE; BOOL specializedEvent = FALSE; if (Client == NULL || HookEvent == NULL) { return ERROR_INVALID_PARAMETER; } if (HookEvent->Kind == BlackbirdIpcHookEventUnknown || HookEvent->Kind > BlackbirdIpcHookEventModule) { return ERROR_INVALID_PARAMETER; } eventPid = (HookEvent->ProcessId != 0) ? HookEvent->ProcessId : Client->ProcessId; if (eventPid == 0) { return ERROR_INVALID_PARAMETER; } if (eventPid != Client->ProcessId) { return ERROR_ACCESS_DENIED; } threadId = HookEvent->ThreadId; ControllerSanitizeAnsiLabel(HookEvent->ApiName, apiName, RTL_NUMBER_OF(apiName)); ControllerSanitizeAnsiLabel(HookEvent->ModuleName, moduleName, RTL_NUMBER_OF(moduleName)); kindName = ControllerHookEventKindName(HookEvent->Kind); argCount = (HookEvent->ArgCount > RTL_NUMBER_OF(HookEvent->Args)) ? RTL_NUMBER_OF(HookEvent->Args) : HookEvent->ArgCount; sampleSize = (HookEvent->DataSize > RTL_NUMBER_OF(HookEvent->DataSample)) ? RTL_NUMBER_OF(HookEvent->DataSample) : HookEvent->DataSize; ZeroMemory(&mapped, sizeof(mapped)); mapped.Source = BlackbirdIpcEtwSourceUserHook; mapped.Family = BlackbirdIpcEtwFamilyUserHook; mapped.EventId = (UINT16)(HookEvent->Operation & 0xFFFFu); mapped.Opcode = (UINT16)(HookEvent->Kind & 0xFFFFu); mapped.Task = 0; mapped.EventProcessId = eventPid; mapped.EventThreadId = threadId; mapped.Severity = 1; mapped.Flags = 0; mapped.ProcessId = eventPid; mapped.ThreadId = threadId; mapped.CallerPid = eventPid; mapped.TargetPid = (HookEvent->Context0 <= 0xFFFFFFFFull) ? HookEvent->Context0 : 0; if (HookEvent->Kind == BlackbirdIpcHookEventIntegrity) { integrityAmsiPatch = (HookEvent->Operation == BK_HOOK_EVENT_OP_AMSI_PATCH); integrityEtwPatch = (HookEvent->Operation == BK_HOOK_EVENT_OP_ETW_PATCH); if (HookEvent->Operation == BK_HOOK_EVENT_OP_LAUNCH_GATE_ENTRY || HookEvent->Operation == BK_HOOK_EVENT_OP_LAUNCH_GATE_TLS_CALLBACK) { integrityTampered = FALSE; mapped.Severity = 1u; } else if (integrityAmsiPatch || integrityEtwPatch) { integrityTampered = (HookEvent->Context0 != 0ull); mapped.Severity = integrityTampered ? 8u : 1u; } else { integrityTampered = (HookEvent->Context0 != 0ull || HookEvent->Operation != 0u); mapped.Severity = integrityTampered ? 7u : 1u; } mapped.TargetPid = eventPid; } if (moduleName[0] != '\0') { (void)StringCchCopyA(mapped.ClassName, RTL_NUMBER_OF(mapped.ClassName), moduleName); } else { (void)StringCchCopyA(mapped.ClassName, RTL_NUMBER_OF(mapped.ClassName), kindName); } if (apiName[0] != '\0') { (void)StringCchCopyA(mapped.Operation, RTL_NUMBER_OF(mapped.Operation), apiName); } else { (void)StringCchPrintfA(mapped.Operation, RTL_NUMBER_OF(mapped.Operation), "%sOp%lu", kindName, (unsigned long)HookEvent->Operation); } wideChars = MultiByteToWideChar(CP_ACP, 0, (apiName[0] != '\0') ? apiName : mapped.Operation, -1, mapped.EventName, RTL_NUMBER_OF(mapped.EventName)); if (wideChars <= 0) { (void)StringCchPrintfW(mapped.EventName, RTL_NUMBER_OF(mapped.EventName), L"%S", mapped.Operation); } if (HookEvent->Kind == BlackbirdIpcHookEventIntegrity) { if (HookEvent->Operation == BK_HOOK_EVENT_OP_LAUNCH_GATE_ENTRY || HookEvent->Operation == BK_HOOK_EVENT_OP_LAUNCH_GATE_TLS_CALLBACK) { BOOL tlsTrap = HookEvent->Operation == BK_HOOK_EVENT_OP_LAUNCH_GATE_TLS_CALLBACK; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), tlsTrap ? "BK_TLS_CALLBACK_TRAP" : "BK_LAUNCH_GATE_TRAP"); (void)StringCchCopyA(mapped.Operation, RTL_NUMBER_OF(mapped.Operation), tlsTrap ? "TlsCallbackTrap" : "LaunchGateEntryTrap"); (void)StringCchPrintfW(mapped.EventName, RTL_NUMBER_OF(mapped.EventName), L"%S", mapped.Operation); (void)StringCchCopyA(mapped.ClassName, RTL_NUMBER_OF(mapped.ClassName), "BK Instrument"); (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"BK launch gate trapped %ws before target user code continued address=0x%llX page=0x%llX index=%llu", tlsTrap ? L"TLS callback" : L"entry point", (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, (unsigned long long)HookEvent->Context3); } else if (integrityAmsiPatch || integrityEtwPatch) { PCSTR detectionName = integrityAmsiPatch ? "AMSI_PATCH_TAMPERED" : "ETW_PATCH_TAMPERED"; PCSTR okDetectionName = integrityAmsiPatch ? "AMSI_PATCH_OK" : "ETW_PATCH_OK"; PCSTR eventLabel = integrityAmsiPatch ? "AmsiPatchTamper" : "EtwPatchTamper"; PCSTR okEventLabel = integrityAmsiPatch ? "AmsiPatchOk" : "EtwPatchOk"; PCWSTR reasonLabel = integrityAmsiPatch ? L"amsi" : L"etw"; if (integrityTampered) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), detectionName); (void)StringCchCopyA(mapped.Operation, RTL_NUMBER_OF(mapped.Operation), eventLabel); (void)StringCchPrintfW(mapped.EventName, RTL_NUMBER_OF(mapped.EventName), L"%S", eventLabel); } else { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), okDetectionName); (void)StringCchCopyA(mapped.Operation, RTL_NUMBER_OF(mapped.Operation), okEventLabel); (void)StringCchPrintfW(mapped.EventName, RTL_NUMBER_OF(mapped.EventName), L"%S", okEventLabel); } if (moduleName[0] != '\0') { (void)StringCchCopyA(mapped.ClassName, RTL_NUMBER_OF(mapped.ClassName), moduleName); } else { (void)StringCchCopyA(mapped.ClassName, RTL_NUMBER_OF(mapped.ClassName), integrityAmsiPatch ? "amsi" : "ntdll"); } (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"%ws tamper=%u suspiciousPrologue=%llu imageMismatch=%llu checkCount=%llu", reasonLabel, integrityTampered ? 1u : 0u, (unsigned long long)HookEvent->Context1, (unsigned long long)HookEvent->Context2, (unsigned long long)HookEvent->Context3); } else { if (integrityTampered) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_HOOK_TAMPERED"); (void)StringCchCopyA(mapped.Operation, RTL_NUMBER_OF(mapped.Operation), "HookIntegrityTamper"); (void)StringCchCopyW(mapped.EventName, RTL_NUMBER_OF(mapped.EventName), L"HookIntegrityTamper"); } else { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_HOOK_INTEGRITY_OK"); (void)StringCchCopyA(mapped.Operation, RTL_NUMBER_OF(mapped.Operation), "HookIntegrityOk"); (void)StringCchCopyW(mapped.EventName, RTL_NUMBER_OF(mapped.EventName), L"HookIntegrityOk"); } (void)StringCchCopyA(mapped.ClassName, RTL_NUMBER_OF(mapped.ClassName), "SR71"); (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"hookIntegrity tampered=%u mask=0x%llX winsock=%llu nt=%llu ki=%llu module=%llu", integrityTampered ? 1u : 0u, (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, (unsigned long long)HookEvent->Context2, (unsigned long long)HookEvent->Context3, (unsigned long long)((argCount > 2u) ? HookEvent->Args[2] : 0ull)); } } else { if (HookEvent->Kind == BlackbirdIpcHookEventNt && (lstrcmpiA(apiName, "NtAllocateVirtualMemory") == 0 || lstrcmpiA(apiName, "NtAllocateVirtualMemoryEx") == 0)) { BOOL isAllocateEx = lstrcmpiA(apiName, "NtAllocateVirtualMemoryEx") == 0; /* NtAllocateVirtualMemory: * Args[0]=ProcessHandle, Args[1]=*BaseAddress, Args[2]=ZeroBits, * Args[3]=*RegionSize, * Args[4]=AllocationType, Args[5]=Protect, Args[6]=TargetPid. * * NtAllocateVirtualMemoryEx: * Args[0]=ProcessHandle, Args[1]=*BaseAddress, * Args[2]=*RegionSize, * Args[3]=AllocationType, Args[4]=Protect, * Args[5]=ExtendedParameters, * Args[6]=ExtendedParameterCount, Args[7]=TargetPid. */ UINT32 allocType = (argCount > (isAllocateEx ? 3u : 4u)) ? (UINT32)(HookEvent->Args[isAllocateEx ? 3u : 4u] & 0xFFFFFFFFull) : 0u; UINT32 protect = (argCount > (isAllocateEx ? 4u : 5u)) ? (UINT32)(HookEvent->Args[isAllocateEx ? 4u : 5u] & 0xFFFFFFFFull) : 0u; UINT32 targetPid = (argCount > (isAllocateEx ? 7u : 6u) && HookEvent->Args[isAllocateEx ? 7u : 6u] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[isAllocateEx ? 7u : 6u] : 0u; BOOL remoteAlloc = (targetPid != 0 && targetPid != eventPid); if (targetPid == 0) { remoteAlloc = (HookEvent->Args[0] != (UINT64)(ULONG_PTR)-1) && (HookEvent->Args[0] != 0u); } BOOL rwxAlloc = (protect & 0x40u) != 0 || (protect & 0x80u) != 0; memoryEvent = TRUE; if (targetPid != 0 && targetPid != eventPid) { mapped.TargetPid = targetPid; } if (remoteAlloc && rwxAlloc) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "SUSPICIOUS_RWX_ALLOCATION"); mapped.Severity = 6u; } else if (rwxAlloc) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "SUSPICIOUS_RWX_ALLOCATION"); mapped.Severity = 5u; } else { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_MEMORY_ACTIVITY"); mapped.Severity = 2u; } (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"memory.alloc base=0x%llX size=0x%llX allocType=0x%X allocTypeName=%S protect=0x%X protectName=%S remote=%u targetPid=%lu", (unsigned long long)HookEvent->Args[1], (unsigned long long)HookEvent->Args[isAllocateEx ? 2u : 3u], allocType, ControllerMemoryAllocTypeName(allocType), protect, ControllerMemoryProtectName(protect), (unsigned int)remoteAlloc, (unsigned long)targetPid); /* Injection chain stage 2 */ if (remoteAlloc) { ControllerInjectionChainObserve((DWORD)eventPid, targetPid, BK_CHAIN_STAGE_ALLOC); } } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && lstrcmpiA(apiName, "NtProtectVirtualMemory") == 0) { UINT32 newProtect = (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull); UINT32 oldProtect = (UINT32)(HookEvent->Context3 & 0xFFFFFFFFull); BOOL hasSyscallStubOutsideNtdll = (HookEvent->Args[5] != 0ull); BOOL sr71ProtectBlocked = (HookEvent->Args[6] == ControllerSr71ProtectBlockedMarker); UINT32 targetPid = (argCount > 7u && HookEvent->Args[7] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[7] : 0u; BOOL remoteProtect = (targetPid != 0 && targetPid != eventPid); CONTROLLER_IMAGE_TAMPER_CLASSIFICATION imageTamper; if (targetPid != 0 && targetPid != eventPid) { mapped.TargetPid = targetPid; } ZeroMemory(&imageTamper, sizeof(imageTamper)); if (sr71ProtectBlocked) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "SR71_HOOK_PROTECT_BLOCKED"); mapped.Severity = 8u; specializedEvent = TRUE; } else if (ControllerProtectAllowsWrite(newProtect) && ControllerClassifyImageTamperTarget((targetPid != 0) ? targetPid : eventPid, HookEvent->Context0, HookEvent->Context1, &imageTamper) && ControllerApplyImageTamperDetection(&mapped, &imageTamper, L"protect-write-enable", HookEvent->Context0, HookEvent->Context1)) { specializedEvent = TRUE; } else if (hasSyscallStubOutsideNtdll) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_SUSPICIOUS_SYSCALL_STUB"); mapped.Severity = 7u; } else { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_MEMORY_ACTIVITY"); mapped.Severity = (remoteProtect && (newProtect & 0xF0u) != 0u) ? 5u : 3u; } memoryEvent = TRUE; if (sr71ProtectBlocked) { (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"blocked SR71 hook protection change base=0x%llX size=0x%llX oldProtect=0x%X newProtect=0x%X targetPid=%lu", (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, oldProtect, newProtect, (unsigned long)targetPid); } else if (imageTamper.Kind == ControllerImageTamperNone) { (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"memory.protect base=0x%llX size=0x%llX oldProtect=0x%X oldProtectName=%S newProtect=0x%X newProtectName=%S remote=%u syscallStubOutsideNtdll=%u sampleBytes=%lu sampleBase=0x%llX targetPid=%lu", (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, oldProtect, ControllerMemoryProtectName(oldProtect), newProtect, ControllerMemoryProtectName(newProtect), (unsigned int)remoteProtect, (unsigned int)hasSyscallStubOutsideNtdll, (unsigned long)sampleSize, (unsigned long long)HookEvent->Args[6], (unsigned long)targetPid); } } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && lstrcmpiA(apiName, "NtWriteVirtualMemory") == 0) { UINT32 targetPid = (argCount > 5u && HookEvent->Args[5] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[5] : 0u; BOOL remoteWrite = (targetPid != 0 && targetPid != eventPid); BOOL mzHeader = (sampleSize >= 2u && HookEvent->DataSample[0] == 0x4Du && HookEvent->DataSample[1] == 0x5Au); BOOL sr71WriteBlocked = (HookEvent->Args[6] == ControllerSr71WriteBlockedMarker); double entropy = (HookEvent->Args[7] != 0ull) ? ((double)HookEvent->Args[7] / 1000.0) : ControllerComputeSampleEntropy(HookEvent->DataSample, sampleSize); CONTROLLER_IMAGE_TAMPER_CLASSIFICATION imageTamper; ZeroMemory(&imageTamper, sizeof(imageTamper)); if (sr71WriteBlocked) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "SR71_HOOK_WRITE_BLOCKED"); mapped.Severity = 8u; specializedEvent = TRUE; } else if (ControllerClassifyImageTamperTarget((targetPid != 0) ? targetPid : eventPid, HookEvent->Context0, HookEvent->Context1, &imageTamper) && ControllerApplyImageTamperDetection(&mapped, &imageTamper, L"write", HookEvent->Context0, HookEvent->Context1)) { specializedEvent = TRUE; } else { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_MEMORY_ACTIVITY"); mapped.Severity = (remoteWrite && mzHeader) ? 7u : (remoteWrite ? 4u : 3u); } memoryEvent = TRUE; if (targetPid != 0 && targetPid != eventPid) { mapped.TargetPid = targetPid; } if (sr71WriteBlocked) { (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"blocked SR71 hook write base=0x%llX size=0x%llX remote=%u sampleBytes=%lu targetPid=%lu", (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, (unsigned int)remoteWrite, (unsigned long)sampleSize, (unsigned long)targetPid); } else if (imageTamper.Kind == ControllerImageTamperNone) { (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"memory.write base=0x%llX size=0x%llX entropy=%.2f remote=%u mzHeader=%u sampleBytes=%lu targetPid=%lu", (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, entropy, (unsigned int)remoteWrite, (unsigned int)mzHeader, (unsigned long)sampleSize, (unsigned long)targetPid); } } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && lstrcmpiA(apiName, "NtOpenProcess") == 0) { ULONG desiredAccess = (ULONG)HookEvent->Context1; UINT32 targetPid = (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull); if (ControllerHookIsInterestingProcessAccess(desiredAccess)) { BOOL credentialAccess = targetPid != 0 && targetPid != eventPid && ((desiredAccess & PROCESS_VM_READ) != 0 || (desiredAccess & PROCESS_ALL_ACCESS) == PROCESS_ALL_ACCESS) && ControllerPidImageNameEquals(targetPid, L"lsass.exe"); (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), credentialAccess ? "CREDENTIAL_ACCESS_LSASS_OPEN" : "USERMODE_PROCESS_HANDLE_ACTIVITY"); mapped.Severity = credentialAccess ? 7u : ControllerHookSeverityForProcessAccess(desiredAccess); mapped.TargetPid = targetPid; specializedEvent = TRUE; if (credentialAccess) { (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"credential-access process.open target=lsass.exe targetPid=%lu desiredAccess=0x%X", (unsigned long)targetPid, (unsigned int)desiredAccess); } else { (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"process.open targetPid=%lu desiredAccess=0x%X", (unsigned long)targetPid, (unsigned int)desiredAccess); } } } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && lstrcmpiA(apiName, "NtTerminateProcess") == 0) { UINT32 targetPid = (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull); NTSTATUS exitStatus = (NTSTATUS)(HookEvent->Context1 & 0xFFFFFFFFull); BOOL currentProcessTarget = HookEvent->Context0 == 0 || HookEvent->Context0 == (UINT64)(ULONG_PTR)-1 || targetPid == 0 || targetPid == eventPid; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_PROCESS_TERMINATE_BREAKPOINT"); mapped.Severity = currentProcessTarget ? 8u : 6u; mapped.TargetPid = targetPid != 0 ? targetPid : eventPid; specializedEvent = TRUE; (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"process.terminate breakpoint=pre-call targetPid=%lu currentProcess=%u processHandle=0x%llX exitStatus=0x%08X frames=%lu", (unsigned long)mapped.TargetPid, (unsigned int)currentProcessTarget, (unsigned long long)HookEvent->Context0, (unsigned int)exitStatus, (unsigned long)HookEvent->StackCount); } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && lstrcmpiA(apiName, "NtOpenThread") == 0) { ULONG desiredAccess = (ULONG)HookEvent->Context1; UINT32 targetPid = (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull); UINT32 targetTid = (UINT32)(HookEvent->Context3 & 0xFFFFFFFFull); if (ControllerHookIsInterestingThreadAccess(desiredAccess)) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_THREAD_HANDLE_ACTIVITY"); mapped.Severity = ControllerHookSeverityForThreadAccess(desiredAccess); mapped.TargetPid = targetPid; specializedEvent = TRUE; (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"thread.open targetPid=%lu targetTid=%lu desiredAccess=0x%X", (unsigned long)targetPid, (unsigned long)targetTid, (unsigned int)desiredAccess); } } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && lstrcmpiA(apiName, "NtDuplicateObject") == 0) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_DUP_HANDLE_ACTIVITY"); mapped.Severity = ((HookEvent->Args[4] & (PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD)) != 0) ? 6u : 3u; specializedEvent = TRUE; (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"handle.duplicate srcProcess=0x%llX srcHandle=0x%llX dstProcess=0x%llX desiredAccess=0x%llX options=0x%llX", (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[1], (unsigned long long)HookEvent->Args[2], (unsigned long long)HookEvent->Args[4], (unsigned long long)HookEvent->Args[6]); } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && (lstrcmpiA(apiName, "NtQueryInformationProcess") == 0 || lstrcmpiA(apiName, "NtQueryVirtualMemory") == 0 || lstrcmpiA(apiName, "NtReadVirtualMemory") == 0 || lstrcmpiA(apiName, "NtQuerySystemInformation") == 0)) { BOOL lsassRead = FALSE; UINT32 targetPid = 0; if (lstrcmpiA(apiName, "NtReadVirtualMemory") == 0) { targetPid = (argCount > 7u && HookEvent->Args[7] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[7] : 0u; lsassRead = targetPid != 0 && targetPid != eventPid && HookEvent->Context3 != 0 && ControllerPidImageNameEquals(targetPid, L"lsass.exe"); } (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), lsassRead ? "CREDENTIAL_ACCESS_LSASS_READ" : "USERMODE_PROCESS_RECON"); mapped.Severity = lsassRead ? 8u : 3u; if (targetPid != 0 && targetPid != eventPid) { mapped.TargetPid = targetPid; } specializedEvent = TRUE; if (lsassRead) { (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"credential-access memory.read target=lsass.exe targetPid=%lu base=0x%llX size=0x%llX", (unsigned long)targetPid, (unsigned long long)HookEvent->Context1, (unsigned long long)HookEvent->Context3); } else { (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"process.recon api=%S c0=0x%llX c1=0x%llX c2=0x%llX", apiName, (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, (unsigned long long)HookEvent->Context2); } } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && (lstrcmpiA(apiName, "NtSetContextThread") == 0 || lstrcmpiA(apiName, "NtGetContextThread") == 0 || lstrcmpiA(apiName, "NtSuspendThread") == 0 || lstrcmpiA(apiName, "NtResumeThread") == 0)) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_THREAD_CONTEXT_ACTIVITY"); mapped.Severity = (lstrcmpiA(apiName, "NtGetContextThread") == 0) ? 4u : 6u; specializedEvent = TRUE; if (lstrcmpiA(apiName, "NtSetContextThread") == 0 || lstrcmpiA(apiName, "NtGetContextThread") == 0) { UINT32 targetTid = (argCount > 5u && HookEvent->Args[5] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[5] : 0u; UINT32 targetPid = (argCount > 6u && HookEvent->Args[6] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[6] : 0u; if (targetPid != 0 && targetPid != eventPid) { mapped.TargetPid = targetPid; } (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"thread.control api=%S targetPid=%lu targetTid=%lu threadHandle=0x%llX ip=0x%llX sp=0x%llX flags=0x%llX", apiName, (unsigned long)targetPid, (unsigned long)targetTid, (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, (unsigned long long)HookEvent->Context2, (unsigned long long)HookEvent->Context3); } else { UINT32 targetTid = (HookEvent->Context2 <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Context2 : 0u; UINT32 targetPid = (HookEvent->Context3 <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Context3 : 0u; if (targetPid != 0 && targetPid != eventPid) { mapped.TargetPid = targetPid; } (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"thread.control api=%S targetPid=%lu targetTid=%lu threadHandle=0x%llX suspendCount=%llu", apiName, (unsigned long)targetPid, (unsigned long)targetTid, (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1); } } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && lstrcmpiA(apiName, "NtQueueApcThread") == 0) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_APC_QUEUE_ACTIVITY"); mapped.Severity = 1u; specializedEvent = TRUE; if (argCount > 6u && HookEvent->Args[6] <= 0xFFFFFFFFull && (UINT32)HookEvent->Args[6] != eventPid) { mapped.TargetPid = (UINT32)HookEvent->Args[6]; } (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"thread.apc targetPid=%lu targetTid=%lu threadHandle=0x%llX routine=0x%llX arg1=0x%llX arg2=0x%llX arg3=0x%llX", (unsigned long)mapped.TargetPid, (unsigned long)((argCount > 5u && HookEvent->Args[5] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[5] : 0u), (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, (unsigned long long)HookEvent->Context2, (unsigned long long)HookEvent->Context3, (unsigned long long)HookEvent->Args[4]); } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && (lstrcmpiA(apiName, "NtCreateThread") == 0 || lstrcmpiA(apiName, "NtCreateThreadEx") == 0)) { BOOL oldCreateThread = lstrcmpiA(apiName, "NtCreateThread") == 0; UINT64 processHandle = oldCreateThread ? HookEvent->Args[3] : HookEvent->Context0; UINT64 startRoutine = oldCreateThread ? 0ull : HookEvent->Context1; UINT32 createFlags = oldCreateThread ? 0u : (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull); UINT32 targetPid = oldCreateThread ? ((argCount > 6u && HookEvent->Args[6] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[6] : 0u) : ((argCount > 6u && HookEvent->Args[6] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[6] : 0u); BOOL remoteThread = (targetPid != 0 && targetPid != eventPid); BOOL createSuspended = oldCreateThread ? (argCount > 7u && HookEvent->Args[7] != 0u) : ((createFlags & 0x1u) != 0); BOOL hiddenThread = !oldCreateThread && ((createFlags & 0x4u) != 0); if (targetPid == 0) { remoteThread = ((processHandle != (UINT64)(ULONG_PTR)-1) && (processHandle != 0)); } (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_THREAD_CREATE_ACTIVITY"); mapped.Severity = remoteThread ? ((hiddenThread || createSuspended) ? 3u : 2u) : ((hiddenThread || createSuspended) ? 2u : 1u); specializedEvent = TRUE; if (targetPid != 0 && targetPid != eventPid) { mapped.TargetPid = targetPid; } (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"thread.create api=%S targetPid=%lu processHandle=0x%llX startRoutine=0x%llX argument=0x%llX createFlags=0x%X remote=%u createSuspended=%u hideFromDebugger=%u", apiName, (unsigned long)targetPid, (unsigned long long)processHandle, (unsigned long long)startRoutine, (unsigned long long)HookEvent->Context3, (unsigned int)createFlags, (unsigned int)remoteThread, (unsigned int)createSuspended, (unsigned int)hiddenThread); } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && (lstrcmpiA(apiName, "NtQueueApcThreadEx") == 0 || lstrcmpiA(apiName, "NtQueueApcThreadEx2") == 0)) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_APC_QUEUE_ACTIVITY"); mapped.Severity = 1u; specializedEvent = TRUE; if (argCount > 7u && HookEvent->Args[7] <= 0xFFFFFFFFull && (UINT32)HookEvent->Args[7] != eventPid) { mapped.TargetPid = (UINT32)HookEvent->Args[7]; } (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"thread.apcEx targetPid=%lu targetTid=%lu threadHandle=0x%llX routine=0x%llX arg1=0x%llX arg2=0x%llX reserve=0x%llX", (unsigned long)mapped.TargetPid, (unsigned long)((argCount > 6u && HookEvent->Args[6] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[6] : 0u), (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, (unsigned long long)HookEvent->Context2, (unsigned long long)HookEvent->Context3, (unsigned long long)((argCount > 1u) ? HookEvent->Args[1] : 0ull)); } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && (lstrcmpiA(apiName, "NtCreateSection") == 0 || lstrcmpiA(apiName, "NtCreateSectionEx") == 0)) { UINT32 sectionPageProtect = (UINT32)(HookEvent->Context1 & 0xFFFFFFFFull); UINT32 allocAttribs = (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull); BOOL isImage = (allocAttribs & 0x1000000u) != 0; BOOL isExec = (sectionPageProtect & 0xF0u) != 0; UINT32 sev = isImage ? 5u : (isExec ? 4u : 3u); (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_IMAGE_SECTION_ACTIVITY"); mapped.Severity = sev; specializedEvent = TRUE; (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"section.create sectionPageProtect=0x%X allocAttribs=0x%X isImage=%u isExec=%u fileHandle=0x%llX", (unsigned int)sectionPageProtect, (unsigned int)allocAttribs, (unsigned int)isImage, (unsigned int)isExec, (unsigned long long)HookEvent->Context3); } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && (lstrcmpiA(apiName, "NtMapViewOfSection") == 0 || lstrcmpiA(apiName, "NtMapViewOfSectionEx") == 0)) { UINT64 processHandle = HookEvent->Context1; UINT32 win32Protect = (argCount > 6u) ? (UINT32)(HookEvent->Args[6] & 0xFFFFFFFFull) : 0u; UINT32 targetPid = (argCount > 7u && HookEvent->Args[7] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[7] : 0u; BOOL remoteMap = (targetPid != 0 && targetPid != eventPid); if (targetPid == 0) { remoteMap = (processHandle != (UINT64)(ULONG_PTR)-1) && (processHandle != 0); } BOOL execMap = (win32Protect & 0xF0u) != 0; UINT32 sev; if (remoteMap && execMap) sev = 3u; else if (remoteMap) sev = 2u; else if (execMap) sev = 2u; else sev = 1u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_SECTION_MAP_ACTIVITY"); mapped.Severity = sev; if (targetPid != 0 && targetPid != eventPid) { mapped.TargetPid = targetPid; } specializedEvent = TRUE; (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"section.map sectionHandle=0x%llX processHandle=0x%llX baseAddress=0x%llX viewSize=0x%llX win32Protect=0x%X remote=%u exec=%u targetPid=%lu", (unsigned long long)HookEvent->Context0, (unsigned long long)processHandle, (unsigned long long)HookEvent->Context2, (unsigned long long)HookEvent->Context3, (unsigned int)win32Protect, (unsigned int)remoteMap, (unsigned int)execMap, (unsigned long)targetPid); } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && (lstrcmpiA(apiName, "NtUnmapViewOfSection") == 0 || lstrcmpiA(apiName, "NtUnmapViewOfSectionEx") == 0)) { UINT32 targetPid = (argCount > 2u && HookEvent->Args[2] <= 0xFFFFFFFFull) ? (UINT32)HookEvent->Args[2] : 0u; BOOL remoteUnmap = targetPid != 0 && targetPid != eventPid; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_SECTION_UNMAP_ACTIVITY"); mapped.Severity = remoteUnmap ? 6u : 3u; if (remoteUnmap) { mapped.TargetPid = targetPid; } specializedEvent = TRUE; (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"section.unmap api=%S processHandle=0x%llX baseAddress=0x%llX targetPid=%lu flags=0x%llX remote=%u", apiName, (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[1], (unsigned long)targetPid, (unsigned long long)((argCount > 3u) ? HookEvent->Args[3] : 0ull), (unsigned int)remoteUnmap); } else if (HookEvent->Kind == BlackbirdIpcHookEventNt && (lstrcmpiA(apiName, "NtCreateUserProcess") == 0 || lstrcmpiA(apiName, "NtCreateProcessEx") == 0)) { UINT32 createFlags = (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull); BOOL suspended = (createFlags & 0x1u) != 0; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_PROCESS_CREATE_ACTIVITY"); mapped.Severity = suspended ? 4u : 2u; specializedEvent = TRUE; (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"process.create processHandle=0x%llX flags=0x%X suspended=%u", (unsigned long long)HookEvent->Context0, (unsigned int)createFlags, (unsigned int)suspended); } else if (HookEvent->Kind == BlackbirdIpcHookEventWinsock && (lstrcmpiA(apiName, "connect") == 0 || lstrcmpiA(apiName, "WSAConnect") == 0)) { UINT16 family = 0; UINT16 port = 0; CHAR ipBuf[48] = {'\0'}; (void)ControllerHookDecodeSockaddr(HookEvent->DataSample, sampleSize, &family, &port, ipBuf, sizeof(ipBuf)); mapped.Family = BlackbirdIpcEtwFamilySocket; mapped.TargetPid = 0; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_NETWORK_CONNECT"); mapped.Severity = 2u; specializedEvent = TRUE; if (ipBuf[0] != '\0') { (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"socket.connect ip=%S port=%u family=%u socket=0x%llX api=%S", ipBuf, (unsigned int)port, (unsigned int)family, (unsigned long long)HookEvent->Context0, apiName); } else { (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"socket.connect port=%u family=%u socket=0x%llX api=%S", (unsigned int)port, (unsigned int)family, (unsigned long long)HookEvent->Context0, apiName); } } else if (HookEvent->Kind == BlackbirdIpcHookEventWinsock && lstrcmpiA(apiName, "GetAddrInfoW") == 0) { mapped.Family = BlackbirdIpcEtwFamilySocket; mapped.TargetPid = 0; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_DOMAIN_RESOLUTION"); mapped.Severity = 1u; specializedEvent = TRUE; ControllerHookCopyWideSampleToReason(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), HookEvent->DataSample, sampleSize); if (mapped.Reason[0] == L'\0') { (void)StringCchCopyW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"domain.resolve"); } } else if (HookEvent->Kind == BlackbirdIpcHookEventWinsock && (lstrcmpiA(apiName, "WSASend") == 0 || lstrcmpiA(apiName, "WSARecv") == 0 || lstrcmpiA(apiName, "send") == 0 || lstrcmpiA(apiName, "recv") == 0)) { mapped.Family = BlackbirdIpcEtwFamilySocket; mapped.TargetPid = 0; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_NETWORK_IO"); mapped.Severity = 1u; specializedEvent = TRUE; (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"socket.io api=%S bytes=%lu socket=0x%llX", apiName, (unsigned long)sampleSize, (unsigned long long)HookEvent->Context0); } else if (HookEvent->Kind == BlackbirdIpcHookEventKi) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_KI_ACTIVITY"); mapped.Severity = 3u; mapped.TargetPid = eventPid; specializedEvent = TRUE; (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"ki.dispatch stub=%S caller=0x%llX stack=0x%llX", apiName, (unsigned long long)HookEvent->Caller, (unsigned long long)HookEvent->Context0); } else if (HookEvent->Kind == BlackbirdIpcHookEventModule) { WCHAR nameBuffer[BKIPC_MAX_ETW_REASON] = {0}; ULONGLONG moduleHandle = HookEvent->Context0; ULONGLONG frontFlags = HookEvent->Context1; ULONGLONG auxValue = HookEvent->Context2; ULONGLONG thirdValue = HookEvent->Context3; UINT32 moduleOp = (UINT32)HookEvent->Operation; BOOL wmiLocator = FALSE; mapped.TargetPid = eventPid; specializedEvent = TRUE; switch (moduleOp) { case ControllerModuleOpRtlAddFunctionTable: { MEMORY_BASIC_INFORMATION mbi; WCHAR abuseReason[128]; BOOL abusive = ControllerFunctionTableBaseLooksAbusive(eventPid, HookEvent->Args[2], &mbi, abuseReason, RTL_NUMBER_OF(abuseReason)); mapped.Severity = abusive ? 6u : 5u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), abusive ? "DYNAMIC_FUNCTION_TABLE_ABUSE" : "USERMODE_FUNCTION_TABLE_ACTIVITY"); if (abusive) { (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"functionTable.abuse table=0x%llX entryCount=0x%llX baseAddress=0x%llX reason=%ws state=0x%lX protect=0x%lX type=0x%lX", (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[1], (unsigned long long)HookEvent->Args[2], (abuseReason[0] != L'\0') ? abuseReason : L"suspicious function-table base", (unsigned long)mbi.State, (unsigned long)mbi.Protect, (unsigned long)mbi.Type); } else { (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"functionTable.add table=0x%llX entryCount=0x%llX baseAddress=0x%llX", (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[1], (unsigned long long)HookEvent->Args[2]); } break; } case ControllerModuleOpRtlInstallFunctionTableCallback: { MEMORY_BASIC_INFORMATION mbi; WCHAR abuseReason[128]; BOOL abusive = ControllerFunctionTableBaseLooksAbusive(eventPid, HookEvent->Args[1], &mbi, abuseReason, RTL_NUMBER_OF(abuseReason)); ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample, sampleSize); mapped.Severity = abusive ? 6u : 5u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), abusive ? "DYNAMIC_FUNCTION_TABLE_ABUSE" : "USERMODE_FUNCTION_TABLE_ACTIVITY"); if (abusive) { (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"functionTable.callback.abuse tableId=0x%llX baseAddress=0x%llX length=0x%llX callback=0x%llX reason=%ws outOfProc=%ws state=0x%lX protect=0x%lX type=0x%lX", (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[1], (unsigned long long)HookEvent->Args[2], (unsigned long long)HookEvent->Args[3], (abuseReason[0] != L'\0') ? abuseReason : L"suspicious function-table base", (nameBuffer[0] != L'\0') ? nameBuffer : L"", (unsigned long)mbi.State, (unsigned long)mbi.Protect, (unsigned long)mbi.Type); } else { (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"functionTable.callback tableId=0x%llX baseAddress=0x%llX length=0x%llX callback=0x%llX outOfProc=%ws", (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[1], (unsigned long long)HookEvent->Args[2], (unsigned long long)HookEvent->Args[3], (nameBuffer[0] != L'\0') ? nameBuffer : L""); } break; } case ControllerModuleOpRtlDeleteFunctionTable: mapped.Severity = 2u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_FUNCTION_TABLE_ACTIVITY"); (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"functionTable.delete table=0x%llX", (unsigned long long)HookEvent->Args[0]); break; case ControllerModuleOpCoInitializeEx: ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample, sampleSize); mapped.Severity = 1u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_COM_INIT"); (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"com.init mode=%ws coinit=0x%llX hr=0x%08llX", (nameBuffer[0] != L'\0') ? nameBuffer : L"COM", (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[1]); break; case ControllerModuleOpCoInitializeSecurity: mapped.Severity = 2u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_COM_SECURITY_INIT"); (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"com.security authSvc=%lld authn=%llu imp=%llu caps=0x%llX", (long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[1], (unsigned long long)HookEvent->Args[2], (unsigned long long)HookEvent->Args[3]); break; case ControllerModuleOpCoCreateInstance: ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample, sampleSize); wmiLocator = (_wcsicmp(nameBuffer, L"WMI:WbemLocator") == 0); mapped.Severity = wmiLocator ? 3u : 1u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), wmiLocator ? "USERMODE_WMI_ACTIVITY" : "USERMODE_COM_INSTANCE_CREATE"); (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"com.instance class=%ws clsctx=0x%llX hr=0x%08llX", (nameBuffer[0] != L'\0') ? nameBuffer : L"COMClass", (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[3]); break; case ControllerModuleOpEventRegister: ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample, sampleSize); mapped.Severity = 2u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_ETW_PROVIDER_REGISTER"); (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"etw.provider.register provider=%ws reg=0x%llX status=0x%08llX", (nameBuffer[0] != L'\0') ? nameBuffer : L"ETWProvider", (unsigned long long)HookEvent->Args[2], (unsigned long long)HookEvent->Args[3]); break; case ControllerModuleOpEventUnregister: mapped.Severity = 1u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_ETW_PROVIDER_UNREGISTER"); (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"etw.provider.unregister reg=0x%llX status=0x%08llX", (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[1]); break; case ControllerModuleOpStartTraceW: ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample, sampleSize); mapped.Severity = 3u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_ETW_SESSION_CONTROL"); (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"etw.session.start name=%ws handle=0x%llX status=0x%08llX", (nameBuffer[0] != L'\0') ? nameBuffer : L"", (unsigned long long)HookEvent->Args[1], (unsigned long long)HookEvent->Args[3]); break; case ControllerModuleOpEnableTraceEx2: ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample, sampleSize); mapped.Severity = 3u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_ETW_SUBSCRIPTION"); (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"etw.subscribe provider=%ws trace=0x%llX control=%llu level=%llu status=0x%08llX", (nameBuffer[0] != L'\0') ? nameBuffer : L"ETWControl", (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[1], (unsigned long long)HookEvent->Args[2], (unsigned long long)HookEvent->Args[3]); break; case ControllerModuleOpCreateJobObjectW: case ControllerModuleOpOpenJobObjectW: ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample, sampleSize); mapped.Severity = 2u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_JOB_OBJECT_ACTIVITY"); (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"job.object api=%S name=%ws handle=0x%llX", apiName, (nameBuffer[0] != L'\0') ? nameBuffer : L"", (unsigned long long)((moduleOp == ControllerModuleOpOpenJobObjectW) ? HookEvent->Args[2] : HookEvent->Args[0])); break; case ControllerModuleOpAssignProcessToJobObject: ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample, sampleSize); mapped.Severity = 3u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_JOB_OBJECT_ACTIVITY"); (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"job.assign job=0x%llX process=0x%llX ok=%llu", (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[1], (unsigned long long)HookEvent->Args[2]); break; case ControllerModuleOpSetInformationJobObject: ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample, sampleSize); mapped.Severity = 3u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_JOB_OBJECT_ACTIVITY"); (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"job.config class=%ws job=0x%llX size=0x%llX ok=%llu", (nameBuffer[0] != L'\0') ? nameBuffer : L"JobInfo", (unsigned long long)HookEvent->Args[0], (unsigned long long)HookEvent->Args[2], (unsigned long long)HookEvent->Args[3]); break; default: (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_MODULE_LOAD"); if (moduleOp == ControllerModuleOpLoadLibraryA || moduleOp == ControllerModuleOpLoadLibraryExA) { ControllerHookCopyAnsiSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample, sampleSize); } else { ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample, sampleSize); } if (nameBuffer[0] != L'\0') { (void)StringCchCopyW(mapped.ImagePath, RTL_NUMBER_OF(mapped.ImagePath), nameBuffer); BOOL heuristicFired = FALSE; PCWSTR baseName = nameBuffer; for (PCWSTR p = nameBuffer; *p != L'\0'; ++p) { if (*p == L'\\' || *p == L'/') { baseName = p + 1; } } if (_wcsicmp(baseName, L"jscript.dll") == 0 || _wcsicmp(baseName, L"jscript9.dll") == 0 || _wcsicmp(baseName, L"vbscript.dll") == 0 || _wcsicmp(baseName, L"scrobj.dll") == 0) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "SCRIPT_ENGINE_LOAD"); mapped.Severity = 3u; heuristicFired = TRUE; } else if (ControllerHookWidePathContainsI(nameBuffer, L"\\") && !ControllerHookWidePathContainsI(nameBuffer, L"\\Windows\\System32\\") && !ControllerHookWidePathContainsI(nameBuffer, L"\\Windows\\SysWOW64\\") && !ControllerHookWidePathContainsI(nameBuffer, L"\\Windows\\WinSxS\\") && (_wcsicmp(baseName, L"version.dll") == 0 || _wcsicmp(baseName, L"winmm.dll") == 0 || _wcsicmp(baseName, L"wtsapi32.dll") == 0 || _wcsicmp(baseName, L"cryptsp.dll") == 0 || _wcsicmp(baseName, L"dwrite.dll") == 0 || _wcsicmp(baseName, L"dwmapi.dll") == 0 || _wcsicmp(baseName, L"propsys.dll") == 0 || _wcsicmp(baseName, L"cryptbase.dll") == 0 || _wcsicmp(baseName, L"uxtheme.dll") == 0 || _wcsicmp(baseName, L"msasn1.dll") == 0)) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "DLL_SEARCH_ORDER_HIJACK_USERMODE"); mapped.Severity = 6u; heuristicFired = TRUE; } else if (ControllerHookWidePathContainsI(nameBuffer, L"\\Temp\\") || ControllerHookWidePathContainsI(nameBuffer, L"\\AppData\\") || ControllerHookWidePathContainsI(nameBuffer, L"\\Downloads\\") || ControllerHookWidePathContainsI(nameBuffer, L"\\Desktop\\") || ControllerHookWidePathContainsI(nameBuffer, L"\\Public\\")) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "SUSPICIOUS_DLL_LOAD_PATH"); mapped.Severity = 4u; heuristicFired = TRUE; } if (!heuristicFired) { mapped.Severity = 1u; } } if (lstrcmpiA(apiName, "LdrLoadDll") == 0) { if (lstrcmpiA(mapped.DetectionName, "USERMODE_MODULE_LOAD") == 0) { mapped.Severity = (((NTSTATUS)auxValue) < 0) ? 1u : 3u; } (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"module.ldr name=%ws handle=0x%llX flags=0x%llX status=0x%08llX searchPath=0x%llX caller=0x%llX", (nameBuffer[0] != L'\0') ? nameBuffer : L"", (unsigned long long)moduleHandle, (unsigned long long)frontFlags, (unsigned long long)auxValue, (unsigned long long)thirdValue, (unsigned long long)HookEvent->Caller); } else if (lstrcmpiA(apiName, "LoadLibraryExA") == 0 || lstrcmpiA(apiName, "LoadLibraryExW") == 0) { if (lstrcmpiA(mapped.DetectionName, "USERMODE_MODULE_LOAD") == 0) { mapped.Severity = 1u; } (void)StringCchPrintfW( mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"module.frontend api=%S name=%ws handle=0x%llX flags=0x%llX hFile=0x%llX caller=0x%llX", apiName, (nameBuffer[0] != L'\0') ? nameBuffer : L"", (unsigned long long)moduleHandle, (unsigned long long)frontFlags, (unsigned long long)auxValue, (unsigned long long)HookEvent->Caller); } else { if (lstrcmpiA(mapped.DetectionName, "USERMODE_MODULE_LOAD") == 0) { mapped.Severity = 1u; } (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"module.frontend api=%S name=%ws handle=0x%llX caller=0x%llX", apiName, (nameBuffer[0] != L'\0') ? nameBuffer : L"", (unsigned long long)moduleHandle, (unsigned long long)HookEvent->Caller); } break; } } if (!memoryEvent && !specializedEvent) { (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_HOOK_API_CALL"); (void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), L"kind=%S op=%lu caller=0x%llX c0=0x%llX c1=0x%llX c2=0x%llX c3=0x%llX", kindName, (unsigned long)HookEvent->Operation, (unsigned long long)HookEvent->Caller, (unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, (unsigned long long)HookEvent->Context2, (unsigned long long)HookEvent->Context3); } } ControllerHookAppendArgsToReason(mapped.Reason, RTL_NUMBER_OF(mapped.Reason), HookEvent->Args, argCount); mapped.OriginAddress = HookEvent->Caller; mapped.StackCount = HookEvent->StackCount; if (mapped.StackCount > RTL_NUMBER_OF(mapped.Stack)) { mapped.StackCount = RTL_NUMBER_OF(mapped.Stack); } if (mapped.StackCount > RTL_NUMBER_OF(HookEvent->Stack)) { mapped.StackCount = RTL_NUMBER_OF(HookEvent->Stack); } if (mapped.StackCount != 0) { CopyMemory(mapped.Stack, HookEvent->Stack, mapped.StackCount * sizeof(mapped.Stack[0])); } mapped.NotifyClass = HookEvent->Kind; mapped.DataType = HookEvent->Operation; ControllerHookCopyArgs(mapped.HookArgs, &mapped.HookArgCount, HookEvent->Args, argCount); ControllerPrimeHookArgumentSymbols(eventPid, apiName, mapped.HookArgs, mapped.HookArgCount); { UINT32 cf = HookEvent->CallerFlags; if (cf & BK_HOOK_CALLER_FLAG_ALL_SYSTEM) mapped.Flags |= BKIPC_ETW_FLAG_HOOK_CALLER_ALL_SYSTEM; if (cf & BK_HOOK_CALLER_FLAG_HAS_UNMAPPED) mapped.Flags |= BKIPC_ETW_FLAG_HOOK_CALLER_HAS_UNMAPPED; if (cf & BK_HOOK_CALLER_FLAG_HAS_PROCESS_IMAGE) mapped.Flags |= BKIPC_ETW_FLAG_HOOK_CALLER_HAS_PROCESS_IMAGE; if (cf & BK_HOOK_CALLER_FLAG_HAS_NONSYSTEM_DLL) mapped.Flags |= BKIPC_ETW_FLAG_HOOK_CALLER_HAS_NONSYSTEM_DLL; if (cf & BK_HOOK_CALLER_FLAG_HAS_OWN_MODULE) mapped.Flags |= BKIPC_ETW_FLAG_HOOK_CALLER_HAS_OWN_MODULE; mapped.Flags |= (cf & (BK_HOOK_CALLER_IMMED_MASK | BK_HOOK_CALLER_DEEP_MASK)); mapped.Flags |= ((cf & BK_HOOK_CALLER_COMPONENT_MASK) << 12u); } mapped.DataSize = sampleSize; mapped.DeepSampleSize = sampleSize; if (sampleSize != 0) { CopyMemory(mapped.DeepSample, HookEvent->DataSample, sampleSize); } { BOOL blackbirdOwned = FALSE; EnterCriticalSection(&Client->Lock); blackbirdOwned = ControllerIsBlackbirdOwnedAddress(Client, HookEvent->Caller); LeaveCriticalSection(&Client->Lock); if (blackbirdOwned) { mapped.Reserved2 = ControllerComputeEtwDetectionTraits(mapped) | BKIPC_ETW_TRAIT_BLACKBIRD_OWN; mapped.Severity = 1u; (void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "BK_INSTRUMENTATION"); ControllerDispatchEtwEvent(&mapped); return ERROR_SUCCESS; } } if (HookEvent->Kind != BlackbirdIpcHookEventIntegrity && mapped.Severity >= 2u && mapped.ProcessId != 0u) { mapped.Reserved2 = ControllerComputeEtwDetectionTraits(mapped); UINT32 heurFlags = ControllerHeurFlagsFromDetectionTraits(mapped.Reserved2); if (heurFlags != 0u) { ControllerHeuristicsObserveEvent((DWORD)mapped.ProcessId, mapped.Severity, heurFlags); } } else { mapped.Reserved2 = ControllerComputeEtwDetectionTraits(mapped); } if (HookEvent->Kind != BlackbirdIpcHookEventIntegrity && mapped.Severity > 0u && mapped.Severity < 8u) { UINT32 boost = ControllerCallerOriginSeverityBoost(HookEvent->CallerFlags); mapped.Severity = (mapped.Severity + boost > 8u) ? 8u : (mapped.Severity + boost); } mapped.Reserved2 = ControllerComputeEtwDetectionTraits(mapped); ControllerObserveUserHookHollowEvent(&mapped); ControllerDispatchEtwEvent(&mapped); return ERROR_SUCCESS; } static DWORD ControllerClientNotifyHookReady(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ const BKIPC_NOTIFY_HOOK_READY_REQUEST *Request, _Out_ BKIPC_NOTIFY_HOOK_READY_RESPONSE *Response) { DWORD observedMask; DWORD processId; if (Client == NULL || Request == NULL || Response == NULL) { return ERROR_INVALID_PARAMETER; } ZeroMemory(Response, sizeof(*Response)); if (Request->ReadyMask == 0) { return ERROR_INVALID_PARAMETER; } processId = (Request->ProcessId != 0) ? Request->ProcessId : Client->ProcessId; if (processId == 0 || processId != Client->ProcessId) { return ERROR_ACCESS_DENIED; } observedMask = (DWORD)InterlockedOr(&Client->HookReadyMask, (LONG)Request->ReadyMask) | Request->ReadyMask; Client->HookReadyTick = GetTickCount64(); ZeroMemory(Response, sizeof(*Response)); Response->ProcessId = processId; Response->ObservedMask = observedMask; Response->RequiredMask = BK_CONTROLLER_HOOK_READY_REQUIRED_MASK; /* If the controller has flagged this client for inline Winsock hook upgrade, deliver the command in the PendingCommand back-channel and clear the flag atomically. */ if (InterlockedCompareExchange(&Client->WinsockInlineUpgradePending, 0, 1) == 1) { Response->PendingCommand = BlackbirdIpcCommandUpgradeWinsockHooks; ControllerLog("[IPC] winsock-inline-upgrade delivered pid=%lu\n", processId); } if ((observedMask & BK_CONTROLLER_HOOK_READY_REQUIRED_MASK) == BK_CONTROLLER_HOOK_READY_REQUIRED_MASK) { ControllerLog("[IPC] hook-ready notify pid=%lu mask=0x%08lX (ready)\n", processId, observedMask); } return ERROR_SUCCESS; } static VOID ControllerClearDriverPendingLaunchBestEffort(_In_z_ PCSTR Reason) { BK_ARM_PENDING_LAUNCH_REQUEST request; BOOL ok = FALSE; DWORD err = ERROR_SUCCESS; ZeroMemory(&request, sizeof(request)); request.Flags = BK_PENDING_LAUNCH_FLAG_CLEAR; ok = ControllerProxyArmPendingLaunch(&request); err = ok ? ERROR_SUCCESS : GetLastError(); if (!ok) { ControllerLog("[IPC][WARN] driver pending-launch clear failed reason=%s err=%lu\n", Reason, err); return; } ControllerLog("[IPC] driver pending-launch cleared reason=%s\n", Reason); } static BOOL ControllerClientDropLaunchSubscriptionsLocked(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ DWORD RootProcessId) { BOOL changed = FALSE; DWORD i = 0; if (Client == NULL || RootProcessId == 0) { return FALSE; } changed |= ControllerDropDynamicDescendantsLocked(Client, RootProcessId); while (i < Client->SubscriptionCount) { if (Client->Subscriptions[i].ProcessId == RootProcessId) { ControllerRemoveSubscriptionAtLocked(Client, i); changed = TRUE; ControllerLog("[IPC] launch failure subscription removed clientPid=%lu targetPid=%lu\n", Client->ProcessId, RootProcessId); continue; } i += 1; } if (changed) { ControllerMarkDriverSubscriptionsDirty(); } return changed; } static VOID ControllerRecoverRuntimeAfterLaunchFailure(_In_ DWORD LaunchError, _In_ DWORD RootProcessId) { static const DWORD recoveryMask = BK_RUNTIME_FLAG_ANTI_VIRTUALIZATION | BK_RUNTIME_FLAG_SELF_HIDE | BK_RUNTIME_FLAG_INTERFACE_PROTECTED_ACCESS | BK_RUNTIME_FLAG_CONTROLLER_PROTECTED_ACCESS | BK_RUNTIME_FLAG_NTAPI_HOOKS_DISARMED | BK_RUNTIME_FLAG_QPC_TIMING_DISABLED; static const DWORD recoveryFlags = BK_RUNTIME_FLAG_NTAPI_HOOKS_DISARMED | BK_RUNTIME_FLAG_QPC_TIMING_DISABLED; if (!ControllerProxySetRuntimeConfig(recoveryFlags, recoveryMask)) { ControllerLog("[IPC][WARN] launch failure runtime recovery failed pid=%lu launchErr=%lu err=%lu\n", RootProcessId, LaunchError, GetLastError()); return; } ControllerLog( "[IPC][WARN] launch failure runtime recovery disarmed hooks/protection pid=%lu launchErr=%lu flags=0x%08lX mask=0x%08lX\n", RootProcessId, LaunchError, recoveryFlags, recoveryMask); } static DWORD ControllerClientSetUserHookTarget(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ const BKIPC_SET_USER_HOOK_TARGET_REQUEST *Request, _Out_ BKIPC_SET_USER_HOOK_TARGET_RESPONSE *Response) { WCHAR hookDllPath[BK_MAX_IMAGE_PATH_CHARS]; BK_QUERY_PROCESS_IMAGE_RESPONSE kernelImage; WIN32_FILE_ATTRIBUTE_DATA hookAttrs; BOOL hookPathVisible = FALSE; ULONGLONG hookSize = 0; DWORD err = ERROR_SUCCESS; DWORD targetPid = 0; BOOL kernelAssured = FALSE; BOOL pendingLaunchArmed = FALSE; ULONGLONG analysisSessionId = 0; BK_ARM_PENDING_LAUNCH_REQUEST pendingLaunchRequest; if (Client == NULL || Request == NULL || Response == NULL) { return ERROR_INVALID_PARAMETER; } ZeroMemory(Response, sizeof(*Response)); ZeroMemory(hookDllPath, sizeof(hookDllPath)); ZeroMemory(&kernelImage, sizeof(kernelImage)); if (!ControllerInjectionResolveHookDllPath(Request, hookDllPath, RTL_NUMBER_OF(hookDllPath))) { return GetLastError(); } ControllerLog( "[IPC] set-user-hook-target mode=%lu flags=0x%08lX pid=%lu image=%ws subjectKind=%lu subject=%ws argsChars=%llu\n", Request->Mode, Request->Flags, Request->ProcessId, Request->ImagePath, Request->AnalysisSubjectKind, Request->AnalysisSubjectPath, Request->CommandLineArguments[0] != L'\0' ? (unsigned long long)wcslen(Request->CommandLineArguments) : 0ull); ZeroMemory(&hookAttrs, sizeof(hookAttrs)); hookPathVisible = GetFileAttributesExW(hookDllPath, GetFileExInfoStandard, &hookAttrs); if (hookPathVisible) { hookSize = (((ULONGLONG)hookAttrs.nFileSizeHigh) << 32) | (ULONGLONG)hookAttrs.nFileSizeLow; } ControllerLog("[IPC] userhook resolved hook path=%ws visible=%u size=%llu\n", hookDllPath, hookPathVisible ? 1u : 0u, (unsigned long long)hookSize); if (!hookPathVisible) { return ERROR_FILE_NOT_FOUND; } switch (Request->Mode) { case BlackbirdIpcUserHookTargetAttach: if (Request->AnalysisSubjectKind != BlackbirdAnalysisSubjectProcess || Request->AnalysisSubjectPath[0] != L'\0') { return ERROR_INVALID_PARAMETER; } EnterCriticalSection(&Client->Lock); ControllerClientClearPendingLaunchLocked(Client); LeaveCriticalSection(&Client->Lock); if (Request->ProcessId == 0) { return ERROR_INVALID_PARAMETER; } if (!ControllerClientCanMonitorPid(Client, Request->ProcessId, NULL, NULL)) { err = GetLastError(); return err == ERROR_SUCCESS ? ERROR_ACCESS_DENIED : err; } err = ControllerInjectionAttachAndVerify(Request->ProcessId, hookDllPath, BK_CONTROLLER_INJECTION_VERIFY_TIMEOUT_MS); if (err != ERROR_SUCCESS) { return err; } targetPid = Request->ProcessId; ControllerLog("[IPC] attach target verified clientPid=%lu rootPid=%lu\n", Client->ProcessId, targetPid); break; case BlackbirdIpcUserHookTargetLaunch: if (Request->ImagePath[0] == L'\0' || !ControllerInjectionPathPointsToFile(Request->ImagePath)) { return ERROR_FILE_NOT_FOUND; } if (Request->AnalysisSubjectKind != BlackbirdAnalysisSubjectProcess && Request->AnalysisSubjectKind != BlackbirdAnalysisSubjectDll) { return ERROR_INVALID_PARAMETER; } if (Request->AnalysisSubjectKind == BlackbirdAnalysisSubjectDll && Request->AnalysisSubjectPath[0] == L'\0') { return ERROR_INVALID_PARAMETER; } if (Request->AnalysisSubjectKind == BlackbirdAnalysisSubjectProcess && Request->AnalysisSubjectPath[0] != L'\0') { return ERROR_INVALID_PARAMETER; } if (Request->AnalysisSubjectKind == BlackbirdAnalysisSubjectDll && !ControllerInjectionPathPointsToFile(Request->AnalysisSubjectPath)) { return ERROR_FILE_NOT_FOUND; } err = ControllerEnsureCaptureReadyForLaunch(); if (err != ERROR_SUCCESS) { return err; } EnterCriticalSection(&Client->Lock); ControllerClientArmPendingLaunchLocked(Client, Request->ImagePath, Request->AnalysisSubjectKind, Request->AnalysisSubjectPath); LeaveCriticalSection(&Client->Lock); if (!ControllerBuildPendingLaunchRequest(Request->ImagePath, Request->AnalysisSubjectKind, Request->AnalysisSubjectPath, BK_CONTROLLER_DRIVER_STREAM_MASK, &pendingLaunchRequest)) { EnterCriticalSection(&Client->Lock); ControllerClientClearPendingLaunchLocked(Client); LeaveCriticalSection(&Client->Lock); return ERROR_INVALID_PARAMETER; } if (ControllerProxyArmPendingLaunch(&pendingLaunchRequest)) { pendingLaunchArmed = TRUE; } else { err = GetLastError(); } if (!pendingLaunchArmed) { EnterCriticalSection(&Client->Lock); ControllerClientClearPendingLaunchLocked(Client); LeaveCriticalSection(&Client->Lock); return err == ERROR_SUCCESS ? ERROR_DEVICE_NOT_CONNECTED : err; } err = ControllerInjectionLaunchAndVerify(Client->Pipe, Request, hookDllPath, BK_CONTROLLER_INJECTION_VERIFY_TIMEOUT_MS, &targetPid); if (err != ERROR_SUCCESS) { BOOL subscriptionsChanged = FALSE; if (pendingLaunchArmed) { ControllerClearDriverPendingLaunchBestEffort("userhook-launch-failed"); } EnterCriticalSection(&Client->Lock); ControllerClientClearPendingLaunchLocked(Client); if (targetPid != 0) { subscriptionsChanged = ControllerClientDropLaunchSubscriptionsLocked(Client, targetPid); } LeaveCriticalSection(&Client->Lock); if (subscriptionsChanged) { (void)ControllerApplyDriverSubscriptionsIfDirty(); } if (targetPid != 0) { ControllerRecoverRuntimeAfterLaunchFailure(err, targetPid); } return err; } EnterCriticalSection(&Client->Lock); ControllerClientPrimePendingLaunchPidLocked(Client, targetPid); analysisSessionId = ControllerClientBeginAnalysisSessionLocked(Client, targetPid, TRUE); LeaveCriticalSection(&Client->Lock); ControllerLog("[IPC] analysis session started clientPid=%lu sessionId=%llu rootPid=%lu launchOwned=1 " "mode=launch\n", Client->ProcessId, (unsigned long long)analysisSessionId, targetPid); (void)ControllerApplyDriverSubscriptionsIfDirty(); break; default: return ERROR_INVALID_PARAMETER; } if (targetPid != 0 && ControllerProxyQueryProcessImage(targetPid, &kernelImage)) { kernelAssured = TRUE; (void)StringCchCopyW(Response->ImagePath, RTL_NUMBER_OF(Response->ImagePath), kernelImage.ImagePath); } else if (targetPid != 0) { BOOL driverConnected = FALSE; DWORD kernelErr = GetLastError(); DWORD normalizedKernelErr = kernelErr; driverConnected = ControllerProxyDriverConnected(); if (driverConnected) { if (normalizedKernelErr == ERROR_SUCCESS || normalizedKernelErr == ERROR_NO_MORE_FILES || normalizedKernelErr == ERROR_BAD_LENGTH || normalizedKernelErr == ERROR_PARTIAL_COPY) { normalizedKernelErr = ERROR_NOT_FOUND; } if (Request->Mode == BlackbirdIpcUserHookTargetLaunch) { ControllerLog("[IPC][WARN] userhook kernel image probe failed pid=%lu err=%lu; continuing unassured\n", targetPid, normalizedKernelErr); } else { ControllerLog( "[IPC] userhook kernel image probe missed pid=%lu err=%lu (attach, pre-existing process)\n", targetPid, normalizedKernelErr); } } } Response->ProcessId = targetPid; Response->Status = kernelAssured ? 1 : 0; Response->AnalysisSubjectKind = Request->AnalysisSubjectKind; if (!kernelAssured && Request->Mode == BlackbirdIpcUserHookTargetLaunch && Request->ImagePath[0] != L'\0') { (void)StringCchCopyW(Response->ImagePath, RTL_NUMBER_OF(Response->ImagePath), Request->ImagePath); } else if (!kernelAssured && targetPid != 0) { HANDLE queryHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, targetPid); if (queryHandle != NULL) { DWORD imageChars = (DWORD)RTL_NUMBER_OF(Response->ImagePath); if (!QueryFullProcessImageNameW(queryHandle, 0, Response->ImagePath, &imageChars)) { Response->ImagePath[0] = L'\0'; } CloseHandle(queryHandle); } } if (Request->AnalysisSubjectKind == BlackbirdAnalysisSubjectDll && Request->AnalysisSubjectPath[0] != L'\0') { (void)StringCchCopyW(Response->AnalysisSubjectPath, RTL_NUMBER_OF(Response->AnalysisSubjectPath), Request->AnalysisSubjectPath); } return ERROR_SUCCESS; } static PCSTR ControllerCommandName(_In_ UINT32 Command) { switch (Command) { case BlackbirdIpcCommandHandshake: return "handshake"; case BlackbirdIpcCommandSubscribe: return "subscribe"; case BlackbirdIpcCommandUnsubscribe: return "unsubscribe"; case BlackbirdIpcCommandSetPids: return "set-pids"; case BlackbirdIpcCommandGetEvent: return "get-event"; case BlackbirdIpcCommandGetStats: return "get-stats"; case BlackbirdIpcCommandQueryProcessImage: return "query-process-image"; case BlackbirdIpcCommandSetShutdownMode: return "set-shutdown-mode"; case BlackbirdIpcCommandGetEtwEvent: return "get-etw-event"; case BlackbirdIpcCommandOpenSharedRing: return "open-shared-ring"; case BlackbirdIpcCommandPublishHookEvent: return "publish-hook-event"; case BlackbirdIpcCommandSetUserHookTarget: return "set-user-hook-target"; case BlackbirdIpcCommandNotifyHookReady: return "notify-hook-ready"; case BlackbirdIpcCommandControlProcessExecution: return "control-process-execution"; case BlackbirdIpcCommandSetRuntimeConfig: return "set-runtime-config"; case BlackbirdIpcCommandGetRuntimeConfig: return "get-runtime-config"; case BlackbirdIpcCommandGetHealth: return "get-health"; case BlackbirdIpcCommandGetDiagnostics: return "get-diagnostics"; case BlackbirdIpcCommandSetQpcTimingConfig: return "set-qpc-timing-config"; case BlackbirdIpcCommandGetQpcTimingState: return "get-qpc-timing-state"; case BlackbirdIpcCommandQueryProcessMemory: return "query-process-memory"; case BlackbirdIpcCommandRegisterInstrumentationRange: return "register-instrumentation-range"; case BlackbirdIpcCommandRegisterHookPatch: return "register-hook-patch"; default: return "unknown"; } } static BOOL ControllerCommandLogsBegin(_In_ UINT32 Command) { switch (Command) { case BlackbirdIpcCommandSetRuntimeConfig: case BlackbirdIpcCommandGetRuntimeConfig: case BlackbirdIpcCommandSetUserHookTarget: case BlackbirdIpcCommandControlProcessExecution: case BlackbirdIpcCommandQueryProcessImage: case BlackbirdIpcCommandQueryProcessMemory: return TRUE; default: return FALSE; } } static DWORD ControllerHandleClientCommand(_Inout_ BK_CONTROLLER_CLIENT *Client, _In_ const BKIPC_PACKET *Request, _Out_ BKIPC_PACKET *Response) { DWORD err = ERROR_SUCCESS; ControllerPrepareResponse(Request, Response); if (!ControllerCommandAllowedForRole(Client->Role, Request->Command)) { err = ERROR_ACCESS_DENIED; goto Complete; } switch (Request->Command) { case BlackbirdIpcCommandHandshake: if (Client->Role == BkctlrClientRoleHook) { (void)InterlockedOr(&Client->HookReadyMask, (LONG)BKIPC_HOOK_READY_FLAG_IPC_CONNECTED); Client->HookReadyTick = GetTickCount64(); } Response->Payload.HandshakeResponse.NegotiatedVersion = BKIPC_VERSION; Response->Payload.HandshakeResponse.Capabilities = BKIPC_CAP_DRIVER_PROXY | BKIPC_CAP_SHARED_RING | BKIPC_CAP_USER_HOOK_INGEST | BKIPC_CAP_USER_HOOK_READY | BKIPC_CAP_DRIVER_DIAGNOSTICS | BKIPC_CAP_QPC_TIMING; Response->Payload.HandshakeResponse.ThreatIntelEnabled = 0u; Response->Payload.HandshakeResponse.Reserved = 0u; break; case BlackbirdIpcCommandSubscribe: err = ControllerClientSubscribe(Client, &Request->Payload.SubscribeRequest); break; case BlackbirdIpcCommandUnsubscribe: err = ControllerClientUnsubscribe(Client, &Request->Payload.UnsubscribeRequest); break; case BlackbirdIpcCommandSetPids: err = ControllerClientSetPids(Client, &Request->Payload.SetPidsRequest); break; case BlackbirdIpcCommandGetEvent: err = ControllerClientGetEvent(Client, Request->Payload.GetEventRequest.TimeoutMs, &Response->Payload.EventRecord); break; case BlackbirdIpcCommandGetStats: err = ControllerClientGetStats(Client, &Response->Payload.StatsResponse); break; case BlackbirdIpcCommandQueryProcessImage: if (Request->Payload.QueryProcessImageRequest.ProcessId == 0) { err = ERROR_INVALID_PARAMETER; break; } if (!ControllerClientCanMonitorPid(Client, Request->Payload.QueryProcessImageRequest.ProcessId, NULL, NULL)) { err = GetLastError(); if (err == ERROR_SUCCESS) { err = ERROR_ACCESS_DENIED; } break; } if (!ControllerProxyQueryProcessImage(Request->Payload.QueryProcessImageRequest.ProcessId, &Response->Payload.QueryProcessImageResponse)) { err = GetLastError(); if (err == ERROR_SUCCESS) { err = ERROR_NOT_FOUND; } } break; case BlackbirdIpcCommandSetShutdownMode: if (!ControllerProxySetShutdownMode()) { err = GetLastError(); } break; case BlackbirdIpcCommandControlProcessExecution: if (Request->Payload.ControlProcessExecutionRequest.ProcessId == 0) { err = ERROR_INVALID_PARAMETER; break; } if (!ControllerProxyControlProcessExecution(Request->Payload.ControlProcessExecutionRequest.ProcessId, Request->Payload.ControlProcessExecutionRequest.Suspend != 0)) { err = GetLastError(); if (err == ERROR_SUCCESS) { err = ERROR_GEN_FAILURE; } } break; case BlackbirdIpcCommandSetRuntimeConfig: if (!ControllerProxySetRuntimeConfig(Request->Payload.SetRuntimeConfigRequest.Flags, Request->Payload.SetRuntimeConfigRequest.Mask)) { err = GetLastError(); } break; case BlackbirdIpcCommandGetRuntimeConfig: if (!ControllerProxyGetRuntimeConfig(&Response->Payload.RuntimeConfigResponse)) { err = GetLastError(); } break; case BlackbirdIpcCommandSetQpcTimingConfig: if (!ControllerProxySetQpcTimingConfig(&Request->Payload.QpcTimingConfig)) { err = GetLastError(); } break; case BlackbirdIpcCommandGetQpcTimingState: if (!ControllerProxyGetQpcTimingState(&Response->Payload.QpcTimingState)) { err = GetLastError(); } break; case BlackbirdIpcCommandGetHealth: if (!ControllerProxyGetHealth(&Response->Payload.HealthResponse)) { err = GetLastError(); } break; case BlackbirdIpcCommandGetDiagnostics: if (!ControllerProxyGetDiagnostics(&Response->Payload.DiagnosticsResponse)) { err = GetLastError(); } break; case BlackbirdIpcCommandGetEtwEvent: err = ControllerClientGetEtwEvent(Client, Request->Payload.GetEventRequest.TimeoutMs, &Response->Payload.EtwEvent); break; case BlackbirdIpcCommandOpenSharedRing: err = ControllerClientOpenSharedRing(Client, &Request->Payload.OpenSharedRingRequest, &Response->Payload.OpenSharedRingResponse); break; case BlackbirdIpcCommandPublishHookEvent: err = ControllerClientPublishHookEvent(Client, &Request->Payload.HookEvent); break; case BlackbirdIpcCommandSetUserHookTarget: err = ControllerClientSetUserHookTarget(Client, &Request->Payload.SetUserHookTargetRequest, &Response->Payload.SetUserHookTargetResponse); break; case BlackbirdIpcCommandNotifyHookReady: err = ControllerClientNotifyHookReady(Client, &Request->Payload.NotifyHookReadyRequest, &Response->Payload.NotifyHookReadyResponse); break; case BlackbirdIpcCommandRegisterInstrumentationRange: { const BKIPC_REGISTER_INSTRUMENTATION_RANGE_REQUEST *range = &Request->Payload.RegisterInstrumentationRangeRequest; if (range->BaseAddress == 0 || range->RegionSize == 0) { err = ERROR_INVALID_PARAMETER; break; } EnterCriticalSection(&Client->Lock); if (Client->OwnedRangeCount < BK_CONTROLLER_MAX_OWNED_RANGES) { BK_CONTROLLER_OWNED_RANGE *slot = &Client->OwnedRanges[Client->OwnedRangeCount]; slot->BaseAddress = range->BaseAddress; slot->RegionSize = range->RegionSize; slot->Flags = range->Flags; (void)StringCchCopyA(slot->Tag, RTL_NUMBER_OF(slot->Tag), range->Tag); Client->OwnedRangeCount += 1; err = ERROR_SUCCESS; } else { err = ERROR_INSUFFICIENT_BUFFER; } LeaveCriticalSection(&Client->Lock); if (err == ERROR_SUCCESS) { if (!ControllerProxyRegisterInstrumentationRange(Client->ProcessId, range->BaseAddress, range->RegionSize, range->Flags, range->Tag)) { DWORD kernelErr = GetLastError(); ControllerLog( "[IPC] instrumentation-range kernel registration failed pid=%lu base=0x%llX size=0x%llX win32=%lu\n", Client->ProcessId, (unsigned long long)range->BaseAddress, (unsigned long long)range->RegionSize, kernelErr); } ControllerLog("[IPC] instrumentation-range registered pid=%lu base=0x%llX size=0x%llX flags=0x%X tag=%s\n", Client->ProcessId, (unsigned long long)range->BaseAddress, (unsigned long long)range->RegionSize, (unsigned int)range->Flags, range->Tag[0] != '\0' ? range->Tag : ""); } break; } case BlackbirdIpcCommandQueryProcessMemory: { HANDLE hClientProc; HANDLE hDupSection = NULL; DWORD bytesRead = 0; if (Request->Payload.QueryMemoryRequest.ProcessId == 0 || Request->Payload.QueryMemoryRequest.RequestedSize == 0) { err = ERROR_INVALID_PARAMETER; break; } if (!ControllerClientCanMonitorPid(Client, Request->Payload.QueryMemoryRequest.ProcessId, NULL, NULL)) { err = GetLastError(); if (err == ERROR_SUCCESS) { err = ERROR_ACCESS_DENIED; } break; } hClientProc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, Client->ProcessId); if (hClientProc == NULL) { err = GetLastError(); break; } if (!ControllerProxyReadProcessMemory( Request->Payload.QueryMemoryRequest.ProcessId, Request->Payload.QueryMemoryRequest.BaseAddress, Request->Payload.QueryMemoryRequest.RequestedSize, hClientProc, &hDupSection, &bytesRead)) { err = GetLastError(); if (err == ERROR_SUCCESS) { err = ERROR_GEN_FAILURE; } CloseHandle(hClientProc); break; } CloseHandle(hClientProc); Response->Payload.QueryMemoryResponse.Status = 0; Response->Payload.QueryMemoryResponse.BytesRead = bytesRead; Response->Payload.QueryMemoryResponse.SectionHandle = (UINT64)(ULONG_PTR)hDupSection; break; } case BlackbirdIpcCommandRegisterHookPatch: { const BKIPC_REGISTER_HOOK_PATCH_REQUEST *patch = &Request->Payload.RegisterHookPatchRequest; if (patch->PatchAddress == 0 || patch->PatchSize == 0 || patch->OriginalSize == 0 || patch->PatchSize > BK_MAX_HOOK_PATCH_BYTES || patch->OriginalSize > BK_MAX_HOOK_PATCH_BYTES) { err = ERROR_INVALID_PARAMETER; break; } if (!ControllerProxyRegisterHookPatch(Client->ProcessId, patch->PatchAddress, patch->PatchSize, patch->OriginalBytes, patch->OriginalSize, patch->Flags, patch->Tag)) { err = GetLastError(); if (err == ERROR_SUCCESS) { err = ERROR_GEN_FAILURE; } ControllerLog("[IPC] hook-patch kernel registration failed pid=%lu address=0x%llX size=%lu win32=%lu\n", Client->ProcessId, (unsigned long long)patch->PatchAddress, (unsigned long)patch->PatchSize, err); break; } ControllerLog("[IPC] hook-patch registered pid=%lu address=0x%llX size=%lu tag=%s\n", Client->ProcessId, (unsigned long long)patch->PatchAddress, (unsigned long)patch->PatchSize, patch->Tag[0] != '\0' ? patch->Tag : ""); break; } default: err = ERROR_INVALID_FUNCTION; break; } Complete: Response->Status = err; if (Request->Command != BlackbirdIpcCommandGetEvent && Request->Command != BlackbirdIpcCommandGetEtwEvent && Request->Command != BlackbirdIpcCommandPublishHookEvent && Request->Command != BlackbirdIpcCommandGetStats && Request->Command != BlackbirdIpcCommandGetHealth && Request->Command != BlackbirdIpcCommandGetDiagnostics && Request->Command != BlackbirdIpcCommandGetQpcTimingState) { ControllerLog("[IPC] cmd=%s seq=%lu role=%lu clientPid=%lu session=%lu status=%lu\n", ControllerCommandName(Request->Command), Request->Sequence, Client->Role, Client->ProcessId, Client->SessionId, err); } else if (err != ERROR_SUCCESS && err != ERROR_NO_MORE_ITEMS) { ControllerLog("[IPC][WARN] cmd=%s seq=%lu role=%lu clientPid=%lu session=%lu status=%lu\n", ControllerCommandName(Request->Command), Request->Sequence, Client->Role, Client->ProcessId, Client->SessionId, err); } return err; } VOID ControllerDetachClient(_Inout_ BK_CONTROLLER_CLIENT *Client) { PBK_CONTROLLER_CLIENT *pp; BK_CONTROLLER_ANALYSIS_TEARDOWN teardown; if (Client == NULL) { return; } ZeroMemory(&teardown, sizeof(teardown)); EnterCriticalSection(g_ClientListLock.get()); pp = &g_ClientList; while (*pp != NULL) { if (*pp == Client) { Client->Detached = 1; *pp = Client->Next; if (Client->SlotIndex != BK_CONTROLLER_INVALID_SLOT) { ControllerReleaseClientSlotLocked(Client->SlotIndex); Client->SlotIndex = BK_CONTROLLER_INVALID_SLOT; } if (g_ClientCount > 0) { g_ClientCount -= 1; } ControllerLog("[IPC] active clients=%lu\n", g_ClientCount); break; } pp = &(*pp)->Next; } ControllerRebuildPidIndexLocked(NULL); LeaveCriticalSection(g_ClientListLock.get()); (void)ControllerRequestDriverSubscriptionApply(FALSE, 0); EnterCriticalSection(&Client->Lock); ControllerClientStopAnalysisLocked(Client, &teardown); LeaveCriticalSection(&Client->Lock); ControllerCompleteAnalysisTeardown(Client, &teardown, "client-disconnect"); if (teardown.HadState) { if (teardown.HadAnalysisLease || teardown.SubscriptionCount != 0) { (void)ControllerRequestDriverSubscriptionApply(FALSE, 0); } ControllerTerminateLaunchOwnedTeardown(&teardown, "client-disconnect"); } } DWORD WINAPI ControllerClientThreadProc(_In_ LPVOID Context) { BK_CONTROLLER_CLIENT *client = (BK_CONTROLLER_CLIENT *)Context; BKIPC_PACKET *request = NULL; BKIPC_PACKET *response = NULL; DWORD disconnectErr = ERROR_SUCCESS; if (client == NULL) { return 1; } request = (BKIPC_PACKET *)calloc(1, sizeof(*request)); response = (BKIPC_PACKET *)calloc(1, sizeof(*response)); if (request == NULL || response == NULL) { free(request); free(response); return ERROR_OUTOFMEMORY; } for (;;) { DWORD bytesRead = 0; DWORD bytesWritten = 0; BOOL ok; if (ControllerShouldStop()) { break; } ZeroMemory(request, sizeof(*request)); ok = ReadFile(client->Pipe, request, sizeof(*request), &bytesRead, NULL); if (!ok || bytesRead != sizeof(*request)) { disconnectErr = GetLastError(); break; } if (!ControllerValidatePacket(request, BlackbirdIpcPacketRequest)) { disconnectErr = ERROR_BAD_FORMAT; break; } { DWORD commandErr; ULONGLONG commandStartTick = GetTickCount64(); if (ControllerCommandLogsBegin(request->Command)) { ControllerLog("[IPC] cmd-begin=%s seq=%lu role=%lu clientPid=%lu session=%lu\n", ControllerCommandName(request->Command), request->Sequence, client->Role, client->ProcessId, client->SessionId); } commandErr = ControllerHandleClientCommand(client, request, response); if ((GetTickCount64() - commandStartTick) >= 1000) { ControllerLog("[IPC][WARN] cmd-slow=%s seq=%lu role=%lu clientPid=%lu session=%lu elapsedMs=%llu " "status=%lu\n", ControllerCommandName(request->Command), request->Sequence, client->Role, client->ProcessId, client->SessionId, (unsigned long long)(GetTickCount64() - commandStartTick), commandErr); } } ok = WriteFile(client->Pipe, response, sizeof(*response), &bytesWritten, NULL); if (!ok || bytesWritten != sizeof(*response)) { disconnectErr = GetLastError(); break; } } ControllerDetachClient(client); if (client->Pipe != INVALID_HANDLE_VALUE) { (void)DisconnectNamedPipe(client->Pipe); CloseHandle(client->Pipe); client->Pipe = INVALID_HANDLE_VALUE; } if (client->DispatchIdleEvent != NULL) { DWORD dispatchWait = WaitForSingleObject(client->DispatchIdleEvent, 3000); if (dispatchWait != WAIT_OBJECT_0) { LONG refs = InterlockedCompareExchange(&client->DispatchRefCount, 0, 0); ControllerLog("[IPC][WARN] client quarantine pid=%lu session=%lu dispatchRefs=%ld wait=%lu; " "detached object left alive to avoid dispatch use-after-free\n", client->ProcessId, client->SessionId, refs, dispatchWait); free(request); free(response); return 0; } } free(request); free(response); EnterCriticalSection(&client->Lock); ControllerLog("[IPC] client disconnected pid=%lu session=%lu subscriptions=%lu queueDepth=%lu dropped=%lu " "etwQueueDepth=%lu etwDropped=%lu lastErr=%lu\n", client->ProcessId, client->SessionId, client->SubscriptionCount, client->QueueDepth, client->DroppedEvents, client->EtwQueueDepth, client->EtwDroppedEvents, disconnectErr); client->SubscriptionCount = 0; ControllerClientDestroySharedRingsLocked(client); ControllerClientFreeQueueLocked(client); ControllerClientFreeEtwQueueLocked(client); LeaveCriticalSection(&client->Lock); if (client->IoctlQueueDataEvent != NULL) { (void)CloseHandle(client->IoctlQueueDataEvent); client->IoctlQueueDataEvent = NULL; } if (client->EtwQueueDataEvent != NULL) { (void)CloseHandle(client->EtwQueueDataEvent); client->EtwQueueDataEvent = NULL; } if (client->DispatchIdleEvent != NULL) { (void)CloseHandle(client->DispatchIdleEvent); client->DispatchIdleEvent = NULL; } DeleteCriticalSection(&client->Lock); if (client->IoctlNodeSlab != NULL) { free(client->IoctlNodeSlab); client->IoctlNodeSlab = NULL; client->IoctlNodeFreeHead = NULL; } if (client->EtwNodeSlab != NULL) { free(client->EtwNodeSlab); client->EtwNodeSlab = NULL; client->EtwNodeFreeHead = NULL; } free(client); return 0; } BOOL ControllerCreatePipeSecurity(_In_ DWORD ClientRole, _Out_ PSECURITY_ATTRIBUTES SecurityAttributes, _Outptr_ PSECURITY_DESCRIPTOR *SecurityDescriptor) { BOOL ok; PCWSTR sddl = NULL; if (SecurityAttributes == NULL || SecurityDescriptor == NULL) { return FALSE; } *SecurityDescriptor = NULL; ZeroMemory(SecurityAttributes, sizeof(*SecurityAttributes)); switch (ClientRole) { case BkctlrClientRoleHook: sddl = L"D:P(A;;GA;;;SY)(A;;GRGW;;;IU)"; break; case BkctlrClientRoleControl: sddl = L"D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;IU)"; break; default: sddl = L"D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;IU)"; break; } ok = ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl, SDDL_REVISION_1, SecurityDescriptor, NULL); if (!ok || *SecurityDescriptor == NULL) { return FALSE; } SecurityAttributes->nLength = sizeof(*SecurityAttributes); SecurityAttributes->lpSecurityDescriptor = *SecurityDescriptor; SecurityAttributes->bInheritHandle = FALSE; return TRUE; }