Files
safetynet/apps/network-broker/native/wfp-topology.h
T

378 lines
13 KiB
C++
Raw Normal View History

#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