├── .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 |
--------------------------------------------------------------------------------