#pragma once // clang-format off #include #include #include #include // clang-format on #include #include #include #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 &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 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(raw); if (header->AceFlags & INHERIT_ONLY_ACE) continue; if (header->AceType == ACCESS_ALLOWED_ACE_TYPE) { auto ace = static_cast(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(&descriptor)); return okay; } struct Rule { GUID key, layer; FWP_ACTION_TYPE action; UINT64 weight; std::vector 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 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(&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 sd(reinterpret_cast(descriptor), reinterpret_cast(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 &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 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 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 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