├── .github ├── label-actions.yml └── workflows │ ├── issues-first-greet.yml │ ├── issues-label-actions.yml │ └── issues-stale.yml ├── .gitignore ├── AUTHORS ├── LICENSE ├── PortmasterKext64.inf ├── README.md ├── TRADEMARKS ├── Version.props ├── deploy.bat ├── pm_kext ├── PortmasterKext32.inx ├── PortmasterKext64.inx ├── include │ ├── packet_cache.h │ ├── pm_callouts.h │ ├── pm_checksum.h │ ├── pm_common.h │ ├── pm_debug.h │ ├── pm_debug_dll.h │ ├── pm_kernel.h │ ├── pm_netbuffer.h │ ├── pm_packet.h │ ├── pm_register.h │ ├── pm_utils.h │ ├── uthash.h │ ├── verdict_cache.h │ └── version.h ├── pm_kext.aps ├── pm_kext.vcxproj ├── pm_kext.vcxproj.filters ├── pm_kext.vcxproj.user ├── resource.h ├── src │ ├── packet_cache.c │ ├── pm_callouts.c │ ├── pm_checksum.c │ ├── pm_debug.c │ ├── pm_kernel.c │ ├── pm_netbuffer.c │ ├── pm_packet.c │ ├── pm_register.c │ ├── pm_utils.c │ └── verdict_cache.c └── version.rc ├── portmaster-windows-kext.sln ├── release ├── .gitignore ├── PortmasterKext.ddf ├── README.md ├── release_finalize.bat └── release_prepackage.bat └── release_build.bat /.github/label-actions.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Label Actions - https://github.com/dessant/label-actions 2 | 3 | community support: 4 | comment: | 5 | Hey @{issue-author}, thank you for raising this issue with us. 6 | 7 | After a first review we noticed that this does not seem to be a technical issue, but rather a configuration issue or general question about how Portmaster works. 8 | 9 | Thus, we invite the community to help with configuration and/or answering this questions. 10 | 11 | If you are in a hurry or haven't received an answer, a good place to ask is in [our Discord community](https://discord.gg/safing). 12 | 13 | If your problem or question has been resolved or answered, please come back and give an update here for other users encountering the same and then close this issue. 14 | 15 | If you are a paying subscriber and want this issue to be checked out by Safing, please send us a message [on Discord](https://discord.gg/safing) or [via Email](mailto:support@safing.io) with your username and the link to this issue, so we can prioritize accordingly. 16 | 17 | needs debug info: 18 | comment: | 19 | Hey @{issue-author}, thank you for raising this issue with us. 20 | 21 | After a first review we noticed that we will require the Debug Info for further investigation. However, you haven't supplied any Debug Info in your report. 22 | 23 | Please [collect Debug Info](https://wiki.safing.io/en/FAQ/DebugInfo) from Portmaster _while_ the reported issue is present. 24 | 25 | in/compatibility: 26 | comment: | 27 | Hey @{issue-author}, thank you for reporting on a compatibility. 28 | 29 | We keep a list of compatible software and user provided guides for improving compatibility [in the wiki - please have a look there](https://wiki.safing.io/en/Portmaster/App/Compatibility). 30 | If you can't find your software in the list, then a good starting point is our guide on [How do I make software compatible with Portmaster](https://wiki.safing.io/en/FAQ/MakeSoftwareCompatibleWithPortmaster). 31 | 32 | If you have managed to establish compatibility with an application, please share your findings here. This will greatly help other users encountering the same issues. 33 | 34 | fixed: 35 | comment: | 36 | This issue has been fixed by the recently referenced commit or PR. 37 | 38 | However, the fix is not released yet. 39 | 40 | It is expected to go into the [Beta Release Channel](https://wiki.safing.io/en/FAQ/SwitchReleaseChannel) for testing within the next two weeks and will be available for everyone within the next four weeks. While this is the typical timeline we work with, things are subject to change. 41 | -------------------------------------------------------------------------------- /.github/workflows/issues-first-greet.yml: -------------------------------------------------------------------------------- 1 | # This workflow responds to first time posters with a greeting message. 2 | # Docs: https://github.com/actions/first-interaction 3 | name: Greet New Users 4 | 5 | # This workflow is triggered when a new issue is created. 6 | on: 7 | issues: 8 | types: opened 9 | 10 | permissions: 11 | contents: read 12 | issues: write 13 | 14 | jobs: 15 | greet: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/first-interaction@v1 19 | with: 20 | repo-token: ${{ secrets.GITHUB_TOKEN }} 21 | # Respond to first time issue raisers. 22 | issue-message: | 23 | Greetings and welcome to our community! As this is the first issue you opened here, we wanted to share some useful infos with you: 24 | 25 | - 🗣️ Our community on [Discord](https://discord.gg/safing) is super helpful and active. We also have an AI-enabled support bot that knows Portmaster well and can give you immediate help. 26 | - 📖 The [Wiki](https://wiki.safing.io/) answers all common questions and has many important details. If you can't find an answer there, let us know, so we can add anything that's missing. 27 | -------------------------------------------------------------------------------- /.github/workflows/issues-label-actions.yml: -------------------------------------------------------------------------------- 1 | # This workflow responds with a message when certain labels are added to an issue or PR. 2 | # Docs: https://github.com/dessant/label-actions 3 | name: Label Actions 4 | 5 | # This workflow is triggered when a label is added to an issue. 6 | on: 7 | issues: 8 | types: labeled 9 | 10 | permissions: 11 | contents: read 12 | issues: write 13 | 14 | jobs: 15 | action: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: dessant/label-actions@v3 19 | with: 20 | github-token: ${{ secrets.GITHUB_TOKEN }} 21 | config-path: ".github/label-actions.yml" 22 | process-only: "issues" 23 | -------------------------------------------------------------------------------- /.github/workflows/issues-stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes stale issues and PRs. 2 | # Docs: https://github.com/actions/stale 3 | name: Close Stale Issues 4 | 5 | on: 6 | schedule: 7 | - cron: "17 5 * * 1-5" # run at 5:17 (UTC) on Monday to Friday 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: read 12 | issues: write 13 | 14 | jobs: 15 | stale: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/stale@v8 19 | with: 20 | repo-token: ${{ secrets.GITHUB_TOKEN }} 21 | # Increase max operations. 22 | # When using GITHUB_TOKEN, the rate limit is 1,000 requests per hour per repository. 23 | operations-per-run: 500 24 | # Handle stale issues 25 | stale-issue-label: 'stale' 26 | # Exemptions 27 | exempt-all-issue-assignees: true 28 | exempt-issue-labels: 'support,dependencies,pinned,security' 29 | # Mark as stale 30 | days-before-issue-stale: 63 # 2 months / 9 weeks 31 | stale-issue-message: | 32 | This issue has been automatically marked as inactive because it has not had activity in the past two months. 33 | 34 | If no further activity occurs, this issue will be automatically closed in one week in order to increase our focus on active topics. 35 | # Close 36 | days-before-issue-close: 7 # 1 week 37 | close-issue-message: | 38 | This issue has been automatically closed because it has not had recent activity. Thank you for your contributions. 39 | 40 | If the issue has not been resolved, you can [find more information in our Wiki](https://wiki.safing.io/) or [continue the conversation on our Discord](https://discord.gg/safing). 41 | # TODO: Handle stale PRs 42 | days-before-pr-stale: 36500 # 100 years - effectively disabled. 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | /Divert.udb 3 | /*.udb 4 | *.log 5 | *.wrn 6 | .vs 7 | /install 8 | log 9 | /sys/objfre_win7_amd64 10 | /col/tmp 11 | /test/tmp 12 | 13 | /pm_kext/x64 14 | /pm_kext_glue_dll/x64 15 | 16 | # os 17 | .DS_Store 18 | /pm_go.exe 19 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | All files in this repository (unless otherwise noted) are authored, owned and copyrighted by Safing ICS Technologies GmbH (Austria). 2 | 3 | Parts of this repository (mainly build system related things) are forked from the WinDivert project: https://github.com/basil00/Divert 4 | These portions are copyrighted by "basil": https://github.com/basil00 https://www.reqrypt.org/ 5 | The exact fork commit is: https://github.com/basil00/Divert/commit/22a5ad0996e8fb1ab4b02b748ea00d15cf9d6a76 6 | -------------------------------------------------------------------------------- /PortmasterKext64.inf: -------------------------------------------------------------------------------- 1 | ;/*++ 2 | ; 3 | ;Copyright (c) Safing ICS Technologies GmbH. 4 | ; 5 | ; This program is free software: you can redistribute it and/or modify 6 | ; it under the terms of the GNU General Public License as published by 7 | ; the Free Software Foundation, either version 3 of the License, or 8 | ; (at your option) any later version. 9 | ; 10 | ; This program is distributed in the hope that it will be useful, 11 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ; GNU General Public License for more details. 14 | ; 15 | ; You should have received a copy of the GNU General Public License 16 | ; along with this program. If not, see . 17 | ; 18 | ;--*/ 19 | 20 | [Version] 21 | Signature = "$Windows NT$" 22 | Class = WFPCALLOUTS 23 | ClassGuid = {57465043-616C-6C6F-7574-5F636C617373} 24 | Provider = %Provider% 25 | CatalogFile = PortmasterKext64.Cat 26 | DriverVer = 01/01/2019,1.0.11.0 27 | 28 | [SourceDisksNames] 29 | 1 = %DiskName% 30 | 31 | [SourceDisksFiles] 32 | PortmasterKext64.sys = 1 33 | 34 | [DestinationDirs] 35 | DefaultDestDir = 12 ; %windir%\system32\drivers 36 | PortmasterKext.DriverFiles = 12 ; %windir%\system32\drivers 37 | 38 | [DefaultInstall] 39 | OptionDesc = %Description% 40 | CopyFiles = PortmasterKext.DriverFiles 41 | 42 | [DefaultInstall.Services] 43 | AddService = %ServiceName%,,PortmasterKext.Service 44 | 45 | [DefaultUninstall] 46 | DelFiles = PortmasterKext.DriverFiles 47 | 48 | [DefaultUninstall.Services] 49 | DelService = PortmasterKext,0x200 ; SPSVCINST_STOPSERVICE 50 | 51 | [PortmasterKext.DriverFiles] 52 | PortmasterKext64.sys,,,0x00000040 ; COPYFLG_OVERWRITE_OLDER_ONLY 53 | 54 | [PortmasterKext.Service] 55 | DisplayName = %ServiceName% 56 | Description = %ServiceDesc% 57 | ServiceType = 1 ; SERVICE_KERNEL_DRIVER 58 | StartType = 0 ; SERVICE_BOOT_START 59 | ErrorControl = 1 ; SERVICE_ERROR_NORMAL 60 | ServiceBinary = %12%\PortmasterKext64.sys 61 | 62 | [Strings] 63 | Provider = "Safing ICS Technologies GmbH" 64 | DiskName = "PortmasterKext Installation Disk" 65 | Description = "PortmasterKext Driver" 66 | ServiceName = "PortmasterKext" 67 | ServiceDesc = "PortmasterKext Driver" 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **Check out the main project repository [safing/portmaster](https://github.com/safing/portmaster)** 2 | 3 | # Portmaster Windows Kernel Extensions 4 | 5 | This Windows kernel-mode driver provides the Portmaster with a high performance OS integration. 6 | It provides an internal caching for best performance and lets the Portmaster do all the decision making. 7 | 8 | ### Architecture 9 | 10 | The basic architecture is as follows: 11 | 12 | +----------------+ 13 | | | 14 | +------>| Portmaster |------>+ 15 | | | | | 16 | | +----------------+ | 17 | | | 18 | | (2a) verdict request | (3) set verdict 19 | [user mode] | | 20 | ..................|................................|........................ 21 | [kernel mode] | | 22 | | | 23 | +-------------------------------------|---+ 24 | | +-> | (2b/4) handle packet 25 | (1) packet | Driver if in cache | according to verdict 26 | ------------>| (2) check verdict cache +---------> |--------------------> 27 | | | 28 | +-----------------------------------------+ 29 | 30 | This architecture allows for high performance, as most packages are handled directly in kernel space. Only new connections need to be pushed into userland so that the Portmaster can set a verdict for them. 31 | 32 | The Driver is installed into the Windows network stack - between OSI layer 2 and 3 - and sees IP and everything above. 33 | 34 | This is how packets are handled: 35 | 36 | 1. Windows Kernel processes a packet in the TCP/IP stack (netbuffer). 37 | 2. Windows Kernel presents packet to Portmaster Kernel Extension via a callout. 38 | 3. The Portmaster Kernel Extension check its cache for a verdict for the given packet, using network (IP) and transport (TCP/UDP/...) layer data. 39 | 4. If not found, the Portmaster Kernel Extension presents the packet to the Portmaster via the blocking API call `PortmasterRecvVerdictRequest`. 40 | 5. The Portmaster inspects the packet information (`packetInfo`) and sets verdict via `PortmasterSetVerdict`. 41 | 6. If necessary, the Portmaster may also inspect the payload of packet via `PortmasterGetPayload`, using the packet ID previously received by `PortmasterRecvVerdictRequest`. 42 | 7. The Portmaster Kernel Extension holds intercepted packet in a packet cache until the verdict is set. 43 | 8. If the packet cache is full, the oldest packet will be dropped, so that the newest packet can be stored. 44 | 45 | ### Building 46 | 47 | The Windows Portmaster Kernel Extension is currently only developed and tested for the amd64 (64-bit) architecture. 48 | 49 | __Prerequesites:__ 50 | 51 | - Visual Studio 2022 52 | - Install C++ and Windows 11 SDK (22H2) components 53 | - Windows 11 WDK (22H2) (https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk) 54 | - Install Visual Studio extension 55 | 56 | __Build driver and library:__ 57 | 58 | - Launch Dev Env 59 | - `C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\LaunchDevCmd.bat` 60 | - Build driver (put into `install\WDDK\x64\Release`) 61 | - `release_build.bat` 62 | - Or, use the shortcut to build, test-sign and copy to portmaster install dir 63 | - `deploy.bat` 64 | 65 | __Setup Test Signing:__ 66 | 67 | In order to test the driver on your machine, you will have to test sign it (starting with Windows 10). 68 | 69 | 70 | Create a new certificate for test signing: (This is already done automatically with Win11 Visual Studio). 71 | 72 | :: Open a *x64 Free Build Environment* console as Administrator. 73 | 74 | :: Run the MakeCert.exe tool to create a test certificate: 75 | MakeCert -r -pe -ss TestCertStoreName -n "CN=TestCertName" CertFileName.cer 76 | 77 | :: Install the test certificate with CertMgr.exe: 78 | CertMgr /add CertFileName.cer /s /r localMachine root 79 | 80 | :: Sign pm_kernel64.sys with the test certificate: 81 | SignTool sign /v /s TestCertStoreName /n TestCertName pm_kernel64.sys 82 | 83 | 84 | Enable Test Signing on the dev machine: 85 | 86 | :: Before you can load test-signed drivers, you must enable Windows test mode. To do this, run this command: 87 | Bcdedit.exe -set TESTSIGNING ON 88 | :: Then, restart Windows. For more information, see The TESTSIGNING Boot Configuration Option. 89 | 90 | __Debugging__ 91 | 92 | Monitor memory usage with: 93 | 94 | "C:\Program Files (x86)\Windows Kits\10\Tools\10.0.22621.0\x64\poolmon.exe" /iPMas 95 | 96 | ### Releasing 97 | 98 | Please check the `release` directory for further information on releasing the Portmaster Kernel Extension. 99 | 100 | ### About 101 | 102 | The Portmaster Windows Kernel Extension is based on / influenced by the excellent work of: 103 | - [WinDivert](https://github.com/basil00/Divert), by basil. 104 | - [WFPStarterKit](https://github.com/JaredWright/WFPStarterKit), by Jared Wright 105 | -------------------------------------------------------------------------------- /TRADEMARKS: -------------------------------------------------------------------------------- 1 | The names "Safing", "Portmaster", "Gate17" and their logos are trademarks owned by Safing ICS Technologies GmbH (Austria). 2 | 3 | Although our code is free, it is very important that we strictly enforce our trademark rights, in order to be able to protect our users against people who use the marks to commit fraud. This means that, while you have considerable freedom to redistribute and modify our software, there are tight restrictions on your ability to use our names and logos in ways which fall in the domain of trademark law, even when built into binaries that we provide. 4 | 5 | This file is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. Parts of it were taken from https://www.mozilla.org/en-US/foundation/licensing/. 6 | -------------------------------------------------------------------------------- /Version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 1 6 | 1 7 | 2 8 | 0 9 | 10 | 11 | 12 | 13 | 14 | $(PM_VERSION_MAJOR) 15 | 16 | 17 | $(PM_VERSION_MINOR) 18 | 19 | 20 | $(PM_VERSION_REVISION) 21 | 22 | 23 | $(PM_VERSION_BUILD) 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /deploy.bat: -------------------------------------------------------------------------------- 1 | echo Compile, Sign and Copy the Kernel Driver 2 | set WDDK_SOURCE=install\WDDK\x64\Debug\pm_kernel_x64.sys 3 | del %WDDK_SOURCE% 4 | 5 | msbuild /t:Clean /p:Configuration=Debug /p:Platform=x64 portmaster-windows-kext.sln 6 | msbuild /t:Build /p:Configuration=Debug /p:Platform=x64 portmaster-windows-kext.sln 7 | 8 | SignTool sign /v /s TestCertStoreName /n TestCertName /fd SHA256 %WDDK_SOURCE% 9 | 10 | echo Copy the Kernel Driver to Portmaster updates dir as dev version 11 | copy %WDDK_SOURCE% C:\ProgramData\Safing\Portmaster\updates\windows_amd64\kext\portmaster-kext_v0-0-0.sys 12 | -------------------------------------------------------------------------------- /pm_kext/PortmasterKext32.inx: -------------------------------------------------------------------------------- 1 | ;/*++ 2 | ; 3 | ;Copyright (c) Safing ICS Technologies GmbH. 4 | ; 5 | ; This program is free software: you can redistribute it and/or modify 6 | ; it under the terms of the GNU General Public License as published by 7 | ; the Free Software Foundation, either version 3 of the License, or 8 | ; (at your option) any later version. 9 | ; 10 | ; This program is distributed in the hope that it will be useful, 11 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ; GNU General Public License for more details. 14 | ; 15 | ; You should have received a copy of the GNU General Public License 16 | ; along with this program. If not, see . 17 | ; 18 | ;--*/ 19 | 20 | [Version] 21 | Signature = "$Windows NT$" 22 | Class = WFPCALLOUTS 23 | ClassGuid = {57465043-616C-6C6F-7574-5F636C617373} 24 | Provider = %Provider% 25 | CatalogFile = PortmasterKext32.Cat 26 | PnpLockdown=1 27 | 28 | [SourceDisksNames] 29 | 1 = %DiskName% 30 | 31 | [SourceDisksFiles] 32 | PortmasterKext32.sys = 1 33 | 34 | [DestinationDirs] 35 | DefaultDestDir = 12 ; %windir%\system32\drivers 36 | PortmasterKext.DriverFiles = 12 ; %windir%\system32\drivers 37 | 38 | [DefaultInstall.NTx86] 39 | OptionDesc = %Description% 40 | CopyFiles = PortmasterKext.DriverFiles 41 | 42 | [DefaultInstall.NTx86.Services] 43 | AddService = %ServiceName%,,PortmasterKext.Service 44 | 45 | [PortmasterKext.DriverFiles] 46 | PortmasterKext32.sys,,,0x00000040 ; COPYFLG_OVERWRITE_OLDER_ONLY 47 | 48 | [PortmasterKext.Service] 49 | DisplayName = %ServiceName% 50 | Description = %ServiceDesc% 51 | ServiceType = 1 ; SERVICE_KERNEL_DRIVER 52 | StartType = 0 ; SERVICE_BOOT_START 53 | ErrorControl = 1 ; SERVICE_ERROR_NORMAL 54 | ServiceBinary = %12%\PortmasterKext32.sys 55 | 56 | [Strings] 57 | Provider = "Safing ICS Technologies GmbH" 58 | DiskName = "PortmasterKext Installation Disk" 59 | Description = "PortmasterKext Driver" 60 | ServiceName = "PortmasterKext" 61 | ServiceDesc = "PortmasterKext Driver" -------------------------------------------------------------------------------- /pm_kext/PortmasterKext64.inx: -------------------------------------------------------------------------------- 1 | ;/*++ 2 | ; 3 | ;Copyright (c) Safing ICS Technologies GmbH. 4 | ; 5 | ; This program is free software: you can redistribute it and/or modify 6 | ; it under the terms of the GNU General Public License as published by 7 | ; the Free Software Foundation, either version 3 of the License, or 8 | ; (at your option) any later version. 9 | ; 10 | ; This program is distributed in the hope that it will be useful, 11 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ; GNU General Public License for more details. 14 | ; 15 | ; You should have received a copy of the GNU General Public License 16 | ; along with this program. If not, see . 17 | ; 18 | ;--*/ 19 | 20 | [Version] 21 | Signature = "$Windows NT$" 22 | Class = WFPCALLOUTS 23 | ClassGuid = {57465043-616C-6C6F-7574-5F636C617373} 24 | Provider = %Provider% 25 | CatalogFile = PortmasterKext64.Cat 26 | PnpLockdown=1 27 | 28 | [SourceDisksNames] 29 | 1 = %DiskName% 30 | 31 | [SourceDisksFiles] 32 | PortmasterKext64.sys = 1 33 | 34 | [DestinationDirs] 35 | DefaultDestDir = 12 ; %windir%\system32\drivers 36 | PortmasterKext.DriverFiles = 12 ; %windir%\system32\drivers 37 | 38 | [DefaultInstall.NTamd64] 39 | OptionDesc = %Description% 40 | CopyFiles = PortmasterKext.DriverFiles 41 | 42 | [DefaultInstall.NTamd64.Services] 43 | AddService = %ServiceName%,,PortmasterKext.Service 44 | 45 | [PortmasterKext.DriverFiles] 46 | PortmasterKext64.sys,,,0x00000040 ; COPYFLG_OVERWRITE_OLDER_ONLY 47 | 48 | [PortmasterKext.Service] 49 | DisplayName = %ServiceName% 50 | Description = %ServiceDesc% 51 | ServiceType = 1 ; SERVICE_KERNEL_DRIVER 52 | StartType = 0 ; SERVICE_BOOT_START 53 | ErrorControl = 1 ; SERVICE_ERROR_NORMAL 54 | ServiceBinary = %12%\PortmasterKext64.sys 55 | 56 | [Strings] 57 | Provider = "Safing ICS Technologies GmbH" 58 | DiskName = "PortmasterKext Installation Disk" 59 | Description = "PortmasterKext Driver" 60 | ServiceName = "PortmasterKext" 61 | ServiceDesc = "PortmasterKext Driver" -------------------------------------------------------------------------------- /pm_kext/include/packet_cache.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: packet_cache.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains declaration of packet cache. IP-Packets must be cached 7 | * until we know what to do with them (block, drop, reinject). 8 | * Caching Algorithm: Last In First Out (LIFO) 9 | * 10 | * Scope: Kernelmode 11 | * (Userland for development) 12 | */ 13 | 14 | #ifndef __COL_PACKETS_H__ 15 | #define __COL_PACKETS_H__ 16 | 17 | #ifndef __LINUX_ENV__ 18 | #include 19 | #define uint8_t UINT8 20 | #define uint16_t UINT16 21 | #define uint32_t UINT32 22 | #define uint64_t UINT64 23 | #endif 24 | 25 | #define PacketCache void 26 | 27 | /** 28 | * @brief Initializes the packet cache 29 | * 30 | * @par maxSize = size of cache 31 | * @par packetCache = returns new packet_cache_t 32 | * @return error code 33 | * 34 | */ 35 | int packetCacheCreate(uint32_t maxSize, PacketCache **packetCache); 36 | 37 | /** 38 | * @brief Tears down the packet cache 39 | * 40 | * @par packet_cache = packet_cache to use 41 | * @return error code 42 | * 43 | */ 44 | int packetCacheTeardown(PacketCache *packetCache, void(*freeData)(PortmasterPacketInfo*, void*)); 45 | 46 | /** 47 | * @brief Registers a packet 48 | * 49 | * @par packetCache = packetCache to use 50 | * @par packetInfo = pointer to packetInfo 51 | * @par packet = pointer to packet 52 | * @return new packet ID 53 | * 54 | */ 55 | uint32_t packetCacheRegister(PacketCache *packetCache, PortmasterPacketInfo *packetInfo, void *packet, size_t packetLength, PortmasterPacketInfo **oldPacketInfo, void **oldPacket); 56 | 57 | /** 58 | * @brief Retrieves and deletes a packet from list, if it exists. 59 | * 60 | * @par packetCache = packetCache to use 61 | * @par packetID = registered packet ID 62 | * @par packetCache = double pointer for packetInfo return 63 | * @par packet = double pointer for packet return 64 | * @return error code 65 | * 66 | */ 67 | int packetCacheRetrieve(PacketCache *packetCache, uint32_t packetID, PortmasterPacketInfo **packetInfoPtr, void **packet, size_t *packetLength); 68 | 69 | /** 70 | * @brief Retrieves a packet from list, if it exists. 71 | * 72 | * @par packetCache = packetCache to use 73 | * @par packetID = registered packet ID 74 | * @par packet = double pointer for packet return 75 | * @return error code 76 | * 77 | */ 78 | int packetCacheGet(PacketCache *packetCache, uint32_t packetID, void **packet, size_t *packetLength); 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /pm_kext/include/pm_callouts.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_callouts.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains declaration of callouts, i.e. functions that are 7 | * called from the kernel if a net traffic filter matches. 8 | * Filters and callouts are registered in "pm_register.c" 9 | * 10 | * Scope: Kernelmode 11 | */ 12 | 13 | #ifndef PM_CALLOUTS_H 14 | #define PM_CALLOUTS_H 15 | 16 | #include "pm_kernel.h" 17 | #include "verdict_cache.h" 18 | #include "packet_cache.h" 19 | 20 | NTSTATUS initCalloutStructure(); 21 | void destroyCalloutStructure(); 22 | 23 | void classifyInboundIPv4( 24 | const FWPS_INCOMING_VALUES* inFixedValues, 25 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 26 | void* layerData, 27 | const void* classifyContext, 28 | const FWPS_FILTER* filter, 29 | UINT64 flowContext, 30 | FWPS_CLASSIFY_OUT* classifyOut); 31 | 32 | void classifyOutboundIPv4( 33 | const FWPS_INCOMING_VALUES* inFixedValues, 34 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 35 | void* layerData, 36 | const void* classifyContext, 37 | const FWPS_FILTER* filter, 38 | UINT64 flowContext, 39 | FWPS_CLASSIFY_OUT* classifyOut); 40 | 41 | void classifyInboundIPv6( 42 | const FWPS_INCOMING_VALUES* inFixedValues, 43 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 44 | void* layerData, 45 | const void* classifyContext, 46 | const FWPS_FILTER* filter, 47 | UINT64 flowContext, 48 | FWPS_CLASSIFY_OUT* classifyOut); 49 | 50 | void classifyOutboundIPv6( 51 | const FWPS_INCOMING_VALUES* inFixedValues, 52 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 53 | void* layerData, 54 | const void* classifyContext, 55 | const FWPS_FILTER* filter, 56 | UINT64 flowContext, 57 | FWPS_CLASSIFY_OUT* classifyOut); 58 | 59 | void classifyALEOutboundIPv4( 60 | const FWPS_INCOMING_VALUES* inFixedValues, 61 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 62 | void* layerData, 63 | const void* classifyContext, 64 | const FWPS_FILTER* filter, 65 | UINT64 flowContext, 66 | FWPS_CLASSIFY_OUT* classifyOut); 67 | 68 | void classifyALEInboundIPv4( 69 | const FWPS_INCOMING_VALUES* inFixedValues, 70 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 71 | void* layerData, 72 | const void* classifyContext, 73 | const FWPS_FILTER* filter, 74 | UINT64 flowContext, 75 | FWPS_CLASSIFY_OUT* classifyOut); 76 | 77 | void classifyALEOutboundIPv6( 78 | const FWPS_INCOMING_VALUES* inFixedValues, 79 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 80 | void* layerData, 81 | const void* classifyContext, 82 | const FWPS_FILTER* filter, 83 | UINT64 flowContext, 84 | FWPS_CLASSIFY_OUT* classifyOut); 85 | 86 | void classifyALEInboundIPv6( 87 | const FWPS_INCOMING_VALUES* inFixedValues, 88 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 89 | void* layerData, 90 | const void* classifyContext, 91 | const FWPS_FILTER* filter, 92 | UINT64 flowContext, 93 | FWPS_CLASSIFY_OUT* classifyOut); 94 | 95 | void classifyStreamIPv4( 96 | const FWPS_INCOMING_VALUES* inFixedValues, 97 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 98 | void* layerData, 99 | const void* classifyContext, 100 | const FWPS_FILTER* filter, 101 | UINT64 flowContext, 102 | FWPS_CLASSIFY_OUT* classifyOut); 103 | 104 | void classifyStreamIPv6( 105 | const FWPS_INCOMING_VALUES* inFixedValues, 106 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 107 | void* layerData, 108 | const void* classifyContext, 109 | const FWPS_FILTER* filter, 110 | UINT64 flowContext, 111 | FWPS_CLASSIFY_OUT* classifyOut); 112 | 113 | void classifyDatagramIPv4( 114 | const FWPS_INCOMING_VALUES* inFixedValues, 115 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 116 | void* layerData, 117 | const void* classifyContext, 118 | const FWPS_FILTER* filter, 119 | UINT64 flowContext, 120 | FWPS_CLASSIFY_OUT* classifyOut); 121 | 122 | void classifyDatagramIPv6( 123 | const FWPS_INCOMING_VALUES* inFixedValues, 124 | const FWPS_INCOMING_METADATA_VALUES* inMetaValues, 125 | void* layerData, 126 | const void* classifyContext, 127 | const FWPS_FILTER* filter, 128 | UINT64 flowContext, 129 | FWPS_CLASSIFY_OUT* classifyOut); 130 | 131 | NTSTATUS genericNotify( 132 | FWPS_CALLOUT_NOTIFY_TYPE notifyType, 133 | const GUID * filterKey, 134 | FWPS_FILTER * filter); 135 | 136 | void genericFlowDelete(UINT16 layerId, UINT32 calloutId, UINT64 flowContext); 137 | 138 | void respondWithVerdict(UINT32 id, verdict_t verdict); 139 | PacketCache* getPacketCache(); 140 | int updateVerdict(VerdictUpdateInfo*); 141 | int getConnectionsStats(PortmasterConnection *connections, int size); 142 | 143 | void clearCache(); 144 | 145 | #endif // PM_CALLOUTS_H 146 | -------------------------------------------------------------------------------- /pm_kext/include/pm_checksum.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_checksum.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains declarations for checksum calculations in order 7 | * to modify and reinject IP Packets 8 | * 9 | * Scope: Kernelmode 10 | */ 11 | 12 | #ifndef PM_CHECKSUM_H 13 | #define PM_CHECKSUM_H 14 | 15 | #include "pm_kernel.h" 16 | #include "pm_debug.h" 17 | 18 | void calcIPv4Checksum(void *data, size_t len, bool calcTransport); 19 | void calcIPv6Checksum(void *data, size_t len, bool calcTransport); 20 | size_t calcIPv4HeaderSize(void *data, size_t len); 21 | size_t calcIPv6HeaderSize(void *data, size_t len, UINT8* returnProtocol); 22 | 23 | #endif //include guard 24 | -------------------------------------------------------------------------------- /pm_kext/include/pm_common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_common.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Common definitions for kernel and userland driver 7 | * 8 | * Scope: Kernelmode 9 | * Userland 10 | */ 11 | 12 | #ifndef PM_COMMON_H 13 | #define PM_COMMON_H 14 | 15 | #define PORTMASTER_DEVICE_NAME_C "PortmasterKext" 16 | #define PORTMASTER_DEVICE_NAME L"PortmasterKext" //Wide String 17 | 18 | /****************************************************************************/ 19 | /* Portmaster TYPES */ 20 | /****************************************************************************/ 21 | 22 | /* 23 | * Container for IPv4 and IPv6 packet information. 24 | */ 25 | //#pragma pack 4 26 | typedef struct { 27 | UINT32 id; //ID from RegisterPacket 28 | UINT64 processID; //Process ID. Nice to have 29 | UINT8 direction; 30 | UINT8 ipV6; //True: IPv6, False: IPv4 31 | UINT8 protocol; //Protocol (UDP, TCP, ...) 32 | UINT8 flags; //Flags 33 | UINT32 localIP[4]; //Source Address, only srcIP[0] if IPv4 34 | UINT32 remoteIP[4]; //Destination Address 35 | UINT16 localPort; //Source Port 36 | UINT16 remotePort; //Destination port 37 | ULONG compartmentId; //Currently unused 38 | UINT32 interfaceIndex; //eth0, ... 39 | UINT32 subInterfaceIndex; 40 | UINT32 packetSize; 41 | } PortmasterPacketInfo; 42 | 43 | typedef struct { 44 | UINT32 localIP[4]; //Source Address, only srcIP[0] if IPv4 45 | UINT32 remoteIP[4]; //Destination Address 46 | UINT16 localPort; //Source Port 47 | UINT16 remotePort; //Destination port 48 | UINT64 receivedBytes; //Number of bytes recived on this connection 49 | UINT64 transmittedBytes; //Number of bytes transsmited from this connection 50 | UINT8 ipV6; //True: IPv6, False: IPv4 51 | UINT8 protocol; //Protocol (UDP, TCP, ...) 52 | } PortmasterConnection; 53 | /* 54 | * Packet Info Flags 55 | */ 56 | #define PM_STATUS_FAST_TRACK_PERMITTED 0x01 57 | #define PM_STATUS_SOCKET_AUTH 0x02 58 | #define PM_STATUS_EXPECT_SOCKET_AUTH 0x04 59 | 60 | /* 61 | * IPv4 Header. 62 | */ 63 | typedef struct { 64 | UINT8 HdrLength:4; 65 | UINT8 Version:4; 66 | UINT8 TOS; 67 | UINT16 Length; 68 | UINT16 Id; 69 | UINT16 FragOff0; 70 | UINT8 TTL; 71 | UINT8 Protocol; 72 | UINT16 Checksum; 73 | UINT32 SrcAddr; 74 | UINT32 DstAddr; 75 | } IPv4Header; 76 | 77 | /* 78 | * IPv6 Header. 79 | */ 80 | typedef struct { 81 | UINT8 TrafficClass0:4; 82 | UINT8 Version:4; 83 | UINT8 FlowLabel0:4; 84 | UINT8 TrafficClass1:4; 85 | UINT16 FlowLabel1; 86 | UINT16 Length; 87 | UINT8 NextHdr; 88 | UINT8 HopLimit; 89 | UINT32 SrcAddr[4]; 90 | UINT32 DstAddr[4]; 91 | } IPv6Header; 92 | 93 | /* 94 | * TCP Header. 95 | */ 96 | typedef struct { 97 | UINT16 SrcPort; 98 | UINT16 DstPort; 99 | UINT32 SeqNum; 100 | UINT32 AckNum; 101 | UINT16 Reserved1:4; 102 | UINT16 HdrLength:4; 103 | UINT16 Fin:1; 104 | UINT16 Syn:1; 105 | UINT16 Rst:1; 106 | UINT16 Psh:1; 107 | UINT16 Ack:1; 108 | UINT16 Urg:1; 109 | UINT16 Reserved2:2; 110 | UINT16 Window; 111 | UINT16 Checksum; 112 | } TCPHeader; 113 | 114 | /* 115 | * UDP Header. 116 | */ 117 | typedef struct { 118 | UINT16 SrcPort; 119 | UINT16 DstPort; 120 | UINT16 Length; 121 | UINT16 Checksum; 122 | } UDPHeader; 123 | 124 | /* 125 | * ICMP Header (used also for ICMPv6) 126 | * Note: This header is used only for ICMP type Destination Unreachable (3). It is not valid for the other message types. 127 | */ 128 | typedef struct 129 | { 130 | UINT8 Type; // message type 131 | UINT8 Code; // type sub-code 132 | UINT16 Checksum; 133 | UINT32 unused; 134 | // This header is not complete for all ICMP packets variants 135 | }ICMPHeader; 136 | 137 | /* 138 | * Verdict can be permanent or temporary for one specific packet. 139 | * If verdict is temporary, portmaster returns negativ value of PORTMASTER_VERDICT_* 140 | */ 141 | typedef INT8 verdict_t; 142 | #define PORTMASTER_VERDICT_ERROR 0 143 | #define PORTMASTER_VERDICT_GET 1 // Initial state of packet is undefined -> ask Portmaster what to do 144 | #define PORTMASTER_VERDICT_ACCEPT 2 // Accept packets and reinject them 145 | #define PORTMASTER_VERDICT_BLOCK 3 // Drop packets silently 146 | #define PORTMASTER_VERDICT_DROP 4 // Block packets with RST or FIN 147 | #define PORTMASTER_VERDICT_REDIR_DNS 5 // Redirect packets to DNS 148 | #define PORTMASTER_VERDICT_REDIR_TUNNEL 6 // Redirect packets to tunnel. 149 | static const char* VERDICT_NAMES[] = { "PORTMASTER_VERDICT_ERROR", 150 | "PORTMASTER_VERDICT_GET", 151 | "PORTMASTER_VERDICT_ACCEPT", 152 | "PORTMASTER_VERDICT_BLOCK", 153 | "PORTMASTER_VERDICT_DROP", 154 | "PORTMASTER_VERDICT_REDIR_DNS", 155 | "PORTMASTER_VERDICT_REDIR_TUNNEL" }; 156 | 157 | /* 158 | * CACHE SIZES for packet and verdict cache 159 | * Packet cache: 160 | * - One entry can be as big as the MTU - eg. 1500 Bytes. 161 | * - A size of 1024 with a mean entry size of 750 Bytes would result in a max space requirement of about 760KB. 162 | * - This cache is quickly emptied, but is not purged, so errors in Portmaster could result in dead entries. 163 | * Verdict cache: 164 | * - On entry has about 50 Bytes. 165 | * - A size of 1024 would result in a requirements of about 50KB which is allocated on initialization. 166 | * - This cache is not emptied or purged, it will pretty much always be at max capacity. 167 | */ 168 | #define PM_PACKET_CACHE_SIZE 1024 169 | #define PM_VERDICT_CACHE_SIZE 1024 170 | 171 | /* 172 | * Container for Verdicts 173 | */ 174 | typedef struct { 175 | UINT32 id; //ID from RegisterPacket 176 | verdict_t verdict; 177 | } PortmasterVerdictInfo; 178 | 179 | /* 180 | * Container for Payload 181 | */ 182 | typedef struct { 183 | UINT32 id; 184 | UINT32 len; // preset with maxlen of payload from caller -> set with actual len of payload from receiver 185 | } PortmasterPayload; 186 | 187 | typedef struct { 188 | UINT32 localIP[4]; // Source Address, only srcIP[0] if IPv4 189 | UINT32 remoteIP[4]; // Destination Address 190 | UINT16 localPort; // Source Port 191 | UINT16 remotePort; // Destination port 192 | UINT8 ipV6; // True: IPv6, False: IPv4 193 | UINT8 protocol; // Protocol (UDP, TCP, ...) 194 | verdict_t verdict; // New verdict 195 | } VerdictUpdateInfo; 196 | 197 | /* 198 | * Currently unused returncodes 199 | */ 200 | #define PM_STATUS_SUCCESS 0x00 201 | #define PM_STATUS_BUF_TOO_SMALL 0x101 202 | #define PM_STATUS_ID_NOT_FOUND 0x102 203 | #define PM_STATUS_UNDEFINED 0x103 204 | #define PM_STATUS_INTERNAL_ERROR 0x104 205 | 206 | /* 207 | * Max size for getPayload function 208 | */ 209 | #define MAX_PAYLOAD_SIZE 1024*10 210 | 211 | /****************************************************************************/ 212 | /* IOCTL declaration */ 213 | /****************************************************************************/ 214 | 215 | #define SIOCTL_TYPE 40000 216 | 217 | #define IOCTL_VERSION \ 218 | CTL_CODE(SIOCTL_TYPE, 0x800, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) 219 | 220 | #define IOCTL_SHUTDOWN_REQUEST \ 221 | CTL_CODE(SIOCTL_TYPE, 0x801, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) // Not used 222 | 223 | #define IOCTL_RECV_VERDICT_REQ \ 224 | CTL_CODE(SIOCTL_TYPE, 0x802, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) 225 | 226 | #define IOCTL_SET_VERDICT \ 227 | CTL_CODE(SIOCTL_TYPE, 0x803, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) 228 | 229 | #define IOCTL_GET_PAYLOAD \ 230 | CTL_CODE(SIOCTL_TYPE, 0x804, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) 231 | 232 | #define IOCTL_CLEAR_CACHE \ 233 | CTL_CODE(SIOCTL_TYPE, 0x805, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) 234 | 235 | #define IOCTL_UPDATE_VERDICT \ 236 | CTL_CODE(SIOCTL_TYPE, 0x806, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) 237 | 238 | #define IOCTL_GET_CONNECTIONS_STATS \ 239 | CTL_CODE(SIOCTL_TYPE, 0x807, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) 240 | 241 | /****************************************************************************/ 242 | /* MISC */ 243 | /****************************************************************************/ 244 | #define IPv4_LOCALHOST_NET_MASK 0xFF000000 245 | #define IPv4_LOCALHOST_NET 0x7F000000 246 | 247 | #define IPv4_LOCALHOST_IP_NETWORK_ORDER 0x0100007f 248 | 249 | #define IPv6_LOCALHOST_PART4 0x1 250 | #define IPv6_LOCALHOST_PART4_NETWORK_ORDER 0x01000000 251 | 252 | #define PORT_DNS 53 253 | #define PORT_DNS_NBO 0x3500 254 | 255 | #define PORT_PM_SPN_ENTRY 717 256 | #define PORT_PM_SPN_ENTRY_NBO 0xCD02 257 | 258 | #define PORT_PM_API 817 259 | 260 | #define IPv4 4 261 | #define IPv6 6 262 | 263 | // https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml 264 | #define PROTOCOL_HOPOPT 0 265 | #define PROTOCOL_ICMP 1 266 | #define PROTOCOL_IGMP 2 267 | #define PROTOCOL_IPv4 4 268 | #define PROTOCOL_TCP 6 269 | #define PROTOCOL_UDP 17 270 | #define PROTOCOL_RDP 27 271 | #define PROTOCOL_DCCP 33 272 | #define PROTOCOL_IPv6 41 273 | #define PROTOCOL_ICMPv6 58 274 | #define PROTOCOL_UDPLite 136 275 | 276 | #define ICMPV4_CODE_DESTINATION_UNREACHABLE 3 277 | #define ICMPV4_CODE_DU_PORT_UNREACHABLE 3 // Destination Unreachable (Port unreachable) 278 | #define ICMPV4_CODE_DU_ADMINISTRATIVELY_PROHIBITED 13 // Destination Unreachable (Communication Administratively Prohibited) 279 | 280 | #define ICMPV6_CODE_DESTINATION_UNREACHABLE 1 281 | #define ICMPV6_CODE_DU_PORT_UNREACHABLE 4 // Destination Unreachable (Port unreachable) 282 | 283 | #define DIRECTION_OUTBOUND 0 284 | #define DIRECTION_INBOUND 1 285 | 286 | #endif // PM_COMMON_H 287 | -------------------------------------------------------------------------------- /pm_kext/include/pm_debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_common.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Common Debug definitions for kernel and userland driver 7 | * Defines the DEBUG_ON Symbol. 8 | * If defined -> Debug Build 9 | * If undefined -> Release Build 10 | * 11 | * Scope: Kernelmode 12 | * Userland 13 | */ 14 | 15 | #ifndef PM_DEBUG_H 16 | #define PM_DEBUG_H 17 | 18 | /****************************************************************************/ 19 | // #define DEBUG_ON // Undefine if Debug Functions should not be compiled 20 | /****************************************************************************/ 21 | 22 | #define DEBUG_BUFSIZE 256 23 | #define LEVEL_DEBUG 0 24 | #define LEVEL_INFO 1 25 | #define LEVEL_WARN 2 26 | #define LEVEL_ERROR 3 27 | 28 | #ifndef LOGGER_NAME 29 | #define LOGGER_NAME __FILE__ 30 | #endif 31 | 32 | /* 33 | * These Logger Variables should be set (REDEFINED) in the including file 34 | */ 35 | extern int logLevel; //must be defined in dll and kernel object 36 | 37 | 38 | /* JCS: Improve Debug 39 | DEBUG("Where do we come from, %s %s", p1, p2); 40 | --> DEBUG(__LINE__, "Where do we come from, %s %s", p1, p2); 41 | Writing to file from kernel mode driver is not recommended and may 42 | not be supported at all: 43 | https://stackoverflow.com/questions/49091442/log-to-a-txt-file-from-a-windows-10-kernel-mode-driver#49243511 44 | All we can do is write to a dedicated debug channel and adjust the loglevel at runtime. 45 | */ 46 | #ifdef DEBUG_ON 47 | #define DEBUG(...) DEBUG_LOG(0, ##__VA_ARGS__) 48 | #define INFO(...) DEBUG_LOG(1, ##__VA_ARGS__) 49 | #define WARN(...) DEBUG_LOG(2, ##__VA_ARGS__) 50 | #define ERR(...) DEBUG_LOG(3, ##__VA_ARGS__) //ERROR is already defined in wingdi.h 51 | 52 | #define DEBUG_LOG(level, format, ...) __DEBUG(LOGGER_NAME, level, __LINE__, format, ##__VA_ARGS__) 53 | void __DEBUG(char *name, int level, int line, char *format, ...); 54 | void printIpHeader(char *buf, unsigned long buf_len, char *data, unsigned long dataLength); 55 | char* printIpv4Packet(void *packet); 56 | char* printPacketInfo(PortmasterPacketInfo *packetInfo); 57 | void initDebugStructure(); 58 | 59 | #else 60 | #define DEBUG(...) {} 61 | #define INFO(...) {} 62 | #define WARN(...) {} 63 | #define ERR(...) {} 64 | 65 | #define printIpHeader {} 66 | #define printIpv4Packet {} 67 | #define printPacketInfo {} 68 | #define initDebugStructure() {} 69 | #endif 70 | 71 | #define FORMAT_ADDR(x) (INT16)((x>>24)&0xFF), (INT16)((x>>16)&0xFF), (INT16)((x>>8)&0xFF), (INT16)(x&0xFF) 72 | 73 | #endif //Include Guard 74 | -------------------------------------------------------------------------------- /pm_kext/include/pm_debug_dll.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_debug_dll.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Defines protoypes for Debug Functions, which are only 7 | * available in dll 8 | * 9 | * Scope: Userland 10 | */ 11 | 12 | 13 | #ifndef __PORTMASTER_HELPER_H 14 | #define __PORTMASTER_HELPER_H 15 | 16 | /****************************************************************************/ 17 | /* Portmaster Helper Prototypes */ 18 | /****************************************************************************/ 19 | #ifdef DEBUG_ON 20 | extern PortmasterPacketInfo *createIPv4PacketInfo(PortmasterPacketInfo *packetInfo); 21 | extern PortmasterPacketInfo *createIPv6PacketInfo1(PortmasterPacketInfo *packetInfo); 22 | extern PortmasterPacketInfo *createIPv6PacketInfo2(PortmasterPacketInfo *packetInfo); 23 | extern void packetToString(PortmasterPacketInfo *packetInfo); 24 | 25 | #else 26 | #define createIPv4PacketInfo(...) {} 27 | #define createIPv6PacketInfo1(...) {} 28 | #define createIPv6PacketInfo2(...) {} 29 | #define packetToString(...) {} 30 | #endif 31 | 32 | /****************************************************************************/ 33 | /* Debugging API -> defined in pm_debug.h (for kernel AND DLL) */ 34 | /****************************************************************************/ 35 | 36 | #endif 37 | 38 | 39 | -------------------------------------------------------------------------------- /pm_kext/include/pm_kernel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_kernel.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains declarations of Windows Driver entrypoints for Portmaster 7 | * Kernel Extension, including DriverEntry, driver_device_control, init_driver_objects 8 | * 9 | * Credits: Based on the excelent work of 10 | * Jared Wright, https://github.com/JaredWright/WFPStarterKit 11 | * Basil, https://github.com/basil00/Divert 12 | * 13 | * Scope: Kernelmode 14 | */ 15 | 16 | #ifndef PM_KERNEL_H 17 | #define PM_KERNEL_H 18 | 19 | #define NDIS61 1 // Need to declare this to compile WFP stuff on Win7, I'm not sure why 20 | 21 | #include "Ntifs.h" 22 | #include // Windows Driver Development Kit 23 | #include // Windows Driver Foundation 24 | 25 | #pragma warning(push) 26 | #pragma warning(disable: 4201) // Disable "Nameless struct/union" compiler warning for fwpsk.h only! 27 | #include // Functions and enumerated types used to implement callouts in kernel mode 28 | #pragma warning(pop) // Re-enable "Nameless struct/union" compiler warning 29 | 30 | #include // Functions used for managing IKE and AuthIP main mode (MM) policy and security associations 31 | #include // Mappings of OS specific function versions (i.e. fn's that end in 0 or 1) 32 | #include // Used to define GUID's 33 | #include // Used to define GUID's 34 | #include "devguid.h" 35 | #include 36 | #include 37 | #include 38 | 39 | #include "pm_common.h" 40 | 41 | extern PRKQUEUE globalIOQueue; 42 | 43 | typedef struct { 44 | LIST_ENTRY entry; 45 | PortmasterPacketInfo *packet; 46 | } DataEntry; 47 | 48 | extern NTSTATUS IPQueueInitialize(WDFDEVICE Device); 49 | extern void QueueEvtIoRead(IN WDFQUEUE queue, IN WDFREQUEST request, IN size_t length); 50 | extern void QueueEvtIoWrite(IN WDFQUEUE queue, IN WDFREQUEST request, IN size_t length); 51 | 52 | #endif // PM_KERNEL_H 53 | -------------------------------------------------------------------------------- /pm_kext/include/pm_netbuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_netbuffer.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains declarations for handling windows netbuffers 7 | * like coping memory from networkstack to kernel 8 | * https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ndis/ns-ndis-_net_buffer 9 | * 10 | * Scope: Kernelmode 11 | */ 12 | 13 | #ifndef PM_NETBUFFER_H 14 | #define PM_NETBUFFER_H 15 | 16 | NTSTATUS initNetBufferPool(); 17 | void freeNetBufferPool(); 18 | NTSTATUS wrapPacketDataInNB(void *packetData, size_t packetLen, PNET_BUFFER_LIST *nbl); 19 | NTSTATUS copyPacketDataFromNB(PNET_BUFFER nb, size_t maxBytes, void **data, size_t *dataLength); 20 | NTSTATUS borrowPacketDataFromNB(PNET_BUFFER nb, size_t bytesNeeded, void **data); 21 | 22 | #endif // PM_NETBUFFER_H 23 | -------------------------------------------------------------------------------- /pm_kext/include/pm_packet.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_packet.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Packet redirect, generate and inject functionality 7 | * 8 | * Scope: Kernelmode 9 | */ 10 | 11 | #ifndef PM_PACKET_H 12 | #define PM_PACKET_H 13 | 14 | #include "pm_kernel.h" 15 | #include "pm_common.h" 16 | 17 | #include 18 | #include 19 | 20 | /** 21 | * @brief Initialize inject handles 22 | * 23 | * @return STATUS_SUCCESS on success 24 | * 25 | */ 26 | NTSTATUS initializeInjectHandles(); 27 | 28 | /** 29 | * @brief Destroy inject handles 30 | * 31 | * @return void 32 | * 33 | */ 34 | void destroyInjectHandles(); 35 | 36 | /** 37 | * @brief Gets the appropriate inject handle for a packet 38 | * 39 | * @par packetInfo -> info for the packet 40 | * @return inject handle 41 | * 42 | */ 43 | HANDLE getInjectionHandleForPacket(PortmasterPacketInfo *packetInfo); 44 | 45 | /** 46 | * @brief Gets the appropriate inject handles for blocked packets 47 | * 48 | * @par packetInfo -> info for the packet 49 | * @return inject handle 50 | * 51 | */ 52 | HANDLE getBlockedPacketInjectHandle(PortmasterPacketInfo *packetInfo); 53 | 54 | /** 55 | * @brief Injects a packet in the network loop with specific handle 56 | * 57 | * @par handle -> inject handle 58 | * @par packetInfo -> info for the packet 59 | * @par direction -> direction on which the packet should be inject inbound or outbound 60 | * @par packet -> raw packet data 61 | * @par packetLength -> size of the raw packet data 62 | * @return STATUS_SUCCESS on success 63 | * 64 | */ 65 | NTSTATUS injectPacketWithHandle(HANDLE handle, PortmasterPacketInfo *packetInfo, UINT8 direction, void *packet, size_t packetLength); 66 | 67 | /** 68 | * @brief Injects a packet in the network loop 69 | * 70 | * @par packetInfo -> info for the packet 71 | * @par direction -> direction on which the packet should be inject inbound or outbound 72 | * @par packet -> raw packet data 73 | * @par packetLength -> size of the raw packet data 74 | * @return STATUS_SUCCESS on success 75 | * 76 | */ 77 | NTSTATUS injectPacket(PortmasterPacketInfo *packetInfo, UINT8 direction, void *packet, size_t packetLength); 78 | 79 | /** 80 | * @brief Copies a packet from net buffer and injects it 81 | * 82 | * @par packetInfo -> info for the packet 83 | * @par nb -> net buffer that contains the packet 84 | * @par ipHeaderSize -> size of the ip header 85 | * @return void 86 | * 87 | */ 88 | void copyAndInject(PortmasterPacketInfo* packetInfo, PNET_BUFFER nb, UINT32 ipHeaderSize); 89 | 90 | /** 91 | * @brief Sends a block packet. RST for tcp and ICMP block for everything else 92 | * 93 | * @par packetInfo -> info for the packet 94 | * @par originalPacket -> raw packet data 95 | * @par originalPacketLength -> raw packet data length 96 | * @return STATUS_SUCCESS on success 97 | * 98 | */ 99 | NTSTATUS sendBlockPacket(PortmasterPacketInfo* packetInfo, void* originalPacket, size_t originalPacketLength); 100 | 101 | /** 102 | * @brief Sends a block packet to be used from callout. RST for tcp and ICMP block for everything else 103 | * 104 | * @par packetInfo -> info for the packet 105 | * @par nb -> net buffer that contains the packet data 106 | * @par ipHeaderSize -> size of ip header 107 | * @return STATUS_SUCCESS on success 108 | * 109 | */ 110 | NTSTATUS sendBlockPacketFromCallout(PortmasterPacketInfo* packetInfo, PNET_BUFFER nb, size_t ipHeaderSize); 111 | 112 | /** 113 | * @brief Redirects a packet 114 | * 115 | * @par packetInfo -> info for the packet 116 | * @par redirInfo -> redirect info for the packet 117 | * @par packet -> raw packet data 118 | * @par packetLength -> raw packet data length 119 | * @par dns -> is dns request 120 | * @return STATUS_SUCCESS on success 121 | * 122 | */ 123 | void redirectPacket(PortmasterPacketInfo *packetInfo, PortmasterPacketInfo *redirInfo, void *packet, size_t packetLength, bool dns); 124 | 125 | /** 126 | * @brief Redirects a packet to be used from callout 127 | * 128 | * @par packetInfo -> info for the packet 129 | * @par redirInfo -> redirect info for the packet 130 | * @par nb -> net buffer that contains packet data 131 | * @par ipHeaderSize -> size of ip header 132 | * @par dns -> is dns request 133 | * @return STATUS_SUCCESS on success 134 | * 135 | */ 136 | void redirectPacketFromCallout(PortmasterPacketInfo *packetInfo, PortmasterPacketInfo *redirInfo, PNET_BUFFER nb, size_t ipHeaderSize, bool dns); 137 | 138 | #endif // PM_PACKET_H -------------------------------------------------------------------------------- /pm_kext/include/pm_register.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_register.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains declarations for rgistering filters and 7 | * callouts for the kernel using the mechanisms supplied by 8 | * Windows Filtering Platform 9 | * 10 | * Scope: Kernelmode 11 | */ 12 | 13 | #ifndef PM_REGISTER_H 14 | #define PM_REGISTER_H 15 | 16 | #include "pm_kernel.h" 17 | #include "pm_callouts.h" 18 | 19 | NTSTATUS registerWFPStack(DEVICE_OBJECT* wdmDevice); 20 | NTSTATUS unregisterFilters(); 21 | NTSTATUS unregisterCallouts(); 22 | 23 | #endif // PM_REGISTER_H 24 | -------------------------------------------------------------------------------- /pm_kext/include/pm_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_utils.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains implementation of utility-functions 7 | * 8 | * Scope: Kernelmode 9 | */ 10 | 11 | #ifndef PM_UTILS_H 12 | #define PM_UTILS_H 13 | 14 | #include "pm_kernel.h" 15 | 16 | #define PORTMASTER_TAG 'saMP' 17 | 18 | void *portmasterMalloc(size_t size, bool paged); 19 | void portmasterFree(void *ptr); 20 | 21 | /** 22 | * @brief Compares two PortmasterPacketInfo for full equality 23 | * 24 | * @par a = Pointer to PortmasterPacketInfo to compare 25 | * @par b = Pointer to PortmasterPacketInfo to compare 26 | * @return equality (bool) 27 | * 28 | */ 29 | bool compareFullPacketInfo(PortmasterPacketInfo *a, PortmasterPacketInfo *b); 30 | 31 | /** 32 | * @brief Compares two PortmasterPacketInfo for local address equality 33 | * 34 | * @par a = Pointer to PortmasterPacketInfo to compare 35 | * @par b = Pointer to PortmasterPacketInfo to compare 36 | * @return equality (bool) 37 | * 38 | */ 39 | bool compareReverseRedirPacketInfo(PortmasterPacketInfo *original, PortmasterPacketInfo *current); 40 | 41 | /** 42 | * @brief Compares two portmaster_packet_info for remote address equality 43 | * 44 | * @par a = Pointer to PortmasterPacketInfo to compare 45 | * @par b = Pointer to PortmasterPacketInfo to compare 46 | * @return equality (bool) 47 | * 48 | */ 49 | int compareRemotePacketInfo(PortmasterPacketInfo *a, PortmasterPacketInfo *b); 50 | 51 | /** 52 | * @brief Checks if the IPv4 address is a loopback address 53 | * 54 | * @par addr = IPv4 address 55 | * @return is loopback (bool) 56 | * 57 | */ 58 | bool isIPv4Loopback(UINT32 addr); 59 | 60 | /** 61 | * @brief Checks if the IPv6 address is a loopback address 62 | * 63 | * @par addr = IPv6 address (the size of the array needs to be no less then 4) 64 | * @return is loopback (bool) 65 | * 66 | */ 67 | bool isIPv6Loopback(UINT32 *addr); 68 | 69 | /** 70 | * @brief Checks if the packet has loopback ip address 71 | * 72 | * @par packet = the packet to be checked 73 | * @return is loopback (bool) 74 | * 75 | */ 76 | bool isPacketLoopback(PortmasterPacketInfo *packet); 77 | 78 | /** 79 | * @brief Copy IPv6 address 80 | * 81 | * @par inFixedValues = values structure from the callout 82 | * @par idx = index of the that contains the ipv6 values 83 | * @par ip = the array in which the values will be filled (length must be 4) 84 | * @return STATUS_SUCCESS on success 85 | * 86 | */ 87 | NTSTATUS copyIPv6(const FWPS_INCOMING_VALUES* inFixedValues, FWPS_FIELDS_OUTBOUND_IPPACKET_V6 idx, UINT32 *ip); 88 | 89 | #endif // PM_UTILS_H 90 | -------------------------------------------------------------------------------- /pm_kext/include/verdict_cache.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: verdict_cache.h 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains declaration of verdict cache. 7 | * Verdicts are set by Portmaster Userland Application 8 | * and cached in kernel for faster access (nona). 9 | * Cache Algorithm: Least Recently Used (LRU). 10 | * 11 | * Scope: Kernelmode 12 | * (Userland for development) 13 | */ 14 | 15 | #ifndef VERDICT_CACHE_H 16 | #define VERDICT_CACHE_H 17 | 18 | #include "pm_common.h" 19 | #include "pm_utils.h" 20 | 21 | #ifndef __LINUX_ENV__ 22 | #include 23 | typedef UINT8 uint8_t; 24 | typedef UINT16 uint16_t; 25 | typedef UINT32 uint32_t; 26 | typedef UINT64 uint64_t; 27 | #endif 28 | 29 | #define VerdictCache void 30 | 31 | /** 32 | * @brief Initializes the verdict cache 33 | * 34 | * @par max_size = size of cache 35 | * @par verdict_cache = returns new verdictCache 36 | * @return error code 37 | * 38 | */ 39 | int verdictCacheCreate(UINT32 maxSize, VerdictCache **verdict_cache); 40 | 41 | /** 42 | * @brief Remove all items from verdict cache 43 | * 44 | * @par verdict_cache = VerdictCache to use 45 | * @par freeData = callback function that is executed for each item before delete were the data of the item can be deleted 46 | * 47 | */ 48 | void verdictCacheClear(VerdictCache *verdictCache, void(*freeData)(PortmasterPacketInfo*, verdict_t)); 49 | 50 | /** 51 | * @brief Tears down the verdict cache 52 | * 53 | * @par verdictCache = verdict cache to use 54 | * @return error code 55 | * 56 | */ 57 | int verdictCacheTeardown(VerdictCache *verdictCache, void(*freeData)(PortmasterPacketInfo*, verdict_t)); 58 | 59 | /** 60 | * @brief Updates a verdict that is already in the cache 61 | * 62 | * @par verdict_cache = VerdictCache to use 63 | * @par info = pointer to verdictUpdateInfo 64 | * @return error code 65 | * 66 | */ 67 | int verdictCacheUpdate(VerdictCache *verdictCache, VerdictUpdateInfo *info); 68 | 69 | /** 70 | * @brief Adds verdict to cache 71 | * 72 | * @par verdictCache = VerdictCache to use 73 | * @par packetInfo = pointer to PacketInfo 74 | * @par verdict = verdict to save 75 | * @return error code 76 | * 77 | */ 78 | int verdictCacheAdd(VerdictCache *verdictCache, PortmasterPacketInfo *packetInfo, verdict_t verdict, PortmasterPacketInfo **removedPacketInfo); 79 | 80 | 81 | /** 82 | * @brief returns the verdict of a packet if inside the cache, with redirect info if available 83 | * 84 | * @par verdictCache = VerdictCache to use 85 | * @par packetInfo = pointer to PacketInfo 86 | * @par redirInfo = double pointer to packetInfo (return value) 87 | * @par verdict = pointer to verdict (return value) 88 | * @return error code 89 | * 90 | */ 91 | verdict_t verdictCacheGet(VerdictCache *verdictCache, PortmasterPacketInfo *packetInfo, PortmasterPacketInfo **redirInfo); 92 | 93 | /** 94 | * @brief Copies the cached connection bandwidth info to the connections input array. 95 | * 96 | * @par verdictCache = VerdictCache to use 97 | * @par connections = array of PortmasterConnection structs 98 | * @par size = size of the connections array 99 | * @par ipv6 = specifies if the verdict cache is for ipv6 100 | * @return number of connection struct writen to the array or -1 for error 101 | * 102 | */ 103 | int verdictCacheWriteBandwidthStats(VerdictCache *verdictCache, PortmasterConnection *connections, int size, UINT8 ipv6); 104 | 105 | /** 106 | * @brief Updates bandwidth stats of a connection. 107 | * 108 | * @par verdictCache = VerdictCache to use. 109 | * @par packetInfo = contains info about the connection. 110 | * @par payload size = size of the payload 111 | * @return -1 on error 112 | * 113 | */ 114 | int verdictCacheUpdateStats(VerdictCache *verdictCache, PortmasterPacketInfo *packetInfo, UINT64 payloadSize); 115 | 116 | #endif // VERDICT_CACHE_H 117 | -------------------------------------------------------------------------------- /pm_kext/include/version.h: -------------------------------------------------------------------------------- 1 | #define STRINGIZE2(s) #s 2 | #define STRINGIZE(s) STRINGIZE2(s) 3 | 4 | #define PM_VERSION PM_VERSION_MAJOR, PM_VERSION_MINOR, PM_VERSION_REVISION, PM_VERSION_BUILD 5 | #define PM_VERSION_STR STRINGIZE(PM_VERSION_MAJOR) \ 6 | "." STRINGIZE(PM_VERSION_MINOR) \ 7 | "." STRINGIZE(PM_VERSION_REVISION) \ 8 | "." STRINGIZE(PM_VERSION_BUILD) 9 | 10 | 11 | #define COMPANY_NAME "Safing ICS Technologies GmbH" 12 | #define LEGAL_COPYWRITE "Safing ICS Technologies GmbH" 13 | -------------------------------------------------------------------------------- /pm_kext/pm_kext.aps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/safing/portmaster-windows-kext/28593163f29c2976efa16f3458d809bf3577aafc/pm_kext/pm_kext.aps -------------------------------------------------------------------------------- /pm_kext/pm_kext.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | Debug 14 | ARM64 15 | 16 | 17 | Release 18 | ARM64 19 | 20 | 21 | 22 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB} 23 | {1bc93793-694f-48fe-9372-81e2b05556fd} 24 | v4.5 25 | 12.0 26 | Debug 27 | x64 28 | Driver 29 | $(LatestTargetPlatformVersion) 30 | pm_kext 31 | 32 | 33 | 34 | Windows10 35 | true 36 | WindowsKernelModeDriver10.0 37 | Driver 38 | KMDF 39 | Desktop 40 | false 41 | 1 42 | <_NT_TARGET_VERSION>0x0601 43 | 44 | 45 | Windows10 46 | false 47 | WindowsKernelModeDriver10.0 48 | Driver 49 | KMDF 50 | Desktop 51 | false 52 | 0 53 | <_NT_TARGET_VERSION>0x0601 54 | 55 | 56 | Windows10 57 | true 58 | WindowsKernelModeDriver10.0 59 | Driver 60 | KMDF 61 | Universal 62 | 63 | 64 | Windows10 65 | false 66 | WindowsKernelModeDriver10.0 67 | Driver 68 | KMDF 69 | Universal 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | DbgengKernelDebugger 93 | $(SolutionDir)install\WDDK\$(Platform)\$(ConfigurationName)\ 94 | pm_kernel_x64 95 | false 96 | 97 | 98 | DbgengKernelDebugger 99 | $(SolutionDir)install\WDDK\$(Platform)\$(ConfigurationName)\ 100 | pm_kernel_x64 101 | false 102 | true 103 | false 104 | true 105 | AllRules.ruleset 106 | 107 | 108 | DbgengKernelDebugger 109 | 110 | 111 | DbgengKernelDebugger 112 | 113 | 114 | 115 | sha256 116 | 117 | 118 | $(ProjectDir)\include;$(SolutionDir)\include;%(AdditionalIncludeDirectories) 119 | false 120 | stdc11 121 | _CRT_SECURE_NO_WARNINGS;PM_VERSION_MAJOR=$(PM_VERSION_MAJOR);PM_VERSION_MINOR=$(PM_VERSION_MINOR);PM_VERSION_REVISION=$(PM_VERSION_REVISION);PM_VERSION_BUILD=$(PM_VERSION_BUILD);DEBUG_ON;%(PreprocessorDefinitions) 122 | false 123 | true 124 | 125 | 126 | $(DDK_LIB_PATH)\wdmsec.lib;$(DDK_LIB_PATH)\ndis.lib;$(DDK_LIB_PATH)\fwpkclnt.lib;$(SDK_LIB_PATH)\uuid.lib;%(AdditionalDependencies) 127 | 128 | 129 | $(PM_VERSION_MAJOR).$(PM_VERSION_MINOR).$(PM_VERSION_REVISION).$(PM_VERSION_BUILD) 130 | 131 | 132 | 133 | 134 | sha256 135 | 136 | 137 | $(SolutionDir)\include;$(ProjectDir)\include;%(AdditionalIncludeDirectories) 138 | false 139 | stdc11 140 | _CRT_SECURE_NO_WARNINGS;PM_VERSION_MAJOR=$(PM_VERSION_MAJOR);PM_VERSION_MINOR=$(PM_VERSION_MINOR);PM_VERSION_REVISION=$(PM_VERSION_REVISION);PM_VERSION_BUILD=$(PM_VERSION_BUILD);%(PreprocessorDefinitions) 141 | false 142 | 143 | 144 | 145 | 146 | $(DDK_LIB_PATH)\wdmsec.lib;$(DDK_LIB_PATH)\ndis.lib;$(DDK_LIB_PATH)\fwpkclnt.lib;$(SDK_LIB_PATH)\uuid.lib;%(AdditionalDependencies) 147 | 148 | 149 | ../include/version.h 150 | $(PM_VERSION_MAJOR).$(PM_VERSION_MINOR).$(PM_VERSION_REVISION).$(PM_VERSION_BUILD) 151 | * 152 | true 153 | true 154 | $(KMDF_VERSION_MAJOR).$(KMDF_VERSION_MINOR) 155 | 156 | 157 | 158 | 159 | sha256 160 | 161 | 162 | 163 | 164 | sha256 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | %(PreprocessorDefinitions);PM_VERSION_MAJOR=$(PM_VERSION_MAJOR);PM_VERSION_MINOR=$(PM_VERSION_MINOR);PM_VERSION_REVISION=$(PM_VERSION_REVISION);PM_VERSION_BUILD=$(PM_VERSION_BUILD) 185 | _WIN64;_AMD64_=1;AMD64;%(PreprocessorDefinitions);PM_VERSION_MAJOR=$(PM_VERSION_MAJOR);PM_VERSION_MINOR=$(PM_VERSION_MINOR);PM_VERSION_REVISION=$(PM_VERSION_REVISION);PM_VERSION_BUILD=$(PM_VERSION_BUILD) 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /pm_kext/pm_kext.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {8E41214B-6785-4CFE-B992-037D68949A14} 18 | inf;inv;inx;mof;mc; 19 | 20 | 21 | {2ae03bcc-3481-4912-ba6b-b8afa684f446} 22 | 23 | 24 | 25 | 26 | Source Files 27 | 28 | 29 | Source Files 30 | 31 | 32 | Source Files 33 | 34 | 35 | Source Files 36 | 37 | 38 | Source Files 39 | 40 | 41 | Source Files 42 | 43 | 44 | Source Files 45 | 46 | 47 | Source Files 48 | 49 | 50 | Source Files 51 | 52 | 53 | Source Files 54 | 55 | 56 | 57 | 58 | Resource Files 59 | 60 | 61 | 62 | 63 | Resource Files 64 | 65 | 66 | Resource Files 67 | 68 | 69 | 70 | 71 | Header Files 72 | 73 | 74 | Header Files 75 | 76 | 77 | Header Files 78 | 79 | 80 | Header Files 81 | 82 | 83 | Header Files 84 | 85 | 86 | Header Files 87 | 88 | 89 | Header Files 90 | 91 | 92 | Header Files 93 | 94 | 95 | Header Files 96 | 97 | 98 | Header Files 99 | 100 | 101 | Header Files 102 | 103 | 104 | Header Files 105 | 106 | 107 | Header Files 108 | 109 | 110 | Header Files 111 | 112 | 113 | -------------------------------------------------------------------------------- /pm_kext/pm_kext.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /pm_kext/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by pm_kext.rc 4 | // 5 | #define VERSION_MINOR 0 6 | #define VERSION_REVISION 0 7 | #define VERSION_BUILD 0 8 | #define VER_VER_DEBUG 0 9 | #define VERSION_MAJOR 1 10 | #define VER_FILETYPE 1 11 | #define VER_FILEOS 10 12 | 13 | // Next default values for new objects 14 | // 15 | #ifdef APSTUDIO_INVOKED 16 | #ifndef APSTUDIO_READONLY_SYMBOLS 17 | #define _APS_NEXT_RESOURCE_VALUE 101 18 | #define _APS_NEXT_COMMAND_VALUE 40001 19 | #define _APS_NEXT_CONTROL_VALUE 1001 20 | #define _APS_NEXT_SYMED_VALUE 101 21 | #endif 22 | #endif 23 | -------------------------------------------------------------------------------- /pm_kext/src/packet_cache.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: packet_cache.c 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains implementation of packet cache. IP-Packets must be cached 7 | * until we know what to do with them (block, drop, reinject). 8 | * Caching Algorithm: Last In First Out (LIFO) 9 | * 10 | * Scope: Kernelmode 11 | * (Userland for development) 12 | */ 13 | 14 | #define BUILD_ENV_DRIVER 15 | 16 | #include 17 | #include 18 | 19 | #include "pm_kernel.h" 20 | #include "pm_common.h" 21 | #include "packet_cache.h" 22 | #include "pm_utils.h" 23 | #include "pm_debug.h" 24 | 25 | typedef struct PacketCacheItem { 26 | UINT32 packetID; 27 | PortmasterPacketInfo *packetInfo; 28 | void *packet; 29 | size_t packetLength; 30 | } PacketCacheItem; 31 | 32 | #undef PacketCache // previously defined as void 33 | typedef struct { 34 | PacketCacheItem *packets; 35 | UINT32 maxSize; 36 | INT64 nextPacketID; // INT64 so we can easy check for overwrites 37 | PKSPIN_LOCK lock; 38 | } PacketCache; 39 | 40 | /** 41 | * @brief Retrieves the packet index located in the array 42 | * 43 | * @par packetCache = packet cache to use 44 | * @par packetID = registered packet ID 45 | * @return packet index 46 | * 47 | */ 48 | static UINT32 getIndexFromPacketID(PacketCache *packetCache, UINT32 packetID) { 49 | return packetID % packetCache->maxSize; 50 | } 51 | 52 | /** 53 | * @brief Initializes the packet cache 54 | * 55 | * @par maxSize = size of cache 56 | * @par packetCache = returns new PacketCache 57 | * @return error code 58 | * 59 | */ 60 | int packetCacheCreate(uint32_t maxSize, PacketCache **packetCache) { 61 | if (maxSize == 0) { 62 | ERR("packetCacheCreate PacketCache maxSize was 0"); 63 | return 1; 64 | } 65 | INFO("packetCacheCreate with size %d", maxSize); 66 | 67 | PacketCache *newPacketCache = portmasterMalloc(sizeof(PacketCache), false); 68 | if (newPacketCache == NULL) { 69 | return 1; 70 | } 71 | 72 | newPacketCache->packets = portmasterMalloc(sizeof(PacketCacheItem) * maxSize, false); 73 | if(newPacketCache->packets == NULL) { 74 | portmasterFree(newPacketCache); 75 | return 1; 76 | } 77 | 78 | newPacketCache->nextPacketID = 1; 79 | newPacketCache->maxSize = maxSize; 80 | 81 | newPacketCache->lock = portmasterMalloc(sizeof(KSPIN_LOCK), false); 82 | KeInitializeSpinLock(newPacketCache->lock); 83 | 84 | *packetCache = newPacketCache; 85 | return 0; 86 | } 87 | 88 | /** 89 | * @brief Tears down the packet cache 90 | * 91 | * @par packet_cache = packet_cache to use 92 | * @return error code 93 | * 94 | */ 95 | int packetCacheTeardown(PacketCache *packetCache, void(*freeData)(PortmasterPacketInfo*, void*)) { 96 | if(packetCache == NULL) { 97 | return 0; 98 | } 99 | 100 | PKSPIN_LOCK lock = packetCache->lock; 101 | KLOCK_QUEUE_HANDLE lockHandle = {0}; 102 | KeAcquireInStackQueuedSpinLock(lock, &lockHandle); 103 | 104 | for(UINT32 i = 0; i < packetCache->maxSize; i++) { 105 | PacketCacheItem *item = &packetCache->packets[i]; 106 | if(item->packetInfo != NULL && item->packet != NULL) { 107 | freeData(item->packetInfo, item->packet); 108 | } 109 | } 110 | 111 | portmasterFree(packetCache->packets); 112 | portmasterFree(packetCache); 113 | 114 | KeReleaseInStackQueuedSpinLock(&lockHandle); 115 | portmasterFree(lock); 116 | 117 | return 0; 118 | } 119 | 120 | /** 121 | * @brief Registers a packet 122 | * 123 | * @par packetCache = packet cache to use 124 | * @par packetInfo = pointer to packetInfo 125 | * @par packet = pointer to packet 126 | * @return new packet ID 127 | * 128 | */ 129 | uint32_t packetCacheRegister(PacketCache* packetCache, PortmasterPacketInfo *packetInfo, void* packet, size_t packetLength, PortmasterPacketInfo **oldPacketInfo, void **oldPacket) { 130 | DEBUG("packetCacheRegister called"); 131 | if(packetCache == NULL || packetInfo == NULL || packet == NULL) { 132 | ERR("packetCacheRegister - invalid params"); 133 | return 0; 134 | } 135 | 136 | KLOCK_QUEUE_HANDLE lockHandle = {0}; 137 | KeAcquireInStackQueuedSpinLock(packetCache->lock, &lockHandle); 138 | 139 | UINT32 packetIndex = getIndexFromPacketID(packetCache, (UINT32)packetCache->nextPacketID); 140 | PacketCacheItem *newItem = &packetCache->packets[packetIndex]; 141 | 142 | if(newItem->packetInfo != NULL && newItem->packet != NULL) { 143 | *oldPacketInfo = newItem->packetInfo; 144 | *oldPacket = newItem->packet; 145 | memset(newItem, 0, sizeof(PacketCacheItem)); 146 | } 147 | 148 | newItem->packetID = (UINT32)packetCache->nextPacketID; 149 | newItem->packetInfo = packetInfo; 150 | newItem->packet = packet; 151 | newItem->packetLength = packetLength; 152 | 153 | packetCache->nextPacketID++; 154 | // check for overflow 155 | if (packetCache->nextPacketID >= UINT_MAX) { 156 | packetCache->nextPacketID = 1; 157 | } 158 | 159 | KeReleaseInStackQueuedSpinLock(&lockHandle); 160 | return newItem->packetID; 161 | } 162 | 163 | /** 164 | * @brief Retrieves the packetItem located in the array 165 | * 166 | * @par packetCache = packet cache to use 167 | * @par packetID = registered packet ID 168 | * @return packet item 169 | * 170 | */ 171 | static PacketCacheItem* getPacketFromID(PacketCache *packetCache, UINT32 packetID) { 172 | if(packetID == 0) { 173 | return NULL; 174 | } 175 | UINT32 index = getIndexFromPacketID(packetCache, packetID); 176 | 177 | PacketCacheItem *item = &packetCache->packets[index]; 178 | if(packetID != item->packetID) { 179 | DEBUG("Packet ID differs: %d %d", packetID, item->packetID); 180 | return NULL; 181 | } 182 | 183 | return item; 184 | } 185 | 186 | /** 187 | * @brief Retrieves a packet, if it exists. The returned packet and packet_info will be removed from the list. 188 | * 189 | * @par packetCache = packet cache to use 190 | * @par packetID = registered packet ID 191 | * @par packetInfo = double pointer for packet_info return 192 | * @par packet = double pointer for packet return 193 | * @return error code 194 | * 195 | */ 196 | int packetCacheRetrieve(PacketCache *packetCache, UINT32 packetID, PortmasterPacketInfo **packetInfo, void **packet, size_t *packetLength) { 197 | DEBUG("retrieve_packet called"); 198 | 199 | int rc = 0; 200 | KLOCK_QUEUE_HANDLE lockHandle = {0}; 201 | KeAcquireInStackQueuedSpinLock(packetCache->lock, &lockHandle); 202 | 203 | // Check if entry was overwritten 204 | if((INT64)packetID <= (packetCache->nextPacketID - (INT64)packetCache->maxSize - 1)) { 205 | DEBUG("Requested packet was overwritten: %d", packetID); 206 | rc = 1; 207 | } 208 | 209 | PacketCacheItem *item = getPacketFromID(packetCache, packetID); 210 | 211 | if(rc == 0) { 212 | if(item != NULL) { 213 | *packetInfo = item->packetInfo; 214 | *packet = item->packet; 215 | *packetLength = item->packetLength; 216 | memset(item, 0, sizeof(PacketCacheItem)); 217 | } else { 218 | rc = 2; 219 | } 220 | } 221 | KeReleaseInStackQueuedSpinLock(&lockHandle); 222 | 223 | return rc; 224 | } 225 | 226 | /** 227 | * @brief Returns a packet, if it exists. The list is not changed. 228 | * 229 | * @par packetCache = packet cache to use 230 | * @par packetID = registered packet ID 231 | * @par packet = double pointer for packet return 232 | * @return error code 233 | * 234 | */ 235 | int packetCacheGet(PacketCache *packetCache, uint32_t packetID, void **packet, size_t *packetLength) { 236 | DEBUG("packetCacheGet called"); 237 | int rc = 0; 238 | KLOCK_QUEUE_HANDLE lockHandle = {0}; 239 | KeAcquireInStackQueuedSpinLock(packetCache->lock, &lockHandle); 240 | 241 | // Check if entry was overwritten 242 | if((INT64)packetID <= (packetCache->nextPacketID - (INT64)packetCache->maxSize - 1)) { 243 | DEBUG("Requested packet was overwritten: %d", packetID); 244 | rc = 1; 245 | } 246 | 247 | if(rc == 0) { 248 | PacketCacheItem *item = getPacketFromID(packetCache, packetID); 249 | if(item != NULL) { 250 | *packet = item->packet; 251 | *packetLength = item->packetLength; 252 | } else { 253 | rc = 2; 254 | } 255 | } 256 | KeReleaseInStackQueuedSpinLock(&lockHandle); 257 | 258 | return rc; 259 | } 260 | -------------------------------------------------------------------------------- /pm_kext/src/pm_checksum.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_checksum.c 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains implementation for checksum calculations in order 7 | * to modify and reinject IP Packets 8 | * 9 | * Scope: Kernelmode 10 | */ 11 | 12 | #define LOGGER_NAME "pm_checksum" 13 | #include "pm_checksum.h" 14 | #include "pm_common.h" 15 | 16 | static UINT32 checksumAdd(void* data, size_t length) { 17 | UINT16 *data16 = (UINT16*) data; 18 | size_t length16 = length/2; 19 | 20 | UINT32 sum = 0; 21 | 22 | // sum two bytes at once 23 | for (size_t i = 0; i < length16; i++) { 24 | // fprintf(stderr, "adding: 0x%x\n", data16[i]); 25 | sum += data16[i]; 26 | } 27 | 28 | // sum single byte left over 29 | if (length & 0x1) { 30 | UINT8 *data8 = (UINT8*) data; 31 | sum += data8[length-1]; 32 | } 33 | 34 | return sum; 35 | } 36 | 37 | static UINT16 checksumFinish(UINT32 sum) { 38 | while (sum >> 16) { 39 | sum = (sum & 0xFFFF) + (sum >> 16); 40 | } 41 | sum = ~sum; 42 | return (UINT16) sum; 43 | } 44 | 45 | VOID calcIPv4Checksum(void *data, size_t length, bool calcTransport) { 46 | size_t ipHeaderLength = calcIPv4HeaderSize(data, length); 47 | 48 | // sanity check 49 | if (!data || length == 0) { 50 | ERR("Invalid parameters"); 51 | return; 52 | } 53 | 54 | if (ipHeaderLength > 0) { 55 | // calc IPv4 Header checksum 56 | IPv4Header *ipHeader = (IPv4Header*) data; 57 | 58 | ipHeader->Checksum = 0; // reset checksum 59 | UINT32 sum = checksumAdd((void*) data, ipHeaderLength); // calc on complete header 60 | ipHeader->Checksum = checksumFinish(sum); // finish calc 61 | 62 | INFO("calculated checksum: 0x%04X (NetworkByteorder), 0x%02X%02X", ipHeader->Checksum, ipHeader->Checksum&0x00FF, (ipHeader->Checksum&0xFF00)>>8); 63 | 64 | if (calcTransport) { 65 | if (ipHeader->Protocol == 6 || ipHeader->Protocol == 17) { 66 | // reset sum 67 | sum = 0; 68 | 69 | // pseudo header 70 | sum += checksumAdd((void*) &ipHeader->SrcAddr, 8); // src, dst address 71 | sum += ipHeader->Protocol << 8; // zero byte + protocol in network order 72 | sum += ipHeader->Length - (UINT32)(ipHeaderLength << 8); // payload length in network order 73 | 74 | // TCP 75 | if (ipHeader->Protocol == 6 && length >= ipHeaderLength + 20 /* TCP Header */) { 76 | TCPHeader *tcpHeader = (TCPHeader*) ((UINT8*)data + ipHeaderLength); 77 | 78 | tcpHeader->Checksum = 0; 79 | sum += checksumAdd((void*) tcpHeader, length - ipHeaderLength); 80 | tcpHeader->Checksum = checksumFinish(sum); 81 | 82 | // UDP 83 | } else if (ipHeader->Protocol == 17 && length >= ipHeaderLength + 8 /* UDP Header */) { 84 | UDPHeader *udpHeader = (UDPHeader*) ((UINT8*)data + ipHeaderLength); 85 | 86 | udpHeader->Checksum = 0; 87 | sum += checksumAdd((void*) udpHeader, length - ipHeaderLength); 88 | udpHeader->Checksum = checksumFinish(sum); 89 | 90 | // special case for UDP 91 | if (udpHeader->Checksum == 0) { 92 | udpHeader->Checksum = 0xFFFF; 93 | } 94 | } 95 | // ICMP 96 | } else if (ipHeader->Protocol == 1 && length > ipHeaderLength + sizeof(ICMPHeader)) { 97 | ICMPHeader *icmpHeader = (ICMPHeader*) ((UINT8*)data + ipHeaderLength); 98 | 99 | sum = 0; 100 | icmpHeader->Checksum = 0; 101 | sum = checksumAdd((void*) icmpHeader, length - ipHeaderLength); 102 | icmpHeader->Checksum = checksumFinish(sum); 103 | } 104 | } 105 | } 106 | } 107 | 108 | void calcIPv6Checksum(void* data, size_t length, bool calcTransport) { 109 | UINT8 protocol; 110 | size_t ipHeaderLength = calcIPv6HeaderSize(data, length, &protocol); 111 | 112 | // sanity check 113 | if (!data || length == 0) { 114 | ERR("Invalid parameters"); 115 | return; 116 | } 117 | 118 | if (ipHeaderLength > 0 && calcTransport && (protocol == 6 || protocol == 17 || protocol == 58)) { 119 | IPv6Header *ipHeader = (IPv6Header*) data; 120 | UINT32 payloadLength = (UINT32)(length - ipHeaderLength); 121 | 122 | // pseudo header 123 | // src, dst address 124 | UINT32 sum = checksumAdd((void*) &ipHeader->SrcAddr, 32); 125 | // payload length in network order 126 | sum += (payloadLength & 0xFF000000) >> 8; 127 | sum += (payloadLength & 0x00FF0000) << 8; 128 | sum += (payloadLength & 0x0000FF00) >> 8; 129 | sum += (payloadLength & 0x000000FF) << 8; 130 | sum = (sum & 0xFFFF) + (sum >> 16); 131 | // zero byte + protocol in network order 132 | sum += protocol << 8; 133 | 134 | // TCP 135 | if (protocol == 6 && length >= ipHeaderLength + 20 /* TCP Header */) { 136 | TCPHeader *tcpHeader = (TCPHeader*) ((UINT8*)data + ipHeaderLength); 137 | 138 | tcpHeader->Checksum = 0; 139 | sum += checksumAdd((void*) tcpHeader, length - ipHeaderLength); 140 | tcpHeader->Checksum = checksumFinish(sum); 141 | 142 | // UDP 143 | } else if (protocol == 17 && length >= ipHeaderLength + 8 /* UDP Header */) { 144 | UDPHeader *udpHeader = (UDPHeader*) ((UINT8*)data + ipHeaderLength); 145 | 146 | udpHeader->Checksum = 0; 147 | sum += checksumAdd((void*) udpHeader, length - ipHeaderLength); 148 | udpHeader->Checksum = checksumFinish(sum); 149 | 150 | // special case for UDP 151 | if (udpHeader->Checksum == 0) { 152 | udpHeader->Checksum = 0xFFFF; 153 | } 154 | // ICMPv6 155 | } else if(protocol == 58 && length > ipHeaderLength + sizeof(ICMPHeader)) { 156 | ICMPHeader *icmpHeader = (ICMPHeader*) ((UINT8*)data + ipHeaderLength); 157 | 158 | icmpHeader->Checksum = 0; 159 | sum += checksumAdd((void*) icmpHeader, length - ipHeaderLength); 160 | icmpHeader->Checksum = checksumFinish(sum); 161 | } 162 | } 163 | } 164 | 165 | size_t calcIPv4HeaderSize(void* data, size_t length) { 166 | 167 | // sanity check 168 | if (!data || length == 0) { 169 | ERR("Invalid parameters"); 170 | return 0; 171 | } 172 | 173 | if (length >= 20) { 174 | // calc IPv4 Header length 175 | IPv4Header *ipHeader = (IPv4Header*) data; 176 | size_t ipHeaderLength = (size_t)ipHeader->HdrLength * 4; 177 | if (length < ipHeaderLength) { 178 | WARN("Invalid Packet len=%u, ipHeaderLength=%u, ipHeader->HdrLength=0x%X", length, ipHeaderLength, ipHeader->HdrLength); 179 | return 0; 180 | } 181 | return ipHeaderLength; 182 | } 183 | WARN("Invalid Packet length=%u < 20", length); 184 | return 0; 185 | } 186 | 187 | size_t calcIPv6HeaderSize(void* data, size_t length, UINT8* returnProtocol) { 188 | 189 | // sanity check 190 | if (!data || length == 0) { 191 | ERR("Invalid parameters"); 192 | return 0; 193 | } 194 | 195 | if (length >= 40) { 196 | IPv6Header *ipHeader = (IPv6Header*) data; 197 | int ipHeaderLength = 40; 198 | UINT8 *data8 = (UINT8*) data; 199 | UINT8 protocol; 200 | 201 | if (length < (size_t)ipHeaderLength) { 202 | return 0; 203 | } 204 | protocol = ipHeader->NextHdr; 205 | 206 | for (;;) { 207 | switch (protocol) { 208 | case 0: 209 | case 43: 210 | case 44: 211 | case 50: 212 | case 51: 213 | case 60: 214 | case 135: 215 | case 139: 216 | case 140: 217 | case 253: 218 | case 254: 219 | if (length < (size_t)ipHeaderLength + 8) { 220 | return 0; 221 | } 222 | protocol = data8[ipHeaderLength]; 223 | ipHeaderLength += 8 + data8[ipHeaderLength + 1] * 8; 224 | if (length < (size_t)ipHeaderLength) { 225 | return 0; 226 | } 227 | break; 228 | default: 229 | if (returnProtocol != NULL) { 230 | *returnProtocol = protocol; 231 | } 232 | return ipHeaderLength; 233 | } 234 | } 235 | } 236 | return 0; 237 | } 238 | -------------------------------------------------------------------------------- /pm_kext/src/pm_debug.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_debug.c 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains implementation of debug and logging features for Portmaster 7 | * 8 | * Scope: Kernelmode 9 | */ 10 | 11 | #include "pm_kernel.h" 12 | #include "pm_common.h" 13 | #include "pm_debug.h" 14 | 15 | int logLevel = LEVEL_INFO; 16 | 17 | #ifdef DEBUG_ON 18 | #define _BUILD "DEBUG" 19 | 20 | static KSPIN_LOCK debugLock; 21 | 22 | void __DEBUG(char* name, int level, int line, char* format, ...) { 23 | if (level >= logLevel) { 24 | KLOCK_QUEUE_HANDLE lockHandle; 25 | //Locking is required because we want to use static variables here for better performance 26 | KeAcquireInStackQueuedSpinLock(&debugLock, &lockHandle); 27 | { 28 | va_list args; 29 | static char buf[DEBUG_BUFSIZE + 1]; 30 | static char *levelNames[] = {"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}; 31 | va_start(args, format); 32 | RtlStringCbVPrintfA(buf, DEBUG_BUFSIZE, format, args); 33 | 34 | DbgPrint("%s %s L%04d: %s\n", name, levelNames[level], line, buf); 35 | va_end(args); 36 | } 37 | KeReleaseInStackQueuedSpinLock(&lockHandle); 38 | } 39 | } 40 | 41 | void ipToString(int *ip, bool ipV6, char* buf, int size) { 42 | if(ipV6) { 43 | RtlStringCbPrintfA(buf, size, "%08x:%08x:%08x:%08x", ip[0], ip[1], ip[2], ip[3]); 44 | } else { 45 | UINT8 a,b,c,d; 46 | a = (UINT8)((ip[0] >> 24) & 0xff); 47 | b = (UINT8)((ip[0] >> 16) & 0xff); 48 | c = (UINT8)((ip[0] >> 8) & 0xff); 49 | d = (UINT8)(ip[0] & 0xff); 50 | RtlStringCbPrintfA(buf, size, "%u.%u.%u.%u", d, c, b, a); 51 | } 52 | return; 53 | } 54 | 55 | void printIpHeader(char* buf, unsigned long bufLength, char* data, unsigned long dataLength) { 56 | UNREFERENCED_PARAMETER(dataLength); 57 | UNREFERENCED_PARAMETER(bufLength); 58 | size_t i = 0; 59 | RtlStringCbPrintfA(buf, 250, "%3u %3u %3u %3u", (UINT8)(data[i]& 0xFF), (UINT8)(data[i+1]& 0xFF), (UINT8)(data[i+2]& 0xFF), (UINT8)(data[i+3]& 0xFF)); 60 | /* for (i = 0; i < dataLength; i++) { 61 | currentPos= i * 3; 62 | if (currentPos >= (bufLength - 3)) { 63 | RtlStringCbPrintfA(buf + currentPos - 3, 3, "%3s", "..."); 64 | buf[bufLength - 1]= 0; 65 | return; 66 | } 67 | RtlStringCbPrintfA(buf + currentPos, 3, "%3u %3u %3u %3u", data[i]& 0xFF, data[i+1]& 0xFF, data[i+2]& 0xFF, data[i+3]& 0xFF); 68 | buf[bufLength - 1]= 0; 69 | }*/ 70 | } 71 | 72 | 73 | char* printIpv4Packet(void* packet) { 74 | static char buf[256]; // this is NOT threadsafe but quick. 75 | IPv4Header *p = (IPv4Header*) packet; 76 | 77 | RtlStringCbPrintfA(buf, sizeof(buf), "ipv4 packet Ver=%ud, Prot=%ud, Check=0x%02x Src=%d.%d.%d.%d, Dst=%d.%d.%d.%d", 78 | p->Version, 79 | p->Protocol, 80 | p->Checksum, 81 | FORMAT_ADDR(RtlUlongByteSwap(p->SrcAddr)), 82 | FORMAT_ADDR(RtlUlongByteSwap(p->DstAddr))); 83 | 84 | return buf; 85 | } 86 | 87 | char* printPacketInfo(PortmasterPacketInfo *packetInfo) { 88 | static char buf[512]; //this is NOT threadsafe but quick. 89 | 90 | if (packetInfo->ipV6 == 1) { 91 | RtlStringCbPrintfA(buf, sizeof(buf), "[%X%02X:%X%02X:%X%02X:%X%02X:%X%02X:%X%02X:%X%02X:%X%02X]:%hu <-%ud-> [%X%02X:%X%02X:%X%02X:%X%02X:%X%02X:%X%02X:%X%02X:%X%02X]:%hu", 92 | FORMAT_ADDR(packetInfo->localIP[0]), 93 | FORMAT_ADDR(packetInfo->localIP[1]), 94 | FORMAT_ADDR(packetInfo->localIP[2]), 95 | FORMAT_ADDR(packetInfo->localIP[3]), 96 | packetInfo->localPort, 97 | packetInfo->direction, 98 | FORMAT_ADDR(packetInfo->remoteIP[0]), 99 | FORMAT_ADDR(packetInfo->remoteIP[1]), 100 | FORMAT_ADDR(packetInfo->remoteIP[2]), 101 | FORMAT_ADDR(packetInfo->remoteIP[3]), 102 | packetInfo->remotePort); 103 | } else { 104 | RtlStringCbPrintfA(buf, sizeof(buf), "%d.%d.%d.%d:%hu <-%ud-> %d.%d.%d.%d:%hu", 105 | FORMAT_ADDR(packetInfo->localIP[0]), 106 | packetInfo->localPort, 107 | packetInfo->direction, 108 | FORMAT_ADDR(packetInfo->remoteIP[0]), 109 | packetInfo->remotePort); 110 | } 111 | return buf; 112 | } 113 | 114 | void initDebugStructure() 115 | { 116 | KeInitializeSpinLock(&debugLock); 117 | } 118 | 119 | 120 | #else // DEBUG_ON 121 | #define _BUILD "RELEASE" 122 | #define __DEBUG(format, ...) 123 | #endif 124 | -------------------------------------------------------------------------------- /pm_kext/src/pm_kernel.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_kernel.c 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains implementation of Windows Driver entrypoints for Portmaster 7 | * Kernel Extension, including DriverEntry, driverDeviceControl, InitDriverObject 8 | * Uses the Windows Filtering Platform (WFP) 9 | * https://docs.microsoft.com/en-us/windows/desktop/FWP/windows-filtering-platform-start-page 10 | * 11 | * Dataflow: 12 | * 1.) Windows Kernel picks up Packet from TCP/IP Stack (netbuffer). 13 | * 2.) Windows Kernel presents packet to Portmaster Kernel Extension via callout. 14 | * 3.) Portmaster Kernel Extension searches verdict for this packet in "verdict_cache", 15 | * using IP-Header Data like protocol, source and destination IP and Port 16 | * 4.) If not found, Portmaster Kernel Extension presents packet to Portmaster Userland 17 | * Application via reverse callback "PortmasterRecvVerdictRequest" 18 | * 5.) Portmaster Userland Application inspects packet_info and sets verdict via 19 | * "PortmasterSetVerdict". 20 | * 6.) If necessary, Portmaster Userland Application may also inspect payload of packet 21 | * via "PortmasterGetPayload", using the packet_id previously received by 22 | * PortmasterRecvVerdictRequest 23 | * 7.) Portmaster Kernel Extension holds intercepted packet in packet_cache until the 24 | * verdict is set. 25 | * 8.) If packet_cache is full, first packet will be dropped, so that the lates packet 26 | * can be stored. 27 | * 28 | * Credits: Based on the excellent work of 29 | * Jared Wright, https://github.com/JaredWright/WFPStarterKit 30 | * Basil, https://github.com/basil00/Divert 31 | * 32 | * Scope: Kernelmode 33 | */ 34 | 35 | #include 36 | 37 | #include "pm_kernel.h" 38 | #include "pm_utils.h" 39 | #define LOGGER_NAME "pm_kernel" 40 | #include "pm_debug.h" 41 | 42 | #include "pm_common.h" 43 | #include "pm_callouts.h" 44 | #include "pm_register.h" 45 | #include "pm_netbuffer.h" 46 | #include "packet_cache.h" 47 | 48 | //#define __STDC_FORMAT_MACROS 49 | //#include 50 | 51 | 52 | /************************************ 53 | Private Data and Prototypes 54 | ************************************/ 55 | // Global handle to the WFP Base Filter Engine 56 | HANDLE filterEngineHandle = NULL; 57 | 58 | #define PORTMASTER_DEVICE_STRING L"\\Device\\" PORTMASTER_DEVICE_NAME //L"\\Device\\PortmasterKext" 59 | #define PORTMASTER_DOS_DEVICE_STRING L"\\??\\" PORTMASTER_DEVICE_NAME 60 | 61 | // Driver entry and exit points 62 | DRIVER_INITIALIZE DriverEntry; 63 | DRIVER_UNLOAD DriverUnload; 64 | EVT_WDF_DRIVER_UNLOAD emptyEventUnload; 65 | 66 | //IO CTL 67 | _IRQL_requires_max_(APC_LEVEL) 68 | __drv_dispatchType(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH driverDeviceControl; 69 | 70 | // Initializes required WDFDriver and WDFDevice objects 71 | NTSTATUS InitDriverObject(DRIVER_OBJECT *driverObject, UNICODE_STRING *registryPath, 72 | WDFDRIVER *driver, WDFDEVICE *device); 73 | 74 | // Global IO Queue for communicating 75 | PRKQUEUE globalIOQueue = NULL; 76 | static LARGE_INTEGER ioQueueTimeout; 77 | #define QUEUE_TIMEOUT_MILI 10000 78 | 79 | /************************************ 80 | Kernel API Functions 81 | ************************************/ 82 | #pragma warning( push ) 83 | // Always disable while making changes to this function! 84 | // FwpmTransactionAbort may fail this will leave filterEngineHandle in locked state. 85 | // If FwpmTransactionCommit() and FwpmTransactionAbort() fail there is noting else to do to release the lock. 86 | #pragma warning( disable : 26165) // warning C26165: Possibly failing to release lock 87 | NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath) { 88 | NTSTATUS status = STATUS_SUCCESS; 89 | WDFDRIVER driver = { 0 }; 90 | WDFDEVICE device = { 0 }; 91 | DEVICE_OBJECT * wdmDevice = NULL; 92 | FWPM_SESSION wdfSession = { 0 }; 93 | bool inTransaction = false; 94 | bool calloutRegistered = false; 95 | 96 | initDebugStructure(); 97 | 98 | INFO("Trying to load Kernel Object '%ls', Compile date: %s %s", PORTMASTER_DEVICE_NAME, __DATE__, __TIME__); 99 | INFO("PM_PACKET_CACHE_SIZE = %d, PM_VERDICT_CACHE_SIZE= %d", PM_PACKET_CACHE_SIZE, PM_VERDICT_CACHE_SIZE); 100 | status = initCalloutStructure(); 101 | if (!NT_SUCCESS(status)) { 102 | status = STATUS_FAILED_DRIVER_ENTRY; 103 | goto Exit; 104 | } 105 | 106 | status = initNetBufferPool(); 107 | if (!NT_SUCCESS(status)) { 108 | goto Exit; 109 | } 110 | 111 | status = InitDriverObject(driverObject, registryPath, &driver, &device); 112 | if (!NT_SUCCESS(status)) { 113 | goto Exit; 114 | } 115 | 116 | // Begin a transaction to the FilterEngine. You must register objects (filter, callouts, sublayers) 117 | //to the filter engine in the context of a 'transaction' 118 | wdfSession.flags = FWPM_SESSION_FLAG_DYNAMIC; // <-- Automatically destroys all filters and callouts after this wdfSession ends 119 | status = FwpmEngineOpen(NULL, RPC_C_AUTHN_WINNT, NULL, &wdfSession, &filterEngineHandle); 120 | if (!NT_SUCCESS(status)) { 121 | goto Exit; 122 | } 123 | status = FwpmTransactionBegin(filterEngineHandle, 0); 124 | if (!NT_SUCCESS(status)) { 125 | goto Exit; 126 | } 127 | inTransaction = true; 128 | 129 | // Register the all Portmaster Callouts and Filters to the filter engine 130 | wdmDevice = WdfDeviceWdmGetDeviceObject(device); 131 | status = registerWFPStack(wdmDevice); 132 | if (!NT_SUCCESS(status)) { 133 | goto Exit; 134 | } 135 | calloutRegistered = true; 136 | 137 | // Commit transaction to the Filter Engine 138 | status = FwpmTransactionCommit(filterEngineHandle); 139 | if (!NT_SUCCESS(status)) { 140 | goto Exit; 141 | } 142 | inTransaction = false; 143 | 144 | // Define this driver's unload function 145 | driverObject->DriverUnload = DriverUnload; 146 | 147 | // Define IO Control via WDDK's IO Request Packet structure 148 | driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = driverDeviceControl; 149 | 150 | 151 | // Cleanup and handle any errors 152 | Exit: 153 | if (!NT_SUCCESS(status)) { 154 | ERR("Portmaster Kernel Extension failed to load, status 0x%08x", status); 155 | if (inTransaction == true) { 156 | FwpmTransactionAbort(filterEngineHandle); 157 | //_Analysis_assume_lock_not_held_(filterEngineHandle); // Potential leak if "FwpmTransactionAbort" fails 158 | } 159 | if (calloutRegistered == true) { 160 | unregisterCallouts(); 161 | } 162 | status = STATUS_FAILED_DRIVER_ENTRY; 163 | } else { 164 | WARN("--- Portmaster Kernel Extension loaded successfully ---"); 165 | } 166 | 167 | return status; 168 | } 169 | #pragma warning( pop ) 170 | 171 | NTSTATUS InitDriverObject(DRIVER_OBJECT * driverObject, UNICODE_STRING * registryPath, WDFDRIVER * driver, WDFDEVICE * device) { 172 | static const long n100nsTimeCount = 1000 * QUEUE_TIMEOUT_MILI; //Unit 100ns -> 1s 173 | 174 | UNICODE_STRING deviceName = { 0 }; 175 | RtlInitUnicodeString(&deviceName, PORTMASTER_DEVICE_STRING); 176 | 177 | UNICODE_STRING deviceSymlink = { 0 }; 178 | RtlInitUnicodeString(&deviceSymlink, PORTMASTER_DOS_DEVICE_STRING); 179 | 180 | // Create a WDFDRIVER for this driver 181 | WDF_DRIVER_CONFIG config = { 0 }; 182 | WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK); 183 | config.DriverInitFlags = WdfDriverInitNonPnpDriver; 184 | config.EvtDriverUnload = emptyEventUnload; // <-- Necessary for this driver to unload correctly 185 | NTSTATUS status = WdfDriverCreate(driverObject, registryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, driver); 186 | if (!NT_SUCCESS(status)) { 187 | goto Exit; 188 | } 189 | 190 | // Create a WDFDEVICE for this driver 191 | PWDFDEVICE_INIT deviceInit = WdfControlDeviceInitAllocate(*driver, &SDDL_DEVOBJ_SYS_ALL_ADM_ALL); // only admins and kernel can access device 192 | if (!deviceInit) { 193 | status = STATUS_INSUFFICIENT_RESOURCES; 194 | goto Exit; 195 | } 196 | 197 | // Configure the WDFDEVICE_INIT with a name to allow for access from user mode 198 | WdfDeviceInitSetDeviceType(deviceInit, FILE_DEVICE_NETWORK); 199 | WdfDeviceInitSetCharacteristics(deviceInit, FILE_DEVICE_SECURE_OPEN, false); 200 | (void) WdfDeviceInitAssignName(deviceInit, &deviceName); 201 | (void) WdfPdoInitAssignRawDevice(deviceInit, &GUID_DEVCLASS_NET); 202 | WdfDeviceInitSetDeviceClass(deviceInit, &GUID_DEVCLASS_NET); 203 | 204 | status = WdfDeviceCreate(&deviceInit, WDF_NO_OBJECT_ATTRIBUTES, device); 205 | if (!NT_SUCCESS(status)) { 206 | WdfDeviceInitFree(deviceInit); 207 | goto Exit; 208 | } 209 | status = WdfDeviceCreateSymbolicLink(*device, &deviceSymlink); 210 | if (!NT_SUCCESS(status)) { 211 | ERR("failed to create device symbolic link: %d", status); 212 | goto Exit; 213 | } 214 | // Initialize a WDF-Queue to transmit questionable packets to userland 215 | ioQueueTimeout.QuadPart = -1LL * n100nsTimeCount; 216 | globalIOQueue = portmasterMalloc(sizeof(KQUEUE), false); 217 | if (globalIOQueue == NULL) { 218 | ERR("Space for Queue could not be allocated (why?)"); 219 | goto Exit; 220 | } 221 | KeInitializeQueue(globalIOQueue, 1); //Only one (1) thread can be satisfied concurrently while waiting for the queue 222 | INFO("Queue created"); 223 | /*status= IPQueueInitialize(*device); 224 | if (!NT_SUCCESS(status)) 225 | { 226 | ERR("Queue could not be initialized: status= 0x%X", status); 227 | goto Exit; 228 | } */ 229 | WdfControlFinishInitializing(*device); 230 | 231 | Exit: 232 | return status; 233 | } 234 | 235 | void DriverUnload(PDRIVER_OBJECT driverObject) { 236 | NTSTATUS status = STATUS_SUCCESS; 237 | UNICODE_STRING symlink = { 0 }; 238 | UNREFERENCED_PARAMETER(driverObject); 239 | 240 | INFO("Starting DriverUnload"); 241 | status = unregisterFilters(); 242 | if (!NT_SUCCESS(status)) { 243 | ERR("Failed to unregister filters, status: 0x%08x", status); 244 | } 245 | status = unregisterCallouts(); 246 | if (!NT_SUCCESS(status)) { 247 | ERR("Failed to unregister callout, status: 0x%08x", status); 248 | } 249 | 250 | destroyCalloutStructure(); 251 | if(globalIOQueue != NULL) { 252 | portmasterFree(globalIOQueue); 253 | globalIOQueue = NULL; 254 | } 255 | 256 | freeNetBufferPool(); 257 | // Close handle to the WFP Filter Engine 258 | if (filterEngineHandle != NULL) { 259 | FwpmEngineClose(filterEngineHandle); 260 | filterEngineHandle = NULL; 261 | } 262 | 263 | RtlInitUnicodeString(&symlink, PORTMASTER_DOS_DEVICE_STRING); 264 | IoDeleteSymbolicLink(&symlink); 265 | 266 | INFO("--- Portmaster Kernel Extension unloaded ---"); 267 | } 268 | 269 | void emptyEventUnload(WDFDRIVER Driver) { 270 | UNREFERENCED_PARAMETER(Driver); 271 | } 272 | 273 | // driverDeviceControl communicates with Userland via 274 | // IO-Request Packets (lrp) 275 | NTSTATUS driverDeviceControl(__in PDEVICE_OBJECT pDeviceObject, __inout PIRP Irp) { 276 | UNREFERENCED_PARAMETER(pDeviceObject); 277 | 278 | //Set pBuf pointer to Irp->AssociatedIrp.SystemBuffer, which was filled in userland 279 | //pBuf is also used to return memory from kernel to userland 280 | void *pBuf = Irp->AssociatedIrp.SystemBuffer; 281 | 282 | PIO_STACK_LOCATION pIoStackLocation = IoGetCurrentIrpStackLocation(Irp); 283 | int IoControlCode = pIoStackLocation->Parameters.DeviceIoControl.IoControlCode; 284 | switch(IoControlCode) { 285 | case IOCTL_VERSION: { 286 | char *versionBuffer = (char*)pBuf; 287 | versionBuffer[0] = PM_VERSION_MAJOR; 288 | versionBuffer[1] = PM_VERSION_MINOR; 289 | versionBuffer[2] = PM_VERSION_REVISION; 290 | versionBuffer[3] = PM_VERSION_BUILD; 291 | Irp->IoStatus.Status = STATUS_SUCCESS; 292 | Irp->IoStatus.Information = 4; 293 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 294 | return STATUS_SUCCESS; 295 | } 296 | case IOCTL_SHUTDOWN_REQUEST: { 297 | INFO("Shutdown request received. Preparing for shutdown ..."); 298 | // Rundown verdict request queue 299 | PLIST_ENTRY entries = KeRundownQueue(globalIOQueue); 300 | if(entries != NULL) { 301 | while(!IsListEmpty(entries)) { 302 | DataEntry *dentry = (DataEntry*)RemoveHeadList(entries); 303 | portmasterFree(dentry); 304 | } 305 | } 306 | Irp->IoStatus.Status = STATUS_SUCCESS; 307 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 308 | return STATUS_SUCCESS; 309 | } 310 | case IOCTL_RECV_VERDICT_REQ: { 311 | DEBUG("IOCTL_RECV_VERDICT_REQ"); 312 | PLIST_ENTRY ple = KeRemoveQueue( 313 | globalIOQueue, 314 | KernelMode, //UserMode, //KernelMode, 315 | &ioQueueTimeout 316 | ); 317 | // Super ugly, but recommended by MS: Callers of KeRemoveQueue should test 318 | // whether its return value is STATUS_TIMEOUT or STATUS_USER_APC before accessing any entry members. 319 | NTSTATUS rc = (NTSTATUS) ((UINT64) ple); 320 | if (rc == STATUS_TIMEOUT) { 321 | INFO("List was empty -> timeout"); 322 | Irp->IoStatus.Status = STATUS_TIMEOUT; 323 | Irp->IoStatus.Information = 0; 324 | IoCompleteRequest(Irp,IO_NO_INCREMENT); 325 | return STATUS_TIMEOUT; 326 | } 327 | if (rc == STATUS_USER_APC) { 328 | INFO("List was empty or not-> STATUS_USER_APC"); 329 | Irp->IoStatus.Status = STATUS_USER_APC; 330 | Irp->IoStatus.Information = 0; 331 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 332 | return STATUS_USER_APC; 333 | } 334 | if (rc == STATUS_ABANDONED) { 335 | INFO("Queue was rundown-> STATUS_ABANDONED"); 336 | Irp->IoStatus.Status = STATUS_ABANDONED; 337 | Irp->IoStatus.Information = 0; 338 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 339 | return STATUS_ABANDONED; 340 | } 341 | 342 | INFO("Sending VERDICT-REQUEST to userland"); 343 | 344 | { 345 | DataEntry *dentry = (DataEntry*)CONTAINING_RECORD(ple, DataEntry, entry); 346 | int size = sizeof(PortmasterPacketInfo); 347 | 348 | RtlZeroMemory(pBuf, pIoStackLocation->Parameters.DeviceIoControl.InputBufferLength); 349 | // Copy message from kernel to pBuf, so that it can be evaluated in userland 350 | RtlCopyMemory(pBuf, dentry->packet, size); 351 | Irp->IoStatus.Status = STATUS_SUCCESS; 352 | Irp->IoStatus.Information = size; 353 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 354 | 355 | if ((dentry->packet->flags & PM_STATUS_SOCKET_AUTH) > 0) { 356 | // Packet comes from the ALE layer and it's not saved in cache. It's not needed anymore. 357 | portmasterFree(dentry->packet); 358 | } 359 | 360 | // Now that the contents of the list-entry is copied, free memory 361 | portmasterFree(dentry); 362 | return STATUS_SUCCESS; 363 | } 364 | } 365 | case IOCTL_SET_VERDICT: { 366 | PortmasterVerdictInfo *verdictInfo = (PortmasterVerdictInfo*) pBuf; 367 | UINT32 id = verdictInfo->id; 368 | verdict_t verdict = verdictInfo->verdict; 369 | 370 | const char *verdictName = NULL; 371 | if ((size_t)abs(verdict) < sizeof(VERDICT_NAMES)) { 372 | verdictName = VERDICT_NAMES[abs(verdict)]; 373 | } else { 374 | verdictName = "UNDEFINED"; 375 | } 376 | INFO("Setting verdict %d for packet id %u: %s", verdict, id, verdictName); 377 | 378 | respondWithVerdict(id, verdict); 379 | 380 | Irp->IoStatus.Status = STATUS_SUCCESS; 381 | Irp->IoStatus.Information = 0; 382 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 383 | return STATUS_SUCCESS; 384 | } 385 | 386 | case IOCTL_GET_PAYLOAD: { 387 | void *packet = NULL; 388 | size_t packetLength = 0; 389 | // 0. Make Userland supplied Buffer useable 390 | PortmasterPayload *payload = (PortmasterPayload*) pBuf; 391 | 392 | INFO("IOCTL_GET_PAYLOAD for id=%u, expect %u Bytes", payload->id, payload->len); 393 | // 1. Locate packet in packet cache 394 | NTSTATUS rc = (NTSTATUS)packetCacheGet(getPacketCache(), payload->id, &packet, &packetLength); 395 | 396 | // 2. Sanity Checks 397 | if (rc != 0) { 398 | // packet id was not in packet cache 399 | WARN("packet_id unknown: %u -> STATUS_OBJECT_NAME_NOT_FOUND", payload->id); 400 | rc = STATUS_OBJECT_NAME_NOT_FOUND; //->Maps to Userland via GetLastError "ERROR_FILE_NOT_FOUND"; 401 | Irp->IoStatus.Information = 0; 402 | goto IOCTL_GET_PAYLOAD_EXIT; 403 | } 404 | if ((packetLength == 0) || (!packet)) { 405 | WARN("packet_id=%d, but packetLength= %u, packet=null", payload->id, packetLength); 406 | rc = STATUS_INVALID_PARAMETER; //->Maps to Userland via GetLastError "??"; 407 | Irp->IoStatus.Information = 0; 408 | goto IOCTL_GET_PAYLOAD_EXIT; 409 | } 410 | 411 | if (packetLength != payload->len && packetLength != pIoStackLocation->Parameters.DeviceIoControl.InputBufferLength) { 412 | WARN("Caller supplied buffer(%u Bytes) for id=%u too small for packet(%u Bytes) -> STATUS_INSUFFICIENT_RESOURCES", payload->len, payload->id, packetLength); 413 | rc = STATUS_INSUFFICIENT_RESOURCES; //->Maps to Userland via GetLastError "ERROR_NO_SYSTEM_RESOURCES" 414 | Irp->IoStatus.Information = 0; 415 | goto IOCTL_GET_PAYLOAD_EXIT; 416 | } 417 | if (packetLength > MAX_PAYLOAD_SIZE) { 418 | WARN("Oh no"); 419 | rc = STATUS_INSUFFICIENT_RESOURCES; //->Maps to Userland via GetLastError "ERROR_NO_SYSTEM_RESOURCES" 420 | Irp->IoStatus.Information = 0; 421 | goto IOCTL_GET_PAYLOAD_EXIT; 422 | } 423 | 424 | // 3. Copy Packet to user supplied buffer 425 | rc = STATUS_SUCCESS; 426 | INFO("Retrieved packet for id=%u, len=%u, rc=%d", payload->id, packetLength, rc); 427 | //RtlZeroMemory(pBuf, packetLength); 428 | RtlCopyMemory(pBuf, packet, packetLength); 429 | 430 | //Finish the I/O operation by simply completing the packet and returning 431 | //the same status as in the packet itself. 432 | Irp->IoStatus.Information = packetLength; 433 | 434 | IOCTL_GET_PAYLOAD_EXIT: 435 | //Irp->IoStatus.Information is the ONLY way to transfer status information to userland 436 | //We need to share it with "Bytes Transferred". That is why we ignore the (unsigned) type 437 | //of Irp->IoStatus.Information and use the first (sign) Bit to distinguish between 438 | // (0) Bytes Transferred and 439 | // (1) Status 440 | //Irp->IoStatus.Status is only used internally and cannot be accessed by userland 441 | //Latest Enlightenments proof this hypothesis wrong: There seems to be some mapping 442 | //between NT-Status Codes and Userland Status Codes! 443 | Irp->IoStatus.Status = rc; 444 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 445 | return rc; 446 | } 447 | case IOCTL_CLEAR_CACHE: { 448 | clearCache(); 449 | Irp->IoStatus.Status = STATUS_SUCCESS; 450 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 451 | return STATUS_SUCCESS; 452 | } 453 | case IOCTL_UPDATE_VERDICT: { 454 | VerdictUpdateInfo *verdictUpdateInfo = (VerdictUpdateInfo*)pBuf; 455 | updateVerdict(verdictUpdateInfo); 456 | Irp->IoStatus.Status = STATUS_SUCCESS; 457 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 458 | return STATUS_SUCCESS; 459 | } 460 | case IOCTL_GET_CONNECTIONS_STATS: { 461 | UINT32 *arraySize = (UINT32*) pBuf; 462 | PortmasterConnection *connections = (PortmasterConnection *) pBuf; 463 | int writeCount = getConnectionsStats(connections, *arraySize); 464 | Irp->IoStatus.Status = STATUS_SUCCESS; 465 | Irp->IoStatus.Information = writeCount * sizeof(PortmasterConnection); 466 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 467 | return STATUS_SUCCESS; 468 | } 469 | default: { 470 | ERR("Don't know how to deal with IoControlCode 0x%x", IoControlCode); 471 | Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED; 472 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 473 | return STATUS_NOT_IMPLEMENTED; 474 | } 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /pm_kext/src/pm_netbuffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_netbuffer.c 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains implementation for handling windows netbuffers 7 | * like coping memory from networkstack to kernel 8 | * https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ndis/ns-ndis-_net_buffer 9 | * 10 | * Scope: Kernelmode 11 | */ 12 | 13 | #include "pm_kernel.h" 14 | #include "pm_utils.h" 15 | #define LOGGER_NAME "pm_netbuffer" 16 | #include "pm_debug.h" 17 | 18 | #include "pm_common.h" 19 | 20 | 21 | /***************************************************************** 22 | Global Variables to handle access to net buffers 23 | *****************************************************************/ 24 | NDIS_HANDLE nblPoolHandle = NULL; // Handle for NetBufferList 25 | NDIS_HANDLE nbPoolHandle = NULL; // Handle for one NetBuffer 26 | 27 | 28 | /***************************************************************** 29 | Helpers 30 | *****************************************************************/ 31 | /* 32 | * Initializes pool for netbuffers 33 | * Called at DriverEntry 34 | */ 35 | NTSTATUS initNetBufferPool() { 36 | // Create a NET_BUFFER_LIST pool handle. 37 | NET_BUFFER_LIST_POOL_PARAMETERS nblPoolParams; 38 | RtlZeroMemory(&nblPoolParams, sizeof(nblPoolParams)); 39 | nblPoolParams.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; 40 | nblPoolParams.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; 41 | nblPoolParams.Header.Size = sizeof(nblPoolParams); 42 | nblPoolParams.fAllocateNetBuffer = true; 43 | nblPoolParams.PoolTag = PORTMASTER_TAG; 44 | nblPoolParams.DataSize = 0; 45 | nblPoolHandle = NdisAllocateNetBufferListPool(NULL, &nblPoolParams); 46 | if (nblPoolHandle == NULL) { 47 | ERR("failed to allocate net buffer list pool"); 48 | return STATUS_INSUFFICIENT_RESOURCES; 49 | } 50 | 51 | // Create a NET_BUFFER pool handle. 52 | NET_BUFFER_POOL_PARAMETERS nbPoolParams; 53 | RtlZeroMemory(&nbPoolParams, sizeof(nbPoolParams)); 54 | nbPoolParams.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; 55 | nbPoolParams.Header.Revision = NET_BUFFER_POOL_PARAMETERS_REVISION_1; 56 | nbPoolParams.Header.Size = NDIS_SIZEOF_NET_BUFFER_POOL_PARAMETERS_REVISION_1; 57 | nbPoolParams.PoolTag = PORTMASTER_TAG; 58 | nbPoolParams.DataSize = 0; 59 | nbPoolHandle = NdisAllocateNetBufferPool(NULL, &nbPoolParams); 60 | if (nbPoolHandle == NULL) { 61 | ERR("failed to allocate net buffer pool"); 62 | return STATUS_INSUFFICIENT_RESOURCES; 63 | } 64 | 65 | INFO("initNetBufferPool OK"); 66 | return STATUS_SUCCESS; 67 | } 68 | 69 | /* 70 | * Frees the NetBufferPool 71 | * Called at DriverUnload 72 | */ 73 | void freeNetBufferPool() { 74 | if (nblPoolHandle != NULL) { 75 | NdisFreeNetBufferListPool(nblPoolHandle); 76 | } 77 | if (nbPoolHandle != NULL) { 78 | NdisFreeNetBufferPool(nbPoolHandle); 79 | } 80 | INFO("freeNetBufferPool OK"); 81 | } 82 | 83 | /***************************************************************** 84 | Acutal Netbuffer Handling 85 | *****************************************************************/ 86 | /* 87 | * Wraps packet data into netbuffer 88 | * Required for Sending / Injecting packets 89 | * packetData: pointer to packet (Endianness must be set correctly at this level) 90 | * packetLength: Length of packet in bytes 91 | * Returns 92 | * PNET_BUFFER_LIST*: packet to be sent 93 | * NTSTATUS 94 | * Called by redir and respondWithVerdict 95 | */ 96 | NTSTATUS wrapPacketDataInNB(void* packetData, size_t packetLength, PNET_BUFFER_LIST* nbl) { 97 | // sanity check 98 | if (!packetData || packetLength == 0 || !nbl) { 99 | ERR("Invalid parameters"); 100 | return STATUS_INVALID_PARAMETER; 101 | } 102 | 103 | PMDL mdl = IoAllocateMdl(packetData, (ULONG)packetLength, false, false, NULL); 104 | if (mdl == NULL) { 105 | ERR("failed to allocate MDL for reinjected packet"); 106 | return STATUS_INSUFFICIENT_RESOURCES; 107 | } 108 | 109 | MmBuildMdlForNonPagedPool(mdl); 110 | 111 | PNET_BUFFER_LIST buffers = NULL; 112 | NTSTATUS status = FwpsAllocateNetBufferAndNetBufferList0(nblPoolHandle, 0, 0, mdl, 0, packetLength, &buffers); 113 | if (!NT_SUCCESS(status)) { 114 | ERR("failed to create NET_BUFFER_LIST for reinjected packet"); 115 | IoFreeMdl(mdl); 116 | return status; 117 | } 118 | *nbl = buffers; 119 | 120 | return STATUS_SUCCESS; 121 | } 122 | 123 | /* 124 | * "Borrows" data from net buffer without actually coping it 125 | * This is faster, but does not always succeed. 126 | * Called by classifyAll. 127 | */ 128 | NTSTATUS borrowPacketDataFromNB(PNET_BUFFER nb, size_t bytesNeeded, void **data) { 129 | // sanity check 130 | if (!nb || !data) { 131 | ERR("Invalid parameters"); 132 | return STATUS_INVALID_PARAMETER; 133 | } 134 | 135 | void *ptr = NdisGetDataBuffer(nb, (ULONG)bytesNeeded, NULL, 1, 0); 136 | if (ptr != NULL) { 137 | *data = ptr; 138 | return STATUS_SUCCESS; 139 | } 140 | 141 | return STATUS_INTERNAL_ERROR; 142 | } 143 | 144 | /* 145 | * copies packet data from net buffer "nb" to "data" up to the size "maxBytes" 146 | * actual bytes copied is stored in "dataLength" 147 | * returns NTSTATUS 148 | * Called by classifyAll and redir_from_callout if "borrow_packet_data_from_nb" fails 149 | * 150 | * NET_BUFFER_LIST can hold multiple NET_BUFFER in rare edge cases. Ignoring these is ok for now. 151 | * TODO: handle these cases. 152 | */ 153 | NTSTATUS copyPacketDataFromNB(PNET_BUFFER nb, size_t maxBytes, void **data, size_t *dataLength) { 154 | *dataLength = NET_BUFFER_DATA_LENGTH(nb); 155 | 156 | // sanity check 157 | if (!nb || !data || !dataLength) { 158 | ERR("Invalid parameters"); 159 | return STATUS_INVALID_PARAMETER; 160 | } 161 | 162 | if (maxBytes == 0 || maxBytes > *dataLength) { 163 | maxBytes = *dataLength; 164 | } else { 165 | *dataLength = maxBytes; 166 | } 167 | 168 | *data = portmasterMalloc(maxBytes, false); 169 | if (*data == NULL) { 170 | return STATUS_INSUFFICIENT_RESOURCES; 171 | } 172 | //Copy data from NET_BUFFER 173 | void *ptr = NdisGetDataBuffer(nb, (ULONG)maxBytes, NULL, 1, 0); 174 | if (ptr != NULL) { 175 | // Contiguous (common) case: 176 | RtlCopyMemory(*data, ptr, maxBytes); 177 | } else { 178 | // Non-contigious case: 179 | ptr = NdisGetDataBuffer(nb, (ULONG)maxBytes, *data, 1, 0); 180 | if (ptr == NULL) { 181 | return STATUS_INTERNAL_ERROR; 182 | } 183 | } 184 | 185 | return STATUS_SUCCESS; 186 | } 187 | -------------------------------------------------------------------------------- /pm_kext/src/pm_packet.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_packet_utils.c 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Helper packet functions 7 | * 8 | * Scope: Kernelmode 9 | */ 10 | 11 | #include "pm_packet.h" 12 | #include "pm_checksum.h" 13 | #include "pm_netbuffer.h" 14 | #include "pm_utils.h" 15 | #include "pm_debug.h" 16 | 17 | static size_t getTCPResetPacketSizeIPv4(); 18 | static size_t getTCPResetPacketSizeIPv6(); 19 | static size_t getICMPBlockedPacketSizeIPv4(void *originalPacket, size_t originalPacketLength); 20 | static size_t getICMPBlockedPacketSizeIPv6(size_t originalPacketLength); 21 | 22 | static void generateTCPResetPacketIPv4(void *originalPacket, size_t originalPacketLength, void *packet); 23 | static void generateTCPResetPacketIPv6(void *originalPacket, size_t originalPacketLength, void *packet); 24 | static void generateICMPBlockedPacketIPv4(void *originalPacket, size_t originalPacketLength, bool useLocalHost, void *icmpPacket); 25 | static void generateICMPBlockedPacketIPv6(void *originalPacket, size_t originalPacketLength, bool useLocalHost, void *icmpPacket); 26 | 27 | static NTSTATUS sendTCPResetPacket(PortmasterPacketInfo* packetInfo, void* originalPacket, size_t originalPacketLength); 28 | static NTSTATUS sendICMPBlockedPacket(PortmasterPacketInfo* packetInfo, void* originalPacket, size_t originalPacketLength, bool useLocalHost); 29 | 30 | static void freeAfterInject(void *context, NET_BUFFER_LIST *nbl, BOOLEAN dispatchLevel); 31 | 32 | // We need separate handles for In/Out for the redirect to work properly. See the use of FWPS_PACKET_INJECTED_BY_SELF. 33 | static HANDLE injectV4InHandle = NULL; 34 | static HANDLE injectV4OutHandle = NULL; 35 | static HANDLE injectV6InHandle = NULL; 36 | static HANDLE injectV6OutHandle = NULL; 37 | 38 | // exclusive injection handlers for generated ICMP and TCP packets for blocked connections. 39 | static HANDLE injectV4Blocked = NULL; 40 | static HANDLE injectV6Blocked = NULL; 41 | 42 | NTSTATUS initializeInjectHandles() { 43 | // Create the packet injection handles. 44 | NTSTATUS status = FwpsInjectionHandleCreate(AF_INET, 45 | FWPS_INJECTION_TYPE_NETWORK, 46 | &injectV4InHandle); 47 | if (!NT_SUCCESS(status)) { 48 | ERR("failed to create WFP in4 injection handle", status); 49 | return status; 50 | } 51 | 52 | status = FwpsInjectionHandleCreate(AF_INET, 53 | FWPS_INJECTION_TYPE_NETWORK, 54 | &injectV4OutHandle); 55 | if (!NT_SUCCESS(status)) { 56 | ERR("failed to create WFP out4 injection handle", status); 57 | return status; 58 | } 59 | 60 | status = FwpsInjectionHandleCreate(AF_INET6, 61 | FWPS_INJECTION_TYPE_NETWORK, 62 | &injectV6InHandle); 63 | if (!NT_SUCCESS(status)) { 64 | ERR("failed to create WFP in6 injection handle", status); 65 | return status; 66 | } 67 | 68 | status = FwpsInjectionHandleCreate(AF_INET6, 69 | FWPS_INJECTION_TYPE_NETWORK, 70 | &injectV6OutHandle); 71 | if (!NT_SUCCESS(status)) { 72 | ERR("failed to create WFP out6 injection handle", status); 73 | return status; 74 | } 75 | 76 | status = FwpsInjectionHandleCreate(AF_INET, 77 | FWPS_INJECTION_TYPE_NETWORK, 78 | &injectV4Blocked); 79 | if (!NT_SUCCESS(status)) { 80 | ERR("failed to create WFP injection v4 blocked handle", status); 81 | return status; 82 | } 83 | 84 | status = FwpsInjectionHandleCreate(AF_INET6, 85 | FWPS_INJECTION_TYPE_NETWORK, 86 | &injectV6Blocked); 87 | if (!NT_SUCCESS(status)) { 88 | ERR("failed to create WFP injection v6 blocked handle", status); 89 | return status; 90 | } 91 | 92 | return STATUS_SUCCESS; 93 | } 94 | 95 | void destroyInjectHandles() { 96 | if (injectV4InHandle != NULL) { 97 | FwpsInjectionHandleDestroy(injectV4InHandle); 98 | injectV4InHandle = NULL; 99 | } 100 | 101 | if (injectV4OutHandle != NULL) { 102 | FwpsInjectionHandleDestroy(injectV4OutHandle); 103 | injectV4OutHandle = NULL; 104 | } 105 | 106 | if (injectV6InHandle != NULL) { 107 | FwpsInjectionHandleDestroy(injectV6InHandle); 108 | injectV6InHandle = NULL; 109 | } 110 | 111 | if (injectV6OutHandle != NULL) { 112 | FwpsInjectionHandleDestroy(injectV6OutHandle); 113 | injectV6OutHandle = NULL; 114 | } 115 | 116 | if (injectV4Blocked != NULL) { 117 | FwpsInjectionHandleDestroy(injectV4Blocked); 118 | injectV4Blocked = NULL; 119 | } 120 | 121 | if (injectV6Blocked != NULL) { 122 | FwpsInjectionHandleDestroy(injectV6Blocked); 123 | injectV6Blocked = NULL; 124 | } 125 | } 126 | 127 | /** 128 | * @brief returns the proper injection handler for the packet. 129 | * Used for DNS or SPN redirected packets, we need separate handlers for inbound and outbound, for proper detection in the callout. 130 | * See the use of FWPS_PACKET_INJECTED_BY_SELF. 131 | * @par packetInfo = info for the packet 132 | * @return injection handler 133 | */ 134 | HANDLE getInjectionHandleForPacket(PortmasterPacketInfo *packetInfo) { 135 | bool isLoopback = isPacketLoopback(packetInfo); 136 | if (packetInfo->ipV6 == 0) { 137 | if(packetInfo->direction == DIRECTION_OUTBOUND || isLoopback) { 138 | return injectV4OutHandle; 139 | } else { 140 | return injectV4InHandle; 141 | } 142 | } else{ 143 | if(packetInfo->direction == DIRECTION_OUTBOUND || isLoopback) { 144 | return injectV6OutHandle; 145 | } else { 146 | return injectV6InHandle; 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * @brief returns the proper injection handler for the packet. 153 | * Used for injecting packets for blocked connections, or checking if packet was injected for blocking a connection. 154 | * @par packetInfo = info for the packet 155 | * @return injection handler 156 | */ 157 | HANDLE getBlockedPacketInjectHandle(PortmasterPacketInfo *packetInfo) { 158 | if (packetInfo->ipV6 == 0) { 159 | return injectV4Blocked; 160 | } else{ 161 | return injectV6Blocked; 162 | } 163 | } 164 | 165 | NTSTATUS injectPacketWithHandle(HANDLE handle, PortmasterPacketInfo *packetInfo, UINT8 direction, void *packet, size_t packetLength) { 166 | // Create network buffer list for the packet 167 | PNET_BUFFER_LIST injectNBL = NULL; 168 | NTSTATUS status = wrapPacketDataInNB(packet, packetLength, &injectNBL); 169 | if (!NT_SUCCESS(status)) { 170 | ERR("wrap_packet_data_in_nb failed: %u", status); 171 | portmasterFree(packet); 172 | return status; 173 | } 174 | 175 | // Inject packet. For localhost we must always send 176 | if (direction == DIRECTION_OUTBOUND) { 177 | status = FwpsInjectNetworkSendAsync(handle, NULL, 0, 178 | UNSPECIFIED_COMPARTMENT_ID, injectNBL, freeAfterInject, 179 | packet); 180 | INFO("InjectNetworkSend executed: %s", printPacketInfo(packetInfo)); 181 | } else { 182 | status = FwpsInjectNetworkReceiveAsync(handle, NULL, 0, 183 | UNSPECIFIED_COMPARTMENT_ID, packetInfo->interfaceIndex, 184 | packetInfo->subInterfaceIndex, injectNBL, freeAfterInject, 185 | packet); 186 | INFO("InjectNetworkReceive executed: %s", printPacketInfo(packetInfo)); 187 | } 188 | 189 | if (!NT_SUCCESS(status)) { 190 | freeAfterInject(packet, injectNBL, false); 191 | } 192 | 193 | return status; 194 | } 195 | 196 | NTSTATUS injectPacket(PortmasterPacketInfo *packetInfo, UINT8 direction, void *packet, size_t packetLength) { 197 | // get inject handle and check if packet is localhost 198 | HANDLE handle = getInjectionHandleForPacket(packetInfo); 199 | bool isLoopback = isPacketLoopback(packetInfo); 200 | NTSTATUS status = STATUS_SUCCESS; 201 | // Inject packet. For localhost we must always send 202 | if (direction == DIRECTION_OUTBOUND || isLoopback) { 203 | status = injectPacketWithHandle(handle, packetInfo, DIRECTION_OUTBOUND, packet, packetLength); 204 | INFO("InjectNetworkSend executed: %s", printPacketInfo(packetInfo)); 205 | } else { 206 | status = injectPacketWithHandle(handle, packetInfo, DIRECTION_INBOUND, packet, packetLength); 207 | INFO("InjectNetworkReceive executed: %s", printPacketInfo(packetInfo)); 208 | } 209 | 210 | return status; 211 | } 212 | 213 | void copyAndInject(PortmasterPacketInfo* packetInfo, PNET_BUFFER nb, UINT32 ipHeaderSize) { 214 | NTSTATUS status = STATUS_SUCCESS; 215 | 216 | // Retreat buffer data start for inbound packet. 217 | if (packetInfo->direction == DIRECTION_INBOUND) { 218 | status = NdisRetreatNetBufferDataStart(nb, ipHeaderSize, 0, NULL); 219 | if (!NT_SUCCESS(status)) { 220 | ERR("copyAndInject > failed to retreat net buffer data start"); 221 | return; 222 | } 223 | } 224 | 225 | // Copy the packet data. 226 | void* packet = NULL; 227 | size_t packetLength = 0; 228 | status = copyPacketDataFromNB(nb, 0, &packet, &packetLength); 229 | if (!NT_SUCCESS(status)) { 230 | ERR("copyAndInject > copy_packet_data_from_nb failed: %d", status); 231 | return; 232 | } 233 | 234 | // Advance data start back to original position. 235 | if (packetInfo->direction == DIRECTION_INBOUND) { 236 | NdisAdvanceNetBufferDataStart(nb, ipHeaderSize, 0, NULL); 237 | } 238 | 239 | // Fix checksums, including TCP/UDP. 240 | if (!packetInfo->ipV6) { 241 | calcIPv4Checksum(packet, packetLength, true); 242 | } else { 243 | calcIPv6Checksum(packet, packetLength, true); 244 | } 245 | 246 | status = injectPacket(packetInfo, packetInfo->direction, packet, packetLength); // this call will free the packet even if the inject fails 247 | 248 | if (!NT_SUCCESS(status)) { 249 | ERR("copyAndInject -> FwpsInjectNetworkSendAsync or FwpsInjectNetworkReceiveAsync returned %d", status); 250 | } 251 | } 252 | 253 | static size_t getTCPResetPacketSizeIPv4() { 254 | return sizeof(IPv4Header) + sizeof(TCPHeader); 255 | } 256 | 257 | static size_t getTCPResetPacketSizeIPv6() { 258 | return sizeof(IPv6Header) + sizeof(TCPHeader); 259 | } 260 | 261 | static void generateTCPResetPacketIPv4(void *originalPacket, size_t originalPacketLength, void *tcpResetPacket) { 262 | // Initialize header for the original packet with SYN flag 263 | size_t originalIPHeaderLength = calcIPv4HeaderSize(originalPacket, originalPacketLength); 264 | IPv4Header *originalIPHeader = (IPv4Header*) originalPacket; 265 | TCPHeader *originalTCPHeader = (TCPHeader*) ((UINT8*)originalPacket + originalIPHeaderLength); 266 | size_t packetLength = getTCPResetPacketSizeIPv4(); 267 | 268 | // initialize IPv4 header 269 | IPv4Header *ipHeader = (IPv4Header*) tcpResetPacket; 270 | ipHeader->HdrLength = sizeof(IPv4Header) / 4; 271 | ipHeader->Version = IPv4; 272 | ipHeader->TOS = 0; 273 | ipHeader->Length = RtlUshortByteSwap(packetLength); 274 | ipHeader->Id = 0; 275 | ipHeader->Protocol = PROTOCOL_TCP; 276 | ipHeader->TTL = 128; 277 | ipHeader->DstAddr = originalIPHeader->SrcAddr; // Source becomes destination 278 | ipHeader->SrcAddr = originalIPHeader->DstAddr; // Destination becomes source 279 | 280 | // Initialize TCP header 281 | TCPHeader *tcpHeader = (TCPHeader*) ((UINT8*)tcpResetPacket + sizeof(IPv4Header)); 282 | tcpHeader->SrcPort = originalTCPHeader->DstPort; // Source becomes destination 283 | tcpHeader->DstPort = originalTCPHeader->SrcPort; // Destination becomes source 284 | tcpHeader->HdrLength = sizeof(TCPHeader) / 4; 285 | tcpHeader->SeqNum = 0; 286 | // We should acknowledge the SYN packet while doing the reset 287 | tcpHeader->AckNum = RtlUlongByteSwap(RtlUlongByteSwap(originalTCPHeader->SeqNum) + 1); 288 | tcpHeader->Ack = 1; 289 | tcpHeader->Rst = 1; 290 | 291 | calcIPv4Checksum(tcpResetPacket, packetLength, true); 292 | } 293 | 294 | static void generateTCPResetPacketIPv6(void *originalPacket, size_t originalPacketLength, void *tcpResetPacket) { 295 | // Initialize header for the original packet with SYN flag 296 | size_t originalIPHeaderLength = calcIPv6HeaderSize(originalPacket, originalPacketLength, NULL); 297 | IPv6Header *originalIPHeader = (IPv6Header*) originalPacket; 298 | TCPHeader *originalTCPHeader = (TCPHeader*) ((UINT8*)originalPacket + originalIPHeaderLength); 299 | 300 | // allocate memory for the reset packet 301 | size_t packetLength = getTCPResetPacketSizeIPv6(); 302 | 303 | // initialize IPv6 header 304 | IPv6Header *ipHeader = (IPv6Header*) tcpResetPacket; 305 | ipHeader->Version = IPv6; 306 | ipHeader->Length = sizeof(TCPHeader); 307 | ipHeader->NextHdr = PROTOCOL_TCP; 308 | ipHeader->HopLimit = 128; 309 | RtlCopyMemory(ipHeader->DstAddr, originalIPHeader->SrcAddr, sizeof(originalIPHeader->SrcAddr)); // Source becomes destination 310 | RtlCopyMemory(ipHeader->SrcAddr, originalIPHeader->DstAddr, sizeof(originalIPHeader->DstAddr)); // Destination becomes source 311 | 312 | // Initialize TCP header 313 | TCPHeader *tcpHeader = (TCPHeader*) ((UINT8*)tcpResetPacket + sizeof(IPv6Header)); 314 | tcpHeader->SrcPort = originalTCPHeader->DstPort; // Source becomes destination 315 | tcpHeader->DstPort = originalTCPHeader->SrcPort; // Destination becomes source 316 | tcpHeader->HdrLength = sizeof(TCPHeader) / 4; 317 | tcpHeader->SeqNum = 0; 318 | // We should acknowledge the SYN packet while doing the reset 319 | tcpHeader->AckNum = RtlUlongByteSwap(RtlUlongByteSwap(originalTCPHeader->SeqNum) + 1); 320 | tcpHeader->Ack = 1; 321 | tcpHeader->Rst = 1; 322 | 323 | calcIPv6Checksum(tcpResetPacket, packetLength, true); 324 | } 325 | 326 | static size_t getICMPBlockedPacketSizeIPv4(void* originalPacket, size_t originalPacketLength) { 327 | size_t originalIPHeaderLength = calcIPv4HeaderSize(originalPacket, originalPacketLength); 328 | // ICMP body is the original packet IP header + first 64bits (8 bytes) of the body https://www.rfc-editor.org/rfc/rfc792 329 | size_t bytesToCopyFromOriginalPacket = originalIPHeaderLength + 8; 330 | // Check if the body is less then 8 bytes 331 | if(bytesToCopyFromOriginalPacket > originalPacketLength) { 332 | bytesToCopyFromOriginalPacket = originalPacketLength; 333 | } 334 | 335 | size_t headerLength = sizeof(IPv4Header) + sizeof(ICMPHeader); 336 | size_t packetLength = headerLength + bytesToCopyFromOriginalPacket; 337 | 338 | return packetLength; 339 | } 340 | 341 | static void generateICMPBlockedPacketIPv4(void* originalPacket, size_t originalPacketLength, bool useLocalHost, void *icmpPacket) { 342 | // Initialize header for the original UDP packet 343 | IPv4Header *originalIPHeader = (IPv4Header*) originalPacket; 344 | 345 | // Initialize variables 346 | UINT16 headerLength = sizeof(IPv4Header) + sizeof(ICMPHeader); 347 | UINT16 packetLength = (UINT16)getICMPBlockedPacketSizeIPv4(originalPacket, originalPacketLength); 348 | UINT16 bytesToCopyFromOriginalPacket = packetLength - headerLength; 349 | 350 | // Initialize IPv4 header 351 | IPv4Header *ipHeader = (IPv4Header*) icmpPacket; 352 | ipHeader->HdrLength = sizeof(IPv4Header) / 4; 353 | ipHeader->Version = IPv4; 354 | ipHeader->TOS = 0; 355 | ipHeader->Length = RtlUshortByteSwap(packetLength); 356 | ipHeader->Id = 0; 357 | ipHeader->Protocol = PROTOCOL_ICMP; 358 | ipHeader->TTL = 128; 359 | 360 | // Use localhost as source and destination to bypass the Windows firewall 361 | if(useLocalHost) { 362 | ipHeader->SrcAddr = IPv4_LOCALHOST_IP_NETWORK_ORDER; // loopback address 127.0.0.1 363 | ipHeader->DstAddr = IPv4_LOCALHOST_IP_NETWORK_ORDER; // loopback address 127.0.0.1 364 | } else { 365 | ipHeader->SrcAddr = originalIPHeader->DstAddr; // Source becomes destination 366 | ipHeader->DstAddr = originalIPHeader->SrcAddr; // Destination becomes source 367 | } 368 | 369 | ICMPHeader *icmpHeader = (ICMPHeader*) ((UINT8*)icmpPacket + sizeof(IPv4Header)); 370 | icmpHeader->Type = ICMPV4_CODE_DESTINATION_UNREACHABLE; 371 | icmpHeader->Code = ICMPV4_CODE_DU_PORT_UNREACHABLE; // the only code that closes the UDP connection on Windows 10. 372 | 373 | // Calculate checksum for the original packet and copy it in the icmp body. 374 | calcIPv4Checksum(originalPacket, originalPacketLength, true); 375 | RtlCopyMemory(((UINT8*)icmpHeader + sizeof(ICMPHeader)), originalPacket, bytesToCopyFromOriginalPacket); 376 | 377 | // Calculate checksum for the icmp packet 378 | calcIPv4Checksum(icmpPacket, packetLength, true); 379 | } 380 | 381 | static size_t getICMPBlockedPacketSizeIPv6(size_t originalPacketLength) { 382 | UINT16 bytesToCopyFromOriginalPacket = (UINT16)originalPacketLength; 383 | UINT16 headerLength = sizeof(IPv6Header) + sizeof(ICMPHeader); 384 | UINT16 packetLength = headerLength + bytesToCopyFromOriginalPacket; 385 | // Check if the packet exceeds the minimum MTU. 386 | // The body of the ICMPv6: As much of invoking packet as possible without the ICMPv6 packet exceeding the minimum IPv6 MTU https://www.rfc-editor.org/rfc/rfc4443#section-3.1 387 | // IPv6 requires that every link in the internet have an MTU of 1280 octets or greater https://www.ietf.org/rfc/rfc2460.txt -> 5. Packet Size Issues. 388 | if(packetLength > 1280) { 389 | bytesToCopyFromOriginalPacket = 1280 - headerLength; 390 | packetLength = headerLength + bytesToCopyFromOriginalPacket; 391 | } 392 | 393 | return packetLength; 394 | } 395 | 396 | static void generateICMPBlockedPacketIPv6(void* originalPacket, size_t originalPacketLength, bool useLocalHost, void *icmpPacket) { 397 | // Initialize header for the original packet 398 | IPv6Header *originalIPHeader = (IPv6Header*) originalPacket; 399 | 400 | // Calculate length variables 401 | UINT16 headerLength = sizeof(IPv6Header) + sizeof(ICMPHeader); 402 | UINT16 packetLength = (UINT16)getICMPBlockedPacketSizeIPv6(originalPacketLength); 403 | UINT16 bytesToCopyFromOriginalPacket = packetLength - headerLength; 404 | 405 | // Initialize IPv6 header 406 | IPv6Header *ipHeader = (IPv6Header*) icmpPacket; 407 | ipHeader->Version = IPv6; 408 | ipHeader->Length = sizeof(ICMPHeader) + bytesToCopyFromOriginalPacket; 409 | ipHeader->NextHdr = PROTOCOL_ICMPv6; 410 | ipHeader->HopLimit = 128; 411 | 412 | // Use localhost as source and destination to bypass the windows firewall. 413 | if(useLocalHost) { 414 | ipHeader->SrcAddr[3] = IPv6_LOCALHOST_PART4_NETWORK_ORDER; // loopback address ::1 415 | ipHeader->DstAddr[3] = IPv6_LOCALHOST_PART4_NETWORK_ORDER; // loopback address ::1 416 | } else { 417 | RtlCopyMemory(ipHeader->SrcAddr, originalIPHeader->DstAddr, sizeof(originalIPHeader->SrcAddr)); // Source becomes destination. 418 | RtlCopyMemory(ipHeader->DstAddr, originalIPHeader->SrcAddr, sizeof(originalIPHeader->DstAddr)); // Destination becomes source. 419 | } 420 | 421 | ICMPHeader *icmpHeader = (ICMPHeader*) ((UINT8*)icmpPacket + sizeof(IPv6Header)); 422 | icmpHeader->Type = ICMPV6_CODE_DESTINATION_UNREACHABLE; 423 | icmpHeader->Code = ICMPV6_CODE_DU_PORT_UNREACHABLE; // the only code that closes the UDP connection on Windows 10. 424 | 425 | // Calculate checksum for the original packet and copy it in the icmp body. 426 | calcIPv6Checksum(originalPacket, originalPacketLength, true); 427 | RtlCopyMemory((UINT8*)icmpHeader + sizeof(ICMPHeader), originalPacket, bytesToCopyFromOriginalPacket); 428 | 429 | // Calculate checksum for the icmp packet 430 | calcIPv6Checksum(icmpPacket, packetLength, true); 431 | } 432 | 433 | static NTSTATUS sendICMPBlockedPacket(PortmasterPacketInfo* packetInfo, void *originalPacket, size_t originalPacketLength, bool useLocalHost) { 434 | size_t packetLength = 0; 435 | void *icmpPacket = NULL; 436 | HANDLE handle = NULL; 437 | if(packetInfo->ipV6) { 438 | packetLength = getICMPBlockedPacketSizeIPv6(originalPacketLength); 439 | icmpPacket = portmasterMalloc(packetLength, false); 440 | generateICMPBlockedPacketIPv6(originalPacket, originalPacketLength, useLocalHost, icmpPacket); 441 | handle = injectV6Blocked; 442 | } else { 443 | packetLength = getICMPBlockedPacketSizeIPv4(originalPacket, originalPacketLength); 444 | icmpPacket = portmasterMalloc(packetLength, false); 445 | generateICMPBlockedPacketIPv4(originalPacket, originalPacketLength, useLocalHost, icmpPacket); 446 | handle = injectV4Blocked; 447 | } 448 | 449 | // Reverse direction and inject packet 450 | UINT8 injectDirection = packetInfo->direction == DIRECTION_INBOUND ? DIRECTION_OUTBOUND : DIRECTION_INBOUND; 451 | if(useLocalHost) { 452 | injectDirection = DIRECTION_OUTBOUND; 453 | } 454 | NTSTATUS status = injectPacketWithHandle(handle, packetInfo, injectDirection, icmpPacket, packetLength); // this call will free the packet even if the inject fails 455 | if (!NT_SUCCESS(status)) { 456 | ERR("sendICMPBlockedPacket -> FwpsInjectNetworkSendAsync or FwpsInjectNetworkReceiveAsync returned %d", status); 457 | } 458 | 459 | return status; 460 | } 461 | 462 | static NTSTATUS sendTCPResetPacket(PortmasterPacketInfo* packetInfo, void* originalPacket, size_t originalPacketLength) { 463 | // Only TCP is supported 464 | if(packetInfo->protocol != PROTOCOL_TCP) { 465 | return STATUS_NOT_SUPPORTED; // Not TCP 466 | } 467 | 468 | size_t packetLength = 0; 469 | void *tcpResetPacket = NULL; 470 | HANDLE handle = NULL; 471 | // Generate reset packet 472 | if(packetInfo->ipV6) { 473 | packetLength = getTCPResetPacketSizeIPv6(); 474 | tcpResetPacket = portmasterMalloc(packetLength, false); 475 | generateTCPResetPacketIPv6(originalPacket, originalPacketLength, tcpResetPacket); 476 | handle = injectV6Blocked; 477 | } else { 478 | packetLength = getTCPResetPacketSizeIPv4(); 479 | tcpResetPacket = portmasterMalloc(packetLength, false); 480 | generateTCPResetPacketIPv4(originalPacket, originalPacketLength, tcpResetPacket); 481 | handle = injectV4Blocked; 482 | } 483 | 484 | // Reverse direction and inject packet 485 | UINT8 injectDirection = packetInfo->direction == DIRECTION_INBOUND ? DIRECTION_OUTBOUND : DIRECTION_INBOUND; 486 | NTSTATUS status = injectPacketWithHandle(handle, packetInfo, injectDirection, tcpResetPacket, packetLength); // this call will free the packet even if the inject fails 487 | 488 | if(!NT_SUCCESS(status)) { 489 | ERR("send_icmp_blocked_packet ipv4 -> FwpsInjectNetworkSendAsync or FwpsInjectNetworkReceiveAsync returned %d", status); 490 | } 491 | 492 | return status; 493 | } 494 | 495 | NTSTATUS sendBlockPacket(PortmasterPacketInfo* packetInfo, void* originalPacket, size_t originalPacketLength) { 496 | if(packetInfo->protocol == PROTOCOL_TCP) { 497 | return sendTCPResetPacket(packetInfo, originalPacket, originalPacketLength); 498 | } else { // Everything else 499 | return sendICMPBlockedPacket(packetInfo, originalPacket, originalPacketLength, true); 500 | } 501 | } 502 | 503 | NTSTATUS sendBlockPacketFromCallout(PortmasterPacketInfo* packetInfo, PNET_BUFFER nb, size_t ipHeaderSize) { 504 | NTSTATUS status = STATUS_SUCCESS; 505 | 506 | if (!packetInfo || !nb || ipHeaderSize == 0) { 507 | ERR("Invalid parameters"); 508 | return status; 509 | } 510 | 511 | // Inbound traffic requires special treatment - dafuq? 512 | if (packetInfo->direction == DIRECTION_INBOUND) { 513 | status = NdisRetreatNetBufferDataStart(nb, (ULONG)ipHeaderSize, 0, NULL); 514 | if (!NT_SUCCESS(status)) { 515 | ERR("failed to retreat net buffer data start"); 516 | return status; 517 | } 518 | } 519 | 520 | // Create new Packet -> wrap it in new nb, so we don't need to shift this nb back. 521 | void* packet = NULL; 522 | size_t packetLength = 0; 523 | status = copyPacketDataFromNB(nb, 0, &packet, &packetLength); 524 | if (!NT_SUCCESS(status)) { 525 | ERR("copyPacketDataFromNB 3: %d", status); 526 | return status; 527 | } 528 | // Now data should contain a full blown packet 529 | 530 | // In order to be as clean as possible, we shift back nb, even though it may not be necessary. 531 | if (packetInfo->direction == DIRECTION_INBOUND) { 532 | NdisAdvanceNetBufferDataStart(nb, (ULONG)ipHeaderSize, 0, NULL); 533 | } 534 | 535 | // Now we can send the RST (for TCP) or ICMP (for UDP) packet 536 | status = sendBlockPacket(packetInfo, packet, packetLength); 537 | portmasterFree(packet); 538 | return status; 539 | } 540 | 541 | void redirectPacket(PortmasterPacketInfo *packetInfo, PortmasterPacketInfo *redirInfo, void *packet, size_t packetLength, bool dns) { 542 | // sanity check 543 | if (!packetInfo || !redirInfo || !packet || packetLength == 0) { 544 | ERR("Invalid parameters"); 545 | return; 546 | } 547 | 548 | INFO("About to modify headers for %s", printPacketInfo(packetInfo)); 549 | INFO("Packet starts at 0p%p with %u bytes", packet, packetLength); 550 | 551 | UINT16 destinationPort = PORT_PM_SPN_ENTRY_NBO; // Port 717 in Network Byte Order! 552 | if(dns) { 553 | destinationPort = PORT_DNS_NBO; // Port 53 in Network Byte Order! 554 | } 555 | 556 | // Modify headers 557 | if (packetInfo->ipV6 == 0) { // IPv4 558 | size_t ipHeaderLength = calcIPv4HeaderSize(packet, packetLength); 559 | if (ipHeaderLength > 0) { // IPv4 Header 560 | IPv4Header *ipHeader = (IPv4Header*) packet; 561 | 562 | if (packetInfo->direction == DIRECTION_OUTBOUND) { 563 | ipHeader->DstAddr = RtlUlongByteSwap(packetInfo->localIP[0]); 564 | // IP_LOCALHOST is rejected by Windows Networkstack (nbl-status 0xc0000207, "STATUS_INVALID_ADDRESS_COMPONENT" 565 | // Problem might be switching Network scope from "eth0" to "lo" 566 | // Instead, just redir to the address the packet came from 567 | } else { 568 | ipHeader->SrcAddr = RtlUlongByteSwap(redirInfo->remoteIP[0]); 569 | } 570 | 571 | // TCP 572 | if (ipHeader->Protocol == PROTOCOL_TCP && packetLength >= ipHeaderLength + 20 /* TCP Header */) { 573 | TCPHeader *tcpHeader = (TCPHeader*) ((UINT8*)packet + ipHeaderLength); 574 | 575 | if (packetInfo->direction == DIRECTION_OUTBOUND) { 576 | tcpHeader->DstPort = destinationPort; // SPN or DNS port 577 | } else { 578 | tcpHeader->SrcPort= RtlUshortByteSwap(redirInfo->remotePort); 579 | } 580 | 581 | // UDP 582 | } else if (ipHeader->Protocol == PROTOCOL_UDP && packetLength >= ipHeaderLength + 8 /* UDP Header */) { 583 | UDPHeader *udpHeader = (UDPHeader*) ((UINT8*)packet + ipHeaderLength); 584 | 585 | if (packetInfo->direction == DIRECTION_OUTBOUND) { 586 | udpHeader->DstPort = destinationPort; // SPN or DNS port 587 | } else { 588 | udpHeader->SrcPort= RtlUshortByteSwap(redirInfo->remotePort); 589 | } 590 | 591 | } else { //Neither UDP nor TCP -> We can only redirect UDP or TCP -> drop the rest 592 | portmasterFree(packet); 593 | WARN("Portmaster issued redirect for Non UDP or TCP Packet:"); 594 | WARN("%s", printPacketInfo(packetInfo)); 595 | return; 596 | } 597 | } else { // not enough data for IPv4 Header 598 | portmasterFree(packet); 599 | WARN("IPv4 Packet too small:"); 600 | WARN("%s", printPacketInfo(packetInfo)); 601 | return; 602 | } 603 | } else { // IPv6 604 | size_t ipHeaderLength = calcIPv6HeaderSize(packet, packetLength, NULL); 605 | if (ipHeaderLength > 0) { // IPv6 Header 606 | IPv6Header *ipHeader = (IPv6Header*) packet; 607 | 608 | 609 | if (packetInfo->direction == DIRECTION_OUTBOUND) { 610 | for (int i = 0; i < 4; i++) { 611 | ipHeader->DstAddr[i]= RtlUlongByteSwap(packetInfo->localIP[i]); 612 | } 613 | } else { 614 | for (int i = 0; i < 4; i++) { 615 | ipHeader->SrcAddr[i]= RtlUlongByteSwap(redirInfo->remoteIP[i]); 616 | } 617 | } 618 | 619 | // TCP 620 | if (ipHeader->NextHdr == PROTOCOL_TCP && packetLength >= ipHeaderLength + 20 /* TCP Header */) { 621 | TCPHeader* tcpHeader = (TCPHeader*) ((UINT8*)packet + ipHeaderLength); 622 | 623 | if (packetInfo->direction == DIRECTION_OUTBOUND) { 624 | tcpHeader->DstPort = destinationPort; // SPN or DNS port 625 | } else { 626 | tcpHeader->SrcPort= RtlUshortByteSwap(redirInfo->remotePort); 627 | } 628 | 629 | // UDP 630 | } else if (ipHeader->NextHdr == PROTOCOL_UDP && packetLength >= ipHeaderLength + 8 /* UDP Header */) { 631 | UDPHeader* udpHeader = (UDPHeader*) ((UINT8*)packet + ipHeaderLength); 632 | 633 | if (packetInfo->direction == DIRECTION_OUTBOUND) { 634 | udpHeader->DstPort = destinationPort; // SPN or DNS port 635 | } else { 636 | udpHeader->SrcPort= RtlUshortByteSwap(redirInfo->remotePort); 637 | } 638 | 639 | } else { // Neither UDP nor TCP -> We can only redirect UDP or TCP -> drop the rest 640 | portmasterFree(packet); 641 | WARN("Portmaster issued redirect for Non UDP or TCP Packet:"); 642 | WARN("%s", printPacketInfo(packetInfo)); 643 | return; 644 | } 645 | } else { // not enough data for IPv6 Header 646 | portmasterFree(packet); 647 | WARN("IPv6 Packet too small:"); 648 | WARN("%s", printPacketInfo(packetInfo)); 649 | return; 650 | } 651 | } 652 | INFO("Headers modified"); 653 | 654 | // Fix checksums, including TCP/UDP. 655 | if (!packetInfo->ipV6) { 656 | calcIPv4Checksum(packet, packetLength, true); 657 | } else { 658 | calcIPv6Checksum(packet, packetLength, true); 659 | } 660 | 661 | // re-inject ... 662 | 663 | // Reset routing compartment ID, as we are changing where this is going to. 664 | // This necessity is unconfirmed. 665 | // Experience shows that using the compartment ID can sometimes cause errors. 666 | // It seems safer to always use UNSPECIFIED_COMPARTMENT_ID. 667 | // packetInfo->compartmentId = UNSPECIFIED_COMPARTMENT_ID; 668 | NTSTATUS status = injectPacket(packetInfo, packetInfo->direction, packet, packetLength); // this call will free the packet even if the inject fails 669 | 670 | if (!NT_SUCCESS(status)) { 671 | ERR("redir -> FwpsInjectNetworkSendAsync or FwpsInjectNetworkReceiveAsync returned %d", status); 672 | } 673 | } 674 | 675 | void redirectPacketFromCallout(PortmasterPacketInfo *packetInfo, PortmasterPacketInfo *redirInfo, PNET_BUFFER nb, size_t ipHeaderSize, bool dns) { 676 | // sanity check 677 | if (!redirInfo) { 678 | ERR("redirInfo is NULL!"); 679 | } 680 | if (!packetInfo || !redirInfo || !nb || ipHeaderSize == 0) { 681 | ERR("Invalid parameters"); 682 | return; 683 | } 684 | 685 | // DEBUG: print its TCP 4-tuple 686 | INFO("Handling redir for %s", printPacketInfo(packetInfo)); 687 | 688 | //Inbound traffic requires special treatment - dafuq? 689 | if (packetInfo->direction == DIRECTION_INBOUND) { 690 | NTSTATUS status = NdisRetreatNetBufferDataStart(nb, (ULONG)ipHeaderSize, 0, NULL); 691 | if (!NT_SUCCESS(status)) { 692 | ERR("failed to retreat net buffer data start"); 693 | return; 694 | } 695 | } 696 | 697 | //Create new Packet -> wrap it in new nb, so we don't need to shift this nb back. 698 | size_t packetLength = 0; 699 | void* packet = NULL; 700 | NTSTATUS status = copyPacketDataFromNB(nb, 0, &packet, &packetLength); 701 | if (!NT_SUCCESS(status)) { 702 | ERR("copy_packet_data_from_nb 3: %d", status); 703 | return; 704 | } 705 | //Now data should contain a full blown packet 706 | 707 | // In order to be as clean as possible, we shift back nb, even though it may not be necessary. 708 | if (packetInfo->direction == DIRECTION_INBOUND) { 709 | NdisAdvanceNetBufferDataStart(nb, (ULONG)ipHeaderSize, 0, NULL); 710 | } 711 | redirectPacket(packetInfo, redirInfo, packet, packetLength, dns); 712 | 713 | } 714 | 715 | static void freeAfterInject(void *context, NET_BUFFER_LIST *nbl, BOOLEAN dispatch_level) { 716 | UNREFERENCED_PARAMETER(dispatch_level); 717 | 718 | // Sanity check. 719 | if (!nbl) { 720 | ERR("Invalid parameters"); 721 | return; 722 | } 723 | 724 | #ifdef DEBUG_ON 725 | // Check for NBL errors. 726 | { 727 | NDIS_STATUS status; 728 | status = NET_BUFFER_LIST_STATUS(nbl); 729 | if (status == STATUS_SUCCESS) { 730 | INFO("injection success: nbl_status=0x%x, %s", NET_BUFFER_LIST_STATUS(nbl), printIpv4Packet(context)); 731 | } else { 732 | // Check here for status codes: http://errorco.de/win32/ntstatus-h/ 733 | ERR("injection failure: nbl_status=0x%x, %s", NET_BUFFER_LIST_STATUS(nbl), printIpv4Packet(context)); 734 | } 735 | } 736 | #endif // DEBUG 737 | 738 | // Free allocated NBL/Mdl memory. 739 | PNET_BUFFER nb = NET_BUFFER_LIST_FIRST_NB(nbl); 740 | PMDL mdl = NET_BUFFER_FIRST_MDL(nb); 741 | IoFreeMdl(mdl); 742 | FwpsFreeNetBufferList(nbl); 743 | 744 | // Free packet, which is passed as context. 745 | if (context != NULL) { 746 | portmasterFree(context); 747 | } 748 | } 749 | -------------------------------------------------------------------------------- /pm_kext/src/pm_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: pm_utils.c 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains implementation of utility-functions 7 | * 8 | * Scope: Kernelmode 9 | */ 10 | 11 | #include "pm_utils.h" 12 | #include "pm_debug.h" 13 | 14 | /* 15 | * PORTMASTER malloc/free. 16 | */ 17 | 18 | void *portmasterMalloc(size_t size, bool paged) { 19 | POOL_TYPE poolFlag = (paged ? PagedPool : NonPagedPool); 20 | if (size == 0) { 21 | return NULL; 22 | } 23 | // ExAllocatePoolWithTag is deprecated but there is no working (tested) alternative for it in the old Windows versions 24 | // ExAllocatePoolZero -> complies but crashes the kernel 25 | // ExAllocatePool2 -> available with Windows 10, version 2004 and after (release around 2020) 26 | #pragma warning(suppress : 4996) 27 | void *pv = ExAllocatePoolWithTag(poolFlag, size, PORTMASTER_TAG); 28 | if (pv != 0) { 29 | RtlZeroMemory(pv, size); 30 | } 31 | return pv; 32 | } 33 | 34 | void portmasterFree(void *ptr) { 35 | if (ptr != NULL) { 36 | ExFreePoolWithTag(ptr, PORTMASTER_TAG); 37 | } 38 | } 39 | 40 | bool isIPv4Loopback(UINT32 addr) { 41 | return (addr & IPv4_LOCALHOST_NET_MASK) == IPv4_LOCALHOST_NET; 42 | } 43 | 44 | bool isIPv6Loopback(UINT32 *addr) { 45 | return addr[0] == 0 && 46 | addr[1] == 0 && 47 | addr[2] == 0 && 48 | addr[3] == IPv6_LOCALHOST_PART4; 49 | } 50 | 51 | bool isPacketLoopback(PortmasterPacketInfo *packet) { 52 | if(packet->ipV6) { 53 | return isIPv6Loopback(packet->remoteIP); 54 | } else { 55 | return isIPv4Loopback(packet->remoteIP[0]); 56 | } 57 | } 58 | 59 | /** 60 | * @brief Compares two PORTMASTER_PACKET_INFO for full equality 61 | * 62 | * @par a = PORTMASTER_PACKET_INFO to compare 63 | * @par b = PORTMASTER_PACKET_INFO to compare 64 | * @return equality (bool as int) 65 | * 66 | */ 67 | bool compareFullPacketInfo(PortmasterPacketInfo *a, PortmasterPacketInfo *b) { 68 | // IP#, Protocol 69 | if (a->ipV6 != b->ipV6) { 70 | return false; 71 | } 72 | if (a->protocol != b->protocol) { 73 | return false; 74 | } 75 | 76 | // Ports 77 | if (a->localPort != b->localPort) { 78 | return false; 79 | } 80 | if (a->remotePort != b->remotePort) { 81 | return false; 82 | } 83 | 84 | // IPs 85 | for (int i = 0; i < 4; i++) { 86 | if (a->localIP[i] != b->localIP[i]) { 87 | return false; 88 | } 89 | if (a->remoteIP[i] != b->remoteIP[i]) { 90 | return false; 91 | } 92 | } 93 | 94 | return true; 95 | } 96 | 97 | /** 98 | * @brief Compares two PORTMASTER_PACKET_INFO for local adress equality 99 | * 100 | * @par original = original PORTMASTER_PACKET_INFO to compare 101 | * @par current = new (of current packet) PORTMASTER_PACKET_INFO to compare 102 | * @return equality (bool as int) 103 | * 104 | */ 105 | bool compareReverseRedirPacketInfo(PortmasterPacketInfo *original, PortmasterPacketInfo *current) { 106 | // IP#, Protocol 107 | if (original->ipV6 != current->ipV6) { 108 | return false; 109 | } 110 | if (original->protocol != current->protocol) { 111 | return false; 112 | } 113 | 114 | // Ports 115 | if (original->localPort != current->localPort) { 116 | return false; 117 | } 118 | 119 | // IPs 120 | for (int i = 0; i < 4; i++) { 121 | if (original->localIP[i] != current->localIP[i]) { 122 | return false; 123 | } 124 | } 125 | 126 | // check local original IP (that we DNAT to) against the new remote IP 127 | // this is always the case for returning DNATed packets 128 | for (int i = 0; i < 4; i++) { 129 | if (original->localIP[i] != current->remoteIP[i]) { 130 | return false; 131 | } 132 | } 133 | 134 | return true; 135 | } 136 | 137 | /** 138 | * @brief Compares two PORTMASTER_PACKET_INFO for remote address equality 139 | * 140 | * @par a = PORTMASTER_PACKET_INFO to compare 141 | * @par b = PORTMASTER_PACKET_INFO to compare 142 | * @return equality (bool as int) 143 | * 144 | */ 145 | int compareRemotePacketInfo(PortmasterPacketInfo *a, PortmasterPacketInfo *b) { 146 | // IP#, Protocol 147 | if (a->ipV6 != b->ipV6) { 148 | return false; 149 | } 150 | if (a->protocol != b->protocol) { 151 | return false; 152 | } 153 | 154 | // Ports 155 | if (a->remotePort != b->remotePort) { 156 | return false; 157 | } 158 | 159 | // IPs 160 | for (int i = 0; i < 4; i++) { 161 | if (a->remoteIP[i] != b->remoteIP[i]) { 162 | return false; 163 | } 164 | } 165 | 166 | return true; 167 | } 168 | 169 | NTSTATUS copyIPv6(const FWPS_INCOMING_VALUES* inFixedValues, FWPS_FIELDS_OUTBOUND_IPPACKET_V6 idx, UINT32* ip) { 170 | // sanity check 171 | if (!inFixedValues || !ip) { 172 | ERR("Invalid parameters"); 173 | return STATUS_INVALID_PARAMETER; 174 | } 175 | 176 | // check type 177 | if (inFixedValues->incomingValue[idx].value.type != FWP_BYTE_ARRAY16_TYPE) { 178 | ERR("invalid IPv6 data type: 0x%X", inFixedValues->incomingValue[idx].value.type); 179 | ip[0] = ip[1] = ip[2] = ip[3] = 0; 180 | return STATUS_INVALID_PARAMETER; 181 | } 182 | 183 | // copy and swap 184 | UINT32* ipV6 = (UINT32*) inFixedValues->incomingValue[idx].value.byteArray16->byteArray16; 185 | for (int i = 0; i < 4; i++) { 186 | ip[i]= RtlUlongByteSwap(ipV6[i]); 187 | } 188 | 189 | return STATUS_SUCCESS; 190 | } 191 | -------------------------------------------------------------------------------- /pm_kext/src/verdict_cache.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Name: verdict_cache.c 3 | * 4 | * Owner: Safing ICS Technologies GmbH 5 | * 6 | * Description: Contains implementation of verdict cache. 7 | * Cache Algorithm: Least Recently Used (LRU). 8 | * 9 | * Scope: Kernelmode 10 | * (Userland for development) 11 | */ 12 | 13 | #define BUILD_ENV_DRIVER 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include "pm_kernel.h" 21 | #include "verdict_cache.h" 22 | #include "pm_debug.h" 23 | 24 | // https://troydhanson.github.io/uthash/userguide.html 25 | #define uthash_malloc(sz) portmasterMalloc(sz, false) 26 | #define uthash_free(ptr, sz) portmasterFree(ptr) 27 | #define uthash_fatal 28 | #define HASH_NO_STDINT 1 29 | #include "uthash.h" 30 | #pragma warning(push) 31 | #pragma warning(disable: 4127) // warning C4127: conditional expression is constant -> if generated by macro HASH_FIND 32 | 33 | typedef struct { 34 | UINT32 localIP[4]; 35 | UINT32 remoteIP[4]; 36 | UINT16 localPort; 37 | UINT16 remotePort; 38 | UINT8 protocol; 39 | } VerdictCacheKey; 40 | 41 | typedef struct VerdictCacheItem { 42 | UINT64 lastAccessed; 43 | VerdictCacheKey key; 44 | VerdictCacheKey redirectKey; 45 | 46 | PortmasterPacketInfo *packetInfo; 47 | verdict_t verdict; 48 | 49 | UINT64 rx; 50 | UINT64 tx; 51 | 52 | UT_hash_handle hh; 53 | UT_hash_handle hhRedirect; 54 | } VerdictCacheItem; 55 | 56 | #undef VerdictCache // previously defined as void 57 | typedef struct { 58 | VerdictCacheItem *map; 59 | VerdictCacheItem *mapRedirect; 60 | 61 | VerdictCacheItem *itemPool; 62 | UINT32 maxSize; 63 | UINT32 *freeItemIndexes; 64 | UINT32 numberOfFreeItems; 65 | 66 | KSPIN_LOCK lock; 67 | } VerdictCache; 68 | 69 | // Holds the number of accesses/modifications performed on the cache 70 | static UINT64 cacheAccessCounter = 0; 71 | 72 | static bool isKeyZero(VerdictCacheKey *key) { 73 | for(size_t i = 0; i < 4; i++) { 74 | if(key->localIP[i] != 0) { 75 | return false; 76 | } 77 | } 78 | 79 | for(size_t i = 0; i < 4; i++) { 80 | if(key->remoteIP[i] != 0) { 81 | return false; 82 | } 83 | } 84 | 85 | return key->localPort == 0 && 86 | key->remotePort == 0 && 87 | key->protocol == 0; 88 | } 89 | 90 | static VerdictCacheKey getCacheKey(PortmasterPacketInfo *info) { 91 | VerdictCacheKey key = {0}; 92 | memcpy(key.localIP, info->localIP, sizeof(UINT32) * 4); 93 | key.localPort = info->localPort; 94 | memcpy(key.remoteIP, info->remoteIP, sizeof(UINT32) * 4); 95 | key.remotePort = info->remotePort; 96 | key.protocol = info->protocol; 97 | return key; 98 | } 99 | 100 | static VerdictCacheKey getCacheKeyFromUpdateInfo(VerdictUpdateInfo *info) { 101 | VerdictCacheKey key = {0}; 102 | memcpy(key.localIP, info->localIP, sizeof(UINT32) * 4); 103 | key.localPort = info->localPort; 104 | memcpy(key.remoteIP, info->remoteIP, sizeof(UINT32) * 4); 105 | key.remotePort = info->remotePort; 106 | key.protocol = info->protocol; 107 | return key; 108 | } 109 | 110 | static VerdictCacheKey getCacheRedirectKey(PortmasterPacketInfo *info) { 111 | VerdictCacheKey key = {0}; 112 | memcpy(key.localIP, info->localIP, sizeof(UINT32) * 4); 113 | key.localPort = info->localPort; 114 | memcpy(key.remoteIP, info->localIP, sizeof(UINT32) * 4); 115 | key.remotePort = 0; 116 | key.protocol = info->protocol; 117 | return key; 118 | } 119 | 120 | /** 121 | * @brief Initializes the verdict cache 122 | * 123 | * @par maxSize = size of cache 124 | * @par verdict_cache = returns new VerdictCache 125 | * @return error code 126 | * 127 | */ 128 | int verdictCacheCreate(UINT32 maxSize, VerdictCache **verdictCache) { 129 | if (maxSize == 0) { 130 | return 1; 131 | } 132 | 133 | VerdictCache *newVerdictCache = portmasterMalloc(sizeof(VerdictCache), false); 134 | if (newVerdictCache == NULL) { 135 | return 1; 136 | } 137 | 138 | newVerdictCache->itemPool = portmasterMalloc(sizeof(VerdictCacheItem) * maxSize, false); 139 | if(newVerdictCache->itemPool == NULL) { 140 | portmasterFree(newVerdictCache); 141 | return 1; 142 | } 143 | 144 | newVerdictCache->numberOfFreeItems = maxSize; 145 | 146 | newVerdictCache->map = NULL; 147 | newVerdictCache->mapRedirect = NULL; 148 | newVerdictCache->maxSize = maxSize; 149 | 150 | KeInitializeSpinLock(&newVerdictCache->lock); 151 | *verdictCache = newVerdictCache; 152 | 153 | return 0; 154 | } 155 | 156 | /** 157 | * @brief Remove all items from verdict cache 158 | * 159 | * @par verdictCache = verdict_cache to use 160 | * @par freeData = callback function that is executed for each item before delete were the data of the item can be deleted 161 | * 162 | */ 163 | 164 | void verdictCacheClear(VerdictCache *verdictCache, void(*freeData)(PortmasterPacketInfo*, verdict_t)) { 165 | DEBUG("verdictCacheClear"); 166 | 167 | // Lock to check verdict cache. 168 | KLOCK_QUEUE_HANDLE lockHandle = {0}; 169 | KeAcquireInStackQueuedSpinLock(&verdictCache->lock, &lockHandle); 170 | 171 | HASH_CLEAR(hh, verdictCache->map); 172 | HASH_CLEAR(hhRedirect, verdictCache->mapRedirect); 173 | 174 | for(UINT32 i = 0; i < verdictCache->maxSize; i++) { 175 | VerdictCacheItem *item = &verdictCache->itemPool[i]; 176 | if(item->packetInfo != NULL) { 177 | freeData(item->packetInfo, item->verdict); 178 | } 179 | } 180 | 181 | memset(verdictCache->itemPool, 0, sizeof(VerdictCacheItem) * verdictCache->maxSize); 182 | verdictCache->numberOfFreeItems = verdictCache->maxSize; 183 | verdictCache->map = NULL; 184 | verdictCache->mapRedirect = NULL; 185 | KeReleaseInStackQueuedSpinLock(&lockHandle); 186 | } 187 | 188 | /** 189 | * @brief Tears down the verdict cache 190 | * 191 | * @par verdictCache = verdict cache to use 192 | * @return error code 193 | * 194 | */ 195 | int verdictCacheTeardown(VerdictCache *verdictCache, void(*freeData)(PortmasterPacketInfo*, verdict_t)) { 196 | if(verdictCache == NULL) { 197 | return 0; 198 | } 199 | 200 | verdictCacheClear(verdictCache, freeData); 201 | portmasterFree(verdictCache->itemPool); 202 | portmasterFree(verdictCache); 203 | return 0; 204 | } 205 | 206 | static VerdictCacheItem *getOldestAccessTimeItem(VerdictCache *verdictCache) { 207 | UINT64 oldestAccessNumber = cacheAccessCounter + 1; 208 | VerdictCacheItem *oldestItem = NULL; 209 | for(UINT32 i = 0; i < verdictCache->maxSize; i++) { 210 | VerdictCacheItem *current = &verdictCache->itemPool[i]; 211 | if(current->lastAccessed < oldestAccessNumber) { 212 | oldestAccessNumber = current->lastAccessed; 213 | oldestItem = current; 214 | } 215 | } 216 | return oldestItem; 217 | } 218 | 219 | static void resetItem(VerdictCache *verdictCache, VerdictCacheItem *item) { 220 | HASH_DELETE(hh, verdictCache->map, item); 221 | // Delete redirect only if the item is in the map 222 | if(!isKeyZero(&item->redirectKey)) { 223 | HASH_DELETE(hhRedirect, verdictCache->mapRedirect, item); 224 | } 225 | memset(item, 0, sizeof(VerdictCacheItem)); 226 | } 227 | 228 | static void verdictCacheUpdateFromItem(VerdictCache *verdictCache, VerdictCacheItem *item, verdict_t newVerdict) { 229 | verdict_t oldVerdict = item->verdict; 230 | 231 | if(oldVerdict != newVerdict) { 232 | // Remove old redirect 233 | if(oldVerdict == PORTMASTER_VERDICT_REDIR_DNS || oldVerdict == PORTMASTER_VERDICT_REDIR_TUNNEL) { 234 | if(!isKeyZero(&item->redirectKey)) { 235 | HASH_DELETE(hhRedirect, verdictCache->mapRedirect, item); 236 | memset(&item->hhRedirect, 0, sizeof(UT_hash_handle)); 237 | } 238 | memset(&item->redirectKey, 0, sizeof(VerdictCacheKey)); 239 | } 240 | 241 | // Set new verdict and create redirect if needed 242 | item->verdict = newVerdict; 243 | if(newVerdict == PORTMASTER_VERDICT_REDIR_DNS || newVerdict == PORTMASTER_VERDICT_REDIR_TUNNEL) { 244 | VerdictCacheKey redirectKey = getCacheRedirectKey(item->packetInfo); 245 | 246 | VerdictCacheItem *redirectItem = NULL; 247 | HASH_FIND(hhRedirect, verdictCache->mapRedirect, &redirectKey, sizeof(VerdictCacheKey), redirectItem); 248 | if(redirectItem == NULL) { 249 | item->redirectKey = redirectKey; 250 | HASH_ADD(hhRedirect, verdictCache->mapRedirect, redirectKey, sizeof(VerdictCacheKey), item); 251 | } 252 | } 253 | 254 | INFO("verdictCacheUpdate verdict updated %s: %d -> %d", printPacketInfo(item->packetInfo), oldVerdict, newVerdict); 255 | } 256 | } 257 | 258 | /** 259 | * @brief Update verdict for specific item in the cache 260 | * 261 | * @par verdictCache = verdict cache to use 262 | * @par item = item from the verdict cache 263 | * @par newVerdict = verdict to save 264 | */ 265 | int verdictCacheUpdate(VerdictCache *verdictCache, VerdictUpdateInfo *info) { 266 | if (verdictCache == NULL || info == NULL) { 267 | ERR("verdictCacheUpdate NULL pointer exception VerdictCache=0p%Xp, VerdictUpdateInfo=0p%Xp", verdictCache, info); 268 | return 1; 269 | } 270 | 271 | VerdictCacheItem *item = NULL; 272 | VerdictCacheKey key = getCacheKeyFromUpdateInfo(info); 273 | 274 | // Lock to check verdict cache. 275 | KLOCK_QUEUE_HANDLE lockHandle = {0}; 276 | KeAcquireInStackQueuedSpinLock(&verdictCache->lock, &lockHandle); 277 | 278 | int rc = 0; 279 | 280 | // Search for verdict 281 | HASH_FIND(hh, verdictCache->map, &key, sizeof(VerdictCacheKey), item); 282 | if(item == NULL) { 283 | ERR("verdictCacheUpdate failed to update verdict, item not found"); 284 | rc = 2; 285 | } 286 | 287 | // Update verdict 288 | if(rc == 0) { 289 | verdict_t newVerdict = info->verdict; 290 | verdictCacheUpdateFromItem(verdictCache, item, newVerdict); 291 | } 292 | 293 | KeReleaseInStackQueuedSpinLock(&lockHandle); 294 | 295 | return rc; 296 | } 297 | 298 | /** 299 | * @brief Adds verdict to cache 300 | * 301 | * @par verdictCache = verdict cache to use 302 | * @par packet_info = pointer to packet_info 303 | * @par verdict = verdict to save 304 | * @par removedPacketInfo = old packet info that was removed from the cache (can be NULL) 305 | * @return error code 306 | * 307 | */ 308 | int verdictCacheAdd(VerdictCache *verdictCache, PortmasterPacketInfo *packetInfo, verdict_t verdict, PortmasterPacketInfo **removedPacketInfo) { 309 | if (verdictCache == NULL || packetInfo == NULL || verdict == 0) { 310 | ERR("verdictCacheAdd NULL pointer exception verdictCache=0p%Xp, packetInfo=0p%Xp, verdict=0p%Xp ", verdictCache, packetInfo, verdict); 311 | return 1; 312 | } 313 | 314 | cacheAccessCounter++; 315 | 316 | VerdictCacheItem *newItem = NULL; 317 | VerdictCacheKey key = getCacheKey(packetInfo); 318 | 319 | // Lock to check verdict cache. 320 | KLOCK_QUEUE_HANDLE lockHandle = {0}; 321 | KeAcquireInStackQueuedSpinLock(&verdictCache->lock, &lockHandle); 322 | 323 | int rc = 0; 324 | HASH_FIND(hh, verdictCache->map, &key, sizeof(VerdictCacheKey), newItem); 325 | if(newItem != NULL) { 326 | // Already in 327 | INFO("verdictCacheAdd packet was already in the verdict cache. Updating the verdict..."); 328 | verdictCacheUpdateFromItem(verdictCache, newItem, verdict); 329 | rc = 3; 330 | } 331 | 332 | if(rc == 0) { 333 | if(verdictCache->numberOfFreeItems > 0) { 334 | newItem = &verdictCache->itemPool[verdictCache->maxSize - verdictCache->numberOfFreeItems]; 335 | verdictCache->numberOfFreeItems -= 1; 336 | } else { 337 | VerdictCacheItem *item = getOldestAccessTimeItem(verdictCache); 338 | if(item == NULL) { 339 | ERR("verdictCacheAdd failed to find free element"); 340 | rc = 2; 341 | } else { 342 | *removedPacketInfo = item->packetInfo; 343 | resetItem(verdictCache, item); 344 | newItem = item; 345 | } 346 | } 347 | } 348 | 349 | if(rc == 0) { 350 | // Add to verdict map 351 | newItem->key = key; 352 | newItem->packetInfo = packetInfo; 353 | newItem->verdict = verdict; 354 | newItem->lastAccessed = cacheAccessCounter; 355 | HASH_ADD(hh, verdictCache->map, key, sizeof(VerdictCacheKey), newItem); 356 | 357 | // Add to redirect map if needed 358 | if(verdict == PORTMASTER_VERDICT_REDIR_DNS || verdict == PORTMASTER_VERDICT_REDIR_TUNNEL) { 359 | VerdictCacheKey redirectKey = getCacheRedirectKey(packetInfo); 360 | 361 | // insert only if we dont have already item with the same key 362 | VerdictCacheItem *redirectItem = NULL; 363 | HASH_FIND(hhRedirect, verdictCache->mapRedirect, &redirectKey, sizeof(VerdictCacheKey), redirectItem); 364 | if(redirectItem == NULL) { 365 | newItem->redirectKey = redirectKey; 366 | HASH_ADD(hhRedirect, verdictCache->mapRedirect, redirectKey, sizeof(VerdictCacheKey), newItem); 367 | } 368 | } 369 | } 370 | KeReleaseInStackQueuedSpinLock(&lockHandle); 371 | return rc; 372 | } 373 | 374 | /** 375 | * @brief Checks packet for verdict 376 | * 377 | * @par verdict_cache = verdict_cache to use 378 | * @par packet_info = pointer to packet info 379 | * @return verdict 380 | * 381 | */ 382 | static verdict_t checkVerdict(VerdictCache *verdictCache, PortmasterPacketInfo *packetInfo) { 383 | if (verdictCache == NULL || packetInfo == NULL) { 384 | ERR("verdictCache 0p%xp or packet_info 0p%xp was null", verdictCache, packetInfo); 385 | return PORTMASTER_VERDICT_ERROR; 386 | } 387 | cacheAccessCounter++; 388 | 389 | if(verdictCache->map == NULL) { 390 | // no entries 391 | return PORTMASTER_VERDICT_GET; 392 | } 393 | 394 | VerdictCacheItem *item = NULL; 395 | VerdictCacheKey key = getCacheKey(packetInfo); 396 | HASH_FIND(hh, verdictCache->map, &key, sizeof(VerdictCacheKey), item); 397 | 398 | if(item == NULL) { 399 | return PORTMASTER_VERDICT_GET; 400 | } 401 | 402 | item->lastAccessed = cacheAccessCounter; 403 | return item->verdict; 404 | } 405 | 406 | /** 407 | * @brief Checks packet for reverse redirection 408 | * 409 | * @par verdict_cache = verdict_cache to use 410 | * @par packetInfo = pointer to packet info 411 | * @par redirInfo = double pointer to packet_info (return value) 412 | * @par verdict = pointer to verdict (return value) 413 | * @return error code 414 | * 415 | */ 416 | static verdict_t checkReverseRedirect(VerdictCache *verdictCache, PortmasterPacketInfo *packetInfo, PortmasterPacketInfo **redirInfo) { 417 | if (verdictCache == NULL || packetInfo == NULL || redirInfo == NULL) { 418 | return PORTMASTER_VERDICT_GET; 419 | } 420 | 421 | cacheAccessCounter++; 422 | 423 | if(verdictCache->mapRedirect == NULL) { 424 | // no entries 425 | return PORTMASTER_VERDICT_GET; 426 | } 427 | 428 | VerdictCacheItem *item = NULL; 429 | VerdictCacheKey key = getCacheRedirectKey(packetInfo); 430 | HASH_FIND(hhRedirect, verdictCache->mapRedirect, &key, sizeof(VerdictCacheKey), item); 431 | if(item == NULL) { 432 | return PORTMASTER_VERDICT_GET; 433 | } 434 | 435 | item->lastAccessed = cacheAccessCounter; 436 | *redirInfo = item->packetInfo; 437 | return item->verdict; 438 | } 439 | 440 | verdict_t verdictCacheGet(VerdictCache *verdictCache, PortmasterPacketInfo *packetInfo, PortmasterPacketInfo **redirInfo) { 441 | verdict_t verdict = PORTMASTER_VERDICT_GET; 442 | 443 | // Lock to check verdict cache. 444 | KLOCK_QUEUE_HANDLE lockHandle = {0}; 445 | KeAcquireInStackQueuedSpinLock(&verdictCache->lock, &lockHandle); 446 | 447 | if (packetInfo->direction == DIRECTION_INBOUND && 448 | (packetInfo->remotePort == PORT_PM_SPN_ENTRY || packetInfo->remotePort == PORT_DNS)) { 449 | verdict = checkReverseRedirect(verdictCache, packetInfo, redirInfo); 450 | 451 | // Verdicts returned by check_reverse_redir must only be 452 | // PORTMASTER_VERDICT_REDIR_DNS or PORTMASTER_VERDICT_REDIR_TUNNEL. 453 | if (verdict != PORTMASTER_VERDICT_REDIR_DNS && verdict != PORTMASTER_VERDICT_REDIR_TUNNEL) { 454 | verdict = PORTMASTER_VERDICT_GET; 455 | } 456 | } 457 | 458 | // Check verdict normally if we did not detect a packet that should be reverse DNAT-ed. 459 | if (verdict == PORTMASTER_VERDICT_GET) { 460 | verdict = checkVerdict(verdictCache, packetInfo); 461 | 462 | // If packet should be DNAT-ed set redirInfo to packetInfo. 463 | if (verdict == PORTMASTER_VERDICT_REDIR_DNS || verdict == PORTMASTER_VERDICT_REDIR_TUNNEL) { 464 | *redirInfo = packetInfo; 465 | } 466 | } 467 | 468 | KeReleaseInStackQueuedSpinLock(&lockHandle); 469 | return verdict; 470 | } 471 | 472 | int verdictCacheUpdateStats(VerdictCache *verdictCache, PortmasterPacketInfo *packetInfo, UINT64 payloadSize) { 473 | if(verdictCache == NULL) { 474 | return -1; 475 | } 476 | 477 | // Lock to check verdict cache. 478 | KLOCK_QUEUE_HANDLE lockHandle = {0}; 479 | KeAcquireInStackQueuedSpinLock(&verdictCache->lock, &lockHandle); 480 | 481 | if(verdictCache->map != NULL) { 482 | VerdictCacheItem *item = NULL; 483 | VerdictCacheKey key = getCacheKey(packetInfo); 484 | HASH_FIND(hh, verdictCache->map, &key, sizeof(VerdictCacheKey), item); 485 | 486 | if(item != NULL && payloadSize > 0) { 487 | if(packetInfo->direction == DIRECTION_INBOUND) { 488 | item->rx += payloadSize; 489 | } else { 490 | item->tx += payloadSize; 491 | } 492 | } 493 | } 494 | 495 | KeReleaseInStackQueuedSpinLock(&lockHandle); 496 | return 0; 497 | } 498 | 499 | int verdictCacheWriteBandwidthStats(VerdictCache *verdictCache, PortmasterConnection *connections, int size, UINT8 ipv6) { 500 | if(verdictCache == NULL) { 501 | return -1; 502 | } 503 | // Lock while reading all entries 504 | KLOCK_QUEUE_HANDLE lockHandle = {0}; 505 | KeAcquireInStackQueuedSpinLock(&verdictCache->lock, &lockHandle); 506 | int i = 0; 507 | for (VerdictCacheItem *item = verdictCache->map; item != NULL; item = item->hh.next) { 508 | // Caller buffer is already full 509 | if(i >= size) { 510 | break; 511 | } 512 | 513 | // We dont need empty entries 514 | if(item->rx == 0 && item->tx == 0) { 515 | continue; 516 | } 517 | 518 | VerdictCacheKey key = item->key; 519 | 520 | // Create the connection struct 521 | PortmasterConnection connection; 522 | memcpy(connection.localIP, &key.localIP, sizeof(UINT32) * 4); 523 | memcpy(connection.remoteIP, &key.remoteIP, sizeof(UINT32) * 4); 524 | connection.localPort = key.localPort; 525 | connection.remotePort = key.remotePort; 526 | connection.protocol = key.protocol; 527 | connection.ipV6 = ipv6; 528 | connection.receivedBytes = item->rx; 529 | connection.transmittedBytes = item->tx; 530 | 531 | // Set the value and increment index 532 | connections[i] = connection; 533 | i += 1; 534 | 535 | // Reset item 536 | item->rx = 0; 537 | item->tx = 0; 538 | } 539 | KeReleaseInStackQueuedSpinLock(&lockHandle); 540 | return i; 541 | } 542 | 543 | #pragma warning(pop) -------------------------------------------------------------------------------- /pm_kext/version.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #include "winres.h" 11 | #include "include/version.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | #pragma code_page(1252) 22 | 23 | #ifdef APSTUDIO_INVOKED 24 | ///////////////////////////////////////////////////////////////////////////// 25 | // 26 | // TEXTINCLUDE 27 | // 28 | 29 | 1 TEXTINCLUDE 30 | BEGIN 31 | "resource.h\0" 32 | END 33 | 34 | 2 TEXTINCLUDE 35 | BEGIN 36 | "#include ""winres.h""\r\n" 37 | "\0" 38 | END 39 | 40 | 3 TEXTINCLUDE 41 | BEGIN 42 | "\r\n" 43 | "\0" 44 | END 45 | 46 | #endif // APSTUDIO_INVOKED 47 | 48 | 49 | ///////////////////////////////////////////////////////////////////////////// 50 | // 51 | // Version 52 | // 53 | 54 | VS_VERSION_INFO VERSIONINFO 55 | FILEVERSION PM_VERSION 56 | PRODUCTVERSION PM_VERSION 57 | FILEFLAGSMASK 0x3fL 58 | #ifdef _DEBUG 59 | FILEFLAGS 0x1L 60 | #else 61 | FILEFLAGS 0x0L 62 | #endif 63 | FILEOS 0x40004L 64 | FILETYPE VFT_DRV 65 | FILESUBTYPE 0x0L 66 | BEGIN 67 | BLOCK "StringFileInfo" 68 | BEGIN 69 | BLOCK "040904b0" 70 | BEGIN 71 | VALUE "CompanyName", COMPANY_NAME 72 | VALUE "FileDescription", "Portmaster Windows Kernel Extension Driver" 73 | VALUE "FileVersion", PM_VERSION_STR 74 | VALUE "LegalCopyright", LEGAL_COPYWRITE 75 | VALUE "OriginalFilename", "PortmasterKext64.sys" 76 | VALUE "ProductName", "Portmaster Windows Kernel Extension" 77 | VALUE "ProductVersion", PM_VERSION_STR 78 | END 79 | END 80 | BLOCK "VarFileInfo" 81 | BEGIN 82 | VALUE "Translation", 0x409, 1200 83 | END 84 | END 85 | 86 | #endif // English (United States) resources 87 | ///////////////////////////////////////////////////////////////////////////// 88 | 89 | 90 | 91 | #ifndef APSTUDIO_INVOKED 92 | ///////////////////////////////////////////////////////////////////////////// 93 | // 94 | // Generated from the TEXTINCLUDE 3 resource. 95 | // 96 | 97 | 98 | ///////////////////////////////////////////////////////////////////////////// 99 | #endif // not APSTUDIO_INVOKED 100 | 101 | -------------------------------------------------------------------------------- /portmaster-windows-kext.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32811.315 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pm_kext", "pm_kext\pm_kext.vcxproj", "{4C60792A-0797-4C9F-AB77-19EB7BD807BB}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|ARM64 = Debug|ARM64 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|ARM64 = Release|ARM64 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Debug|ARM64.ActiveCfg = Debug|ARM64 19 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Debug|ARM64.Build.0 = Debug|ARM64 20 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Debug|ARM64.Deploy.0 = Debug|ARM64 21 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Debug|x64.ActiveCfg = Debug|x64 22 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Debug|x64.Build.0 = Debug|x64 23 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Debug|x86.ActiveCfg = Debug|x64 24 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Debug|x86.Build.0 = Debug|x64 25 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Debug|x86.Deploy.0 = Debug|x64 26 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Release|ARM64.ActiveCfg = Release|ARM64 27 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Release|ARM64.Build.0 = Release|ARM64 28 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Release|ARM64.Deploy.0 = Release|ARM64 29 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Release|x64.ActiveCfg = Release|x64 30 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Release|x64.Build.0 = Release|x64 31 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Release|x86.ActiveCfg = Release|x64 32 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Release|x86.Build.0 = Release|x64 33 | {4C60792A-0797-4C9F-AB77-19EB7BD807BB}.Release|x86.Deploy.0 = Release|x64 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {EB6819CA-E0E9-4789-91F4-C2E21CF0268A} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /release/.gitignore: -------------------------------------------------------------------------------- 1 | PortmasterKext 2 | PortmasterKext*.cab 3 | PortmasterKext*.zip 4 | Signable* 5 | Signed* 6 | dist 7 | verpatch.exe 8 | -------------------------------------------------------------------------------- /release/PortmasterKext.ddf: -------------------------------------------------------------------------------- 1 | ;*** PortmasterKext.ddf 2 | .OPTION EXPLICIT ; Generate errors 3 | .Set CabinetFileCountThreshold=0 4 | .Set FolderFileCountThreshold=0 5 | .Set FolderSizeThreshold=0 6 | .Set MaxCabinetSize=0 7 | .Set MaxDiskFileCount=0 8 | .Set MaxDiskSize=0 9 | .Set CompressionType=MSZIP 10 | .Set Cabinet=on 11 | .Set Compress=on 12 | 13 | ;Specify file name for new cab file 14 | .Set CabinetNameTemplate=PortmasterKext.cab 15 | 16 | ; Specify the subdirectory for the files. 17 | ; Your cab file should not have files at the root level, 18 | ; and each driver package must be in a separate subfolder. 19 | .Set DestinationDir=PortmasterKext 20 | 21 | ;Specify files to be included in cab file 22 | .\PortmasterKext\amd64\PortmasterKext64.inf 23 | .\PortmasterKext\amd64\PortmasterKext64.sys 24 | .\PortmasterKext\amd64\PortmasterKext64.pdb -------------------------------------------------------------------------------- /release/README.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | This directory holds everything to go through the Microsoft signing procedure. 4 | You need an EV certificate. 5 | Get one here: https://docs.microsoft.com/en-us/windows-hardware/drivers/dashboard/get-a-code-signing-certificate 6 | (Important: certificate may be cheaper if you buy them through the above link) 7 | 8 | ### Prepare 9 | 10 | - Compile the driver. See main README. 11 | - You need: 12 | - `makecab` 13 | - `signtool` from Win SDK 8.1 or newer (for the `remove` command) 14 | 15 | ### Update version 16 | 17 | The version is defined in `Version.props` in the repo root. 18 | 19 | ### Pre-Packaging 20 | 21 | - Run `release_prepackage.bat` to: 22 | - copy all the needed files 23 | - remove existing signatures (we want the Microsoft signature to be the primary one for max. compatibility) 24 | - package everything for signing by Microsoft 25 | - calls `release_set_metadata.bat`, which you can create to set extra file version and metadata. 26 | 27 | - Verify that the `PortmasterKext` dir looks like this: 28 | 29 | PortmasterKext 30 | └── amd64 31 | ├── PortmasterKext64.inf 32 | ├── PortmasterKext64.pdb 33 | └── PortmasterKext64.sys 34 | 35 | - Sign the `.cab` file with your EV Code Signing Cert 36 | 37 | ### Let Microsoft Sign 38 | 39 | - Go to https://partner.microsoft.com/en-us/dashboard/hardware/driver/New 40 | - Enter "PortmasterKext v1.1.1 #1" as the product name 41 | - Upload `PortmasterKext.cab` 42 | - Select the Windows 10 versions that you compiled and tested on 43 | - Wait for the process to finish, download the `.zip`, extract it and rename to folder from `Signed_xxx` to `Signed`. 44 | 45 | ### Finalize and Add Own Signatures 46 | 47 | - Run `release_finalize.bat` to: 48 | - copy the signed `.sys` to the Portmaster dist directory 49 | - Sign the `.sys` files with your EV Code Signing Cert (for additional transparency) [Optional] 50 | 51 | # Relevant Documentation 52 | 53 | The available options for signing drivers for multiple Windows versions is here: 54 | https://docs.microsoft.com/en-gb/windows-hardware/drivers/dashboard/get-drivers-signed-by-microsoft-for-multiple-windows-versions 55 | 56 | What they do not mention, is that `Universal` drivers will work on more Windows versions. The above process uses `attestation signing`, but still works on Win7, Win8.1 and Win10. 57 | -------------------------------------------------------------------------------- /release/release_finalize.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set DISTDIR=dist\windows_amd64\kext 3 | set SIGNEDDIR=Signed\drivers\PortmasterKext 4 | 5 | echo. 6 | echo ===== 7 | echo copying files ... 8 | mkdir %DISTDIR% 9 | echo copy %SIGNEDDIR%\PortmasterKext64.sys %DISTDIR%\portmaster-kext_vX-X-X.sys 10 | copy %SIGNEDDIR%\PortmasterKext64.sys %DISTDIR%\portmaster-kext_vX-X-X.sys 11 | 12 | echo. 13 | echo ===== 14 | echo OPTIONAL: 15 | echo YOUR TURN: sign .sys (add your sig for additional transparency) 16 | echo use something along the lines of: 17 | echo. 18 | echo signtool sign /sha1 C2CBB3A0256A157FEB08B661D72BF490B68724C4 /tr http://timestamp.digicert.com /td sha256 /fd sha256 /a /as %DISTDIR%\portmaster-kext_vX-X-X.sys 19 | echo. 20 | 21 | echo. 22 | echo ===== 23 | echo YOUR TURN: rename %DISTDIR%\portmaster-kext-vX-X-X.sys to correct versions! 24 | echo DONE! 25 | echo. 26 | -------------------------------------------------------------------------------- /release/release_prepackage.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set CABDIR=PortmasterKext 3 | set INSTALL_WDDK_AMD64=..\install\WDDK\x64\Release 4 | set INSTALL_DLL_AMD64=..\install\DLL\x64\Release 5 | set DISTDIR=dist\windows_amd64\kext 6 | 7 | echo. 8 | echo ===== 9 | echo checking if build is set to release ... 10 | findstr /R /C:"^#define DEBUG_ON" ..\include\pm_debug.h 11 | if %ERRORLEVEL% equ 0 echo BUILD IS SET TO DEBUG! && exit /b 12 | 13 | echo. 14 | echo ===== 15 | echo removing old files ... 16 | rmdir /Q /S %CABDIR% 17 | del PortmasterKext.cab 18 | 19 | echo. 20 | echo ===== 21 | echo copying files ... 22 | mkdir %CABDIR%\amd64 23 | copy %INSTALL_WDDK_AMD64%\pm_kernel_x64.sys %CABDIR%\amd64\PortmasterKext64.sys 24 | copy %INSTALL_WDDK_AMD64%\pm_kernel_x64.pdb %CABDIR%\amd64\PortmasterKext64.pdb 25 | copy %INSTALL_WDDK_AMD64%\PortmasterKext64.inf %CABDIR%\amd64\PortmasterKext64.inf 26 | copy %CABDIR%\amd64\PortmasterKext64.pdb %DISTDIR%\portmaster-kext_vX-X-X.pdb 27 | echo. 28 | echo ===== 29 | echo removing existing signatures ... 30 | signtool remove /s %CABDIR%\amd64\PortmasterKext64.sys 31 | 32 | echo. 33 | echo ===== 34 | echo creating .cab ... 35 | MakeCab /f PortmasterKext.ddf 36 | 37 | echo. 38 | echo ===== 39 | echo cleaning up ... 40 | del setup.inf 41 | del setup.rpt 42 | move disk1\PortmasterKext.cab PortmasterKext.cab 43 | rmdir disk1 44 | 45 | echo. 46 | echo ===== 47 | echo YOUR TURN: sign the .cab 48 | echo use something along the lines of: 49 | echo. 50 | echo signtool sign /sha1 C2CBB3A0256A157FEB08B661D72BF490B68724C4 /tr http://timestamp.digicert.com /td sha256 /fd sha256 /a PortmasterKext.cab 51 | echo. 52 | -------------------------------------------------------------------------------- /release_build.bat: -------------------------------------------------------------------------------- 1 | echo Release build of the Kernel Extenion 2 | msbuild /t:Clean /p:Configuration=Release /p:Platform=x64 portmaster-windows-kext.sln 3 | msbuild /t:Build /p:Configuration=Release /p:Platform=x64 portmaster-windows-kext.sln 4 | --------------------------------------------------------------------------------