mirror of
https://github.com/8damon/Blackbird-Platform
synced 2026-06-21 13:41:12 +00:00
375dbd1aff
Replace Blackbird_controller_ipc.c with ipc.cpp. Critical section handles are now wrapped through g_DriverLock.get() using the RAII lock wrapper introduced in the private header. The IPC server dispatch loop and all command handler stubs are preserved functionally. Refs #11.
3029 lines
107 KiB
C++
3029 lines
107 KiB
C++
#include "../blackbird_controller_private.h"
|
|
#include "../injection/blackbird_controller_injection.h"
|
|
#include <math.h>
|
|
#include <wctype.h>
|
|
|
|
static BOOL ControllerProxyQueryProcessImage(_In_ DWORD ProcessId,
|
|
_Out_ BLACKBIRD_QUERY_PROCESS_IMAGE_RESPONSE *Response)
|
|
{
|
|
WCHAR imagePath[BLACKBIRD_MAX_IMAGE_PATH_CHARS];
|
|
BOOL ok;
|
|
|
|
if (Response == NULL || ProcessId == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
ZeroMemory(Response, sizeof(*Response));
|
|
Response->ProcessId = ProcessId;
|
|
imagePath[0] = L'\0';
|
|
|
|
EnterCriticalSection(g_DriverLock.get());
|
|
ok = (g_DriverHandle != INVALID_HANDLE_VALUE) &&
|
|
BLACKBIRDSCQueryProcessImagePath(g_DriverHandle, ProcessId, imagePath, RTL_NUMBER_OF(imagePath));
|
|
LeaveCriticalSection(g_DriverLock.get());
|
|
|
|
if (!ok)
|
|
{
|
|
Response->Status = (INT32)HRESULT_FROM_WIN32(GetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
Response->Status = 0;
|
|
(void)StringCchCopyW(Response->ImagePath, RTL_NUMBER_OF(Response->ImagePath), imagePath);
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL ControllerProxySetShutdownMode(VOID)
|
|
{
|
|
BOOL ok;
|
|
|
|
EnterCriticalSection(g_DriverLock.get());
|
|
ok = (g_DriverHandle != INVALID_HANDLE_VALUE) && BLACKBIRDSCSetShutdownMode(g_DriverHandle);
|
|
LeaveCriticalSection(g_DriverLock.get());
|
|
return ok;
|
|
}
|
|
|
|
static BOOL ControllerProxyControlProcessExecution(_In_ DWORD ProcessId, _In_ BOOL Suspend)
|
|
{
|
|
BOOL ok;
|
|
|
|
EnterCriticalSection(g_DriverLock.get());
|
|
ok = (g_DriverHandle != INVALID_HANDLE_VALUE) &&
|
|
BLACKBIRDSCControlProcessExecution(g_DriverHandle, ProcessId, Suspend);
|
|
LeaveCriticalSection(g_DriverLock.get());
|
|
return ok;
|
|
}
|
|
|
|
static BOOL ControllerProxySetRuntimeConfig(_In_ DWORD Flags, _In_ DWORD Mask)
|
|
{
|
|
BOOL ok;
|
|
|
|
EnterCriticalSection(g_DriverLock.get());
|
|
ok = (g_DriverHandle != INVALID_HANDLE_VALUE) && BLACKBIRDSCSetRuntimeConfig(g_DriverHandle, Flags, Mask);
|
|
LeaveCriticalSection(g_DriverLock.get());
|
|
return ok;
|
|
}
|
|
|
|
static BOOL ControllerProxyGetRuntimeConfig(_Out_ BLACKBIRD_RUNTIME_CONFIG_RESPONSE *Response)
|
|
{
|
|
BOOL ok;
|
|
|
|
if (Response == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
ZeroMemory(Response, sizeof(*Response));
|
|
EnterCriticalSection(g_DriverLock.get());
|
|
ok = (g_DriverHandle != INVALID_HANDLE_VALUE) && BLACKBIRDSCGetRuntimeConfig(g_DriverHandle, Response);
|
|
LeaveCriticalSection(g_DriverLock.get());
|
|
return ok;
|
|
}
|
|
|
|
static VOID ControllerClientClearPendingLaunchLocked(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client)
|
|
{
|
|
if (Client == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Client->PendingLaunchArmed = FALSE;
|
|
Client->PendingLaunchPid = 0;
|
|
Client->PendingLaunchArmedTick = 0;
|
|
Client->PendingLaunchImagePath[0] = L'\0';
|
|
}
|
|
|
|
static VOID ControllerClientArmPendingLaunchLocked(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_In_opt_z_ PCWSTR ImagePath)
|
|
{
|
|
if (Client == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ControllerClientClearPendingLaunchLocked(Client);
|
|
if (ImagePath == NULL || ImagePath[0] == L'\0')
|
|
{
|
|
return;
|
|
}
|
|
|
|
(void)StringCchCopyW(Client->PendingLaunchImagePath, RTL_NUMBER_OF(Client->PendingLaunchImagePath), ImagePath);
|
|
Client->PendingLaunchArmed = TRUE;
|
|
Client->PendingLaunchArmedTick = GetTickCount64();
|
|
}
|
|
|
|
static VOID ControllerClientPrimePendingLaunchPidLocked(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_In_ DWORD ProcessId)
|
|
{
|
|
DWORD i;
|
|
|
|
if (Client == NULL || ProcessId == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Client->PendingLaunchPid = ProcessId;
|
|
Client->PendingLaunchArmed = FALSE;
|
|
Client->PendingLaunchArmedTick = 0;
|
|
|
|
for (i = 0; i < Client->SubscriptionCount; ++i)
|
|
{
|
|
if (Client->Subscriptions[i].ProcessId == ProcessId)
|
|
{
|
|
Client->Subscriptions[i].StreamMask |= BLACKBIRD_CONTROLLER_DRIVER_STREAM_MASK;
|
|
if (Client->Subscriptions[i].Dynamic)
|
|
{
|
|
Client->Subscriptions[i].Dynamic = FALSE;
|
|
Client->Subscriptions[i].Depth = 0;
|
|
Client->Subscriptions[i].SourceProcessId = 0;
|
|
Client->Subscriptions[i].LastSeenTick = 0;
|
|
}
|
|
ControllerMarkDriverSubscriptionsDirty();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (Client->SubscriptionCount >= BLACKBIRD_CONTROLLER_MAX_CLIENT_SUBSCRIPTIONS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Client->Subscriptions[Client->SubscriptionCount].ProcessId = ProcessId;
|
|
Client->Subscriptions[Client->SubscriptionCount].StreamMask = BLACKBIRD_CONTROLLER_DRIVER_STREAM_MASK;
|
|
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;
|
|
ControllerMarkDriverSubscriptionsDirty();
|
|
}
|
|
|
|
static VOID ControllerNormalizePathForCompare(_In_z_ const WCHAR *Input, _Out_writes_z_(OutputChars) WCHAR *Output,
|
|
_In_ size_t OutputChars)
|
|
{
|
|
size_t i;
|
|
size_t j = 0;
|
|
|
|
if (Output == NULL || OutputChars == 0)
|
|
{
|
|
return;
|
|
}
|
|
Output[0] = L'\0';
|
|
|
|
if (Input == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (i = 0; Input[i] != L'\0' && (j + 1) < OutputChars; ++i)
|
|
{
|
|
WCHAR ch = Input[i];
|
|
if (ch == L'/')
|
|
{
|
|
ch = L'\\';
|
|
}
|
|
Output[j++] = (WCHAR)towlower(ch);
|
|
}
|
|
Output[j] = L'\0';
|
|
}
|
|
|
|
static BOOL ControllerIsDrivePathW(_In_z_ const WCHAR *Path)
|
|
{
|
|
if (Path == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
return (Path[0] != L'\0' && Path[1] == L':');
|
|
}
|
|
|
|
static VOID ControllerBuildTailFromDosPath(_In_z_ const WCHAR *DosPath, _Out_writes_z_(TailChars) WCHAR *Tail,
|
|
_In_ size_t TailChars)
|
|
{
|
|
WCHAR normalized[BLACKBIRD_MAX_IMAGE_PATH_CHARS];
|
|
|
|
if (Tail == NULL || TailChars == 0)
|
|
{
|
|
return;
|
|
}
|
|
Tail[0] = L'\0';
|
|
|
|
if (DosPath == NULL || !ControllerIsDrivePathW(DosPath) || DosPath[2] == L'\0')
|
|
{
|
|
return;
|
|
}
|
|
|
|
ControllerNormalizePathForCompare(DosPath + 2, normalized, RTL_NUMBER_OF(normalized));
|
|
(void)StringCchCopyW(Tail, TailChars, normalized);
|
|
}
|
|
|
|
static BOOL ControllerBuildNtPathFromDosPath(_In_z_ const WCHAR *DosPath, _Out_writes_z_(NtChars) WCHAR *NtPath,
|
|
_In_ size_t NtChars)
|
|
{
|
|
WCHAR drive[3];
|
|
WCHAR devicePrefix[BLACKBIRD_MAX_IMAGE_PATH_CHARS];
|
|
|
|
if (NtPath != NULL && NtChars != 0)
|
|
{
|
|
NtPath[0] = L'\0';
|
|
}
|
|
|
|
if (DosPath == NULL || NtPath == NULL || NtChars == 0 || !ControllerIsDrivePathW(DosPath))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
drive[0] = (WCHAR)towupper(DosPath[0]);
|
|
drive[1] = L':';
|
|
drive[2] = L'\0';
|
|
|
|
if (!QueryDosDeviceW(drive, devicePrefix, RTL_NUMBER_OF(devicePrefix)))
|
|
{
|
|
NtPath[0] = L'\0';
|
|
return FALSE;
|
|
}
|
|
|
|
if (FAILED(StringCchPrintfW(NtPath, NtChars, L"%ls%ls", devicePrefix, DosPath + 2)))
|
|
{
|
|
NtPath[0] = L'\0';
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static VOID ControllerStripPathPrefixes(_Inout_updates_z_(BufferChars) WCHAR *Buffer, _In_ size_t BufferChars)
|
|
{
|
|
size_t len;
|
|
|
|
if (Buffer == NULL || BufferChars == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_wcsnicmp(Buffer, L"\\\\?\\", 4) == 0 || _wcsnicmp(Buffer, L"\\??\\", 4) == 0)
|
|
{
|
|
len = wcslen(Buffer);
|
|
if (len > 4)
|
|
{
|
|
MoveMemory(Buffer, Buffer + 4, (len - 3) * sizeof(WCHAR));
|
|
}
|
|
else
|
|
{
|
|
Buffer[0] = L'\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
static BOOL ControllerBuildPendingLaunchRequest(_In_z_ PCWSTR ImagePath, _In_ DWORD StreamMask,
|
|
_Out_ BLACKBIRD_ARM_PENDING_LAUNCH_REQUEST *Request)
|
|
{
|
|
WCHAR canonical[BLACKBIRD_MAX_IMAGE_PATH_CHARS];
|
|
WCHAR ntPath[BLACKBIRD_MAX_IMAGE_PATH_CHARS];
|
|
WCHAR effective[BLACKBIRD_MAX_IMAGE_PATH_CHARS];
|
|
DWORD fullLen;
|
|
|
|
if (ImagePath == NULL || ImagePath[0] == L'\0' || Request == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
ZeroMemory(Request, sizeof(*Request));
|
|
ZeroMemory(canonical, sizeof(canonical));
|
|
ZeroMemory(ntPath, sizeof(ntPath));
|
|
ZeroMemory(effective, sizeof(effective));
|
|
|
|
(void)StringCchCopyW(effective, RTL_NUMBER_OF(effective), ImagePath);
|
|
ControllerStripPathPrefixes(effective, RTL_NUMBER_OF(effective));
|
|
if (ControllerIsDrivePathW(effective))
|
|
{
|
|
fullLen = GetFullPathNameW(effective, RTL_NUMBER_OF(canonical), canonical, NULL);
|
|
if (fullLen != 0 && fullLen < RTL_NUMBER_OF(canonical))
|
|
{
|
|
(void)StringCchCopyW(effective, RTL_NUMBER_OF(effective), canonical);
|
|
}
|
|
}
|
|
|
|
Request->StreamMask = StreamMask;
|
|
ControllerNormalizePathForCompare(effective, Request->ImagePathNormDos, RTL_NUMBER_OF(Request->ImagePathNormDos));
|
|
|
|
if (ControllerIsDrivePathW(effective))
|
|
{
|
|
ControllerBuildTailFromDosPath(effective, Request->ImagePathTail, RTL_NUMBER_OF(Request->ImagePathTail));
|
|
if (ControllerBuildNtPathFromDosPath(effective, ntPath, RTL_NUMBER_OF(ntPath)))
|
|
{
|
|
ControllerNormalizePathForCompare(ntPath, Request->ImagePathNormNt,
|
|
RTL_NUMBER_OF(Request->ImagePathNormNt));
|
|
}
|
|
}
|
|
else if (_wcsnicmp(effective, L"\\device\\", 8) == 0 || _wcsnicmp(effective, L"\\systemroot\\", 12) == 0)
|
|
{
|
|
ControllerNormalizePathForCompare(effective, Request->ImagePathNormNt, RTL_NUMBER_OF(Request->ImagePathNormNt));
|
|
}
|
|
|
|
return (Request->ImagePathNormDos[0] != L'\0' || Request->ImagePathNormNt[0] != L'\0' ||
|
|
Request->ImagePathTail[0] != L'\0');
|
|
}
|
|
|
|
static DWORD ControllerQueryHookReadyMaskForProcess(_In_ DWORD ProcessId)
|
|
{
|
|
PBLACKBIRD_CONTROLLER_CLIENT current;
|
|
DWORD observedMask = 0;
|
|
|
|
if (ProcessId == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
EnterCriticalSection(g_ClientListLock.get());
|
|
current = g_ClientList;
|
|
while (current != NULL)
|
|
{
|
|
if (current->ProcessId == ProcessId)
|
|
{
|
|
observedMask |= (DWORD)InterlockedCompareExchange(¤t->HookReadyMask, 0, 0);
|
|
}
|
|
current = current->Next;
|
|
}
|
|
LeaveCriticalSection(g_ClientListLock.get());
|
|
|
|
return observedMask;
|
|
}
|
|
|
|
DWORD ControllerWaitForHookReady(_In_ DWORD ProcessId)
|
|
{
|
|
static const DWORD kPollIntervalMs = 5u;
|
|
static const ULONGLONG kLogPeriodMs = 1000ull;
|
|
static const ULONGLONG kTimeoutMs = (ULONGLONG)BLACKBIRD_CONTROLLER_HOOK_READY_TIMEOUT_MS;
|
|
static const DWORD kRequiredMask = BLACKBIRD_CONTROLLER_HOOK_LAUNCH_REQUIRED_MASK;
|
|
HANDLE processHandle = NULL;
|
|
DWORD readyMask = 0;
|
|
ULONGLONG startTick;
|
|
ULONGLONG lastLogTick;
|
|
ULONGLONG now;
|
|
|
|
if (ProcessId == 0)
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
processHandle = OpenProcess(SYNCHRONIZE, FALSE, ProcessId);
|
|
startTick = GetTickCount64();
|
|
lastLogTick = startTick;
|
|
|
|
for (;;)
|
|
{
|
|
readyMask = ControllerQueryHookReadyMaskForProcess(ProcessId);
|
|
if ((readyMask & kRequiredMask) == kRequiredMask)
|
|
{
|
|
ULONGLONG elapsed = GetTickCount64() - startTick;
|
|
ControllerLog("[IPC] hook-ready confirmed pid=%lu mask=0x%08lX elapsedMs=%llu\n", ProcessId, readyMask,
|
|
elapsed);
|
|
if (processHandle != NULL)
|
|
{
|
|
CloseHandle(processHandle);
|
|
}
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
now = GetTickCount64();
|
|
if (now - startTick >= kTimeoutMs)
|
|
{
|
|
ControllerLog("[IPC][WARN] hook-ready timed out pid=%lu mask=0x%08lX required=0x%08X elapsedMs=%llu\n",
|
|
ProcessId, readyMask, kRequiredMask, now - startTick);
|
|
if (processHandle != NULL)
|
|
{
|
|
CloseHandle(processHandle);
|
|
}
|
|
return ERROR_TIMEOUT;
|
|
}
|
|
|
|
if (now - lastLogTick >= kLogPeriodMs)
|
|
{
|
|
ControllerLog("[IPC] waiting hook-ready pid=%lu mask=0x%08lX required=0x%08X elapsedMs=%llu\n", ProcessId,
|
|
readyMask, kRequiredMask, now - startTick);
|
|
lastLogTick = now;
|
|
}
|
|
|
|
if (processHandle != NULL)
|
|
{
|
|
DWORD waitResult = WaitForSingleObject(processHandle, kPollIntervalMs);
|
|
if (waitResult == WAIT_OBJECT_0)
|
|
{
|
|
DWORD exitCode = 0;
|
|
(void)GetExitCodeProcess(processHandle, &exitCode);
|
|
ControllerLog(
|
|
"[IPC][WARN] hook-ready aborted pid=%lu process exited before ready mask=0x%08lX exitCode=0x%08lX\n",
|
|
ProcessId, readyMask, exitCode);
|
|
CloseHandle(processHandle);
|
|
return ERROR_DLL_INIT_FAILED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Sleep(kPollIntervalMs);
|
|
}
|
|
}
|
|
}
|
|
|
|
static DWORD ControllerEnsureCaptureReadyForLaunch(VOID)
|
|
{
|
|
DWORD openErr = ERROR_SUCCESS;
|
|
|
|
if (ControllerShouldStop())
|
|
{
|
|
return ERROR_SHUTDOWN_IN_PROGRESS;
|
|
}
|
|
if (g_EtwSession == NULL || g_EtwThread == NULL)
|
|
{
|
|
return ERROR_SERVICE_NOT_ACTIVE;
|
|
}
|
|
|
|
EnterCriticalSection(g_DriverLock.get());
|
|
if (g_DriverHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
g_DriverHandle = BLACKBIRDSCOpenControlDevice();
|
|
if (g_DriverHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
openErr = GetLastError();
|
|
}
|
|
}
|
|
LeaveCriticalSection(g_DriverLock.get());
|
|
if (openErr != ERROR_SUCCESS)
|
|
{
|
|
return (openErr == ERROR_SUCCESS) ? ERROR_DEVICE_NOT_CONNECTED : openErr;
|
|
}
|
|
|
|
ControllerMarkDriverSubscriptionsDirty();
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
static BOOL ControllerValidatePacket(_In_ const BLACKBIRD_IPC_PACKET *Packet, _In_ UINT16 ExpectedType)
|
|
{
|
|
if (Packet == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (Packet->Magic != BLACKBIRD_IPC_MAGIC)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (Packet->Version != BLACKBIRD_IPC_VERSION)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (Packet->PacketType != ExpectedType)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static VOID ControllerPrepareResponse(_In_ const BLACKBIRD_IPC_PACKET *Request, _Out_ BLACKBIRD_IPC_PACKET *Response)
|
|
{
|
|
ZeroMemory(Response, sizeof(*Response));
|
|
Response->Magic = BLACKBIRD_IPC_MAGIC;
|
|
Response->Version = BLACKBIRD_IPC_VERSION;
|
|
Response->PacketType = BlackbirdIpcPacketResponse;
|
|
Response->Command = Request->Command;
|
|
Response->Sequence = Request->Sequence;
|
|
Response->Status = ERROR_SUCCESS;
|
|
}
|
|
|
|
static DWORD ControllerClampSharedRingCapacity(_In_ DWORD Desired, _In_ DWORD DefaultValue, _In_ DWORD MaxValue)
|
|
{
|
|
if (Desired == 0)
|
|
{
|
|
return DefaultValue;
|
|
}
|
|
if (Desired > MaxValue)
|
|
{
|
|
return MaxValue;
|
|
}
|
|
if (Desired < 64)
|
|
{
|
|
return 64;
|
|
}
|
|
return Desired;
|
|
}
|
|
|
|
_Success_(return) static BOOL
|
|
ControllerCreateSharedRing(_In_ DWORD Capacity, _In_ DWORD RecordSize, _Out_ HANDLE *MappingHandle,
|
|
_Out_ HANDLE *DataReadyEvent, _Out_ PBLACKBIRD_IPC_SHARED_RING_HEADER *Header,
|
|
_Out_ PBYTE *Records)
|
|
{
|
|
SIZE_T totalBytes;
|
|
HANDLE mapping;
|
|
PBYTE view;
|
|
HANDLE ready;
|
|
PBLACKBIRD_IPC_SHARED_RING_HEADER hdr;
|
|
|
|
if (MappingHandle != NULL)
|
|
{
|
|
*MappingHandle = NULL;
|
|
}
|
|
if (DataReadyEvent != NULL)
|
|
{
|
|
*DataReadyEvent = NULL;
|
|
}
|
|
if (Header != NULL)
|
|
{
|
|
*Header = NULL;
|
|
}
|
|
if (Records != NULL)
|
|
{
|
|
*Records = NULL;
|
|
}
|
|
|
|
if (MappingHandle == NULL || DataReadyEvent == NULL || Header == NULL || Records == NULL || Capacity == 0 ||
|
|
RecordSize == 0)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
totalBytes = sizeof(*hdr) + ((SIZE_T)Capacity * (SIZE_T)RecordSize);
|
|
mapping = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, (DWORD)((ULONGLONG)totalBytes >> 32),
|
|
(DWORD)(totalBytes & 0xFFFFFFFFu), NULL);
|
|
if (mapping == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
view = (PBYTE)MapViewOfFile(mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, totalBytes);
|
|
if (view == NULL)
|
|
{
|
|
DWORD err = GetLastError();
|
|
(void)CloseHandle(mapping);
|
|
SetLastError(err);
|
|
return FALSE;
|
|
}
|
|
|
|
ready = CreateEventW(NULL, TRUE, FALSE, NULL);
|
|
if (ready == NULL)
|
|
{
|
|
DWORD err = GetLastError();
|
|
(void)UnmapViewOfFile(view);
|
|
(void)CloseHandle(mapping);
|
|
SetLastError(err);
|
|
return FALSE;
|
|
}
|
|
|
|
hdr = (PBLACKBIRD_IPC_SHARED_RING_HEADER)view;
|
|
ZeroMemory(hdr, sizeof(*hdr));
|
|
hdr->Capacity = Capacity;
|
|
hdr->RecordSize = RecordSize;
|
|
|
|
*MappingHandle = mapping;
|
|
*DataReadyEvent = ready;
|
|
*Header = hdr;
|
|
*Records = view + sizeof(*hdr);
|
|
return TRUE;
|
|
}
|
|
|
|
static DWORD ControllerClientEnsureSharedRingsLocked(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_In_ DWORD DesiredIoctlCapacity, _In_ DWORD DesiredEtwCapacity)
|
|
{
|
|
DWORD ioctlCap;
|
|
DWORD etwCap;
|
|
|
|
if (Client == NULL)
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Client->IoctlSharedHeader != NULL && Client->EtwSharedHeader != NULL && Client->IoctlSharedDataEvent != NULL &&
|
|
Client->EtwSharedDataEvent != NULL)
|
|
{
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
ioctlCap = ControllerClampSharedRingCapacity(DesiredIoctlCapacity, BLACKBIRD_CONTROLLER_SHARED_IOCTL_RING_CAPACITY,
|
|
1048576);
|
|
etwCap =
|
|
ControllerClampSharedRingCapacity(DesiredEtwCapacity, BLACKBIRD_CONTROLLER_SHARED_ETW_RING_CAPACITY, 262144);
|
|
|
|
if (!ControllerCreateSharedRing(ioctlCap, sizeof(BLACKBIRD_EVENT_RECORD), &Client->IoctlSharedMapping,
|
|
&Client->IoctlSharedDataEvent, &Client->IoctlSharedHeader,
|
|
&Client->IoctlSharedRecords))
|
|
{
|
|
return GetLastError();
|
|
}
|
|
|
|
if (!ControllerCreateSharedRing(etwCap, sizeof(BLACKBIRD_IPC_ETW_EVENT), &Client->EtwSharedMapping,
|
|
&Client->EtwSharedDataEvent, &Client->EtwSharedHeader, &Client->EtwSharedRecords))
|
|
{
|
|
DWORD err = GetLastError();
|
|
ControllerClientDestroySharedRingsLocked(Client);
|
|
return err;
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
static VOID ControllerCloseRemoteHandle(_In_ HANDLE ClientProcess, _Inout_ HANDLE *RemoteHandle)
|
|
{
|
|
HANDLE localHandle = NULL;
|
|
|
|
if (ClientProcess == NULL || ClientProcess == INVALID_HANDLE_VALUE || RemoteHandle == NULL ||
|
|
*RemoteHandle == NULL || *RemoteHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (DuplicateHandle(ClientProcess, *RemoteHandle, GetCurrentProcess(), &localHandle, 0, FALSE,
|
|
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
|
|
{
|
|
if (localHandle != NULL && localHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
(void)CloseHandle(localHandle);
|
|
}
|
|
}
|
|
|
|
*RemoteHandle = NULL;
|
|
}
|
|
|
|
static DWORD ControllerClientOpenSharedRing(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_In_ const BLACKBIRD_IPC_OPEN_SHARED_RING_REQUEST *Request,
|
|
_Out_ BLACKBIRD_IPC_OPEN_SHARED_RING_RESPONSE *Response)
|
|
{
|
|
DWORD err;
|
|
HANDLE clientProcess = NULL;
|
|
HANDLE ioctlMapDup = NULL;
|
|
HANDLE ioctlEventDup = NULL;
|
|
HANDLE etwMapDup = NULL;
|
|
HANDLE etwEventDup = NULL;
|
|
|
|
if (Client == NULL || Request == NULL || Response == NULL || Client->ProcessId == 0)
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
ZeroMemory(Response, sizeof(*Response));
|
|
EnterCriticalSection(&Client->Lock);
|
|
err = ControllerClientEnsureSharedRingsLocked(Client, Request->DesiredIoctlCapacity, Request->DesiredEtwCapacity);
|
|
if (err != ERROR_SUCCESS)
|
|
{
|
|
LeaveCriticalSection(&Client->Lock);
|
|
return err;
|
|
}
|
|
|
|
clientProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, Client->ProcessId);
|
|
if (clientProcess == NULL)
|
|
{
|
|
err = GetLastError();
|
|
LeaveCriticalSection(&Client->Lock);
|
|
return err;
|
|
}
|
|
|
|
if (!DuplicateHandle(GetCurrentProcess(), Client->IoctlSharedMapping, clientProcess, &ioctlMapDup, 0, FALSE,
|
|
DUPLICATE_SAME_ACCESS) ||
|
|
!DuplicateHandle(GetCurrentProcess(), Client->IoctlSharedDataEvent, clientProcess, &ioctlEventDup, 0, FALSE,
|
|
DUPLICATE_SAME_ACCESS) ||
|
|
!DuplicateHandle(GetCurrentProcess(), Client->EtwSharedMapping, clientProcess, &etwMapDup, 0, FALSE,
|
|
DUPLICATE_SAME_ACCESS) ||
|
|
!DuplicateHandle(GetCurrentProcess(), Client->EtwSharedDataEvent, clientProcess, &etwEventDup, 0, FALSE,
|
|
DUPLICATE_SAME_ACCESS))
|
|
{
|
|
err = GetLastError();
|
|
ControllerCloseRemoteHandle(clientProcess, &ioctlMapDup);
|
|
ControllerCloseRemoteHandle(clientProcess, &ioctlEventDup);
|
|
ControllerCloseRemoteHandle(clientProcess, &etwMapDup);
|
|
ControllerCloseRemoteHandle(clientProcess, &etwEventDup);
|
|
LeaveCriticalSection(&Client->Lock);
|
|
(void)CloseHandle(clientProcess);
|
|
return err;
|
|
}
|
|
|
|
ZeroMemory(Response, sizeof(*Response));
|
|
Response->IoctlMappingHandle = (UINT64)(ULONG_PTR)ioctlMapDup;
|
|
Response->IoctlDataReadyEventHandle = (UINT64)(ULONG_PTR)ioctlEventDup;
|
|
Response->IoctlCapacity = Client->IoctlSharedHeader->Capacity;
|
|
Response->IoctlRecordSize = Client->IoctlSharedHeader->RecordSize;
|
|
Response->EtwMappingHandle = (UINT64)(ULONG_PTR)etwMapDup;
|
|
Response->EtwDataReadyEventHandle = (UINT64)(ULONG_PTR)etwEventDup;
|
|
Response->EtwCapacity = Client->EtwSharedHeader->Capacity;
|
|
Response->EtwRecordSize = Client->EtwSharedHeader->RecordSize;
|
|
Client->SharedRingEnabled = TRUE;
|
|
LeaveCriticalSection(&Client->Lock);
|
|
|
|
(void)CloseHandle(clientProcess);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
_Success_(return) static BOOL ControllerCheckTokenMembershipRid(_In_ HANDLE Token, _In_ DWORD Rid, _Out_ BOOL *IsMember)
|
|
{
|
|
SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY;
|
|
PSID sid = NULL;
|
|
BOOL isMember = FALSE;
|
|
|
|
if (Token == NULL || Token == INVALID_HANDLE_VALUE || IsMember == NULL)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
*IsMember = FALSE;
|
|
if (!AllocateAndInitializeSid(&ntAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, Rid, 0, 0, 0, 0, 0, 0, &sid))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!CheckTokenMembership(Token, sid, &isMember))
|
|
{
|
|
DWORD err = GetLastError();
|
|
FreeSid(sid);
|
|
SetLastError(err);
|
|
return FALSE;
|
|
}
|
|
|
|
FreeSid(sid);
|
|
*IsMember = isMember;
|
|
return TRUE;
|
|
}
|
|
|
|
_Success_(return) static BOOL ControllerCheckTokenIsLocalSystem(_In_ HANDLE Token, _Out_ BOOL *IsSystem)
|
|
{
|
|
SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY;
|
|
PSID systemSid = NULL;
|
|
BOOL isSystem = FALSE;
|
|
|
|
if (Token == NULL || Token == INVALID_HANDLE_VALUE || IsSystem == NULL)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
*IsSystem = FALSE;
|
|
if (!AllocateAndInitializeSid(&ntAuthority, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &systemSid))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!CheckTokenMembership(Token, systemSid, &isSystem))
|
|
{
|
|
DWORD err = GetLastError();
|
|
FreeSid(systemSid);
|
|
SetLastError(err);
|
|
return FALSE;
|
|
}
|
|
|
|
FreeSid(systemSid);
|
|
*IsSystem = isSystem;
|
|
return TRUE;
|
|
}
|
|
|
|
_Success_(return) static BOOL
|
|
ControllerClientIsPrivileged(_In_ const BLACKBIRD_CONTROLLER_CLIENT *Client, _Out_ BOOL *IsPrivileged)
|
|
{
|
|
HANDLE process = NULL;
|
|
HANDLE token = NULL;
|
|
BOOL isAdmin = FALSE;
|
|
BOOL isSystem = FALSE;
|
|
|
|
if (Client == NULL || IsPrivileged == NULL || Client->ProcessId == 0)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
*IsPrivileged = FALSE;
|
|
process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, Client->ProcessId);
|
|
if (process == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!OpenProcessToken(process, TOKEN_QUERY | TOKEN_DUPLICATE, &token))
|
|
{
|
|
DWORD err = GetLastError();
|
|
CloseHandle(process);
|
|
SetLastError(err);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!ControllerCheckTokenMembershipRid(token, DOMAIN_ALIAS_RID_ADMINS, &isAdmin))
|
|
{
|
|
DWORD err = GetLastError();
|
|
CloseHandle(token);
|
|
CloseHandle(process);
|
|
SetLastError(err);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!ControllerCheckTokenIsLocalSystem(token, &isSystem))
|
|
{
|
|
DWORD err = GetLastError();
|
|
CloseHandle(token);
|
|
CloseHandle(process);
|
|
SetLastError(err);
|
|
return FALSE;
|
|
}
|
|
|
|
CloseHandle(token);
|
|
CloseHandle(process);
|
|
*IsPrivileged = (isAdmin || isSystem);
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL ControllerCommandAllowedForRole(_In_ DWORD ClientRole, _In_ UINT32 Command)
|
|
{
|
|
switch (ClientRole)
|
|
{
|
|
case BlackbirdControllerClientRoleHook:
|
|
return (Command == BlackbirdIpcCommandHandshake || Command == BlackbirdIpcCommandPublishHookEvent ||
|
|
Command == BlackbirdIpcCommandNotifyHookReady);
|
|
case BlackbirdControllerClientRoleControl:
|
|
return (Command != BlackbirdIpcCommandPublishHookEvent && Command != BlackbirdIpcCommandNotifyHookReady);
|
|
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 BLACKBIRD_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_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_In_ const BLACKBIRD_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);
|
|
ControllerMarkDriverSubscriptionsDirty();
|
|
if (!ControllerApplyDriverSubscriptions())
|
|
{
|
|
return GetLastError();
|
|
}
|
|
return ERROR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (Client->SubscriptionCount >= BLACKBIRD_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);
|
|
|
|
ControllerMarkDriverSubscriptionsDirty();
|
|
if (!ControllerApplyDriverSubscriptions())
|
|
{
|
|
return GetLastError();
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
static DWORD ControllerClientUnsubscribe(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_In_ const BLACKBIRD_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)ControllerApplyDriverSubscriptions();
|
|
}
|
|
return ERROR_SUCCESS;
|
|
}
|
|
}
|
|
LeaveCriticalSection(&Client->Lock);
|
|
|
|
return ERROR_NOT_FOUND;
|
|
}
|
|
|
|
static DWORD ControllerClientSetPids(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_In_ const BLACKBIRD_SET_PIDS_REQUEST *Request)
|
|
{
|
|
DWORD i;
|
|
BOOL privilegeResolved = FALSE;
|
|
BOOL isPrivileged = FALSE;
|
|
|
|
if (Client == NULL || Request == NULL || Request->ProcessCount > BLACKBIRD_MAX_PID_LIST ||
|
|
Request->ProcessCount == 0 || !ControllerIsValidStreamMask(Request->StreamMask))
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
for (i = 0; i < Request->ProcessCount; ++i)
|
|
{
|
|
DWORD pid = Request->ProcessIds[i];
|
|
if (pid == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!ControllerClientCanMonitorPid(Client, pid, &privilegeResolved, &isPrivileged))
|
|
{
|
|
DWORD err = GetLastError();
|
|
return (err == ERROR_SUCCESS) ? ERROR_ACCESS_DENIED : err;
|
|
}
|
|
}
|
|
|
|
EnterCriticalSection(&Client->Lock);
|
|
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 < BLACKBIRD_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;
|
|
}
|
|
}
|
|
LeaveCriticalSection(&Client->Lock);
|
|
|
|
if (Client->SubscriptionCount == 0)
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
ControllerLog("[IPC] set-pids clientPid=%lu count=%lu streamMask=0x%08lX\n", Client->ProcessId,
|
|
Client->SubscriptionCount, Request->StreamMask);
|
|
|
|
ControllerMarkDriverSubscriptionsDirty();
|
|
if (!ControllerApplyDriverSubscriptions())
|
|
{
|
|
return GetLastError();
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
static DWORD ControllerClientGetEvent(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client, _In_ DWORD TimeoutMs,
|
|
_Out_ BLACKBIRD_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_ BLACKBIRD_CONTROLLER_CLIENT *Client, _In_ DWORD TimeoutMs,
|
|
_Out_ BLACKBIRD_IPC_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 ControllerClientGetStats(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_Out_ BLACKBIRD_STATS_RESPONSE *Stats)
|
|
{
|
|
if (Client == NULL || Stats == NULL)
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
ZeroMemory(Stats, sizeof(*Stats));
|
|
EnterCriticalSection(&Client->Lock);
|
|
Stats->SubscriptionCount = Client->SubscriptionCount;
|
|
Stats->QueueDepth = Client->QueueDepth;
|
|
Stats->DroppedEvents = Client->DroppedEvents;
|
|
LeaveCriticalSection(&Client->Lock);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
static VOID ControllerSanitizeAnsiLabel(_In_opt_z_ PCSTR Input, _Out_writes_z_(OutputChars) PSTR Output,
|
|
_In_ size_t OutputChars)
|
|
{
|
|
size_t i;
|
|
size_t write = 0;
|
|
|
|
if (Output == NULL || OutputChars == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Output[0] = '\0';
|
|
if (Input == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (i = 0; Input[i] != '\0' && write + 1 < OutputChars; ++i)
|
|
{
|
|
CHAR ch = Input[i];
|
|
if ((unsigned char)ch < 0x20u || ch == '\x7F')
|
|
{
|
|
continue;
|
|
}
|
|
Output[write++] = ch;
|
|
}
|
|
Output[write] = '\0';
|
|
}
|
|
|
|
static PCSTR ControllerHookEventKindName(_In_ UINT32 Kind)
|
|
{
|
|
switch (Kind)
|
|
{
|
|
case BlackbirdIpcHookEventNt:
|
|
return "Nt";
|
|
case BlackbirdIpcHookEventWinsock:
|
|
return "Winsock";
|
|
case BlackbirdIpcHookEventKi:
|
|
return "Ki";
|
|
case BlackbirdIpcHookEventExceptionLowNoise:
|
|
return "ExceptionLowNoise";
|
|
case BlackbirdIpcHookEventExceptionHighPriv:
|
|
return "ExceptionHighPriv";
|
|
case BlackbirdIpcHookEventIntegrity:
|
|
return "Integrity";
|
|
case BlackbirdIpcHookEventModule:
|
|
return "Module";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
static PCSTR ControllerMemoryProtectName(_In_ UINT32 Protect)
|
|
{
|
|
switch (Protect)
|
|
{
|
|
case 0x01:
|
|
return "PAGE_NOACCESS";
|
|
case 0x02:
|
|
return "PAGE_READONLY";
|
|
case 0x04:
|
|
return "PAGE_READWRITE";
|
|
case 0x08:
|
|
return "PAGE_WRITECOPY";
|
|
case 0x10:
|
|
return "PAGE_EXECUTE";
|
|
case 0x20:
|
|
return "PAGE_EXECUTE_READ";
|
|
case 0x40:
|
|
return "PAGE_EXECUTE_READWRITE";
|
|
case 0x80:
|
|
return "PAGE_EXECUTE_WRITECOPY";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
static PCSTR ControllerMemoryAllocTypeName(_In_ UINT32 AllocationType)
|
|
{
|
|
if ((AllocationType & 0x3000u) == 0x3000u)
|
|
{
|
|
return "MEM_COMMIT_RESERVE";
|
|
}
|
|
if ((AllocationType & 0x1000u) != 0)
|
|
{
|
|
return "MEM_COMMIT";
|
|
}
|
|
if ((AllocationType & 0x2000u) != 0)
|
|
{
|
|
return "MEM_RESERVE";
|
|
}
|
|
if ((AllocationType & 0x1000000u) != 0)
|
|
{
|
|
return "MEM_LARGE_PAGES";
|
|
}
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
static UINT16 ControllerHookByteSwap16(_In_ UINT16 Value)
|
|
{
|
|
return (UINT16)(((Value & 0x00FFu) << 8) | ((Value & 0xFF00u) >> 8));
|
|
}
|
|
|
|
static BOOL ControllerHookIsInterestingProcessAccess(_In_ ULONG DesiredAccess)
|
|
{
|
|
return ((DesiredAccess & PROCESS_VM_OPERATION) != 0) || ((DesiredAccess & PROCESS_VM_WRITE) != 0) ||
|
|
((DesiredAccess & PROCESS_VM_READ) != 0) || ((DesiredAccess & PROCESS_CREATE_THREAD) != 0) ||
|
|
((DesiredAccess & PROCESS_DUP_HANDLE) != 0) || ((DesiredAccess & PROCESS_QUERY_INFORMATION) != 0) ||
|
|
((DesiredAccess & PROCESS_QUERY_LIMITED_INFORMATION) != 0) ||
|
|
((DesiredAccess & PROCESS_SUSPEND_RESUME) != 0);
|
|
}
|
|
|
|
static BOOL ControllerHookIsInterestingThreadAccess(_In_ ULONG DesiredAccess)
|
|
{
|
|
return ((DesiredAccess & THREAD_SET_CONTEXT) != 0) || ((DesiredAccess & THREAD_GET_CONTEXT) != 0) ||
|
|
((DesiredAccess & THREAD_SUSPEND_RESUME) != 0) || ((DesiredAccess & THREAD_QUERY_INFORMATION) != 0) ||
|
|
((DesiredAccess & THREAD_SET_INFORMATION) != 0);
|
|
}
|
|
|
|
static UINT32 ControllerHookSeverityForProcessAccess(_In_ ULONG DesiredAccess)
|
|
{
|
|
if ((DesiredAccess & (PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD)) != 0)
|
|
{
|
|
return 6u;
|
|
}
|
|
if ((DesiredAccess & (PROCESS_VM_READ | PROCESS_DUP_HANDLE)) != 0)
|
|
{
|
|
return 4u;
|
|
}
|
|
return 2u;
|
|
}
|
|
|
|
static UINT32 ControllerHookSeverityForThreadAccess(_In_ ULONG DesiredAccess)
|
|
{
|
|
if ((DesiredAccess & (THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME)) != 0)
|
|
{
|
|
return 6u;
|
|
}
|
|
if ((DesiredAccess & (THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION)) != 0)
|
|
{
|
|
return 4u;
|
|
}
|
|
return 2u;
|
|
}
|
|
|
|
static UINT32 ControllerCallerOriginSeverityBoost(_In_ UINT32 CallerFlags)
|
|
{
|
|
if (CallerFlags & BLACKBIRD_HOOK_CALLER_FLAG_HAS_UNMAPPED)
|
|
{
|
|
return 2u;
|
|
}
|
|
if (CallerFlags & BLACKBIRD_HOOK_CALLER_FLAG_HAS_NONSYSTEM_DLL)
|
|
{
|
|
return 1u;
|
|
}
|
|
return 0u;
|
|
}
|
|
|
|
static BOOL ControllerHookDecodeSockaddr(_In_reads_bytes_(SampleSize) const UINT8 *Sample, _In_ UINT32 SampleSize,
|
|
_Out_ UINT16 *FamilyOut, _Out_ UINT16 *PortOut)
|
|
{
|
|
UINT16 family;
|
|
|
|
if (FamilyOut != NULL)
|
|
{
|
|
*FamilyOut = 0;
|
|
}
|
|
if (PortOut != NULL)
|
|
{
|
|
*PortOut = 0;
|
|
}
|
|
|
|
if (FamilyOut == NULL || PortOut == NULL || Sample == NULL || SampleSize < sizeof(UINT16))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
CopyMemory(&family, Sample, sizeof(family));
|
|
*FamilyOut = family;
|
|
*PortOut = 0;
|
|
|
|
if (family == 2u && SampleSize >= 4u)
|
|
{
|
|
UINT16 netPort;
|
|
CopyMemory(&netPort, Sample + 2, sizeof(netPort));
|
|
*PortOut = ControllerHookByteSwap16(netPort);
|
|
return TRUE;
|
|
}
|
|
|
|
if (family == 23u && SampleSize >= 4u)
|
|
{
|
|
UINT16 netPort;
|
|
CopyMemory(&netPort, Sample + 2, sizeof(netPort));
|
|
*PortOut = ControllerHookByteSwap16(netPort);
|
|
return TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static VOID ControllerHookCopyWideSampleToReason(_Out_writes_z_(ReasonChars) PWSTR Reason, _In_ size_t ReasonChars,
|
|
_In_reads_bytes_(SampleSize) const UINT8 *Sample,
|
|
_In_ UINT32 SampleSize)
|
|
{
|
|
size_t charCount;
|
|
|
|
if (Reason == NULL || ReasonChars == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Reason[0] = L'\0';
|
|
if (Sample == NULL || SampleSize < sizeof(WCHAR))
|
|
{
|
|
return;
|
|
}
|
|
|
|
charCount = (size_t)(SampleSize / sizeof(WCHAR));
|
|
if (charCount >= ReasonChars)
|
|
{
|
|
charCount = ReasonChars - 1;
|
|
}
|
|
|
|
CopyMemory(Reason, Sample, charCount * sizeof(WCHAR));
|
|
Reason[charCount] = L'\0';
|
|
}
|
|
|
|
static VOID ControllerHookCopyAnsiSampleToReason(_Out_writes_z_(ReasonChars) PWSTR Reason, _In_ size_t ReasonChars,
|
|
_In_reads_bytes_(SampleSize) const UINT8 *Sample,
|
|
_In_ UINT32 SampleSize)
|
|
{
|
|
CHAR buffer[BLACKBIRD_IPC_MAX_HOOK_DATA_SAMPLE + 1];
|
|
int written;
|
|
|
|
if (Reason == NULL || ReasonChars == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Reason[0] = L'\0';
|
|
if (Sample == NULL || SampleSize == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RtlZeroMemory(buffer, sizeof(buffer));
|
|
CopyMemory(buffer, Sample,
|
|
(SampleSize < BLACKBIRD_IPC_MAX_HOOK_DATA_SAMPLE) ? SampleSize : BLACKBIRD_IPC_MAX_HOOK_DATA_SAMPLE);
|
|
written = MultiByteToWideChar(CP_ACP, 0, buffer, -1, Reason, (int)ReasonChars);
|
|
if (written <= 0)
|
|
{
|
|
Reason[0] = L'\0';
|
|
}
|
|
}
|
|
|
|
static double ControllerComputeSampleEntropy(_In_reads_(SampleSize) const UINT8 *Sample, _In_ UINT32 SampleSize)
|
|
{
|
|
UINT32 i;
|
|
UINT32 counts[256];
|
|
double entropy = 0.0;
|
|
double invLength;
|
|
|
|
if (Sample == NULL || SampleSize <= 1)
|
|
{
|
|
return -1.0;
|
|
}
|
|
|
|
ZeroMemory(counts, sizeof(counts));
|
|
for (i = 0; i < SampleSize; ++i)
|
|
{
|
|
counts[Sample[i]] += 1u;
|
|
}
|
|
|
|
invLength = 1.0 / (double)SampleSize;
|
|
for (i = 0; i < RTL_NUMBER_OF(counts); ++i)
|
|
{
|
|
if (counts[i] != 0u)
|
|
{
|
|
double p = (double)counts[i] * invLength;
|
|
entropy -= p * (log(p) / log(2.0));
|
|
}
|
|
}
|
|
|
|
return entropy;
|
|
}
|
|
|
|
static VOID ControllerHookAppendArgsToReason(_Inout_updates_(ReasonChars) PWSTR Reason, _In_ size_t ReasonChars,
|
|
_In_reads_(ArgCount) const UINT64 *Args, _In_ UINT32 ArgCount)
|
|
{
|
|
size_t reasonOffset;
|
|
UINT32 i;
|
|
|
|
if (Reason == NULL || ReasonChars == 0 || Args == NULL || ArgCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
reasonOffset = wcslen(Reason);
|
|
for (i = 0; i < ArgCount && reasonOffset + 20 < ReasonChars; ++i)
|
|
{
|
|
WCHAR token[32];
|
|
|
|
(void)StringCchPrintfW(token, RTL_NUMBER_OF(token), L"a%lu=", (unsigned long)i);
|
|
if (wcsstr(Reason, token) != NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
(void)StringCchPrintfW(Reason + reasonOffset, ReasonChars - reasonOffset, L" a%lu=0x%llX", (unsigned long)i,
|
|
(unsigned long long)Args[i]);
|
|
reasonOffset = wcslen(Reason);
|
|
}
|
|
}
|
|
|
|
static VOID ControllerHookCopyArgs(_Out_writes_(BLACKBIRD_IPC_MAX_HOOK_ARGS) UINT64 *Destination,
|
|
_Out_opt_ UINT32 *DestinationCount, _In_reads_(SourceCount) const UINT64 *Source,
|
|
_In_ UINT32 SourceCount)
|
|
{
|
|
UINT32 i;
|
|
UINT32 count;
|
|
|
|
if (Destination == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RtlZeroMemory(Destination, sizeof(UINT64) * BLACKBIRD_IPC_MAX_HOOK_ARGS);
|
|
count = SourceCount;
|
|
if (count > BLACKBIRD_IPC_MAX_HOOK_ARGS)
|
|
{
|
|
count = BLACKBIRD_IPC_MAX_HOOK_ARGS;
|
|
}
|
|
|
|
if (Source != NULL)
|
|
{
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
Destination[i] = Source[i];
|
|
}
|
|
}
|
|
|
|
if (DestinationCount != NULL)
|
|
{
|
|
*DestinationCount = count;
|
|
}
|
|
}
|
|
|
|
static VOID ControllerPrimeHookArgumentSymbols(_In_ DWORD ProcessId, _In_z_ PCSTR ApiName,
|
|
_In_reads_(ArgCount) const UINT64 *Args, _In_ UINT32 ArgCount)
|
|
{
|
|
if (ProcessId == 0 || ApiName == NULL || ApiName[0] == '\0' || Args == NULL || ArgCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (lstrcmpiA(ApiName, "NtCreateThreadEx") == 0 && ArgCount >= 4)
|
|
{
|
|
ControllerSymbolServicePrimeHookAddress(ProcessId, Args[3]);
|
|
}
|
|
else if ((lstrcmpiA(ApiName, "NtQueueApcThread") == 0 || lstrcmpiA(ApiName, "NtQueueApcThreadEx") == 0) &&
|
|
ArgCount >= 3)
|
|
{
|
|
ControllerSymbolServicePrimeHookAddress(ProcessId, Args[2]);
|
|
}
|
|
else if (lstrcmpiA(ApiName, "NtMapViewOfSectionEx") == 0 && ArgCount >= 4)
|
|
{
|
|
ControllerSymbolServicePrimeHookAddress(ProcessId, Args[3]);
|
|
}
|
|
}
|
|
|
|
static DWORD ControllerClientPublishHookEvent(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_In_ const BLACKBIRD_IPC_HOOK_EVENT *HookEvent)
|
|
{
|
|
BLACKBIRD_IPC_ETW_EVENT mapped;
|
|
DWORD eventPid = 0;
|
|
DWORD threadId = 0;
|
|
CHAR apiName[BLACKBIRD_IPC_MAX_HOOK_API_NAME];
|
|
CHAR moduleName[BLACKBIRD_IPC_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 == BLACKBIRD_HOOK_EVENT_OP_AMSI_PATCH);
|
|
integrityEtwPatch = (HookEvent->Operation == BLACKBIRD_HOOK_EVENT_OP_ETW_PATCH);
|
|
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 (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)
|
|
{
|
|
UINT32 allocType = (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull);
|
|
UINT32 protect = (UINT32)(HookEvent->Context3 & 0xFFFFFFFFull);
|
|
(void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_MEMORY_ACTIVITY");
|
|
mapped.Severity = 2u;
|
|
memoryEvent = TRUE;
|
|
(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",
|
|
(unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, allocType,
|
|
ControllerMemoryAllocTypeName(allocType), protect, ControllerMemoryProtectName(protect));
|
|
}
|
|
else if (HookEvent->Kind == BlackbirdIpcHookEventNt && lstrcmpiA(apiName, "NtProtectVirtualMemory") == 0)
|
|
{
|
|
UINT32 newProtect = (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull);
|
|
UINT32 protectFlips = (UINT32)(HookEvent->Context3 & 0xFFFFFFFFull);
|
|
BOOL hasSyscallStubOutsideNtdll = (HookEvent->Args[5] != 0ull);
|
|
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 = (protectFlips >= 4u) ? 6u : 3u;
|
|
}
|
|
memoryEvent = TRUE;
|
|
(void)StringCchPrintfW(
|
|
mapped.Reason, RTL_NUMBER_OF(mapped.Reason),
|
|
L"memory.protect base=0x%llX size=0x%llX newProtect=0x%X newProtectName=%S protectFlips=%lu syscallStubOutsideNtdll=%u sampleBytes=%lu sampleBase=0x%llX",
|
|
(unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, newProtect,
|
|
ControllerMemoryProtectName(newProtect), (unsigned long)protectFlips,
|
|
(unsigned int)hasSyscallStubOutsideNtdll, (unsigned long)sampleSize,
|
|
(unsigned long long)HookEvent->Args[6]);
|
|
}
|
|
else if (HookEvent->Kind == BlackbirdIpcHookEventNt && lstrcmpiA(apiName, "NtWriteVirtualMemory") == 0)
|
|
{
|
|
UINT32 entropyBucket = (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull);
|
|
UINT32 protectFlips = (UINT32)((HookEvent->Context3 >> 32) & 0xFFFFFFFFull);
|
|
UINT32 entropyFlips = (UINT32)(HookEvent->Context3 & 0xFFFFFFFFull);
|
|
double entropy = (HookEvent->Args[7] != 0ull)
|
|
? ((double)HookEvent->Args[7] / 1000.0)
|
|
: ControllerComputeSampleEntropy(HookEvent->DataSample, sampleSize);
|
|
(void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_MEMORY_ACTIVITY");
|
|
mapped.Severity = (entropyFlips >= 4u || protectFlips >= 4u) ? 7u : 3u;
|
|
memoryEvent = TRUE;
|
|
(void)StringCchPrintfW(
|
|
mapped.Reason, RTL_NUMBER_OF(mapped.Reason),
|
|
L"memory.write base=0x%llX size=0x%llX entropy=%.2f entropyBucket=%lu entropyFlips=%lu protectFlips=%lu sampleBytes=%lu",
|
|
(unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1, entropy,
|
|
(unsigned long)entropyBucket, (unsigned long)entropyFlips, (unsigned long)protectFlips,
|
|
(unsigned long)sampleSize);
|
|
}
|
|
else if (HookEvent->Kind == BlackbirdIpcHookEventNt && lstrcmpiA(apiName, "NtOpenProcess") == 0)
|
|
{
|
|
ULONG desiredAccess = (ULONG)HookEvent->Context1;
|
|
UINT32 targetPid = (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull);
|
|
if (ControllerHookIsInterestingProcessAccess(desiredAccess))
|
|
{
|
|
(void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName),
|
|
"USERMODE_PROCESS_HANDLE_ACTIVITY");
|
|
mapped.Severity = ControllerHookSeverityForProcessAccess(desiredAccess);
|
|
mapped.TargetPid = targetPid;
|
|
specializedEvent = TRUE;
|
|
(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, "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))
|
|
{
|
|
(void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_PROCESS_RECON");
|
|
mapped.Severity = 3u;
|
|
specializedEvent = TRUE;
|
|
(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;
|
|
(void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason),
|
|
L"thread.control api=%S threadHandle=0x%llX arg1=0x%llX", apiName,
|
|
(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 = 6u;
|
|
specializedEvent = TRUE;
|
|
(void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason),
|
|
L"thread.apc threadHandle=0x%llX routine=0x%llX arg1=0x%llX arg2=0x%llX arg3=0x%llX",
|
|
(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, "NtCreateThreadEx") == 0)
|
|
{
|
|
UINT64 processHandle = HookEvent->Context0;
|
|
UINT64 startRoutine = HookEvent->Context1;
|
|
UINT32 createFlags = (UINT32)(HookEvent->Context2 & 0xFFFFFFFFull);
|
|
BOOL remoteThread = (processHandle != (UINT64)(ULONG_PTR)-1) && (processHandle != 0);
|
|
BOOL hiddenThread = (createFlags & 0x4u) != 0;
|
|
|
|
(void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName),
|
|
remoteThread ? "USERMODE_REMOTE_THREAD_CREATE" : "USERMODE_THREAD_CREATE");
|
|
mapped.Severity = remoteThread ? (hiddenThread ? 7u : 5u) : (hiddenThread ? 6u : 2u);
|
|
specializedEvent = TRUE;
|
|
(void)StringCchPrintfW(
|
|
mapped.Reason, RTL_NUMBER_OF(mapped.Reason),
|
|
L"thread.create processHandle=0x%llX startRoutine=0x%llX createFlags=0x%X remote=%u hidden=%u",
|
|
(unsigned long long)processHandle, (unsigned long long)startRoutine, (unsigned int)createFlags,
|
|
(unsigned int)remoteThread, (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 = 6u;
|
|
specializedEvent = TRUE;
|
|
(void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason),
|
|
L"thread.apcEx threadHandle=0x%llX routine=0x%llX arg1=0x%llX arg2=0x%llX",
|
|
(unsigned long long)HookEvent->Context0, (unsigned long long)HookEvent->Context1,
|
|
(unsigned long long)HookEvent->Context2, (unsigned long long)HookEvent->Context3);
|
|
}
|
|
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;
|
|
BOOL remoteMap = (processHandle != (UINT64)(ULONG_PTR)-1) && (processHandle != 0);
|
|
BOOL execMap = (win32Protect & 0xF0u) != 0;
|
|
UINT32 sev;
|
|
|
|
if (remoteMap && execMap)
|
|
sev = 5u;
|
|
else if (remoteMap)
|
|
sev = 4u;
|
|
else if (execMap)
|
|
sev = 3u;
|
|
else
|
|
sev = 2u;
|
|
|
|
(void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName),
|
|
"USERMODE_SECTION_MAP_ACTIVITY");
|
|
mapped.Severity = sev;
|
|
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",
|
|
(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);
|
|
}
|
|
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;
|
|
(void)ControllerHookDecodeSockaddr(HookEvent->DataSample, sampleSize, &family, &port);
|
|
mapped.Family = BlackbirdIpcEtwFamilySocket;
|
|
mapped.TargetPid = 0;
|
|
(void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_NETWORK_CONNECT");
|
|
mapped.Severity = 2u;
|
|
specializedEvent = TRUE;
|
|
(void)StringCchPrintfW(mapped.Reason, RTL_NUMBER_OF(mapped.Reason),
|
|
L"socket.connect family=%u port=%u socket=0x%llX api=%S", (unsigned int)family,
|
|
(unsigned int)port, (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[BLACKBIRD_IPC_MAX_ETW_REASON] = {0};
|
|
ULONGLONG moduleHandle = HookEvent->Context0;
|
|
ULONGLONG frontFlags = HookEvent->Context1;
|
|
ULONGLONG auxValue = HookEvent->Context2;
|
|
ULONGLONG thirdValue = HookEvent->Context3;
|
|
|
|
mapped.TargetPid = eventPid;
|
|
specializedEvent = TRUE;
|
|
|
|
if (lstrcmpiA(apiName, "RtlAddFunctionTable") == 0)
|
|
{
|
|
mapped.Severity = 5u;
|
|
(void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName),
|
|
"USERMODE_FUNCTION_TABLE_ACTIVITY");
|
|
(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]);
|
|
}
|
|
else if (lstrcmpiA(apiName, "RtlInstallFunctionTableCallback") == 0)
|
|
{
|
|
ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample,
|
|
sampleSize);
|
|
mapped.Severity = 5u;
|
|
(void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName),
|
|
"USERMODE_FUNCTION_TABLE_ACTIVITY");
|
|
(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>");
|
|
}
|
|
else if (lstrcmpiA(apiName, "RtlDeleteFunctionTable") == 0)
|
|
{
|
|
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]);
|
|
}
|
|
else
|
|
{
|
|
(void)StringCchCopyA(mapped.DetectionName, RTL_NUMBER_OF(mapped.DetectionName), "USERMODE_MODULE_LOAD");
|
|
|
|
if (lstrcmpiA(apiName, "LoadLibraryA") == 0 || lstrcmpiA(apiName, "LoadLibraryExA") == 0)
|
|
{
|
|
ControllerHookCopyAnsiSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample,
|
|
sampleSize);
|
|
}
|
|
else
|
|
{
|
|
ControllerHookCopyWideSampleToReason(nameBuffer, RTL_NUMBER_OF(nameBuffer), HookEvent->DataSample,
|
|
sampleSize);
|
|
}
|
|
|
|
if (lstrcmpiA(apiName, "LdrLoadDll") == 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)
|
|
{
|
|
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
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 & BLACKBIRD_HOOK_CALLER_FLAG_ALL_SYSTEM)
|
|
mapped.Flags |= BLACKBIRD_IPC_ETW_FLAG_HOOK_CALLER_ALL_SYSTEM;
|
|
if (cf & BLACKBIRD_HOOK_CALLER_FLAG_HAS_UNMAPPED)
|
|
mapped.Flags |= BLACKBIRD_IPC_ETW_FLAG_HOOK_CALLER_HAS_UNMAPPED;
|
|
if (cf & BLACKBIRD_HOOK_CALLER_FLAG_HAS_PROCESS_IMAGE)
|
|
mapped.Flags |= BLACKBIRD_IPC_ETW_FLAG_HOOK_CALLER_HAS_PROCESS_IMAGE;
|
|
if (cf & BLACKBIRD_HOOK_CALLER_FLAG_HAS_NONSYSTEM_DLL)
|
|
mapped.Flags |= BLACKBIRD_IPC_ETW_FLAG_HOOK_CALLER_HAS_NONSYSTEM_DLL;
|
|
}
|
|
mapped.DataSize = sampleSize;
|
|
mapped.DeepSampleSize = sampleSize;
|
|
if (sampleSize != 0)
|
|
{
|
|
CopyMemory(mapped.DeepSample, HookEvent->DataSample, sampleSize);
|
|
}
|
|
|
|
if (HookEvent->Kind != BlackbirdIpcHookEventIntegrity && mapped.Severity >= 2u && mapped.ProcessId != 0u)
|
|
{
|
|
UINT32 heurFlags = 0;
|
|
if (memoryEvent)
|
|
{
|
|
if (lstrcmpiA(apiName, "NtAllocateVirtualMemory") == 0)
|
|
heurFlags |= BLACKBIRD_HEUR_FLAG_ALLOC_RW;
|
|
else if (lstrcmpiA(apiName, "NtWriteVirtualMemory") == 0)
|
|
heurFlags |= BLACKBIRD_HEUR_FLAG_WRITE_VM;
|
|
else if (lstrcmpiA(apiName, "NtProtectVirtualMemory") == 0)
|
|
heurFlags |= BLACKBIRD_HEUR_FLAG_PROTECT_RX;
|
|
}
|
|
if (HookEvent->Kind == BlackbirdIpcHookEventWinsock &&
|
|
(lstrcmpiA(apiName, "connect") == 0 || lstrcmpiA(apiName, "WSAConnect") == 0))
|
|
{
|
|
heurFlags |= BLACKBIRD_HEUR_FLAG_NETWORK;
|
|
}
|
|
if (lstrcmpiA(apiName, "NtCreateThreadEx") == 0 && specializedEvent)
|
|
{
|
|
heurFlags |= BLACKBIRD_HEUR_FLAG_REMOTE_TH;
|
|
}
|
|
if (mapped.Severity >= 4u)
|
|
{
|
|
heurFlags |= BLACKBIRD_HEUR_FLAG_DETECTION;
|
|
}
|
|
if (heurFlags != 0u)
|
|
{
|
|
ControllerHeuristicsObserveEvent((DWORD)mapped.ProcessId, mapped.Severity, heurFlags);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
ControllerDispatchEtwEvent(&mapped);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
static DWORD ControllerClientNotifyHookReady(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_In_ const BLACKBIRD_IPC_NOTIFY_HOOK_READY_REQUEST *Request,
|
|
_Out_ BLACKBIRD_IPC_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 = BLACKBIRD_IPC_HOOK_READY_REQUIRED_MASK;
|
|
|
|
if ((observedMask & BLACKBIRD_IPC_HOOK_READY_REQUIRED_MASK) == BLACKBIRD_IPC_HOOK_READY_REQUIRED_MASK)
|
|
{
|
|
ControllerLog("[IPC] hook-ready notify pid=%lu mask=0x%08lX (ready)\n", processId, observedMask);
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
static DWORD ControllerClientSetUserHookTarget(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_In_ const BLACKBIRD_IPC_SET_USER_HOOK_TARGET_REQUEST *Request,
|
|
_Out_ BLACKBIRD_IPC_SET_USER_HOOK_TARGET_RESPONSE *Response)
|
|
{
|
|
WCHAR hookDllPath[BLACKBIRD_MAX_IMAGE_PATH_CHARS];
|
|
BLACKBIRD_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;
|
|
BLACKBIRD_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\n", Request->Mode,
|
|
Request->Flags, Request->ProcessId, Request->ImagePath);
|
|
|
|
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:
|
|
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,
|
|
BLACKBIRD_CONTROLLER_INJECTION_VERIFY_TIMEOUT_MS);
|
|
if (err != ERROR_SUCCESS)
|
|
{
|
|
return err;
|
|
}
|
|
|
|
targetPid = Request->ProcessId;
|
|
break;
|
|
|
|
case BlackbirdIpcUserHookTargetLaunch:
|
|
if (Request->ImagePath[0] == L'\0' || !ControllerInjectionPathPointsToFile(Request->ImagePath))
|
|
{
|
|
return ERROR_FILE_NOT_FOUND;
|
|
}
|
|
err = ControllerEnsureCaptureReadyForLaunch();
|
|
if (err != ERROR_SUCCESS)
|
|
{
|
|
return err;
|
|
}
|
|
|
|
EnterCriticalSection(&Client->Lock);
|
|
ControllerClientArmPendingLaunchLocked(Client, Request->ImagePath);
|
|
LeaveCriticalSection(&Client->Lock);
|
|
|
|
if (!ControllerBuildPendingLaunchRequest(Request->ImagePath, BLACKBIRD_CONTROLLER_DRIVER_STREAM_MASK,
|
|
&pendingLaunchRequest))
|
|
{
|
|
EnterCriticalSection(&Client->Lock);
|
|
ControllerClientClearPendingLaunchLocked(Client);
|
|
LeaveCriticalSection(&Client->Lock);
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
EnterCriticalSection(g_DriverLock.get());
|
|
if (g_DriverHandle != INVALID_HANDLE_VALUE &&
|
|
BLACKBIRDSCArmPendingLaunch(g_DriverHandle, &pendingLaunchRequest))
|
|
{
|
|
pendingLaunchArmed = TRUE;
|
|
}
|
|
else
|
|
{
|
|
err = GetLastError();
|
|
}
|
|
LeaveCriticalSection(g_DriverLock.get());
|
|
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,
|
|
BLACKBIRD_CONTROLLER_INJECTION_VERIFY_TIMEOUT_MS, &targetPid);
|
|
if (err != ERROR_SUCCESS)
|
|
{
|
|
EnterCriticalSection(&Client->Lock);
|
|
ControllerClientClearPendingLaunchLocked(Client);
|
|
LeaveCriticalSection(&Client->Lock);
|
|
return err;
|
|
}
|
|
|
|
EnterCriticalSection(&Client->Lock);
|
|
ControllerClientPrimePendingLaunchPidLocked(Client, targetPid);
|
|
LeaveCriticalSection(&Client->Lock);
|
|
(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;
|
|
|
|
EnterCriticalSection(g_DriverLock.get());
|
|
driverConnected = (g_DriverHandle != INVALID_HANDLE_VALUE);
|
|
LeaveCriticalSection(g_DriverLock.get());
|
|
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;
|
|
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);
|
|
}
|
|
}
|
|
|
|
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";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static DWORD ControllerHandleClientCommand(_Inout_ BLACKBIRD_CONTROLLER_CLIENT *Client,
|
|
_In_ const BLACKBIRD_IPC_PACKET *Request,
|
|
_Out_ BLACKBIRD_IPC_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 == BlackbirdControllerClientRoleHook)
|
|
{
|
|
(void)InterlockedOr(&Client->HookReadyMask, (LONG)BLACKBIRD_IPC_HOOK_READY_FLAG_IPC_CONNECTED);
|
|
Client->HookReadyTick = GetTickCount64();
|
|
}
|
|
Response->Payload.HandshakeResponse.NegotiatedVersion = BLACKBIRD_IPC_VERSION;
|
|
Response->Payload.HandshakeResponse.Capabilities =
|
|
BLACKBIRD_IPC_CAP_DRIVER_PROXY | BLACKBIRD_IPC_CAP_SHARED_RING | BLACKBIRD_IPC_CAP_USER_HOOK_INGEST |
|
|
BLACKBIRD_IPC_CAP_USER_HOOK_READY;
|
|
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 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;
|
|
default:
|
|
err = ERROR_INVALID_FUNCTION;
|
|
break;
|
|
}
|
|
|
|
Complete:
|
|
Response->Status = err;
|
|
if (Request->Command != BlackbirdIpcCommandGetEvent && Request->Command != BlackbirdIpcCommandGetEtwEvent &&
|
|
Request->Command != BlackbirdIpcCommandPublishHookEvent)
|
|
{
|
|
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_ BLACKBIRD_CONTROLLER_CLIENT *Client)
|
|
{
|
|
PBLACKBIRD_CONTROLLER_CLIENT *pp;
|
|
|
|
if (Client == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EnterCriticalSection(g_ClientListLock.get());
|
|
pp = &g_ClientList;
|
|
while (*pp != NULL)
|
|
{
|
|
if (*pp == Client)
|
|
{
|
|
Client->Detached = 1;
|
|
*pp = Client->Next;
|
|
if (Client->SlotIndex != BLACKBIRD_CONTROLLER_INVALID_SLOT)
|
|
{
|
|
ControllerReleaseClientSlotLocked(Client->SlotIndex);
|
|
Client->SlotIndex = BLACKBIRD_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());
|
|
|
|
ControllerMarkDriverSubscriptionsDirty();
|
|
}
|
|
DWORD WINAPI ControllerClientThreadProc(_In_ LPVOID Context)
|
|
{
|
|
BLACKBIRD_CONTROLLER_CLIENT *client = (BLACKBIRD_CONTROLLER_CLIENT *)Context;
|
|
BLACKBIRD_IPC_PACKET *request = NULL;
|
|
BLACKBIRD_IPC_PACKET *response = NULL;
|
|
DWORD disconnectErr = ERROR_SUCCESS;
|
|
|
|
if (client == NULL)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
request = (BLACKBIRD_IPC_PACKET *)calloc(1, sizeof(*request));
|
|
response = (BLACKBIRD_IPC_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;
|
|
}
|
|
|
|
(void)ControllerHandleClientCommand(client, request, response);
|
|
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)
|
|
{
|
|
(void)WaitForSingleObject(client->DispatchIdleEvent, 3000);
|
|
}
|
|
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 BlackbirdControllerClientRoleHook:
|
|
sddl = L"D:P(A;;GA;;;SY)(A;;GRGW;;;IU)";
|
|
break;
|
|
case BlackbirdControllerClientRoleControl:
|
|
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;
|
|
}
|