diff --git a/.github/workflows/kext.yml b/.github/workflows/kext.yml new file mode 100644 index 000000000..6ccdee66c --- /dev/null +++ b/.github/workflows/kext.yml @@ -0,0 +1,36 @@ +name: Windows Kernel Extension + +on: + push: + paths: + - 'windows_kext/**' + branches: + - master + - develop + + pull_request: + paths: + - 'windows_kext/**' + branches: + - master + - develop + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: earthly/actions-setup@v1 + with: + version: v0.8.0 + - uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Kernel Extension + run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +kext-build diff --git a/.gitignore b/.gitignore index 8268448fb..d534dccd8 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ _testmain.go win_dev_* go.work go.work.sum + +# Kext releases +windows_kext/release/kext_release_*.zip diff --git a/Earthfile b/Earthfile index 5d9d3394d..d691fc37d 100644 --- a/Earthfile +++ b/Earthfile @@ -3,6 +3,7 @@ VERSION --arg-scope-and-set --global-cache 0.8 ARG --global go_version = 1.22 ARG --global node_version = 18 ARG --global rust_version = 1.76 +ARG --global golangci_lint_version = 1.57.1 ARG --global go_builder_image = "golang:${go_version}-alpine" ARG --global node_builder_image = "node:${node_version}" @@ -164,6 +165,12 @@ go-test-all: BUILD +go-test --GOARCH="${GOARCH}" --GOOS="${GOOS}" --GOARM="${GOARM}" END +go-lint: + FROM +go-base + + RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v${golangci_lint_version} + RUN golangci-lint run -c ./.golangci.yml --timeout 15m --show-stats + # Builds portmaster-start, portmaster-core, hub and notifier for all supported platforms go-release: FROM ${work_image} @@ -454,6 +461,27 @@ tauri-release: BUILD +tauri-build --target="${arch}" --bundle="${bundle}" END +kext-build: + FROM ${rust_builder_image} + + # Install architecture target + DO rust+INIT --keep_fingerprints=true + + # Build kext + WORKDIR /app/kext + # --keep-ts is necessary to ensure that the timestamps of the source files + # are preserved such that Rust's incremental compilation works correctly. + COPY --keep-ts ./windows_kext/ . + + # Add target architecture + RUN rustup target add x86_64-pc-windows-msvc + + # Build using special earthly lib + WORKDIR /app/kext/release + DO rust+CARGO --args="run" + + SAVE ARTIFACT --keep-ts "portmaster-kext-release-bundle.zip" AS LOCAL "${outputDir}/windows_amd64/portmaster-kext-release-bundle.zip" + build: BUILD +go-release BUILD +angular-release @@ -466,7 +494,7 @@ release: RUN echo -e "\033[1;31m Refusing to release a dirty git repository. Please commit your local changes first! \033[0m" ; exit 1 END - BUILD +build-all + BUILD +build # Takes GOOS, GOARCH and optionally GOARM and creates a string representation for file-names. diff --git a/go.sum b/go.sum index 5f2b38340..b008d9881 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,6 @@ github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIo github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/VictoriaMetrics/metrics v1.29.1 h1:yTORfGeO1T0C6P/tEeT4Mf7rBU5TUu3kjmHvmlaoeO8= -github.com/VictoriaMetrics/metrics v1.29.1/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8= github.com/VictoriaMetrics/metrics v1.33.1 h1:CNV3tfm2Kpv7Y9W3ohmvqgFWPR55tV2c7M2U6OIo+UM= github.com/VictoriaMetrics/metrics v1.33.1/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8= github.com/Xuanwo/go-locale v1.1.0 h1:51gUxhxl66oXAjI9uPGb2O0qwPECpriKQb2hl35mQkg= @@ -32,8 +30,6 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= -github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= -github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= github.com/cilium/ebpf v0.14.0 h1:0PsxAjO6EjI1rcT+rkp6WcCnE0ZvfkXBYiMedJtrSUs= github.com/cilium/ebpf v0.14.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= @@ -65,15 +61,10 @@ github.com/florianl/go-nfqueue v1.3.1 h1:khQ9fYCrjbu5CF8dZF55G2RTIEIQRI0Aj5k3msJ github.com/florianl/go-nfqueue v1.3.1/go.mod h1:aHWbgkhryJxF5XxYvJ3oRZpdD4JP74Zu/hP1zuhja+M= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= -github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.3-0.20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg= github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU= -github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= -github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/garyburd/redigo v1.1.1-0.20170914051019-70e1b1943d4f/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= @@ -82,6 +73,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= @@ -120,8 +113,6 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= @@ -161,8 +152,6 @@ github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4h github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= -github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -201,12 +190,8 @@ github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/ github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g= github.com/mdlayher/socket v0.1.0/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= -github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= -github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= -github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= @@ -235,32 +220,19 @@ github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg= github.com/r3labs/diff/v3 v3.0.1/go.mod h1:f1S9bourRbiM66NskseyUdo0fTmEE0qKrikYJX63dgo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rot256/pblind v0.0.0-20231024115251-cd3f239f28c1 h1:vfAp3Jbca7Vt8axzmkS5M/RtFJmj0CKmrtWAlHtesaA= github.com/rot256/pblind v0.0.0-20231024115251-cd3f239f28c1/go.mod h1:2x8fbm9T+uTl919COhEVHKGkve1DnkrEnDbtGptZuW8= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/safing/jess v0.3.3 h1:0U0bWdO0sFCgox+nMOqISFrnJpVmi+VFOW1xdX6q3qw= github.com/safing/jess v0.3.3/go.mod h1:t63qHB+4xd1HIv9MKN/qI2rc7ytvx7d6l4hbX7zxer0= -github.com/safing/portbase v0.18.9 h1:j+ToHKQz0U2+Tx4jMP7QPky/H0R4uY6qUM+lIJlO6ks= -github.com/safing/portbase v0.18.9/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc= -github.com/safing/portbase v0.19.0 h1:2T6f/w90IdIsSgUfyXoveqZM7tVwW+IFrtLbPVXtY3k= -github.com/safing/portbase v0.19.0/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc= -github.com/safing/portbase v0.19.1 h1:Uk/WyP9HsIJrCn0pE4a7AWIrfUSHyCOObQyRmXsGQ9A= -github.com/safing/portbase v0.19.1/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc= -github.com/safing/portbase v0.19.2 h1:qGF5Jv9eEE33d2aIxeBQdnitnBoF44BGVFtboqfE+1A= -github.com/safing/portbase v0.19.2/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc= -github.com/safing/portbase v0.19.3 h1:fzb4d2nzhmRq4Lt6sgn9R20iykireAkBNyf9pfGqQjk= -github.com/safing/portbase v0.19.3/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc= github.com/safing/portbase v0.19.4 h1:Oh7oUBp6xn5whhKtvnNKS5rhHqyXJDDxfxwf+gRswhQ= github.com/safing/portbase v0.19.4/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc= github.com/safing/portbase v0.19.5 h1:3/8odzlvb629tHPwdj/sthSeJcwZHYrqA6YuvNUZzNc= github.com/safing/portbase v0.19.5/go.mod h1:Qrh3ck+7VZloFmnozCs9Hj8godhJAi55cmiDiC7BwTc= github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec h1:oSJY1seobofPwpMoJRkCgXnTwfiQWNfGMCPDfqgAEfg= github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec/go.mod h1:abwyAQrZGemWbSh/aCD9nnkp0SvFFf/mGWkAbOwPnFE= -github.com/safing/spn v0.7.5 h1:WfkMs2omLrwxBWccGGG9Akx0AvsvJLG+W7rjWQpQhl4= -github.com/safing/spn v0.7.5/go.mod h1:Hg585WJuib4JI3R7Kndq/10MJPCUl1UmeJJwL3JIwdQ= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/seehuhn/fortuna v1.0.1 h1:lu9+CHsmR0bZnx5Ay646XvCSRJ8PJTi5UYJwDBX68H0= @@ -298,8 +270,6 @@ github.com/tannerryan/ring v1.1.2/go.mod h1:DkELJEjbZhJBtFKR9Xziwj3HKZnb/knRgljN github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= -github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -330,8 +300,6 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= -github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= @@ -344,22 +312,14 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= -go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE= -golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= @@ -371,8 +331,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -396,10 +354,6 @@ golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -407,8 +361,6 @@ golang.org/x/sync v0.0.0-20170517211232-f52d1811a629/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -445,10 +397,6 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -469,8 +417,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -492,30 +438,32 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gvisor.dev/gvisor v0.0.0-20240110202538-8053cd8f0bf6 h1:Ass5FAjCCQ5WECPE9NN7ItZnKJ38i6sM8MCMNBGee5I= -gvisor.dev/gvisor v0.0.0-20240110202538-8053cd8f0bf6/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= -gvisor.dev/gvisor v0.0.0-20240327015314-08ed01b28587 h1:wH3g/qTCPlVBwkFktYuKNFJGeo7ctLNEjzrMlfPrVgE= -gvisor.dev/gvisor v0.0.0-20240327015314-08ed01b28587/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= gvisor.dev/gvisor v0.0.0-20240409213450-87d8df37c71e h1:jpvBdtqDLzu2MZuruscr008NwJxiDidjFF4ZQq7YZbk= gvisor.dev/gvisor v0.0.0-20240409213450-87d8df37c71e/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= gvisor.dev/gvisor v0.0.0-20240422234719-5cecdfbabd15 h1:zf/5V7KP7FL0G5GXg1zGPeaBkBG3CdJagSHbYZxsYKU= gvisor.dev/gvisor v0.0.0-20240422234719-5cecdfbabd15/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= -modernc.org/libc v1.40.1 h1:ZhRylEBcj3GyQbPVC8JxIg7SdrT4JOxIDJoUon0NfF8= -modernc.org/libc v1.40.1/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= +modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk= +modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA= +modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg= modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo= modernc.org/libc v1.50.2 h1:I0+3wlRvXmAEjAJvD7BhP1kmKHwkzV0rOcqFcD85u+0= modernc.org/libc v1.50.2/go.mod h1:Fd8TZdfRorOd1vB0QCtYSHYAuzobS4xS3mhMGUkeVcA= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4= modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= modernc.org/sqlite v1.29.8 h1:nGKglNx9K5v0As+zF0/Gcl1kMkmaU1XynYyq92PbsC8= diff --git a/service/firewall/interception/interception_windows.go b/service/firewall/interception/interception_windows.go index 71033c1a8..ab7535188 100644 --- a/service/firewall/interception/interception_windows.go +++ b/service/firewall/interception/interception_windows.go @@ -5,12 +5,16 @@ import ( "fmt" "time" - "github.com/safing/portmaster/service/firewall/interception/windowskext" + "github.com/safing/portbase/log" + kext1 "github.com/safing/portmaster/service/firewall/interception/windowskext" + kext2 "github.com/safing/portmaster/service/firewall/interception/windowskext2" "github.com/safing/portmaster/service/network" "github.com/safing/portmaster/service/network/packet" "github.com/safing/portmaster/service/updates" ) +var useOldKext = false + // start starts the interception. func startInterception(packets chan packet.Packet) error { kextFile, err := updates.GetPlatformFile("kext/portmaster-kext.sys") @@ -18,51 +22,142 @@ func startInterception(packets chan packet.Packet) error { return fmt.Errorf("interception: could not get kext sys: %s", err) } - err = windowskext.Init(kextFile.Path()) + err = kext2.Init(kextFile.Path()) if err != nil { return fmt.Errorf("interception: could not init windows kext: %s", err) } - err = windowskext.Start() + err = kext2.Start() if err != nil { return fmt.Errorf("interception: could not start windows kext: %s", err) } - // Start packet handler. - module.StartServiceWorker("kext packet handler", 0, func(ctx context.Context) error { - windowskext.Handler(ctx, packets) - return nil - }) + version, err := kext2.GetVersion() + if err != nil { + return fmt.Errorf("interception: failed to read version: %s", err) + } + log.Debugf("Kext version: %s", version.String()) + + if version.Major < 2 { + useOldKext = true + + // Transfer ownership. + kext1.SetKextHandler(kext2.GetKextHandle()) + kext1.SetKextService(kext2.GetKextServiceHandle(), kextFile.Path()) + + // Start packet handler. + module.StartServiceWorker("kext packet handler", 0, func(ctx context.Context) error { + kext1.Handler(ctx, packets) + return nil + }) + + // Start bandwidth stats monitor. + module.StartServiceWorker("kext bandwidth stats monitor", 0, func(ctx context.Context) error { + return kext1.BandwidthStatsWorker(ctx, 1*time.Second, BandwidthUpdates) + }) + } else { + + // Start packet handler. + module.StartServiceWorker("kext packet handler", 0, func(ctx context.Context) error { + kext2.Handler(ctx, packets, BandwidthUpdates) + return nil + }) + + // Start bandwidth stats monitor. + module.StartServiceWorker("kext bandwidth request worker", 0, func(ctx context.Context) error { + timer := time.NewTicker(1 * time.Second) + defer timer.Stop() + for { + select { + case <-timer.C: + err := kext2.SendBandwidthStatsRequest() + if err != nil { + return err + } + case <-ctx.Done(): + return nil + } - // Start bandwidth stats monitor. - module.StartServiceWorker("kext bandwidth stats monitor", 0, func(ctx context.Context) error { - return windowskext.BandwidthStatsWorker(ctx, 1*time.Second, BandwidthUpdates) - }) + } + }) + + // Start kext logging. The worker will periodically send request to the kext to send logs. + module.StartServiceWorker("kext log request worker", 0, func(ctx context.Context) error { + timer := time.NewTicker(1 * time.Second) + defer timer.Stop() + for { + select { + case <-timer.C: + err := kext2.SendLogRequest() + if err != nil { + return err + } + case <-ctx.Done(): + return nil + } + + } + }) + + module.StartServiceWorker("kext clean ended connection worker", 0, func(ctx context.Context) error { + timer := time.NewTicker(30 * time.Second) + defer timer.Stop() + for { + select { + case <-timer.C: + err := kext2.SendCleanEndedConnection() + if err != nil { + return err + } + case <-ctx.Done(): + return nil + } + + } + }) + } return nil } // stop starts the interception. func stopInterception() error { - return windowskext.Stop() + if useOldKext { + return kext1.Stop() + } + return kext2.Stop() } // ResetVerdictOfAllConnections resets all connections so they are forced to go thought the firewall again. func ResetVerdictOfAllConnections() error { - return windowskext.ClearCache() + if useOldKext { + return kext1.ClearCache() + } + return kext2.ClearCache() } // UpdateVerdictOfConnection updates the verdict of the given connection in the kernel extension. func UpdateVerdictOfConnection(conn *network.Connection) error { - return windowskext.UpdateVerdict(conn) + if useOldKext { + return kext1.UpdateVerdict(conn) + } + return kext2.UpdateVerdict(conn) } // GetKextVersion returns the version of the kernel extension. func GetKextVersion() (string, error) { - version, err := windowskext.GetVersion() - if err != nil { - return "", err + if useOldKext { + version, err := kext1.GetVersion() + if err != nil { + return "", err + } + return version.String(), nil + } else { + version, err := kext2.GetVersion() + if err != nil { + return "", err + } + return version.String(), nil } - return version.String(), nil } diff --git a/service/firewall/interception/windowskext/bandwidth_stats.go b/service/firewall/interception/windowskext/bandwidth_stats.go index f1fb856bd..a29e50d99 100644 --- a/service/firewall/interception/windowskext/bandwidth_stats.go +++ b/service/firewall/interception/windowskext/bandwidth_stats.go @@ -81,7 +81,7 @@ func reportBandwidth(ctx context.Context, bandwidthUpdates chan *packet.Bandwidt return nil } -func StartBandwithConsoleLogger() { +func StartBandwidthConsoleLogger() { go func() { ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() diff --git a/service/firewall/interception/windowskext/kext.go b/service/firewall/interception/windowskext/kext.go index 7699c35a3..34badd6d7 100644 --- a/service/firewall/interception/windowskext/kext.go +++ b/service/firewall/interception/windowskext/kext.go @@ -76,6 +76,15 @@ func Start() error { return nil } +func SetKextHandler(handle windows.Handle) { + kextHandle = handle +} + +func SetKextService(handle windows.Handle, path string) { + service = &KextService{handle: handle} + driverPath = path +} + // Stop intercepting. func Stop() error { // Prepare kernel for shutdown diff --git a/service/firewall/interception/windowskext/service.go b/service/firewall/interception/windowskext/service.go index 399407bbf..e3e4ac2a9 100644 --- a/service/firewall/interception/windowskext/service.go +++ b/service/firewall/interception/windowskext/service.go @@ -24,6 +24,7 @@ func createKextService(driverName string, driverPath string) (*KextService, erro } defer windows.CloseServiceHandle(manager) + // Convert the driver name to a UTF16 string driverNameU16, err := syscall.UTF16FromString(driverName) if err != nil { return nil, fmt.Errorf("failed to convert driver name to UTF16 string: %w", err) diff --git a/service/firewall/interception/windowskext2/doc.go b/service/firewall/interception/windowskext2/doc.go new file mode 100644 index 000000000..c68a942fe --- /dev/null +++ b/service/firewall/interception/windowskext2/doc.go @@ -0,0 +1,4 @@ +// +build windows + +// Package windowskext provides network interception capabilities on windows via the Portmaster Kernel Extension. +package windowskext diff --git a/service/firewall/interception/windowskext2/handler.go b/service/firewall/interception/windowskext2/handler.go new file mode 100644 index 000000000..bb6348dd2 --- /dev/null +++ b/service/firewall/interception/windowskext2/handler.go @@ -0,0 +1,195 @@ +//go:build windows +// +build windows + +package windowskext + +import ( + "context" + "fmt" + "net" + "time" + + "github.com/safing/portmaster/service/process" + + "github.com/tevino/abool" + + "github.com/safing/portbase/log" + "github.com/safing/portmaster/service/network/packet" +) + +type VersionInfo struct { + Major uint8 + Minor uint8 + Revision uint8 + Build uint8 +} + +func (v *VersionInfo) String() string { + return fmt.Sprintf("%d.%d.%d.%d", v.Major, v.Minor, v.Revision, v.Build) +} + +// Handler transforms received packets to the Packet interface. +func Handler(ctx context.Context, packets chan packet.Packet, bandwidthUpdate chan *packet.BandwidthUpdate) { + for { + packetInfo, err := RecvVerdictRequest() + if err != nil { + log.Warningf("failed to get packet from windows kext: %s", err) + return + } + + switch { + case packetInfo.ConnectionV4 != nil: + { + // log.Tracef("packet: %+v", packetInfo.ConnectionV4) + conn := packetInfo.ConnectionV4 + // New Packet + newPacket := &Packet{ + verdictRequest: conn.ID, + payload: conn.Payload, + verdictSet: abool.NewBool(false), + } + info := newPacket.Info() + info.Inbound = conn.Direction > 0 + info.InTunnel = false + info.Protocol = packet.IPProtocol(conn.Protocol) + info.PID = int(conn.ProcessID) + info.SeenAt = time.Now() + + // Check PID + if info.PID == 0 { + // Windows does not have zero PIDs. + // Set to UndefinedProcessID. + info.PID = process.UndefinedProcessID + } + + // Set IP version + info.Version = packet.IPv4 + + // Set IPs + if info.Inbound { + // Inbound + info.Src = conn.RemoteIP[:] + info.Dst = conn.LocalIP[:] + } else { + // Outbound + info.Src = conn.LocalIP[:] + info.Dst = conn.RemoteIP[:] + } + + // Set Ports + if info.Inbound { + // Inbound + info.SrcPort = conn.RemotePort + info.DstPort = conn.LocalPort + } else { + // Outbound + info.SrcPort = conn.LocalPort + info.DstPort = conn.RemotePort + } + + packets <- newPacket + } + case packetInfo.ConnectionV6 != nil: + { + // log.Tracef("packet: %+v", packetInfo.ConnectionV6) + conn := packetInfo.ConnectionV6 + // New Packet + newPacket := &Packet{ + verdictRequest: conn.ID, + payload: conn.Payload, + verdictSet: abool.NewBool(false), + } + info := newPacket.Info() + info.Inbound = conn.Direction > 0 + info.InTunnel = false + info.Protocol = packet.IPProtocol(conn.Protocol) + info.PID = int(conn.ProcessID) + info.SeenAt = time.Now() + + // Check PID + if info.PID == 0 { + // Windows does not have zero PIDs. + // Set to UndefinedProcessID. + info.PID = process.UndefinedProcessID + } + + // Set IP version + info.Version = packet.IPv6 + + // Set IPs + if info.Inbound { + // Inbound + info.Src = conn.RemoteIP[:] + info.Dst = conn.LocalIP[:] + } else { + // Outbound + info.Src = conn.LocalIP[:] + info.Dst = conn.RemoteIP[:] + } + + // Set Ports + if info.Inbound { + // Inbound + info.SrcPort = conn.RemotePort + info.DstPort = conn.LocalPort + } else { + // Outbound + info.SrcPort = conn.LocalPort + info.DstPort = conn.RemotePort + } + + packets <- newPacket + } + case packetInfo.LogLine != nil: + { + line := packetInfo.LogLine + switch line.Severity { + case byte(log.DebugLevel): + log.Debugf("kext: %s", line.Line) + case byte(log.InfoLevel): + log.Infof("kext: %s", line.Line) + case byte(log.WarningLevel): + log.Warningf("kext: %s", line.Line) + case byte(log.ErrorLevel): + log.Errorf("kext: %s", line.Line) + case byte(log.CriticalLevel): + log.Criticalf("kext: %s", line.Line) + } + } + case packetInfo.BandwidthStats != nil: + { + bandwidthStats := packetInfo.BandwidthStats + for _, stat := range bandwidthStats.ValuesV4 { + connID := packet.CreateConnectionID( + packet.IPProtocol(bandwidthStats.Protocol), + net.IP(stat.LocalIP[:]), stat.LocalPort, + net.IP(stat.RemoteIP[:]), stat.RemotePort, + false, + ) + update := &packet.BandwidthUpdate{ + ConnID: connID, + BytesReceived: stat.ReceivedBytes, + BytesSent: stat.TransmittedBytes, + Method: packet.Additive, + } + bandwidthUpdate <- update + } + for _, stat := range bandwidthStats.ValuesV6 { + connID := packet.CreateConnectionID( + packet.IPProtocol(bandwidthStats.Protocol), + net.IP(stat.LocalIP[:]), stat.LocalPort, + net.IP(stat.RemoteIP[:]), stat.RemotePort, + false, + ) + update := &packet.BandwidthUpdate{ + ConnID: connID, + BytesReceived: stat.ReceivedBytes, + BytesSent: stat.TransmittedBytes, + Method: packet.Additive, + } + bandwidthUpdate <- update + } + } + } + } +} diff --git a/service/firewall/interception/windowskext2/kext.go b/service/firewall/interception/windowskext2/kext.go new file mode 100644 index 000000000..fd6adb721 --- /dev/null +++ b/service/firewall/interception/windowskext2/kext.go @@ -0,0 +1,166 @@ +//go:build windows +// +build windows + +package windowskext + +import ( + "fmt" + + "github.com/safing/portbase/log" + "github.com/safing/portmaster/service/network" + "github.com/safing/portmaster/windows_kext/kextinterface" + "golang.org/x/sys/windows" +) + +// Package errors +var ( + driverPath string + + service *kextinterface.KextService + kextFile *kextinterface.KextFile +) + +const ( + driverName = "PortmasterKext" +) + +func Init(path string) error { + driverPath = path + return nil +} + +// Start intercepting. +func Start() error { + // initialize and start driver service + var err error + service, err = kextinterface.CreateKextService(driverName, driverPath) + if err != nil { + return fmt.Errorf("failed to create service: %w", err) + } + + // Start service and open file + service.Start(true) + kextFile, err = service.OpenFile(1024) + + if err != nil { + return fmt.Errorf("failed to open driver: %w", err) + } + + return nil +} + +func GetKextHandle() windows.Handle { + return kextFile.GetHandle() +} + +func GetKextServiceHandle() windows.Handle { + return service.GetHandle() +} + +// Stop intercepting. +func Stop() error { + // Prepare kernel for shutdown + err := shutdownRequest() + if err != nil { + log.Warningf("winkext: shutdown request failed: %s", err) + } + // Close the interface to the driver. Driver will continue to run. + err = kextFile.Close() + if err != nil { + log.Warningf("winkext: failed to close kext file: %s", err) + } + + // Stop and delete the driver. + err = service.Stop(true) + if err != nil { + log.Warningf("winkext: failed to stop kernel service: %s", err) + } + + err = service.Delete() + if err != nil { + log.Warningf("winkext: failed to delete kernel service: %s", err) + } + return nil +} + +// Sends a shutdown request. +func shutdownRequest() error { + return kextinterface.SendShutdownCommand(kextFile) +} + +// Send request for logs of the kext. +func SendLogRequest() error { + return kextinterface.SendGetLogsCommand(kextFile) +} + +func SendBandwidthStatsRequest() error { + return kextinterface.SendGetBandwidthStatsCommand(kextFile) +} + +func SendPrintMemoryStatsCommand() error { + return kextinterface.SendPrintMemoryStatsCommand(kextFile) +} + +func SendCleanEndedConnection() error { + return kextinterface.SendCleanEndedConnectionsCommand(kextFile) +} + +// RecvVerdictRequest waits for the next verdict request from the kext. If a timeout is reached, both *VerdictRequest and error will be nil. +func RecvVerdictRequest() (*kextinterface.Info, error) { + return kextinterface.RecvInfo(kextFile) +} + +// SetVerdict sets the verdict for a packet and/or connection. +func SetVerdict(pkt *Packet, verdict kextinterface.KextVerdict) error { + verdictCommand := kextinterface.Verdict{ID: pkt.verdictRequest, Verdict: uint8(verdict)} + return kextinterface.SendVerdictCommand(kextFile, verdictCommand) +} + +// Clears the internal connection cache. +func ClearCache() error { + return kextinterface.SendClearCacheCommand(kextFile) +} + +// Updates a specific connection verdict. +func UpdateVerdict(conn *network.Connection) error { + if conn.IPVersion == 4 { + update := kextinterface.UpdateV4{ + Protocol: conn.Entity.Protocol, + LocalAddress: [4]byte(conn.LocalIP), + LocalPort: conn.LocalPort, + RemoteAddress: [4]byte(conn.Entity.IP), + RemotePort: conn.Entity.Port, + Verdict: uint8(conn.Verdict), + } + + return kextinterface.SendUpdateV4Command(kextFile, update) + } else if conn.IPVersion == 6 { + update := kextinterface.UpdateV6{ + Protocol: conn.Entity.Protocol, + LocalAddress: [16]byte(conn.LocalIP), + LocalPort: conn.LocalPort, + RemoteAddress: [16]byte(conn.Entity.IP), + RemotePort: conn.Entity.Port, + Verdict: uint8(conn.Verdict), + } + + return kextinterface.SendUpdateV6Command(kextFile, update) + } + return nil +} + +// Returns the kext version. +func GetVersion() (*VersionInfo, error) { + data, err := kextinterface.ReadVersion(kextFile) + if err != nil { + return nil, err + } + + version := &VersionInfo{ + Major: data[0], + Minor: data[1], + Revision: data[2], + Build: data[3], + } + return version, nil +} diff --git a/service/firewall/interception/windowskext2/packet.go b/service/firewall/interception/windowskext2/packet.go new file mode 100644 index 000000000..3ea9c0095 --- /dev/null +++ b/service/firewall/interception/windowskext2/packet.go @@ -0,0 +1,132 @@ +//go:build windows +// +build windows + +package windowskext + +import ( + "sync" + + "github.com/tevino/abool" + + "github.com/safing/portbase/log" + "github.com/safing/portmaster/service/network/packet" + "github.com/safing/portmaster/windows_kext/kextinterface" +) + +// Packet represents an IP packet. +type Packet struct { + packet.Base + + verdictRequest uint64 + payload []byte + verdictSet *abool.AtomicBool + + payloadLoaded bool + lock sync.Mutex +} + +// FastTrackedByIntegration returns whether the packet has been fast-track +// accepted by the OS integration. +func (pkt *Packet) FastTrackedByIntegration() bool { + return false +} + +// InfoOnly returns whether the packet is informational only and does not +// represent an actual packet. +func (pkt *Packet) InfoOnly() bool { + return false +} + +// ExpectInfo returns whether the next packet is expected to be informational only. +func (pkt *Packet) ExpectInfo() bool { + return false +} + +// GetPayload returns the full raw packet. +func (pkt *Packet) LoadPacketData() error { + pkt.lock.Lock() + defer pkt.lock.Unlock() + + if !pkt.payloadLoaded { + pkt.payloadLoaded = true + + if len(pkt.payload) > 0 { + err := packet.Parse(pkt.payload, &pkt.Base) + if err != nil { + log.Tracef("payload: %#v", pkt.payload) + log.Tracer(pkt.Ctx()).Warningf("windowskext: failed to parse payload: %s", err) + return packet.ErrFailedToLoadPayload + } + } + } + + if len(pkt.Raw()) == 0 { + return packet.ErrFailedToLoadPayload + } + + return nil +} + +// Accept accepts the packet. +func (pkt *Packet) Accept() error { + if pkt.verdictSet.SetToIf(false, true) { + return SetVerdict(pkt, kextinterface.VerdictAccept) + } + return nil +} + +// Block blocks the packet. +func (pkt *Packet) Block() error { + if pkt.verdictSet.SetToIf(false, true) { + return SetVerdict(pkt, kextinterface.VerdictBlock) + } + return nil +} + +// Drop drops the packet. +func (pkt *Packet) Drop() error { + if pkt.verdictSet.SetToIf(false, true) { + return SetVerdict(pkt, kextinterface.VerdictDrop) + } + return nil +} + +// PermanentAccept permanently accepts connection (and the current packet). +func (pkt *Packet) PermanentAccept() error { + if pkt.verdictSet.SetToIf(false, true) { + return SetVerdict(pkt, kextinterface.VerdictPermanentAccept) + } + return nil +} + +// PermanentBlock permanently blocks connection (and the current packet). +func (pkt *Packet) PermanentBlock() error { + if pkt.verdictSet.SetToIf(false, true) { + return SetVerdict(pkt, kextinterface.VerdictPermanentBlock) + } + return nil +} + +// PermanentDrop permanently drops connection (and the current packet). +func (pkt *Packet) PermanentDrop() error { + if pkt.verdictSet.SetToIf(false, true) { + return SetVerdict(pkt, kextinterface.VerdictPermanentDrop) + } + return nil +} + +// RerouteToNameserver permanently reroutes the connection to the local nameserver (and the current packet). +func (pkt *Packet) RerouteToNameserver() error { + if pkt.verdictSet.SetToIf(false, true) { + return SetVerdict(pkt, kextinterface.VerdictRerouteToNameserver) + } + return nil +} + +// RerouteToTunnel permanently reroutes the connection to the local tunnel entrypoint (and the current packet). +func (pkt *Packet) RerouteToTunnel() error { + if pkt.verdictSet.SetToIf(false, true) { + return SetVerdict(pkt, kextinterface.VerdictRerouteToTunnel) + } + return nil +} diff --git a/service/firewall/interception/windowskext2/service.go b/service/firewall/interception/windowskext2/service.go new file mode 100644 index 000000000..5c939f625 --- /dev/null +++ b/service/firewall/interception/windowskext2/service.go @@ -0,0 +1,10 @@ +//go:build windows +// +build windows + +package windowskext + +import "github.com/safing/portmaster/windows_kext/kextinterface" + +func createKextService(driverName string, driverPath string) (*kextinterface.KextService, error) { + return kextinterface.CreateKextService(driverName, driverPath) +} diff --git a/service/network/connection.go b/service/network/connection.go index 457a2283f..b83ee5423 100644 --- a/service/network/connection.go +++ b/service/network/connection.go @@ -424,6 +424,7 @@ func NewIncompleteConnection(pkt packet.Packet) *Connection { IPProtocol: info.Protocol, Started: info.SeenAt.Unix(), PID: info.PID, + Inbound: info.Inbound, dataComplete: abool.NewBool(false), } conn.lastSeen.Store(conn.Started) diff --git a/windows_kext/.gitignore b/windows_kext/.gitignore new file mode 100644 index 000000000..c42308415 --- /dev/null +++ b/windows_kext/.gitignore @@ -0,0 +1,400 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +*/**/[Rr]elease/ +*/**/[Rr]eleases/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc +*.exe + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +pm_kext/RCa04400 +pm_kext/RCa05788 +pm_kext/RCa06452 +target +kext.sys +release/build diff --git a/windows_kext/Cargo.lock b/windows_kext/Cargo.lock new file mode 100644 index 000000000..29a6169a0 --- /dev/null +++ b/windows_kext/Cargo.lock @@ -0,0 +1,417 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "include_dir" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b56e147e6187d61e9d0f039f10e070d0c0a887e24fe0bb9ca3f29bfde62cab" +dependencies = [ + "glob", + "include_dir_impl", + "proc-macro-hack", +] + +[[package]] +name = "include_dir_impl" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0c890c85da4bab7bce4204c707396bbd3c6c8a681716a51c8814cfc2b682df" +dependencies = [ + "anyhow", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros", + "phf_shared", + "proc-macro-hack", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "portmaster-windows-kext" +version = "0.1.0" +dependencies = [ + "serde", + "serde-generate", + "serde-reflection", + "widestring", + "winapi", + "windows-sys", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-generate" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c9331265d81c61212dc75df7b0836544ed8e32dba77a522f113805ff9a948e" +dependencies = [ + "heck", + "include_dir", + "phf", + "serde", + "serde-reflection", + "textwrap", +] + +[[package]] +name = "serde-reflection" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05a5f801ac62a51a49d378fdb3884480041b99aced450b28990673e8ff99895" +dependencies = [ + "once_cell", + "serde", + "thiserror", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textwrap" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd05616119e612a8041ef58f2b578906cc2531a6069047ae092cfb86a325d835" +dependencies = [ + "smawk", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + +[[package]] +name = "winapi" +version = "0.3.7" +source = "git+https://github.com/Trantect/winapi-rs.git?branch=feature/km#981da7663da0dd9f82bcfceacdc949293a612ef0" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "git+https://github.com/Trantect/winapi-rs.git?branch=feature/km#981da7663da0dd9f82bcfceacdc949293a612ef0" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "git+https://github.com/Trantect/winapi-rs.git?branch=feature/km#981da7663da0dd9f82bcfceacdc949293a612ef0" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1eeca1c172a285ee6c2c84c341ccea837e7c01b12fbb2d0fe3c9e550ce49ec8" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10d0c968ba7f6166195e13d593af609ec2e3d24f916f081690695cf5eaffb2f" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571d8d4e62f26d4932099a9efe89660e8bd5087775a2ab5cdd8b747b811f1058" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2229ad223e178db5fbbc8bd8d3835e51e566b8474bfca58d2e6150c48bb723cd" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600956e2d840c194eedfc5d18f8242bc2e17c7775b6684488af3a9fff6fe3287" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea99ff3f8b49fb7a8e0d305e5aec485bd068c2ba691b6e277d29eaeac945868a" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1a05a1ece9a7a0d5a7ccf30ba2c33e3a61a30e042ffd247567d1de1d94120d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" diff --git a/windows_kext/PacketFlow.md b/windows_kext/PacketFlow.md new file mode 100644 index 000000000..67eab5f31 --- /dev/null +++ b/windows_kext/PacketFlow.md @@ -0,0 +1,89 @@ +# There and back again, a packets tale. + +An explanation on the complete path of the packet from entering to the exit of the kernel extension. + +## Entry + +The packet entry point depends on the packet and the internal windows filter state: + +- First packet of outbound connection -> AleAuthConnect Layer +- First packet of inbound connection -> InboundIppacket Layer + +## ALE layer + +Each defined ALE layer has a filter linked to it. This filter has a state. +When a decision is made to block or permit a connection it will be saved to the filter state. +The only way to update the decision in a filter is to clear the whole state and apply the decision for the next packet of each connection. + +### First packet + +For outgoing connections this logic fallows: + - Packet enters in one of the ALE layer + - Packet is TCP or UDP + 1. Save and absorb packet. + 2. Send an event to Portmaster. + 2. Create a cache entry. + - If Packet is not TCP/UDP forward to packet layer + +For incoming connection this logic fallow: + - Packet enter in one of the Packet layer, if packet is TCP or UDP it will be forwarded to ALE layer. From there: + 1. Save packet and absorb. + 2. Send an event to Portmaster. + 2. Create a cache entry. + 3. Wait for Portmasters decision. + - If Packet is not TCP/UDP. It will be handled only by the packet layer. + + +If more packets arrive before Portmaster returns a decision, packet will be absorbed and another event will be sent. +For Outgoing connection this will happen in ALE layer. +For Incoming connection this will happen in Packet layer. + +### Pormtaster returns a verdict for the connection + +Connection cache will be updated and the packet will be injected. +The next steps depend of the direction of the packet and the verdict + +* Permanent Verdict / Outgoing connection + - Allow / Block / Drop directly in the ALE layer. For Block and Drop packet layer will not see the rest of the packet in the connection. +* Temporary Verdict / Outgoing connection + - Always Allow - this connections are solely handled by the packet layer. (This is true only for outgoing connections) + +* Permanent or Temporary Verdict / Incoming connection + - Allow / Block / Drop directly in the ALE layer. They always go through the packet layer first no need to do anything special + +Fallowing specifics apply to the ALE layer: +1. Connections with flag `reauthorize == false` are special. When the flag is `false` that means that a applications is calling a function `connect()` or `accept()` for a connection. This is a special case because we control the result of the function, telling the application that it's allowed or not allowed to continue with the connection. Since we are making request to Portmaster we need to take longer time. This is done with pending the packet. This allows the kernel extension to pause the event and continue when it has the verdict. See `ale_callouts.rs -> save_packet()` function. +2. If packet payload is present it is from the transport layer. + + +## Packet layer + +The logic for the packet is split in two: + +### TCP or UDP protocols + +The packet layer will not process packets that miss a cache entry: +- Incoming packet: it will forward it to the ALE layer. +- Outgoing packet: this is treated as invalid state since ALE should be the entry for the packets. If it happens the packet layer will create a request to Portmaster for it. + +For packets with a cache entry: +- Permanent Verdict: apply the verdict. +- Redirect Verdict: copy the packet, modify and inject. Drop the original packet. +- Temporary verdict: send request to Portmaster. + +After portmaster returns the verdict for the packet. If its allowed it will be modified (if needed) and injected everything else will be dropped. +The packet layer will permit all injected packets. + +### Not TCP or UDP protocols -> ICMP, IGMP ... + +Does packets are treated as with temporary verdict. There will be no cache entry for them. +Every packet will be send to Portmaster for a decision and re-injected if allowed. + +## Connection Cache + +It holds information for all TCP and UDP connections. Local and destination ip addresses and ports, verdict, protocol, process id +It also holds last active time and end time. + +Cache entry is removed automatically 1 minute after an end state has been set or after 10 minutes of inactivity. + +End stat is set by Endpoint layers or Resource release layers. \ No newline at end of file diff --git a/windows_kext/PortmasterKext64.inf b/windows_kext/PortmasterKext64.inf new file mode 100644 index 000000000..1e3c5aae1 --- /dev/null +++ b/windows_kext/PortmasterKext64.inf @@ -0,0 +1,67 @@ +;/*++ +; +;Copyright (c) Safing ICS Technologies GmbH. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; +;--*/ + +[Version] +Signature = "$Windows NT$" +Class = WFPCALLOUTS +ClassGuid = {57465043-616C-6C6F-7574-5F636C617373} +Provider = %Provider% +CatalogFile = PortmasterKext64.Cat +DriverVer = 01/01/2019,1.0.11.0 + +[SourceDisksNames] +1 = %DiskName% + +[SourceDisksFiles] +PortmasterKext64.sys = 1 + +[DestinationDirs] +DefaultDestDir = 12 ; %windir%\system32\drivers +PortmasterKext.DriverFiles = 12 ; %windir%\system32\drivers + +[DefaultInstall] +OptionDesc = %Description% +CopyFiles = PortmasterKext.DriverFiles + +[DefaultInstall.Services] +AddService = %ServiceName%,,PortmasterKext.Service + +[DefaultUninstall] +DelFiles = PortmasterKext.DriverFiles + +[DefaultUninstall.Services] +DelService = PortmasterKext,0x200 ; SPSVCINST_STOPSERVICE + +[PortmasterKext.DriverFiles] +PortmasterKext64.sys,,,0x00000040 ; COPYFLG_OVERWRITE_OLDER_ONLY + +[PortmasterKext.Service] +DisplayName = %ServiceName% +Description = %ServiceDesc% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 0 ; SERVICE_BOOT_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\PortmasterKext64.sys + +[Strings] +Provider = "Safing ICS Technologies GmbH" +DiskName = "PortmasterKext Installation Disk" +Description = "PortmasterKext Driver" +ServiceName = "PortmasterKext" +ServiceDesc = "PortmasterKext Driver" diff --git a/windows_kext/README.md b/windows_kext/README.md new file mode 100644 index 000000000..f77de3479 --- /dev/null +++ b/windows_kext/README.md @@ -0,0 +1,71 @@ +# Portmaster Windows kext +Implementation of Safing's Portmaster Windows kernel extension in Rust. + +### Documentation + +- [Driver](driver/README.md) -> entry point. +- [WDK](wdk/README.md) -> Windows Driver Kit interface. +- [Packet Path](PacketDoc.md) -> Detiled documentation of what happens to a packet when it enters the kernel extension. +- [Release](release/README.md) -> Guide how to do a release build + +### Building + +The Windows Portmaster Kernel Extension is currently only developed and tested for the amd64 (64-bit) architecture. + +__Prerequesites:__ + +- Visual Studio 2022 + - Install C++ and Windows 11 SDK (22H2) components + - Add `link.exe` and `signtool` in the PATH +- Rust + - https://www.rust-lang.org/tools/install +- Cargo make(optional) + - https://github.com/sagiegurari/cargo-make + +__Setup Test Signing:__ + +In order to test the driver on your machine, you will have to test sign it (starting with Windows 10). + + +Create a new certificate for test signing: + + :: Open a *x64 Free Build Environment* console as Administrator. + + :: Run the MakeCert.exe tool to create a test certificate: + MakeCert -r -pe -ss PrivateCertStore -n "CN=DriverCertificate" DriverCertificate.cer + + :: Install the test certificate with CertMgr.exe: + CertMgr /add DriverCertificate.cer /s /r localMachine root + + +Enable Test Signing on the dev machine: + + :: Before you can load test-signed drivers, you must enable Windows test mode. To do this, run this command: + Bcdedit.exe -set TESTSIGNING ON + :: Then, restart Windows. For more information, see The TESTSIGNING Boot Configuration Option. + + +__Build driver:__ + +``` +cd driver +cargo build +``` +> Build also works on linux + +__Link and sign:__ +On a windows machine copy `driver.lib` form the project target directory (`driver/target/x86_64-pc-windows-msvc/debug/driver.lib`) in the same folder as `link.bat`. +Run `link.bat`. + +`driver.sys` should appear in the folder. Load and use the driver. + +### Test +- Install go + - https://go.dev/dl/ + +``` +cd kext_tester +go run . +``` + +> make sure the hardcoded path in main.go is pointing to the correct `.sys` file diff --git a/windows_kext/c_helper/ARM64/c_helper.lib b/windows_kext/c_helper/ARM64/c_helper.lib new file mode 100644 index 000000000..c9b45ef16 Binary files /dev/null and b/windows_kext/c_helper/ARM64/c_helper.lib differ diff --git a/windows_kext/c_helper/c_helper.filters b/windows_kext/c_helper/c_helper.filters new file mode 100644 index 000000000..29fc7ed4e --- /dev/null +++ b/windows_kext/c_helper/c_helper.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/windows_kext/c_helper/c_helper.sln b/windows_kext/c_helper/c_helper.sln new file mode 100644 index 000000000..134d688a7 --- /dev/null +++ b/windows_kext/c_helper/c_helper.sln @@ -0,0 +1,51 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33502.453 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "c_helper", "c_helper.vcxproj", "{39A5E911-A716-4708-8B88-3895183C6372}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM.ActiveCfg = Debug|ARM + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM.Build.0 = Debug|ARM + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM.Deploy.0 = Debug|ARM + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM64.Build.0 = Debug|ARM64 + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|x64.ActiveCfg = Debug|x64 + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|x64.Build.0 = Debug|x64 + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|x64.Deploy.0 = Debug|x64 + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|x86.ActiveCfg = Debug|Win32 + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|x86.Build.0 = Debug|Win32 + {39A5E911-A716-4708-8B88-3895183C6372}.Debug|x86.Deploy.0 = Debug|Win32 + {39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM.ActiveCfg = Release|ARM + {39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM.Build.0 = Release|ARM + {39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM.Deploy.0 = Release|ARM + {39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM64.ActiveCfg = Release|ARM64 + {39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM64.Build.0 = Release|ARM64 + {39A5E911-A716-4708-8B88-3895183C6372}.Release|ARM64.Deploy.0 = Release|ARM64 + {39A5E911-A716-4708-8B88-3895183C6372}.Release|x64.ActiveCfg = Release|x64 + {39A5E911-A716-4708-8B88-3895183C6372}.Release|x64.Build.0 = Release|x64 + {39A5E911-A716-4708-8B88-3895183C6372}.Release|x64.Deploy.0 = Release|x64 + {39A5E911-A716-4708-8B88-3895183C6372}.Release|x86.ActiveCfg = Release|Win32 + {39A5E911-A716-4708-8B88-3895183C6372}.Release|x86.Build.0 = Release|Win32 + {39A5E911-A716-4708-8B88-3895183C6372}.Release|x86.Deploy.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {91E52350-EBB9-4B0F-9C28-61C0BBAEDC6A} + EndGlobalSection +EndGlobal diff --git a/windows_kext/c_helper/c_helper.vcxproj b/windows_kext/c_helper/c_helper.vcxproj new file mode 100644 index 000000000..250c31093 --- /dev/null +++ b/windows_kext/c_helper/c_helper.vcxproj @@ -0,0 +1,188 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + + + + {39A5E911-A716-4708-8B88-3895183C6372} + {0a049372-4c4d-4ea0-a64e-dc6ad88ceca1} + v4.5 + 12.0 + Debug + Win32 + c_helper + KMDF + $(LatestTargetPlatformVersion) + c_helper + + + + Windows10 + true + WindowsKernelModeDriver10.0 + StaticLibrary + Universal + Unicode + + + Windows10 + false + WindowsKernelModeDriver10.0 + StaticLibrary + Universal + Unicode + + + Windows10 + true + WindowsKernelModeDriver10.0 + StaticLibrary + Universal + Unicode + false + + + Windows10 + false + WindowsKernelModeDriver10.0 + StaticLibrary + Universal + Unicode + false + + + Windows10 + true + WindowsKernelModeDriver10.0 + StaticLibrary + Unicode + + + Windows10 + false + WindowsKernelModeDriver10.0 + StaticLibrary + Universal + Unicode + + + Windows10 + true + WindowsKernelModeDriver10.0 + StaticLibrary + Universal + Unicode + + + Windows10 + false + WindowsKernelModeDriver10.0 + StaticLibrary + Universal + Unicode + false + + + + + + + + + + $(SolutionDir)$(Platform) + + + $(SolutionDir)$(Platform) + + + $(SolutionDir)$(Platform) + $(Platform)\$(ConfigurationName)\ + $(TargetName.Replace(' ','')) + + + + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + + + + + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + + + + + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + false + + + + + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + false + + + + + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + + + + + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + + + + + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + + + + + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + Default + Default + + + + + + \ No newline at end of file diff --git a/windows_kext/c_helper/helper.c b/windows_kext/c_helper/helper.c new file mode 100644 index 000000000..93e893e2a --- /dev/null +++ b/windows_kext/c_helper/helper.c @@ -0,0 +1,89 @@ + +/* + * Name: helper.c + */ + +#include +#include + +#define NDIS640 1 // Windows 8 and Windows Server 2012 + +#include "Ntifs.h" +#include // Windows Driver Development Kit +#include // Windows Driver Foundation + +#pragma warning(push) +#pragma warning(disable: 4201) // Disable "Nameless struct/union" compiler warning for fwpsk.h only! +#include // Functions and enumerated types used to implement callouts in kernel mode +#pragma warning(pop) // Re-enable "Nameless struct/union" compiler warning + +#include // Functions used for managing IKE and AuthIP main mode (MM) policy and security associations +#include // Mappings of OS specific function versions (i.e. fn's that end in 0 or 1) +#include // Used to define GUID's +#include // Used to define GUID's +#include "devguid.h" +#include +#include +#include + +EVT_WDF_DRIVER_UNLOAD emptyEventUnload; + +NTSTATUS pm_InitDriverObject(DRIVER_OBJECT * driverObject, UNICODE_STRING * registryPath, WDFDRIVER * driver, WDFDEVICE * device, wchar_t *win_device_name, wchar_t *dos_device_name, WDF_OBJECT_ATTRIBUTES * objectAttributes, void (*wdfEventUnload)(WDFDRIVER)) { + UNICODE_STRING deviceName = { 0 }; + RtlInitUnicodeString(&deviceName, win_device_name); + + UNICODE_STRING deviceSymlink = { 0 }; + RtlInitUnicodeString(&deviceSymlink, dos_device_name); + + // Create a WDFDRIVER for this driver + WDF_DRIVER_CONFIG config = { 0 }; + WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK); + config.DriverInitFlags = WdfDriverInitNonPnpDriver; + config.EvtDriverUnload = wdfEventUnload; // <-- Necessary for this driver to unload correctly + NTSTATUS status = WdfDriverCreate(driverObject, registryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, driver); + if (!NT_SUCCESS(status)) { + return status; + } + + // Create a WDFDEVICE for this driver + PWDFDEVICE_INIT deviceInit = WdfControlDeviceInitAllocate(*driver, &SDDL_DEVOBJ_SYS_ALL_ADM_ALL); // only admins and kernel can access device + if (!deviceInit) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Configure the WDFDEVICE_INIT with a name to allow for access from user mode + WdfDeviceInitSetDeviceType(deviceInit, FILE_DEVICE_NETWORK); + WdfDeviceInitSetCharacteristics(deviceInit, FILE_DEVICE_SECURE_OPEN, false); + (void) WdfDeviceInitAssignName(deviceInit, &deviceName); + (void) WdfPdoInitAssignRawDevice(deviceInit, &GUID_DEVCLASS_NET); + WdfDeviceInitSetDeviceClass(deviceInit, &GUID_DEVCLASS_NET); + + status = WdfDeviceCreate(&deviceInit, objectAttributes, device); + if (!NT_SUCCESS(status)) { + WdfDeviceInitFree(deviceInit); + return status; + } + status = WdfDeviceCreateSymbolicLink(*device, &deviceSymlink); + if (!NT_SUCCESS(status)) { + return status; + } + + // The system will not send I/O requests or Windows Management Instrumentation (WMI) requests to a control device object unless the driver has called WdfControlFinishInitializing. + WdfControlFinishInitializing(*device); + + return STATUS_SUCCESS; +} + +void* pm_WdfObjectGetTypedContextWorker(WDFOBJECT wdfObject, PCWDF_OBJECT_CONTEXT_TYPE_INFO typeInfo) { + return WdfObjectGetTypedContextWorker(wdfObject, typeInfo->UniqueType); +} + +DEVICE_OBJECT* pm_GetDeviceObject(WDFDEVICE device) { + return WdfDeviceWdmGetDeviceObject(device); +} + +UINT64 pm_QuerySystemTime() { + UINT64 timestamp = 0; + KeQuerySystemTime(×tamp); + return timestamp; +} \ No newline at end of file diff --git a/windows_kext/c_helper/x64/c_helper.lib b/windows_kext/c_helper/x64/c_helper.lib new file mode 100644 index 000000000..249648f77 Binary files /dev/null and b/windows_kext/c_helper/x64/c_helper.lib differ diff --git a/windows_kext/driver/.cargo/config.toml b/windows_kext/driver/.cargo/config.toml new file mode 100644 index 000000000..0a58c1483 --- /dev/null +++ b/windows_kext/driver/.cargo/config.toml @@ -0,0 +1,3 @@ +[build] +target = "x86_64-pc-windows-msvc" +rustflags = ["-C", "panic=abort"] diff --git a/windows_kext/driver/Cargo.lock b/windows_kext/driver/Cargo.lock new file mode 100644 index 000000000..0374a4c7f --- /dev/null +++ b/windows_kext/driver/Cargo.lock @@ -0,0 +1,415 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "driver" +version = "0.0.0" +dependencies = [ + "hashbrown", + "num", + "num-derive", + "num-traits", + "protocol", + "smoltcp", + "wdk", + "windows-sys", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + +[[package]] +name = "ntstatus" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ea8ea6a9a8cbe8fefe99b632bd45ec4b41b0bf234e4d740c516372922fb180" +dependencies = [ + "num_enum", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "protocol" +version = "0.0.0" +dependencies = [ + "num", + "num-derive", + "num-traits", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + +[[package]] +name = "smoltcp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2e3a36ac8fea7b94e666dfa3871063d6e0a5c9d5d4fec9a1a6b7b6760f0229" +dependencies = [ + "bitflags", + "byteorder", + "cfg-if", + "heapless", + "managed", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wdk" +version = "0.0.0" +dependencies = [ + "ntstatus", + "widestring", + "windows-sys", +] + +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "zerocopy" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/windows_kext/driver/Cargo.toml b/windows_kext/driver/Cargo.toml new file mode 100644 index 000000000..09ca639dc --- /dev/null +++ b/windows_kext/driver/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "driver" +version = "0.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "driver" +path = "src/lib.rs" +crate-type = ["staticlib"] + +[dependencies] +wdk = { path = "../wdk" } +protocol = { path = "../protocol" } +num = { version = "0.4", default-features = false } +num-derive = { version = "0.4", default-features = false } +num-traits = { version = "0.2", default-features = false } +smoltcp = { version = "0.10", default-features = false, features = ["proto-ipv4", "proto-ipv6"] } +hashbrown = { version = "0.14.3", default-features = false, features = ["ahash"]} + +# WARNING: Do not update. The version was choosen for a reason. See wdk/README.md for more detiels. +[dependencies.windows-sys] +git = "https://github.com/microsoft/windows-rs" +rev = "41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" +features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_SystemServices", "Win32_Foundation", "Win32_Security", "Win32_System_IO", "Win32_System_Kernel", "Win32_System_Power", "Win32_System_WindowsProgramming", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_NetworkManagement_WindowsFilteringPlatform"] diff --git a/windows_kext/driver/Makefile.toml b/windows_kext/driver/Makefile.toml new file mode 100644 index 000000000..ee662237b --- /dev/null +++ b/windows_kext/driver/Makefile.toml @@ -0,0 +1,18 @@ +[env.development] +TARGET_PATH = "target/x86_64-pc-windows-msvc/debug" + +[env.production] +TARGET_PATH = "target/x86_64-pc-windows-msvc/release" +BUILD_FLAGS = "--release" + +[tasks.build-driver] +script = [ + "cargo build $BUILD_FLAGS", +] + +[tasks.upload] +dependencies = ["build-driver"] +script = [ + "scp $TARGET_PATH/driver.lib windows:'C:/Dev/'", +] + diff --git a/windows_kext/driver/README.md b/windows_kext/driver/README.md new file mode 100644 index 000000000..63c8d5814 --- /dev/null +++ b/windows_kext/driver/README.md @@ -0,0 +1,70 @@ +# Driver + +This is the entry point of the Kernel extension. + +## Quick overview + +`entry.rs`: +This file contains the entry point and calling all the needed initialization code. +- Setting up the driver object +- Allocating global state + +`fn driver_entry()` -> entry pointer of the driver. + +`device.rs`: +Holds the global state of the driver. +Initialization: Setting up global state, Filter engine and callouts. + +Portmaster communication: +The communication happens concurrently with the File read/write API. +That means when Pormtaster sends a command the kernel extension will start to process it and queue the result in the `IOQueue`. + +`fn read()` -> called on read request from Portmaster +- `IOQueue` holds all the events queued for Portmaster. + +Blocks until there is a element that can be poped or shutdown request is sent from Portmaster. +If there is more then one event in the queue it will write as much as it can in the supplied buffer. + +`fn write()` -> called on write request from Portmaster. +Used when Portmaster wants to send a command to kernel extension. +Verdict Response, GetLogs ... (see `protocol` for list of all the commands) + + +## Callouts + +`callouts.rs` -> defines the list of all used callouts in the kernel extension. + +ALE (Application Layer Enforcement) +https://learn.microsoft.com/en-us/windows/win32/fwp/application-layer-enforcement--ale- + +### ALE Auth + +Connection level filtering. It will make a decision based on the first packet of a connection. Works together with the packet layer to provide firewall functionality. +- **AleLayerOutboundV4** +- **AleLayerInboundV4** +- **AleLayerOutboundV6** +- **AleLayerInboundV6** + + +### ALE endpoint / resource assignment and release + +Used to listen for event when connection has ended. Does no filtering. +- **AleEndpointClosureV4, AleEndpointClosureV6** - Triggered when connection to an endpoint has ended. Usually only TCP is triggered. The triggered connection will be marked for deletion. + +- **AleResourceAssignmentV4, AleResourceAssignmentV6** -> only for logging (not used) +- AleResourceReleaseV4, AleResourceReleaseV6 -> Triggered when port is release from an application. The triggered connection/s will be marked for deletion. + +### Stream layer + +This layer works on the application OSI layer. Meaning that only the payload of the TCP/UDP connection will be available. +It is used for bandwidth monitoring. This functionality is completely separate from the rest of the system so it can be disabled or enabled without affect anything else. + +- **StreamLayerV4, StreamLayerV6** -> For TCP connections +- **DatagramDataLayerV4, DatagramDataLayerV6** -> For UDP connections + + +### Packet layer + +This layer handled each packet on the network OSI layer. Works together with ALE Auth layer to provide firewall functionality. +- **IPPacketOutboundV4, IPPacketOutboundV6** -> Triggered on every outbound packet. +- **IPPacketInboundV4, IPPacketInboundV6** -> Triggered on every inbound packet. diff --git a/windows_kext/driver/rust-toolchain b/windows_kext/driver/rust-toolchain new file mode 100644 index 000000000..2bf5ad044 --- /dev/null +++ b/windows_kext/driver/rust-toolchain @@ -0,0 +1 @@ +stable diff --git a/windows_kext/driver/src/ale_callouts.rs b/windows_kext/driver/src/ale_callouts.rs new file mode 100644 index 000000000..c45b7f4ca --- /dev/null +++ b/windows_kext/driver/src/ale_callouts.rs @@ -0,0 +1,537 @@ +use crate::connection::{Connection, ConnectionV4, ConnectionV6, Direction, Verdict}; +use crate::connection_map::Key; +use crate::device::{Device, Packet}; + +use crate::info; +use smoltcp::wire::{ + IpAddress, IpProtocol, Ipv4Address, Ipv6Address, IPV4_HEADER_LEN, IPV6_HEADER_LEN, +}; +use wdk::filter_engine::callout_data::CalloutData; +use wdk::filter_engine::layer::{ + self, FieldsAleAuthConnectV4, FieldsAleAuthConnectV6, FieldsAleAuthRecvAcceptV4, + FieldsAleAuthRecvAcceptV6, ValueType, +}; +use wdk::filter_engine::net_buffer::NetBufferList; +use wdk::filter_engine::packet::{Injector, TransportPacketList}; + +// ALE Layers + +#[derive(Debug)] +#[allow(dead_code)] +struct AleLayerData { + is_ipv6: bool, + reauthorize: bool, + process_id: u64, + protocol: IpProtocol, + direction: Direction, + local_ip: IpAddress, + local_port: u16, + remote_ip: IpAddress, + remote_port: u16, + interface_index: u32, + sub_interface_index: u32, +} + +impl AleLayerData { + fn as_key(&self) -> Key { + let mut local_port = 0; + let mut remote_port = 0; + match self.protocol { + IpProtocol::Tcp | IpProtocol::Udp => { + local_port = self.local_port; + remote_port = self.remote_port; + } + _ => {} + } + + Key { + protocol: self.protocol, + local_address: self.local_ip, + local_port, + remote_address: self.remote_ip, + remote_port, + } + } +} + +fn get_protocol(data: &CalloutData, index: usize) -> IpProtocol { + IpProtocol::from(data.get_value_u8(index)) +} + +fn get_ipv4_address(data: &CalloutData, index: usize) -> IpAddress { + IpAddress::Ipv4(Ipv4Address::from_bytes( + &data.get_value_u32(index).to_be_bytes(), + )) +} + +fn get_ipv6_address(data: &CalloutData, index: usize) -> IpAddress { + IpAddress::Ipv6(Ipv6Address::from_bytes(data.get_value_byte_array16(index))) +} + +pub fn ale_layer_connect_v4(data: CalloutData) { + type Fields = FieldsAleAuthConnectV4; + let ale_data = AleLayerData { + is_ipv6: false, + reauthorize: data.is_reauthorize(Fields::Flags as usize), + process_id: data.get_process_id().unwrap_or(0), + protocol: get_protocol(&data, Fields::IpProtocol as usize), + direction: Direction::Outbound, + local_ip: get_ipv4_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_ip: get_ipv4_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + interface_index: 0, + sub_interface_index: 0, + }; + + ale_layer_auth(data, ale_data); +} + +pub fn ale_layer_accept_v4(data: CalloutData) { + type Fields = FieldsAleAuthRecvAcceptV4; + let ale_data = AleLayerData { + is_ipv6: false, + reauthorize: data.is_reauthorize(Fields::Flags as usize), + process_id: data.get_process_id().unwrap_or(0), + protocol: get_protocol(&data, Fields::IpProtocol as usize), + direction: Direction::Inbound, + local_ip: get_ipv4_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_ip: get_ipv4_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + interface_index: data.get_value_u32(Fields::InterfaceIndex as usize), + sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize), + }; + ale_layer_auth(data, ale_data); +} + +pub fn ale_layer_connect_v6(data: CalloutData) { + type Fields = FieldsAleAuthConnectV6; + + let ale_data = AleLayerData { + is_ipv6: true, + reauthorize: data.is_reauthorize(Fields::Flags as usize), + process_id: data.get_process_id().unwrap_or(0), + protocol: get_protocol(&data, Fields::IpProtocol as usize), + direction: Direction::Outbound, + local_ip: get_ipv6_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_ip: get_ipv6_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + interface_index: data.get_value_u32(Fields::InterfaceIndex as usize), + sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize), + }; + + ale_layer_auth(data, ale_data); +} + +pub fn ale_layer_accept_v6(data: CalloutData) { + type Fields = FieldsAleAuthRecvAcceptV6; + let ale_data = AleLayerData { + is_ipv6: true, + reauthorize: data.is_reauthorize(Fields::Flags as usize), + process_id: data.get_process_id().unwrap_or(0), + protocol: get_protocol(&data, Fields::IpProtocol as usize), + direction: Direction::Inbound, + local_ip: get_ipv6_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_ip: get_ipv6_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + interface_index: data.get_value_u32(Fields::InterfaceIndex as usize), + sub_interface_index: data.get_value_u32(Fields::SubInterfaceIndex as usize), + }; + ale_layer_auth(data, ale_data); +} + +fn ale_layer_auth(mut data: CalloutData, ale_data: AleLayerData) { + let Some(device) = crate::entry::get_device() else { + return; + }; + + match ale_data.protocol { + IpProtocol::Tcp | IpProtocol::Udp => { + // Only TCP and UDP make sense to be supported in the ALE layer. + // Everything else is not associated with a connection and will be handled in the packet layer. + } + _ => { + // Outbound: Will be handled by packet layer next. + // Inbound: Was already handled by the packet layer. + data.action_permit(); + return; + } + } + + let key = ale_data.as_key(); + + // Check if connection is already in cache. + let verdict = if ale_data.is_ipv6 { + device + .connection_cache + .read_connection_v6(&key, |conn| -> Option { + // Function is behind spin lock, just copy and return. + Some(conn.verdict) + }) + } else { + device + .connection_cache + .read_connection_v4(&ale_data.as_key(), |conn| -> Option { + // Function is behind spin lock, just copy and return. + Some(conn.verdict) + }) + }; + + // Connection already in cache. + if let Some(verdict) = verdict { + crate::dbg!("processing existing connection: {} {}", key, verdict); + match verdict { + // No verdict yet + Verdict::Undecided => { + crate::dbg!("saving packet: {}", key); + // Connection is already pended. Save packet and wait for verdict. + match save_packet(device, &mut data, &ale_data, false) { + Ok(packet) => { + let info = device.packet_cache.push( + (key, packet), + ale_data.process_id, + ale_data.direction, + true, + ); + if let Some(info) = info { + let _ = device.event_queue.push(info); + } + } + Err(err) => { + crate::err!("failed to pend packet: {}", err); + } + }; + data.block_and_absorb(); + } + // There is a verdict + Verdict::PermanentAccept + | Verdict::Accept + | Verdict::RedirectNameServer + | Verdict::RedirectTunnel => { + // Continue to packet layer. + data.action_permit(); + } + Verdict::PermanentBlock | Verdict::Undeterminable | Verdict::Failed => { + // Packet layer will not see this connection. + crate::dbg!("permanent block {}", key); + data.action_block(); + } + Verdict::PermanentDrop => { + // Packet layer will not see this connection. + crate::dbg!("permanent drop {}", key); + data.block_and_absorb(); + } + Verdict::Block => { + if let Direction::Outbound = ale_data.direction { + // Handled by packet layer. + data.action_permit(); + } else { + // packet layer will still see the packets. + data.action_block(); + } + } + Verdict::Drop => { + if let Direction::Outbound = ale_data.direction { + // Handled by packet layer. + data.action_permit(); + } else { + // packet layer will still see the packets. + data.block_and_absorb(); + } + } + } + } else { + crate::dbg!("pending connection: {} {}", key, ale_data.direction); + // Only first packet of a connection can be pended: reauthorize == false + let can_pend_connection = !ale_data.reauthorize; + match save_packet(device, &mut data, &ale_data, can_pend_connection) { + Ok(packet) => { + let info = device.packet_cache.push( + (key, packet), + ale_data.process_id, + ale_data.direction, + true, + ); + if let Some(info) = info { + let _ = device.event_queue.push(info); + } + } + Err(err) => { + crate::err!("failed to pend packet: {}", err); + } + }; + + // Connection is not in cache, add it. + crate::dbg!("adding connection: {} PID: {}", key, ale_data.process_id); + if ale_data.is_ipv6 { + let conn = + ConnectionV6::from_key(&key, ale_data.process_id, ale_data.direction).unwrap(); + device.connection_cache.add_connection_v6(conn); + } else { + let conn = + ConnectionV4::from_key(&key, ale_data.process_id, ale_data.direction).unwrap(); + device.connection_cache.add_connection_v4(conn); + } + + // Drop packet. It will be re-injected after Portmaster returns a verdict. + data.block_and_absorb(); + } +} + +fn save_packet( + device: &Device, + callout_data: &mut CalloutData, + ale_data: &AleLayerData, + pend: bool, +) -> Result { + let mut packet_list = None; + let mut save_packet_list = true; + match ale_data.protocol { + IpProtocol::Tcp => { + if let Direction::Outbound = ale_data.direction { + // Only time a packet data is missing is during connect state of outbound TCP connection. + // Don't save packet list only if connection is outbound, reauthorize is false and the protocol is TCP. + save_packet_list = ale_data.reauthorize; + } + } + _ => {} + }; + if save_packet_list { + packet_list = create_packet_list(device, callout_data, ale_data); + } + if pend && matches!(ale_data.protocol, IpProtocol::Tcp | IpProtocol::Udp) { + match callout_data.pend_operation(packet_list) { + Ok(classify_defer) => Ok(Packet::AleLayer(classify_defer)), + Err(err) => Err(alloc::format!("failed to defer connection: {}", err)), + } + } else { + Ok(Packet::AleLayer(callout_data.pend_filter_rest(packet_list))) + } +} + +fn create_packet_list( + device: &Device, + callout_data: &mut CalloutData, + ale_data: &AleLayerData, +) -> Option { + let mut nbl = NetBufferList::new(callout_data.get_layer_data() as _); + let mut inbound = false; + if let Direction::Inbound = ale_data.direction { + if ale_data.is_ipv6 { + nbl.retreat(IPV6_HEADER_LEN as u32, true); + } else { + nbl.retreat(IPV4_HEADER_LEN as u32, true); + } + inbound = true; + } + + let address: &[u8] = match &ale_data.remote_ip { + IpAddress::Ipv4(address) => &address.0, + IpAddress::Ipv6(address) => &address.0, + }; + if let Ok(clone) = nbl.clone(&device.network_allocator) { + return Some(Injector::from_ale_callout( + ale_data.is_ipv6, + callout_data, + clone, + address, + inbound, + ale_data.interface_index, + ale_data.sub_interface_index, + )); + } + return None; +} + +pub fn endpoint_closure_v4(data: CalloutData) { + type Fields = layer::FieldsAleEndpointClosureV4; + let Some(device) = crate::entry::get_device() else { + return; + }; + let ip_address_type = data.get_value_type(Fields::IpLocalAddress as usize); + if let ValueType::FwpUint32 = ip_address_type { + let key = Key { + protocol: get_protocol(&data, Fields::IpProtocol as usize), + local_address: get_ipv4_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_address: get_ipv4_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + }; + + let conn = device.connection_cache.end_connection_v4(key); + if let Some(conn) = conn { + let info = protocol::info::connection_end_event_v4_info( + data.get_process_id().unwrap_or(0), + conn.get_direction() as u8, + u8::from(get_protocol(&data, Fields::IpProtocol as usize)), + conn.local_address.0, + conn.remote_address.0, + conn.local_port, + conn.remote_port, + ); + let _ = device.event_queue.push(info); + } + } else { + // Invalid ip address type. Just ignore the error. + // err!( + // device.logger, + // "unknown ipv4 address type: {:?}", + // ip_address_type + // ); + } +} + +pub fn endpoint_closure_v6(data: CalloutData) { + type Fields = layer::FieldsAleEndpointClosureV6; + let Some(device) = crate::entry::get_device() else { + return; + }; + let local_ip_address_type = data.get_value_type(Fields::IpLocalAddress as usize); + let remote_ip_address_type = data.get_value_type(Fields::IpRemoteAddress as usize); + + if let ValueType::FwpByteArray16Type = local_ip_address_type { + if let ValueType::FwpByteArray16Type = remote_ip_address_type { + let key = Key { + protocol: get_protocol(&data, Fields::IpProtocol as usize), + local_address: get_ipv6_address(&data, Fields::IpLocalAddress as usize), + local_port: data.get_value_u16(Fields::IpLocalPort as usize), + remote_address: get_ipv6_address(&data, Fields::IpRemoteAddress as usize), + remote_port: data.get_value_u16(Fields::IpRemotePort as usize), + }; + + let conn = device.connection_cache.end_connection_v6(key); + if let Some(conn) = conn { + let info = protocol::info::connection_end_event_v6_info( + data.get_process_id().unwrap_or(0), + conn.get_direction() as u8, + u8::from(get_protocol(&data, Fields::IpProtocol as usize)), + conn.local_address.0, + conn.remote_address.0, + conn.local_port, + conn.remote_port, + ); + let _ = device.event_queue.push(info); + } + } + } +} + +pub fn ale_resource_monitor(data: CalloutData) { + let Some(device) = crate::entry::get_device() else { + return; + }; + match data.layer { + layer::Layer::AleResourceAssignmentV4Discard => { + type Fields = layer::FieldsAleResourceAssignmentV4; + if let Some(conns) = device.connection_cache.end_all_on_port_v4(( + get_protocol(&data, Fields::IpProtocol as usize), + data.get_value_u16(Fields::IpLocalPort as usize), + )) { + let process_id = data.get_process_id().unwrap_or(0); + info!( + "Port {}/{} Ipv4 assign request discarded pid={}", + data.get_value_u16(Fields::IpLocalPort as usize), + get_protocol(&data, Fields::IpProtocol as usize), + process_id, + ); + for conn in conns { + let info = protocol::info::connection_end_event_v4_info( + process_id, + conn.get_direction() as u8, + data.get_value_u8(Fields::IpProtocol as usize), + conn.local_address.0, + conn.remote_address.0, + conn.local_port, + conn.remote_port, + ); + let _ = device.event_queue.push(info); + } + } + } + layer::Layer::AleResourceAssignmentV6Discard => { + type Fields = layer::FieldsAleResourceAssignmentV6; + if let Some(conns) = device.connection_cache.end_all_on_port_v6(( + get_protocol(&data, Fields::IpProtocol as usize), + data.get_value_u16(Fields::IpLocalPort as usize), + )) { + let process_id = data.get_process_id().unwrap_or(0); + info!( + "Port {}/{} Ipv6 assign request discarded pid={}", + data.get_value_u16(Fields::IpLocalPort as usize), + get_protocol(&data, Fields::IpProtocol as usize), + process_id, + ); + for conn in conns { + let info = protocol::info::connection_end_event_v6_info( + process_id, + conn.get_direction() as u8, + data.get_value_u8(Fields::IpProtocol as usize), + conn.local_address.0, + conn.remote_address.0, + conn.local_port, + conn.remote_port, + ); + let _ = device.event_queue.push(info); + } + } + } + layer::Layer::AleResourceReleaseV4 => { + type Fields = layer::FieldsAleResourceReleaseV4; + if let Some(conns) = device.connection_cache.end_all_on_port_v4(( + get_protocol(&data, Fields::IpProtocol as usize), + data.get_value_u16(Fields::IpLocalPort as usize), + )) { + let process_id = data.get_process_id().unwrap_or(0); + info!( + "Port {}/{} released pid={}", + data.get_value_u16(Fields::IpLocalPort as usize), + get_protocol(&data, Fields::IpProtocol as usize), + process_id, + ); + for conn in conns { + let info = protocol::info::connection_end_event_v4_info( + process_id, + conn.get_direction() as u8, + data.get_value_u8(Fields::IpProtocol as usize), + conn.local_address.0, + conn.remote_address.0, + conn.local_port, + conn.remote_port, + ); + let _ = device.event_queue.push(info); + } + } + } + layer::Layer::AleResourceReleaseV6 => { + type Fields = layer::FieldsAleResourceReleaseV6; + if let Some(conns) = device.connection_cache.end_all_on_port_v6(( + get_protocol(&data, Fields::IpProtocol as usize), + data.get_value_u16(Fields::IpLocalPort as usize), + )) { + let process_id = data.get_process_id().unwrap_or(0); + info!( + "Port {}/{} released pid={}", + data.get_value_u16(Fields::IpLocalPort as usize), + get_protocol(&data, Fields::IpProtocol as usize), + process_id, + ); + for conn in conns { + let info = protocol::info::connection_end_event_v6_info( + process_id, + conn.get_direction() as u8, + data.get_value_u8(Fields::IpProtocol as usize), + conn.local_address.0, + conn.remote_address.0, + conn.local_port, + conn.remote_port, + ); + let _ = device.event_queue.push(info); + } + } + } + _ => {} + } +} diff --git a/windows_kext/driver/src/array_holder.rs b/windows_kext/driver/src/array_holder.rs new file mode 100644 index 000000000..077a24957 --- /dev/null +++ b/windows_kext/driver/src/array_holder.rs @@ -0,0 +1,25 @@ +use core::cell::RefCell; + +use alloc::vec::Vec; + +pub struct ArrayHolder(RefCell>>); +unsafe impl Sync for ArrayHolder {} + +impl ArrayHolder { + pub const fn default() -> Self { + Self(RefCell::new(None)) + } + + pub fn save(&self, data: &[u8]) { + if let Ok(mut opt) = self.0.try_borrow_mut() { + opt.replace(data.to_vec()); + } + } + + pub fn load(&self) -> Option> { + if let Ok(mut opt) = self.0.try_borrow_mut() { + return opt.take(); + } + None + } +} diff --git a/windows_kext/driver/src/bandwidth.rs b/windows_kext/driver/src/bandwidth.rs new file mode 100644 index 000000000..4fb487867 --- /dev/null +++ b/windows_kext/driver/src/bandwidth.rs @@ -0,0 +1,293 @@ +use protocol::info::{BandwidthValueV4, BandwidthValueV6, Info}; +use smoltcp::wire::{IpProtocol, Ipv4Address, Ipv6Address}; +use wdk::rw_spin_lock::RwSpinLock; + +use crate::driver_hashmap::DeviceHashMap; + +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] +pub struct Key
+where + Address: Eq + PartialEq, +{ + pub local_ip: Address, + pub local_port: u16, + pub remote_ip: Address, + pub remote_port: u16, +} + +struct Value { + received_bytes: usize, + transmitted_bytes: usize, +} + +enum Direction { + Tx(usize), + Rx(usize), +} +pub struct Bandwidth { + stats_tcp_v4: DeviceHashMap, Value>, + stats_tcp_v4_lock: RwSpinLock, + + stats_tcp_v6: DeviceHashMap, Value>, + stats_tcp_v6_lock: RwSpinLock, + + stats_udp_v4: DeviceHashMap, Value>, + stats_udp_v4_lock: RwSpinLock, + + stats_udp_v6: DeviceHashMap, Value>, + stats_udp_v6_lock: RwSpinLock, +} + +impl Bandwidth { + pub fn new() -> Self { + Self { + stats_tcp_v4: DeviceHashMap::new(), + stats_tcp_v4_lock: RwSpinLock::default(), + + stats_tcp_v6: DeviceHashMap::new(), + stats_tcp_v6_lock: RwSpinLock::default(), + + stats_udp_v4: DeviceHashMap::new(), + stats_udp_v4_lock: RwSpinLock::default(), + + stats_udp_v6: DeviceHashMap::new(), + stats_udp_v6_lock: RwSpinLock::default(), + } + } + + pub fn get_all_updates_tcp_v4(&mut self) -> Option { + let stats_map; + { + let _guard = self.stats_tcp_v4_lock.write_lock(); + if self.stats_tcp_v4.is_empty() { + return None; + } + stats_map = core::mem::replace(&mut self.stats_tcp_v4, DeviceHashMap::new()); + } + + let mut values = alloc::vec::Vec::with_capacity(stats_map.len()); + for (key, value) in stats_map.iter() { + values.push(BandwidthValueV4 { + local_ip: key.local_ip.0, + local_port: key.local_port, + remote_ip: key.remote_ip.0, + remote_port: key.remote_port, + transmitted_bytes: value.transmitted_bytes as u64, + received_bytes: value.received_bytes as u64, + }); + } + Some(protocol::info::bandiwth_stats_array_v4( + u8::from(IpProtocol::Tcp), + values, + )) + } + + pub fn get_all_updates_tcp_v6(&mut self) -> Option { + let stats_map; + { + let _guard = self.stats_tcp_v6_lock.write_lock(); + if self.stats_tcp_v6.is_empty() { + return None; + } + stats_map = core::mem::replace(&mut self.stats_tcp_v6, DeviceHashMap::new()); + } + + let mut values = alloc::vec::Vec::with_capacity(stats_map.len()); + for (key, value) in stats_map.iter() { + values.push(BandwidthValueV6 { + local_ip: key.local_ip.0, + local_port: key.local_port, + remote_ip: key.remote_ip.0, + remote_port: key.remote_port, + transmitted_bytes: value.transmitted_bytes as u64, + received_bytes: value.received_bytes as u64, + }); + } + Some(protocol::info::bandiwth_stats_array_v6( + u8::from(IpProtocol::Tcp), + values, + )) + } + + pub fn get_all_updates_udp_v4(&mut self) -> Option { + let stats_map; + { + let _guard = self.stats_udp_v4_lock.write_lock(); + if self.stats_udp_v4.is_empty() { + return None; + } + stats_map = core::mem::replace(&mut self.stats_udp_v4, DeviceHashMap::new()); + } + + let mut values = alloc::vec::Vec::with_capacity(stats_map.len()); + for (key, value) in stats_map.iter() { + values.push(BandwidthValueV4 { + local_ip: key.local_ip.0, + local_port: key.local_port, + remote_ip: key.remote_ip.0, + remote_port: key.remote_port, + transmitted_bytes: value.transmitted_bytes as u64, + received_bytes: value.received_bytes as u64, + }); + } + Some(protocol::info::bandiwth_stats_array_v4( + u8::from(IpProtocol::Udp), + values, + )) + } + + pub fn get_all_updates_udp_v6(&mut self) -> Option { + let stats_map; + { + let _guard = self.stats_udp_v6_lock.write_lock(); + if self.stats_tcp_v6.is_empty() { + return None; + } + stats_map = core::mem::replace(&mut self.stats_tcp_v6, DeviceHashMap::new()); + } + + let mut values = alloc::vec::Vec::with_capacity(stats_map.len()); + for (key, value) in stats_map.iter() { + values.push(BandwidthValueV6 { + local_ip: key.local_ip.0, + local_port: key.local_port, + remote_ip: key.remote_ip.0, + remote_port: key.remote_port, + transmitted_bytes: value.transmitted_bytes as u64, + received_bytes: value.received_bytes as u64, + }); + } + Some(protocol::info::bandiwth_stats_array_v6( + u8::from(IpProtocol::Udp), + values, + )) + } + + pub fn update_tcp_v4_tx(&mut self, key: Key, tx_bytes: usize) { + Self::update( + &mut self.stats_tcp_v4, + &mut self.stats_tcp_v4_lock, + key, + Direction::Tx(tx_bytes), + ); + } + + pub fn update_tcp_v4_rx(&mut self, key: Key, rx_bytes: usize) { + Self::update( + &mut self.stats_tcp_v4, + &mut self.stats_tcp_v4_lock, + key, + Direction::Rx(rx_bytes), + ); + } + + pub fn update_tcp_v6_tx(&mut self, key: Key, tx_bytes: usize) { + Self::update( + &mut self.stats_tcp_v6, + &mut self.stats_tcp_v6_lock, + key, + Direction::Tx(tx_bytes), + ); + } + + pub fn update_tcp_v6_rx(&mut self, key: Key, rx_bytes: usize) { + Self::update( + &mut self.stats_tcp_v6, + &mut self.stats_tcp_v6_lock, + key, + Direction::Rx(rx_bytes), + ); + } + + pub fn update_udp_v4_tx(&mut self, key: Key, tx_bytes: usize) { + Self::update( + &mut self.stats_udp_v4, + &mut self.stats_udp_v4_lock, + key, + Direction::Tx(tx_bytes), + ); + } + + pub fn update_udp_v4_rx(&mut self, key: Key, rx_bytes: usize) { + Self::update( + &mut self.stats_udp_v4, + &mut self.stats_udp_v4_lock, + key, + Direction::Rx(rx_bytes), + ); + } + + pub fn update_udp_v6_tx(&mut self, key: Key, tx_bytes: usize) { + Self::update( + &mut self.stats_udp_v6, + &mut self.stats_udp_v6_lock, + key, + Direction::Tx(tx_bytes), + ); + } + + pub fn update_udp_v6_rx(&mut self, key: Key, rx_bytes: usize) { + Self::update( + &mut self.stats_udp_v6, + &mut self.stats_udp_v6_lock, + key, + Direction::Rx(rx_bytes), + ); + } + + fn update( + map: &mut DeviceHashMap, Value>, + lock: &mut RwSpinLock, + key: Key
, + bytes: Direction, + ) { + let _guard = lock.write_lock(); + if let Some(value) = map.get_mut(&key) { + match bytes { + Direction::Tx(bytes_count) => value.transmitted_bytes += bytes_count, + Direction::Rx(bytes_count) => value.received_bytes += bytes_count, + } + } else { + let mut received_bytes = 0; + let mut transmitted_bytes = 0; + match bytes { + Direction::Tx(bytes_count) => transmitted_bytes += bytes_count, + Direction::Rx(bytes_count) => received_bytes += bytes_count, + } + map.insert( + key, + Value { + received_bytes, + transmitted_bytes, + }, + ); + } + } + + #[allow(dead_code)] + pub fn get_entries_count(&self) -> usize { + let mut size = 0; + { + let values = &self.stats_tcp_v4.values(); + let _guard = self.stats_tcp_v4_lock.read_lock(); + size += values.len(); + } + { + let values = &self.stats_tcp_v6.values(); + let _guard = self.stats_tcp_v6_lock.read_lock(); + size += values.len(); + } + { + let values = &self.stats_udp_v4.values(); + let _guard = self.stats_udp_v4_lock.read_lock(); + size += values.len(); + } + { + let values = &self.stats_udp_v6.values(); + let _guard = self.stats_udp_v6_lock.read_lock(); + size += values.len(); + } + + return size; + } +} diff --git a/windows_kext/driver/src/callouts.rs b/windows_kext/driver/src/callouts.rs new file mode 100644 index 000000000..717016925 --- /dev/null +++ b/windows_kext/driver/src/callouts.rs @@ -0,0 +1,185 @@ +use alloc::vec::Vec; +use wdk::filter_engine::callout::FilterType; +use wdk::{ + consts, + filter_engine::{callout::Callout, layer::Layer}, +}; + +use crate::{ale_callouts, packet_callouts, stream_callouts}; + +pub fn get_callout_vec() -> Vec { + alloc::vec![ + // ----------------------------------------- + // ALE Auth layers + Callout::new( + "AleLayerOutboundV4", + "ALE layer for outbound connection for ipv4", + 0x58545073_f893_454c_bbea_a57bc964f46d, + Layer::AleAuthConnectV4, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::Resettable, + ale_callouts::ale_layer_connect_v4, + ), + Callout::new( + "AleLayerInboundV4", + "ALE layer for inbound connections for ipv4", + 0xc6021395_0724_4e2c_ae20_3dde51fc3c68, + Layer::AleAuthRecvAcceptV4, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::Resettable, + ale_callouts::ale_layer_accept_v4, + ), + Callout::new( + "AleLayerOutboundV6", + "ALE layer for outbound connections for ipv6", + 0x4bd2a080_2585_478d_977c_7f340c6bc3d4, + Layer::AleAuthConnectV6, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::Resettable, + ale_callouts::ale_layer_connect_v6, + ), + Callout::new( + "AleLayerInboundV6", + "ALE layer for inbound connections for ipv6", + 0xd24480da_38fa_4099_9383_b5c83b69e4f2, + Layer::AleAuthRecvAcceptV6, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::Resettable, + ale_callouts::ale_layer_accept_v6, + ), + // ----------------------------------------- + // ALE connection end layers + Callout::new( + "AleEndpointClosureV4", + "ALE layer for indicating closing of connection for ipv4", + 0x58f02845_ace9_4455_ac80_8a84b86fe566, + Layer::AleEndpointClosureV4, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + ale_callouts::endpoint_closure_v4, + ), + Callout::new( + "AleEndpointClosureV6", + "ALE layer for indicating closing of connection for ipv6", + 0x2bc82359_9dc5_4315_9c93_c89467e283ce, + Layer::AleEndpointClosureV6, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + ale_callouts::endpoint_closure_v6, + ), + // ----------------------------------------- + // ALE resource assignment and release. + // Callout::new( + // "AleResourceAssignmentV4", + // "Ipv4 Port assignment monitoring", + // 0x6b9d1985_6f75_4d05_b9b5_1607e187906f, + // Layer::AleResourceAssignmentV4Discard, + // consts::FWP_ACTION_CALLOUT_INSPECTION, + // FilterType::NonResettable, + // ale_callouts::ale_resource_monitor, + // ), + Callout::new( + "AleResourceReleaseV4", + "Ipv4 Port release monitor", + 0x7b513bb3_a0be_4f77_a4bc_03c052abe8d7, + Layer::AleResourceReleaseV4, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + ale_callouts::ale_resource_monitor, + ), + // Callout::new( + // "AleResourceAssignmentV6", + // "Ipv4 Port assignment monitor", + // 0xb0d02299_3d3e_437d_916a_f0e96a60cc18, + // Layer::AleResourceAssignmentV6Discard, + // consts::FWP_ACTION_CALLOUT_INSPECTION, + // FilterType::NonResettable, + // ale_callouts::ale_resource_monitor, + // ), + Callout::new( + "AleResourceReleaseV6", + "Ipv6 Port release monitor", + 0x6cf36e04_e656_42c3_8cac_a1ce05328bd1, + Layer::AleResourceReleaseV6, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + ale_callouts::ale_resource_monitor, + ), + // ----------------------------------------- + // Stream layer + Callout::new( + "StreamLayerV4", + "Stream layer for ipv4", + 0xe2ca13bf_9710_4caa_a45c_e8c78b5ac780, + Layer::StreamV4, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + stream_callouts::stream_layer_tcp_v4, + ), + Callout::new( + "StreamLayerV6", + "Stream layer for ipv6", + 0x66c549b3_11e2_4b27_8f73_856e6fd82baa, + Layer::StreamV6, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + stream_callouts::stream_layer_tcp_v6, + ), + Callout::new( + "DatagramDataLayerV4", + "DatagramData layer for ipv4", + 0xe7eeeaba_168a_45bb_8747_e1a702feb2c5, + Layer::DatagramDataV4, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + stream_callouts::stream_layer_udp_v4, + ), + Callout::new( + "DatagramDataLayerV6", + "DatagramData layer for ipv4", + 0xb25862cd_f744_4452_b14a_d0c1e5a25b30, + Layer::DatagramDataV6, + consts::FWP_ACTION_CALLOUT_INSPECTION, + FilterType::NonResettable, + stream_callouts::stream_layer_udp_v6, + ), + // ----------------------------------------- + // Packet layers + Callout::new( + "IPPacketOutboundV4", + "IP packet outbound network layer callout for Ipv4", + 0xf3183afe_dc35_49f1_8ea2_b16b5666dd36, + Layer::OutboundIppacketV4, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::NonResettable, + packet_callouts::ip_packet_layer_outbound_v4, + ), + Callout::new( + "IPPacketInboundV4", + "IP packet inbound network layer callout for Ipv4", + 0xf0369374_203d_4bf0_83d2_b2ad3cc17a50, + Layer::InboundIppacketV4, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::NonResettable, + packet_callouts::ip_packet_layer_inbound_v4, + ), + Callout::new( + "IPPacketOutboundV6", + "IP packet outbound network layer callout for Ipv6", + 0x91daf8bc_0908_4bf8_9f81_2c538ab8f25a, + Layer::OutboundIppacketV6, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::NonResettable, + packet_callouts::ip_packet_layer_outbound_v6, + ), + Callout::new( + "IPPacketInboundV6", + "IP packet inbound network layer callout for Ipv6", + 0xfe9faf5f_ceb2_4cd9_9995_f2f2b4f5fcc0, + Layer::InboundIppacketV6, + consts::FWP_ACTION_CALLOUT_TERMINATING, + FilterType::NonResettable, + packet_callouts::ip_packet_layer_inbound_v6, + ) + ] +} diff --git a/windows_kext/driver/src/common.rs b/windows_kext/driver/src/common.rs new file mode 100644 index 000000000..70c01b46b --- /dev/null +++ b/windows_kext/driver/src/common.rs @@ -0,0 +1,59 @@ +#![allow(dead_code)] + +use core::fmt::Display; + +use num_derive::{FromPrimitive, ToPrimitive}; + +pub const ICMPV4_CODE_DESTINATION_UNREACHABLE: u32 = 3; +pub const ICMPV4_CODE_DU_PORT_UNREACHABLE: u32 = 3; // Destination Unreachable (Port unreachable) ; +pub const ICMPV4_CODE_DU_ADMINISTRATIVELY_PROHIBITED: u32 = 13; // Destination Unreachable (Communication Administratively Prohibited) ; + +pub const ICMPV6_CODE_DESTINATION_UNREACHABLE: u32 = 1; +pub const ICMPV6_CODE_DU_PORT_UNREACHABLE: u32 = 4; // Destination Unreachable (Port unreachable) ; + +enum Direction { + Outbound = 0, + Inbound = 1, +} + +const SIOCTL_TYPE: u32 = 40000; +macro_rules! ctl_code { + ($device_type:expr, $function:expr, $method:expr, $access:expr) => { + ($device_type << 16) | ($access << 14) | ($function << 2) | $method + }; +} + +pub const METHOD_BUFFERED: u32 = 0; +pub const METHOD_IN_DIRECT: u32 = 1; +pub const METHOD_OUT_DIRECT: u32 = 2; +pub const METHOD_NEITHER: u32 = 3; + +pub const FILE_READ_DATA: u32 = 0x0001; // file & pipe +pub const FILE_WRITE_DATA: u32 = 0x0002; // file & pipe + +#[repr(u32)] +#[derive(FromPrimitive, ToPrimitive)] +pub enum ControlCode { + Version = ctl_code!( + SIOCTL_TYPE, + 0x800, + METHOD_BUFFERED, + FILE_READ_DATA | FILE_WRITE_DATA + ), + ShutdownRequest = ctl_code!( + SIOCTL_TYPE, + 0x801, + METHOD_BUFFERED, + FILE_READ_DATA | FILE_WRITE_DATA + ), +} + +impl Display for ControlCode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ControlCode::Version => _ = write!(f, "Version"), + ControlCode::ShutdownRequest => _ = write!(f, "Shutdown"), + }; + return Ok(()); + } +} diff --git a/windows_kext/driver/src/connection.rs b/windows_kext/driver/src/connection.rs new file mode 100644 index 000000000..f969ac84a --- /dev/null +++ b/windows_kext/driver/src/connection.rs @@ -0,0 +1,499 @@ +use alloc::{ + boxed::Box, + string::{String, ToString}, +}; +use core::{ + fmt::{Debug, Display}, + sync::atomic::{AtomicU64, Ordering}, +}; +use num_derive::FromPrimitive; +use smoltcp::wire::{IpAddress, IpProtocol, Ipv4Address, Ipv6Address}; + +use crate::connection_map::Key; + +pub static PM_DNS_PORT: u16 = 53; +pub static PM_SPN_PORT: u16 = 717; + +// Make sure this in sync with the Go version +#[derive(Copy, Clone, FromPrimitive)] +#[repr(u8)] +#[rustfmt::skip] +pub enum Verdict { + Undecided = 0, // Undecided is the default status of new connections. + Undeterminable = 1, + Accept = 2, + PermanentAccept = 3, + Block = 4, + PermanentBlock = 5, + Drop = 6, + PermanentDrop = 7, + RedirectNameServer = 8, + RedirectTunnel = 9, + Failed = 10, +} + +impl Display for Verdict { + #[rustfmt::skip] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Verdict::Undecided => write!(f, "Undecided"), + Verdict::Undeterminable => write!(f, "Undeterminable"), + Verdict::Accept => write!(f, "Accept"), + Verdict::PermanentAccept => write!(f, "PermanentAccept"), + Verdict::Block => write!(f, "Block"), + Verdict::PermanentBlock => write!(f, "PermanentBlock"), + Verdict::Drop => write!(f, "Drop"), + Verdict::PermanentDrop => write!(f, "PermanentDrop"), + Verdict::RedirectNameServer => write!(f, "RedirectNameServer"), + Verdict::RedirectTunnel => write!(f, "RedirectTunnel"), + Verdict::Failed => write!(f, "Failed"), + } + } +} + +#[allow(dead_code)] +impl Verdict { + /// Returns true if the verdict is a redirect. + pub fn is_redirect(&self) -> bool { + matches!(self, Verdict::RedirectNameServer | Verdict::RedirectTunnel) + } + + /// Returns true if the verdict is a permanent verdict. + pub fn is_permanent(&self) -> bool { + matches!( + self, + Verdict::PermanentAccept + | Verdict::PermanentBlock + | Verdict::PermanentDrop + | Verdict::RedirectNameServer + | Verdict::RedirectTunnel + ) + } +} + +/// Direction of the connection. +#[derive(Copy, Clone, FromPrimitive)] +#[repr(u8)] +pub enum Direction { + Outbound = 0, + Inbound = 1, +} + +impl Display for Direction { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Direction::Outbound => write!(f, "Outbound"), + Direction::Inbound => write!(f, "Inbound"), + } + } +} + +impl Debug for Direction { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self) + } +} + +#[derive(Clone)] +pub struct ConnectionExtra { + pub(crate) end_timestamp: u64, + pub(crate) direction: Direction, +} + +pub trait Connection { + fn redirect_info(&self) -> Option { + let redirect_address = if self.is_ipv6() { + IpAddress::Ipv6(Ipv6Address::LOOPBACK) + } else { + IpAddress::Ipv4(Ipv4Address::new(127, 0, 0, 1)) + }; + + match self.get_verdict() { + Verdict::RedirectNameServer => Some(RedirectInfo { + local_address: self.get_local_address(), + remote_address: self.get_remote_address(), + remote_port: self.get_remote_port(), + redirect_port: PM_DNS_PORT, + unify: false, + redirect_address, + }), + Verdict::RedirectTunnel => Some(RedirectInfo { + local_address: self.get_local_address(), + remote_address: self.get_remote_address(), + remote_port: self.get_remote_port(), + redirect_port: PM_SPN_PORT, + unify: true, + redirect_address, + }), + _ => None, + } + } + + /// Returns the key of the connection. + fn get_key(&self) -> Key { + Key { + protocol: self.get_protocol(), + local_address: self.get_local_address(), + local_port: self.get_local_port(), + remote_address: self.get_remote_address(), + remote_port: self.get_remote_port(), + } + } + + /// Returns true if the connection is equal to the given key. The key is considered equal if the remote port and address are equal. + fn remote_equals(&self, key: &Key) -> bool; + /// Returns true if the connection is equal to the given key for redirecting. The key is considered equal if the remote port and address are equal. + fn redirect_equals(&self, key: &Key) -> bool; + /// Returns the protocol of the connection. + fn get_protocol(&self) -> IpProtocol; + /// Returns the verdict of the connection. + fn get_verdict(&self) -> Verdict; + /// Returns the local address of the connection. + fn get_local_address(&self) -> IpAddress; + /// Returns the local port of the connection. + fn get_local_port(&self) -> u16; + /// Returns the remote address of the connection. + fn get_remote_address(&self) -> IpAddress; + /// Returns the remote port of the connection. + fn get_remote_port(&self) -> u16; + /// Returns true if the connection is an IPv6 connection. + fn is_ipv6(&self) -> bool; + /// Returns the direction of the connection. + fn get_direction(&self) -> Direction; + // Returns the process id of the connection. + fn get_process_id(&self) -> u64; + /// Ends the connection. + fn end(&mut self, timestamp: u64); + /// Returns true if the connection has ended. + fn has_ended(&self) -> bool { + self.get_end_time() > 0 + } + /// Returns the timestamp when the connection ended. + fn get_end_time(&self) -> u64; + /// Returns the timestamp when the connection was last accessed. + fn get_last_accessed_time(&self) -> u64; + /// Sets the timestamp when the connection was last accessed. + fn set_last_accessed_time(&self, timestamp: u64); +} + +pub struct ConnectionV4 { + pub(crate) protocol: IpProtocol, + pub(crate) local_address: Ipv4Address, + pub(crate) local_port: u16, + pub(crate) remote_address: Ipv4Address, + pub(crate) remote_port: u16, + pub(crate) verdict: Verdict, + pub(crate) process_id: u64, + pub(crate) last_accessed_timestamp: AtomicU64, + pub(crate) extra: Box, +} + +pub struct ConnectionV6 { + pub(crate) protocol: IpProtocol, + pub(crate) local_address: Ipv6Address, + pub(crate) local_port: u16, + pub(crate) remote_address: Ipv6Address, + pub(crate) remote_port: u16, + pub(crate) verdict: Verdict, + pub(crate) process_id: u64, + pub(crate) last_accessed_timestamp: AtomicU64, + pub(crate) extra: Box, +} + +#[derive(Debug)] +pub struct RedirectInfo { + pub(crate) local_address: IpAddress, + pub(crate) remote_address: IpAddress, + pub(crate) remote_port: u16, + pub(crate) redirect_port: u16, + pub(crate) unify: bool, + pub(crate) redirect_address: IpAddress, +} + +impl ConnectionV4 { + /// Creates a new ipv4 connection from the given key. + pub fn from_key(key: &Key, process_id: u64, direction: Direction) -> Result { + let IpAddress::Ipv4(local_address) = key.local_address else { + return Err("wrong ip address version".to_string()); + }; + + let IpAddress::Ipv4(remote_address) = key.remote_address else { + return Err("wrong ip address version".to_string()); + }; + + let timestamp = wdk::utils::get_system_timestamp_ms(); + + Ok(Self { + protocol: key.protocol, + local_address, + local_port: key.local_port, + remote_address, + remote_port: key.remote_port, + verdict: Verdict::Undecided, + process_id, + last_accessed_timestamp: AtomicU64::new(timestamp), + extra: Box::new(ConnectionExtra { + direction, + end_timestamp: 0, + }), + }) + } +} + +impl Connection for ConnectionV4 { + fn remote_equals(&self, key: &Key) -> bool { + if self.remote_port != key.remote_port { + return false; + } + if let IpAddress::Ipv4(remote_address) = &key.remote_address { + return self.remote_address.eq(remote_address); + } + false + } + + fn get_key(&self) -> Key { + Key { + protocol: self.protocol, + local_address: IpAddress::Ipv4(self.local_address), + local_port: self.local_port, + remote_address: IpAddress::Ipv4(self.remote_address), + remote_port: self.remote_port, + } + } + + fn redirect_equals(&self, key: &Key) -> bool { + match self.verdict { + Verdict::RedirectNameServer => { + if key.remote_port != PM_DNS_PORT { + return false; + } + + match key.remote_address { + IpAddress::Ipv4(a) => a.is_loopback(), + IpAddress::Ipv6(_) => false, + } + } + Verdict::RedirectTunnel => { + if key.remote_port != PM_SPN_PORT { + return false; + } + key.local_address.eq(&key.remote_address) + } + _ => false, + } + } + + fn get_protocol(&self) -> IpProtocol { + self.protocol + } + + fn get_verdict(&self) -> Verdict { + self.verdict + } + + fn get_local_address(&self) -> IpAddress { + IpAddress::Ipv4(self.local_address) + } + + fn get_local_port(&self) -> u16 { + self.local_port + } + + fn get_remote_address(&self) -> IpAddress { + IpAddress::Ipv4(self.remote_address) + } + + fn get_remote_port(&self) -> u16 { + self.remote_port + } + + fn is_ipv6(&self) -> bool { + false + } + + fn get_process_id(&self) -> u64 { + self.process_id + } + + fn get_direction(&self) -> Direction { + self.extra.direction + } + + fn end(&mut self, timestamp: u64) { + self.extra.end_timestamp = timestamp; + } + + fn get_end_time(&self) -> u64 { + self.extra.end_timestamp + } + + fn get_last_accessed_time(&self) -> u64 { + self.last_accessed_timestamp.load(Ordering::Relaxed) + } + + fn set_last_accessed_time(&self, timestamp: u64) { + self.last_accessed_timestamp + .store(timestamp, Ordering::Relaxed); + } +} + +impl Clone for ConnectionV4 { + fn clone(&self) -> Self { + Self { + protocol: self.protocol, + local_address: self.local_address, + local_port: self.local_port, + remote_address: self.remote_address, + remote_port: self.remote_port, + verdict: self.verdict, + process_id: self.process_id, + last_accessed_timestamp: AtomicU64::new( + self.last_accessed_timestamp.load(Ordering::Relaxed), + ), + extra: self.extra.clone(), + } + } +} + +impl ConnectionV6 { + /// Creates a new ipv6 connection from the given key. + pub fn from_key(key: &Key, process_id: u64, direction: Direction) -> Result { + let IpAddress::Ipv6(local_address) = key.local_address else { + return Err("wrong ip address version".to_string()); + }; + + let IpAddress::Ipv6(remote_address) = key.remote_address else { + return Err("wrong ip address version".to_string()); + }; + let timestamp = wdk::utils::get_system_timestamp_ms(); + + Ok(Self { + protocol: key.protocol, + local_address, + local_port: key.local_port, + remote_address, + remote_port: key.remote_port, + verdict: Verdict::Undecided, + process_id, + last_accessed_timestamp: AtomicU64::new(timestamp), + extra: Box::new(ConnectionExtra { + direction, + end_timestamp: 0, + }), + }) + } +} + +impl Connection for ConnectionV6 { + fn remote_equals(&self, key: &Key) -> bool { + if self.remote_port != key.remote_port { + return false; + } + if let IpAddress::Ipv6(remote_address) = &key.remote_address { + return self.remote_address.eq(remote_address); + } + false + } + fn get_key(&self) -> Key { + Key { + protocol: self.protocol, + local_address: IpAddress::Ipv6(self.local_address), + local_port: self.local_port, + remote_address: IpAddress::Ipv6(self.remote_address), + remote_port: self.remote_port, + } + } + + fn redirect_equals(&self, key: &Key) -> bool { + match self.verdict { + Verdict::RedirectNameServer => { + if key.remote_port != PM_DNS_PORT { + return false; + } + + match key.remote_address { + IpAddress::Ipv4(_) => false, + IpAddress::Ipv6(a) => a.is_loopback(), + } + } + Verdict::RedirectTunnel => { + if key.remote_port != PM_SPN_PORT { + return false; + } + key.local_address.eq(&key.remote_address) + } + _ => false, + } + } + + fn get_protocol(&self) -> IpProtocol { + self.protocol + } + + fn get_verdict(&self) -> Verdict { + self.verdict + } + + fn get_local_address(&self) -> IpAddress { + IpAddress::Ipv6(self.local_address) + } + + fn get_local_port(&self) -> u16 { + self.local_port + } + + fn get_remote_address(&self) -> IpAddress { + IpAddress::Ipv6(self.remote_address) + } + + fn get_remote_port(&self) -> u16 { + self.remote_port + } + + fn is_ipv6(&self) -> bool { + true + } + + fn get_process_id(&self) -> u64 { + self.process_id + } + + fn get_direction(&self) -> Direction { + self.extra.direction + } + + fn end(&mut self, timestamp: u64) { + self.extra.end_timestamp = timestamp; + } + + fn get_end_time(&self) -> u64 { + self.extra.end_timestamp + } + + fn get_last_accessed_time(&self) -> u64 { + self.last_accessed_timestamp.load(Ordering::Relaxed) + } + + fn set_last_accessed_time(&self, timestamp: u64) { + self.last_accessed_timestamp + .store(timestamp, Ordering::Relaxed); + } +} + +impl Clone for ConnectionV6 { + fn clone(&self) -> Self { + Self { + protocol: self.protocol, + local_address: self.local_address, + local_port: self.local_port, + remote_address: self.remote_address, + remote_port: self.remote_port, + verdict: self.verdict, + process_id: self.process_id, + last_accessed_timestamp: AtomicU64::new( + self.last_accessed_timestamp.load(Ordering::Relaxed), + ), + extra: self.extra.clone(), + } + } +} diff --git a/windows_kext/driver/src/connection_cache.rs b/windows_kext/driver/src/connection_cache.rs new file mode 100644 index 000000000..6e1495086 --- /dev/null +++ b/windows_kext/driver/src/connection_cache.rs @@ -0,0 +1,200 @@ +use core::time::Duration; + +use crate::{ + connection::{Connection, ConnectionV4, ConnectionV6, RedirectInfo, Verdict}, + connection_map::{ConnectionMap, Key}, +}; +use alloc::{format, string::String, vec::Vec}; + +use smoltcp::wire::IpProtocol; +use wdk::rw_spin_lock::RwSpinLock; + +pub struct ConnectionCache { + connections_v4: ConnectionMap, + connections_v6: ConnectionMap, + lock_v4: RwSpinLock, + lock_v6: RwSpinLock, +} + +impl ConnectionCache { + pub fn new() -> Self { + Self { + connections_v4: ConnectionMap::new(), + connections_v6: ConnectionMap::new(), + lock_v4: RwSpinLock::default(), + lock_v6: RwSpinLock::default(), + } + } + + pub fn add_connection_v4(&mut self, connection: ConnectionV4) { + let _guard = self.lock_v4.write_lock(); + self.connections_v4.add(connection); + } + + pub fn add_connection_v6(&mut self, connection: ConnectionV6) { + let _guard = self.lock_v6.write_lock(); + self.connections_v6.add(connection); + } + + pub fn update_connection(&mut self, key: Key, verdict: Verdict) -> Option { + if key.is_ipv6() { + let _guard = self.lock_v6.write_lock(); + if let Some(conn) = self.connections_v6.get_mut(&key) { + conn.verdict = verdict; + return conn.redirect_info(); + } + } else { + let _guard = self.lock_v4.write_lock(); + if let Some(conn) = self.connections_v4.get_mut(&key) { + conn.verdict = verdict; + return conn.redirect_info(); + } + } + None + } + + pub fn read_connection_v4( + &self, + key: &Key, + process_connection: fn(&ConnectionV4) -> Option, + ) -> Option { + let _guard = self.lock_v4.read_lock(); + self.connections_v4.read(&key, process_connection) + } + + pub fn read_connection_v6( + &self, + key: &Key, + process_connection: fn(&ConnectionV6) -> Option, + ) -> Option { + let _guard = self.lock_v6.read_lock(); + self.connections_v6.read(&key, process_connection) + } + + pub fn end_connection_v4(&mut self, key: Key) -> Option { + let _guard = self.lock_v4.write_lock(); + self.connections_v4.end(key) + } + + pub fn end_connection_v6(&mut self, key: Key) -> Option { + let _guard = self.lock_v6.write_lock(); + self.connections_v6.end(key) + } + + pub fn end_all_on_port_v4(&mut self, key: (IpProtocol, u16)) -> Option> { + let _guard = self.lock_v4.write_lock(); + self.connections_v4.end_all_on_port(key) + } + + pub fn end_all_on_port_v6(&mut self, key: (IpProtocol, u16)) -> Option> { + let _guard = self.lock_v6.write_lock(); + self.connections_v6.end_all_on_port(key) + } + + pub fn clean_ended_connections(&mut self) { + { + let _guard = self.lock_v4.write_lock(); + self.connections_v4.clean_ended_connections(); + } + { + let _guard = self.lock_v6.write_lock(); + self.connections_v6.clean_ended_connections(); + } + } + + pub fn clear(&mut self) { + { + let _guard = self.lock_v4.write_lock(); + self.connections_v4.clear(); + } + { + let _guard = self.lock_v6.write_lock(); + self.connections_v6.clear(); + } + } + + #[allow(dead_code)] + pub fn get_entries_count(&self) -> usize { + let mut size = 0; + { + let _guard = self.lock_v4.read_lock(); + size += self.connections_v4.get_count(); + } + + { + let _guard = self.lock_v6.read_lock(); + size += self.connections_v6.get_count(); + } + + return size; + } + + #[allow(dead_code)] + pub fn get_full_cache_info(&self) -> String { + let mut info = String::new(); + let now = wdk::utils::get_system_timestamp_ms(); + { + let _guard = self.lock_v4.read_lock(); + for ((protocol, port), connections) in self.connections_v4.iter() { + info.push_str(&format!("{} -> {}\n", protocol, port,)); + for conn in connections { + let active_time_seconds = + Duration::from_millis(now - conn.get_last_accessed_time()).as_secs(); + info.push_str(&format!( + "\t{}:{} -> {}:{} {} last active {}m {}s ago", + conn.local_address, + conn.local_port, + conn.remote_address, + conn.remote_port, + conn.verdict, + active_time_seconds / 60, + active_time_seconds % 60 + )); + if conn.has_ended() { + let end_time_seconds = + Duration::from_millis(now - conn.get_end_time()).as_secs(); + info.push_str(&format!( + "\t ended {}m {}s ago", + end_time_seconds / 60, + end_time_seconds % 60 + )); + } + info.push('\n'); + } + } + } + + { + let _guard = self.lock_v6.read_lock(); + for ((protocol, port), connections) in self.connections_v6.iter() { + info.push_str(&format!("{} -> {} \n", protocol, port)); + for conn in connections { + let active_time_seconds = + Duration::from_millis(now - conn.get_last_accessed_time()).as_secs(); + info.push_str(&format!( + "\t{}:{} -> {}:{} {} last active {}m {}s ago", + conn.local_address, + conn.local_port, + conn.remote_address, + conn.remote_port, + conn.verdict, + active_time_seconds / 60, + active_time_seconds % 60 + )); + if conn.has_ended() { + let end_time_seconds = + Duration::from_millis(now - conn.get_end_time()).as_secs(); + info.push_str(&format!( + "\t ended {}m {}s ago", + end_time_seconds / 60, + end_time_seconds % 60 + )); + } + info.push('\n'); + } + } + } + + return info; + } +} diff --git a/windows_kext/driver/src/connection_map.rs b/windows_kext/driver/src/connection_map.rs new file mode 100644 index 000000000..282da1534 --- /dev/null +++ b/windows_kext/driver/src/connection_map.rs @@ -0,0 +1,179 @@ +use core::{fmt::Display, time::Duration}; + +use crate::connection::Connection; +use alloc::vec::Vec; +use hashbrown::HashMap; +use smoltcp::wire::{IpAddress, IpProtocol}; + +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] +pub struct Key { + pub(crate) protocol: IpProtocol, + pub(crate) local_address: IpAddress, + pub(crate) local_port: u16, + pub(crate) remote_address: IpAddress, + pub(crate) remote_port: u16, +} + +impl Display for Key { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "p: {} l: {}:{} r: {}:{}", + self.protocol, + self.local_address, + self.local_port, + self.remote_address, + self.remote_port + ) + } +} + +impl Key { + /// Returns the protocol and port as a tuple. + pub fn small(&self) -> (IpProtocol, u16) { + (self.protocol, self.local_port) + } + + /// Returns true if the local address is an IPv4 address. + pub fn is_ipv6(&self) -> bool { + match self.local_address { + IpAddress::Ipv4(_) => false, + IpAddress::Ipv6(_) => true, + } + } + + /// Returns true if the local address is a loopback address. + pub fn is_loopback(&self) -> bool { + match self.local_address { + IpAddress::Ipv4(ip) => ip.is_loopback(), + IpAddress::Ipv6(ip) => ip.is_loopback(), + } + } + + /// Returns a new key with the local and remote addresses and ports reversed. + #[allow(dead_code)] + pub fn reverse(&self) -> Key { + Key { + protocol: self.protocol, + local_address: self.remote_address, + local_port: self.remote_port, + remote_address: self.local_address, + remote_port: self.local_port, + } + } +} + +pub struct ConnectionMap(HashMap<(IpProtocol, u16), Vec>); + +impl ConnectionMap { + pub fn new() -> Self { + Self(HashMap::new()) + } + + pub fn add(&mut self, conn: T) { + let key = conn.get_key().small(); + if let Some(connections) = self.0.get_mut(&key) { + connections.push(conn); + } else { + self.0.insert(key, alloc::vec![conn]); + } + } + + pub fn get_mut(&mut self, key: &Key) -> Option<&mut T> { + if let Some(connections) = self.0.get_mut(&key.small()) { + for conn in connections { + if conn.remote_equals(key) { + conn.set_last_accessed_time(wdk::utils::get_system_timestamp_ms()); + return Some(conn); + } + } + } + + None + } + + pub fn read(&self, key: &Key, read_connection: fn(&T) -> Option) -> Option { + if let Some(connections) = self.0.get(&key.small()) { + for conn in connections { + if conn.remote_equals(key) { + conn.set_last_accessed_time(wdk::utils::get_system_timestamp_ms()); + return read_connection(conn); + } + if conn.redirect_equals(key) { + conn.set_last_accessed_time(wdk::utils::get_system_timestamp_ms()); + return read_connection(conn); + } + } + } + + None + } + + pub fn end(&mut self, key: Key) -> Option { + if let Some(connections) = self.0.get_mut(&key.small()) { + for (_, conn) in connections.iter_mut().enumerate() { + if conn.remote_equals(&key) { + conn.end(wdk::utils::get_system_timestamp_ms()); + return Some(conn.clone()); + } + } + } + return None; + } + + pub fn end_all_on_port(&mut self, key: (IpProtocol, u16)) -> Option> { + if let Some(connections) = self.0.get_mut(&key) { + let mut vec = Vec::with_capacity(connections.len()); + for (_, conn) in connections.iter_mut().enumerate() { + if !conn.has_ended() { + conn.end(wdk::utils::get_system_timestamp_ms()); + vec.push(conn.clone()); + } + } + return Some(vec); + } + return None; + } + + pub fn clear(&mut self) { + self.0.clear(); + } + + pub fn clean_ended_connections(&mut self) { + let now = wdk::utils::get_system_timestamp_ms(); + const TEN_MINUETS: u64 = Duration::from_secs(60 * 10).as_millis() as u64; + let before_ten_minutes = now - TEN_MINUETS; + let before_one_minute = now - Duration::from_secs(60).as_millis() as u64; + + for (_, connections) in self.0.iter_mut() { + connections.retain(|c| { + if c.has_ended() && c.get_end_time() < before_one_minute { + // Ended more than 1 minute ago + return false; + } + + if c.get_last_accessed_time() < before_ten_minutes { + // Last active more than 10 minutes ago + return false; + } + + // Keep + return true; + }); + } + self.0.retain(|_, v| !v.is_empty()); + } + + #[allow(dead_code)] + pub fn get_count(&self) -> usize { + let mut count = 0; + for conn in self.0.values() { + count += conn.len(); + } + return count; + } + + pub fn iter(&self) -> hashbrown::hash_map::Iter<'_, (IpProtocol, u16), Vec> { + self.0.iter() + } +} diff --git a/windows_kext/driver/src/device.rs b/windows_kext/driver/src/device.rs new file mode 100644 index 000000000..e5eeca7d7 --- /dev/null +++ b/windows_kext/driver/src/device.rs @@ -0,0 +1,329 @@ +use alloc::string::String; +use num_traits::FromPrimitive; +use protocol::{command::CommandType, info::Info}; +use smoltcp::wire::{IpAddress, IpProtocol, Ipv4Address, Ipv6Address}; +use wdk::{ + driver::Driver, + filter_engine::{ + callout_data::ClassifyDefer, + net_buffer::{NetBufferList, NetworkAllocator}, + packet::{InjectInfo, Injector}, + FilterEngine, + }, + ioqueue::{self, IOQueue}, + irp_helpers::{ReadRequest, WriteRequest}, +}; + +use crate::{ + array_holder::ArrayHolder, bandwidth::Bandwidth, callouts, connection_cache::ConnectionCache, + connection_map::Key, dbg, err, id_cache::IdCache, logger, packet_util::Redirect, +}; + +pub enum Packet { + PacketLayer(NetBufferList, InjectInfo), + AleLayer(ClassifyDefer), +} + +// Device Context +pub struct Device { + pub(crate) filter_engine: FilterEngine, + pub(crate) read_leftover: ArrayHolder, + pub(crate) event_queue: IOQueue, + pub(crate) packet_cache: IdCache, + pub(crate) connection_cache: ConnectionCache, + pub(crate) injector: Injector, + pub(crate) network_allocator: NetworkAllocator, + pub(crate) bandwidth_stats: Bandwidth, +} + +impl Device { + /// Initialize all members of the device. Memory is handled by windows. + /// Make sure everything is initialized here. + pub fn new(driver: &Driver) -> Result { + let mut filter_engine = + match FilterEngine::new(driver, 0x7dab1057_8e2b_40c4_9b85_693e381d7896) { + Ok(fe) => fe, + Err(err) => return Err(alloc::format!("filter engine error: {}", err)), + }; + + if let Err(err) = filter_engine.commit(callouts::get_callout_vec()) { + return Err(err); + } + + Ok(Self { + filter_engine, + read_leftover: ArrayHolder::default(), + event_queue: IOQueue::new(), + packet_cache: IdCache::new(), + connection_cache: ConnectionCache::new(), + injector: Injector::new(), + network_allocator: NetworkAllocator::new(), + bandwidth_stats: Bandwidth::new(), + }) + } + + /// Cleanup is called just before drop. + // pub fn cleanup(&mut self) {} + + fn write_buffer(&mut self, read_request: &mut ReadRequest, info: Info) { + let bytes = info.as_bytes(); + let count = read_request.write(bytes); + + // Check if the full buffer was written. + if count < bytes.len() { + // Save the leftovers for later. + self.read_leftover.save(&bytes[count..]); + } + } + + /// Called when handle. Read is called from user-space. + pub fn read(&mut self, read_request: &mut ReadRequest) { + if let Some(data) = self.read_leftover.load() { + // There are leftovers from previous request. + let count = read_request.write(&data); + + // Check if full command was written. + if count < data.len() { + // Save the leftovers for later. + self.read_leftover.save(&data[count..]); + } + } else { + // Noting left from before. Wait for next commands. + match self.event_queue.wait_and_pop() { + Ok(info) => { + self.write_buffer(read_request, info); + } + Err(ioqueue::Status::Timeout) => { + // Timeout. This will only trigger if pop function is called with timeout. + read_request.timeout(); + return; + } + Err(err) => { + // Queue failed. Send EOF, to notify user-space. Usually happens on rundown. + err!("failed to pop value: {}", err); + read_request.end_of_file(); + return; + } + } + } + + // Check if we have more space. InfoType + data_size == 5 bytes + while read_request.free_space() > 5 { + match self.event_queue.pop() { + Ok(info) => { + self.write_buffer(read_request, info); + } + Err(_) => { + break; + } + } + } + read_request.complete(); + } + + // Called when handle.Write is called from user-space. + pub fn write(&mut self, write_request: &mut WriteRequest) { + // Try parsing the command. + let mut buffer = write_request.get_buffer(); + let command = protocol::command::parse_type(buffer); + let Some(command) = command else { + err!("Unknown command number: {}", buffer[0]); + return; + }; + buffer = &buffer[1..]; + + let mut _classify_defer = None; + + match command { + CommandType::Shutdown => { + wdk::dbg!("Shutdown command"); + self.shutdown(); + } + CommandType::Verdict => { + let verdict = protocol::command::parse_verdict(buffer); + wdk::dbg!("Verdict command"); + // Received verdict decision for a specific connection. + if let Some((key, mut packet)) = self.packet_cache.pop_id(verdict.id) { + if let Some(verdict) = FromPrimitive::from_u8(verdict.verdict) { + dbg!("Verdict received {}: {}", key, verdict); + // Add verdict in the cache. + let redirect_info = self.connection_cache.update_connection(key, verdict); + + // if verdict.is_permanent() { + // dbg!(self.logger, "resetting filters {}: {}", key, verdict); + // _ = self.filter_engine.reset_all_filters(); + // } + + match verdict { + crate::connection::Verdict::Accept + | crate::connection::Verdict::PermanentAccept => { + if let Err(err) = self.inject_packet(packet, false) { + err!("failed to inject packet: {}", err); + } else { + dbg!("packet injected: {}", key); + } + } + crate::connection::Verdict::RedirectNameServer + | crate::connection::Verdict::RedirectTunnel => { + if let Some(redirect_info) = redirect_info { + if let Err(err) = packet.redirect(redirect_info) { + err!("failed to redirect packet: {}", err); + } + if let Err(err) = self.inject_packet(packet, false) { + err!("failed to inject packet: {}", err); + } + } + } + _ => { + if let Err(err) = self.inject_packet(packet, true) { + err!("failed to inject packet: {}", err); + } + } + } + }; + } else { + // Id was not in the packet cache. + let id = verdict.id; + err!("Verdict invalid id: {}", id); + } + } + CommandType::UpdateV4 => { + let update = protocol::command::parse_update_v4(buffer); + // Build the new action. + if let Some(verdict) = FromPrimitive::from_u8(update.verdict) { + // Update with new action. + dbg!("Verdict update received {:?}: {}", update, verdict); + _classify_defer = self.connection_cache.update_connection( + Key { + protocol: IpProtocol::from(update.protocol), + local_address: IpAddress::Ipv4(Ipv4Address::from_bytes( + &update.local_address, + )), + local_port: update.local_port, + remote_address: IpAddress::Ipv4(Ipv4Address::from_bytes( + &update.remote_address, + )), + remote_port: update.remote_port, + }, + verdict, + ); + } else { + err!("invalid verdict value: {}", update.verdict); + } + } + CommandType::UpdateV6 => { + let update = protocol::command::parse_update_v6(buffer); + // Build the new action. + if let Some(verdict) = FromPrimitive::from_u8(update.verdict) { + // Update with new action. + dbg!("Verdict update received {:?}: {}", update, verdict); + _classify_defer = self.connection_cache.update_connection( + Key { + protocol: IpProtocol::from(update.protocol), + local_address: IpAddress::Ipv6(Ipv6Address::from_bytes( + &update.local_address, + )), + local_port: update.local_port, + remote_address: IpAddress::Ipv6(Ipv6Address::from_bytes( + &update.remote_address, + )), + remote_port: update.remote_port, + }, + verdict, + ); + } else { + err!("invalid verdict value: {}", update.verdict); + } + } + CommandType::ClearCache => { + wdk::dbg!("ClearCache command"); + self.connection_cache.clear(); + if let Err(err) = self.filter_engine.reset_all_filters() { + err!("failed to reset filters: {}", err); + } + } + CommandType::GetLogs => { + wdk::dbg!("GetLogs command"); + let lines_vec = logger::flush(); + for line in lines_vec { + let _ = self.event_queue.push(line); + } + } + CommandType::GetBandwidthStats => { + wdk::dbg!("GetBandwidthStats command"); + let stats = self.bandwidth_stats.get_all_updates_tcp_v4(); + if let Some(stats) = stats { + _ = self.event_queue.push(stats); + } + + let stats = self.bandwidth_stats.get_all_updates_tcp_v6(); + if let Some(stats) = stats { + _ = self.event_queue.push(stats); + } + + let stats = self.bandwidth_stats.get_all_updates_udp_v4(); + if let Some(stats) = stats { + _ = self.event_queue.push(stats); + } + + let stats = self.bandwidth_stats.get_all_updates_udp_v6(); + if let Some(stats) = stats { + _ = self.event_queue.push(stats); + } + } + CommandType::PrintMemoryStats => { + // Getting the information takes a long time and interferes with the callouts causing the device to crash. + // TODO(vladimir): Make more optimized version + // info!( + // "Packet cache: {} entries", + // self.packet_cache.get_entries_count() + // ); + // info!( + // "BandwidthStats cache: {} entries", + // self.bandwidth_stats.get_entries_count() + // ); + // info!( + // "Connection cache: {} entries\n {}", + // self.connection_cache.get_entries_count(), + // self.connection_cache.get_full_cache_info() + // ); + } + CommandType::CleanEndedConnections => { + wdk::dbg!("CleanEndedConnections command"); + self.connection_cache.clean_ended_connections(); + } + } + } + + pub fn shutdown(&self) { + // End blocking operations from the queue. This will end pending read requests. + self.event_queue.rundown(); + } + + pub fn inject_packet(&mut self, packet: Packet, blocked: bool) -> Result<(), String> { + match packet { + Packet::PacketLayer(nbl, inject_info) => { + if !blocked { + self.injector.inject_net_buffer_list(nbl, inject_info) + } else { + Ok(()) + } + } + Packet::AleLayer(defer) => { + let packet_list = defer.complete(&mut self.filter_engine)?; + if let Some(packet_list) = packet_list { + self.injector.inject_packet_list_transport(packet_list)?; + } + + Ok(()) + } + } + } +} + +impl Drop for Device { + fn drop(&mut self) { + _ = logger::flush(); + // dbg!("Device Context drop called."); + } +} diff --git a/windows_kext/driver/src/driver_hashmap.rs b/windows_kext/driver/src/driver_hashmap.rs new file mode 100644 index 000000000..1c8b706ab --- /dev/null +++ b/windows_kext/driver/src/driver_hashmap.rs @@ -0,0 +1,25 @@ +use core::ops::{Deref, DerefMut}; + +use hashbrown::HashMap; + +pub struct DeviceHashMap(Option>); + +impl DeviceHashMap { + pub fn new() -> Self { + Self(Some(HashMap::new())) + } +} + +impl Deref for DeviceHashMap { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + self.0.as_ref().unwrap() + } +} + +impl DerefMut for DeviceHashMap { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut().unwrap() + } +} diff --git a/windows_kext/driver/src/entry.rs b/windows_kext/driver/src/entry.rs new file mode 100644 index 000000000..479fe42a1 --- /dev/null +++ b/windows_kext/driver/src/entry.rs @@ -0,0 +1,135 @@ +use crate::common::ControlCode; +use crate::device; +use alloc::boxed::Box; +use num_traits::FromPrimitive; +use wdk::irp_helpers::{DeviceControlRequest, ReadRequest, WriteRequest}; +use wdk::{err, info, interface}; +use windows_sys::Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, IRP}; +use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_SUCCESS}; + +static VERSION: [u8; 4] = include!("../../kextinterface/version.txt"); + +static mut DEVICE: *mut device::Device = core::ptr::null_mut(); +pub fn get_device() -> Option<&'static mut device::Device> { + return unsafe { DEVICE.as_mut() }; +} + +// DriverEntry is the entry point of the driver (main function). Will be called when driver is loaded. +// Name should not be changed +#[export_name = "DriverEntry"] +pub extern "system" fn driver_entry( + driver_object: *mut windows_sys::Wdk::Foundation::DRIVER_OBJECT, + registry_path: *mut windows_sys::Win32::Foundation::UNICODE_STRING, +) -> windows_sys::Win32::Foundation::NTSTATUS { + info!("Starting initialization..."); + + // Initialize driver object. + let mut driver = match interface::init_driver_object( + driver_object, + registry_path, + "PortmasterKext", + core::ptr::null_mut(), + ) { + Ok(driver) => driver, + Err(status) => { + err!("driver_entry: failed to initialize driver: {}", status); + return windows_sys::Win32::Foundation::STATUS_FAILED_DRIVER_ENTRY; + } + }; + + // Set driver functions. + driver.set_driver_unload(driver_unload); + driver.set_read_fn(driver_read); + driver.set_write_fn(driver_write); + driver.set_device_control_fn(device_control); + + // Initialize device. + unsafe { + let device = match device::Device::new(&driver) { + Ok(device) => Box::new(device), + Err(err) => { + wdk::err!("filed to initialize device: {}", err); + return -1; + } + }; + DEVICE = Box::into_raw(device); + } + + STATUS_SUCCESS +} + +// driver_unload function is called when service delete is called from user-space. +unsafe extern "system" fn driver_unload(_object: *const DRIVER_OBJECT) { + info!("Unloading complete"); + unsafe { + if !DEVICE.is_null() { + _ = Box::from_raw(DEVICE); + } + } +} + +// driver_read event triggered from user-space on file.Read. +unsafe extern "system" fn driver_read( + _device_object: &mut DEVICE_OBJECT, + irp: &mut IRP, +) -> NTSTATUS { + let mut read_request = ReadRequest::new(irp); + let Some(device) = get_device() else { + read_request.complete(); + + return read_request.get_status(); + }; + + device.read(&mut read_request); + read_request.get_status() +} + +/// driver_write event triggered from user-space on file.Write. +unsafe extern "system" fn driver_write( + _device_object: &mut DEVICE_OBJECT, + irp: &mut IRP, +) -> NTSTATUS { + let mut write_request = WriteRequest::new(irp); + let Some(device) = get_device() else { + write_request.complete(); + return write_request.get_status(); + }; + + device.write(&mut write_request); + + write_request.mark_all_as_read(); + write_request.complete(); + write_request.get_status() +} + +/// device_control event triggered from user-space on file.deviceIOControl. +unsafe extern "system" fn device_control( + _device_object: &mut DEVICE_OBJECT, + irp: &mut IRP, +) -> NTSTATUS { + let mut control_request = DeviceControlRequest::new(irp); + let Some(device) = get_device() else { + control_request.complete(); + return control_request.get_status(); + }; + + let Some(control_code): Option = + FromPrimitive::from_u32(control_request.get_control_code()) + else { + wdk::info!("Unknown IOCTL code: {}", control_request.get_control_code()); + control_request.not_implemented(); + return control_request.get_status(); + }; + + wdk::info!("IOCTL: {}", control_code); + + match control_code { + ControlCode::Version => { + control_request.write(&VERSION); + } + ControlCode::ShutdownRequest => device.shutdown(), + }; + + control_request.complete(); + control_request.get_status() +} diff --git a/windows_kext/driver/src/id_cache.rs b/windows_kext/driver/src/id_cache.rs new file mode 100644 index 000000000..e84745383 --- /dev/null +++ b/windows_kext/driver/src/id_cache.rs @@ -0,0 +1,131 @@ +use alloc::collections::VecDeque; +use protocol::info::Info; +use smoltcp::wire::{IpAddress, IpProtocol}; +use wdk::rw_spin_lock::RwSpinLock; + +use crate::{connection::Direction, connection_map::Key, device::Packet}; + +struct Entry { + value: T, + id: u64, +} + +pub struct IdCache { + values: VecDeque>, + lock: RwSpinLock, + next_id: u64, +} + +impl IdCache { + pub fn new() -> Self { + Self { + values: VecDeque::with_capacity(1000), + lock: RwSpinLock::default(), + next_id: 1, // 0 is invalid id + } + } + + pub fn push( + &mut self, + value: (Key, Packet), + process_id: u64, + direction: Direction, + ale_layer: bool, + ) -> Option { + let _guard = self.lock.write_lock(); + let id = self.next_id; + let info = build_info(&value.0, id, process_id, direction, &value.1, ale_layer); + self.values.push_back(Entry { value, id }); + self.next_id = self.next_id.wrapping_add(1); // Assuming this will not overflow. + + return info; + } + + pub fn pop_id(&mut self, id: u64) -> Option<(Key, Packet)> { + let _guard = self.lock.write_lock(); + if let Ok(index) = self.values.binary_search_by_key(&id, |val| val.id) { + return Some(self.values.remove(index).unwrap().value); + } + None + } + + #[allow(dead_code)] + pub fn get_entries_count(&self) -> usize { + let _guard = self.lock.read_lock(); + return self.values.len(); + } +} + +fn get_payload<'a>(packet: &'a Packet) -> Option<&'a [u8]> { + match packet { + Packet::PacketLayer(nbl, _) => nbl.get_data(), + Packet::AleLayer(defer) => { + let p = match defer { + wdk::filter_engine::callout_data::ClassifyDefer::Initial(_, p) => p, + wdk::filter_engine::callout_data::ClassifyDefer::Reauthorization(_, p) => p, + }; + if let Some(tpl) = p { + tpl.net_buffer_list_queue.get_data() + } else { + None + } + } + } +} + +fn build_info( + key: &Key, + packet_id: u64, + process_id: u64, + direction: Direction, + packet: &Packet, + ale_layer: bool, +) -> Option { + let (local_port, remote_port) = match key.protocol { + IpProtocol::Tcp | IpProtocol::Udp => (key.local_port, key.remote_port), + _ => (0, 0), + }; + + let payload_layer = if ale_layer { + 4 // Transport layer + } else { + 3 // Network layer + }; + + let mut payload = &[][..]; + if let Some(p) = get_payload(packet) { + payload = p; + } + + match (key.local_address, key.remote_address) { + (IpAddress::Ipv6(local_ip), IpAddress::Ipv6(remote_ip)) if key.is_ipv6() => { + Some(protocol::info::connection_info_v6( + packet_id, + process_id, + direction as u8, + u8::from(key.protocol), + local_ip.0, + remote_ip.0, + local_port, + remote_port, + payload_layer, + payload, + )) + } + (IpAddress::Ipv4(local_ip), IpAddress::Ipv4(remote_ip)) => { + Some(protocol::info::connection_info_v4( + packet_id, + process_id, + direction as u8, + u8::from(key.protocol), + local_ip.0, + remote_ip.0, + local_port, + remote_port, + payload_layer, + payload, + )) + } + _ => None, + } +} diff --git a/windows_kext/driver/src/lib.rs b/windows_kext/driver/src/lib.rs new file mode 100644 index 000000000..d13e9d3f3 --- /dev/null +++ b/windows_kext/driver/src/lib.rs @@ -0,0 +1,43 @@ +#![cfg_attr(not(test), no_std)] +#![no_main] +#![allow(clippy::needless_return)] + +extern crate alloc; + +mod ale_callouts; +mod array_holder; +mod bandwidth; +mod callouts; +mod common; +mod connection; +mod connection_cache; +mod connection_map; +mod device; +mod driver_hashmap; +mod entry; +mod id_cache; +pub mod logger; +mod packet_callouts; +mod packet_util; +mod stream_callouts; + +use wdk::allocator::WindowsAllocator; + +#[cfg(not(test))] +use core::panic::PanicInfo; + +// Declaration of the global memory allocator +#[global_allocator] +static HEAP: WindowsAllocator = WindowsAllocator {}; + +#[no_mangle] +pub extern "system" fn _DllMainCRTStartup() {} + +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + use wdk::err; + + err!("{}", info); + loop {} +} diff --git a/windows_kext/driver/src/logger.rs b/windows_kext/driver/src/logger.rs new file mode 100644 index 000000000..5a0440a25 --- /dev/null +++ b/windows_kext/driver/src/logger.rs @@ -0,0 +1,114 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::{ + mem::MaybeUninit, + sync::atomic::{AtomicPtr, AtomicUsize, Ordering}, +}; +use protocol::info::{Info, Severity}; + +#[cfg(not(debug_assertions))] +pub const LOG_LEVEL: u8 = Severity::Warning as u8; + +#[cfg(debug_assertions)] +pub const LOG_LEVEL: u8 = Severity::Trace as u8; + +pub const MAX_LOG_LINE_SIZE: usize = 150; + +static mut LOG_LINES: [AtomicPtr; 1024] = unsafe { MaybeUninit::zeroed().assume_init() }; +static START_INDEX: AtomicUsize = unsafe { MaybeUninit::zeroed().assume_init() }; +static END_INDEX: AtomicUsize = unsafe { MaybeUninit::zeroed().assume_init() }; + +pub fn add_line(log_line: Info) { + let mut index = END_INDEX.fetch_add(1, Ordering::Acquire); + unsafe { + index %= LOG_LINES.len(); + let ptr = &mut LOG_LINES[index]; + let line = Box::new(log_line); + let old = ptr.swap(Box::into_raw(line), Ordering::SeqCst); + if !old.is_null() { + _ = Box::from_raw(old); + } + } +} + +pub fn flush() -> Vec { + let mut vec = Vec::new(); + let end_index = END_INDEX.load(Ordering::Acquire); + let start_index = START_INDEX.load(Ordering::Acquire); + if end_index <= start_index { + return vec; + } + unsafe { + let count = end_index - start_index; + for i in start_index..start_index + count { + let index = i % LOG_LINES.len(); + let ptr = LOG_LINES[index].swap(core::ptr::null_mut(), Ordering::SeqCst); + if !ptr.is_null() { + vec.push(*Box::from_raw(ptr)); + } + } + } + + START_INDEX.store(end_index, Ordering::Release); + vec +} + +#[macro_export] +macro_rules! log_internal { + ($log_line:expr, $($arg:tt)*) => ({ + use core::fmt::Write; + _ = write!($log_line, "{}:{} ", file!(), line!()); + _ = write!($log_line, $($arg)*); + $crate::logger::add_line($log_line); + }); +} + +#[macro_export] +macro_rules! crit { + ($($arg:tt)*) => ({ + if protocol::info::Severity::Critical as u8 >= $crate::logger::LOG_LEVEL { + let message = alloc::format!($($arg)*); + $crate::logger::add_line(protocol::info::Severity::Critical, alloc::format!("{}:{} ", file!(), line!()), message) + } + }); +} + +#[macro_export] +macro_rules! err { + ($($arg:tt)*) => ({ + if protocol::info::Severity::Error as u8 >= $crate::logger::LOG_LEVEL { + let mut log_line = protocol::info::log_line(protocol::info::Severity::Error, $crate::logger::MAX_LOG_LINE_SIZE); + $crate::log_internal!(log_line, $($arg)*); + } + }); +} + +#[macro_export] +macro_rules! warn { + ($($arg:tt)*) => ({ + if protocol::info::Severity::Warning as u8 >= $crate::logger::LOG_LEVEL { + let mut log_line = protocol::info::log_line(protocol::info::Severity::Warning, $crate::logger::MAX_LOG_LINE_SIZE); + $crate::log_internal!(log_line, $($arg)*); + } + }); +} + +#[macro_export] +macro_rules! dbg { + ($($arg:tt)*) => ({ + if protocol::info::Severity::Debug as u8 >= $crate::logger::LOG_LEVEL { + let mut log_line = protocol::info::log_line(protocol::info::Severity::Debug, $crate::logger::MAX_LOG_LINE_SIZE); + $crate::log_internal!(log_line, $($arg)*); + } + }); +} + +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => ({ + if protocol::info::Severity::Info as u8 >= $crate::logger::LOG_LEVEL { + let mut log_line = protocol::info::log_line(protocol::info::Severity::Info, $crate::logger::MAX_LOG_LINE_SIZE); + $crate::log_internal!(log_line, $($arg)*); + } + }); +} diff --git a/windows_kext/driver/src/packet_callouts.rs b/windows_kext/driver/src/packet_callouts.rs new file mode 100644 index 000000000..9971d6afb --- /dev/null +++ b/windows_kext/driver/src/packet_callouts.rs @@ -0,0 +1,298 @@ +use alloc::string::String; +use smoltcp::wire::{IPV4_HEADER_LEN, IPV6_HEADER_LEN}; +use wdk::filter_engine::callout_data::CalloutData; +use wdk::filter_engine::layer; +use wdk::filter_engine::net_buffer::{NetBufferList, NetBufferListIter}; +use wdk::filter_engine::packet::InjectInfo; + +use crate::connection::{ + Connection, ConnectionV4, ConnectionV6, Direction, RedirectInfo, Verdict, PM_DNS_PORT, + PM_SPN_PORT, +}; +use crate::connection_cache::ConnectionCache; +use crate::connection_map::Key; +use crate::device::{Device, Packet}; +use crate::packet_util::{get_key_from_nbl_v4, get_key_from_nbl_v6, Redirect}; +use crate::{err, warn}; + +// IP packet layers +pub fn ip_packet_layer_outbound_v4(data: CalloutData) { + type Fields = layer::FieldsOutboundIppacketV4; + let interface_index = data.get_value_u32(Fields::InterfaceIndex as usize); + let sub_interface_index = data.get_value_u32(Fields::SubInterfaceIndex as usize); + + ip_packet_layer( + data, + false, + Direction::Outbound, + interface_index, + sub_interface_index, + ); +} + +pub fn ip_packet_layer_inbound_v4(data: CalloutData) { + type Fields = layer::FieldsInboundIppacketV4; + let interface_index = data.get_value_u32(Fields::InterfaceIndex as usize); + let sub_interface_index = data.get_value_u32(Fields::SubInterfaceIndex as usize); + ip_packet_layer( + data, + false, + Direction::Inbound, + interface_index, + sub_interface_index, + ); +} + +pub fn ip_packet_layer_outbound_v6(data: CalloutData) { + type Fields = layer::FieldsOutboundIppacketV6; + let interface_index = data.get_value_u32(Fields::InterfaceIndex as usize); + let sub_interface_index = data.get_value_u32(Fields::SubInterfaceIndex as usize); + + ip_packet_layer( + data, + true, + Direction::Outbound, + interface_index, + sub_interface_index, + ); +} + +pub fn ip_packet_layer_inbound_v6(data: CalloutData) { + type Fields = layer::FieldsInboundIppacketV6; + let interface_index = data.get_value_u32(Fields::InterfaceIndex as usize); + let sub_interface_index = data.get_value_u32(Fields::SubInterfaceIndex as usize); + + ip_packet_layer( + data, + true, + Direction::Inbound, + interface_index, + sub_interface_index, + ); +} + +struct ConnectionInfo { + verdict: Verdict, + process_id: u64, + redirect_info: Option, +} + +impl ConnectionInfo { + fn from_connection(conn: &T) -> Self { + ConnectionInfo { + verdict: conn.get_verdict(), + process_id: conn.get_process_id(), + redirect_info: conn.redirect_info(), + } + } +} + +fn fast_track_pm_packets(key: &Key, direction: Direction) -> bool { + match direction { + Direction::Outbound => { + if key.local_port == PM_DNS_PORT || key.local_port == PM_SPN_PORT { + return key.local_address == key.remote_address; + } + } + Direction::Inbound => { + if key.local_port == PM_DNS_PORT || key.local_port == PM_SPN_PORT { + return key.local_address == key.remote_address; + } + } + } + + return false; +} + +fn ip_packet_layer( + mut data: CalloutData, + ipv6: bool, + direction: Direction, + interface_index: u32, + sub_interface_index: u32, +) { + let Some(device) = crate::entry::get_device() else { + return; + }; + if device + .injector + .was_network_packet_injected_by_self(data.get_layer_data() as _, ipv6) + { + data.action_permit(); + return; + } + + for mut nbl in NetBufferListIter::new(data.get_layer_data() as _) { + if let Direction::Inbound = direction { + // The header is not part of the NBL for incoming packets. Move the beginning of the buffer back so we get access to it. + // The NBL will auto advance after it loses scope. + if ipv6 { + nbl.retreat(IPV6_HEADER_LEN as u32, true); + } else { + nbl.retreat(IPV4_HEADER_LEN as u32, true); + } + } + + // Get key from packet. + let key = match if ipv6 { + get_key_from_nbl_v6(&nbl, direction) + } else { + get_key_from_nbl_v4(&nbl, direction) + } { + Ok(key) => key, + Err(err) => { + warn!("failed to get key from nbl: {}", err); + return; + } + }; + + if fast_track_pm_packets(&key, direction) { + data.action_permit(); + return; + } + + let mut is_tmp_verdict = false; + let mut process_id = 0; + + if matches!( + key.protocol, + smoltcp::wire::IpProtocol::Tcp | smoltcp::wire::IpProtocol::Udp + ) { + if let Some(mut conn_info) = + get_connection_info(&mut device.connection_cache, &key, ipv6) + { + process_id = conn_info.process_id; + // Check if there is action for this connection. + match conn_info.verdict { + Verdict::Undecided | Verdict::Accept | Verdict::Block | Verdict::Drop => { + is_tmp_verdict = true + } + Verdict::PermanentAccept => data.action_permit(), + Verdict::PermanentBlock => data.action_block(), + Verdict::Undeterminable | Verdict::PermanentDrop | Verdict::Failed => { + data.block_and_absorb() + } + Verdict::RedirectNameServer | Verdict::RedirectTunnel => { + if let Some(redirect_info) = conn_info.redirect_info.take() { + match clone_packet( + device, + nbl, + direction, + ipv6, + key.is_loopback(), + interface_index, + sub_interface_index, + ) { + Ok(mut packet) => { + let _ = packet.redirect(redirect_info); + if let Err(err) = device.inject_packet(packet, false) { + err!("failed to inject packet: {}", err); + } + } + Err(err) => err!("failed to clone packet: {}", err), + } + } + + // This will block the original packet. Even if injection failed. + data.block_and_absorb(); + continue; + } + } + } else { + // TCP and UDP always need to go through ALE layer first. + if matches!(direction, Direction::Inbound) { + // If it's an inbound packet and the connection is not found, we need to continue to ALE layer + data.action_permit(); + return; + } else { + // This happens sometimes. Leave the decision for portmaster. TODO(vladimir): Find out why. + err!("Invalid state for: {}", key); + is_tmp_verdict = true; + } + } + } else { + // Every other protocol treat as a tmp verdict. + is_tmp_verdict = true; + } + + // Clone packet and send to Portmaster if it's a temporary verdict. + if is_tmp_verdict { + let packet = match clone_packet( + device, + nbl, + direction, + ipv6, + key.is_loopback(), + interface_index, + sub_interface_index, + ) { + Ok(p) => p, + Err(err) => { + err!("failed to clone packet: {}", err); + return; + } + }; + + let info = device + .packet_cache + .push((key, packet), process_id, direction, false); + // Send to Portmaster + if let Some(info) = info { + let _ = device.event_queue.push(info); + } + data.block_and_absorb(); + } + } +} + +fn clone_packet( + device: &mut Device, + nbl: NetBufferList, + direction: Direction, + ipv6: bool, + loopback: bool, + interface_index: u32, + sub_interface_index: u32, +) -> Result { + let clone = nbl.clone(&device.network_allocator)?; + let inbound = match direction { + Direction::Outbound => false, + Direction::Inbound => true, + }; + Ok(Packet::PacketLayer( + clone, + InjectInfo { + ipv6, + inbound, + loopback, + interface_index, + sub_interface_index, + }, + )) +} + +fn get_connection_info( + connection_cache: &mut ConnectionCache, + key: &Key, + ipv6: bool, +) -> Option { + if ipv6 { + let conn_info = connection_cache.read_connection_v6( + &key, + |conn: &ConnectionV6| -> Option { + // Function is is behind spin lock. Just copy and return. + Some(ConnectionInfo::from_connection(conn)) + }, + ); + return conn_info; + } else { + let conn_info = connection_cache.read_connection_v4( + &key, + |conn: &ConnectionV4| -> Option { + // Function is is behind spin lock. Just copy and return. + Some(ConnectionInfo::from_connection(conn)) + }, + ); + return conn_info; + } +} diff --git a/windows_kext/driver/src/packet_util.rs b/windows_kext/driver/src/packet_util.rs new file mode 100644 index 000000000..42b4dac75 --- /dev/null +++ b/windows_kext/driver/src/packet_util.rs @@ -0,0 +1,399 @@ +use alloc::string::{String, ToString}; +use smoltcp::wire::{ + IpAddress, IpProtocol, Ipv4Address, Ipv4Packet, Ipv6Address, Ipv6Packet, TcpPacket, UdpPacket, +}; +use wdk::filter_engine::net_buffer::NetBufferList; + +use crate::connection_map::Key; +use crate::device::Packet; +use crate::{ + connection::{Direction, RedirectInfo}, + dbg, err, +}; + +/// `Redirect` is a trait that defines a method for redirecting network packets. +/// +/// This trait is used to implement different strategies for redirecting packets, +/// depending on the specific requirements of the application. +pub trait Redirect { + /// Redirects a network packet based on the provided `RedirectInfo`. + /// + /// # Arguments + /// + /// * `redirect_info` - A struct containing information about how to redirect the packet. + /// + /// # Returns + /// + /// * `Ok(())` if the packet was successfully redirected. + /// * `Err(String)` if there was an error redirecting the packet. + fn redirect(&mut self, redirect_info: RedirectInfo) -> Result<(), String>; +} + +impl Redirect for Packet { + fn redirect(&mut self, redirect_info: RedirectInfo) -> Result<(), String> { + if let Packet::PacketLayer(nbl, inject_info) = self { + let Some(data) = nbl.get_data_mut() else { + return Err("trying to redirect immutable NBL".to_string()); + }; + + if inject_info.inbound { + redirect_inbound_packet( + data, + redirect_info.local_address, + redirect_info.remote_address, + redirect_info.remote_port, + ) + } else { + redirect_outbound_packet( + data, + redirect_info.redirect_address, + redirect_info.redirect_port, + redirect_info.unify, + ) + } + return Ok(()); + } + // return Err("can't redirect from non packet layer".to_string()); + return Ok(()); + } +} + +/// Redirects an outbound packet to a specified remote address and port. +/// +/// # Arguments +/// +/// * `packet` - A mutable reference to the packet data. +/// * `remote_address` - The IP address to redirect the packet to. +/// * `remote_port` - The port to redirect the packet to. +/// * `unify` - If true, the source and destination addresses of the packet will be set to the same value. +/// +/// This function modifies the packet in-place to change its destination address and port. +/// It also updates the checksums for the IP and transport layer headers. +/// If the `unify` parameter is true, it sets the source and destination addresses to be the same. +/// If the remote address is a loopback address, it sets the source address to the loopback address. +fn redirect_outbound_packet( + packet: &mut [u8], + remote_address: IpAddress, + remote_port: u16, + unify: bool, +) { + match remote_address { + IpAddress::Ipv4(remote_address) => { + if let Ok(mut ip_packet) = Ipv4Packet::new_checked(packet) { + if unify { + ip_packet.set_dst_addr(ip_packet.src_addr()); + } else { + ip_packet.set_dst_addr(remote_address); + if remote_address.is_loopback() { + ip_packet.set_src_addr(Ipv4Address::new(127, 0, 0, 1)); + } + } + ip_packet.fill_checksum(); + let src_addr = ip_packet.src_addr(); + let dst_addr = ip_packet.dst_addr(); + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { + udp_packet.set_dst_port(remote_port); + udp_packet + .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { + tcp_packet.set_dst_port(remote_port); + tcp_packet + .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); + } + } + } + } + IpAddress::Ipv6(remote_address) => { + if let Ok(mut ip_packet) = Ipv6Packet::new_checked(packet) { + ip_packet.set_dst_addr(remote_address); + if unify { + ip_packet.set_dst_addr(ip_packet.src_addr()); + } else { + ip_packet.set_dst_addr(remote_address); + if remote_address.is_loopback() { + ip_packet.set_src_addr(Ipv6Address::LOOPBACK); + } + } + let src_addr = ip_packet.src_addr(); + let dst_addr = ip_packet.dst_addr(); + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { + udp_packet.set_dst_port(remote_port); + udp_packet + .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { + tcp_packet.set_dst_port(remote_port); + tcp_packet + .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); + } + } + } + } + } +} + +/// Redirects an inbound packet to a local address. +/// +/// This function takes a mutable reference to a packet and modifies it in place. +/// It changes the destination address to the provided local address and the source address +/// to the original remote address. It also sets the source port to the original remote port. +/// The function handles both IPv4 and IPv6 addresses. +/// +/// # Arguments +/// +/// * `packet` - A mutable reference to the packet data. +/// * `local_address` - The local IP address to redirect the packet to. +/// * `original_remote_address` - The original remote IP address of the packet. +/// * `original_remote_port` - The original remote port of the packet. +/// +fn redirect_inbound_packet( + packet: &mut [u8], + local_address: IpAddress, + original_remote_address: IpAddress, + original_remote_port: u16, +) { + match local_address { + IpAddress::Ipv4(local_address) => { + let IpAddress::Ipv4(original_remote_address) = original_remote_address else { + return; + }; + + if let Ok(mut ip_packet) = Ipv4Packet::new_checked(packet) { + ip_packet.set_dst_addr(local_address); + ip_packet.set_src_addr(original_remote_address); + ip_packet.fill_checksum(); + let src_addr = ip_packet.src_addr(); + let dst_addr = ip_packet.dst_addr(); + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { + udp_packet.set_src_port(original_remote_port); + udp_packet + .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { + tcp_packet.set_src_port(original_remote_port); + tcp_packet + .fill_checksum(&IpAddress::Ipv4(src_addr), &IpAddress::Ipv4(dst_addr)); + } + } + } + } + IpAddress::Ipv6(local_address) => { + if let Ok(mut ip_packet) = Ipv6Packet::new_checked(packet) { + let IpAddress::Ipv6(original_remote_address) = original_remote_address else { + return; + }; + ip_packet.set_dst_addr(local_address); + ip_packet.set_src_addr(original_remote_address); + let src_addr = ip_packet.src_addr(); + let dst_addr = ip_packet.dst_addr(); + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(mut udp_packet) = UdpPacket::new_checked(ip_packet.payload_mut()) { + udp_packet.set_src_port(original_remote_port); + udp_packet + .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(mut tcp_packet) = TcpPacket::new_checked(ip_packet.payload_mut()) { + tcp_packet.set_src_port(original_remote_port); + tcp_packet + .fill_checksum(&IpAddress::Ipv6(src_addr), &IpAddress::Ipv6(dst_addr)); + } + } + } + } + } +} + +#[allow(dead_code)] +fn print_packet(packet: &[u8]) { + if let Ok(ip_packet) = Ipv4Packet::new_checked(packet) { + if ip_packet.next_header() == IpProtocol::Udp { + if let Ok(udp_packet) = UdpPacket::new_checked(ip_packet.payload()) { + dbg!("packet {} {}", ip_packet, udp_packet); + } + } + if ip_packet.next_header() == IpProtocol::Tcp { + if let Ok(tcp_packet) = TcpPacket::new_checked(ip_packet.payload()) { + dbg!("packet {} {}", ip_packet, tcp_packet); + } + } + } else { + err!("failed to print packet: invalid ip header: {:?}", packet); + } +} + +/// This function extracts a key from a given IPv4 network buffer list (NBL). +/// The key contains the protocol, local and remote addresses and ports. +/// +/// # Arguments +/// +/// * `nbl` - A reference to the network buffer list from which the key will be extracted. +/// * `direction` - The direction of the packet (Inbound or Outbound). +/// +/// # Returns +/// +/// * `Ok(Key)` - A key containing the protocol, local and remote addresses and ports. +/// * `Err(String)` - An error message if the function fails to get net_buffer data. +const HEADERS_LEN: usize = smoltcp::wire::IPV4_HEADER_LEN + smoltcp::wire::TCP_HEADER_LEN; + +fn get_ports(packet: &[u8], protocol: smoltcp::wire::IpProtocol) -> (u16, u16) { + match protocol { + smoltcp::wire::IpProtocol::Tcp => { + let tcp_packet = TcpPacket::new_unchecked(packet); + (tcp_packet.src_port(), tcp_packet.dst_port()) + } + smoltcp::wire::IpProtocol::Udp => { + let udp_packet = UdpPacket::new_unchecked(packet); + (udp_packet.src_port(), udp_packet.dst_port()) + } + _ => (0, 0), // No ports for other protocols + } +} + +pub fn get_key_from_nbl_v4(nbl: &NetBufferList, direction: Direction) -> Result { + // Get bytes + let mut headers = [0; HEADERS_LEN]; + if nbl.read_bytes(&mut headers).is_err() { + return Err("failed to get net_buffer data".to_string()); + } + + // Parse packet + let ip_packet = Ipv4Packet::new_unchecked(&headers); + let (src_port, dst_port) = get_ports( + &headers[smoltcp::wire::IPV4_HEADER_LEN..], + ip_packet.next_header(), + ); + + // Build key + match direction { + Direction::Outbound => Ok(Key { + protocol: ip_packet.next_header(), + local_address: IpAddress::Ipv4(ip_packet.src_addr()), + local_port: src_port, + remote_address: IpAddress::Ipv4(ip_packet.dst_addr()), + remote_port: dst_port, + }), + Direction::Inbound => Ok(Key { + protocol: ip_packet.next_header(), + local_address: IpAddress::Ipv4(ip_packet.dst_addr()), + local_port: dst_port, + remote_address: IpAddress::Ipv4(ip_packet.src_addr()), + remote_port: src_port, + }), + } +} + +/// This function extracts a key from a given IPv6 network buffer list (NBL). +/// The key contains the protocol, local and remote addresses and ports. +/// +/// # Arguments +/// +/// * `nbl` - A reference to the network buffer list from which the key will be extracted. +/// * `direction` - The direction of the packet (Inbound or Outbound). +/// +/// # Returns +/// +/// * `Ok(Key)` - A key containing the protocol, local and remote addresses and ports. +/// * `Err(String)` - An error message if the function fails to get net_buffer data. +pub fn get_key_from_nbl_v6(nbl: &NetBufferList, direction: Direction) -> Result { + // Get bytes + let mut headers = [0; smoltcp::wire::IPV6_HEADER_LEN + smoltcp::wire::TCP_HEADER_LEN]; + let Ok(()) = nbl.read_bytes(&mut headers) else { + return Err("failed to get net_buffer data".to_string()); + }; + // Parse packet + let ip_packet = Ipv6Packet::new_unchecked(&headers); + let (src_port, dst_port) = get_ports( + &headers[smoltcp::wire::IPV6_HEADER_LEN..], + ip_packet.next_header(), + ); + + // Build key + match direction { + Direction::Outbound => Ok(Key { + protocol: ip_packet.next_header(), + local_address: IpAddress::Ipv6(ip_packet.src_addr()), + local_port: src_port, + remote_address: IpAddress::Ipv6(ip_packet.dst_addr()), + remote_port: dst_port, + }), + Direction::Inbound => Ok(Key { + protocol: ip_packet.next_header(), + local_address: IpAddress::Ipv6(ip_packet.dst_addr()), + local_port: dst_port, + remote_address: IpAddress::Ipv6(ip_packet.src_addr()), + remote_port: src_port, + }), + } +} + +// Converts a given key into connection information. +// +// This function takes a key, packet id, process id, and direction as input. +// It then uses these to create a new `ConnectionInfoV6` or `ConnectionInfoV4` object, +// depending on whether the IP addresses in the key are IPv6 or IPv4 respectively. +// +// # Arguments +// +// * `key` - A reference to the key object containing the connection details. +// * `packet_id` - The id of the packet. +// * `process_id` - The id of the process. +// * `direction` - The direction of the connection. +// +// # Returns +// +// * `Some(Box)` - A boxed `Info` trait object if the key contains valid IPv4 or IPv6 addresses. +// * `None` - If the key does not contain valid IPv4 or IPv6 addresses. +// pub fn key_to_connection_info( +// key: &Key, +// packet_id: u64, +// process_id: u64, +// direction: Direction, +// payload: &[u8], +// ) -> Option { +// let (local_port, remote_port) = match key.protocol { +// IpProtocol::Tcp | IpProtocol::Udp => (key.local_port, key.remote_port), +// _ => (0, 0), +// }; + +// match (key.local_address, key.remote_address) { +// (IpAddress::Ipv6(local_ip), IpAddress::Ipv6(remote_ip)) if key.is_ipv6() => { +// Some(protocol::info::connection_info_v6( +// packet_id, +// process_id, +// direction as u8, +// u8::from(key.protocol), +// local_ip.0, +// remote_ip.0, +// local_port, +// remote_port, +// payload, +// )) +// } +// (IpAddress::Ipv4(local_ip), IpAddress::Ipv4(remote_ip)) => { +// Some(protocol::info::connection_info_v4( +// packet_id, +// process_id, +// direction as u8, +// u8::from(key.protocol), +// local_ip.0, +// remote_ip.0, +// local_port, +// remote_port, +// payload, +// )) +// } +// _ => None, +// } +// } diff --git a/windows_kext/driver/src/stream_callouts.rs b/windows_kext/driver/src/stream_callouts.rs new file mode 100644 index 000000000..a63937643 --- /dev/null +++ b/windows_kext/driver/src/stream_callouts.rs @@ -0,0 +1,203 @@ +use smoltcp::wire::{Ipv4Address, Ipv6Address}; +use wdk::filter_engine::{callout_data::CalloutData, layer, net_buffer::NetBufferListIter}; + +use crate::{bandwidth, connection::Direction}; + +pub fn stream_layer_tcp_v4(data: CalloutData) { + let Some(device) = crate::entry::get_device() else { + return; + }; + let mut direction = Direction::Outbound; + let data_length = if let Some(packet) = data.get_stream_callout_packet() { + if packet.is_receive() { + direction = Direction::Inbound; + } + packet.get_data_len() + } else { + return; + }; + type Fields = layer::FieldsStreamV4; + let local_ip = Ipv4Address::from_bytes( + &data + .get_value_u32(Fields::IpLocalAddress as usize) + .to_be_bytes(), + ); + let local_port = data.get_value_u16(Fields::IpLocalPort as usize); + let remote_ip = Ipv4Address::from_bytes( + &data + .get_value_u32(Fields::IpRemoteAddress as usize) + .to_be_bytes(), + ); + let remote_port = data.get_value_u16(Fields::IpRemotePort as usize); + match direction { + Direction::Outbound => { + device.bandwidth_stats.update_tcp_v4_tx( + bandwidth::Key { + local_ip, + local_port, + remote_ip, + remote_port, + }, + data_length, + ); + } + Direction::Inbound => { + device.bandwidth_stats.update_tcp_v4_rx( + bandwidth::Key { + local_ip, + local_port, + remote_ip, + remote_port, + }, + data_length, + ); + } + } +} + +pub fn stream_layer_tcp_v6(data: CalloutData) { + let Some(device) = crate::entry::get_device() else { + return; + }; + let mut direction = Direction::Outbound; + let data_length = if let Some(packet) = data.get_stream_callout_packet() { + if packet.is_receive() { + direction = Direction::Inbound; + } + packet.get_data_len() + } else { + return; + }; + type Fields = layer::FieldsStreamV6; + if data_length == 0 { + return; + } + let local_ip = + Ipv6Address::from_bytes(data.get_value_byte_array16(Fields::IpLocalAddress as usize)); + let local_port = data.get_value_u16(Fields::IpLocalPort as usize); + let remote_ip = + Ipv6Address::from_bytes(data.get_value_byte_array16(Fields::IpRemoteAddress as usize)); + let remote_port = data.get_value_u16(Fields::IpRemotePort as usize); + match direction { + Direction::Outbound => { + device.bandwidth_stats.update_tcp_v6_tx( + bandwidth::Key { + local_ip, + local_port, + remote_ip, + remote_port, + }, + data_length, + ); + } + Direction::Inbound => { + device.bandwidth_stats.update_tcp_v6_rx( + bandwidth::Key { + local_ip, + local_port, + remote_ip, + remote_port, + }, + data_length, + ); + } + } +} + +pub fn stream_layer_udp_v4(data: CalloutData) { + let Some(device) = crate::entry::get_device() else { + return; + }; + let mut data_length: usize = 0; + for nbl in NetBufferListIter::new(data.get_layer_data() as _) { + data_length += nbl.get_data_length() as usize; + } + type Fields = layer::FieldsDatagramDataV4; + let mut direction = Direction::Inbound; + if data.get_value_u8(Fields::Direction as usize) == 0 { + direction = Direction::Outbound; + } + + let local_ip = Ipv4Address::from_bytes( + &data + .get_value_u32(Fields::IpLocalAddress as usize) + .to_be_bytes(), + ); + let local_port = data.get_value_u16(Fields::IpLocalPort as usize); + let remote_ip = Ipv4Address::from_bytes( + &data + .get_value_u32(Fields::IpRemoteAddress as usize) + .to_be_bytes(), + ); + let remote_port = data.get_value_u16(Fields::IpRemotePort as usize); + match direction { + Direction::Outbound => { + device.bandwidth_stats.update_udp_v4_tx( + bandwidth::Key { + local_ip, + local_port, + remote_ip, + remote_port, + }, + data_length, + ); + } + Direction::Inbound => { + device.bandwidth_stats.update_udp_v4_rx( + bandwidth::Key { + local_ip, + local_port, + remote_ip, + remote_port, + }, + data_length, + ); + } + } +} + +pub fn stream_layer_udp_v6(data: CalloutData) { + let Some(device) = crate::entry::get_device() else { + return; + }; + let mut data_length: usize = 0; + for nbl in NetBufferListIter::new(data.get_layer_data() as _) { + data_length += nbl.get_data_length() as usize; + } + type Fields = layer::FieldsDatagramDataV6; + let mut direction = Direction::Inbound; + if data.get_value_u8(Fields::Direction as usize) == 0 { + direction = Direction::Outbound; + } + + let local_ip = + Ipv6Address::from_bytes(data.get_value_byte_array16(Fields::IpLocalAddress as usize)); + let local_port = data.get_value_u16(Fields::IpLocalPort as usize); + let remote_ip = + Ipv6Address::from_bytes(data.get_value_byte_array16(Fields::IpRemoteAddress as usize)); + let remote_port = data.get_value_u16(Fields::IpRemotePort as usize); + match direction { + Direction::Outbound => { + device.bandwidth_stats.update_udp_v6_tx( + bandwidth::Key { + local_ip, + local_port, + remote_ip, + remote_port, + }, + data_length, + ); + } + Direction::Inbound => { + device.bandwidth_stats.update_udp_v6_rx( + bandwidth::Key { + local_ip, + local_port, + remote_ip, + remote_port, + }, + data_length, + ); + } + } +} diff --git a/windows_kext/kextinterface/command.go b/windows_kext/kextinterface/command.go new file mode 100644 index 000000000..b40be18c7 --- /dev/null +++ b/windows_kext/kextinterface/command.go @@ -0,0 +1,119 @@ +package kextinterface + +import ( + "encoding/binary" + "io" +) + +// Command IDs. +const ( + CommandShutdown = 0 + CommandVerdict = 1 + CommandUpdateV4 = 2 + CommandUpdateV6 = 3 + CommandClearCache = 4 + CommandGetLogs = 5 + CommandBandwidthStats = 6 + CommandPrintMemoryStats = 7 + CommandCleanEndedConnections = 8 +) + +// KextVerdict is the verdict ID used to with the kext. +type KextVerdict uint8 + +// Kext Verdicts. +// Make sure this is in sync with the Rust version. +const ( + // VerdictUndecided is the default status of new connections. + VerdictUndecided KextVerdict = 0 + VerdictUndeterminable KextVerdict = 1 + VerdictAccept KextVerdict = 2 + VerdictPermanentAccept KextVerdict = 3 + VerdictBlock KextVerdict = 4 + VerdictPermanentBlock KextVerdict = 5 + VerdictDrop KextVerdict = 6 + VerdictPermanentDrop KextVerdict = 7 + VerdictRerouteToNameserver KextVerdict = 8 + VerdictRerouteToTunnel KextVerdict = 9 + VerdictFailed KextVerdict = 10 +) + +type Verdict struct { + command uint8 + ID uint64 + Verdict uint8 +} + +type UpdateV4 struct { + command uint8 + Protocol uint8 + LocalAddress [4]byte + LocalPort uint16 + RemoteAddress [4]byte + RemotePort uint16 + Verdict uint8 +} + +type UpdateV6 struct { + command uint8 + Protocol uint8 + LocalAddress [16]byte + LocalPort uint16 + RemoteAddress [16]byte + RemotePort uint16 + Verdict uint8 +} + +// SendShutdownCommand sends a Shutdown command to the kext. +func SendShutdownCommand(writer io.Writer) error { + _, err := writer.Write([]byte{CommandShutdown}) + return err +} + +// SendVerdictCommand sends a Verdict command to the kext. +func SendVerdictCommand(writer io.Writer, verdict Verdict) error { + verdict.command = CommandVerdict + return binary.Write(writer, binary.LittleEndian, verdict) +} + +// SendUpdateV4Command sends a UpdateV4 command to the kext. +func SendUpdateV4Command(writer io.Writer, update UpdateV4) error { + update.command = CommandUpdateV4 + return binary.Write(writer, binary.LittleEndian, update) +} + +// SendUpdateV6Command sends a UpdateV6 command to the kext. +func SendUpdateV6Command(writer io.Writer, update UpdateV6) error { + update.command = CommandUpdateV6 + return binary.Write(writer, binary.LittleEndian, update) +} + +// SendClearCacheCommand sends a ClearCache command to the kext. +func SendClearCacheCommand(writer io.Writer) error { + _, err := writer.Write([]byte{CommandClearCache}) + return err +} + +// SendGetLogsCommand sends a GetLogs command to the kext. +func SendGetLogsCommand(writer io.Writer) error { + _, err := writer.Write([]byte{CommandGetLogs}) + return err +} + +// SendGetBandwidthStatsCommand sends a GetBandwidthStats command to the kext. +func SendGetBandwidthStatsCommand(writer io.Writer) error { + _, err := writer.Write([]byte{CommandBandwidthStats}) + return err +} + +// SendPrintMemoryStatsCommand sends a PrintMemoryStats command to the kext. +func SendPrintMemoryStatsCommand(writer io.Writer) error { + _, err := writer.Write([]byte{CommandPrintMemoryStats}) + return err +} + +// SendCleanEndedConnectionsCommand sends a CleanEndedConnections command to the kext. +func SendCleanEndedConnectionsCommand(writer io.Writer) error { + _, err := writer.Write([]byte{CommandCleanEndedConnections}) + return err +} diff --git a/windows_kext/kextinterface/info.go b/windows_kext/kextinterface/info.go new file mode 100644 index 000000000..763c3e8e7 --- /dev/null +++ b/windows_kext/kextinterface/info.go @@ -0,0 +1,287 @@ +package kextinterface + +import ( + "encoding/binary" + "errors" + "io" +) + +const ( + InfoLogLine = 0 + InfoConnectionIpv4 = 1 + InfoConnectionIpv6 = 2 + InfoConnectionEndEventV4 = 3 + InfoConnectionEndEventV6 = 4 + InfoBandwidthStatsV4 = 5 + InfoBandwidthStatsV6 = 6 +) + +var ( + ErrUnknownInfoType = errors.New("unknown info type") + ErrUnexpectedReadError = errors.New("unexpected read error") +) + +type connectionV4Internal struct { + ID uint64 + ProcessID uint64 + Direction byte + Protocol byte + LocalIP [4]byte + RemoteIP [4]byte + LocalPort uint16 + RemotePort uint16 + PayloadLayer uint8 +} + +type ConnectionV4 struct { + connectionV4Internal + Payload []byte +} + +func (c *ConnectionV4) Compare(other *ConnectionV4) bool { + return c.ID == other.ID && + c.ProcessID == other.ProcessID && + c.Direction == other.Direction && + c.Protocol == other.Protocol && + c.LocalIP == other.LocalIP && + c.RemoteIP == other.RemoteIP && + c.LocalPort == other.LocalPort && + c.RemotePort == other.RemotePort +} + +type connectionV6Internal struct { + ID uint64 + ProcessID uint64 + Direction byte + Protocol byte + LocalIP [16]byte + RemoteIP [16]byte + LocalPort uint16 + RemotePort uint16 + PayloadLayer uint8 +} + +type ConnectionV6 struct { + connectionV6Internal + Payload []byte +} + +func (c ConnectionV6) Compare(other *ConnectionV6) bool { + return c.ID == other.ID && + c.ProcessID == other.ProcessID && + c.Direction == other.Direction && + c.Protocol == other.Protocol && + c.LocalIP == other.LocalIP && + c.RemoteIP == other.RemoteIP && + c.LocalPort == other.LocalPort && + c.RemotePort == other.RemotePort +} + +type ConnectionEndV4 struct { + ProcessID uint64 + Direction byte + Protocol byte + LocalIP [4]byte + RemoteIP [4]byte + LocalPort uint16 + RemotePort uint16 +} + +type ConnectionEndV6 struct { + ProcessID uint64 + Direction byte + Protocol byte + LocalIP [16]byte + RemoteIP [16]byte + LocalPort uint16 + RemotePort uint16 +} + +type LogLine struct { + Severity byte + Line string +} + +type BandwidthValueV4 struct { + LocalIP [4]byte + LocalPort uint16 + RemoteIP [4]byte + RemotePort uint16 + TransmittedBytes uint64 + ReceivedBytes uint64 +} + +type BandwidthValueV6 struct { + LocalIP [16]byte + LocalPort uint16 + RemoteIP [16]byte + RemotePort uint16 + TransmittedBytes uint64 + ReceivedBytes uint64 +} + +type BandwidthStatsArray struct { + Protocol uint8 + ValuesV4 []BandwidthValueV4 + ValuesV6 []BandwidthValueV6 +} + +type Info struct { + ConnectionV4 *ConnectionV4 + ConnectionV6 *ConnectionV6 + ConnectionEndV4 *ConnectionEndV4 + ConnectionEndV6 *ConnectionEndV6 + LogLine *LogLine + BandwidthStats *BandwidthStatsArray +} + +func RecvInfo(reader io.Reader) (*Info, error) { + var infoType byte + err := binary.Read(reader, binary.LittleEndian, &infoType) + if err != nil { + return nil, err + } + + // Read size of data + var size uint32 + err = binary.Read(reader, binary.LittleEndian, &size) + if err != nil { + return nil, err + } + + // Read data + switch infoType { + case InfoConnectionIpv4: + { + var fixedSizeValues connectionV4Internal + err = binary.Read(reader, binary.LittleEndian, &fixedSizeValues) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + // Read size of payload + var size uint32 + err = binary.Read(reader, binary.LittleEndian, &size) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + newInfo := ConnectionV4{connectionV4Internal: fixedSizeValues, Payload: make([]byte, size)} + err = binary.Read(reader, binary.LittleEndian, &newInfo.Payload) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + return &Info{ConnectionV4: &newInfo}, nil + } + case InfoConnectionIpv6: + { + var fixedSizeValues connectionV6Internal + err = binary.Read(reader, binary.LittleEndian, &fixedSizeValues) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + // Read size of payload + var size uint32 + err = binary.Read(reader, binary.LittleEndian, &size) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + newInfo := ConnectionV6{connectionV6Internal: fixedSizeValues, Payload: make([]byte, size)} + err = binary.Read(reader, binary.LittleEndian, &newInfo.Payload) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + return &Info{ConnectionV6: &newInfo}, nil + } + case InfoConnectionEndEventV4: + { + var connectionEnd ConnectionEndV4 + err = binary.Read(reader, binary.LittleEndian, &connectionEnd) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + return &Info{ConnectionEndV4: &connectionEnd}, nil + } + case InfoConnectionEndEventV6: + { + var connectionEnd ConnectionEndV6 + err = binary.Read(reader, binary.LittleEndian, &connectionEnd) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + return &Info{ConnectionEndV6: &connectionEnd}, nil + } + case InfoLogLine: + { + logLine := LogLine{} + // Read severity + err = binary.Read(reader, binary.LittleEndian, &logLine.Severity) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + // Read string + line := make([]byte, size-1) // -1 for the severity enum. + err = binary.Read(reader, binary.LittleEndian, &line) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + logLine.Line = string(line) + return &Info{LogLine: &logLine}, nil + } + case InfoBandwidthStatsV4: + { + // Read Protocol + var protocol uint8 + err = binary.Read(reader, binary.LittleEndian, &protocol) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + // Read size of array + var size uint32 + err = binary.Read(reader, binary.LittleEndian, &size) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + // Read array + statsArray := make([]BandwidthValueV4, size) + for i := range int(size) { + err = binary.Read(reader, binary.LittleEndian, &statsArray[i]) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + } + + return &Info{BandwidthStats: &BandwidthStatsArray{Protocol: protocol, ValuesV4: statsArray}}, nil + } + case InfoBandwidthStatsV6: + { + // Read Protocol + var protocol uint8 + err = binary.Read(reader, binary.LittleEndian, &protocol) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + // Read size of array + var size uint32 + err = binary.Read(reader, binary.LittleEndian, &size) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + // Read array + statsArray := make([]BandwidthValueV6, size) + for i := range int(size) { + err = binary.Read(reader, binary.LittleEndian, &statsArray[i]) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + } + + return &Info{BandwidthStats: &BandwidthStatsArray{Protocol: protocol, ValuesV6: statsArray}}, nil + } + } + + // Command not recognized, read until the end of command and return. + // During normal operation this should not happen. + unknownData := make([]byte, size) + _, _ = reader.Read(unknownData) + + return nil, ErrUnknownInfoType +} diff --git a/windows_kext/kextinterface/ioctl.go b/windows_kext/kextinterface/ioctl.go new file mode 100644 index 000000000..89ae03926 --- /dev/null +++ b/windows_kext/kextinterface/ioctl.go @@ -0,0 +1,35 @@ +//go:build windows +// +build windows + +package kextinterface + +import ( + "golang.org/x/sys/windows" +) + +const ( + METHOD_BUFFERED = 0 + METHOD_IN_DIRECT = 1 + METHOD_OUT_DIRECT = 2 + METHOD_NEITHER = 3 + + SIOCTL_TYPE = 40000 +) + +func ctlCode(device_type, function, method, access uint32) uint32 { + return (device_type << 16) | (access << 14) | (function << 2) | method +} + +var ( + IOCTL_VERSION = ctlCode(SIOCTL_TYPE, 0x800, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA) + IOCTL_SHUTDOWN_REQUEST = ctlCode(SIOCTL_TYPE, 0x801, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA) +) + +func ReadVersion(file *KextFile) ([]uint8, error) { + data := make([]uint8, 4) + _, err := file.deviceIOControl(IOCTL_VERSION, nil, data) + if err != nil { + return nil, err + } + return data, nil +} diff --git a/windows_kext/kextinterface/kext.go b/windows_kext/kextinterface/kext.go new file mode 100644 index 000000000..2707a791f --- /dev/null +++ b/windows_kext/kextinterface/kext.go @@ -0,0 +1,248 @@ +//go:build windows +// +build windows + +package kextinterface + +import ( + _ "embed" + "fmt" + "strconv" + "strings" + "syscall" + "time" + + "golang.org/x/sys/windows" +) + +var ( + //go:embed version.txt + versionTxt string + + // 4 byte version of the Kext interface + InterfaceVersion = func() (v [4]byte) { + // Parse version from file "version.txt". Expected format: [0, 1, 2, 3] + s := strings.TrimSpace(versionTxt) + s = strings.TrimPrefix(s, "[") + s = strings.TrimSuffix(s, "]") + str_ver := strings.Split(s, ",") + for i := range v { + n, err := strconv.Atoi(strings.TrimSpace(str_ver[i])) + if err != nil { + panic(err) + } + v[i] = byte(n) + } + return + }() +) + +const ( + winInvalidHandleValue = windows.Handle(^uintptr(0)) // Max value + stopServiceTimeoutDuration = time.Duration(30 * time.Second) +) + +type KextService struct { + handle windows.Handle + driverName string +} + +func (s *KextService) isValid() bool { + return s != nil && s.handle != winInvalidHandleValue && s.handle != 0 +} + +func (s *KextService) isRunning() (bool, error) { + if !s.isValid() { + return false, fmt.Errorf("kext service not initialized") + } + var status windows.SERVICE_STATUS + err := windows.QueryServiceStatus(s.handle, &status) + if err != nil { + return false, err + } + return status.CurrentState == windows.SERVICE_RUNNING, nil +} + +func (s *KextService) waitForServiceStatus(neededStatus uint32, timeLimit time.Duration) (bool, error) { + var status windows.SERVICE_STATUS + status.CurrentState = windows.SERVICE_NO_CHANGE + start := time.Now() + for status.CurrentState != neededStatus { + err := windows.QueryServiceStatus(s.handle, &status) + if err != nil { + return false, fmt.Errorf("failed while waiting for service to start: %w", err) + } + + if time.Since(start) > timeLimit { + return false, fmt.Errorf("time limit reached") + } + + // Sleep for 1/10 of the wait hint, recommended time from microsoft + time.Sleep(time.Duration((status.WaitHint / 10)) * time.Millisecond) + } + + return true, nil +} + +func (s *KextService) Start(wait bool) error { + if !s.isValid() { + return fmt.Errorf("kext service not initialized") + } + + // Start the service: + err := windows.StartService(s.handle, 0, nil) + if err != nil { + err = windows.GetLastError() + if err != windows.ERROR_SERVICE_ALREADY_RUNNING { + // Failed to start service; clean-up: + var status windows.SERVICE_STATUS + _ = windows.ControlService(s.handle, windows.SERVICE_CONTROL_STOP, &status) + _ = windows.DeleteService(s.handle) + _ = windows.CloseServiceHandle(s.handle) + s.handle = winInvalidHandleValue + return err + } + } + + // Wait for service to start + if wait { + success, err := s.waitForServiceStatus(windows.SERVICE_RUNNING, stopServiceTimeoutDuration) + if err != nil || !success { + return fmt.Errorf("service did not start: %w", err) + } + } + + return nil +} + +func (s *KextService) GetHandle() windows.Handle { + return s.handle +} + +func (s *KextService) Stop(wait bool) error { + if !s.isValid() { + return fmt.Errorf("kext service not initialized") + } + + // Stop the service + var status windows.SERVICE_STATUS + err := windows.ControlService(s.handle, windows.SERVICE_CONTROL_STOP, &status) + if err != nil { + return fmt.Errorf("service failed to stop: %w", err) + } + + // Wait for service to stop + if wait { + success, err := s.waitForServiceStatus(windows.SERVICE_STOPPED, time.Duration(10*time.Second)) + if err != nil || !success { + return fmt.Errorf("service did not stop: %w", err) + } + } + + return nil +} + +func (s *KextService) Delete() error { + if !s.isValid() { + return fmt.Errorf("kext service not initialized") + } + + err := windows.DeleteService(s.handle) + if err != nil { + return fmt.Errorf("failed to delete service: %s", err) + } + + // Service wont be deleted until all handles are closed. + err = windows.CloseServiceHandle(s.handle) + if err != nil { + return fmt.Errorf("failed to close service handle: %s", err) + } + + s.handle = winInvalidHandleValue + return nil +} + +func (s *KextService) WaitUntilDeleted(serviceManager windows.Handle) error { + driverNameU16, err := syscall.UTF16FromString(s.driverName) + if err != nil { + return fmt.Errorf("failed to convert driver name to UTF16 string: %w", err) + } + // Wait until we can no longer open the old service. + // Not very efficient but NotifyServiceStatusChange cannot be used with driver service. + start := time.Now() + timeLimit := time.Duration(30 * time.Second) + for { + handle, err := windows.OpenService(serviceManager, &driverNameU16[0], windows.SERVICE_ALL_ACCESS) + if err != nil { + break + } + _ = windows.CloseServiceHandle(handle) + + if time.Since(start) > timeLimit { + return fmt.Errorf("time limit reached") + } + + time.Sleep(100 * time.Millisecond) + } + + return nil +} + +func (s *KextService) OpenFile(readBufferSize int) (*KextFile, error) { + if !s.isValid() { + return nil, fmt.Errorf("invalid kext object") + } + + driverNameU16, err := syscall.UTF16FromString(`\\.\` + s.driverName) + if err != nil { + return nil, fmt.Errorf("failed to convert driver driverName to UTF16 string %w", err) + } + + handle, err := windows.CreateFile(&driverNameU16[0], windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED, 0) + if err != nil { + return nil, err + } + + return &KextFile{handle: handle, buffer: make([]byte, readBufferSize)}, nil +} + +func CreateKextService(driverName string, driverPath string) (*KextService, error) { + // Open the service manager: + manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS) + if err != nil { + return nil, fmt.Errorf("failed to open service manager: %d", err) + } + defer windows.CloseServiceHandle(manager) + + driverNameU16, err := syscall.UTF16FromString(driverName) + if err != nil { + return nil, fmt.Errorf("failed to convert driver name to UTF16 string: %w", err) + } + + // Check if there is an old service. + service, err := windows.OpenService(manager, &driverNameU16[0], windows.SERVICE_ALL_ACCESS) + if err == nil { + fmt.Println("kext: old driver service was found") + oldService := &KextService{handle: service, driverName: driverName} + oldService.Stop(true) + err = oldService.Delete() + if err != nil { + return nil, err + } + err := oldService.WaitUntilDeleted(manager) + if err != nil { + return nil, err + } + + service = winInvalidHandleValue + fmt.Println("kext: old driver service was deleted successfully") + } + + driverPathU16, err := syscall.UTF16FromString(driverPath) + + // Create the service + service, err = windows.CreateService(manager, &driverNameU16[0], &driverNameU16[0], windows.SERVICE_ALL_ACCESS, windows.SERVICE_KERNEL_DRIVER, windows.SERVICE_DEMAND_START, windows.SERVICE_ERROR_NORMAL, &driverPathU16[0], nil, nil, nil, nil, nil) + if err != nil { + return nil, err + } + return &KextService{handle: service, driverName: driverName}, nil +} diff --git a/windows_kext/kextinterface/kext_file.go b/windows_kext/kextinterface/kext_file.go new file mode 100644 index 000000000..045ee06e5 --- /dev/null +++ b/windows_kext/kextinterface/kext_file.go @@ -0,0 +1,97 @@ +//go:build windows +// +build windows + +package kextinterface + +import ( + "golang.org/x/sys/windows" +) + +type KextFile struct { + handle windows.Handle + buffer []byte + read_slice []byte +} + +func (f *KextFile) Read(buffer []byte) (int, error) { + if f.read_slice == nil || len(f.read_slice) == 0 { + err := f.refill_read_buffer() + if err != nil { + return 0, err + } + } + + if len(f.read_slice) >= len(buffer) { + // Write all requested bytes. + copy(buffer, f.read_slice[0:len(buffer)]) + f.read_slice = f.read_slice[len(buffer):] + } else { + // Write all available bytes and read again. + copy(buffer[0:len(f.read_slice)], f.read_slice) + copiedBytes := len(f.read_slice) + f.read_slice = nil + _, err := f.Read(buffer[copiedBytes:]) + if err != nil { + return 0, err + } + } + + return len(buffer), nil +} + +func (f *KextFile) refill_read_buffer() error { + var count uint32 = 0 + overlapped := &windows.Overlapped{} + err := windows.ReadFile(f.handle, f.buffer[:], &count, overlapped) + if err != nil { + return err + } + f.read_slice = f.buffer[0:count] + + return nil +} + +func (f *KextFile) Write(buffer []byte) (int, error) { + var count uint32 = 0 + overlapped := &windows.Overlapped{} + err := windows.WriteFile(f.handle, buffer, &count, overlapped) + return int(count), err +} + +func (f *KextFile) Close() error { + err := windows.CloseHandle(f.handle) + f.handle = winInvalidHandleValue + return err +} + +func (f *KextFile) deviceIOControl(code uint32, inData []byte, outData []byte) (*windows.Overlapped, error) { + var inDataPtr *byte = nil + var inDataSize uint32 = 0 + if inData != nil { + inDataPtr = &inData[0] + inDataSize = uint32(len(inData)) + } + + var outDataPtr *byte = nil + var outDataSize uint32 = 0 + if outData != nil { + outDataPtr = &outData[0] + outDataSize = uint32(len(outData)) + } + + overlapped := &windows.Overlapped{} + err := windows.DeviceIoControl(f.handle, + code, + inDataPtr, inDataSize, + outDataPtr, outDataSize, + nil, overlapped) + if err != nil { + return nil, err + } + + return overlapped, nil +} + +func (f *KextFile) GetHandle() windows.Handle { + return f.handle +} diff --git a/windows_kext/kextinterface/kext_file_test.go b/windows_kext/kextinterface/kext_file_test.go new file mode 100644 index 000000000..7dbd4d99b --- /dev/null +++ b/windows_kext/kextinterface/kext_file_test.go @@ -0,0 +1,12 @@ +//go:build linux +// +build linux + +package kextinterface + +type KextFile struct{} + +func (f *KextFile) Read(buffer []byte) (int, error) { + return 0, nil +} + +// func (f *KextFile) flushBuffer() {} diff --git a/windows_kext/kextinterface/protocol_test.go b/windows_kext/kextinterface/protocol_test.go new file mode 100644 index 000000000..cf0474426 --- /dev/null +++ b/windows_kext/kextinterface/protocol_test.go @@ -0,0 +1,275 @@ +package kextinterface + +import ( + "bytes" + "errors" + "math/rand" + "os" + "testing" +) + +func TestRustInfoFile(t *testing.T) { + t.Parallel() + + file, err := os.Open("testdata/rust_info_test.bin") + if err != nil { + panic(err) + } + defer func() { + _ = file.Close() + }() + for { + info, err := RecvInfo(file) + if err != nil { + if errors.Is(err, ErrUnexpectedReadError) { + t.Errorf("unexpected error: %s\n", err) + } + return + } + + switch { + case info.LogLine != nil: + if info.LogLine.Severity != 1 { + t.Errorf("unexpected Log severity: %d\n", info.LogLine.Severity) + } + if info.LogLine.Line != "prefix: test log" { + t.Errorf("unexpected Log line: %s\n", info.LogLine.Line) + } + + case info.ConnectionV4 != nil: + conn := info.ConnectionV4 + expected := connectionV4Internal{ + ID: 1, + ProcessID: 2, + Direction: 3, + Protocol: 4, + LocalIP: [4]byte{1, 2, 3, 4}, + RemoteIP: [4]byte{2, 3, 4, 5}, + LocalPort: 5, + RemotePort: 6, + PayloadLayer: 7, + } + if conn.connectionV4Internal != expected { + t.Errorf("unexpected ConnectionV4: %+v\n", conn) + } + if !bytes.Equal(conn.Payload, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { + t.Errorf("unexpected ConnectionV4 payload: %+v\n", conn.Payload) + } + + case info.ConnectionV6 != nil: + conn := info.ConnectionV6 + expected := connectionV6Internal{ + ID: 1, + ProcessID: 2, + Direction: 3, + Protocol: 4, + LocalIP: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + RemoteIP: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + LocalPort: 5, + RemotePort: 6, + PayloadLayer: 7, + } + if conn.connectionV6Internal != expected { + t.Errorf("unexpected ConnectionV6: %+v\n", conn) + } + if !bytes.Equal(conn.Payload, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { + t.Errorf("unexpected ConnectionV6 payload: %+v\n", conn.Payload) + } + + case info.ConnectionEndV4 != nil: + endEvent := info.ConnectionEndV4 + expected := ConnectionEndV4{ + ProcessID: 1, + Direction: 2, + Protocol: 3, + LocalIP: [4]byte{1, 2, 3, 4}, + RemoteIP: [4]byte{2, 3, 4, 5}, + LocalPort: 4, + RemotePort: 5, + } + if *endEvent != expected { + t.Errorf("unexpected ConnectionEndV4: %+v\n", endEvent) + } + + case info.ConnectionEndV6 != nil: + endEvent := info.ConnectionEndV6 + expected := ConnectionEndV6{ + ProcessID: 1, + Direction: 2, + Protocol: 3, + LocalIP: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + RemoteIP: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + LocalPort: 4, + RemotePort: 5, + } + if *endEvent != expected { + t.Errorf("unexpected ConnectionEndV6: %+v\n", endEvent) + } + + case info.BandwidthStats != nil: + stats := info.BandwidthStats + if stats.Protocol != 1 { + t.Errorf("unexpected Bandwidth stats protocol: %d\n", stats.Protocol) + } + + if stats.ValuesV4 != nil { + if len(stats.ValuesV4) != 2 { + t.Errorf("unexpected Bandwidth stats value length: %d\n", len(stats.ValuesV4)) + } + expected1 := BandwidthValueV4{ + LocalIP: [4]byte{1, 2, 3, 4}, + LocalPort: 1, + RemoteIP: [4]byte{2, 3, 4, 5}, + RemotePort: 2, + TransmittedBytes: 3, + ReceivedBytes: 4, + } + if stats.ValuesV4[0] != expected1 { + t.Errorf("unexpected Bandwidth stats value: %+v expected: %+v\n", stats.ValuesV4[0], expected1) + } + expected2 := BandwidthValueV4{ + LocalIP: [4]byte{1, 2, 3, 4}, + LocalPort: 5, + RemoteIP: [4]byte{2, 3, 4, 5}, + RemotePort: 6, + TransmittedBytes: 7, + ReceivedBytes: 8, + } + if stats.ValuesV4[1] != expected2 { + t.Errorf("unexpected Bandwidth stats value: %+v expected: %+v\n", stats.ValuesV4[1], expected2) + } + + } else if stats.ValuesV6 != nil { + if len(stats.ValuesV6) != 2 { + t.Errorf("unexpected Bandwidth stats value length: %d\n", len(stats.ValuesV6)) + } + + expected1 := BandwidthValueV6{ + LocalIP: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + LocalPort: 1, + RemoteIP: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + RemotePort: 2, + TransmittedBytes: 3, + ReceivedBytes: 4, + } + if stats.ValuesV6[0] != expected1 { + t.Errorf("unexpected Bandwidth stats value: %+v expected: %+v\n", stats.ValuesV6[0], expected1) + } + expected2 := BandwidthValueV6{ + LocalIP: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + LocalPort: 5, + RemoteIP: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + RemotePort: 6, + TransmittedBytes: 7, + ReceivedBytes: 8, + } + if stats.ValuesV6[1] != expected2 { + t.Errorf("unexpected Bandwidth stats value: %+v expected: %+v\n", stats.ValuesV6[1], expected2) + } + + } + } + } +} + +func TestGenerateCommandFile(t *testing.T) { + t.Parallel() + + file, err := os.Create("../protocol/testdata/go_command_test.bin") + if err != nil { + t.Errorf("failed to create file: %s", err) + } + defer func() { + _ = file.Close() + }() + enums := []byte{ + CommandShutdown, + CommandVerdict, + CommandUpdateV4, + CommandUpdateV6, + CommandClearCache, + CommandGetLogs, + CommandBandwidthStats, + CommandCleanEndedConnections, + } + + selected := make([]byte, 5000) + for i := range selected { + selected[i] = enums[rand.Intn(len(enums))] //nolint:gosec + } + + for _, value := range selected { + switch value { + case CommandShutdown: + err := SendShutdownCommand(file) + if err != nil { + t.Fatal(err) + } + + case CommandVerdict: + err := SendVerdictCommand(file, Verdict{ + ID: 1, + Verdict: 2, + }) + if err != nil { + t.Fatal(err) + } + + case CommandUpdateV4: + err := SendUpdateV4Command(file, UpdateV4{ + Protocol: 1, + LocalAddress: [4]byte{1, 2, 3, 4}, + LocalPort: 2, + RemoteAddress: [4]byte{2, 3, 4, 5}, + RemotePort: 3, + Verdict: 4, + }) + if err != nil { + t.Fatal(err) + } + + case CommandUpdateV6: + err := SendUpdateV6Command(file, UpdateV6{ + Protocol: 1, + LocalAddress: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + LocalPort: 2, + RemoteAddress: [16]byte{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + RemotePort: 3, + Verdict: 4, + }) + if err != nil { + t.Fatal(err) + } + + case CommandClearCache: + err := SendClearCacheCommand(file) + if err != nil { + t.Fatal(err) + } + + case CommandGetLogs: + err := SendGetLogsCommand(file) + if err != nil { + t.Fatal(err) + } + + case CommandBandwidthStats: + err := SendGetBandwidthStatsCommand(file) + if err != nil { + t.Fatal(err) + } + + case CommandPrintMemoryStats: + err := SendPrintMemoryStatsCommand(file) + if err != nil { + t.Fatal(err) + } + + case CommandCleanEndedConnections: + err := SendCleanEndedConnectionsCommand(file) + if err != nil { + t.Fatal(err) + } + } + } +} diff --git a/windows_kext/kextinterface/testdata/rust_info_test.bin b/windows_kext/kextinterface/testdata/rust_info_test.bin new file mode 100644 index 000000000..3f9049a9d Binary files /dev/null and b/windows_kext/kextinterface/testdata/rust_info_test.bin differ diff --git a/windows_kext/kextinterface/version.txt b/windows_kext/kextinterface/version.txt new file mode 100644 index 000000000..9475e7df1 --- /dev/null +++ b/windows_kext/kextinterface/version.txt @@ -0,0 +1 @@ +[2, 0, 0, 0] \ No newline at end of file diff --git a/windows_kext/protocol/Cargo.lock b/windows_kext/protocol/Cargo.lock new file mode 100644 index 000000000..2ec552197 --- /dev/null +++ b/windows_kext/protocol/Cargo.lock @@ -0,0 +1,193 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "protocol" +version = "0.0.0" +dependencies = [ + "num", + "num-derive", + "num-traits", + "rand", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/windows_kext/protocol/Cargo.toml b/windows_kext/protocol/Cargo.toml new file mode 100644 index 000000000..a8e600db8 --- /dev/null +++ b/windows_kext/protocol/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "protocol" +version = "0.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +num = { version = "0.4", default-features = false } +num-derive = { version = "0.4", default-features = false } +num-traits = { version = "0.2", default-features = false } + +[dev-dependencies] +rand = "0.8.5" diff --git a/windows_kext/protocol/README.md b/windows_kext/protocol/README.md new file mode 100644 index 000000000..cde5d85c9 --- /dev/null +++ b/windows_kext/protocol/README.md @@ -0,0 +1,4 @@ +# Protocol + +Defines protocol that communicates with `kextinterface` / Portmaster. + diff --git a/windows_kext/protocol/src/command.rs b/windows_kext/protocol/src/command.rs new file mode 100644 index 000000000..80c2f3ce8 --- /dev/null +++ b/windows_kext/protocol/src/command.rs @@ -0,0 +1,158 @@ +// Commands from user space + +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; + +#[repr(u8)] +#[derive(Clone, Copy, FromPrimitive)] +#[rustfmt::skip] +pub enum CommandType { + Shutdown = 0, + Verdict = 1, + UpdateV4 = 2, + UpdateV6 = 3, + ClearCache = 4, + GetLogs = 5, + GetBandwidthStats = 6, + PrintMemoryStats = 7, + CleanEndedConnections = 8, +} + +#[repr(C, packed)] +pub struct Command { + pub command_type: CommandType, + value: [u8; 0], +} + +#[repr(C, packed)] +#[derive(Debug, PartialEq, Eq)] +pub struct Verdict { + pub id: u64, + pub verdict: u8, +} + +#[repr(C, packed)] +#[derive(Debug, PartialEq, Eq)] +pub struct UpdateV4 { + pub protocol: u8, + pub local_address: [u8; 4], + pub local_port: u16, + pub remote_address: [u8; 4], + pub remote_port: u16, + pub verdict: u8, +} + +#[repr(C, packed)] +#[derive(Debug, PartialEq, Eq)] +pub struct UpdateV6 { + pub protocol: u8, + pub local_address: [u8; 16], + pub local_port: u16, + pub remote_address: [u8; 16], + pub remote_port: u16, + pub verdict: u8, +} + +pub fn parse_type(bytes: &[u8]) -> Option { + FromPrimitive::from_u8(bytes[0]) +} + +pub fn parse_verdict(bytes: &[u8]) -> &Verdict { + as_type(bytes) +} + +pub fn parse_update_v4(bytes: &[u8]) -> &UpdateV4 { + as_type(bytes) +} + +pub fn parse_update_v6(bytes: &[u8]) -> &UpdateV6 { + as_type(bytes) +} + +fn as_type(bytes: &[u8]) -> &T { + let ptr: *const u8 = &bytes[0]; + let t_ptr: *const T = ptr as _; + unsafe { t_ptr.as_ref().unwrap() } +} + +#[cfg(test)] +use std::fs::File; +#[cfg(test)] +use std::io::Read; +#[cfg(test)] +use std::mem::size_of; +#[cfg(test)] +use std::panic; + +#[test] +fn test_go_command_file() { + let mut file = File::open("testdata/go_command_test.bin").unwrap(); + loop { + let mut command: [u8; 1] = [0]; + let bytes_count = file.read(&mut command).unwrap(); + if bytes_count == 0 { + return; + } + if let Some(command) = parse_type(&command) { + match command { + CommandType::Shutdown => {} + CommandType::Verdict => { + let mut buf = [0; size_of::()]; + let bytes_count = file.read(&mut buf).unwrap(); + if bytes_count != size_of::() { + panic!("unexpected bytes count") + } + + assert_eq!(parse_verdict(&buf), &Verdict { id: 1, verdict: 2 }) + } + CommandType::UpdateV4 => { + let mut buf = [0; size_of::()]; + let bytes_count = file.read(&mut buf).unwrap(); + if bytes_count != size_of::() { + panic!("unexpected bytes count") + } + + assert_eq!( + parse_update_v4(&buf), + &UpdateV4 { + protocol: 1, + local_address: [1, 2, 3, 4], + local_port: 2, + remote_address: [2, 3, 4, 5], + remote_port: 3, + verdict: 4 + } + ) + } + CommandType::UpdateV6 => { + let mut buf = [0; size_of::()]; + let bytes_count = file.read(&mut buf).unwrap(); + if bytes_count != size_of::() { + panic!("unexpected bytes count") + } + + assert_eq!( + parse_update_v6(&buf), + &UpdateV6 { + protocol: 1, + local_address: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + local_port: 2, + remote_address: [ + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 + ], + remote_port: 3, + verdict: 4 + } + ) + } + CommandType::ClearCache => {} + CommandType::GetLogs => {} + CommandType::GetBandwidthStats => {} + CommandType::PrintMemoryStats => {} + CommandType::CleanEndedConnections => {} + } + } else { + panic!("Unknown command: {}", command[0]); + } + } +} diff --git a/windows_kext/protocol/src/info.rs b/windows_kext/protocol/src/info.rs new file mode 100644 index 000000000..b8eb0c794 --- /dev/null +++ b/windows_kext/protocol/src/info.rs @@ -0,0 +1,552 @@ +use alloc::vec::Vec; + +#[repr(u8)] +#[derive(Clone, Copy)] +enum InfoType { + LogLine = 0, + ConnectionIpv4 = 1, + ConnectionIpv6 = 2, + ConnectionEndEventV4 = 3, + ConnectionEndEventV6 = 4, + BandwidthStatsV4 = 5, + BandwidthStatsV6 = 6, +} + +// Fallow this pattern when adding new packets: [InfoType: u8, data_size_in_bytes: u32, data: ...] + +trait PushBytes { + fn push(self, vec: &mut Vec); +} + +impl PushBytes for u8 { + fn push(self, vec: &mut Vec) { + vec.push(self); + } +} + +impl PushBytes for InfoType { + fn push(self, vec: &mut Vec) { + vec.push(self as u8); + } +} + +impl PushBytes for u16 { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&u16::to_le_bytes(self)); + } +} + +impl PushBytes for u32 { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&u32::to_le_bytes(self)); + } +} + +impl PushBytes for u64 { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&u64::to_le_bytes(self)); + } +} + +impl PushBytes for usize { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&usize::to_le_bytes(self)); + } +} + +impl PushBytes for [u8; 4] { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&self); + } +} + +impl PushBytes for [u8; 16] { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(&self); + } +} + +impl PushBytes for &[u8] { + fn push(self, vec: &mut Vec) { + vec.extend_from_slice(self); + } +} + +macro_rules! push_bytes { + ($vec:expr,$value:expr) => { + PushBytes::push($value, $vec); + }; +} + +macro_rules! get_combined_size{ + ($($a:expr),*)=>{{0 $(+core::mem::size_of_val(&$a))*}} +} + +pub struct Info(Vec); + +impl Info { + fn new(info_type: InfoType, size: usize) -> Self { + let mut vec = Vec::with_capacity(size + 5); // +1 for the info type +4 for the size. + push_bytes!(&mut vec, info_type); + push_bytes!(&mut vec, size as u32); + Self(vec) + } + + fn with_capacity(info_type: InfoType, capacity: usize) -> Self { + let mut vec = Vec::with_capacity(capacity + 5); // +1 for the info type + 4 for the size. + push_bytes!(&mut vec, info_type); + push_bytes!(&mut vec, 0 as u32); + Self(vec) + } + + #[cfg(test)] + fn assert_size(&self) { + let size = u32::from_le_bytes([self.0[1], self.0[2], self.0[3], self.0[4]]) as usize; + assert_eq!(size, self.0.len() - 5); + } + + fn update_size(&mut self) { + let size = self.0.len() - 5; + let bytes = &mut self.0; + bytes[1] = size as u8; + bytes[2] = (size >> 8) as u8; + bytes[3] = (size >> 16) as u8; + bytes[4] = (size >> 24) as u8; + } + + pub fn as_bytes(&self) -> &[u8] { + return self.0.as_slice(); + } +} + +impl core::fmt::Write for Info { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + const MAX_CAPACITY: usize = 500; + + let space_left = self.0.capacity() - self.0.len(); + if s.len() > space_left { + if self.0.capacity() < MAX_CAPACITY { + self.0.reserve(MAX_CAPACITY); + } else { + return Ok(()); + } + } + + self.0.extend_from_slice(s.as_bytes()); + self.update_size(); + Ok(()) + } +} + +pub fn connection_info_v4( + id: u64, + process_id: u64, + direction: u8, + protocol: u8, + local_ip: [u8; 4], + remote_ip: [u8; 4], + local_port: u16, + remote_port: u16, + payload_layer: u8, + payload: &[u8], +) -> Info { + let mut size = get_combined_size!( + id, + process_id, + direction, + protocol, + local_ip, + remote_ip, + local_port, + remote_port, + payload_layer, + payload.len() as u32 + ); + size += payload.len(); + + let mut info = Info::new(InfoType::ConnectionIpv4, size); + let vec = &mut info.0; + push_bytes!(vec, id); + push_bytes!(vec, process_id); + push_bytes!(vec, direction); + push_bytes!(vec, protocol); + push_bytes!(vec, local_ip); + push_bytes!(vec, remote_ip); + push_bytes!(vec, local_port); + push_bytes!(vec, remote_port); + push_bytes!(vec, payload_layer); + push_bytes!(vec, payload.len() as u32); + push_bytes!(vec, payload); + info +} + +pub fn connection_info_v6( + id: u64, + process_id: u64, + direction: u8, + protocol: u8, + local_ip: [u8; 16], + remote_ip: [u8; 16], + local_port: u16, + remote_port: u16, + payload_layer: u8, + payload: &[u8], +) -> Info { + let mut size = get_combined_size!( + id, + process_id, + direction, + protocol, + local_ip, + remote_ip, + local_port, + remote_port, + payload_layer, + payload.len() as u32 + ); + size += payload.len(); + let mut info = Info::new(InfoType::ConnectionIpv6, size); + let vec = &mut info.0; + push_bytes!(vec, id); + push_bytes!(vec, process_id); + push_bytes!(vec, direction); + push_bytes!(vec, protocol); + push_bytes!(vec, local_ip); + push_bytes!(vec, remote_ip); + push_bytes!(vec, local_port); + push_bytes!(vec, remote_port); + push_bytes!(vec, payload_layer); + push_bytes!(vec, payload.len() as u32); + if !payload.is_empty() { + push_bytes!(vec, payload); + } + info +} + +pub fn connection_end_event_v4_info( + process_id: u64, + direction: u8, + protocol: u8, + local_ip: [u8; 4], + remote_ip: [u8; 4], + local_port: u16, + remote_port: u16, +) -> Info { + let size = get_combined_size!( + process_id, + direction, + protocol, + local_ip, + remote_ip, + local_port, + remote_port + ); + let mut info = Info::new(InfoType::ConnectionEndEventV4, size); + let vec = &mut info.0; + push_bytes!(vec, process_id); + push_bytes!(vec, direction); + push_bytes!(vec, protocol); + push_bytes!(vec, local_ip); + push_bytes!(vec, remote_ip); + push_bytes!(vec, local_port); + push_bytes!(vec, remote_port); + info +} + +pub fn connection_end_event_v6_info( + process_id: u64, + direction: u8, + protocol: u8, + local_ip: [u8; 16], + remote_ip: [u8; 16], + local_port: u16, + remote_port: u16, +) -> Info { + let size = get_combined_size!( + process_id, + direction, + protocol, + local_ip, + remote_ip, + local_port, + remote_port + ); + let mut info = Info::new(InfoType::ConnectionEndEventV6, size); + let vec = &mut info.0; + push_bytes!(vec, process_id); + push_bytes!(vec, direction); + push_bytes!(vec, protocol); + push_bytes!(vec, local_ip); + push_bytes!(vec, remote_ip); + push_bytes!(vec, local_port); + push_bytes!(vec, remote_port); + info +} + +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum Severity { + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, + Critical = 6, + Disabled = 7, +} + +// pub fn log_line(severity: Severity, prefix: String, line: String) -> Info { +// let mut size = get_combined_size!(severity); +// size += prefix.len() + line.len(); + +// let mut info = Info::new(InfoType::LogLine, size); +// let vec = &mut info.0; +// push_bytes!(vec, severity as u8); +// push_bytes!(vec, prefix.as_bytes()); +// push_bytes!(vec, line.as_bytes()); +// info +// } + +pub fn log_line(severity: Severity, capacity: usize) -> Info { + let mut info = Info::with_capacity(InfoType::LogLine, capacity); + let vec = &mut info.0; + push_bytes!(vec, severity as u8); + info +} + +// Special struct for Bandwidth stats +pub struct BandwidthValueV4 { + pub local_ip: [u8; 4], + pub local_port: u16, + pub remote_ip: [u8; 4], + pub remote_port: u16, + pub transmitted_bytes: u64, + pub received_bytes: u64, +} + +impl BandwidthValueV4 { + fn get_size(&self) -> usize { + get_combined_size!( + self.local_ip, + self.local_port, + self.remote_ip, + self.remote_port, + self.transmitted_bytes, + self.received_bytes + ) + } +} + +impl PushBytes for BandwidthValueV4 { + fn push(self, vec: &mut Vec) { + push_bytes!(vec, self.local_ip); + push_bytes!(vec, self.local_port); + push_bytes!(vec, self.remote_ip); + push_bytes!(vec, self.remote_port); + push_bytes!(vec, self.transmitted_bytes); + push_bytes!(vec, self.received_bytes); + } +} + +pub struct BandwidthValueV6 { + pub local_ip: [u8; 16], + pub local_port: u16, + pub remote_ip: [u8; 16], + pub remote_port: u16, + pub transmitted_bytes: u64, + pub received_bytes: u64, +} + +impl BandwidthValueV6 { + fn get_size(&self) -> usize { + get_combined_size!( + self.local_ip, + self.local_port, + self.remote_ip, + self.remote_port, + self.transmitted_bytes, + self.received_bytes + ) + } +} + +impl PushBytes for BandwidthValueV6 { + fn push(self, vec: &mut Vec) { + push_bytes!(vec, self.local_ip); + push_bytes!(vec, self.local_port); + push_bytes!(vec, self.remote_ip); + push_bytes!(vec, self.remote_port); + push_bytes!(vec, self.transmitted_bytes); + push_bytes!(vec, self.received_bytes); + } +} + +pub fn bandiwth_stats_array_v4(protocol: u8, values: Vec) -> Info { + let mut size = get_combined_size!(protocol, values.len() as u32); + + if !values.is_empty() { + size += values[0].get_size() * values.len(); + } + + let mut info = Info::new(InfoType::BandwidthStatsV4, size); + let vec = &mut info.0; + push_bytes!(vec, protocol); + push_bytes!(vec, values.len() as u32); + for v in values { + push_bytes!(vec, v); + } + info +} + +pub fn bandiwth_stats_array_v6(protocol: u8, values: Vec) -> Info { + let mut size = get_combined_size!(protocol, values.len() as u32); + + if !values.is_empty() { + size += values[0].get_size() * values.len(); + } + + let mut info = Info::new(InfoType::BandwidthStatsV6, size); + let vec = &mut info.0; + push_bytes!(vec, protocol); + push_bytes!(vec, values.len() as u32); + for v in values { + push_bytes!(vec, v); + } + info +} + +#[cfg(test)] +use std::fs::File; +#[cfg(test)] +use std::io::Write; + +#[cfg(test)] +use rand::seq::SliceRandom; + +#[test] +fn generate_test_info_file() -> Result<(), std::io::Error> { + let mut file = File::create("../kextinterface/testdata/rust_info_test.bin")?; + let enums = [ + InfoType::LogLine, + InfoType::ConnectionIpv4, + InfoType::ConnectionIpv6, + InfoType::ConnectionEndEventV4, + InfoType::ConnectionEndEventV6, + InfoType::BandwidthStatsV4, + InfoType::BandwidthStatsV6, + ]; + + let mut selected: Vec = Vec::with_capacity(1000); + let mut rng = rand::thread_rng(); + for _ in 0..selected.capacity() { + selected.push(enums.choose(&mut rng).unwrap().clone()); + } + + for value in selected { + file.write_all(&match value { + InfoType::LogLine => { + let mut info = log_line(Severity::Trace, 5); + use std::fmt::Write; + _ = write!(info, "prefix: test log"); + info.assert_size(); + info.0 + } + InfoType::ConnectionIpv4 => { + let info = connection_info_v4( + 1, + 2, + 3, + 4, + [1, 2, 3, 4], + [2, 3, 4, 5], + 5, + 6, + 7, + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + ); + info.assert_size(); + info.0 + } + + InfoType::ConnectionIpv6 => { + let info = connection_info_v6( + 1, + 2, + 3, + 4, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + 5, + 6, + 7, + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + ); + info.assert_size(); + info.0 + } + InfoType::ConnectionEndEventV4 => { + let info = connection_end_event_v4_info(1, 2, 3, [1, 2, 3, 4], [2, 3, 4, 5], 4, 5); + info.assert_size(); + info.0 + } + InfoType::ConnectionEndEventV6 => { + let info = connection_end_event_v6_info( + 1, + 2, + 3, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + 4, + 5, + ); + info.assert_size(); + info.0 + } + InfoType::BandwidthStatsV4 => { + let mut vec = Vec::new(); + vec.push(BandwidthValueV4 { + local_ip: [1, 2, 3, 4], + local_port: 1, + remote_ip: [2, 3, 4, 5], + remote_port: 2, + transmitted_bytes: 3, + received_bytes: 4, + }); + vec.push(BandwidthValueV4 { + local_ip: [1, 2, 3, 4], + local_port: 5, + remote_ip: [2, 3, 4, 5], + remote_port: 6, + transmitted_bytes: 7, + received_bytes: 8, + }); + let info = bandiwth_stats_array_v4(1, vec); + info.assert_size(); + info.0 + } + InfoType::BandwidthStatsV6 => { + let mut vec = Vec::new(); + vec.push(BandwidthValueV6 { + local_ip: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + local_port: 1, + remote_ip: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + remote_port: 2, + transmitted_bytes: 3, + received_bytes: 4, + }); + vec.push(BandwidthValueV6 { + local_ip: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + local_port: 5, + remote_ip: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + remote_port: 6, + transmitted_bytes: 7, + received_bytes: 8, + }); + let info = bandiwth_stats_array_v6(1, vec); + info.assert_size(); + info.0 + } + })?; + } + return Ok(()); +} diff --git a/windows_kext/protocol/src/lib.rs b/windows_kext/protocol/src/lib.rs new file mode 100644 index 000000000..d4d09d517 --- /dev/null +++ b/windows_kext/protocol/src/lib.rs @@ -0,0 +1,5 @@ +#![cfg_attr(not(test), no_std)] +extern crate alloc; + +pub mod command; +pub mod info; diff --git a/windows_kext/protocol/testdata/go_command_test.bin b/windows_kext/protocol/testdata/go_command_test.bin new file mode 100644 index 000000000..586c70adf Binary files /dev/null and b/windows_kext/protocol/testdata/go_command_test.bin differ diff --git a/windows_kext/release/Cargo.lock b/windows_kext/release/Cargo.lock new file mode 100644 index 000000000..0883f77cc --- /dev/null +++ b/windows_kext/release/Cargo.lock @@ -0,0 +1,525 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "handlebars" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab283476b99e66691dee3f1640fea91487a8d81f50fb5ecc75538f8f8879a1e4" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "pest" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "release" +version = "0.0.0" +dependencies = [ + "chrono", + "handlebars", + "serde", + "serde_derive", + "serde_json", + "zip", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", +] diff --git a/windows_kext/release/Cargo.toml b/windows_kext/release/Cargo.toml new file mode 100644 index 000000000..966d966b1 --- /dev/null +++ b/windows_kext/release/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "release" +version = "0.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +handlebars = "5.1.0" +serde = "1.0.197" +serde_derive = "1.0.197" +serde_json = "1.0.114" +chrono = "0.4.35" +zip = { version = "0.6.6", default-features = false } diff --git a/windows_kext/release/README.md b/windows_kext/release/README.md new file mode 100644 index 000000000..939f88d6e --- /dev/null +++ b/windows_kext/release/README.md @@ -0,0 +1,28 @@ +# Kext release tool + +### Generate the zip file +- Make sure `kextinterface/version.txt` is up to date +- Execute: `cargo run` + * This will generate release `kext_release_vX-X-X.zip` file. Which contains all the necessary files to make the release. + +### Generate the cab file +- Copy the zip and extract it on a windows machine. + * Some version Visual Studio needs to be installed. +- From VS Command Prompt / PowerShell run: +``` +cd kext_release_v.../ +./build_cab.bat +``` + +3. Sing the cab file + +### Let Microsoft Sign +- Go to https://partner.microsoft.com/en-us/dashboard/hardware/driver/New +- Enter "PortmasterKext vX.X.X #1" as the product name +- Upload `portmaster-kext_vX-X-X.cab` +- Select the Windows 10 versions that you compiled and tested on + - Currently: Windows 11 Client, version 22H2 x64 (Ni) +- Wait for the process to finish, download the `.zip`. + +The zip will contain the release files. +> Optionally sign the .sys file. diff --git a/windows_kext/release/src/main.rs b/windows_kext/release/src/main.rs new file mode 100644 index 000000000..b956322f3 --- /dev/null +++ b/windows_kext/release/src/main.rs @@ -0,0 +1,124 @@ +use std::{fs::File, io::Write, process::Command}; + +use chrono::Local; +use handlebars::Handlebars; +use serde_json::json; +use zip::{write::FileOptions, ZipWriter}; + +static VERSION: [u8; 4] = include!("../../kextinterface/version.txt"); +static LIB_PATH: &'static str = "./build/x86_64-pc-windows-msvc/release/driver.lib"; + +fn main() { + build_driver(); + println!( + "Building kext v{}-{}-{} #{}", + VERSION[0], VERSION[1], VERSION[2], VERSION[3] + ); + + // Create Zip that will hold all the release files and scripts. + let file = File::create("portmaster-kext-release-bundle.zip").unwrap(); + let mut zip = zip::ZipWriter::new(file); + + // Write files to zip + zip.add_directory("cab", FileOptions::default()).unwrap(); + + // Write driver.lib + write_lib_file_zip(&mut zip); + + // Write ddf file + write_to_zip(&mut zip, "PortmasterKext.ddf", get_ddf_content()); + + // Write build cab script + write_to_zip(&mut zip, "build_cab.ps1", get_build_cab_script_content()); + + // Write metadata file + write_to_zip(&mut zip, "version.rc", get_metadata_file_content()); + + // Write inf file + write_to_zip(&mut zip, "cab/PortmasterKext64.inf", get_inf_content()); + + zip.finish().unwrap(); +} + +fn version_str() -> String { + return format!( + "{}.{}.{}.{}", + VERSION[0], VERSION[1], VERSION[2], VERSION[3] + ); +} + +fn build_driver() { + let output = Command::new("cargo") + .current_dir("../driver") + .arg("build") + .arg("--release") + .args(["--target", "x86_64-pc-windows-msvc"]) + .args(["--target-dir", "../release/build"]) + .output() + .unwrap(); + println!("{}", String::from_utf8(output.stderr).unwrap()); +} + +fn get_inf_content() -> String { + let reg = Handlebars::new(); + let today = Local::now(); + reg.render_template( + include_str!("../templates/PortmasterKext64.inf"), + &json!({"date": today.format("%m/%d/%Y").to_string(), "version": version_str()}), + ) + .unwrap() +} + +fn get_ddf_content() -> String { + let reg = Handlebars::new(); + let cab_file = format!( + "PortmasterKext_v{}-{}-{}.cab", + VERSION[0], VERSION[1], VERSION[2] + ); + reg.render_template( + include_str!("../templates/PortmasterKext.ddf"), + &json!({"cab_file": cab_file}), + ) + .unwrap() +} + +fn get_metadata_file_content() -> String { + let reg = Handlebars::new(); + + let version = format!( + "{}, {}, {}, {}", + VERSION[0], VERSION[1], VERSION[2], VERSION[3] + ); + reg.render_template( + include_str!("../templates/version.rc"), + &json!({"version": version, "version_str": version_str()}), + ) + .unwrap() +} + +fn get_build_cab_script_content() -> String { + let reg = Handlebars::new(); + let cab_file = format!( + "PortmasterKext_v{}-{}-{}.cab", + VERSION[0], VERSION[1], VERSION[2] + ); + + reg + .render_template( + include_str!("../templates/build_cab.ps1"), + &json!({"sys_file": "PortmasterKext64.sys", "pdb_file": "PortmasterKext64.pdb", "lib_file": "driver.lib", "cab_file": &cab_file }), + ) + .unwrap() +} + +fn write_to_zip(zip: &mut ZipWriter, filename: &str, content: String) { + zip.start_file(filename, FileOptions::default()).unwrap(); + zip.write(&content.into_bytes()).unwrap(); +} + +fn write_lib_file_zip(zip: &mut ZipWriter) { + zip.start_file("driver.lib", FileOptions::default()) + .unwrap(); + let mut driver_file = File::open(LIB_PATH).unwrap(); + std::io::copy(&mut driver_file, zip).unwrap(); +} diff --git a/windows_kext/release/templates/PortmasterKext.ddf b/windows_kext/release/templates/PortmasterKext.ddf new file mode 100644 index 000000000..41732d222 --- /dev/null +++ b/windows_kext/release/templates/PortmasterKext.ddf @@ -0,0 +1,24 @@ +;*** PortmasterKext.ddf +.OPTION EXPLICIT ; Generate errors +.Set CabinetFileCountThreshold=0 +.Set FolderFileCountThreshold=0 +.Set FolderSizeThreshold=0 +.Set MaxCabinetSize=0 +.Set MaxDiskFileCount=0 +.Set MaxDiskSize=0 +.Set CompressionType=MSZIP +.Set Cabinet=on +.Set Compress=on + +;Specify file name for new cab file +.Set CabinetNameTemplate={{cab_file}} + +; Specify the subdirectory for the files. +; Your cab file should not have files at the root level, +; and each driver package must be in a separate subfolder. +.Set DestinationDir=PortmasterKext + +;Specify files to be included in cab file +.\cab\\PortmasterKext64.inf +.\cab\\PortmasterKext64.sys +.\cab\\PortmasterKext64.pdb diff --git a/windows_kext/release/templates/PortmasterKext64.inf b/windows_kext/release/templates/PortmasterKext64.inf new file mode 100644 index 000000000..796e46f83 --- /dev/null +++ b/windows_kext/release/templates/PortmasterKext64.inf @@ -0,0 +1,62 @@ + +;/*++ +; +;Copyright (c) Safing ICS Technologies GmbH. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; +;--*/ + +[Version] +Signature = "$Windows NT$" +Class = WFPCALLOUTS +ClassGuid = {57465043-616C-6C6F-7574-5F636C617373} +Provider = %Provider% +CatalogFile = PortmasterKext64.Cat +DriverVer = {{date}},{{version}} + +[SourceDisksNames] +1 = %DiskName% + +[SourceDisksFiles] +PortmasterKext64.sys = 1 + +[DestinationDirs] +DefaultDestDir = 12 ; %windir%\system32\drivers +PortmasterKext.DriverFiles = 12 ; %windir%\system32\drivers + +[DefaultInstall.NTamd64] +OptionDesc = %Description% +CopyFiles = PortmasterKext.DriverFiles + +[DefaultInstall.NTamd64.Services] +AddService = %ServiceName%,,PortmasterKext.Service + +[PortmasterKext.DriverFiles] +PortmasterKext64.sys,,,0x00000040 ; COPYFLG_OVERWRITE_OLDER_ONLY + +[PortmasterKext.Service] +DisplayName = %ServiceName% +Description = %ServiceDesc% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 0 ; SERVICE_BOOT_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\PortmasterKext64.sys + +[Strings] +Provider = "Safing ICS Technologies GmbH" +DiskName = "PortmasterKext Installation Disk" +Description = "PortmasterKext Driver" +ServiceName = "PortmasterKext" +ServiceDesc = "PortmasterKext Driver" diff --git a/windows_kext/release/templates/build_cab.ps1 b/windows_kext/release/templates/build_cab.ps1 new file mode 100644 index 000000000..aefce0487 --- /dev/null +++ b/windows_kext/release/templates/build_cab.ps1 @@ -0,0 +1,59 @@ +# Remove previous cab build +Remove-Item -Path "PortmasterKext_v2-0-0.cab" -ErrorAction SilentlyContinue + +$SDK_Version = "10.0.22621.0" + +# Build metadata file +rc -I "C:\Program Files (x86)\Windows Kits\10\Include\$SDK_Version\um" ` + -I "C:\Program Files (x86)\Windows Kits\10\Include\$SDK_Version\shared" ` + .\version.rc + +# Link the driver. +link.exe /OUT:{{sys_file}} ` +/MANIFEST:NO /PROFILE /Driver ` +"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\wdmsec.lib" ` +"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\ndis.lib" ` +"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\fwpkclnt.lib" ` +"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\um\x64\uuid.lib" ` +"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\BufferOverflowK.lib" ` +"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\ntoskrnl.lib" ` +"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\hal.lib" ` +"C:\Program Files (x86)\Windows Kits\10\lib\$SDK_Version\km\x64\wmilib.lib" ` +"C:\Program Files (x86)\Windows Kits\10\lib\wdf\kmdf\x64\1.15\WdfLdr.lib" ` +"C:\Program Files (x86)\Windows Kits\10\lib\wdf\kmdf\x64\1.15\WdfDriverEntry.lib" ` +"{{lib_file}}" "version.res" ` +/RELEASE /VERSION:"10.0" /DEBUG /MACHINE:X64 /ENTRY:"FxDriverEntry" /OPT:REF /INCREMENTAL:NO /SUBSYSTEM:NATIVE",6.01" /OPT:ICF /ERRORREPORT:PROMPT /MERGE:"_TEXT=.text;_PAGE=PAGE" /NOLOGO /NODEFAULTLIB /SECTION:"INIT,d" +if(!$?) { + Exit $LASTEXITCODE +} + +# Move the driver and debug symbolds into the cab directory. +move {{sys_file}} cab\\PortmasterKext64.sys +move {{pdb_file}} cab\\PortmasterKext64.pdb + +# Create the cab. +Write-Host +Write-Host ===== +Write-Host creating .cab ... +MakeCab /f PortmasterKext.ddf +if(!$?) { + Exit $LASTEXITCODE +} + +# Clean up after cab creation. +Write-Host +Write-Host ===== +Write-Host cleaning up ... +Remove-Item -Path "setup.inf" -ErrorAction SilentlyContinue +Remove-Item -Path "setup.rpt" -ErrorAction SilentlyContinue +Move-Item disk1\\{{cab_file}} {{cab_file}} +Remove-Item disk1 + +# Print signing instructions. +Write-Host +Write-Host ===== +Write-Host YOUR TURN: sign the .cab +Write-Host "(If the sha1 fingerprint of the cert has changed, you can find it in the cert properties on Windows as Thumbprint)" +Write-Host +Write-Host signtool sign /sha1 69ADFEACD5AC42D0DB5698E38CA917B9C60FBFA6 /tr http://timestamp.digicert.com /td sha256 /fd sha256 /a {{cab_file}} +Write-Host diff --git a/windows_kext/release/templates/version.rc b/windows_kext/release/templates/version.rc new file mode 100644 index 000000000..797a7d7e2 --- /dev/null +++ b/windows_kext/release/templates/version.rc @@ -0,0 +1,34 @@ +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION {{version}} + PRODUCTVERSION {{version}} + FILEFLAGSMASK 0x3fL + FILEFLAGS 0x0L + FILEOS 0x40004L + FILETYPE VFT_DRV + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Safing ICS Technologies GmbH" + VALUE "FileDescription", "Portmaster Windows Kernel Extension Driver" + VALUE "FileVersion", "{{version_str}}" + VALUE "LegalCopyright", "Safing ICS Technologies GmbH" + VALUE "OriginalFilename", "PortmasterKext64.sys" + VALUE "ProductName", "Portmaster Windows Kernel Extension" + VALUE "ProductVersion", "{{version_str}}" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/windows_kext/test_protocol.sh b/windows_kext/test_protocol.sh new file mode 100755 index 000000000..b3b496417 --- /dev/null +++ b/windows_kext/test_protocol.sh @@ -0,0 +1,8 @@ +#!/bin/sh +echo Running tests +echo ======================== +cd protocol +cargo test + +cd ../kextinterface +go test -v . diff --git a/windows_kext/wdk/.cargo/config.toml b/windows_kext/wdk/.cargo/config.toml new file mode 100644 index 000000000..33806ae7f --- /dev/null +++ b/windows_kext/wdk/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "x86_64-pc-windows-msvc" diff --git a/windows_kext/wdk/Cargo.lock b/windows_kext/wdk/Cargo.lock new file mode 100644 index 000000000..a6ffcec35 --- /dev/null +++ b/windows_kext/wdk/Cargo.lock @@ -0,0 +1,139 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ntstatus" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ea8ea6a9a8cbe8fefe99b632bd45ec4b41b0bf234e4d740c516372922fb180" +dependencies = [ + "num_enum", +] + +[[package]] +name = "num_enum" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wdk" +version = "0.0.0" +dependencies = [ + "ntstatus", + "widestring", + "windows-sys", +] + +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "git+https://github.com/microsoft/windows-rs?rev=41ad38d8c42c92fd23fe25ba4dca76c2d861ca06#41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" diff --git a/windows_kext/wdk/Cargo.toml b/windows_kext/wdk/Cargo.toml new file mode 100644 index 000000000..a3edbf083 --- /dev/null +++ b/windows_kext/wdk/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wdk" +version = "0.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ntstatus = { version = "0.1.2", default-features = false } + +[dependencies.widestring] +version = "1.0.2" +default-features = false +features = ["alloc"] + +# WARNING: Do not update. The version was choosen for a reason. See wdk/README.md for more detiels. +[dependencies.windows-sys] +git = "https://github.com/microsoft/windows-rs" +rev = "41ad38d8c42c92fd23fe25ba4dca76c2d861ca06" +features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_SystemServices", "Win32_Foundation", "Win32_Security", "Win32_System_IO", "Win32_System_Kernel", "Win32_System_Power", "Win32_System_WindowsProgramming", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_NetworkManagement_WindowsFilteringPlatform", "Win32_System_Rpc"] diff --git a/windows_kext/wdk/README.md b/windows_kext/wdk/README.md new file mode 100644 index 000000000..36107c4bc --- /dev/null +++ b/windows_kext/wdk/README.md @@ -0,0 +1,14 @@ +# WDK (Windows Driver Kit) + +A library that interfaces with the windows kernel. +The crate has extensive use of **unsafe** rust, be more causes when making changes. + +Do not update `windows-sys` dependency. +The version contains bugs that have specific workarounds in this crate. Updating without reviewing the new version can result in broken build or undefined behavior. + +see: `wdk/src/driver.rs` +see: `wdk/src/irp_helper.rs` + +Open issues need to be resolved: +https://github.com/microsoft/wdkmetadata/issues/59 +https://github.com/microsoft/windows-rs/issues/2805 diff --git a/windows_kext/wdk/build.rs b/windows_kext/wdk/build.rs new file mode 100644 index 000000000..884db1c1f --- /dev/null +++ b/windows_kext/wdk/build.rs @@ -0,0 +1,13 @@ +#[cfg(target_arch = "x86_64")] +fn main() { + // C Helper + println!("cargo:rerun-if-changed=../c_helper/x64/c_helper.lib"); + println!("cargo:rustc-link-search=native=../c_helper/x64"); +} + +#[cfg(target_arch = "aarch64")] +fn main() { + // C Helper + println!("cargo:rerun-if-changed=../c_helper/ARM64/c_helper.lib"); + println!("cargo:rustc-link-search=native=../c_helper/ARM64"); +} diff --git a/windows_kext/wdk/rust-analyzer.cargo.target b/windows_kext/wdk/rust-analyzer.cargo.target new file mode 100644 index 000000000..ba09e4240 --- /dev/null +++ b/windows_kext/wdk/rust-analyzer.cargo.target @@ -0,0 +1 @@ +x86_64-pc-windows-msvc \ No newline at end of file diff --git a/windows_kext/wdk/rust-toolchain b/windows_kext/wdk/rust-toolchain new file mode 100644 index 000000000..2bf5ad044 --- /dev/null +++ b/windows_kext/wdk/rust-toolchain @@ -0,0 +1 @@ +stable diff --git a/windows_kext/wdk/src/allocator.rs b/windows_kext/wdk/src/allocator.rs new file mode 100644 index 000000000..e3f65fa88 --- /dev/null +++ b/windows_kext/wdk/src/allocator.rs @@ -0,0 +1,70 @@ +extern crate alloc; + +use core::alloc::{GlobalAlloc, Layout}; + +use alloc::alloc::handle_alloc_error; +use windows_sys::Wdk::System::SystemServices::{ExAllocatePool2, ExFreePoolWithTag}; + +// For reference: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/pool_flags +#[allow(dead_code)] +#[repr(u64)] +enum PoolType { + RequiredStartUseQuota = 0x0000000000000001, + Uninitialized = 0x0000000000000002, // Don't zero-initialize allocation + Session = 0x0000000000000004, // Use session specific pool + CacheAligned = 0x0000000000000008, // Cache aligned allocation + RaiseOnFailure = 0x0000000000000020, // Raise exception on failure + NonPaged = 0x0000000000000040, // Non paged pool NX + NonPagedExecute = 0x0000000000000080, // Non paged pool executable + Paged = 0x0000000000000100, // Paged pool + RequiredEnd = 0x0000000080000000, + OptionalStart = 0x0000000100000000, + OptionalEnd = 0x8000000000000000, +} + +pub struct WindowsAllocator {} + +unsafe impl Sync for WindowsAllocator {} + +pub(crate) const POOL_TAG: u32 = u32::from_ne_bytes(*b"PMrs"); + +unsafe impl GlobalAlloc for WindowsAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let pool = ExAllocatePool2(PoolType::NonPaged as u64, layout.size(), POOL_TAG); + if pool.is_null() { + handle_alloc_error(layout); + } + + pool as *mut u8 + } + + unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) { + ExFreePoolWithTag(ptr as _, POOL_TAG); + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + let pool = self.alloc(layout); + pool + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + // SAFETY: the caller must ensure that the `new_size` does not overflow. + // `layout.align()` comes from a `Layout` and is thus guaranteed to be valid. + let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) }; + // SAFETY: the caller must ensure that `new_layout` is greater than zero. + let new_ptr = unsafe { self.alloc(new_layout) }; + if !new_ptr.is_null() { + // SAFETY: the previously allocated block cannot overlap the newly allocated block. + // The safety contract for `dealloc` must be upheld by the caller. + unsafe { + core::ptr::copy_nonoverlapping( + ptr, + new_ptr, + core::cmp::min(layout.size(), new_size), + ); + self.dealloc(ptr, layout); + } + } + new_ptr + } +} diff --git a/windows_kext/wdk/src/attributes.rs b/windows_kext/wdk/src/attributes.rs new file mode 100644 index 000000000..2ee3fab99 --- /dev/null +++ b/windows_kext/wdk/src/attributes.rs @@ -0,0 +1,12 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use quote::quote; + +// using proc_macro_attribute to declare an attribute like procedural macro + +#[proc_macro_attribute] +// _metadata is argument provided to macro call and _input is code to which attribute like macro attaches +pub fn my_custom_attribute(_metadata: TokenStream, _input: TokenStream) -> TokenStream { + // returning a simple TokenStream for Struct + TokenStream::from(quote! {struct H{}}) +} diff --git a/windows_kext/wdk/src/consts.rs b/windows_kext/wdk/src/consts.rs new file mode 100644 index 000000000..0d1d7eebb --- /dev/null +++ b/windows_kext/wdk/src/consts.rs @@ -0,0 +1,50 @@ +// Actions +pub const FWP_ACTION_FLAG_TERMINATING: u32 = 0x00001000; +pub const FWP_ACTION_FLAG_NON_TERMINATING: u32 = 0x00002000; +pub const FWP_ACTION_FLAG_CALLOUT: u32 = 0x00004000; + +pub const FWP_ACTION_BLOCK: u32 = 0x00000001 | FWP_ACTION_FLAG_TERMINATING; +pub const FWP_ACTION_PERMIT: u32 = 0x00000002 | FWP_ACTION_FLAG_TERMINATING; +pub const FWP_ACTION_CALLOUT_TERMINATING: u32 = + 0x00000003 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_TERMINATING; +pub const FWP_ACTION_CALLOUT_INSPECTION: u32 = + 0x00000004 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_NON_TERMINATING; +pub const FWP_ACTION_CALLOUT_UNKNOWN: u32 = 0x00000005 | FWP_ACTION_FLAG_CALLOUT; +pub const FWP_ACTION_CONTINUE: u32 = 0x00000006 | FWP_ACTION_FLAG_NON_TERMINATING; +pub const FWP_ACTION_NONE: u32 = 0x00000007; +pub const FWP_ACTION_NONE_NO_MATCH: u32 = 0x00000008; +pub const FWP_CONDITION_FLAG_IS_LOOPBACK: u32 = 0x00000001; +pub const FWP_CONDITION_FLAG_IS_IPSEC_SECURED: u32 = 0x00000002; +pub const FWP_CONDITION_FLAG_IS_REAUTHORIZE: u32 = 0x00000004; +pub const FWP_CONDITION_FLAG_IS_WILDCARD_BIND: u32 = 0x00000008; +pub const FWP_CONDITION_FLAG_IS_RAW_ENDPOINT: u32 = 0x00000010; +pub const FWP_CONDITION_FLAG_IS_FRAGMENT: u32 = 0x00000020; +pub const FWP_CONDITION_FLAG_IS_FRAGMENT_GROUP: u32 = 0x00000040; +pub const FWP_CONDITION_FLAG_IS_IPSEC_NATT_RECLASSIFY: u32 = 0x00000080; +pub const FWP_CONDITION_FLAG_REQUIRES_ALE_CLASSIFY: u32 = 0x00000100; +pub const FWP_CONDITION_FLAG_IS_IMPLICIT_BIND: u32 = 0x00000200; +pub const FWP_CONDITION_FLAG_IS_REASSEMBLED: u32 = 0x00000400; +pub const FWP_CONDITION_FLAG_IS_NAME_APP_SPECIFIED: u32 = 0x00004000; +pub const FWP_CONDITION_FLAG_IS_PROMISCUOUS: u32 = 0x00008000; +pub const FWP_CONDITION_FLAG_IS_AUTH_FW: u32 = 0x00010000; +pub const FWP_CONDITION_FLAG_IS_RECLASSIFY: u32 = 0x00020000; +pub const FWP_CONDITION_FLAG_IS_OUTBOUND_PASS_THRU: u32 = 0x00040000; +pub const FWP_CONDITION_FLAG_IS_INBOUND_PASS_THRU: u32 = 0x00080000; +pub const FWP_CONDITION_FLAG_IS_CONNECTION_REDIRECTED: u32 = 0x00100000; + +// Driver +pub const METHOD_BUFFERED: u32 = 0; +pub const METHOD_IN_DIRECT: u32 = 1; +pub const METHOD_OUT_DIRECT: u32 = 2; +pub const METHOD_NEITHER: u32 = 3; + +pub const SIOCTL_TYPE: u32 = 40000; + +pub const FILE_READ_DATA: u32 = 0x00000001; +pub const FILE_READ_ATTRIBUTES: u32 = 0x00000080; +pub const FILE_READ_EA: u32 = 0x00000008; +pub const FILE_WRITE_DATA: u32 = 0x00000002; +pub const FILE_WRITE_ATTRIBUTES: u32 = 0x00000100; +pub const FILE_WRITE_EA: u32 = 0x00000010; +pub const FILE_APPEND_DATA: u32 = 0x00000004; +pub const FILE_EXECUTE: u32 = 0x00000020; diff --git a/windows_kext/wdk/src/debug.rs b/windows_kext/wdk/src/debug.rs new file mode 100644 index 000000000..6c3fdb445 --- /dev/null +++ b/windows_kext/wdk/src/debug.rs @@ -0,0 +1,33 @@ +#[cfg(debug_assertions)] +#[macro_export] +macro_rules! log { + ($level:expr, $($arg:tt)*) => ({ + let message = alloc::format!($($arg)*); + $crate::interface::dbg_print(alloc::format!("{} {}: {}", $level, core::module_path!(), message)); + }); +} + +#[cfg(not(debug_assertions))] +#[macro_export] +macro_rules! log { + ($($arg:expr),*) => {{ + $( + _ = $arg; + )* + }}; +} + +#[macro_export] +macro_rules! err { + ($($arg:tt)*) => ($crate::log!("ERROR", $($arg)*)); +} + +#[macro_export] +macro_rules! dbg { + ($($arg:tt)*) => ($crate::log!("DEBUG", $($arg)*)); +} + +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => ($crate::log!("INFO", $($arg)*)); +} diff --git a/windows_kext/wdk/src/driver.rs b/windows_kext/wdk/src/driver.rs new file mode 100644 index 000000000..a8b7440d1 --- /dev/null +++ b/windows_kext/wdk/src/driver.rs @@ -0,0 +1,103 @@ +use windows_sys::{ + Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, IRP}, + Win32::Foundation::{HANDLE, NTSTATUS}, +}; + +use crate::{ + interface, + irp_helpers::{ReadRequest, WriteRequest}, +}; + +pub trait Device { + fn new(driver: &Driver) -> Self; + fn cleanup(&mut self); + fn read(&mut self, read_request: &mut ReadRequest); + fn write(&mut self, write_request: &mut WriteRequest); + fn shutdown(&mut self); +} + +pub struct Driver { + _device_handle: HANDLE, + driver_object: *mut DRIVER_OBJECT, + device_object: *mut DEVICE_OBJECT, +} +unsafe impl Sync for Driver {} + +// This is a workaround for current state of wdk bindings. +// TODO: replace with official version when they are correct: https://github.com/microsoft/wdkmetadata/issues/59 +pub type UnloadFnType = unsafe extern "system" fn(driver_object: *const DRIVER_OBJECT); +pub type MjFnType = unsafe extern "system" fn(&mut DEVICE_OBJECT, &mut IRP) -> NTSTATUS; + +impl Driver { + pub(crate) fn new( + driver_object: *mut DRIVER_OBJECT, + _driver_handle: HANDLE, + device_handle: HANDLE, + ) -> Driver { + return Driver { + // driver_handle, + _device_handle: device_handle, + driver_object, + device_object: interface::wdf_device_wdm_get_device_object(device_handle), + }; + } + + pub fn get_device_object(&self) -> *mut DEVICE_OBJECT { + return self.device_object; + } + + pub fn get_device_object_ref(&self) -> Option<&mut DEVICE_OBJECT> { + return unsafe { self.device_object.as_mut() }; + } + + pub fn set_driver_unload(&mut self, driver_unload: UnloadFnType) { + if let Some(driver) = unsafe { self.driver_object.as_mut() } { + driver.DriverUnload = Some(unsafe { core::mem::transmute(driver_unload) }) + } + } + + pub fn set_read_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn(windows_sys::Wdk::System::SystemServices::IRP_MJ_READ, mj_fn); + } + + pub fn set_write_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn( + windows_sys::Wdk::System::SystemServices::IRP_MJ_WRITE, + mj_fn, + ); + } + + pub fn set_create_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn( + windows_sys::Wdk::System::SystemServices::IRP_MJ_CREATE, + mj_fn, + ); + } + + pub fn set_device_control_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn( + windows_sys::Wdk::System::SystemServices::IRP_MJ_DEVICE_CONTROL, + mj_fn, + ); + } + + pub fn set_close_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn( + windows_sys::Wdk::System::SystemServices::IRP_MJ_CLOSE, + mj_fn, + ); + } + + pub fn set_cleanup_fn(&mut self, mj_fn: MjFnType) { + self.set_major_fn( + windows_sys::Wdk::System::SystemServices::IRP_MJ_CLEANUP, + mj_fn, + ); + } + + fn set_major_fn(&mut self, fn_index: u32, mj_fn: MjFnType) { + if let Some(driver) = unsafe { self.driver_object.as_mut() } { + driver.MajorFunction[fn_index as usize] = Some(unsafe { core::mem::transmute(mj_fn) }) + } + } +} diff --git a/windows_kext/wdk/src/error.rs b/windows_kext/wdk/src/error.rs new file mode 100644 index 000000000..e9d29008b --- /dev/null +++ b/windows_kext/wdk/src/error.rs @@ -0,0 +1,9 @@ +// use anyhow::anyhow; + +// pub fn anyhow_ntstatus(status: i32) -> anyhow::Error { +// if let Some(value) = ntstatus::ntstatus::NtStatus::from_u32(status as u32) { +// return anyhow!(value); +// } + +// return anyhow!("UNKNOWN_NTSTATUS_CODE"); +// } diff --git a/windows_kext/wdk/src/fast_mutex.rs b/windows_kext/wdk/src/fast_mutex.rs new file mode 100644 index 000000000..6068d5f24 --- /dev/null +++ b/windows_kext/wdk/src/fast_mutex.rs @@ -0,0 +1,143 @@ +use alloc::boxed::Box; +use core::{ + cell::UnsafeCell, + ops::{Deref, DerefMut}, +}; +use windows_sys::{ + Wdk::{ + Foundation::{FAST_MUTEX, KEVENT}, + System::SystemServices::FM_LOCK_BIT, + }, + Win32::System::Kernel::{SynchronizationEvent, EVENT_TYPE}, +}; + +// #[link(name = "NtosKrnl", kind = "static")] +extern "C" { + fn KeInitializeEvent(event: *mut KEVENT, event_type: EVENT_TYPE, state: bool); + + /// The ExAcquireFastMutex routine acquires the given fast mutex with APCs to the current thread disabled. + fn ExAcquireFastMutex(kmutex: *mut FAST_MUTEX); + + /// The ExTryToAcquireFastMutex routine acquires the given fast mutex, if possible, with APCs to the current thread disabled. + fn ExTryToAcquireFastMutex(kmutex: *mut FAST_MUTEX) -> bool; + + // The ExReleaseFastMutex routine releases ownership of a fast mutex that was acquired with ExAcquireFastMutex or ExTryToAcquireFastMutex. + fn ExReleaseFastMutex(kmutex: *mut FAST_MUTEX); +} + +/// The ExInitializeFastMutex routine initializes a fast mutex variable, used to synchronize mutually exclusive access by a set of threads to a shared resource. +/// ExInitializeFastMutex must be called before any calls to other ExXxxFastMutex routines occur. +#[allow(non_snake_case)] +unsafe fn ExInitializeFastMutex(kmutex: *mut FAST_MUTEX) { + core::ptr::write_volatile(&mut (*kmutex).Count, FM_LOCK_BIT as i32); + // (*kmutex).Count = FM_LOCK_BIT as i32; + + (*kmutex).Owner = core::ptr::null_mut(); + (*kmutex).Contention = 0; + KeInitializeEvent(&mut (*kmutex).Event, SynchronizationEvent, false) +} + +pub struct FastMutex { + kmutex: UnsafeCell>, + val: UnsafeCell, +} + +impl FastMutex { + pub const fn default(val: T) -> Self { + Self { + kmutex: UnsafeCell::new(None), + val: UnsafeCell::new(val), + } + } + + pub fn init(&self) { + let mutex = Box::into_raw(Box::new(unsafe { + MaybeUninit::zeroed().assume_init(); + })); + unsafe { + ExInitializeFastMutex(mutex); + *self.kmutex.get() = Some(mutex); + } + } + + pub fn deinit(&self) { + unsafe { + let opt = &mut (*self.kmutex.get()); + if let Some(mutex) = opt { + _ = Box::from_raw(mutex); + } + opt.take(); + } + } + + pub fn lock(&self) -> Result, ()> { + unsafe { + if let Some(mutex) = *self.kmutex.get() { + ExAcquireFastMutex(mutex); + return Ok(LockGuard::new(self)); + } + } + + return Err(()); + } + + pub fn try_lock(&self) -> Option> { + unsafe { + if let Some(mutex) = *self.kmutex.get() { + ExTryToAcquireFastMutex(mutex); + return Some(LockGuard::new(self)); + } + } + return None; + } + + fn get<'a>(&self) -> *mut T { + self.val.get() + } + + fn unlock(&self) { + unsafe { + if let Some(mutex) = *self.kmutex.get() { + ExReleaseFastMutex(mutex); + } else { + panic!("Mutex not initialized"); + } + } + } +} + +impl Drop for FastMutex { + fn drop(&mut self) { + self.deinit(); + } +} + +pub struct LockGuard<'a, T> { + mutex: &'a FastMutex, +} + +impl<'a, T> LockGuard<'a, T> { + fn new(mutex: &'a FastMutex) -> Self { + return LockGuard { mutex }; + } +} + +impl<'a, T> Drop for LockGuard<'a, T> { + fn drop(&mut self) { + self.mutex.unlock(); + } +} + +impl<'a, T> Deref for LockGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.mutex.get() } + } +} + +impl<'a, T> DerefMut for LockGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.mutex.get() } + } +} diff --git a/windows_kext/wdk/src/ffi.rs b/windows_kext/wdk/src/ffi.rs new file mode 100644 index 000000000..b7fea16c3 --- /dev/null +++ b/windows_kext/wdk/src/ffi.rs @@ -0,0 +1,535 @@ +use core::ffi::c_void; + +use windows_sys::{ + core::{GUID, PCWSTR}, + Wdk::Foundation::{DEVICE_OBJECT, DRIVER_OBJECT, MDL}, + Win32::{ + Foundation::{HANDLE, NTSTATUS, UNICODE_STRING}, + NetworkManagement::WindowsFilteringPlatform::{ + FWPM_PROVIDER_CONTEXT2, FWP_CONDITION_VALUE0, FWP_MATCH_TYPE, FWP_VALUE0, + }, + Networking::WinSock::{ADDRESS_FAMILY, SCOPE_ID}, + System::Kernel::COMPARTMENT_ID, + }, +}; + +use crate::filter_engine::{ + classify::ClassifyOut, layer::IncomingValues, metadata::FwpsIncomingMetadataValues, +}; + +pub(crate) type FwpsCalloutClassifyFn = unsafe extern "C" fn( + inFixedValues: *const IncomingValues, + inMetaValues: *const FwpsIncomingMetadataValues, + layerData: *mut c_void, + classifyContext: *mut c_void, + filter: *const FWPS_FILTER2, + flowContext: u64, + classifyOut: *mut ClassifyOut, +); + +pub(crate) type FwpsCalloutNotifyFn = unsafe extern "C" fn( + notifyType: u32, + filterKey: *const GUID, + filter: *mut FWPS_FILTER2, +) -> NTSTATUS; + +pub(crate) type FwpsCalloutFlowDeleteNotifyFn = + unsafe extern "C" fn(layerId: u16, calloutId: u32, flowContext: u64); + +/// The FWPS_ACTION0 structure specifies the run-time action that the filter engine takes if all of the filter's filtering conditions are true. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct FWPS_ACTION0 { + r#type: u32, + calloutId: u32, +} + +/// The FWPS_FILTER_CONDITION0 structure defines a run-time filtering condition for a filter. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct FWPS_FILTER_CONDITION0 { + fieldId: u16, + reserved: u16, + matchType: FWP_MATCH_TYPE, + conditionValue: FWP_CONDITION_VALUE0, +} + +/// The WdfExecutionLevel enumeration type specifies the maximum IRQL at which the framework will call the event callback functions that a driver has supplied for a framework object. +#[repr(C)] +enum WdfExecutionLevel { + Invalid = 0, + InheritFromParent, + Passive, + Dispatch, +} + +/// The WDF_SYNCHRONIZATION_SCOPE enumeration type specifies how the framework will synchronize execution of an object's event callback functions. +#[repr(C)] +enum WdfSynchronizationScope { + Invalid = 0x00, + InheritFromParent, + Device, + Queue, + None, +} + +unsafe impl Sync for WdfObjectContextTypeInfo {} + +/// The FWPS_FILTER2 structure defines a run-time filter in the filter engine. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct FWPS_FILTER2 { + pub(crate) filterId: u64, + pub(crate) weight: FWP_VALUE0, + pub(crate) subLayerWeight: u16, + pub(crate) flags: u16, + pub(crate) numFilterConditions: u32, + pub(crate) filterCondition: *mut FWPS_FILTER_CONDITION0, + pub(crate) action: FWPS_ACTION0, + pub(crate) context: u64, + pub(crate) providerContext: *mut FWPM_PROVIDER_CONTEXT2, +} + +/// The FWPS_CALLOUT3 structure defines the data that is required for a callout driver to register a callout with the filter engine. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct FWPS_CALLOUT3 { + pub(crate) calloutKey: GUID, + pub(crate) flags: u32, + pub(crate) classifyFn: Option, + pub(crate) notifyFn: Option, + pub(crate) flowDeleteFn: Option, +} + +/// The filter engine calls a callout's completionFn callout function whenever packet data, described by the netBufferList parameter in one of the packet injection functions, has been injected into the network stack. +#[allow(non_camel_case_types)] +type FWPS_INJECT_COMPLETE0 = unsafe extern "C" fn( + context: *mut c_void, + net_buffer_list: *mut NET_BUFFER_LIST, + dispatch_level: bool, +); + +/// The FWPS_TRANSPORT_SEND_PARAMS1 structure defines properties of an outbound transport layer packet. +#[allow(non_camel_case_types)] +#[repr(C)] +pub(crate) struct FWPS_TRANSPORT_SEND_PARAMS1 { + pub(crate) remote_address: *const u8, + pub(crate) remote_scope_id: SCOPE_ID, + pub(crate) control_data: *mut c_void, //WSACMSGHDR, + pub(crate) control_data_length: u32, + pub(crate) header_include_header: *mut u8, + pub(crate) header_include_header_length: u32, +} + +/// The FWPS_PACKET_INJECTION_STATE enumeration type specifies the injection state of a network buffer list. +#[allow(non_camel_case_types)] +#[repr(C)] +pub(crate) enum FWPS_PACKET_INJECTION_STATE { + FWPS_PACKET_NOT_INJECTED, + FWPS_PACKET_INJECTED_BY_SELF, + FWPS_PACKET_INJECTED_BY_OTHER, + FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF, + FWPS_PACKET_INJECTION_STATE_MAX, +} + +pub(crate) const FWPS_INJECTION_TYPE_STREAM: u32 = 0x00000001; +pub(crate) const FWPS_INJECTION_TYPE_TRANSPORT: u32 = 0x00000002; +pub(crate) const FWPS_INJECTION_TYPE_NETWORK: u32 = 0x00000004; +pub(crate) const FWPS_INJECTION_TYPE_FORWARD: u32 = 0x00000008; +pub(crate) const FWPS_INJECTION_TYPE_L2: u32 = 0x00000010; +pub(crate) const FWPS_INJECTION_TYPE_VSWITCH_TRANSPORT: u32 = 0x00000020; + +pub(crate) const NDIS_OBJECT_TYPE_DEFAULT: u8 = 0x80; // used when object type is implicit in the API call +pub(crate) const NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1: u8 = 1; + +/// The NBListHeader is the header of NET_BUFFER_LIST struct. +#[repr(C)] +pub(crate) struct NBListHeader { + pub(crate) next: *mut NET_BUFFER_LIST, + pub(crate) first_net_buffer: *mut NET_BUFFER, +} + +/// The NET_BUFFER_LIST structure specifies a linked list of NET_BUFFER structures. +/// This is internal struct should never be allocated from the driver. Use provided functions by microsoft. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub struct NET_BUFFER_LIST { + pub(crate) Header: NBListHeader, + pub(crate) Context: *mut c_void, + pub(crate) ParentNetBufferList: *mut NET_BUFFER_LIST, + pub(crate) NdisPoolHandle: NDIS_HANDLE, + pub(crate) NdisReserved: [*mut c_void; 2], + pub(crate) ProtocolReserved: [*mut c_void; 4], + pub(crate) MiniportReserved: [*mut c_void; 2], + pub(crate) Scratch: *mut c_void, + pub(crate) SourceHandle: NDIS_HANDLE, + pub(crate) NblFlags: u32, + pub(crate) ChildRefCount: i32, + pub(crate) Flags: u32, + pub(crate) Status: NDIS_STATUS, + pub(crate) NetBufferListInfo: [*mut c_void; 20], // Extra data at the end of the struct. The size of the array is not fixed. +} + +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub union NBSize { + pub DataLength: u32, + pub stDataLength: u64, +} + +/// This is internal struct should never be allocated from the driver. Use provided functions by microsoft. +/// The NET_BUFFER structure specifies data that is transmitted or received over the network. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub struct NET_BUFFER { + pub(crate) Next: *mut NET_BUFFER, + pub(crate) CurrentMdl: *mut MDL, + pub(crate) CurrentMdlOffset: u32, + pub(crate) nbSize: NBSize, + pub(crate) MdlChain: *mut MDL, + pub(crate) DataOffset: u32, + pub(crate) ChecksumBias: u16, + pub(crate) Reserved: u16, + pub(crate) NdisPoolHandle: NDIS_HANDLE, + pub(crate) NdisReserved: [*mut c_void; 2], + pub(crate) ProtocolReserved: [*mut c_void; 6], + pub(crate) MiniportReserved: [*mut c_void; 4], + pub(crate) DataPhysicalAddress: u64, + pub(crate) SharedMemoryInfo: *mut c_void, +} + +/// This data type is used as the generic handle type in NDIS function calls. +#[allow(non_camel_case_types)] +pub type NDIS_HANDLE = *mut c_void; + +/// This data type is used to indicate success and error states in numerous functions and object identifiers. +#[allow(non_camel_case_types)] +pub type NDIS_STATUS = i32; + +/// The NDIS_OBJECT_HEADER structure packages the object type, version, and size information that is required in many NDIS 6.0 structures. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct NDIS_OBJECT_HEADER { + pub(crate) Type: u8, + pub(crate) Revision: u8, + pub(crate) Size: u16, +} + +/// The NET_BUFFER_LIST_POOL_PARAMETERS structure defines the parameters for a pool of NET_BUFFER_LIST structures. +#[allow(non_camel_case_types, non_snake_case)] +#[repr(C)] +pub(crate) struct NET_BUFFER_LIST_POOL_PARAMETERS { + pub(crate) Header: NDIS_OBJECT_HEADER, + pub(crate) ProtocolId: u8, + pub(crate) fAllocateNetBuffer: bool, + pub(crate) ContextSize: u16, + pub(crate) PoolTag: u32, + pub(crate) DataSize: u32, + pub(crate) Flags: u32, +} +/// WdfObjectContextTypeInfo is a description of the device context. +#[repr(C)] +pub struct WdfObjectContextTypeInfo { + size: u32, + context_name: *const u8, + context_size: usize, + unique_type: *const WdfObjectContextTypeInfo, + _evt_driver_get_unique_context_type: *const c_void, // Internal use +} + +impl WdfObjectContextTypeInfo { + pub const fn default(null_terminated_name: &'static str) -> Self { + Self { + size: core::mem::size_of::() as u32, + context_name: null_terminated_name.as_ptr(), + context_size: 0, + unique_type: core::ptr::null(), + _evt_driver_get_unique_context_type: core::ptr::null(), + } + } +} + +/// WdfObjectAttributes contains attributes for the device context. +#[repr(C)] +pub struct WdfObjectAttributes { + size: u32, + evt_cleanup_callback: Option, + evt_destroy_callback: Option, + execution_level: WdfExecutionLevel, + synchronization_scope: WdfSynchronizationScope, + parent_object: HANDLE, + context_size_override: usize, + context_type_info: *const WdfObjectContextTypeInfo, +} + +impl WdfObjectAttributes { + pub fn new() -> Self { + Self { + size: core::mem::size_of::() as u32, + evt_cleanup_callback: None, + evt_destroy_callback: None, + execution_level: WdfExecutionLevel::InheritFromParent, + synchronization_scope: WdfSynchronizationScope::InheritFromParent, + parent_object: 0, + context_size_override: 0, + context_type_info: core::ptr::null(), + } + } + + pub fn add_context(&mut self, context_info: &'static mut WdfObjectContextTypeInfo) { + context_info.context_size = core::mem::size_of::(); + context_info.unique_type = context_info; + self.context_size_override = 0; + self.context_type_info = context_info.unique_type; + } + + pub fn set_cleanup_fn(&mut self, callback: extern "system" fn(wdf_object: HANDLE)) { + self.evt_cleanup_callback = Some(callback); + } + + pub fn set_destroy_fn(&mut self, callback: extern "system" fn(wdf_object: HANDLE)) { + self.evt_destroy_callback = Some(callback); + } +} + +// #[link(name = "Fwpkclnt", kind = "static")] +// #[link(name = "Fwpuclnt", kind = "static")] +// #[link(name = "WdfDriverEntry", kind = "static")] +// #[link(name = "WdfLdr", kind = "static")] +// #[link(name = "BufferOverflowK", kind = "static")] +// #[link(name = "uuid", kind = "static")] +// #[link(name = "wdmsec", kind = "static")] +// #[link(name = "wmilib", kind = "static")] +// #[link(name = "NtosKrnl", kind = "static")] +// #[link(name = "ndis", kind = "static")] +#[link(name = "c_helper", kind = "static")] +extern "C" { + /// The FwpsCalloutUnregisterById0 function unregisters a callout from the filter engine. + pub(crate) fn FwpsCalloutUnregisterById0(id: u32) -> NTSTATUS; + + /// The FwpsCalloutRegister3 function registers a callout with the filter engine. + pub(crate) fn FwpsCalloutRegister3( + deviceObject: *mut c_void, + callout: *const FWPS_CALLOUT3, + calloutId: *mut u32, + ) -> NTSTATUS; + + /// The FwpsPendOperation0 function is called by a callout to suspend packet processing pending completion of another operation. + pub(crate) fn FwpsPendOperation0( + completionHandle: HANDLE, + completionContext: *mut HANDLE, + ) -> NTSTATUS; + + /// The FwpsCompleteOperation0 function is called by a callout to resume packet processing that was suspended pending completion of another operation. + pub(crate) fn FwpsCompleteOperation0(completionContext: HANDLE, netBufferList: *mut c_void); + + /// The FwpsAcquireClassifyHandle0 function generates a classification handle that is used to identify asynchronous classification operations and requests for writable layer data. + pub(crate) fn FwpsAcquireClassifyHandle0( + classify_context: *mut c_void, + reserved: u32, // Must be zero. + classify_handle: *mut u64, + ) -> NTSTATUS; + + /// A callout driver calls FwpsReleaseClassifyHandle0 to release a classification handle that was previously acquired through a call to FwpsAcquireClassifyHandle0. + pub(crate) fn FwpsReleaseClassifyHandle0(classify_handle: u64); + + /// A callout's classifyFn function calls FwpsPendClassify0 to pend the current classify request. After the request is pended, the callout driver must complete the processing of the classify request asynchronously by calling FwpsCompleteClassify0. + pub(crate) fn FwpsPendClassify0( + classify_handle: u64, + filterId: u64, + flags: u32, // Must be zero. + classifyOut: *const ClassifyOut, + ) -> NTSTATUS; + + /// A callout driver calls FwpsCompleteClassify0 to asynchronously complete a pended classify request. The callout driver's classifyFn function must have previously called FwpsPendClassify0 to pend the classify request. + pub(crate) fn FwpsCompleteClassify0( + classify_handle: u64, + flags: u32, // Must be zero. + classifyOut: *const ClassifyOut, + ); + + /// The FwpsAcquireWritableLayerDataPointer0 function returns layer-specific data that can be inspected and changed. + pub(crate) fn FwpsAcquireWritableLayerDataPointer0( + classify_handle: u64, + filter_id: u64, + flags: u32, + writable_layer_data: *mut c_void, + classify_out: *mut ClassifyOut, + ) -> NTSTATUS; + + /// The FwpsApplyModifiedLayerData0 function applies changes to layer-specific data made after a call to FwpsAcquireWritableLayerDataPointer0. + pub(crate) fn FwpsApplyModifiedLayerData0( + classifyHandle: u64, + modifiedLayerData: *mut *mut c_void, + flags: u32, + ); + + /// pm_InitDriverObject initialize driver object. This function initializes requerd memory for the device context. + pub(crate) fn pm_InitDriverObject( + driver_object: *mut DRIVER_OBJECT, + registry_path: *mut UNICODE_STRING, + wdf_driver: *mut HANDLE, + wdf_device: *mut HANDLE, + win_driver_path: PCWSTR, + dos_driver_path: PCWSTR, + object_attributes: *mut WdfObjectAttributes, + wdf_driver_unload: extern "C" fn(HANDLE), + ) -> NTSTATUS; + + /// pm_WdfObjectGetTypedContextWerker 1to1 reference to the WdfObjectGetTypedContextWorker macro. The WdfObjectGetTypedContext macro returns a pointer to an object's context space. + pub(crate) fn pm_WdfObjectGetTypedContextWorker( + wdf_object: HANDLE, + type_info: *const WdfObjectContextTypeInfo, + ) -> *mut c_void; + + /// WdfObjectGetTypedContext 1to1 reference to WdfDeviceWdmGetDeviceObject. The WdfDeviceWdmGetDeviceObject method returns the Windows Driver Model (WDM) device object that is associated with a specified framework device object. + pub(crate) fn pm_GetDeviceObject(wdf_device: HANDLE) -> *mut DEVICE_OBJECT; + + /// The FwpsInjectNetworkSendAsync0 function injects packet data into the send data path. + pub(crate) fn FwpsInjectNetworkSendAsync0( + injectionHandle: HANDLE, + injectionContext: HANDLE, + flags: u32, + compartmentId: COMPARTMENT_ID, + netBufferList: *mut NET_BUFFER_LIST, + completionFn: FWPS_INJECT_COMPLETE0, + completionContext: *mut c_void, + ) -> NTSTATUS; + + /// The FwpsInjectNetworkReceiveAsync0 function injects packet data into the receive data path. + pub(crate) fn FwpsInjectNetworkReceiveAsync0( + injectionHandle: HANDLE, + injectionContext: HANDLE, + flags: u32, + compartmentId: COMPARTMENT_ID, + interfaceIndex: u32, + subInterfaceIndex: u32, + netBufferList: *mut NET_BUFFER_LIST, + completionFn: FWPS_INJECT_COMPLETE0, + completionContext: *mut c_void, + ) -> NTSTATUS; + + /// The FwpsInjectTransportSendAsync1 function injects packet data from the transport, datagram data, or ICMP error layers into the send data path. This function differs from the previous version (FwpsInjectTransportSendAsync0) in that it takes an updated parameters structure as an argument. + pub(crate) fn FwpsInjectTransportSendAsync1( + injectionHandle: HANDLE, + injectionContext: HANDLE, + endpointHandle: u64, + flags: u32, + sendArgs: *mut FWPS_TRANSPORT_SEND_PARAMS1, + addressFamily: ADDRESS_FAMILY, + compartmentId: COMPARTMENT_ID, + netBufferList: *mut NET_BUFFER_LIST, + completionFn: FWPS_INJECT_COMPLETE0, + completionContext: *mut c_void, + ) -> NTSTATUS; + + /// The FwpsInjectTransportReceiveAsync0 function injects packet data from the transport, datagram data, or ICMP error layers into the receive data path. + pub(crate) fn FwpsInjectTransportReceiveAsync0( + injectionHandle: HANDLE, + injectionContext: HANDLE, + reserved: *const c_void, + flags: u32, + addressFamily: ADDRESS_FAMILY, + compartmentId: COMPARTMENT_ID, + interfaceIndex: u32, + subInterfaceIndex: u32, + netBufferList: *mut NET_BUFFER_LIST, + completionFn: FWPS_INJECT_COMPLETE0, + completionContext: *mut c_void, + ) -> NTSTATUS; + + /// The FwpsInjectionHandleCreate0 function creates a handle that can be used by packet injection functions to inject packet or stream data into the TCP/IP network stack and by the FwpsQueryPacketInjectionState0 function to query the packet injection state. + pub(crate) fn FwpsInjectionHandleCreate0( + addressFamily: ADDRESS_FAMILY, + flags: u32, + injectionHandle: &mut HANDLE, + ) -> NTSTATUS; + + /// The FwpsQueryPacketInjectionState0 function is called by a callout to query the injection state of packet data. + pub(crate) fn FwpsQueryPacketInjectionState0( + injectionHandle: HANDLE, + netBufferList: *const NET_BUFFER_LIST, + injectionContext: *mut HANDLE, + ) -> FWPS_PACKET_INJECTION_STATE; + + /// The FwpsInjectionHandleDestroy0 function destroys an injection handle that was previously created by calling the FwpsInjectionHandleCreate0 function. + pub(crate) fn FwpsInjectionHandleDestroy0(injectionHandle: HANDLE) -> NTSTATUS; + + /// The FwpsReferenceNetBufferList0 function increments the reference count for a NET_BUFFER_LIST structure. + pub(crate) fn FwpsReferenceNetBufferList0( + netBufferList: *mut NET_BUFFER_LIST, + intendToModify: bool, + ); + + /// The FwpsDereferenceNetBufferList0 function decrements the reference count for a NET_BUFFER_LIST structure that a callout driver had acquired earlier using the FwpsReferenceNetBufferList0 function. + pub(crate) fn FwpsDereferenceNetBufferList0( + netBufferList: *mut NET_BUFFER_LIST, + dispatchLevel: bool, + ); + + /// Call the NdisGetDataBuffer function to gain access to a contiguous block of data from a NET_BUFFER structure. + pub(crate) fn NdisGetDataBuffer( + NetBuffer: *const NET_BUFFER, + BytesNeeded: u32, + Storage: *mut u8, + AlignMultiple: u32, + AlignOffset: u32, + ) -> *mut u8; + + /// Call the NdisAllocateCloneNetBufferList function to create a new clone NET_BUFFER_LIST structure. + pub(crate) fn NdisAllocateCloneNetBufferList( + OriginalNetBufferList: *mut NET_BUFFER_LIST, + NetBufferListPoolHandle: NDIS_HANDLE, + NetBufferPoolHandle: NDIS_HANDLE, + AllocateCloneFlag: u32, + ) -> *mut NET_BUFFER_LIST; + + /// Call the NdisFreeCloneNetBufferList function to free a NET_BUFFER_LIST structure and all associated NET_BUFFER structures and MDL chains that were previously allocated by calling the NdisAllocateCloneNetBufferList function. + pub(crate) fn NdisFreeCloneNetBufferList( + CloneNetBufferList: *mut NET_BUFFER_LIST, + FreeCloneFlags: u32, + ); + + /// The FwpsAllocateNetBufferAndNetBufferList0 function allocates a new NET_BUFFER_LIST structure. + pub(crate) fn FwpsAllocateNetBufferAndNetBufferList0( + poolHandle: NDIS_HANDLE, + contextSize: u16, + contextBackFill: u16, + mdlChain: *mut MDL, + dataOffset: u32, + dataLength: u64, + netBufferList: *mut *mut NET_BUFFER_LIST, + ) -> NTSTATUS; + + /// The FwpsFreeNetBufferList0 function frees a NET_BUFFER_LIST structure that was previously allocated by a call to the FwpsAllocateNetBufferAndNetBufferList0 function. + pub(crate) fn FwpsFreeNetBufferList0(netBufferList: *mut NET_BUFFER_LIST); + + /// Call the NdisAllocateNetBufferListPool function to allocate a pool of NET_BUFFER_LIST structures. + pub(crate) fn NdisAllocateNetBufferListPool( + NdisHandle: NDIS_HANDLE, + Parameters: *const NET_BUFFER_LIST_POOL_PARAMETERS, + ) -> NDIS_HANDLE; + + /// Call the NdisFreeNetBufferListPool function to free a NET_BUFFER_LIST structure pool. + pub(crate) fn NdisFreeNetBufferListPool(PoolHandle: NDIS_HANDLE); + + /// Call the NdisRetreatNetBufferDataStart function to access more used data space in the MDL chain of a NET_BUFFER structure. + pub(crate) fn NdisRetreatNetBufferDataStart( + NetBuffer: *mut NET_BUFFER, + DataOffsetDelta: u32, + DataBackFill: u32, + AllocateMdlHandler: *mut c_void, + ) -> NDIS_STATUS; + + /// Call the NdisAdvanceNetBufferDataStart function to release the used data space that was added with the NdisRetreatNetBufferDataStart function. + pub(crate) fn NdisAdvanceNetBufferDataStart( + NetBuffer: *mut NET_BUFFER, + DataOffsetDelta: u32, + FreeMdl: bool, + FreeMdlHandler: *mut c_void, + ); + + /// The KeQuerySystemTime routine obtains the current system time. + /// System time is a count of 100-nanosecond intervals since January 1, 1601. System time is typically updated approximately every ten milliseconds. This value is computed for the GMT time zone. + pub(crate) fn pm_QuerySystemTime() -> u64; +} diff --git a/windows_kext/wdk/src/filter_engine/callout.rs b/windows_kext/wdk/src/filter_engine/callout.rs new file mode 100644 index 000000000..ad4fcf4ef --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/callout.rs @@ -0,0 +1,101 @@ +use super::{callout_data::CalloutData, ffi, layer::Layer}; +use crate::ffi::FwpsCalloutClassifyFn; +use alloc::{borrow::ToOwned, format, string::String}; +use windows_sys::Wdk::Foundation::DEVICE_OBJECT; + +pub enum FilterType { + Resettable, + NonResettable, +} + +pub struct Callout { + pub(crate) id: u32, + pub(super) address: u64, + pub(crate) name: String, + pub(crate) description: String, + pub(crate) guid: u128, + pub(crate) layer: Layer, + pub(crate) action: u32, + pub(crate) registered: bool, + pub(crate) filter_type: FilterType, + pub(crate) filter_id: u64, + pub(crate) callout_fn: fn(CalloutData), +} + +impl Callout { + pub fn new( + name: &str, + description: &str, + guid: u128, + layer: Layer, + action: u32, + filter_type: FilterType, + callout_fn: fn(CalloutData), + ) -> Self { + Self { + id: 0, + address: 0, + name: name.to_owned(), + description: description.to_owned(), + guid, + layer, + action, + registered: false, + filter_type, + filter_id: 0, + callout_fn, + } + } + + pub fn register_filter( + &mut self, + filter_engine_handle: isize, + sublayer_guid: u128, + ) -> Result<(), String> { + match ffi::register_filter( + filter_engine_handle, + sublayer_guid, + &format!("{}-filter", self.name), + &self.description, + self.guid, + self.layer, + self.action, + self.address, // The address of the callout is passed as context. + ) { + Ok(id) => { + self.filter_id = id; + } + Err(error) => { + return Err(format!("failed to register filter: {}", error)); + } + }; + + return Ok(()); + } + + pub(crate) fn register_callout( + &mut self, + filter_engine_handle: isize, + device_object: *mut DEVICE_OBJECT, + callout_fn: FwpsCalloutClassifyFn, + ) -> Result<(), String> { + match ffi::register_callout( + device_object, + filter_engine_handle, + &format!("{}-callout", self.name), + &self.description, + self.guid, + self.layer, + callout_fn, + ) { + Ok(id) => { + self.registered = true; + self.id = id; + } + Err(code) => { + return Err(format!("failed to register callout: {}", code)); + } + }; + return Ok(()); + } +} diff --git a/windows_kext/wdk/src/filter_engine/callout_data.rs b/windows_kext/wdk/src/filter_engine/callout_data.rs new file mode 100644 index 000000000..c09be368a --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/callout_data.rs @@ -0,0 +1,209 @@ +use crate::{ + ffi::{FwpsCompleteOperation0, FwpsPendOperation0}, + utils::check_ntstatus, +}; + +use super::{ + classify::ClassifyOut, + layer::{Layer, Value, ValueType}, + metadata::FwpsIncomingMetadataValues, + packet::TransportPacketList, + stream_data::StreamCalloutIoPacket, + FilterEngine, +}; +use alloc::string::{String, ToString}; +use core::{ffi::c_void, ptr::NonNull}; +use windows_sys::Win32::{ + Foundation::HANDLE, + NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_FLAG_IS_REAUTHORIZE, + Networking::WinSock::SCOPE_ID, +}; + +pub enum ClassifyDefer { + Initial(HANDLE, Option), + Reauthorization(usize, Option), +} + +impl ClassifyDefer { + pub fn complete( + self, + filter_engine: &mut FilterEngine, + ) -> Result, String> { + unsafe { + match self { + ClassifyDefer::Initial(context, packet_list) => { + FwpsCompleteOperation0(context, core::ptr::null_mut()); + return Ok(packet_list); + } + ClassifyDefer::Reauthorization(_callout_id, packet_list) => { + // There is no way to reset single filter. If another request for filter reset is trigger at the same time it will fail. + if let Err(err) = filter_engine.reset_all_filters() { + return Err(err); + } + return Ok(packet_list); + } + } + } + } + + // pub fn add_net_buffer(&mut self, nbl: NetBufferList) { + // if let Some(packet_list) = match self { + // ClassifyDefer::Initial(_, packet_list) => packet_list, + // ClassifyDefer::Reauthorization(_, packet_list) => packet_list, + // } { + // packet_list.net_buffer_list_queue.push(nbl); + // } + // } +} + +pub struct CalloutData<'a> { + pub layer: Layer, + pub(crate) callout_id: usize, + pub(crate) values: &'a [Value], + pub(crate) metadata: *const FwpsIncomingMetadataValues, + pub(crate) classify_out: *mut ClassifyOut, + pub(crate) layer_data: *mut c_void, +} + +impl<'a> CalloutData<'a> { + pub fn get_value_type(&self, index: usize) -> ValueType { + self.values[index].value_type + } + + pub fn get_value_u8(&'a self, index: usize) -> u8 { + unsafe { + return self.values[index].value.uint8; + }; + } + + pub fn get_value_u16(&'a self, index: usize) -> u16 { + unsafe { + return self.values[index].value.uint16; + }; + } + + pub fn get_value_u32(&'a self, index: usize) -> u32 { + unsafe { + return self.values[index].value.uint32; + }; + } + + pub fn get_value_byte_array16(&'a self, index: usize) -> &[u8; 16] { + unsafe { + return self.values[index].value.byte_array16.as_ref().unwrap(); + }; + } + + pub fn get_process_id(&self) -> Option { + unsafe { (*self.metadata).get_process_id() } + } + + pub fn get_process_path(&self) -> Option { + unsafe { + return (*self.metadata).get_process_path(); + } + } + + pub fn get_transport_endpoint_handle(&self) -> Option { + unsafe { + return (*self.metadata).get_transport_endpoint_handle(); + } + } + + pub fn get_remote_scope_id(&self) -> Option { + unsafe { + return (*self.metadata).get_remote_scope_id(); + } + } + + pub fn get_control_data(&self) -> Option> { + unsafe { + return (*self.metadata).get_control_data(); + } + } + + pub fn get_layer_data(&self) -> *mut c_void { + return self.layer_data; + } + + pub fn get_stream_callout_packet(&self) -> Option<&mut StreamCalloutIoPacket> { + match self.layer { + Layer::StreamV4 | Layer::StreamV4Discard | Layer::StreamV6 | Layer::StreamV6Discard => unsafe { + (self.layer_data as *mut StreamCalloutIoPacket).as_mut() + }, + _ => None, + } + } + + pub fn pend_operation( + &mut self, + packet_list: Option, + ) -> Result { + unsafe { + let mut completion_context = 0; + if let Some(completion_handle) = (*self.metadata).get_completion_handle() { + let status = FwpsPendOperation0(completion_handle, &mut completion_context); + check_ntstatus(status)?; + + return Ok(ClassifyDefer::Initial(completion_context, packet_list)); + } + + Err("callout not supported".to_string()) + } + } + + pub fn pend_filter_rest(&mut self, packet_list: Option) -> ClassifyDefer { + ClassifyDefer::Reauthorization(self.callout_id, packet_list) + } + + pub fn action_permit(&mut self) { + unsafe { + (*self.classify_out).action_permit(); + } + } + + pub fn action_continue(&mut self) { + unsafe { + (*self.classify_out).action_continue(); + } + } + + pub fn action_block(&mut self) { + unsafe { + (*self.classify_out).action_block(); + } + } + + pub fn action_none(&mut self) { + unsafe { + (*self.classify_out).set_none(); + } + } + + pub fn block_and_absorb(&mut self) { + unsafe { + (*self.classify_out).action_block(); + (*self.classify_out).set_absorb(); + } + } + pub fn clear_write_flag(&mut self) { + unsafe { + (*self.classify_out).clear_write_flag(); + } + } + + pub fn is_reauthorize(&self, flags_index: usize) -> bool { + self.get_value_u32(flags_index) & FWP_CONDITION_FLAG_IS_REAUTHORIZE > 0 + } + + pub fn parmit_and_absorb(&mut self) { + unsafe { + (*self.classify_out).action_permit(); + (*self.classify_out).set_absorb(); + } + } + + pub fn get_callout_id(&self) -> usize { + self.callout_id + } +} diff --git a/windows_kext/wdk/src/filter_engine/classify.rs b/windows_kext/wdk/src/filter_engine/classify.rs new file mode 100644 index 000000000..1acff2edd --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/classify.rs @@ -0,0 +1,87 @@ +#![allow(dead_code)] + +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPS_CLASSIFY_OUT_FLAG_ABSORB; + +const FWP_ACTION_FLAG_TERMINATING: u32 = 0x00001000; +const FWP_ACTION_FLAG_NON_TERMINATING: u32 = 0x00002000; +const FWP_ACTION_FLAG_CALLOUT: u32 = 0x00004000; + +const FWP_ACTION_BLOCK: u32 = 0x00000001 | FWP_ACTION_FLAG_TERMINATING; +const FWP_ACTION_PERMIT: u32 = 0x00000002 | FWP_ACTION_FLAG_TERMINATING; +const FWP_ACTION_CALLOUT_TERMINATING: u32 = + 0x00000003 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_TERMINATING; +const FWP_ACTION_CALLOUT_INSPECTION: u32 = + 0x00000004 | FWP_ACTION_FLAG_CALLOUT | FWP_ACTION_FLAG_NON_TERMINATING; +const FWP_ACTION_CALLOUT_UNKNOWN: u32 = 0x00000005 | FWP_ACTION_FLAG_CALLOUT; +const FWP_ACTION_CONTINUE: u32 = 0x00000006 | FWP_ACTION_FLAG_NON_TERMINATING; +const FWP_ACTION_NONE: u32 = 0x00000007; +const FWP_ACTION_NONE_NO_MATCH: u32 = 0x00000008; + +const FWP_CONDITION_FLAG_IS_LOOPBACK: u32 = 0x00000001; +const FWP_CONDITION_FLAG_IS_IPSEC_SECURED: u32 = 0x00000002; +const FWP_CONDITION_FLAG_IS_REAUTHORIZE: u32 = 0x00000004; +const FWP_CONDITION_FLAG_IS_WILDCARD_BIND: u32 = 0x00000008; +const FWP_CONDITION_FLAG_IS_RAW_ENDPOINT: u32 = 0x00000010; +const FWP_CONDITION_FLAG_IS_FRAGMENT: u32 = 0x00000020; +const FWP_CONDITION_FLAG_IS_FRAGMENT_GROUP: u32 = 0x00000040; +const FWP_CONDITION_FLAG_IS_IPSEC_NATT_RECLASSIFY: u32 = 0x00000080; +const FWP_CONDITION_FLAG_REQUIRES_ALE_CLASSIFY: u32 = 0x00000100; +const FWP_CONDITION_FLAG_IS_IMPLICIT_BIND: u32 = 0x00000200; +const FWP_CONDITION_FLAG_IS_REASSEMBLED: u32 = 0x00000400; +const FWP_CONDITION_FLAG_IS_NAME_APP_SPECIFIED: u32 = 0x00004000; +const FWP_CONDITION_FLAG_IS_PROMISCUOUS: u32 = 0x00008000; +const FWP_CONDITION_FLAG_IS_AUTH_FW: u32 = 0x00010000; +const FWP_CONDITION_FLAG_IS_RECLASSIFY: u32 = 0x00020000; +const FWP_CONDITION_FLAG_IS_OUTBOUND_PASS_THRU: u32 = 0x00040000; +const FWP_CONDITION_FLAG_IS_INBOUND_PASS_THRU: u32 = 0x00080000; +const FWP_CONDITION_FLAG_IS_CONNECTION_REDIRECTED: u32 = 0x00100000; + +const FWPS_RIGHT_ACTION_WRITE: u32 = 0x00000001; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct ClassifyOut { + action_type: u32, + _out_context: u64, // System use + _filter_id: u64, // System use + rights: u32, + flags: u32, + reserved: u32, +} + +impl ClassifyOut { + // Checks if write action flag is set. Indicates if the callout can change the action. + pub fn can_set_action(&self) -> bool { + self.rights & FWPS_RIGHT_ACTION_WRITE > 0 + } + + /// Set block action. Write flag should be cleared, after this. + pub fn action_block(&mut self) { + self.action_type = FWP_ACTION_BLOCK; + } + + /// Set permit action. + pub fn action_permit(&mut self) { + self.action_type = FWP_ACTION_PERMIT; + } + + // Set continue action. + pub fn action_continue(&mut self) { + self.action_type = FWP_ACTION_CONTINUE; + } + + // Set none action. + pub fn set_none(&mut self) { + self.action_type = FWP_ACTION_NONE; + } + + // Set absorb flag. This will drop the packet. Used when the packets will be reinjected in the future. + pub fn set_absorb(&mut self) { + self.flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB; + } + + // Clear the write flag permission. Next filter in the chain will not change the action. + pub fn clear_write_flag(&mut self) { + self.rights &= !FWPS_RIGHT_ACTION_WRITE; + } +} diff --git a/windows_kext/wdk/src/filter_engine/connect_request.rs b/windows_kext/wdk/src/filter_engine/connect_request.rs new file mode 100644 index 000000000..8ec068858 --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/connect_request.rs @@ -0,0 +1,79 @@ +use core::ffi::c_void; + +use windows_sys::Win32::{ + Foundation::HANDLE, + Networking::WinSock::{AF_INET, AF_INET6}, +}; + +use crate::info; + +#[repr(C)] +pub(crate) struct FwpsConnectRequest0 { + pub(crate) local_address_and_port: [u8; 128], + pub(crate) remote_address_and_port: [u8; 128], + pub(crate) port_reservation_token: u64, + pub(crate) local_redirect_target_pid: u32, + pub(crate) previous_version: *const FwpsConnectRequest0, + pub(crate) modifier_filter_id: u64, + pub(crate) local_redirect_handle: HANDLE, + pub(crate) local_redirect_context: *mut c_void, + pub(crate) local_redirect_context_size: usize, +} + +#[repr(C)] +struct SocketAddressGeneric { + family: u16, + padding: [u8; 128 - 2], +} + +#[repr(C)] +struct SocketAddressIPv4 { + family: u16, + port: u16, + addr: [u8; 4], + zero: [u8; 8], + padding: [u8; 128 - 2 - 2 - 4 - 8], +} + +#[repr(C)] +struct SocketAddressIPv6 { + family: u16, + port: u16, + flowinfo: u16, + addr: [u8; 16], + scope_id: u32, + padding: [u8; 128 - 2 - 2 - 2 - 16 - 4], +} + +impl FwpsConnectRequest0 { + pub(crate) fn set_remote(&mut self, ip: &[u8], port: u16) { + info!("local: {:?}", self.local_address_and_port); + info!("remote: {:?}", self.remote_address_and_port); + unsafe { + let generic_socket: &mut SocketAddressGeneric = + core::mem::transmute(&mut self.remote_address_and_port); + match generic_socket.family { + AF_INET => { + info!("Socket type AF_INET"); + let socket_ipv4: &mut SocketAddressIPv4 = core::mem::transmute(generic_socket); + for i in 0..4 { + socket_ipv4.addr[i] = ip[i]; + } + socket_ipv4.port = u16::to_be(port); + } + AF_INET6 => { + info!("Socket type AF_INET6"); + let socket_ipv6: &mut SocketAddressIPv6 = core::mem::transmute(generic_socket); + for i in 0..16 { + socket_ipv6.addr[i] = ip[i]; + } + socket_ipv6.port = u16::to_be(port); + } + _ => { + info!("Unsupported socket type: {}", generic_socket.family); + } + } + } + info!("after: {:?}", self.remote_address_and_port); + } +} diff --git a/windows_kext/wdk/src/filter_engine/ffi.rs b/windows_kext/wdk/src/filter_engine/ffi.rs new file mode 100644 index 000000000..766c1ef14 --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/ffi.rs @@ -0,0 +1,255 @@ +use crate::alloc::borrow::ToOwned; +use crate::ffi::FwpsCalloutClassifyFn; +use crate::ffi::{FwpsCalloutRegister3, FwpsCalloutUnregisterById0, FWPS_CALLOUT3, FWPS_FILTER2}; +use crate::utils::check_ntstatus; +use alloc::string::String; + +use core::mem::MaybeUninit; +use core::ptr; +use widestring::U16CString; + +use windows_sys::Wdk::Foundation::DEVICE_OBJECT; +use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_SUCCESS}; +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::{ + FwpmCalloutAdd0, FwpmEngineClose0, FwpmEngineOpen0, FwpmFilterAdd0, FwpmFilterDeleteById0, + FwpmSubLayerAdd0, FwpmSubLayerDeleteByKey0, FwpmTransactionAbort0, FwpmTransactionBegin0, + FwpmTransactionCommit0, FWPM_CALLOUT0, FWPM_CALLOUT_FLAG_USES_PROVIDER_CONTEXT, + FWPM_DISPLAY_DATA0, FWPM_FILTER0, FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT, FWPM_SESSION0, + FWPM_SESSION_FLAG_DYNAMIC, FWPM_SUBLAYER0, FWP_UINT8, +}; +use windows_sys::Win32::System::Rpc::RPC_C_AUTHN_WINNT; +use windows_sys::{ + core::GUID, + Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}, +}; + +use super::layer::Layer; + +pub(crate) fn create_filter_engine() -> Result { + unsafe { + let mut handle: HANDLE = INVALID_HANDLE_VALUE; + let mut wdf_session: FWPM_SESSION0 = MaybeUninit::zeroed().assume_init(); + wdf_session.flags = FWPM_SESSION_FLAG_DYNAMIC; + let status = FwpmEngineOpen0( + core::ptr::null(), + RPC_C_AUTHN_WINNT, + core::ptr::null_mut(), + &wdf_session, + &mut handle, + ); + check_ntstatus(status as i32)?; + + return Ok(handle); + } +} + +pub(crate) fn register_sublayer( + filter_engine_handle: HANDLE, + name: &str, + description: &str, + guid: u128, +) -> Result<(), String> { + let Ok(name) = U16CString::from_str(name) else { + return Err("invalid argument name".to_owned()); + }; + let Ok(description) = U16CString::from_str(description) else { + return Err("invalid argument description".to_owned()); + }; + + unsafe { + let mut sublayer: FWPM_SUBLAYER0 = MaybeUninit::zeroed().assume_init(); + sublayer.subLayerKey = GUID::from_u128(guid); + sublayer.displayData.name = name.as_ptr() as _; + sublayer.displayData.description = description.as_ptr() as _; + sublayer.flags = 0; + sublayer.weight = 0xFFFF; + + let status = FwpmSubLayerAdd0(filter_engine_handle, &sublayer, core::ptr::null_mut()); + check_ntstatus(status as i32)?; + + return Ok(()); + } +} + +pub(crate) fn unregister_sublayer(filter_engine_handle: HANDLE, guid: u128) -> Result<(), String> { + let guid = GUID::from_u128(guid); + unsafe { + let status = FwpmSubLayerDeleteByKey0(filter_engine_handle, ptr::addr_of!(guid)); + check_ntstatus(status as i32)?; + return Ok(()); + } +} + +unsafe extern "C" fn generic_notify( + _notify_type: u32, + _filter_key: *const GUID, + _filter: *mut FWPS_FILTER2, +) -> NTSTATUS { + return STATUS_SUCCESS; +} + +unsafe extern "C" fn generic_delete_notify(_layer_id: u16, _callout_id: u32, _flow_context: u64) {} + +pub(crate) fn register_callout( + device_object: *mut DEVICE_OBJECT, + filter_engine_handle: HANDLE, + name: &str, + description: &str, + guid: u128, + layer: Layer, + callout_fn: FwpsCalloutClassifyFn, +) -> Result { + let s_callout = FWPS_CALLOUT3 { + calloutKey: GUID::from_u128(guid), + flags: 0, + classifyFn: Some(callout_fn), + notifyFn: Some(generic_notify), + flowDeleteFn: Some(generic_delete_notify), + }; + + unsafe { + let mut callout_id: u32 = 0; + let status = FwpsCalloutRegister3(device_object as _, &s_callout, &mut callout_id); + + check_ntstatus(status)?; + + if let Err(err) = callout_add(filter_engine_handle, guid, layer, name, description) { + return Err(err); + } + + return Ok(callout_id); + } +} + +fn callout_add( + filter_engine_handle: HANDLE, + guid: u128, + layer: Layer, + name: &str, + description: &str, +) -> Result<(), String> { + let Ok(name) = U16CString::from_str(name) else { + return Err("invalid argument name".to_owned()); + }; + let Ok(description) = U16CString::from_str(description) else { + return Err("invalid argument description".to_owned()); + }; + let display_data = FWPM_DISPLAY_DATA0 { + name: name.as_ptr() as _, + description: description.as_ptr() as _, + }; + + unsafe { + let mut callout: FWPM_CALLOUT0 = MaybeUninit::zeroed().assume_init(); + callout.calloutKey = GUID::from_u128(guid); + callout.displayData = display_data; + callout.applicableLayer = layer.get_guid(); + callout.flags = FWPM_CALLOUT_FLAG_USES_PROVIDER_CONTEXT; + let status = FwpmCalloutAdd0( + filter_engine_handle, + &callout, + core::ptr::null_mut(), + core::ptr::null_mut(), + ); + check_ntstatus(status as i32)?; + }; + return Ok(()); +} + +pub(crate) fn unregister_callout(callout_id: u32) -> Result<(), String> { + unsafe { + let status = FwpsCalloutUnregisterById0(callout_id); + + check_ntstatus(status as i32)?; + return Ok(()); + } +} + +pub(crate) fn register_filter( + filter_engine_handle: HANDLE, + sublayer_guid: u128, + name: &str, + description: &str, + callout_guid: u128, + layer: Layer, + action: u32, + context: u64, +) -> Result { + let Ok(name) = U16CString::from_str(name) else { + return Err("invalid argument name".to_owned()); + }; + let Ok(description) = U16CString::from_str(description) else { + return Err("invalid argument description".to_owned()); + }; + let mut filter_id: u64 = 0; + unsafe { + let mut filter: FWPM_FILTER0 = MaybeUninit::zeroed().assume_init(); + filter.displayData.name = name.as_ptr() as _; + filter.displayData.description = description.as_ptr() as _; + filter.action.r#type = action; // Says this filter's callout MUST make a block/permit decision. Also see doc excerpts below. + filter.subLayerKey = GUID::from_u128(sublayer_guid); + filter.weight.r#type = FWP_UINT8; + filter.weight.Anonymous.uint8 = 15; // The weight of this filter within its sublayer + filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT; + filter.numFilterConditions = 0; // If you specify 0, this filter invokes its callout for all traffic in its layer + filter.layerKey = layer.get_guid(); // This layer must match the layer that ExampleCallout is registered to + filter.action.Anonymous.calloutKey = GUID::from_u128(callout_guid); + filter.Anonymous.rawContext = context; + let status = FwpmFilterAdd0( + filter_engine_handle, + &filter, + core::ptr::null_mut(), + &mut filter_id, + ); + + check_ntstatus(status as i32)?; + + return Ok(filter_id); + } +} + +pub(crate) fn unregister_filter( + filter_engine_handle: HANDLE, + filter_id: u64, +) -> Result<(), String> { + unsafe { + let status = FwpmFilterDeleteById0(filter_engine_handle, filter_id); + check_ntstatus(status as i32)?; + return Ok(()); + } +} + +pub(crate) fn filter_engine_close(filter_engine_handle: HANDLE) -> Result<(), String> { + unsafe { + let status = FwpmEngineClose0(filter_engine_handle); + check_ntstatus(status as i32)?; + return Ok(()); + } +} + +pub(crate) fn filter_engine_transaction_begin( + filter_engine_handle: HANDLE, + flags: u32, +) -> Result<(), String> { + unsafe { + let status = FwpmTransactionBegin0(filter_engine_handle, flags); + check_ntstatus(status as i32)?; + return Ok(()); + } +} + +pub(crate) fn filter_engine_transaction_commit(filter_engine_handle: HANDLE) -> Result<(), String> { + unsafe { + let status = FwpmTransactionCommit0(filter_engine_handle); + check_ntstatus(status as i32)?; + return Ok(()); + } +} + +pub(crate) fn filter_engine_transaction_abort(filter_engine_handle: HANDLE) -> Result<(), String> { + unsafe { + let status = FwpmTransactionAbort0(filter_engine_handle); + check_ntstatus(status as i32)?; + return Ok(()); + } +} diff --git a/windows_kext/wdk/src/filter_engine/layer.rs b/windows_kext/wdk/src/filter_engine/layer.rs new file mode 100644 index 000000000..18216342c --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/layer.rs @@ -0,0 +1,1519 @@ +use core::fmt::Debug; + +use windows_sys::{ + core::GUID, + Win32::NetworkManagement::WindowsFilteringPlatform::{ + FWPM_LAYER_ALE_AUTH_CONNECT_V4, FWPM_LAYER_ALE_AUTH_CONNECT_V4_DISCARD, + FWPM_LAYER_ALE_AUTH_CONNECT_V6, FWPM_LAYER_ALE_AUTH_CONNECT_V6_DISCARD, + FWPM_LAYER_ALE_AUTH_LISTEN_V4, FWPM_LAYER_ALE_AUTH_LISTEN_V4_DISCARD, + FWPM_LAYER_ALE_AUTH_LISTEN_V6, FWPM_LAYER_ALE_AUTH_LISTEN_V6_DISCARD, + FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4, FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4_DISCARD, + FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6, FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6_DISCARD, + FWPM_LAYER_ALE_BIND_REDIRECT_V4, FWPM_LAYER_ALE_BIND_REDIRECT_V6, + FWPM_LAYER_ALE_CONNECT_REDIRECT_V4, FWPM_LAYER_ALE_CONNECT_REDIRECT_V6, + FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V4, FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V6, + FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4, FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4_DISCARD, + FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6, FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6_DISCARD, + FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4, FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4_DISCARD, + FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6, FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6_DISCARD, + FWPM_LAYER_ALE_RESOURCE_RELEASE_V4, FWPM_LAYER_ALE_RESOURCE_RELEASE_V6, + FWPM_LAYER_DATAGRAM_DATA_V4, FWPM_LAYER_DATAGRAM_DATA_V4_DISCARD, + FWPM_LAYER_DATAGRAM_DATA_V6, FWPM_LAYER_DATAGRAM_DATA_V6_DISCARD, + FWPM_LAYER_INBOUND_ICMP_ERROR_V4, FWPM_LAYER_INBOUND_ICMP_ERROR_V4_DISCARD, + FWPM_LAYER_INBOUND_ICMP_ERROR_V6, FWPM_LAYER_INBOUND_ICMP_ERROR_V6_DISCARD, + FWPM_LAYER_INBOUND_IPPACKET_V4, FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD, + FWPM_LAYER_INBOUND_IPPACKET_V6, FWPM_LAYER_INBOUND_IPPACKET_V6_DISCARD, + FWPM_LAYER_INBOUND_TRANSPORT_V4, FWPM_LAYER_INBOUND_TRANSPORT_V4_DISCARD, + FWPM_LAYER_INBOUND_TRANSPORT_V6, FWPM_LAYER_INBOUND_TRANSPORT_V6_DISCARD, + FWPM_LAYER_IPFORWARD_V4, FWPM_LAYER_IPFORWARD_V4_DISCARD, FWPM_LAYER_IPFORWARD_V6, + FWPM_LAYER_IPFORWARD_V6_DISCARD, FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4, + FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4_DISCARD, FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6, + FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6_DISCARD, FWPM_LAYER_OUTBOUND_IPPACKET_V4, + FWPM_LAYER_OUTBOUND_IPPACKET_V4_DISCARD, FWPM_LAYER_OUTBOUND_IPPACKET_V6, + FWPM_LAYER_OUTBOUND_IPPACKET_V6_DISCARD, FWPM_LAYER_OUTBOUND_TRANSPORT_V4, + FWPM_LAYER_OUTBOUND_TRANSPORT_V4_DISCARD, FWPM_LAYER_OUTBOUND_TRANSPORT_V6, + FWPM_LAYER_OUTBOUND_TRANSPORT_V6_DISCARD, FWPM_LAYER_STREAM_V4, + FWPM_LAYER_STREAM_V4_DISCARD, FWPM_LAYER_STREAM_V6, FWPM_LAYER_STREAM_V6_DISCARD, + }, +}; + +#[repr(C)] +pub(crate) struct Value { + pub(crate) value_type: ValueType, + pub(crate) value: ValueData, +} + +#[repr(C)] +pub(crate) struct IncomingValues { + pub(crate) layer_id: u16, + pub(crate) value_count: u32, + pub(crate) incoming_value_array: *const Value, +} + +#[repr(C)] +pub(crate) union ValueData { + pub(crate) uint8: u8, + pub(crate) uint16: u16, + pub(crate) uint32: u32, + pub(crate) uint64: *const u64, + pub(crate) byte_array16: *const [u8; 16], + // TODO: add the rest of possible values. +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub enum ValueType { + FwpEmpty = 0, + FwpUint8 = 1, + FwpUint16 = 2, + FwpUint32 = 3, + FwpUint64 = 4, + FwpInt8 = 5, + FwpInt16 = 6, + FwpInt32 = 7, + FwpInt64 = 8, + FwpFloat = 9, + FwpDouble = 10, + FwpByteArray16Type = 11, + FwpByteBlobType = 12, + FwpSid = 13, + FwpSecurityDescriptorType = 14, + FwpTokenInformationType = 15, + FwpTokenAccessInformationType = 16, + FwpUnicodeStringType = 17, + FwpByteArray6Type = 18, + FwpSingleDataTypeMax = 0xff, + FwpV4AddrMask = 0xff + 1, + FwpV6AddrMask = 0xff + 2, + FwpRangeType = 0xff + 3, + FwpDataTypeMax = 0xff + 4, +} + +#[derive(Copy, Clone, Debug)] +pub enum Layer { + InboundIppacketV4, + InboundIppacketV4Discard, + InboundIppacketV6, + InboundIppacketV6Discard, + OutboundIppacketV4, + OutboundIppacketV4Discard, + OutboundIppacketV6, + OutboundIppacketV6Discard, + IpforwardV4, + IpforwardV4Discard, + IpforwardV6, + IpforwardV6Discard, + InboundTransportV4, + InboundTransportV4Discard, + InboundTransportV6, + InboundTransportV6Discard, + OutboundTransportV4, + OutboundTransportV4Discard, + OutboundTransportV6, + OutboundTransportV6Discard, + StreamV4, + StreamV4Discard, + StreamV6, + StreamV6Discard, + DatagramDataV4, + DatagramDataV4Discard, + DatagramDataV6, + DatagramDataV6Discard, + InboundIcmpErrorV4, + InboundIcmpErrorV4Discard, + InboundIcmpErrorV6, + InboundIcmpErrorV6Discard, + OutboundIcmpErrorV4, + OutboundIcmpErrorV4Discard, + OutboundIcmpErrorV6, + OutboundIcmpErrorV6Discard, + AleResourceAssignmentV4, + AleResourceAssignmentV4Discard, + AleResourceAssignmentV6, + AleResourceAssignmentV6Discard, + AleAuthListenV4, + AleAuthListenV4Discard, + AleAuthListenV6, + AleAuthListenV6Discard, + AleAuthRecvAcceptV4, + AleAuthRecvAcceptV4Discard, + AleAuthRecvAcceptV6, + AleAuthRecvAcceptV6Discard, + AleAuthConnectV4, + AleAuthConnectV4Discard, + AleAuthConnectV6, + AleAuthConnectV6Discard, + AleFlowEstablishedV4, + AleFlowEstablishedV4Discard, + AleFlowEstablishedV6, + AleFlowEstablishedV6Discard, + AleConnectRedirectV4, + AleConnectRedirectV6, + AleBindRedirectV4, + AleBindRedirectV6, + AleResourceReleaseV4, + AleResourceReleaseV6, + AleEndpointClosureV4, + AleEndpointClosureV6, +} + +impl Layer { + pub fn get_guid(&self) -> GUID { + match self { + Layer::InboundIppacketV4 => FWPM_LAYER_INBOUND_IPPACKET_V4, + Layer::InboundIppacketV4Discard => FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD, + Layer::InboundIppacketV6 => FWPM_LAYER_INBOUND_IPPACKET_V6, + Layer::InboundIppacketV6Discard => FWPM_LAYER_INBOUND_IPPACKET_V6_DISCARD, + Layer::OutboundIppacketV4 => FWPM_LAYER_OUTBOUND_IPPACKET_V4, + Layer::OutboundIppacketV4Discard => FWPM_LAYER_OUTBOUND_IPPACKET_V4_DISCARD, + Layer::OutboundIppacketV6 => FWPM_LAYER_OUTBOUND_IPPACKET_V6, + Layer::OutboundIppacketV6Discard => FWPM_LAYER_OUTBOUND_IPPACKET_V6_DISCARD, + Layer::IpforwardV4 => FWPM_LAYER_IPFORWARD_V4, + Layer::IpforwardV4Discard => FWPM_LAYER_IPFORWARD_V4_DISCARD, + Layer::IpforwardV6 => FWPM_LAYER_IPFORWARD_V6, + Layer::IpforwardV6Discard => FWPM_LAYER_IPFORWARD_V6_DISCARD, + Layer::InboundTransportV4 => FWPM_LAYER_INBOUND_TRANSPORT_V4, + Layer::InboundTransportV4Discard => FWPM_LAYER_INBOUND_TRANSPORT_V4_DISCARD, + Layer::InboundTransportV6 => FWPM_LAYER_INBOUND_TRANSPORT_V6, + Layer::InboundTransportV6Discard => FWPM_LAYER_INBOUND_TRANSPORT_V6_DISCARD, + Layer::OutboundTransportV4 => FWPM_LAYER_OUTBOUND_TRANSPORT_V4, + Layer::OutboundTransportV4Discard => FWPM_LAYER_OUTBOUND_TRANSPORT_V4_DISCARD, + Layer::OutboundTransportV6 => FWPM_LAYER_OUTBOUND_TRANSPORT_V6, + Layer::OutboundTransportV6Discard => FWPM_LAYER_OUTBOUND_TRANSPORT_V6_DISCARD, + Layer::StreamV4 => FWPM_LAYER_STREAM_V4, + Layer::StreamV4Discard => FWPM_LAYER_STREAM_V4_DISCARD, + Layer::StreamV6 => FWPM_LAYER_STREAM_V6, + Layer::StreamV6Discard => FWPM_LAYER_STREAM_V6_DISCARD, + Layer::DatagramDataV4 => FWPM_LAYER_DATAGRAM_DATA_V4, + Layer::DatagramDataV4Discard => FWPM_LAYER_DATAGRAM_DATA_V4_DISCARD, + Layer::DatagramDataV6 => FWPM_LAYER_DATAGRAM_DATA_V6, + Layer::DatagramDataV6Discard => FWPM_LAYER_DATAGRAM_DATA_V6_DISCARD, + Layer::InboundIcmpErrorV4 => FWPM_LAYER_INBOUND_ICMP_ERROR_V4, + Layer::InboundIcmpErrorV4Discard => FWPM_LAYER_INBOUND_ICMP_ERROR_V4_DISCARD, + Layer::InboundIcmpErrorV6 => FWPM_LAYER_INBOUND_ICMP_ERROR_V6, + Layer::InboundIcmpErrorV6Discard => FWPM_LAYER_INBOUND_ICMP_ERROR_V6_DISCARD, + Layer::OutboundIcmpErrorV4 => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4, + Layer::OutboundIcmpErrorV4Discard => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V4_DISCARD, + Layer::OutboundIcmpErrorV6 => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6, + Layer::OutboundIcmpErrorV6Discard => FWPM_LAYER_OUTBOUND_ICMP_ERROR_V6_DISCARD, + Layer::AleResourceAssignmentV4 => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4, + Layer::AleResourceAssignmentV4Discard => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4_DISCARD, + Layer::AleResourceAssignmentV6 => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6, + Layer::AleResourceAssignmentV6Discard => FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6_DISCARD, + Layer::AleAuthListenV4 => FWPM_LAYER_ALE_AUTH_LISTEN_V4, + Layer::AleAuthListenV4Discard => FWPM_LAYER_ALE_AUTH_LISTEN_V4_DISCARD, + Layer::AleAuthListenV6 => FWPM_LAYER_ALE_AUTH_LISTEN_V6, + Layer::AleAuthListenV6Discard => FWPM_LAYER_ALE_AUTH_LISTEN_V6_DISCARD, + Layer::AleAuthRecvAcceptV4 => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4, + Layer::AleAuthRecvAcceptV4Discard => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4_DISCARD, + Layer::AleAuthRecvAcceptV6 => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6, + Layer::AleAuthRecvAcceptV6Discard => FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6_DISCARD, + Layer::AleAuthConnectV4 => FWPM_LAYER_ALE_AUTH_CONNECT_V4, + Layer::AleAuthConnectV4Discard => FWPM_LAYER_ALE_AUTH_CONNECT_V4_DISCARD, + Layer::AleAuthConnectV6 => FWPM_LAYER_ALE_AUTH_CONNECT_V6, + Layer::AleAuthConnectV6Discard => FWPM_LAYER_ALE_AUTH_CONNECT_V6_DISCARD, + Layer::AleFlowEstablishedV4 => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4, + Layer::AleFlowEstablishedV4Discard => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4_DISCARD, + Layer::AleFlowEstablishedV6 => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6, + Layer::AleFlowEstablishedV6Discard => FWPM_LAYER_ALE_FLOW_ESTABLISHED_V6_DISCARD, + Layer::AleConnectRedirectV4 => FWPM_LAYER_ALE_CONNECT_REDIRECT_V4, + Layer::AleConnectRedirectV6 => FWPM_LAYER_ALE_CONNECT_REDIRECT_V6, + Layer::AleBindRedirectV4 => FWPM_LAYER_ALE_BIND_REDIRECT_V4, + Layer::AleBindRedirectV6 => FWPM_LAYER_ALE_BIND_REDIRECT_V6, + Layer::AleResourceReleaseV4 => FWPM_LAYER_ALE_RESOURCE_RELEASE_V4, + Layer::AleResourceReleaseV6 => FWPM_LAYER_ALE_RESOURCE_RELEASE_V6, + Layer::AleEndpointClosureV4 => FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V4, + Layer::AleEndpointClosureV6 => FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V6, + } + } +} + +#[repr(usize)] +pub enum FieldsInboundIppacketV4 { + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundIppacketV6 { + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundIppacketV4 { + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundIppacketV6 { + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpforwardV4 { + IpSourceAddress, + IpDestinationAddress, + IpDestinationAddressType, + IpLocalInterface, + IpForwardInterface, + SourceInterfaceIndex, + SourceSubInterfaceIndex, + DestinationInterfaceIndex, + DestinationSubInterfaceIndex, + Flags, + IpPhysicalArrivalInterface, + ArrivalInterfaceProfileId, + IpPhysicalNexthopInterface, + NexthopInterfaceProfileId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpforwardV6 { + IpSourceAddress, + IpDestinationAddress, + IpDestinationAddressType, + IpLocalInterface, + IpForwardInterface, + SourceInterfaceIndex, + SourceSubInterfaceIndex, + DestinationInterfaceIndex, + DestinationSubInterfaceIndex, + Flags, + IpPhysicalArrivalInterface, + ArrivalInterfaceProfileId, + IpPhysicalNexthopInterface, + NexthopInterfaceProfileId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundTransportV4 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + ProfileId, + IpsecSecurityRealmId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundTransportFas { + FieldInboundTransportFastMax, +} + +#[repr(usize)] +pub enum FieldsOutboundTransportFas { + FieldOutboundTransportFastMax, +} + +#[repr(usize)] +pub enum FieldsInboundTransportV6 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Flags, + InterfaceType, + TunnelType, + ProfileId, + IpsecSecurityRealmId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundTransportV4 { + IpProtocol, + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + IpDestinationAddressType, + Flags, + InterfaceType, + TunnelType, + ProfileId, + IpsecSecurityRealmId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundTransportV6 { + IpProtocol, + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + IpDestinationAddressType, + Flags, + InterfaceType, + TunnelType, + ProfileId, + IpsecSecurityRealmId, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsStreamV4 { + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + Direction, + Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsStreamV6 { + IpLocalAddress, + IpLocalAddressType, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + Direction, + Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsDatagramDataV4 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Direction, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsDatagramDataV6 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Direction, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsStreamPacketV4 { + IpLocalAddress, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Direction, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsStreamPacketV6 { + IpLocalAddress, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + InterfaceIndex, + SubInterfaceIndex, + Direction, + Flags, + InterfaceType, + TunnelType, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundIcmpErrorV4 { + EmbeddedProtocol, + IpLocalAddress, + IpRemoteAddress, + EmbeddedRemoteAddress, + EmbeddedLocalAddressType, + EmbeddedLocalPort, + EmbeddedRemotePort, + IpLocalInterface, + IcmpType, + IcmpCode, + InterfaceIndex, // of local/delivery interface + SubInterfaceIndex, // of arrival interface + InterfaceType, // of local/delivery interface + TunnelType, // of local/delivery interface + IpArrivalInterface, + ArrivalInterfaceIndex, + ArrivalInterfaceType, + ArrivalTunnelType, + Flags, + ArrivalInterfaceProfileId, + InterfaceQuarantineEpoch, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundIcmpErrorV6 { + EmbeddedProtocol, + IpLocalAddress, + IpRemoteAddress, + EmbeddedRemoteAddress, + EmbeddedLocalAddressType, + EmbeddedLocalPort, + EmbeddedRemotePort, + IpLocalInterface, + IcmpType, + IcmpCode, + InterfaceIndex, // of local/delivery interface + SubInterfaceIndex, // of arrival interface + InterfaceType, // of local/delivery interface + TunnelType, // of local/delivery interface + IpArrivalInterface, + ArrivalInterfaceIndex, + ArrivalInterfaceType, + ArrivalTunnelType, + Flags, + ArrivalInterfaceProfileId, + InterfaceQuarantineEpoch, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundIcmpErrorV4 { + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalInterface, + IcmpType, + IcmpCode, + InterfaceIndex, + SubInterfaceIndex, + InterfaceType, + TunnelType, + Flags, + NexthopInterfaceProfileId, + InterfaceQuarantineEpoch, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundIcmpErrorV6 { + IpLocalAddress, + IpRemoteAddress, + IpLocalAddressType, + IpLocalInterface, + IpLocalPort, + IpRemotePort, + InterfaceIndex, + SubInterfaceIndex, + InterfaceType, + TunnelType, + Flags, + NexthopInterfaceProfileId, + InterfaceQuarantineEpoch, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleResourceAssignmentV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + AlePromiscuousMode, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + LocalInterfaceProfileId, + SioFirewallSocketProperty, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Max, +} + +#[repr(usize)] +pub enum FieldsAleResourceAssignmentV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + AlePromiscuousMode, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + LocalInterfaceProfileId, + SioFirewallSocketProperty, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Max, +} + +#[repr(usize)] +pub enum FieldsAleResourceReleaseV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpLocalInterface, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleResourceReleaseV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpLocalInterface, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleEndpointClosureV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + IpLocalInterface, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleEndpointClosureV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + IpLocalInterface, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthListenV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + LocalInterfaceProfileId, + SioFirewallSocketProperty, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthListenV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + LocalInterfaceProfileId, + SioFirewallSocketProperty, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthRecvAcceptV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpLocalInterface, + Flags, + SioFirewallSystemPort, + NapContext, + InterfaceType, // of local/delivery interface + TunnelType, // of local/delivery interface + InterfaceIndex, // of local/delivery interface + SubInterfaceIndex, // of arrival interface + IpArrivalInterface, + ArrivalInterfaceType, + ArrivalTunnelType, + ArrivalInterfaceIndex, + NexthopSubInterfaceIndex, + IpNexthopInterface, + NexthopInterfaceType, + NexthopTunnelType, + NexthopInterfaceIndex, + OriginalProfileId, + CurrentProfileId, + ReauthorizeReason, + OriginalIcmpType, + InterfaceQuarantineEpoch, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthRecvAcceptV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpLocalInterface, + Flags, + SioFirewallSystemPort, + NapContext, + InterfaceType, // of local/delivery interface + TunnelType, // of local/delivery interface + InterfaceIndex, // of local/delivery interface + SubInterfaceIndex, // of arrival interface + IpArrivalInterface, + ArrivalInterfaceType, + ArrivalTunnelType, + ArrivalInterfaceIndex, + NexthopSubInterfaceIndex, + IpNexthopInterface, + NexthopInterfaceType, + NexthopTunnelType, + NexthopInterfaceIndex, + OriginalProfileId, + CurrentProfileId, + ReauthorizeReason, + OriginalIcmpType, + InterfaceQuarantineEpoch, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsAleBindRedirectV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleBindRedirectV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + Flags, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleConnectRedirectV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpDestinationAddressType, + IpRemotePort, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleConnectRedirectV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpDestinationAddressType, + IpRemotePort, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthConnectV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpDestinationAddressType, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + InterfaceIndex, + SubInterfaceIndex, + IpArrivalInterface, + ArrivalInterfaceType, + ArrivalTunnelType, + ArrivalInterfaceIndex, + NexthopSubInterfaceIndex, + IpNexthopInterface, + NexthopInterfaceType, + NexthopTunnelType, + NexthopInterfaceIndex, + OriginalProfileId, + CurrentProfileId, + ReauthorizeReason, + PeerName, + OriginalIcmpType, + InterfaceQuarantineEpoch, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + AleEffectiveName, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsAleAuthConnectV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpDestinationAddressType, + IpLocalInterface, + Flags, + InterfaceType, + TunnelType, + InterfaceIndex, + SubInterfaceIndex, + IpArrivalInterface, + ArrivalInterfaceType, + ArrivalTunnelType, + ArrivalInterfaceIndex, + NexthopSubInterfaceIndex, + IpNexthopInterface, + NexthopInterfaceType, + NexthopTunnelType, + NexthopInterfaceIndex, + OriginalProfileId, + CurrentProfileId, + ReauthorizeReason, + PeerName, + OriginalIcmpType, + InterfaceQuarantineEpoch, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + AleEffectiveName, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsAleFlowEstablishedV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpDestinationAddressType, + IpLocalInterface, + Direction, + InterfaceType, + TunnelType, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsAleFlowEstablishedV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpRemotePort, + AleRemoteUserId, + AleRemoteMachineId, + IpDestinationAddressType, + IpLocalInterface, + Direction, + InterfaceType, + TunnelType, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + // + // These reserved fields MUST be in this order. DO NOT change their order + // + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Max, +} + +#[repr(usize)] +pub enum FieldsNameResolutionCacheV4 { + AleUserId, + AleAppId, + IpRemoteAddress, + PeerName, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsNameResolutionCacheV6 { + AleUserId, + AleAppId, + IpRemoteAddress, + PeerName, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundMacFrameEthernet { + InterfaceMacAddress, + MacLocalAddress, + MacRemoteAddress, + MacLocalAddressType, + MacRemoteAddressType, + EtherType, + VlanId, + Interface, + InterfaceIndex, + NdisPort, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundMacFrameEthernet { + InterfaceMacAddress, + MacLocalAddress, + MacRemoteAddress, + MacLocalAddressType, + MacRemoteAddressType, + EtherType, + VlanId, + Interface, + InterfaceIndex, + NdisPort, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundMacFrameNative { + NdisMediaType, + NdisPhysicalMediaType, + Interface, + InterfaceType, + InterfaceIndex, + NdisPort, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundMacFrameNativeFast { + FastMax, +} + +#[repr(usize)] +pub enum FieldsOutboundMacFrameNative { + NdisMediaType, + NdisPhysicalMediaType, + Interface, + InterfaceType, + InterfaceIndex, + NdisPort, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundMacFrameNativeFast { + Max, +} + +#[repr(usize)] +pub enum FieldsIngressVswitchEthernet { + MacSourceAddress, + MacSourceAddressType, + MacDestinationAddress, + MacDestinationAddressType, + EtherType, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsEgressVswitchEthernet { + MacSourceAddress, + MacSourceAddressType, + MacDestinationAddress, + MacDestinationAddressType, + EtherType, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + VswitchDestinationInterfaceId, + VswitchDestinationInterfaceType, + VswitchDestinationVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsIngressVswitchTransportV4 { + IpSourceAddress, + IpDestinationAddress, + IpProtocol, + IpSourcePort, + IpDestinationPort, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsIngressVswitchTransportV6 { + IpSourceAddress, + IpDestinationAddress, + IpProtocol, + IpSourcePort, + IpDestinationPort, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsEgressVswitchTransportV4 { + IpSourceAddress, + IpDestinationAddress, + IpProtocol, + IpSourcePort, + IpDestinationPort, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + VswitchDestinationInterfaceId, + VswitchDestinationInterfaceType, + VswitchDestinationVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsEgressVswitchTransportV6 { + IpSourceAddress, + IpDestinationAddress, + IpProtocol, + IpSourcePort, + IpDestinationPort, + VlanId, + VswitchTenantNetworkId, + VswitchId, + VswitchNetworkType, + VswitchSourceInterfaceId, + VswitchSourceInterfaceType, + VswitchSourceVmId, + VswitchDestinationInterfaceId, + VswitchDestinationInterfaceType, + VswitchDestinationVmId, + L2Flags, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpsecKmDemuxV4 { + IpLocalAddress, + IpRemoteAddress, + QmMode, + IpLocalInterface, + CurrentProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpsecKmDemuxV6 { + IpLocalAddress, + IpRemoteAddress, + QmMode, + IpLocalInterface, + CurrentProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpsecV4 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + ProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsIpsecV6 { + IpProtocol, + IpLocalAddress, + IpRemoteAddress, + IpLocalPort, + IpRemotePort, + IpLocalInterface, + ProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsIkeextV4 { + IpLocalAddress, + IpRemoteAddress, + IpLocalInterface, + ProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsIkeextV6 { + IpLocalAddress, + IpRemoteAddress, + IpLocalInterface, + ProfileId, + IpsecSecurityRealmId, + Max, +} + +#[repr(usize)] +pub enum FieldsRpcUm { + RemoteUserToken, + AuthLevel, + AuthType, + DcomAppId, + IfFlag, + IfUuid, + IfVersion, + ImageName, + LocalAddrV4, + LocalAddrV6, + LocalPort, + Max, + Pipe, + Protocol, + RemoteAddrV4, + RemoteAddrV6, + SecEncryptAlgorithm, + SecKeySize, +} + +#[repr(usize)] +pub enum FieldsRpcEpmap { + RemoteUserToken, + IfUuid, + IfVersion, + Protocol, + AuthType, + AuthLevel, + SecEncryptAlgorithm, + SecKeySize, + LocalAddrV4, + LocalAddrV6, + LocalPort, + Pipe, + RemoteAddrV4, + RemoteAddrV6, + Max, +} + +#[repr(usize)] +pub enum FieldsRpcEpAdd { + ProcessWithRpcIfUuid, + Protocol, + EpValue, + EpFlags, + Max, +} + +#[repr(usize)] +pub enum FieldsRpcProxyConn { + ClientToken, + ServerName, + ServerPort, + ProxyAuthType, + ClientCertKeyLength, + ClientCertOid, + Max, +} + +#[repr(usize)] +pub enum FieldsRpcProxyIf { + ClientToken, + IfUuid, + IfVersion, + ServerName, + ServerPort, + ProxyAuthType, + ClientCertKeyLength, + ClientCertOid, + Max, +} + +#[repr(usize)] +pub enum FieldsKmAuthorization { + RemoteId, + AuthenticationType, + KmType, + Direction, + KmMode, + IpsecPolicyKey, + NapContext, + Max, +} + +#[repr(usize)] +pub enum FieldsInboundReserved2 { + Reserved0, + Reserved1, + Reserved2, + Reserved3, + Reserved4, + Reserved5, + Reserved6, + Reserved7, + Reserved8, + Reserved9, + Reserved10, + Reserved11, + Reserved12, + Reserved13, + Reserved14, + Reserved15, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundNetworkConnectionPolicyV4 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpDestinationAddressType, + IpRemotePort, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} + +#[repr(usize)] +pub enum FieldsOutboundNetworkConnectionPolicyV6 { + AleAppId, + AleUserId, + IpLocalAddress, + IpLocalAddressType, + IpLocalPort, + IpProtocol, + IpRemoteAddress, + IpDestinationAddressType, + IpRemotePort, + Flags, + AleOriginalAppId, + AlePackageId, + AleSecurityAttributeFqbnValue, + CompartmentId, + Max, +} diff --git a/windows_kext/wdk/src/filter_engine/metadata.rs b/windows_kext/wdk/src/filter_engine/metadata.rs new file mode 100644 index 000000000..294197866 --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/metadata.rs @@ -0,0 +1,175 @@ +use core::{ffi::c_void, ptr::NonNull}; + +use alloc::string::String; +use widestring::U16CString; +use windows_sys::Win32::{ + Foundation::HANDLE, + NetworkManagement::{ + IpHelper::IP_ADDRESS_PREFIX, + WindowsFilteringPlatform::{ + FWPS_METADATA_FIELD_COMPLETION_HANDLE, FWPS_METADATA_FIELD_PROCESS_ID, + FWPS_METADATA_FIELD_PROCESS_PATH, FWPS_METADATA_FIELD_REMOTE_SCOPE_ID, + FWPS_METADATA_FIELD_TRANSPORT_CONTROL_DATA, + FWPS_METADATA_FIELD_TRANSPORT_ENDPOINT_HANDLE, FWP_BYTE_BLOB, FWP_DIRECTION, + }, + }, + Networking::WinSock::SCOPE_ID, +}; + +#[repr(C)] +pub(crate) struct FwpsIncomingMetadataValues { + /// Bitmask representing which values are set. + current_metadata_values: u32, + /// Internal flags; + flags: u32, + /// Reserved for system use. + reserved: u64, + /// Discard module and reason. + discard_metadata: FwpsDiscardMetadata0, + /// Flow Handle. + flow_handle: u64, + /// IP Header size. + ip_header_size: u32, + /// Transport Header size + transport_header_size: u32, + /// Process Path. + process_path: *const FWP_BYTE_BLOB, + /// Token used for authorization. + token: u64, + /// Process Id. + process_id: u64, + /// Source and Destination interface indices for discard indications. + source_interface_index: u32, + destination_interface_index: u32, + /// Compartment Id for injection APIs. + compartment_id: u32, + /// Fragment data for inbound fragments. + fragment_metadata: FwpsInboundFragmentMetadata0, + /// Path MTU for outbound packets (to enable calculation of fragments). + path_mtu: u32, + /// Completion handle (required in order to be able to pend at this layer). + completion_handle: HANDLE, + /// Endpoint handle for use in outbound transport layer injection. + transport_endpoint_handle: u64, + /// Remote scope id for use in outbound transport layer injection. + remote_scope_id: SCOPE_ID, + /// Socket control data (and length) for use in outbound transport layer injection. + control_data: *const u8, + control_data_length: u32, + /// Direction for the current packet. Only specified for ALE re-authorization. + packet_direction: FWP_DIRECTION, + /// Raw IP header (and length) if the packet is sent with IP header from a RAW socket. + header_include_header: *mut c_void, + header_include_header_length: u32, + destination_prefix: IP_ADDRESS_PREFIX, + frame_length: u16, + parent_endpoint_handle: u64, + icmp_id_and_sequence: u32, + /// PID of the process that will be accepting the redirected connection + local_redirect_target_pid: u64, + /// original destination of a redirected connection + original_destination: *mut c_void, + redirect_records: HANDLE, + /// Bitmask representing which L2 values are set. + current_l2_metadata_values: u32, + /// L2 layer Flags; + l2_flags: u32, + ethernet_mac_header_size: u32, + wifi_operation_mode: u32, + padding0: u32, + padding1: u16, + padding2: u32, + v_switch_packet_context: HANDLE, + sub_process_tag: *mut c_void, + // Reserved for system use. + reserved1: u64, +} + +impl FwpsIncomingMetadataValues { + pub(crate) fn has_field(&self, field: u32) -> bool { + self.current_metadata_values & field > 0 + } + + pub(crate) fn get_process_id(&self) -> Option { + if self.has_field(FWPS_METADATA_FIELD_PROCESS_ID) { + return Some(self.process_id); + } + + None + } + + pub(crate) unsafe fn get_process_path(&self) -> Option { + if self.has_field(FWPS_METADATA_FIELD_PROCESS_PATH) { + if let Ok(path16) = U16CString::from_ptr( + core::mem::transmute((*self.process_path).data), + (*self.process_path).size as usize / 2, + ) { + if let Ok(path) = path16.to_string() { + return Some(path); + } + } + } + + None + } + + pub(crate) fn get_completion_handle(&self) -> Option { + if self.has_field(FWPS_METADATA_FIELD_COMPLETION_HANDLE) { + return Some(self.completion_handle); + } + + None + } + + pub(crate) fn get_transport_endpoint_handle(&self) -> Option { + if self.has_field(FWPS_METADATA_FIELD_TRANSPORT_ENDPOINT_HANDLE) { + return Some(self.transport_endpoint_handle); + } + + None + } + + pub(crate) fn get_remote_scope_id(&self) -> Option { + if self.has_field(FWPS_METADATA_FIELD_REMOTE_SCOPE_ID) { + return Some(self.remote_scope_id); + } + + None + } + + pub(crate) unsafe fn get_control_data(&self) -> Option> { + if self.has_field(FWPS_METADATA_FIELD_TRANSPORT_CONTROL_DATA) { + if self.control_data.is_null() || self.control_data_length == 0 { + return None; + } + let ptr = NonNull::new(self.control_data as *mut u8).unwrap(); + let slice = NonNull::slice_from_raw_parts(ptr, self.control_data_length as usize); + return Some(slice); + } + + None + } +} + +#[allow(dead_code)] +#[repr(C)] +enum FwpsDiscardModule0 { + FwpsDiscardModuleNetwork = 0, + FwpsDiscardModuleTransport = 1, + FwpsDiscardModuleGeneral = 2, + FwpsDiscardModuleMax = 3, +} + +#[repr(C)] +struct FwpsDiscardMetadata0 { + discard_module: FwpsDiscardModule0, + discard_reason: u32, + filter_id: u64, +} + +#[repr(C)] +struct FwpsInboundFragmentMetadata0 { + fragment_identification: u32, + fragment_offset: u16, + fragment_length: u32, +} diff --git a/windows_kext/wdk/src/filter_engine/mod.rs b/windows_kext/wdk/src/filter_engine/mod.rs new file mode 100644 index 000000000..7e6cc20f5 --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/mod.rs @@ -0,0 +1,232 @@ +use core::ffi::c_void; + +use crate::alloc::borrow::ToOwned; +use crate::driver::Driver; +use crate::ffi::FWPS_FILTER2; +use crate::filter_engine::transaction::Transaction; +use crate::{dbg, info}; +use alloc::boxed::Box; +use alloc::string::String; +use alloc::{format, vec::Vec}; +use windows_sys::Wdk::Foundation::DEVICE_OBJECT; +use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}; + +use self::callout::{Callout, FilterType}; +use self::callout_data::CalloutData; +use self::classify::ClassifyOut; +use self::layer::IncomingValues; +use self::metadata::FwpsIncomingMetadataValues; + +pub mod callout; +pub mod callout_data; +pub(crate) mod classify; +#[allow(dead_code)] +pub mod ffi; +pub mod layer; +pub(crate) mod metadata; +pub mod net_buffer; +pub mod packet; +pub mod stream_data; +pub mod transaction; +// Helper functions for ALE Readirect layers. Not needed for the current implementation. +// pub mod connect_request; + +pub struct FilterEngine { + device_object: *mut DEVICE_OBJECT, + handle: HANDLE, + sublayer_guid: u128, + committed: bool, + callouts: Option>>, +} + +impl FilterEngine { + pub fn new(driver: &Driver, layer_guid: u128) -> Result { + let filter_engine_handle: HANDLE; + match ffi::create_filter_engine() { + Ok(handle) => { + filter_engine_handle = handle; + } + Err(code) => { + return Err(format!("failed to initialize filter engine {}", code).to_owned()); + } + } + Ok(Self { + device_object: driver.get_device_object(), + handle: filter_engine_handle, + sublayer_guid: layer_guid, + committed: false, + callouts: None, + }) + } + + pub fn commit(&mut self, callouts: Vec) -> Result<(), String> { + { + // Begin write transaction. This is also a lock guard. + let mut filter_engine = match Transaction::begin_write(self) { + Ok(transaction) => transaction, + Err(err) => { + return Err(err); + } + }; + + if let Err(err) = filter_engine.register_sublayer() { + return Err(format!("filter_engine: {}", err)); + } + + dbg!("Callouts count: {}", callouts.len()); + let mut boxed_callouts = Vec::new(); + // Register all callouts + for callout in callouts { + let mut callout = Box::new(callout); + callout.address = callout.as_ref() as *const Callout as u64; + + if let Err(err) = callout.register_callout( + filter_engine.handle, + filter_engine.device_object, + catch_all_callout, + ) { + // This will destroy the callout structs. + return Err(err); + } + if let Err(err) = + callout.register_filter(filter_engine.handle, filter_engine.sublayer_guid) + { + // This will destroy the callout structs. + return Err(err); + } + dbg!( + "registering callout: {} -> {}", + callout.name, + callout.filter_id + ); + boxed_callouts.push(callout) + } + if let Some(callouts) = &mut filter_engine.callouts { + callouts.append(&mut boxed_callouts); + } else { + filter_engine.callouts = Some(boxed_callouts); + } + + if let Err(err) = filter_engine.commit() { + return Err(err); + } + } + self.committed = true; + info!("transaction committed"); + + return Ok(()); + } + + pub fn reset_all_filters(&mut self) -> Result<(), String> { + // Begin to write transaction. This is also a lock guard. It will abort if transaction is not committed. + let mut filter_engine = match Transaction::begin_write(self) { + Ok(transaction) => transaction, + Err(err) => { + return Err(err); + } + }; + let filter_engine_handle = filter_engine.handle; + let sublayer_guid = filter_engine.sublayer_guid; + if let Some(callouts) = &mut filter_engine.callouts { + for callout in callouts { + if let FilterType::Resettable = callout.filter_type { + if callout.filter_id != 0 { + // Remove old filter. + if let Err(err) = + ffi::unregister_filter(filter_engine_handle, callout.filter_id) + { + return Err(format!("filter_engine: {}", err)); + } + callout.filter_id = 0; + } + // Create new filter. + if let Err(err) = callout.register_filter(filter_engine_handle, sublayer_guid) { + return Err(format!("filter_engine: {}", err)); + } + } + } + } + // Commit transaction. + if let Err(err) = filter_engine.commit() { + return Err(err); + } + return Ok(()); + } + + fn register_sublayer(&self) -> Result<(), String> { + let result = ffi::register_sublayer( + self.handle, + "PortmasterSublayer", + "The Portmaster sublayer holds all it's filters.", + self.sublayer_guid, + ); + if let Err(code) = result { + return Err(format!("failed to register sublayer: {}", code)); + } + + return Ok(()); + } +} + +impl Drop for FilterEngine { + fn drop(&mut self) { + dbg!("Unregistering callouts"); + if let Some(callouts) = &self.callouts { + for callout in callouts { + if callout.registered { + if let Err(code) = ffi::unregister_callout(callout.id) { + dbg!("failed to unregister callout: {}", code); + } + if callout.filter_id != 0 { + if let Err(code) = ffi::unregister_filter(self.handle, callout.filter_id) { + dbg!("failed to unregister filter: {}", code) + } + } + } + } + } + + if self.committed { + if let Err(code) = ffi::unregister_sublayer(self.handle, self.sublayer_guid) { + dbg!("Failed to unregister sublayer: {}", code); + } + } + + if self.handle != 0 && self.handle != INVALID_HANDLE_VALUE { + _ = ffi::filter_engine_close(self.handle); + } + } +} + +#[no_mangle] +unsafe extern "C" fn catch_all_callout( + fixed_values: *const IncomingValues, + meta_values: *const FwpsIncomingMetadataValues, + layer_data: *mut c_void, + _context: *mut c_void, + filter: *const FWPS_FILTER2, + _flow_context: u64, + classify_out: *mut ClassifyOut, +) { + let filter = &(*filter); + // Filter context is the address of the callout. + let callout = filter.context as *mut Callout; + + if let Some(callout) = callout.as_ref() { + // Setup callout data. + let array = core::slice::from_raw_parts( + (*fixed_values).incoming_value_array, + (*fixed_values).value_count as usize, + ); + let data = CalloutData { + layer: callout.layer, + callout_id: filter.context as usize, + values: array, + metadata: meta_values, + classify_out, + layer_data, + }; + // Call the defined function. + (callout.callout_fn)(data); + } +} diff --git a/windows_kext/wdk/src/filter_engine/net_buffer.rs b/windows_kext/wdk/src/filter_engine/net_buffer.rs new file mode 100644 index 000000000..f52745470 --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/net_buffer.rs @@ -0,0 +1,355 @@ +use core::mem::MaybeUninit; + +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; +use windows_sys::Wdk::System::SystemServices::{ + IoAllocateMdl, IoFreeMdl, MmBuildMdlForNonPagedPool, +}; + +use crate::{ + allocator::POOL_TAG, + ffi::{ + FwpsAllocateNetBufferAndNetBufferList0, FwpsFreeNetBufferList0, + NdisAdvanceNetBufferDataStart, NdisAllocateNetBufferListPool, NdisFreeNetBufferListPool, + NdisGetDataBuffer, NdisRetreatNetBufferDataStart, NDIS_HANDLE, NDIS_OBJECT_TYPE_DEFAULT, + NET_BUFFER_LIST, NET_BUFFER_LIST_POOL_PARAMETERS, + NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1, + }, + utils::check_ntstatus, +}; + +pub struct NetBufferList { + pub(crate) nbl: *mut NET_BUFFER_LIST, + data: Option>, + advance_on_drop: Option, +} + +impl NetBufferList { + pub fn new(nbl: *mut NET_BUFFER_LIST) -> NetBufferList { + NetBufferList { + nbl, + data: None, + advance_on_drop: None, + } + } + + pub fn iter(&self) -> NetBufferListIter { + NetBufferListIter(self.nbl) + } + + pub fn read_bytes(&self, buffer: &mut [u8]) -> Result<(), ()> { + unsafe { + let Some(nbl) = self.nbl.as_ref() else { + return Err(()); + }; + let nb = nbl.Header.first_net_buffer; + if let Some(nb) = nb.as_ref() { + let data_length = nb.nbSize.DataLength; + if data_length == 0 { + return Err(()); + } + + if buffer.len() > data_length as usize { + return Err(()); + } + + let mut ptr = + NdisGetDataBuffer(nb, buffer.len() as u32, core::ptr::null_mut(), 1, 0); + if !ptr.is_null() { + buffer.copy_from_slice(core::slice::from_raw_parts(ptr, buffer.len())); + return Ok(()); + } + + ptr = NdisGetDataBuffer(nb, buffer.len() as u32, buffer.as_mut_ptr(), 1, 0); + if !ptr.is_null() { + return Ok(()); + } + } + } + return Err(()); + } + + pub fn clone(&self, net_allocator: &NetworkAllocator) -> Result { + unsafe { + let Some(nbl) = self.nbl.as_ref() else { + return Err("net buffer list is null".to_string()); + }; + + let nb = nbl.Header.first_net_buffer; + if let Some(nb) = nb.as_ref() { + let data_length = nb.nbSize.DataLength; + if data_length == 0 { + return Err("can't clone empty packet".to_string()); + } + + // Allocate space in buffer, if buffer is too small. + let mut buffer = alloc::vec![0 as u8; data_length as usize]; + + let ptr = NdisGetDataBuffer(nb, data_length, buffer.as_mut_ptr(), 1, 0); + + if !ptr.is_null() { + buffer.copy_from_slice(core::slice::from_raw_parts(ptr, data_length as usize)); + } else { + let ptr = NdisGetDataBuffer(nb, data_length, buffer.as_mut_ptr(), 1, 0); + if ptr.is_null() { + return Err("failed to copy packet buffer".to_string()); + } + } + + let new_nbl = net_allocator.wrap_packet_in_nbl(&buffer)?; + + return Ok(NetBufferList { + nbl: new_nbl, + data: Some(buffer), + advance_on_drop: None, + }); + } else { + return Err("net buffer is null".to_string()); + } + } + } + + pub fn get_data_mut(&mut self) -> Option<&mut [u8]> { + if let Some(data) = &mut self.data { + return Some(data.as_mut_slice()); + } + return None; + } + + pub fn get_data(&self) -> Option<&[u8]> { + if let Some(data) = &self.data { + return Some(data.as_slice()); + } + return None; + } + + pub fn get_data_length(&self) -> u32 { + unsafe { + if let Some(nbl) = self.nbl.as_ref() { + let mut nb = nbl.Header.first_net_buffer; + let mut data_length = 0; + while !nb.is_null() { + let mut next = core::ptr::null_mut(); + if let Some(nb) = nb.as_ref() { + data_length += nb.nbSize.DataLength; + next = nb.Next; + } + nb = next; + } + + data_length + } else { + 0 + } + } + } + + /// Retreats the mnl of the buffer. Does not auto advance multiple retreats. + pub fn retreat(&mut self, size: u32, auto_advance: bool) { + unsafe { + if let Some(nbl) = self.nbl.as_mut() { + if let Some(nb) = nbl.Header.first_net_buffer.as_mut() { + NdisRetreatNetBufferDataStart(nb as _, size, 0, core::ptr::null_mut()); + if auto_advance { + self.advance_on_drop = Some(size); + } + } + } + } + } + + /// Advances the MDL of the buffer. + pub fn advance(&self, size: u32) { + unsafe { + if let Some(nbl) = self.nbl.as_mut() { + if let Some(nb) = nbl.Header.first_net_buffer.as_mut() { + NdisAdvanceNetBufferDataStart(nb as _, size, false, core::ptr::null_mut()); + } + } + } + } +} + +impl Drop for NetBufferList { + fn drop(&mut self) { + if let Some(advance_amount) = self.advance_on_drop { + self.advance(advance_amount); + } + if self.data.is_some() { + NetworkAllocator::free_net_buffer(self.nbl); + } + } +} + +pub struct NetBufferListIter(*mut NET_BUFFER_LIST); + +impl NetBufferListIter { + pub fn new(nbl: *mut NET_BUFFER_LIST) -> Self { + Self(nbl) + } +} + +impl Iterator for NetBufferListIter { + type Item = NetBufferList; + + fn next(&mut self) -> Option { + unsafe { + if let Some(nbl) = self.0.as_mut() { + self.0 = nbl.Header.next as _; + return Some(NetBufferList { + nbl, + data: None, + advance_on_drop: None, + }); + } + None + } + } +} + +pub fn read_packet_partial<'a>(nbl: *mut NET_BUFFER_LIST, buffer: &'a mut [u8]) -> Result<(), ()> { + unsafe { + let Some(nbl) = nbl.as_ref() else { + return Err(()); + }; + let nb = nbl.Header.first_net_buffer; + if let Some(nb) = nb.as_ref() { + let data_length = nb.nbSize.DataLength; + if data_length == 0 { + return Err(()); + } + + if buffer.len() > data_length as usize { + return Err(()); + } + + let ptr = NdisGetDataBuffer(nb, buffer.len() as u32, buffer.as_mut_ptr(), 1, 0); + if !ptr.is_null() { + return Ok(()); + } + } + } + return Err(()); +} + +pub struct RetreatGuard { + size: u32, + nbl: *mut NET_BUFFER_LIST, +} + +impl Drop for RetreatGuard { + fn drop(&mut self) { + NetworkAllocator::advance_net_buffer(self.nbl, self.size); + } +} + +pub struct NetworkAllocator { + pool_handle: NDIS_HANDLE, +} + +impl NetworkAllocator { + pub fn new() -> Self { + unsafe { + let mut params: NET_BUFFER_LIST_POOL_PARAMETERS = MaybeUninit::zeroed().assume_init(); + params.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + params.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; + params.Header.Size = core::mem::size_of::() as u16; + params.fAllocateNetBuffer = true; + params.PoolTag = POOL_TAG; + params.DataSize = 0; + + let pool_handle = NdisAllocateNetBufferListPool(core::ptr::null_mut(), ¶ms); + Self { pool_handle } + } + } + + pub fn wrap_packet_in_nbl(&self, packet_data: &[u8]) -> Result<*mut NET_BUFFER_LIST, String> { + if self.pool_handle.is_null() { + return Err("allocator not initialized".to_string()); + } + unsafe { + // Create MDL struct that will hold the buffer. + let mdl = IoAllocateMdl( + packet_data.as_ptr() as _, + packet_data.len() as u32, + 0, + 0, + core::ptr::null_mut(), + ); + if mdl.is_null() { + return Err("failed to allocate mdl".to_string()); + } + + // Build mdl with packet_data buffer. + MmBuildMdlForNonPagedPool(mdl); + + // Initialize NBL structure. + let mut nbl = core::ptr::null_mut(); + let status = FwpsAllocateNetBufferAndNetBufferList0( + self.pool_handle, + 0, + 0, + mdl, + 0, + packet_data.len() as u64, + &mut nbl, + ); + if let Err(err) = check_ntstatus(status) { + IoFreeMdl(mdl); + return Err(err); + } + return Ok(nbl); + } + } + + pub fn free_net_buffer(nbl: *mut NET_BUFFER_LIST) { + NetBufferListIter::new(nbl).for_each(|nbl| unsafe { + if let Some(nbl) = nbl.nbl.as_mut() { + if let Some(nb) = nbl.Header.first_net_buffer.as_mut() { + IoFreeMdl(nb.MdlChain); + } + FwpsFreeNetBufferList0(nbl); + } + }); + } + + pub fn retreat_net_buffer( + nbl: *mut NET_BUFFER_LIST, + size: u32, + auto_advance: bool, + ) -> Option { + unsafe { + if let Some(nbl) = nbl.as_mut() { + if let Some(nb) = nbl.Header.first_net_buffer.as_mut() { + NdisRetreatNetBufferDataStart(nb as _, size, 0, core::ptr::null_mut()); + if auto_advance { + return Some(RetreatGuard { size, nbl }); + } + } + } + } + + return None; + } + pub fn advance_net_buffer(nbl: *mut NET_BUFFER_LIST, size: u32) { + unsafe { + if let Some(nbl) = nbl.as_mut() { + if let Some(nb) = nbl.Header.first_net_buffer.as_mut() { + NdisAdvanceNetBufferDataStart(nb as _, size, false, core::ptr::null_mut()); + } + } + } + } +} + +impl Drop for NetworkAllocator { + fn drop(&mut self) { + unsafe { + if !self.pool_handle.is_null() { + NdisFreeNetBufferListPool(self.pool_handle); + } + } + } +} diff --git a/windows_kext/wdk/src/filter_engine/packet.rs b/windows_kext/wdk/src/filter_engine/packet.rs new file mode 100644 index 000000000..f6b9018fe --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/packet.rs @@ -0,0 +1,344 @@ +use alloc::{ + boxed::Box, + string::{String, ToString}, +}; +use core::{ffi::c_void, mem::MaybeUninit, ptr::NonNull}; +use windows_sys::Win32::{ + Foundation::{HANDLE, INVALID_HANDLE_VALUE}, + Networking::WinSock::{AF_INET, AF_INET6, AF_UNSPEC, SCOPE_ID}, + System::Kernel::UNSPECIFIED_COMPARTMENT_ID, +}; + +use crate::{ + ffi::{ + FwpsInjectNetworkReceiveAsync0, FwpsInjectNetworkSendAsync0, + FwpsInjectTransportReceiveAsync0, FwpsInjectTransportSendAsync1, + FwpsInjectionHandleCreate0, FwpsInjectionHandleDestroy0, FwpsQueryPacketInjectionState0, + FWPS_INJECTION_TYPE_NETWORK, FWPS_INJECTION_TYPE_TRANSPORT, FWPS_PACKET_INJECTION_STATE, + FWPS_TRANSPORT_SEND_PARAMS1, NET_BUFFER_LIST, + }, + utils::check_ntstatus, +}; + +use super::{callout_data::CalloutData, net_buffer::NetBufferList}; + +pub struct TransportPacketList { + ipv6: bool, + pub net_buffer_list_queue: NetBufferList, + remote_ip: [u8; 16], + endpoint_handle: u64, + remote_scope_id: SCOPE_ID, + control_data: Option>, + inbound: bool, + interface_index: u32, + sub_interface_index: u32, +} + +pub struct InjectInfo { + pub ipv6: bool, + pub inbound: bool, + pub loopback: bool, + pub interface_index: u32, + pub sub_interface_index: u32, +} + +pub struct Injector { + transport_inject_handle: HANDLE, + packet_inject_handle_v4: HANDLE, + packet_inject_handle_v6: HANDLE, +} + +// TODO: Implement custom allocator for the packet buffers for reusing memory and reducing allocations. This should improve latency. +impl Injector { + pub fn new() -> Self { + let mut transport_inject_handle: HANDLE = INVALID_HANDLE_VALUE; + let mut packet_inject_handle_v4: HANDLE = INVALID_HANDLE_VALUE; + let mut packet_inject_handle_v6: HANDLE = INVALID_HANDLE_VALUE; + unsafe { + let status = FwpsInjectionHandleCreate0( + AF_UNSPEC, + FWPS_INJECTION_TYPE_TRANSPORT, + &mut transport_inject_handle, + ); + if let Err(err) = check_ntstatus(status) { + crate::err!("error allocating transport inject handle: {}", err); + } + let status = FwpsInjectionHandleCreate0( + AF_INET, + FWPS_INJECTION_TYPE_NETWORK, + &mut packet_inject_handle_v4, + ); + + if let Err(err) = check_ntstatus(status) { + crate::err!("error allocating network inject handle: {}", err); + } + let status = FwpsInjectionHandleCreate0( + AF_INET6, + FWPS_INJECTION_TYPE_NETWORK, + &mut packet_inject_handle_v6, + ); + + if let Err(err) = check_ntstatus(status) { + crate::err!("error allocating network inject handle: {}", err); + } + } + Self { + transport_inject_handle, + packet_inject_handle_v4, + packet_inject_handle_v6, + } + } + + // TODO: pick a better name + pub fn from_ale_callout( + ipv6: bool, + callout_data: &CalloutData, + net_buffer_list: NetBufferList, + remote_ip_slice: &[u8], + inbound: bool, + interface_index: u32, + sub_interface_index: u32, + ) -> TransportPacketList { + let mut control_data = None; + if let Some(cd) = callout_data.get_control_data() { + control_data = Some(cd); + } + let mut remote_ip: [u8; 16] = [0; 16]; + if ipv6 { + remote_ip[0..16].copy_from_slice(&remote_ip_slice); + } else { + remote_ip[0..4].copy_from_slice(&remote_ip_slice); + } + + TransportPacketList { + ipv6, + net_buffer_list_queue: net_buffer_list, + remote_ip, + endpoint_handle: callout_data.get_transport_endpoint_handle().unwrap_or(0), + remote_scope_id: callout_data + .get_remote_scope_id() + .unwrap_or(unsafe { MaybeUninit::zeroed().assume_init() }), + control_data, + inbound, + interface_index, + sub_interface_index, + } + } + + // TODO: pick a better name. This is not transport + pub fn inject_packet_list_transport( + &self, + packet_list: TransportPacketList, + ) -> Result<(), String> { + if self.transport_inject_handle == INVALID_HANDLE_VALUE { + return Err("failed to inject packet: invalid handle value".to_string()); + } + unsafe { + let mut control_data_length = 0; + let control_data = match &packet_list.control_data { + Some(cd) => { + control_data_length = cd.len(); + cd.as_ptr().cast() + } + None => core::ptr::null_mut(), + }; + + let mut send_params = FWPS_TRANSPORT_SEND_PARAMS1 { + remote_address: &packet_list.remote_ip as _, + remote_scope_id: packet_list.remote_scope_id, + control_data: control_data as _, + control_data_length: control_data_length as u32, + header_include_header: core::ptr::null_mut(), + header_include_header_length: 0, + }; + let address_family = if packet_list.ipv6 { AF_INET6 } else { AF_INET }; + + let net_buffer_list = packet_list.net_buffer_list_queue; + // Escape the stack. Packet buffer should be valid until the packet is injected. + let boxed_nbl = Box::new(net_buffer_list); + let raw_nbl = boxed_nbl.nbl; + let raw_ptr = Box::into_raw(boxed_nbl); + + // Inject + let status = if packet_list.inbound { + FwpsInjectTransportReceiveAsync0( + self.transport_inject_handle, + 0, + core::ptr::null_mut(), + 0, + address_family, + UNSPECIFIED_COMPARTMENT_ID, + packet_list.interface_index, + packet_list.sub_interface_index, + raw_nbl, + free_packet, + raw_ptr as _, + ) + } else { + FwpsInjectTransportSendAsync1( + self.transport_inject_handle, + 0, + packet_list.endpoint_handle, + 0, + &mut send_params, + address_family, + UNSPECIFIED_COMPARTMENT_ID, + raw_nbl, + free_packet, + raw_ptr as _, + ) + }; + // Check for success + if let Err(err) = check_ntstatus(status) { + _ = Box::from_raw(raw_ptr); + return Err(err); + } + } + + return Ok(()); + } + + pub fn inject_net_buffer_list( + &self, + net_buffer_list: NetBufferList, + inject_info: InjectInfo, + ) -> Result<(), String> { + if self.packet_inject_handle_v4 == INVALID_HANDLE_VALUE { + return Err("failed to inject packet: invalid handle value".to_string()); + } + // Escape the stack, so the data can be freed after inject is complete. + let packet_boxed = Box::new(net_buffer_list); + let nbl = packet_boxed.nbl; + let packet_pointer = Box::into_raw(packet_boxed); + + let inject_handle = if inject_info.ipv6 { + self.packet_inject_handle_v6 + } else { + self.packet_inject_handle_v4 + }; + + let status = if inject_info.inbound && !inject_info.loopback { + // Inject inbound. + unsafe { + FwpsInjectNetworkReceiveAsync0( + inject_handle, + 0, + 0, + UNSPECIFIED_COMPARTMENT_ID, + inject_info.interface_index, + inject_info.sub_interface_index, + nbl, + free_packet, + (packet_pointer as *mut NetBufferList) as _, + ) + } + } else { + // Inject outbound. + unsafe { + FwpsInjectNetworkSendAsync0( + inject_handle, + 0, + 0, + UNSPECIFIED_COMPARTMENT_ID, + nbl, + free_packet, + (packet_pointer as *mut NetBufferList) as _, + ) + } + }; + + // Check for error. + if let Err(err) = check_ntstatus(status) { + unsafe { + // Get back ownership for data. + _ = Box::from_raw(packet_pointer); + } + return Err(err); + } + + return Ok(()); + } + + pub fn was_network_packet_injected_by_self( + &self, + nbl: *const NET_BUFFER_LIST, + ipv6: bool, + ) -> bool { + let inject_handle = if ipv6 { + self.packet_inject_handle_v6 + } else { + self.packet_inject_handle_v4 + }; + if inject_handle == INVALID_HANDLE_VALUE || inject_handle == 0 { + return false; + } + + unsafe { + let state = FwpsQueryPacketInjectionState0(inject_handle, nbl, core::ptr::null_mut()); + + match state { + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_NOT_INJECTED => false, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_SELF => true, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_OTHER => false, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF => true, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTION_STATE_MAX => false, + } + } + } + + pub fn was_network_packet_injected_by_self_ale(&self, nbl: *const NET_BUFFER_LIST) -> bool { + unsafe { + let state = FwpsQueryPacketInjectionState0( + self.transport_inject_handle, + nbl, + core::ptr::null_mut(), + ); + + match state { + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_NOT_INJECTED => false, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_SELF => true, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTED_BY_OTHER => false, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF => true, + FWPS_PACKET_INJECTION_STATE::FWPS_PACKET_INJECTION_STATE_MAX => false, + } + } + } +} + +impl Drop for Injector { + fn drop(&mut self) { + unsafe { + if self.transport_inject_handle != INVALID_HANDLE_VALUE + && self.transport_inject_handle != 0 + { + FwpsInjectionHandleDestroy0(self.transport_inject_handle); + self.transport_inject_handle = INVALID_HANDLE_VALUE; + } + if self.packet_inject_handle_v4 != INVALID_HANDLE_VALUE + && self.packet_inject_handle_v4 != 0 + { + FwpsInjectionHandleDestroy0(self.packet_inject_handle_v4); + self.packet_inject_handle_v4 = INVALID_HANDLE_VALUE; + } + if self.packet_inject_handle_v6 != INVALID_HANDLE_VALUE + && self.packet_inject_handle_v6 != 0 + { + FwpsInjectionHandleDestroy0(self.packet_inject_handle_v6); + self.packet_inject_handle_v6 = INVALID_HANDLE_VALUE; + } + } + } +} + +unsafe extern "C" fn free_packet( + context: *mut c_void, + net_buffer_list: *mut NET_BUFFER_LIST, + _dispatch_level: bool, +) { + if let Some(nbl) = net_buffer_list.as_ref() { + if let Err(err) = check_ntstatus(nbl.Status) { + crate::err!("inject status: {}", err); + } + } + _ = Box::from_raw(context as *mut NetBufferList); +} diff --git a/windows_kext/wdk/src/filter_engine/stream_data.rs b/windows_kext/wdk/src/filter_engine/stream_data.rs new file mode 100644 index 000000000..7f36ee90a --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/stream_data.rs @@ -0,0 +1,67 @@ +use crate::ffi::{NET_BUFFER, NET_BUFFER_LIST}; +use windows_sys::Wdk::Foundation::MDL; + +const FWPS_STREAM_FLAG_RECEIVE: u32 = 0x00000001; + +#[repr(C)] +pub enum StreamActionType { + None, + NeedMoreData, + DropConnection, + Defer, + AllowConnection, + TypeMax, +} + +#[repr(C)] +pub struct StreamCalloutIoPacket { + stream_data: *mut StreamData, + missed_bytes: usize, + count_bytes_required: usize, + count_bytes_enforced: usize, + stream_action: StreamActionType, +} + +#[repr(C)] +pub struct StreamDataOffset { + // NET_BUFFER_LIST in which offset lies. + net_buffer_list: *mut NET_BUFFER_LIST, + // NET_BUFFER in which offset lies. + net_buffer: *mut NET_BUFFER, + // MDL in which offset lies. + mdl: *mut MDL, + // Byte offset from the beginning of the MDL in which data lies. + mdl_offset: u32, + // Offset relative to the DataOffset of the NET_BUFFER. + net_buffer_offset: u32, + // Offset from the beginning of the entire stream buffer. + stream_data_offset: usize, +} + +#[repr(C)] +pub struct StreamData { + flags: u32, + data_offset: StreamDataOffset, + data_length: usize, + net_buffer_list_chain: *mut NET_BUFFER_LIST, +} + +impl StreamCalloutIoPacket { + pub fn get_data_len(&self) -> usize { + unsafe { + if let Some(stream_data) = self.stream_data.as_ref() { + return stream_data.data_length; + } + } + return 0; + } + + pub fn is_receive(&self) -> bool { + unsafe { + if let Some(stream_data) = self.stream_data.as_ref() { + return stream_data.flags & FWPS_STREAM_FLAG_RECEIVE > 0; + } + } + return false; + } +} diff --git a/windows_kext/wdk/src/filter_engine/transaction.rs b/windows_kext/wdk/src/filter_engine/transaction.rs new file mode 100644 index 000000000..e80728b3e --- /dev/null +++ b/windows_kext/wdk/src/filter_engine/transaction.rs @@ -0,0 +1,74 @@ +use core::ops::{Deref, DerefMut}; + +use super::{ffi, FilterEngine}; +use alloc::{format, string::String}; +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_TXN_READ_ONLY; + +/// Transaction guard for Filter Engine. Internally useses a lock. DO NOT USE WITH OTHER LOCKS. +pub(super) struct Transaction<'a> { + filter_engine: &'a mut FilterEngine, + committed: bool, +} + +impl<'a> Transaction<'a> { + fn begin(filter_engine: &'a mut FilterEngine, flags: u32) -> Result { + if let Err(code) = ffi::filter_engine_transaction_begin(filter_engine.handle, flags) { + return Err(format!( + "filter-engine: failed to begin transaction: {}", + code + )); + } + + Ok(Self { + filter_engine, + committed: false, + }) + } + + /// Creates a read only guard for filter engine transaction. + #[allow(dead_code)] + pub(super) fn begin_read(filter_engine: &'a mut FilterEngine) -> Result { + return Self::begin(filter_engine, FWPM_TXN_READ_ONLY); + } + + /// Creates a read/write guard for filter engine transaction. + pub(super) fn begin_write(filter_engine: &'a mut FilterEngine) -> Result { + return Self::begin(filter_engine, 0); + } + + /// Applying all the changes and releases the lock. + pub(super) fn commit(&mut self) -> Result<(), String> { + if let Err(code) = ffi::filter_engine_transaction_commit(self.filter_engine.handle) { + return Err(format!( + "filter-engine: failed to commit transaction: {}", + code + )); + } + self.committed = true; + + Ok(()) + } +} + +impl<'a> Deref for Transaction<'a> { + type Target = FilterEngine; + + fn deref(&self) -> &Self::Target { + self.filter_engine + } +} + +impl<'a> DerefMut for Transaction<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.filter_engine + } +} + +impl<'a> Drop for Transaction<'a> { + /// Releases the lock of transaction was not committed. + fn drop(&mut self) { + if !self.committed { + _ = ffi::filter_engine_transaction_abort(self.filter_engine.handle); + } + } +} diff --git a/windows_kext/wdk/src/interface.rs b/windows_kext/wdk/src/interface.rs new file mode 100644 index 000000000..88970b56b --- /dev/null +++ b/windows_kext/wdk/src/interface.rs @@ -0,0 +1,100 @@ +use crate::{ + alloc::borrow::ToOwned, + driver::Driver, + ffi::{ + pm_GetDeviceObject, pm_InitDriverObject, pm_WdfObjectGetTypedContextWorker, + WdfObjectAttributes, WdfObjectContextTypeInfo, + }, + utils::check_ntstatus, +}; +use alloc::ffi::CString; +use alloc::format; +use alloc::string::String; +use widestring::U16CString; +use windows_sys::{ + Wdk::{ + Foundation::{DEVICE_OBJECT, DRIVER_OBJECT}, + System::SystemServices::DbgPrint, + }, + Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE, UNICODE_STRING}, +}; + +// Debug +pub fn dbg_print(str: String) { + if let Ok(c_str) = CString::new(str) { + unsafe { + DbgPrint(c_str.as_ptr() as _); + } + } +} + +pub fn init_driver_object( + driver_object: *mut DRIVER_OBJECT, + registry_path: *mut UNICODE_STRING, + driver_name: &str, + object_attributes: *mut WdfObjectAttributes, +) -> Result { + let win_driver_path = format!("\\Device\\{}", driver_name); + let dos_driver_path = format!("\\??\\{}", driver_name); + + let mut wdf_driver_handle = INVALID_HANDLE_VALUE; + let mut wdf_device_handle = INVALID_HANDLE_VALUE; + + let Ok(win_driver) = U16CString::from_str(win_driver_path) else { + return Err("Invalid argument win_driver_path".to_owned()); + }; + let Ok(dos_driver) = U16CString::from_str(dos_driver_path) else { + return Err("Invalid argument dos_driver_path".to_owned()); + }; + + unsafe { + let status = pm_InitDriverObject( + driver_object, + registry_path, + &mut wdf_driver_handle, + &mut wdf_device_handle, + win_driver.as_ptr(), + dos_driver.as_ptr(), + object_attributes, + empty_wdf_driver_unload, + ); + + check_ntstatus(status)?; + + return Ok(Driver::new( + driver_object, + wdf_driver_handle, + wdf_device_handle, + )); + } +} + +pub fn get_device_context_from_wdf_device( + wdf_device: HANDLE, + type_info: &'static WdfObjectContextTypeInfo, +) -> *mut T { + unsafe { + return core::mem::transmute(pm_WdfObjectGetTypedContextWorker(wdf_device, type_info)); + } +} + +pub(crate) fn wdf_device_wdm_get_device_object(wdf_device: HANDLE) -> *mut DEVICE_OBJECT { + unsafe { + return pm_GetDeviceObject(wdf_device); + } +} + +pub fn get_device_context_from_device_object<'a, T>( + device_object: &mut DEVICE_OBJECT, +) -> Result<&'a mut T, ()> { + unsafe { + if let Some(context) = device_object.DeviceExtension.as_mut() { + return Ok(core::mem::transmute(context)); + } + } + + return Err(()); +} + +/// Empty unload event +extern "C" fn empty_wdf_driver_unload(_driver: HANDLE) {} diff --git a/windows_kext/wdk/src/ioqueue.rs b/windows_kext/wdk/src/ioqueue.rs new file mode 100644 index 000000000..8f7addfa5 --- /dev/null +++ b/windows_kext/wdk/src/ioqueue.rs @@ -0,0 +1,216 @@ +use core::{ + cell::UnsafeCell, + ffi::c_void, + fmt::Display, + marker::PhantomData, + mem::MaybeUninit, + pin::Pin, + sync::atomic::{AtomicBool, Ordering}, +}; + +use crate::dbg; +use alloc::boxed::Box; +use ntstatus::ntstatus::NtStatus; +use windows_sys::{Wdk::Foundation::KQUEUE, Win32::System::Kernel::LIST_ENTRY}; + +#[derive(Debug)] +pub enum Status { + Uninitialized, + Timeout, + UserAPC, + Abandoned, +} + +impl Display for Status { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Status::Uninitialized => write!(f, "Uninitialized"), + Status::Timeout => write!(f, "Timeout"), + Status::UserAPC => write!(f, "UserAPC"), + Status::Abandoned => write!(f, "Abandoned"), + } + } +} + +#[repr(i8)] +pub enum KprocessorMode { + KernelMode = 0, + UserMode = 1, +} + +// #[link(name = "NtosKrnl", kind = "static")] +extern "C" { + /* + KeInitializeQueue + [out] Queue + Pointer to a KQUEUE structure for which the caller must provide resident storage in nonpaged pool. This structure is defined as follows: + + [in] Count + The maximum number of threads for which the waits on the queue object can be satisfied concurrently. If this parameter is not supplied, the number of processors in the machine is used. + */ + fn KeInitializeQueue(queue: *mut KQUEUE, count: u64); + /* + KeInsertQueue returns the previous signal state of the given Queue. If it was set to zero (that is, not signaled) before KeInsertQueue was called, KeInsertQueue returns zero, meaning that no entries were queued. If it was nonzero (signaled), KeInsertQueue returns the number of entries that were queued before KeInsertQueue was called. + */ + fn KeInsertQueue(queue: *mut KQUEUE, list_entry: *mut c_void) -> i32; + /* + KeRemoveQueue returns one of the following: + A pointer to a dequeued entry from the given queue object, if one is available + STATUS_TIMEOUT, if the given Timeout interval expired before an entry became available + STATUS_USER_APC, if a user-mode APC was delivered in the context of the calling thread + STATUS_ABANDONED, if the queue has been run down + */ + fn KeRemoveQueue( + queue: *mut KQUEUE, + waitmode: KprocessorMode, + timeout: *const i64, + ) -> *mut LIST_ENTRY; + + // If the queue is empty, KeRundownQueue returns NULL; otherwise, it returns the address of the first entry in the queue. + fn KeRundownQueue(queue: *mut KQUEUE) -> *mut LIST_ENTRY; +} + +#[repr(C)] +struct Entry { + list: LIST_ENTRY, // Internal use + entry: T, +} + +pub struct IOQueue { + // The address of the value should not change. + kernel_queue: Pin>>, + initialized: AtomicBool, + _type: PhantomData, // 0 size variable. Required for the generic to work properly. Compiler limitation. +} + +unsafe impl Sync for IOQueue {} + +impl IOQueue { + /// Make sure `rundown` is called on exit, if `drop()` is not called for queue. + pub fn new() -> Self { + unsafe { + let kernel_queue = Box::pin(UnsafeCell::new(MaybeUninit::zeroed().assume_init())); + KeInitializeQueue(kernel_queue.get(), 1); + + Self { + kernel_queue, + initialized: AtomicBool::new(true), + _type: PhantomData, + } + } + } + + /// Pushes new entry of any type. + pub fn push(&self, entry: T) -> Result<(), Status> { + let kqueue = self.kernel_queue.get(); + // Allocate entry. + let list_entry = Box::new(Entry { + list: LIST_ENTRY { + Flink: core::ptr::null_mut(), + Blink: core::ptr::null_mut(), + }, + entry, + }); + let raw_ptr = Box::into_raw(list_entry); + + // Check if initialized. + let result = if self.initialized.load(Ordering::Acquire) { + unsafe { KeInsertQueue(kqueue, raw_ptr as *mut c_void) } + } else { + -1 + }; + // There is no documentation that rundown queue will return error. This is here just for good measures. + // It is unlikely to happen and not critical. + if result >= 0 { + return Ok(()); + } + + _ = unsafe { Box::from_raw(raw_ptr) }; + return Err(Status::Uninitialized); + } + + /// Returns an Element or a status. + fn pop_internal(&self, timeout: *const i64) -> Result { + unsafe { + let kqueue = self.kernel_queue.get(); + // Check if initialized. + if self.initialized.load(Ordering::Acquire) { + // Pop and check the return value. + let list_entry = + KeRemoveQueue(kqueue, KprocessorMode::KernelMode, timeout) as *mut Entry; + let error_code = NtStatus::try_from(list_entry as u32); + match error_code { + Ok(NtStatus::STATUS_TIMEOUT) => return Err(Status::Timeout), + Ok(NtStatus::STATUS_USER_APC) => return Err(Status::UserAPC), + Ok(NtStatus::STATUS_ABANDONED) => return Err(Status::Abandoned), + _ => { + // The return value is a pointer. + let list_entry = Box::from_raw(list_entry); + let entry = list_entry.entry; + return Ok(entry); + } + } + } + } + + Err(Status::Uninitialized) + } + + /// Returns element or a status. Waits until element is pushed or the queue is interrupted. + pub fn wait_and_pop(&self) -> Result { + // No timeout. + self.pop_internal(core::ptr::null()) + } + + /// Returns element or a status. Does not wait. + pub fn pop(&self) -> Result { + let timeout: i64 = 0; + self.pop_internal(&timeout) + } + + /// Returns element or a status. Waits the specified timeout. + pub fn pop_timeout(&self, timeout: i64) -> Result { + let timeout_ptr: i64 = timeout * -10000; + self.pop_internal(&timeout_ptr) + } + + /// Removes all elements and frees all the memory. The object can't be used after this function is called. + pub fn rundown(&self) { + unsafe { + let kqueue = self.kernel_queue.get(); + if kqueue.is_null() { + return; + } + + // Check if initialized. + if self.initialized.swap(false, Ordering::Acquire) { + // Remove and free all elements from the queue. + let list_entries: *mut LIST_ENTRY = KeRundownQueue(kqueue); + if !list_entries.is_null() { + let mut entry = list_entries; + while !core::ptr::eq((*entry).Flink, list_entries) { + let next = (*entry).Flink; + dbg!("discarding entry"); + let _ = Box::from_raw(entry as *mut Entry); + entry = next; + } + dbg!("discarding last entry"); + let _ = Box::from_raw(entry as *mut Entry); + } + } + } + } +} + +impl Drop for IOQueue { + fn drop(&mut self) { + // Reinitialize queue. + self.rundown(); + unsafe { + let ptr = self.kernel_queue.get(); + if !ptr.is_null() { + *ptr = MaybeUninit::zeroed().assume_init(); + } + } + } +} diff --git a/windows_kext/wdk/src/irp_helpers.rs b/windows_kext/wdk/src/irp_helpers.rs new file mode 100644 index 000000000..52960d5ea --- /dev/null +++ b/windows_kext/wdk/src/irp_helpers.rs @@ -0,0 +1,198 @@ +use windows_sys::{ + Wdk::{ + Foundation::{IO_STACK_LOCATION_0_4, IRP}, + Storage::FileSystem::IO_NO_INCREMENT, + System::SystemServices::IofCompleteRequest, + }, + Win32::Foundation::{ + NTSTATUS, STATUS_END_OF_FILE, STATUS_NOT_IMPLEMENTED, STATUS_SUCCESS, STATUS_TIMEOUT, + }, +}; + +pub struct ReadRequest<'a> { + irp: &'a mut IRP, + buffer: &'a mut [u8], + fill_index: usize, +} + +impl ReadRequest<'_> { + pub fn new(irp: &mut IRP) -> ReadRequest { + unsafe { + let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation; + let device_io = (*irp_sp).Parameters.Read; + + let system_buffer = irp.AssociatedIrp.SystemBuffer; + let buffer = core::slice::from_raw_parts_mut( + system_buffer as *mut u8, + device_io.Length as usize, + ); + ReadRequest { + irp, + buffer, + fill_index: 0, + } + } + } + + pub fn free_space(&self) -> usize { + self.buffer.len() - self.fill_index + } + + pub fn complete(&mut self) { + self.irp.IoStatus.Information = self.fill_index; + self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS; + } + + pub fn end_of_file(&mut self) { + self.irp.IoStatus.Information = self.fill_index; + self.irp.IoStatus.Anonymous.Status = STATUS_END_OF_FILE; + } + + pub fn timeout(&mut self) { + self.irp.IoStatus.Anonymous.Status = STATUS_TIMEOUT; + } + + pub fn get_status(&self) -> NTSTATUS { + unsafe { self.irp.IoStatus.Anonymous.Status } + } + + pub fn write(&mut self, bytes: &[u8]) -> usize { + let mut bytes_to_write: usize = bytes.len(); + + // Check if we have enough space + if bytes_to_write > self.free_space() { + bytes_to_write = self.free_space(); + } + + for i in 0..bytes_to_write { + self.buffer[self.fill_index + i] = bytes[i]; + } + self.fill_index = self.fill_index + bytes_to_write; + + bytes_to_write + } +} + +pub struct WriteRequest<'a> { + irp: &'a mut IRP, + buffer: &'a mut [u8], +} + +impl WriteRequest<'_> { + pub fn new(irp: &mut IRP) -> WriteRequest { + unsafe { + let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation; + let device_io = (*irp_sp).Parameters.Write; + + let system_buffer = irp.AssociatedIrp.SystemBuffer; + let buffer = core::slice::from_raw_parts_mut( + system_buffer as *mut u8, + device_io.Length as usize, + ); + WriteRequest { irp, buffer } + } + } + + pub fn get_buffer(&self) -> &[u8] { + &self.buffer + } + + pub fn mark_all_as_read(&mut self) { + self.irp.IoStatus.Information = self.buffer.len(); + } + + pub fn complete(&mut self) { + self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS; + } + + pub fn get_status(&self) -> NTSTATUS { + unsafe { self.irp.IoStatus.Anonymous.Status } + } +} + +pub struct DeviceControlRequest<'a> { + irp: &'a mut IRP, + buffer: &'a mut [u8], + fill_index: usize, + control_code: u32, +} + +// Windows-rs version of the struct is incorrect (18.01.2024). +// TODO: Use the official version when fixed. +// For future reference: https://github.com/microsoft/windows-rs/issues/2805 +#[repr(C)] +struct DeviceIOControlParams { + output_buffer_length: u32, + _padding1: u32, + input_buffer_length: u32, + _padding2: u32, + io_control_code: u32, + _padding3: u32, +} + +impl DeviceControlRequest<'_> { + pub fn new(irp: &mut IRP) -> DeviceControlRequest { + unsafe { + let irp_sp = irp.Tail.Overlay.Anonymous2.Anonymous.CurrentStackLocation; + // Use the struct directly when replaced with proper version. + let device_io: DeviceIOControlParams = + core::mem::transmute::( + (*irp_sp).Parameters.DeviceIoControl, + ); + + let system_buffer = irp.AssociatedIrp.SystemBuffer; + let buffer = core::slice::from_raw_parts_mut( + system_buffer as *mut u8, + device_io.output_buffer_length as usize, + ); + DeviceControlRequest { + irp, + buffer, + fill_index: 0, + control_code: device_io.io_control_code, + } + } + } + + pub fn get_buffer(&self) -> &[u8] { + &self.buffer + } + pub fn write(&mut self, bytes: &[u8]) -> usize { + let mut bytes_to_write: usize = bytes.len(); + + // Check if we have enough space + if bytes_to_write > self.free_space() { + bytes_to_write = self.free_space(); + } + + for i in 0..bytes_to_write { + self.buffer[self.fill_index + i] = bytes[i]; + } + self.fill_index = self.fill_index + bytes_to_write; + + bytes_to_write + } + + pub fn complete(&mut self) { + self.irp.IoStatus.Information = self.buffer.len(); + self.irp.IoStatus.Anonymous.Status = STATUS_SUCCESS; + unsafe { IofCompleteRequest(self.irp, IO_NO_INCREMENT as i8) }; + } + + pub fn not_implemented(&mut self) { + self.irp.IoStatus.Anonymous.Status = STATUS_NOT_IMPLEMENTED; + unsafe { IofCompleteRequest(self.irp, IO_NO_INCREMENT as i8) }; + } + + pub fn get_status(&self) -> NTSTATUS { + unsafe { self.irp.IoStatus.Anonymous.Status } + } + + pub fn get_control_code(&self) -> u32 { + self.control_code + } + + pub fn free_space(&self) -> usize { + self.buffer.len() - self.fill_index + } +} diff --git a/windows_kext/wdk/src/lib.rs b/windows_kext/wdk/src/lib.rs new file mode 100644 index 000000000..ba1daf76a --- /dev/null +++ b/windows_kext/wdk/src/lib.rs @@ -0,0 +1,32 @@ +#![cfg_attr(not(test), no_std)] +#![allow(clippy::needless_return)] + +extern crate alloc; + +pub mod allocator; +pub mod consts; +pub mod debug; +pub mod driver; +pub mod error; +pub mod filter_engine; +pub mod interface; +pub mod ioqueue; +pub mod irp_helpers; +pub mod rw_spin_lock; +pub mod spin_lock; +pub mod utils; + +#[allow(dead_code)] +pub mod ffi; + +// Needed by the linker for legacy reasons. Not important for rust. +#[cfg(not(test))] +#[export_name = "_fltused"] +static _FLTUSED: i32 = 0; + +// Needed by the compiler but not used. +#[cfg(not(test))] +#[no_mangle] +pub extern "system" fn __CxxFrameHandler3(_: *mut u8, _: *mut u8, _: *mut u8, _: *mut u8) -> i32 { + 0 +} diff --git a/windows_kext/wdk/src/rw_spin_lock.rs b/windows_kext/wdk/src/rw_spin_lock.rs new file mode 100644 index 000000000..625b81320 --- /dev/null +++ b/windows_kext/wdk/src/rw_spin_lock.rs @@ -0,0 +1,73 @@ +use core::cell::UnsafeCell; + +use windows_sys::Wdk::System::SystemServices::{ + ExAcquireSpinLockExclusive, ExAcquireSpinLockShared, ExReleaseSpinLockExclusive, + ExReleaseSpinLockShared, +}; + +/// A reader-writer spin lock implementation. +/// +/// This lock allows multiple readers to access the data simultaneously, +/// but only one writer can access the data at a time. It uses a spin loop +/// to wait for the lock to become available. +pub struct RwSpinLock { + data: UnsafeCell, +} + +impl RwSpinLock { + /// Creates a new `RwSpinLock` with the default initial value. + pub const fn default() -> Self { + Self { + data: UnsafeCell::new(0), + } + } + + /// Acquires a read lock on the `RwSpinLock`. + /// + /// This method blocks until a read lock can be acquired. + /// Returns a `RwLockGuard` that represents the acquired read lock. + pub fn read_lock(&self) -> RwLockGuard { + let irq = unsafe { ExAcquireSpinLockShared(self.data.get()) }; + RwLockGuard { + data: &self.data, + exclusive: false, + old_irq: irq, + } + } + + /// Acquires a write lock on the `RwSpinLock`. + /// + /// This method blocks until a write lock can be acquired. + /// Returns a `RwLockGuard` that represents the acquired write lock. + pub fn write_lock(&self) -> RwLockGuard { + let irq = unsafe { ExAcquireSpinLockExclusive(self.data.get()) }; + RwLockGuard { + data: &self.data, + exclusive: true, + old_irq: irq, + } + } +} + +/// Represents a guard for a read-write lock. +pub struct RwLockGuard<'a> { + data: &'a UnsafeCell, + exclusive: bool, + old_irq: u8, +} + +impl<'a> Drop for RwLockGuard<'a> { + /// Releases the acquired spin lock when the `RwLockGuard` goes out of scope. + /// + /// If the lock was acquired exclusively, it releases the spin lock using `ExReleaseSpinLockExclusive`. + /// If the lock was acquired shared, it releases the spin lock using `ExReleaseSpinLockShared`. + fn drop(&mut self) { + unsafe { + if self.exclusive { + ExReleaseSpinLockExclusive(self.data.get(), self.old_irq); + } else { + ExReleaseSpinLockShared(self.data.get(), self.old_irq); + } + } + } +} diff --git a/windows_kext/wdk/src/spin_lock.rs b/windows_kext/wdk/src/spin_lock.rs new file mode 100644 index 000000000..fd6802f7e --- /dev/null +++ b/windows_kext/wdk/src/spin_lock.rs @@ -0,0 +1,53 @@ +use core::{ffi::c_void, mem::MaybeUninit, ptr}; + +use windows_sys::Wdk::System::SystemServices::{ + KeAcquireInStackQueuedSpinLock, KeInitializeSpinLock, KeReleaseInStackQueuedSpinLock, + KLOCK_QUEUE_HANDLE, +}; + +// Copy of KSPIN_LOCK_QUEUE WDK C struct +#[repr(C)] +#[allow(dead_code)] +struct KSpinLockQueue { + next: *mut c_void, // struct _KSPIN_LOCK_QUEUE * volatile Next; + lock: *mut c_void, // PKSPIN_LOCK volatile Lock; +} + +// Copy of KLOCK_QUEUE_HANDLE WDK C struct +pub struct KLockQueueHandle { + lock: KLOCK_QUEUE_HANDLE, +} + +// Copy of KSpinLock WDK C struct +#[repr(C)] +pub struct KSpinLock { + ptr: *mut usize, +} + +impl KSpinLock { + pub fn create() -> Self { + unsafe { + let p: KSpinLock = KSpinLock { + ptr: ptr::null_mut(), + }; + KeInitializeSpinLock(p.ptr); + return p; + } + } + + pub fn lock(&mut self) -> KLockQueueHandle { + unsafe { + let mut handle = MaybeUninit::zeroed().assume_init(); + KeAcquireInStackQueuedSpinLock(self.ptr, &mut handle); + KLockQueueHandle { lock: handle } + } + } +} + +impl Drop for KLockQueueHandle { + fn drop(&mut self) { + unsafe { + KeReleaseInStackQueuedSpinLock(&mut self.lock); + } + } +} diff --git a/windows_kext/wdk/src/utils.rs b/windows_kext/wdk/src/utils.rs new file mode 100644 index 000000000..8a4caa56d --- /dev/null +++ b/windows_kext/wdk/src/utils.rs @@ -0,0 +1,22 @@ +use alloc::string::{String, ToString}; +use ntstatus::ntstatus::NtStatus; +use windows_sys::Win32::Foundation::STATUS_SUCCESS; + +use crate::ffi; + +pub fn check_ntstatus(status: i32) -> Result<(), String> { + if status == STATUS_SUCCESS { + return Ok(()); + } + + let Some(status) = NtStatus::from_u32(status as u32) else { + return Err("UNKNOWN_ERROR_CODE".to_string()); + }; + + return Err(status.to_string()); +} + +pub fn get_system_timestamp_ms() -> u64 { + // 100 nano seconds units -> device by 10 -> micro seconds -> divide by 1000 -> milliseconds + unsafe { ffi::pm_QuerySystemTime() / 10_000 } +}