| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- //go:build (amd64 || arm64) && !e2e_testing
- // +build amd64 arm64
- // +build !e2e_testing
- // Package wfp installs Windows Filtering Platform (WFP) PERMIT filters in a dynamic, session-scoped sublayer.
- // Because WFP sits below Windows Defender Firewall, a high-weight permit at FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4/V6 lets
- // the matching inbound traffic through regardless of WDF rules.
- //
- // Each Session owns its own engine handle. When the handle closes, every dynamic object added during the session
- // is auto-deleted by Windows, so there are no orphaned filters.
- //
- // Type definitions and constants are derived from the wireguard-windows firewall package (MIT).
- // Only the subset we exercise is reproduced.
- package wfp
- import (
- "fmt"
- "unsafe"
- "golang.org/x/sys/windows"
- )
- // FWPM layer GUIDs (fwpmu.h).
- //
- // FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4 = e1cd9fe7-f4b5-4273-96c0-592e487b8650
- // FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6 = a3b42c97-9f04-4672-b87e-cee9c483257f
- var (
- fwpmLayerAleAuthRecvAcceptV4 = windows.GUID{
- Data1: 0xe1cd9fe7, Data2: 0xf4b5, Data3: 0x4273,
- Data4: [8]byte{0x96, 0xc0, 0x59, 0x2e, 0x48, 0x7b, 0x86, 0x50},
- }
- fwpmLayerAleAuthRecvAcceptV6 = windows.GUID{
- Data1: 0xa3b42c97, Data2: 0x9f04, Data3: 0x4672,
- Data4: [8]byte{0xb8, 0x7e, 0xce, 0xe9, 0xc4, 0x83, 0x25, 0x7f},
- }
- )
- // FWPM_CONDITION_IP_LOCAL_INTERFACE = 4cd62a49-59c3-4969-b7f3-bda5d32890a4
- var fwpmConditionIPLocalInterface = windows.GUID{
- Data1: 0x4cd62a49, Data2: 0x59c3, Data3: 0x4969,
- Data4: [8]byte{0xb7, 0xf3, 0xbd, 0xa5, 0xd3, 0x28, 0x90, 0xa4},
- }
- // FWPM_CONDITION_IP_PROTOCOL = 3971ef2b-623e-4f9a-8cb1-6e79b806b9a7
- var fwpmConditionIPProtocol = windows.GUID{
- Data1: 0x3971ef2b, Data2: 0x623e, Data3: 0x4f9a,
- Data4: [8]byte{0x8c, 0xb1, 0x6e, 0x79, 0xb8, 0x06, 0xb9, 0xa7},
- }
- // FWPM_CONDITION_IP_LOCAL_PORT = 0c1ba1af-5765-453f-af22-a8f791ac775b
- var fwpmConditionIPLocalPort = windows.GUID{
- Data1: 0x0c1ba1af, Data2: 0x5765, Data3: 0x453f,
- Data4: [8]byte{0xaf, 0x22, 0xa8, 0xf7, 0x91, 0xac, 0x77, 0x5b},
- }
- // IPPROTO_UDP from in.h.
- const ipprotoUDP uint8 = 17
- // FWP_ACTION_TYPE values (fwptypes.h). PERMIT is terminating.
- const fwpActionPermit uint32 = 0x00001002 // 0x2 | FWP_ACTION_FLAG_TERMINATING(0x1000)
- // FWP_DATA_TYPE values we use.
- const (
- fwpEmpty uint32 = 0
- fwpUint8 uint32 = 1
- fwpUint16 uint32 = 2
- fwpUint64 uint32 = 4
- )
- // FWP_MATCH_TYPE values.
- const fwpMatchEqual uint32 = 0
- // FWPM_SESSION flags.
- const fwpmSessionFlagDynamic uint32 = 0x1
- // FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT prevents lower-priority filters in other sublayers,
- // notably Windows Defender Firewall's MPSSVC_WF sublayer, which shares our 0xFFFF weight from overriding this PERMIT.
- // Without it, a default WDF block at the same sublayer weight can still win arbitration.
- const fwpmFilterFlagClearActionRight uint32 = 0x8
- // RPC authentication.
- // RPC_C_AUTHN_WINNT works on workgroup machines with no domain context
- // RPC_C_AUTHN_DEFAULT falls back through a chain that can land on something WFP doesn't accept on a fresh box.
- const rpcCAuthnWinNT uint32 = 10
- // fwpByteBlob (FWP_BYTE_BLOB). 16 bytes on 64-bit.
- type fwpByteBlob struct {
- size uint32
- _ uint32 // padding
- data *uint8
- }
- // fwpValue0 / FWP_CONDITION_VALUE0 layout. 16 bytes on 64-bit.
- // The union is pointer-sized; types <= 32 bits (UINT8/16/32, INT8/16/32, float) live inline in the low bytes
- // of `value`, while UINT64/INT64/double and aggregate types are stored *by pointer*, even on 64-bit, where the
- // union member is declared as UINT64*. So when populating an FWP_UINT64 condition, pass
- // uintptr(unsafe.Pointer(&luidVar)) instead of the LUID inline.
- type fwpValue0 struct {
- type_ uint32
- _ uint32 // padding before union to 8-byte alignment
- value uintptr
- }
- // fwpmDisplayData0 / FWPM_DISPLAY_DATA0. 16 bytes on 64-bit.
- type fwpmDisplayData0 struct {
- name *uint16
- description *uint16
- }
- // fwpmAction0 / FWPM_ACTION0. 20 bytes; no leading padding because actionType
- // is uint32 and GUID's first field is uint32.
- type fwpmAction0 struct {
- actionType uint32
- filterType windows.GUID
- }
- // fwpmFilterCondition0. 40 bytes on 64-bit.
- type fwpmFilterCondition0 struct {
- fieldKey windows.GUID // 16
- matchType uint32 // 4
- _ uint32 // 4 padding
- conditionValue fwpValue0 // 16
- }
- // fwpmFilter0. 200 bytes on 64-bit.
- type fwpmFilter0 struct {
- filterKey windows.GUID
- displayData fwpmDisplayData0
- flags uint32
- _ uint32 // padding before *GUID
- providerKey *windows.GUID
- providerData fwpByteBlob
- layerKey windows.GUID
- subLayerKey windows.GUID
- weight fwpValue0
- numFilterConditions uint32
- _ uint32 // padding before pointer
- filterCondition *fwpmFilterCondition0
- action fwpmAction0
- _ [4]byte // layout correction
- providerContextKey windows.GUID
- reserved *windows.GUID
- filterID uint64
- effectiveWeight fwpValue0
- }
- // fwpmSublayer0. 72 bytes on 64-bit.
- type fwpmSublayer0 struct {
- subLayerKey windows.GUID
- displayData fwpmDisplayData0
- flags uint32
- _ uint32 // padding before *GUID
- providerKey *windows.GUID
- providerData fwpByteBlob
- weight uint16
- _ [6]byte // padding to 72 bytes
- }
- // fwpmSession0. 72 bytes on 64-bit.
- type fwpmSession0 struct {
- sessionKey windows.GUID
- displayData fwpmDisplayData0
- flags uint32
- txnWaitTimeoutInMSec uint32
- processId uint32
- _ uint32 // padding before *SID
- sid *windows.SID
- username *uint16
- kernelMode uint8
- _ [7]byte // tail padding
- }
- // fwpuclnt.dll bindings. Only the calls we use.
- var (
- modFwpuclnt = windows.NewLazySystemDLL("fwpuclnt.dll")
- procFwpmEngineOpen0 = modFwpuclnt.NewProc("FwpmEngineOpen0")
- procFwpmEngineClose0 = modFwpuclnt.NewProc("FwpmEngineClose0")
- procFwpmSubLayerAdd0 = modFwpuclnt.NewProc("FwpmSubLayerAdd0")
- procFwpmFilterAdd0 = modFwpuclnt.NewProc("FwpmFilterAdd0")
- )
- // Session holds the WFP engine handle for a single bypass operation. The handle owns a dynamic session:
- // when it is closed, every WFP object added during the session (sublayer + filters) is automatically deleted by
- // Windows. That gives us correct cleanup even if the host process is killed hard between Permit* and Close.
- type Session struct {
- engine uintptr
- }
- // Close releases the engine handle. Windows deletes every dynamic object (sublayer + filters) the session installed.
- // Safe to call on a nil receiver.
- func (s *Session) Close() {
- if s == nil || s.engine == 0 {
- return
- }
- procFwpmEngineClose0.Call(s.engine)
- s.engine = 0
- }
- // PermitInterface installs PERMIT filters at FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4 and _V6 scoped to the given network
- // interface LUID. Inbound traffic on that interface bypasses Windows Defender Firewall.
- func PermitInterface(luid uint64) (*Session, error) {
- s, sublayerKey, err := newSession()
- if err != nil {
- return nil, err
- }
- if err := addInterfaceFilter(s.engine, sublayerKey, fwpmLayerAleAuthRecvAcceptV4, luid); err != nil {
- s.Close()
- return nil, fmt.Errorf("add v4 filter: %w", err)
- }
- if err := addInterfaceFilter(s.engine, sublayerKey, fwpmLayerAleAuthRecvAcceptV6, luid); err != nil {
- s.Close()
- return nil, fmt.Errorf("add v6 filter: %w", err)
- }
- return s, nil
- }
- // PermitUDPPort installs PERMIT filters at FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4 and _V6 scoped to UDP traffic with the
- // given local port. Inbound UDP to that port on any interface bypasses Windows Defender Firewall.
- func PermitUDPPort(port uint16) (*Session, error) {
- s, sublayerKey, err := newSession()
- if err != nil {
- return nil, err
- }
- if err := addUDPPortFilter(s.engine, sublayerKey, fwpmLayerAleAuthRecvAcceptV4, port); err != nil {
- s.Close()
- return nil, fmt.Errorf("add v4 filter: %w", err)
- }
- if err := addUDPPortFilter(s.engine, sublayerKey, fwpmLayerAleAuthRecvAcceptV6, port); err != nil {
- s.Close()
- return nil, fmt.Errorf("add v6 filter: %w", err)
- }
- return s, nil
- }
- func newSession() (*Session, windows.GUID, error) {
- engine, err := openDynamicEngine()
- if err != nil {
- return nil, windows.GUID{}, err
- }
- sublayerKey, err := registerSublayer(engine)
- if err != nil {
- procFwpmEngineClose0.Call(engine)
- return nil, windows.GUID{}, err
- }
- return &Session{engine: engine}, sublayerKey, nil
- }
- func openDynamicEngine() (uintptr, error) {
- session := fwpmSession0{flags: fwpmSessionFlagDynamic}
- var engine uintptr
- r1, _, _ := procFwpmEngineOpen0.Call(
- 0, // serverName == NULL (local)
- uintptr(rpcCAuthnWinNT),
- 0, // authIdentity == NULL
- uintptr(unsafe.Pointer(&session)),
- uintptr(unsafe.Pointer(&engine)),
- )
- if r1 != 0 {
- return 0, fmt.Errorf("FwpmEngineOpen0: 0x%x", r1)
- }
- return engine, nil
- }
- // registerSublayer adds a session-scoped sublayer with a freshly generated GUID, weight 0xFFFF so its filters arbitrate
- // above WDF's default sublayer. The sublayer is dynamic (no PERSISTENT flag) and goes away when the engine handle closes.
- func registerSublayer(engine uintptr) (windows.GUID, error) {
- key, err := windows.GenerateGUID()
- if err != nil {
- return windows.GUID{}, fmt.Errorf("GenerateGUID for sublayer: %w", err)
- }
- name, _ := windows.UTF16PtrFromString("Nebula WDF bypass sublayer")
- desc, _ := windows.UTF16PtrFromString("Permit filters bypassing Windows Defender Firewall")
- sl := fwpmSublayer0{
- subLayerKey: key,
- displayData: fwpmDisplayData0{name: name, description: desc},
- weight: 0xFFFF,
- }
- r1, _, _ := procFwpmSubLayerAdd0.Call(
- engine,
- uintptr(unsafe.Pointer(&sl)),
- 0, // sd == NULL
- )
- if r1 != 0 {
- return windows.GUID{}, fmt.Errorf("FwpmSubLayerAdd0: 0x%x", r1)
- }
- return key, nil
- }
- func addInterfaceFilter(engine uintptr, sublayerKey, layer windows.GUID, luid uint64) error {
- name, _ := windows.UTF16PtrFromString("Nebula allow interface inbound")
- desc, _ := windows.UTF16PtrFromString("Permits inbound traffic on a nebula interface")
- // luid must remain addressable through the syscall -- FWP_UINT64 is stored
- // by pointer in the FWP_VALUE0 union.
- cond := fwpmFilterCondition0{
- fieldKey: fwpmConditionIPLocalInterface,
- matchType: fwpMatchEqual,
- conditionValue: fwpValue0{
- type_: fwpUint64,
- value: uintptr(unsafe.Pointer(&luid)),
- },
- }
- filter := fwpmFilter0{
- // filterKey left zero: WFP assigns one when the filter is added.
- displayData: fwpmDisplayData0{name: name, description: desc},
- flags: fwpmFilterFlagClearActionRight,
- layerKey: layer,
- subLayerKey: sublayerKey,
- weight: fwpValue0{type_: fwpUint8, value: uintptr(15)},
- numFilterConditions: 1,
- filterCondition: &cond,
- action: fwpmAction0{actionType: fwpActionPermit},
- }
- r1, _, _ := procFwpmFilterAdd0.Call(
- engine,
- uintptr(unsafe.Pointer(&filter)),
- 0, // sd == NULL
- 0, // id == NULL
- )
- if r1 != 0 {
- return fmt.Errorf("FwpmFilterAdd0: 0x%x", r1)
- }
- return nil
- }
- // addUDPPortFilter installs a PERMIT filter that matches (IP_PROTOCOL == UDP) AND (IP_LOCAL_PORT == port).
- // FWP_UINT8 and FWP_UINT16 are <= 32 bits so they live inline in the FWP_VALUE0 union.
- func addUDPPortFilter(engine uintptr, sublayerKey, layer windows.GUID, port uint16) error {
- name, _ := windows.UTF16PtrFromString("Nebula allow UDP port inbound")
- desc, _ := windows.UTF16PtrFromString("Permits inbound UDP to a nebula listener port")
- conds := [2]fwpmFilterCondition0{
- {
- fieldKey: fwpmConditionIPProtocol,
- matchType: fwpMatchEqual,
- conditionValue: fwpValue0{
- type_: fwpUint8,
- value: uintptr(ipprotoUDP),
- },
- },
- {
- fieldKey: fwpmConditionIPLocalPort,
- matchType: fwpMatchEqual,
- conditionValue: fwpValue0{
- type_: fwpUint16,
- value: uintptr(port),
- },
- },
- }
- filter := fwpmFilter0{
- displayData: fwpmDisplayData0{name: name, description: desc},
- flags: fwpmFilterFlagClearActionRight,
- layerKey: layer,
- subLayerKey: sublayerKey,
- weight: fwpValue0{type_: fwpUint8, value: uintptr(15)},
- numFilterConditions: 2,
- filterCondition: &conds[0],
- action: fwpmAction0{actionType: fwpActionPermit},
- }
- r1, _, _ := procFwpmFilterAdd0.Call(
- engine,
- uintptr(unsafe.Pointer(&filter)),
- 0,
- 0,
- )
- if r1 != 0 {
- return fmt.Errorf("FwpmFilterAdd0: 0x%x", r1)
- }
- return nil
- }
|