mirror of
https://github.com/8damon/Blackbird-Platform
synced 2026-06-21 13:41:12 +00:00
3404 lines
141 KiB
C++
3404 lines
141 KiB
C++
#include "../controller_private.h"
|
|
#include "../injection/injection.h"
|
|
#include "ipc_internal.h"
|
|
#include <tlhelp32.h>
|
|
|
|
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 : "<unknown>", (unsigned long long)Address,
|
|
Classification->Rva, (unsigned long long)Size, Classification->DirectoryRva, Classification->DirectorySize,
|
|
Classification->ModulePath[0] != L'\0' ? Classification->ModulePath : L"<unknown>");
|
|
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"<none>", (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"<none>");
|
|
}
|
|
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"<unnamed>",
|
|
(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"<unnamed>",
|
|
(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"<unknown>", (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"<unknown>", (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"<unknown>",
|
|
(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 : "<untagged>");
|
|
}
|
|
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 : "<untagged>");
|
|
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;
|
|
}
|