Add packaged DLP broker and protected Windows service foundation
This commit is contained in:
1 parent
95d23f4e0b
commit
7b5057901a
140 files changed
+11215
-60
No files matched your search
@@ -0,0 +1,378 @@
|
||||
#pragma once
|
||||
// clang-format off
|
||||
#include <windows.h>
|
||||
#include <sddl.h>
|
||||
#include <objbase.h>
|
||||
#include <fwpmu.h>
|
||||
// clang-format on
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <windows.h>
|
||||
#pragma comment(lib, "fwpuclnt.lib")
|
||||
#pragma comment(lib, "ole32.lib")
|
||||
namespace topology_contract {
|
||||
static constexpr UINT16 BROKER_PORT = 43120;
|
||||
static GUID guid(const wchar_t *text) {
|
||||
GUID value{};
|
||||
if (FAILED(CLSIDFromString(text, &value)))
|
||||
throw 1;
|
||||
return value;
|
||||
}
|
||||
struct Memory {
|
||||
void *p = nullptr;
|
||||
~Memory() {
|
||||
if (p)
|
||||
FwpmFreeMemory0(&p);
|
||||
}
|
||||
};
|
||||
struct Engine {
|
||||
HANDLE h = nullptr;
|
||||
~Engine() {
|
||||
if (h)
|
||||
FwpmEngineClose0(h);
|
||||
}
|
||||
};
|
||||
struct Enumeration {
|
||||
HANDLE engine, h = nullptr;
|
||||
explicit Enumeration(HANDLE e) : engine(e) {}
|
||||
~Enumeration() {
|
||||
if (h)
|
||||
FwpmFilterDestroyEnumHandle0(engine, h);
|
||||
}
|
||||
};
|
||||
struct Result {
|
||||
DWORD code = ERROR_ACCESS_DENIED;
|
||||
UINT32 accessibleFilters = 0;
|
||||
UINT32 ownedFilters = 0;
|
||||
bool ownedVerified = false;
|
||||
bool collision = false;
|
||||
bool bfeRunning = false;
|
||||
// Enumeration omits unreadable objects even at successful EOF. Never infer
|
||||
// this from token elevation, caller JSON or the number of returned rows.
|
||||
bool allObjectsReadable = false;
|
||||
};
|
||||
static bool blobEqual(const FWP_BYTE_BLOB *actual,
|
||||
const std::vector<BYTE> &expected) {
|
||||
return actual && actual->data && actual->size == expected.size() &&
|
||||
memcmp(actual->data, expected.data(), expected.size()) == 0;
|
||||
}
|
||||
struct ExpectedCondition {
|
||||
GUID field;
|
||||
FWP_MATCH_TYPE match;
|
||||
FWP_DATA_TYPE type;
|
||||
UINT32 value;
|
||||
std::vector<BYTE> bytes;
|
||||
};
|
||||
static bool marker(const FWP_BYTE_BLOB &value) {
|
||||
static constexpr char expected[] = "SafetyNet.NetworkBoundary.v1";
|
||||
return value.data && value.size == sizeof(expected) - 1 &&
|
||||
memcmp(value.data, expected, sizeof(expected) - 1) == 0;
|
||||
}
|
||||
using SecurityQuery = DWORD(WINAPI *)(HANDLE, const GUID *,
|
||||
SECURITY_INFORMATION, PSID *, PSID *,
|
||||
PACL *, PACL *, PSECURITY_DESCRIPTOR *);
|
||||
static bool protectedObject(HANDLE engine, const GUID &key,
|
||||
SecurityQuery query) {
|
||||
PSECURITY_DESCRIPTOR descriptor = nullptr;
|
||||
PACL dacl = nullptr;
|
||||
PSID owner = nullptr;
|
||||
DWORD code = query(engine, &key,
|
||||
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
|
||||
&owner, nullptr, &dacl, nullptr, &descriptor);
|
||||
BYTE system[SECURITY_MAX_SID_SIZE], admins[SECURITY_MAX_SID_SIZE];
|
||||
DWORD ss = sizeof(system), as = sizeof(admins);
|
||||
CreateWellKnownSid(WinLocalSystemSid, nullptr, system, &ss);
|
||||
CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, admins, &as);
|
||||
SECURITY_DESCRIPTOR_CONTROL control{};
|
||||
DWORD revision = 0;
|
||||
bool okay = !code && dacl && owner &&
|
||||
(EqualSid(owner, system) || EqualSid(owner, admins)) &&
|
||||
GetSecurityDescriptorControl(descriptor, &control, &revision) &&
|
||||
(control & SE_DACL_PROTECTED);
|
||||
const DWORD write = FWPM_ACTRL_ADD | FWPM_ACTRL_ADD_LINK |
|
||||
FWPM_ACTRL_BEGIN_WRITE_TXN | FWPM_ACTRL_WRITE | DELETE |
|
||||
WRITE_DAC | WRITE_OWNER | GENERIC_ALL | GENERIC_WRITE;
|
||||
if (okay)
|
||||
for (DWORD i = 0; i < dacl->AceCount; ++i) {
|
||||
void *raw = nullptr;
|
||||
if (!GetAce(dacl, i, &raw)) {
|
||||
okay = false;
|
||||
break;
|
||||
}
|
||||
auto header = static_cast<ACE_HEADER *>(raw);
|
||||
if (header->AceFlags & INHERIT_ONLY_ACE)
|
||||
continue;
|
||||
if (header->AceType == ACCESS_ALLOWED_ACE_TYPE) {
|
||||
auto ace = static_cast<ACCESS_ALLOWED_ACE *>(raw);
|
||||
if ((ace->Mask & write) && !EqualSid(&ace->SidStart, system) &&
|
||||
!EqualSid(&ace->SidStart, admins)) {
|
||||
okay = false;
|
||||
break;
|
||||
}
|
||||
} else if (header->AceType != ACCESS_DENIED_ACE_TYPE) {
|
||||
okay = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (descriptor)
|
||||
FwpmFreeMemory0(reinterpret_cast<void **>(&descriptor));
|
||||
return okay;
|
||||
}
|
||||
struct Rule {
|
||||
GUID key, layer;
|
||||
FWP_ACTION_TYPE action;
|
||||
UINT64 weight;
|
||||
std::vector<ExpectedCondition> conditions;
|
||||
};
|
||||
static bool matches(const FWPM_FILTER0 &actual, const Rule &rule,
|
||||
const GUID &provider, const GUID &sublayer) {
|
||||
if (!actual.providerKey || !IsEqualGUID(*actual.providerKey, provider) ||
|
||||
!IsEqualGUID(actual.subLayerKey, sublayer) ||
|
||||
!IsEqualGUID(actual.layerKey, rule.layer) ||
|
||||
actual.action.type != rule.action ||
|
||||
actual.flags != FWPM_FILTER_FLAG_PERSISTENT ||
|
||||
actual.numFilterConditions != rule.conditions.size() ||
|
||||
!actual.filterCondition)
|
||||
return false;
|
||||
if (rule.weight) {
|
||||
if (actual.weight.type != FWP_UINT64 || !actual.weight.uint64 ||
|
||||
*actual.weight.uint64 != rule.weight)
|
||||
return false;
|
||||
} else if (actual.weight.type != FWP_EMPTY)
|
||||
return false;
|
||||
for (size_t i = 0; i < rule.conditions.size(); ++i) {
|
||||
const auto &e = rule.conditions[i];
|
||||
const auto &a = actual.filterCondition[i];
|
||||
if (!IsEqualGUID(a.fieldKey, e.field) || a.matchType != e.match ||
|
||||
a.conditionValue.type != e.type)
|
||||
return false;
|
||||
switch (e.type) {
|
||||
case FWP_UINT8:
|
||||
if (a.conditionValue.uint8 != e.value)
|
||||
return false;
|
||||
break;
|
||||
case FWP_UINT16:
|
||||
if (a.conditionValue.uint16 != e.value)
|
||||
return false;
|
||||
break;
|
||||
case FWP_UINT32:
|
||||
if (a.conditionValue.uint32 != e.value)
|
||||
return false;
|
||||
break;
|
||||
case FWP_BYTE_BLOB_TYPE:
|
||||
if (!blobEqual(a.conditionValue.byteBlob, e.bytes))
|
||||
return false;
|
||||
break;
|
||||
case FWP_SECURITY_DESCRIPTOR_TYPE:
|
||||
if (!blobEqual(a.conditionValue.sd, e.bytes))
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static std::vector<BYTE> appId(const std::wstring &path) {
|
||||
Memory memory;
|
||||
FWP_BYTE_BLOB *blob = nullptr;
|
||||
if (FwpmGetAppIdFromFileName0(path.c_str(), &blob))
|
||||
throw 1;
|
||||
memory.p = blob;
|
||||
if (!blob || !blob->data || blob->size < 8 || blob->size > 65536)
|
||||
throw 1;
|
||||
return {blob->data, blob->data + blob->size};
|
||||
}
|
||||
static Result inspect(const std::wstring &root,
|
||||
const std::wstring &serviceSid) {
|
||||
Result result;
|
||||
try {
|
||||
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
|
||||
if (!manager) {
|
||||
result.code = GetLastError();
|
||||
return result;
|
||||
}
|
||||
SC_HANDLE bfe = OpenServiceW(manager, L"BFE", SERVICE_QUERY_STATUS);
|
||||
SERVICE_STATUS_PROCESS status{};
|
||||
DWORD needed = 0;
|
||||
result.bfeRunning = bfe &&
|
||||
QueryServiceStatusEx(bfe, SC_STATUS_PROCESS_INFO,
|
||||
reinterpret_cast<BYTE *>(&status),
|
||||
sizeof(status), &needed) &&
|
||||
status.dwCurrentState == SERVICE_RUNNING;
|
||||
if (bfe)
|
||||
CloseServiceHandle(bfe);
|
||||
CloseServiceHandle(manager);
|
||||
if (!result.bfeRunning)
|
||||
return result;
|
||||
const GUID provider = guid(L"{2a7bcbf2-e4f5-4e94-bd33-d5442a3bef50}"),
|
||||
sub = guid(L"{403a69b8-fc7c-460a-a247-72e0d72e6bf8}");
|
||||
const GUID c4 = FWPM_LAYER_ALE_AUTH_CONNECT_V4,
|
||||
c6 = FWPM_LAYER_ALE_AUTH_CONNECT_V6,
|
||||
r4 = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4,
|
||||
r6 = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||
const auto browser = appId(root + L"\\SafetyNet.exe"),
|
||||
broker = appId(root + L"\\resources\\broker\\node.exe");
|
||||
PSECURITY_DESCRIPTOR descriptor = nullptr;
|
||||
ULONG size = 0;
|
||||
std::wstring sddl = L"D:(A;;CC;;;" + serviceSid + L")";
|
||||
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
||||
sddl.c_str(), SDDL_REVISION_1, &descriptor, &size))
|
||||
throw 1;
|
||||
std::vector<BYTE> sd(reinterpret_cast<BYTE *>(descriptor),
|
||||
reinterpret_cast<BYTE *>(descriptor) + size);
|
||||
LocalFree(descriptor);
|
||||
auto scalar = [](GUID field, FWP_MATCH_TYPE match, FWP_DATA_TYPE type,
|
||||
UINT32 value) {
|
||||
return ExpectedCondition{field, match, type, value, {}};
|
||||
};
|
||||
auto bytes = [](GUID field, FWP_DATA_TYPE type,
|
||||
const std::vector<BYTE> &value) {
|
||||
return ExpectedCondition{field, FWP_MATCH_EQUAL, type, 0, value};
|
||||
};
|
||||
const auto app = bytes(FWPM_CONDITION_ALE_APP_ID, FWP_BYTE_BLOB_TYPE,
|
||||
browser),
|
||||
tcp = scalar(FWPM_CONDITION_IP_PROTOCOL, FWP_MATCH_EQUAL,
|
||||
FWP_UINT8, 6),
|
||||
local = scalar(FWPM_CONDITION_IP_REMOTE_ADDRESS, FWP_MATCH_EQUAL,
|
||||
FWP_UINT32, 0x7f000001);
|
||||
const std::vector<ExpectedCondition> tuple = {
|
||||
tcp,
|
||||
scalar(FWPM_CONDITION_IP_LOCAL_ADDRESS, FWP_MATCH_EQUAL, FWP_UINT32,
|
||||
0x7f000001),
|
||||
scalar(FWPM_CONDITION_IP_LOCAL_PORT, FWP_MATCH_EQUAL, FWP_UINT16,
|
||||
BROKER_PORT)};
|
||||
auto permit = tuple;
|
||||
permit.push_back(
|
||||
bytes(FWPM_CONDITION_ALE_APP_ID, FWP_BYTE_BLOB_TYPE, broker));
|
||||
permit.push_back(
|
||||
bytes(FWPM_CONDITION_ALE_USER_ID, FWP_SECURITY_DESCRIPTOR_TYPE, sd));
|
||||
const std::vector<Rule> rules = {
|
||||
{guid(L"{4ac4e1d1-f08b-50fe-83fe-1872672c42b9}"),
|
||||
c4,
|
||||
FWP_ACTION_BLOCK,
|
||||
0,
|
||||
{app, scalar(FWPM_CONDITION_IP_PROTOCOL, FWP_MATCH_NOT_EQUAL,
|
||||
FWP_UINT8, 6)}},
|
||||
{guid(L"{07130743-8828-58b5-9476-3a0c2a001796}"),
|
||||
c4,
|
||||
FWP_ACTION_BLOCK,
|
||||
0,
|
||||
{app, tcp,
|
||||
scalar(FWPM_CONDITION_IP_REMOTE_ADDRESS, FWP_MATCH_NOT_EQUAL,
|
||||
FWP_UINT32, 0x7f000001)}},
|
||||
{guid(L"{51401be2-30ce-5808-878f-7e181d0f6fde}"),
|
||||
c4,
|
||||
FWP_ACTION_BLOCK,
|
||||
0,
|
||||
{app, tcp, local,
|
||||
scalar(FWPM_CONDITION_IP_REMOTE_PORT, FWP_MATCH_NOT_EQUAL, FWP_UINT16,
|
||||
BROKER_PORT)}},
|
||||
{guid(L"{1ee3c54d-84f8-5461-bd50-b74f32335ff3}"),
|
||||
c6,
|
||||
FWP_ACTION_BLOCK,
|
||||
0,
|
||||
{app}},
|
||||
{guid(L"{a71f6123-ef64-5836-90ee-f624c6841f18}"),
|
||||
r4,
|
||||
FWP_ACTION_BLOCK,
|
||||
0,
|
||||
{app}},
|
||||
{guid(L"{e537682c-948d-5bbf-a231-5db3ba896eda}"),
|
||||
r6,
|
||||
FWP_ACTION_BLOCK,
|
||||
0,
|
||||
{app}},
|
||||
{guid(L"{dcbbb5f1-5936-5978-8349-21a2e791ffbf}"), r4, FWP_ACTION_PERMIT,
|
||||
2, permit},
|
||||
{guid(L"{7c4dba0f-904a-5685-a3ed-ae774a45136a}"), r4, FWP_ACTION_BLOCK,
|
||||
1, tuple}};
|
||||
Engine engine;
|
||||
result.code = FwpmEngineOpen0(nullptr, RPC_C_AUTHN_WINNT, nullptr, nullptr,
|
||||
&engine.h);
|
||||
if (result.code)
|
||||
return result;
|
||||
Enumeration enumeration(engine.h);
|
||||
Memory providerMemory;
|
||||
FWPM_PROVIDER0 *p = nullptr;
|
||||
result.code = FwpmProviderGetByKey0(engine.h, &provider, &p);
|
||||
providerMemory.p = p;
|
||||
if (result.code)
|
||||
return result;
|
||||
if (p->flags != FWPM_PROVIDER_FLAG_PERSISTENT || !p->serviceName ||
|
||||
wcscmp(p->serviceName, L"SafetyNetNetworkBroker") ||
|
||||
!marker(p->providerData) ||
|
||||
!protectedObject(engine.h, provider,
|
||||
FwpmProviderGetSecurityInfoByKey0)) {
|
||||
result.code = ERROR_INVALID_DATA;
|
||||
return result;
|
||||
}
|
||||
result.code =
|
||||
FwpmFilterCreateEnumHandle0(engine.h, nullptr, &enumeration.h);
|
||||
if (result.code)
|
||||
return result;
|
||||
std::vector<GUID> seen;
|
||||
for (;;) {
|
||||
Memory memory;
|
||||
FWPM_FILTER0 **entries = nullptr;
|
||||
UINT32 count = 0;
|
||||
result.code =
|
||||
FwpmFilterEnum0(engine.h, enumeration.h, 128, &entries, &count);
|
||||
memory.p = entries;
|
||||
if (result.code)
|
||||
return result;
|
||||
if (count > 128 || result.accessibleFilters + count > 65536) {
|
||||
result.code = ERROR_MORE_DATA;
|
||||
return result;
|
||||
}
|
||||
result.accessibleFilters += count;
|
||||
for (UINT32 i = 0; i < count; ++i) {
|
||||
const auto &f = *entries[i];
|
||||
if (IsEqualGUID(f.subLayerKey, sub) ||
|
||||
(f.providerKey && IsEqualGUID(*f.providerKey, provider))) {
|
||||
bool okay = false;
|
||||
for (const auto &rule : rules)
|
||||
if (IsEqualGUID(f.filterKey, rule.key) &&
|
||||
matches(f, rule, provider, sub))
|
||||
okay = true;
|
||||
if (!okay || !protectedObject(engine.h, f.filterKey,
|
||||
FwpmFilterGetSecurityInfoByKey0)) {
|
||||
result.code = ERROR_INVALID_DATA;
|
||||
return result;
|
||||
}
|
||||
seen.push_back(f.filterKey);
|
||||
++result.ownedFilters;
|
||||
} else if (IsEqualGUID(f.layerKey, c4) || IsEqualGUID(f.layerKey, c6) ||
|
||||
IsEqualGUID(f.layerKey, r4) || IsEqualGUID(f.layerKey, r6)) {
|
||||
Memory sm;
|
||||
FWPM_SUBLAYER0 *s = nullptr;
|
||||
result.code = FwpmSubLayerGetByKey0(engine.h, &f.subLayerKey, &s);
|
||||
sm.p = s;
|
||||
if (result.code)
|
||||
return result;
|
||||
if (s->weight == 65535)
|
||||
result.collision = true;
|
||||
}
|
||||
}
|
||||
if (count < 128)
|
||||
break;
|
||||
}
|
||||
Memory sm;
|
||||
FWPM_SUBLAYER0 *s = nullptr;
|
||||
result.code = FwpmSubLayerGetByKey0(engine.h, &sub, &s);
|
||||
sm.p = s;
|
||||
if (result.code)
|
||||
return result;
|
||||
result.ownedVerified =
|
||||
marker(s->providerData) &&
|
||||
protectedObject(engine.h, sub, FwpmSubLayerGetSecurityInfoByKey0) &&
|
||||
result.ownedFilters == 8 && !result.collision && s->weight == 65535 &&
|
||||
s->providerKey && IsEqualGUID(*s->providerKey, provider) &&
|
||||
s->flags == FWPM_SUBLAYER_FLAG_PERSISTENT;
|
||||
result.code = result.ownedVerified ? ERROR_SUCCESS : ERROR_INVALID_DATA;
|
||||
} catch (...) {
|
||||
result.code = ERROR_INVALID_DATA;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace topology_contract
|
||||
Reference in new issue
Block a user