wfp_windows.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. //go:build (amd64 || arm64) && !e2e_testing
  2. // +build amd64 arm64
  3. // +build !e2e_testing
  4. // Package wfp installs Windows Filtering Platform (WFP) PERMIT filters in a dynamic, session-scoped sublayer.
  5. // Because WFP sits below Windows Defender Firewall, a high-weight permit at FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4/V6 lets
  6. // the matching inbound traffic through regardless of WDF rules.
  7. //
  8. // Each Session owns its own engine handle. When the handle closes, every dynamic object added during the session
  9. // is auto-deleted by Windows, so there are no orphaned filters.
  10. //
  11. // Type definitions and constants are derived from the wireguard-windows firewall package (MIT).
  12. // Only the subset we exercise is reproduced.
  13. package wfp
  14. import (
  15. "fmt"
  16. "unsafe"
  17. "golang.org/x/sys/windows"
  18. )
  19. // FWPM layer GUIDs (fwpmu.h).
  20. //
  21. // FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4 = e1cd9fe7-f4b5-4273-96c0-592e487b8650
  22. // FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6 = a3b42c97-9f04-4672-b87e-cee9c483257f
  23. var (
  24. fwpmLayerAleAuthRecvAcceptV4 = windows.GUID{
  25. Data1: 0xe1cd9fe7, Data2: 0xf4b5, Data3: 0x4273,
  26. Data4: [8]byte{0x96, 0xc0, 0x59, 0x2e, 0x48, 0x7b, 0x86, 0x50},
  27. }
  28. fwpmLayerAleAuthRecvAcceptV6 = windows.GUID{
  29. Data1: 0xa3b42c97, Data2: 0x9f04, Data3: 0x4672,
  30. Data4: [8]byte{0xb8, 0x7e, 0xce, 0xe9, 0xc4, 0x83, 0x25, 0x7f},
  31. }
  32. )
  33. // FWPM_CONDITION_IP_LOCAL_INTERFACE = 4cd62a49-59c3-4969-b7f3-bda5d32890a4
  34. var fwpmConditionIPLocalInterface = windows.GUID{
  35. Data1: 0x4cd62a49, Data2: 0x59c3, Data3: 0x4969,
  36. Data4: [8]byte{0xb7, 0xf3, 0xbd, 0xa5, 0xd3, 0x28, 0x90, 0xa4},
  37. }
  38. // FWPM_CONDITION_IP_PROTOCOL = 3971ef2b-623e-4f9a-8cb1-6e79b806b9a7
  39. var fwpmConditionIPProtocol = windows.GUID{
  40. Data1: 0x3971ef2b, Data2: 0x623e, Data3: 0x4f9a,
  41. Data4: [8]byte{0x8c, 0xb1, 0x6e, 0x79, 0xb8, 0x06, 0xb9, 0xa7},
  42. }
  43. // FWPM_CONDITION_IP_LOCAL_PORT = 0c1ba1af-5765-453f-af22-a8f791ac775b
  44. var fwpmConditionIPLocalPort = windows.GUID{
  45. Data1: 0x0c1ba1af, Data2: 0x5765, Data3: 0x453f,
  46. Data4: [8]byte{0xaf, 0x22, 0xa8, 0xf7, 0x91, 0xac, 0x77, 0x5b},
  47. }
  48. // IPPROTO_UDP from in.h.
  49. const ipprotoUDP uint8 = 17
  50. // FWP_ACTION_TYPE values (fwptypes.h). PERMIT is terminating.
  51. const fwpActionPermit uint32 = 0x00001002 // 0x2 | FWP_ACTION_FLAG_TERMINATING(0x1000)
  52. // FWP_DATA_TYPE values we use.
  53. const (
  54. fwpEmpty uint32 = 0
  55. fwpUint8 uint32 = 1
  56. fwpUint16 uint32 = 2
  57. fwpUint64 uint32 = 4
  58. )
  59. // FWP_MATCH_TYPE values.
  60. const fwpMatchEqual uint32 = 0
  61. // FWPM_SESSION flags.
  62. const fwpmSessionFlagDynamic uint32 = 0x1
  63. // FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT prevents lower-priority filters in other sublayers,
  64. // notably Windows Defender Firewall's MPSSVC_WF sublayer, which shares our 0xFFFF weight from overriding this PERMIT.
  65. // Without it, a default WDF block at the same sublayer weight can still win arbitration.
  66. const fwpmFilterFlagClearActionRight uint32 = 0x8
  67. // RPC authentication.
  68. // RPC_C_AUTHN_WINNT works on workgroup machines with no domain context
  69. // RPC_C_AUTHN_DEFAULT falls back through a chain that can land on something WFP doesn't accept on a fresh box.
  70. const rpcCAuthnWinNT uint32 = 10
  71. // fwpByteBlob (FWP_BYTE_BLOB). 16 bytes on 64-bit.
  72. type fwpByteBlob struct {
  73. size uint32
  74. _ uint32 // padding
  75. data *uint8
  76. }
  77. // fwpValue0 / FWP_CONDITION_VALUE0 layout. 16 bytes on 64-bit.
  78. // The union is pointer-sized; types <= 32 bits (UINT8/16/32, INT8/16/32, float) live inline in the low bytes
  79. // of `value`, while UINT64/INT64/double and aggregate types are stored *by pointer*, even on 64-bit, where the
  80. // union member is declared as UINT64*. So when populating an FWP_UINT64 condition, pass
  81. // uintptr(unsafe.Pointer(&luidVar)) instead of the LUID inline.
  82. type fwpValue0 struct {
  83. type_ uint32
  84. _ uint32 // padding before union to 8-byte alignment
  85. value uintptr
  86. }
  87. // fwpmDisplayData0 / FWPM_DISPLAY_DATA0. 16 bytes on 64-bit.
  88. type fwpmDisplayData0 struct {
  89. name *uint16
  90. description *uint16
  91. }
  92. // fwpmAction0 / FWPM_ACTION0. 20 bytes; no leading padding because actionType
  93. // is uint32 and GUID's first field is uint32.
  94. type fwpmAction0 struct {
  95. actionType uint32
  96. filterType windows.GUID
  97. }
  98. // fwpmFilterCondition0. 40 bytes on 64-bit.
  99. type fwpmFilterCondition0 struct {
  100. fieldKey windows.GUID // 16
  101. matchType uint32 // 4
  102. _ uint32 // 4 padding
  103. conditionValue fwpValue0 // 16
  104. }
  105. // fwpmFilter0. 200 bytes on 64-bit.
  106. type fwpmFilter0 struct {
  107. filterKey windows.GUID
  108. displayData fwpmDisplayData0
  109. flags uint32
  110. _ uint32 // padding before *GUID
  111. providerKey *windows.GUID
  112. providerData fwpByteBlob
  113. layerKey windows.GUID
  114. subLayerKey windows.GUID
  115. weight fwpValue0
  116. numFilterConditions uint32
  117. _ uint32 // padding before pointer
  118. filterCondition *fwpmFilterCondition0
  119. action fwpmAction0
  120. _ [4]byte // layout correction
  121. providerContextKey windows.GUID
  122. reserved *windows.GUID
  123. filterID uint64
  124. effectiveWeight fwpValue0
  125. }
  126. // fwpmSublayer0. 72 bytes on 64-bit.
  127. type fwpmSublayer0 struct {
  128. subLayerKey windows.GUID
  129. displayData fwpmDisplayData0
  130. flags uint32
  131. _ uint32 // padding before *GUID
  132. providerKey *windows.GUID
  133. providerData fwpByteBlob
  134. weight uint16
  135. _ [6]byte // padding to 72 bytes
  136. }
  137. // fwpmSession0. 72 bytes on 64-bit.
  138. type fwpmSession0 struct {
  139. sessionKey windows.GUID
  140. displayData fwpmDisplayData0
  141. flags uint32
  142. txnWaitTimeoutInMSec uint32
  143. processId uint32
  144. _ uint32 // padding before *SID
  145. sid *windows.SID
  146. username *uint16
  147. kernelMode uint8
  148. _ [7]byte // tail padding
  149. }
  150. // fwpuclnt.dll bindings. Only the calls we use.
  151. var (
  152. modFwpuclnt = windows.NewLazySystemDLL("fwpuclnt.dll")
  153. procFwpmEngineOpen0 = modFwpuclnt.NewProc("FwpmEngineOpen0")
  154. procFwpmEngineClose0 = modFwpuclnt.NewProc("FwpmEngineClose0")
  155. procFwpmSubLayerAdd0 = modFwpuclnt.NewProc("FwpmSubLayerAdd0")
  156. procFwpmFilterAdd0 = modFwpuclnt.NewProc("FwpmFilterAdd0")
  157. )
  158. // Session holds the WFP engine handle for a single bypass operation. The handle owns a dynamic session:
  159. // when it is closed, every WFP object added during the session (sublayer + filters) is automatically deleted by
  160. // Windows. That gives us correct cleanup even if the host process is killed hard between Permit* and Close.
  161. type Session struct {
  162. engine uintptr
  163. }
  164. // Close releases the engine handle. Windows deletes every dynamic object (sublayer + filters) the session installed.
  165. // Safe to call on a nil receiver.
  166. func (s *Session) Close() {
  167. if s == nil || s.engine == 0 {
  168. return
  169. }
  170. procFwpmEngineClose0.Call(s.engine)
  171. s.engine = 0
  172. }
  173. // PermitInterface installs PERMIT filters at FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4 and _V6 scoped to the given network
  174. // interface LUID. Inbound traffic on that interface bypasses Windows Defender Firewall.
  175. func PermitInterface(luid uint64) (*Session, error) {
  176. s, sublayerKey, err := newSession()
  177. if err != nil {
  178. return nil, err
  179. }
  180. if err := addInterfaceFilter(s.engine, sublayerKey, fwpmLayerAleAuthRecvAcceptV4, luid); err != nil {
  181. s.Close()
  182. return nil, fmt.Errorf("add v4 filter: %w", err)
  183. }
  184. if err := addInterfaceFilter(s.engine, sublayerKey, fwpmLayerAleAuthRecvAcceptV6, luid); err != nil {
  185. s.Close()
  186. return nil, fmt.Errorf("add v6 filter: %w", err)
  187. }
  188. return s, nil
  189. }
  190. // PermitUDPPort installs PERMIT filters at FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4 and _V6 scoped to UDP traffic with the
  191. // given local port. Inbound UDP to that port on any interface bypasses Windows Defender Firewall.
  192. func PermitUDPPort(port uint16) (*Session, error) {
  193. s, sublayerKey, err := newSession()
  194. if err != nil {
  195. return nil, err
  196. }
  197. if err := addUDPPortFilter(s.engine, sublayerKey, fwpmLayerAleAuthRecvAcceptV4, port); err != nil {
  198. s.Close()
  199. return nil, fmt.Errorf("add v4 filter: %w", err)
  200. }
  201. if err := addUDPPortFilter(s.engine, sublayerKey, fwpmLayerAleAuthRecvAcceptV6, port); err != nil {
  202. s.Close()
  203. return nil, fmt.Errorf("add v6 filter: %w", err)
  204. }
  205. return s, nil
  206. }
  207. func newSession() (*Session, windows.GUID, error) {
  208. engine, err := openDynamicEngine()
  209. if err != nil {
  210. return nil, windows.GUID{}, err
  211. }
  212. sublayerKey, err := registerSublayer(engine)
  213. if err != nil {
  214. procFwpmEngineClose0.Call(engine)
  215. return nil, windows.GUID{}, err
  216. }
  217. return &Session{engine: engine}, sublayerKey, nil
  218. }
  219. func openDynamicEngine() (uintptr, error) {
  220. session := fwpmSession0{flags: fwpmSessionFlagDynamic}
  221. var engine uintptr
  222. r1, _, _ := procFwpmEngineOpen0.Call(
  223. 0, // serverName == NULL (local)
  224. uintptr(rpcCAuthnWinNT),
  225. 0, // authIdentity == NULL
  226. uintptr(unsafe.Pointer(&session)),
  227. uintptr(unsafe.Pointer(&engine)),
  228. )
  229. if r1 != 0 {
  230. return 0, fmt.Errorf("FwpmEngineOpen0: 0x%x", r1)
  231. }
  232. return engine, nil
  233. }
  234. // registerSublayer adds a session-scoped sublayer with a freshly generated GUID, weight 0xFFFF so its filters arbitrate
  235. // above WDF's default sublayer. The sublayer is dynamic (no PERSISTENT flag) and goes away when the engine handle closes.
  236. func registerSublayer(engine uintptr) (windows.GUID, error) {
  237. key, err := windows.GenerateGUID()
  238. if err != nil {
  239. return windows.GUID{}, fmt.Errorf("GenerateGUID for sublayer: %w", err)
  240. }
  241. name, _ := windows.UTF16PtrFromString("Nebula WDF bypass sublayer")
  242. desc, _ := windows.UTF16PtrFromString("Permit filters bypassing Windows Defender Firewall")
  243. sl := fwpmSublayer0{
  244. subLayerKey: key,
  245. displayData: fwpmDisplayData0{name: name, description: desc},
  246. weight: 0xFFFF,
  247. }
  248. r1, _, _ := procFwpmSubLayerAdd0.Call(
  249. engine,
  250. uintptr(unsafe.Pointer(&sl)),
  251. 0, // sd == NULL
  252. )
  253. if r1 != 0 {
  254. return windows.GUID{}, fmt.Errorf("FwpmSubLayerAdd0: 0x%x", r1)
  255. }
  256. return key, nil
  257. }
  258. func addInterfaceFilter(engine uintptr, sublayerKey, layer windows.GUID, luid uint64) error {
  259. name, _ := windows.UTF16PtrFromString("Nebula allow interface inbound")
  260. desc, _ := windows.UTF16PtrFromString("Permits inbound traffic on a nebula interface")
  261. // luid must remain addressable through the syscall -- FWP_UINT64 is stored
  262. // by pointer in the FWP_VALUE0 union.
  263. cond := fwpmFilterCondition0{
  264. fieldKey: fwpmConditionIPLocalInterface,
  265. matchType: fwpMatchEqual,
  266. conditionValue: fwpValue0{
  267. type_: fwpUint64,
  268. value: uintptr(unsafe.Pointer(&luid)),
  269. },
  270. }
  271. filter := fwpmFilter0{
  272. // filterKey left zero: WFP assigns one when the filter is added.
  273. displayData: fwpmDisplayData0{name: name, description: desc},
  274. flags: fwpmFilterFlagClearActionRight,
  275. layerKey: layer,
  276. subLayerKey: sublayerKey,
  277. weight: fwpValue0{type_: fwpUint8, value: uintptr(15)},
  278. numFilterConditions: 1,
  279. filterCondition: &cond,
  280. action: fwpmAction0{actionType: fwpActionPermit},
  281. }
  282. r1, _, _ := procFwpmFilterAdd0.Call(
  283. engine,
  284. uintptr(unsafe.Pointer(&filter)),
  285. 0, // sd == NULL
  286. 0, // id == NULL
  287. )
  288. if r1 != 0 {
  289. return fmt.Errorf("FwpmFilterAdd0: 0x%x", r1)
  290. }
  291. return nil
  292. }
  293. // addUDPPortFilter installs a PERMIT filter that matches (IP_PROTOCOL == UDP) AND (IP_LOCAL_PORT == port).
  294. // FWP_UINT8 and FWP_UINT16 are <= 32 bits so they live inline in the FWP_VALUE0 union.
  295. func addUDPPortFilter(engine uintptr, sublayerKey, layer windows.GUID, port uint16) error {
  296. name, _ := windows.UTF16PtrFromString("Nebula allow UDP port inbound")
  297. desc, _ := windows.UTF16PtrFromString("Permits inbound UDP to a nebula listener port")
  298. conds := [2]fwpmFilterCondition0{
  299. {
  300. fieldKey: fwpmConditionIPProtocol,
  301. matchType: fwpMatchEqual,
  302. conditionValue: fwpValue0{
  303. type_: fwpUint8,
  304. value: uintptr(ipprotoUDP),
  305. },
  306. },
  307. {
  308. fieldKey: fwpmConditionIPLocalPort,
  309. matchType: fwpMatchEqual,
  310. conditionValue: fwpValue0{
  311. type_: fwpUint16,
  312. value: uintptr(port),
  313. },
  314. },
  315. }
  316. filter := fwpmFilter0{
  317. displayData: fwpmDisplayData0{name: name, description: desc},
  318. flags: fwpmFilterFlagClearActionRight,
  319. layerKey: layer,
  320. subLayerKey: sublayerKey,
  321. weight: fwpValue0{type_: fwpUint8, value: uintptr(15)},
  322. numFilterConditions: 2,
  323. filterCondition: &conds[0],
  324. action: fwpmAction0{actionType: fwpActionPermit},
  325. }
  326. r1, _, _ := procFwpmFilterAdd0.Call(
  327. engine,
  328. uintptr(unsafe.Pointer(&filter)),
  329. 0,
  330. 0,
  331. )
  332. if r1 != 0 {
  333. return fmt.Errorf("FwpmFilterAdd0: 0x%x", r1)
  334. }
  335. return nil
  336. }