diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..ee026f1 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustdocflags = ["--document-private-items"] \ No newline at end of file diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3e5f49c --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: Microsoft \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..dcad455 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a45d60 --- /dev/null +++ b/.gitignore @@ -0,0 +1,273 @@ +/target +/Cargo.lock +/.idea +/.vs + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc +/Application/Python37/Lib/encodings +/Application/Python37/Lib/importlib +/Application/Python37/Lib/collections +/Application/Python37/Lib + +# Custom +*.exe +/src/temp.rs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..56f1f9d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# v0.3.0 + +- Improve performance of minifilter by using -O2 and -LTO alongside release build +- Improve performance of [example](bin/minifilter.rs) by not locking and releasing IO +- Refactor and reformat minifilter +- Stop using debug libraries for minifilter +- Update readme and add changelog +- Add LICENSE \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0b8f5ea --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "fsfilter-rs" +version = "0.3.0" +edition = "2021" +authors = ["sn99 "] +description = "A rust library to monitor filesystem and more in windows" +repository = "https://github.com/sn99/fsfilter-rs" +license = "MIT" +readme = "README.md" +keywords = ["filesystem", "driver", "windows", "minifilter", "syscalls"] +documentation = "https://docs.rs/fsfilter-rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +sysinfo = "0.26.4" +widestring = "1.0.1" +serde_json = "1.0.68" +serde = { version = "1.0.130", features = ["derive"] } +num = "0.4" +num-derive = "0.3" +num-traits = "0.2.14" +strum = "0.24.1" +strum_macros = "0.24.3" +wchar = "0.11.0" +kodama = "0.2.3" + +[dependencies.windows] +version = "0.43.0" +features = [ + "Win32_Storage_InstallableFileSystems", + "Win32_Foundation", + "Win32_Security", + "Win32_Storage_FileSystem", + "Win32_System_Threading", + "Win32_System_ProcessStatus", + "Win32_System_Diagnostics_Debug", +] + +[profile.release] +lto = true +codegen-units = 1 +opt-level = 3 \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..e661113 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Siddharth Naithani + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MINIFILTER.md b/MINIFILTER.md new file mode 100644 index 0000000..4974eaa --- /dev/null +++ b/MINIFILTER.md @@ -0,0 +1,136 @@ +# minifilter-rs + +**Use `cargo doc --no-deps --document-private-items --open` to read Documentation** + +## Table of Contents + +
+ Table of Contents + +- [Minifilter Driver](https://github.com/sn99/fsfilter-rs#minifilter-driver) + - [Building Driver](https://github.com/sn99/fsfilter-rs#building-driver) + - [Installing Driver](https://github.com/sn99/fsfilter-rs#building-driver) + - [Loading/Removing Driver](https://github.com/sn99/fsfilter-rs#loadingremoving-driver) +- [Rust Application](https://github.com/sn99/fsfilter-rs#rust-application) + - [Building Rust App](https://github.com/sn99/fsfilter-rs#building-rust-app) + - [Running Rust App](https://github.com/sn99/fsfilter-rs#running-rust-app) +- [What and the How](https://github.com/sn99/fsfilter-rs#what-and-the-how) + +
+ +## Minifilter Driver + +### Building Driver + +1. Open `VS 2022` +2. Goto `minifilter-rs -> minifilter -> RWatch.sln` +3. Build solution in `Release` mode with `x64` + +**NOTE: Enable Loading of Test Signed Drivers by executing `Bcdedit.exe -set TESTSIGNING ON` in administrative cmd** + +### Installing Driver + +1. Open Powershell or command prompt as Administrator +2. `RUNDLL32.EXE SETUPAPI.DLL,InstallHinfSection DefaultInstall 132 \minifilter-rs\minifilter\x64\Debug\snFilter.inf` + +You should be able to see the driver at `"C:\Windows\System32\drivers\snFilter.sys"` + +### Loading/Removing Driver + +1. Open Powershell or command prompt as Administrator +2. Start the driver using `sc start snFilter`, expected output: + ``` + SERVICE_NAME: snFilter + TYPE : 2 FILE_SYSTEM_DRIVER + STATE : 4 RUNNING + (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN) + WIN32_EXIT_CODE : 0 (0x0) + SERVICE_EXIT_CODE : 0 (0x0) + CHECKPOINT : 0x0 + WAIT_HINT : 0x0 + PID : 0 + FLAGS : + ``` +3. Stop the driver using `sc stop snFilter`, should give the following output: + ``` + SERVICE_NAME: snFilter + TYPE : 2 FILE_SYSTEM_DRIVER + STATE : 1 STOPPED + WIN32_EXIT_CODE : 0 (0x0) + SERVICE_EXIT_CODE : 0 (0x0) + CHECKPOINT : 0x0 + WAIT_HINT : 0x0 + ``` +4. Remove it by `sc delete snFilter`, should give the following output: + ``` + [SC] DeleteService SUCCESS + ``` + +You can also run `Fltmc.exe` to see the currently loaded drivers: + +``` + +Filter Name Num Instances Altitude Frame +------------------------------ ------------- ------------ ----- +bindflt 1 409800 0 +snFilter 4 378781 0 // our minifilter driver +WdFilter 5 328010 0 +storqosflt 0 244000 0 +wcifs 0 189900 0 +CldFlt 0 180451 0 +FileCrypt 0 141100 0 +luafv 1 135000 0 +npsvctrig 1 46000 0 +Wof 3 40700 0 +FileInfo 5 40500 0 +``` + +## Rust Application + +### Building Rust App + +Simply use `cargo build --release` to build the application + +### Running Rust App + +Use `cargo run --bin minifilter --release` to run the application + +The program starts to print the `IOMessage` which is defined like: + +```rust +#[repr(C)] +pub struct IOMessage { + pub extension: [wchar_t; 12], + pub file_id_vsn: c_ulonglong, + pub file_id_id: [u8; 16], + pub mem_sized_used: c_ulonglong, + pub entropy: f64, + pub pid: c_ulong, + pub irp_op: c_uchar, + pub is_entropy_calc: u8, + pub file_change: c_uchar, + pub file_location_info: c_uchar, + pub filepathstr: String, + pub gid: c_ulonglong, + pub runtime_features: RuntimeFeatures, + pub file_size: i64, +} +``` + +We end the process using `ctrl + c` in the example video: +![video](readme_resources/example.gif) + +#### NOTE: + +- Might fail if not ran with administrative privileges +- You need to [load and start the driver]((https://github.com/sn99/fsfilter-rs#loadingremoving-driver)) before running + the program or else it will error out + +## What and the How + +We basically share definition between the mini-filter and Rust using `#[repr(C)]` + +![shared_def](readme_resources/shared_def.png) + +We use [channels](https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html) to process +all [IRPs](https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/irps-are-different-from-fast-i-o). diff --git a/README.md b/README.md new file mode 100644 index 0000000..4651105 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# fsfilter-rs + +[![Rust](https://github.com/sn99/fsfilter-rs/actions/workflows/rust.yml/badge.svg)](https://github.com/sn99/fsfilter-rs/actions/workflows/rust.yml) + +A rust library to monitor filesystem and more in windows + +Prepared as part of ongoing thesis work at uni. + +![shared_def](readme_resources/shared_def.png) + +### MINIFILTER + +See [MINIFILTER.md](MINIFILTER.md) for building the minifilter or just [right click install using the `.inf` file +provided in releases](https://github.com/sn99/fsfilter-rs/releases/latest/download/snFilter.zip). + +**NOTE: By default it is built for Windows 10 and above** + +**NOTE: Enable Loading of Test Signed Drivers by executing `Bcdedit.exe -set TESTSIGNING ON` in administrative cmd** + +### RUNNING EXAMPLE + +Use `cargo run --bin minifilter --release` to run the example application. The program starts to print the `IOMessage` +which is defined like: + +```rust +#[repr(C)] +pub struct IOMessage { + pub extension: [wchar_t; 12], + pub file_id_vsn: c_ulonglong, + pub file_id_id: [u8; 16], + pub mem_sized_used: c_ulonglong, + pub entropy: f64, + pub pid: c_ulong, + pub irp_op: c_uchar, + pub is_entropy_calc: u8, + pub file_change: c_uchar, + pub file_location_info: c_uchar, + pub filepathstr: String, + pub gid: c_ulonglong, + pub runtime_features: RuntimeFeatures, + pub file_size: i64, +} +``` + +## PERFORMANCE + +The performance of the minifilter doesn't really exceed `1%` of the CPU usage (I never saw it tickle even to 1% while +running scripts to make multiple temporary files). Although depending on you console if you try running +`cargo run --bin minifilter` you might see spikes reaching `1-3%` but that is because of the console itself (comment out +the `writeln!` in the bin example). + +## LICENSE + +This project is licensed under the terms of the [MIT license](LICENSE.md). + +## ACKNOWLEDGEMENTS + +- [RansomWatch](https://github.com/RafWu/RansomWatch) +- [SitinCloud](https://github.com/SitinCloud) +- [SubconsciousCompute](https://github.com/SubconsciousCompute) \ No newline at end of file diff --git a/minifilter/.gitignore b/minifilter/.gitignore new file mode 100644 index 0000000..369bc4f --- /dev/null +++ b/minifilter/.gitignore @@ -0,0 +1,271 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc +/Application/Python37/Lib/encodings +/Application/Python37/Lib/importlib +/Application/Python37/Lib/collections +/Application/Python37/Lib + +# Custom +*.exe + +# CLion files +/.idea diff --git a/minifilter/RWatch.sln b/minifilter/RWatch.sln new file mode 100644 index 0000000..11b0206 --- /dev/null +++ b/minifilter/RWatch.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32922.545 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "snFilter", "snFilter\snFilter.vcxproj", "{DF8682E7-17C1-4450-A68C-D7CEF8780D8F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D289F655-3416-4C39-97D1-9EC964BA43FF}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sharedDefs", "sharedDefs", "{FBDB615D-884C-4735-9C31-372EE539AE85}" + ProjectSection(SolutionItems) = preProject + SharedDefs\SharedDefs.h = SharedDefs\SharedDefs.h + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|ARM.ActiveCfg = Debug|ARM + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|ARM.Build.0 = Debug|ARM + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|ARM.Deploy.0 = Debug|ARM + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|ARM64.Build.0 = Debug|ARM64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|x64.ActiveCfg = Debug|x64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|x64.Build.0 = Debug|x64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|x64.Deploy.0 = Debug|x64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|x86.ActiveCfg = Debug|x64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|x86.Build.0 = Debug|x64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Debug|x86.Deploy.0 = Debug|x64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|ARM.ActiveCfg = Release|ARM + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|ARM.Build.0 = Release|ARM + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|ARM.Deploy.0 = Release|ARM + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|ARM64.ActiveCfg = Release|ARM64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|ARM64.Build.0 = Release|ARM64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|ARM64.Deploy.0 = Release|ARM64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|x64.ActiveCfg = Release|x64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|x64.Build.0 = Release|x64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|x64.Deploy.0 = Release|x64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|x86.ActiveCfg = Debug|x64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|x86.Build.0 = Debug|x64 + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F}.Release|x86.Deploy.0 = Debug|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BA4B40B9-6827-4A96-8EE1-2BB52F53967A} + EndGlobalSection +EndGlobal diff --git a/minifilter/SharedDefs/Common.h b/minifilter/SharedDefs/Common.h new file mode 100644 index 0000000..5fcfd87 --- /dev/null +++ b/minifilter/SharedDefs/Common.h @@ -0,0 +1,8 @@ +#pragma once + +// https://learn.microsoft.com/en-us/cpp/build/reference/kernel-create-kernel-mode-binary?view=msvc-170 +#ifdef _KERNEL_MODE + +#else + +#endif \ No newline at end of file diff --git a/minifilter/SharedDefs/SharedDefs.h b/minifilter/SharedDefs/SharedDefs.h new file mode 100644 index 0000000..f064a9e --- /dev/null +++ b/minifilter/SharedDefs/SharedDefs.h @@ -0,0 +1,136 @@ +#pragma once + +/*++ + +Abstract : + +Header file which contains the structures, type definitions, +constants, global variables and function prototypes that are +shared between kernel and user mode. + +Environment : + + Kernel & user mode + +--*/ + +// +// Name of port used to communicate +// + +const PWSTR ComPortName = L"\\snFilter"; + +#define MAX_FILE_NAME_LENGTH 520 +#define MAX_FILE_NAME_SIZE (MAX_FILE_NAME_LENGTH * sizeof(WCHAR)) // max length in bytes of files sizes and dir paths +#define FILE_OBJECT_ID_SIZE 16 +#define FILE_OBJEC_MAX_EXTENSION_SIZE 11 + +#define MAX_COMM_BUFFER_SIZE 0x10000 // size of the buffer we allocate to receive irp ops from the driver +#define MAX_OPS_SAVE \ + 0x1000 // max ops to save, we limit this to prevent driver from filling the non paged memory and crashing the os + +// msgs types that the application may send to the driver +enum COM_MESSAGE_TYPE { + MESSAGE_ADD_SCAN_DIRECTORY, + MESSAGE_REM_SCAN_DIRECTORY, + MESSAGE_GET_OPS, + MESSAGE_SET_PID, + MESSAGE_KILL_GID +}; + +// msgs struct that the application send when sending msg to the driver, type member should be one of the +// COM_MESSAGE_TYPE +typedef struct _COM_MESSAGE { + ULONG type; + ULONG pid; + ULONGLONG gid; + WCHAR path[MAX_FILE_NAME_LENGTH]; + +} COM_MESSAGE, *PCOM_MESSAGE; + +enum FILE_CHANGE_INFO { + FILE_CHANGE_NOT_SET, + FILE_OPEN_DIRECTORY, + FILE_CHANGE_WRITE, + FILE_CHANGE_NEW_FILE, + FILE_CHANGE_RENAME_FILE, + FILE_CHANGE_EXTENSION_CHANGED, + FILE_CHANGE_DELETE_FILE, + FILE_CHANGE_DELETE_NEW_FILE, + FILE_CHANGE_OVERWRITE_FILE +}; + +enum FILE_LOCATION_INFO { + FILE_NOT_PROTECTED, // nothing to set, not protected + FILE_PROTECTED, // if not read remember change in file + FILE_MOVED_IN, // new file to remove from protected + FILE_MOVED_OUT // keep filename if not already exist +}; + +enum IRP_MAJOR_OP { + IRP_NONE, + IRP_READ, + IRP_WRITE, + IRP_SETINFO, + IRP_CREATE, + IRP_CLEANUP, +}; + +// -64- bytes structure, fixed to -96- bytes, fixed to 104 bytes +typedef struct _DRIVER_MESSAGE { + WCHAR Extension[FILE_OBJEC_MAX_EXTENSION_SIZE + 1]; // null terminated 24 bytes + +#ifdef _KERNEL_MODE + FILE_ID_INFORMATION + FileID; // 24 bytes - file id 128 bits and its volume serial number +#else + FILE_ID_INFO + FileID; // 24 bytes - file id 128 bits and its volume serial number +#endif + + ULONGLONG + MemSizeUsed; // for read and write, we follow buffer sizes 8 bytes + DOUBLE Entropy; // 8 bytes + ULONG PID; // 4 bytes + UCHAR IRP_OP; // 1 byte + BOOLEAN isEntropyCalc; // 1 byte + UCHAR FileChange; // 1 byte + UCHAR FileLocationInfo; // 1 byte align + UNICODE_STRING + filePath; // 16 bytes unicode string - filename, also contains size and max size, buffer is outside the struct + ULONGLONG Gid; // 8 bytes process gid + PVOID + next; // 8 bytes - next PDRIVER_MESSAGE, we use it to allow adding the fileName to the same buffer, this pointer + // should point to the next PDRIVER_MESSAGE in buffer (kernel handled) + +} DRIVER_MESSAGE, *PDRIVER_MESSAGE; + +// header for return buffer from driver on irp ops, has pointer to the first driver message, num ops in the buffer and +// readable data size in the buffer +typedef struct _RWD_REPLY_IRPS { + size_t dataSize; // 8 bytes + PDRIVER_MESSAGE + data; // 8 bytes points to the first IRP driver message, the next DRIVER_MESSAGE is a pointer inside DRIVER_MESSAGE + ULONGLONG num_ops; // 8 bytes + + size_t size() { + return dataSize + sizeof(_RWD_REPLY_IRPS); + } + + size_t addSize(size_t size) { + dataSize += size; + return dataSize; + } + + ULONGLONG addOp() { + num_ops++; + return num_ops; + } + + ULONGLONG numOps() { + return num_ops; + } + + _RWD_REPLY_IRPS() : dataSize(sizeof(_RWD_REPLY_IRPS)), data(nullptr), num_ops(0) { + } +} RWD_REPLY_IRPS, *PRWD_REPLY_IRPS; \ No newline at end of file diff --git a/minifilter/SharedDefs/Unique_ptr.h b/minifilter/SharedDefs/Unique_ptr.h new file mode 100644 index 0000000..cb3cf12 --- /dev/null +++ b/minifilter/SharedDefs/Unique_ptr.h @@ -0,0 +1,3 @@ +#pragma once + +#include \ No newline at end of file diff --git a/minifilter/snFilter/Communication.cpp b/minifilter/snFilter/Communication.cpp new file mode 100644 index 0000000..d86af2e --- /dev/null +++ b/minifilter/snFilter/Communication.cpp @@ -0,0 +1,238 @@ +#include "Communication.h" + +#define POOL_FLAG_NON_PAGED 0x0000000000000040UI64 // Non paged pool NX + +NTSTATUS InitCommData() { + HRESULT status; + OBJECT_ATTRIBUTES oa; + UNICODE_STRING uniString; + PSECURITY_DESCRIPTOR sd; + // + // Create a communication port. + // + RtlInitUnicodeString(&uniString, ComPortName); + + status = FltBuildDefaultSecurityDescriptor( + &sd, + FLT_PORT_ALL_ACCESS); // We secure the port so only ADMIN(s) & SYSTEM can access it. + status = RtlSetDaclSecurityDescriptor(sd, TRUE, NULL, + FALSE); // allow user application without admin to enter + + if (NT_SUCCESS(status)) { + InitializeObjectAttributes(&oa, &uniString, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, sd); + + status = FltCreateCommunicationPort(commHandle->Filter, &commHandle->ServerPort, &oa, NULL, RWFConnect, + RWFDissconnect, RWFNewMessage, 1); + // + // Free the security descriptor in all cases. It is not needed once + // the call to FltCreateCommunicationPort() is made. + // + + FltFreeSecurityDescriptor(sd); + } + + return status; +} + +BOOLEAN IsCommClosed() { + return commHandle->CommClosed; +} + +void CommClose() { + // FLT_ASSERT(IsCommClosed()); + + if (commHandle->ClientPort) { + FltCloseClientPort(commHandle->Filter, &commHandle->ClientPort); + commHandle->ClientPort = NULL; + } + + if (commHandle->ServerPort) { + FltCloseCommunicationPort(commHandle->ServerPort); + commHandle->ServerPort = NULL; + } + commHandle->UserProcess = NULL; + commHandle->CommClosed = TRUE; +} + +NTSTATUS +RWFConnect(_In_ PFLT_PORT ClientPort, _In_opt_ PVOID ServerPortCookie, + _In_reads_bytes_opt_(SizeOfContext) PVOID ConnectionContext, _In_ ULONG SizeOfContext, + _Outptr_result_maybenull_ PVOID + +*ConnectionCookie) +{ +UNREFERENCED_PARAMETER(ServerPortCookie); +UNREFERENCED_PARAMETER(ConnectionContext); +UNREFERENCED_PARAMETER(SizeOfContext); +UNREFERENCED_PARAMETER(ConnectionCookie = NULL +); + +FLT_ASSERT(commHandle +->ClientPort == NULL); + +// +// Set the user process and port. In a production filter it may +// be necessary to synchronize access to such fields with port +// lifetime. For instance, while filter manager will synchronize +// FltCloseClientPort with FltSendMessage's reading of the port +// handle, synchronizing access to the UserProcess would be up to +// the filter. +// + +commHandle-> +ClientPort = ClientPort; +DbgPrint("!!! user connected, port=0x%p\n", ClientPort); + +return +STATUS_SUCCESS; +} + +VOID RWFDissconnect(_In_opt_ PVOID ConnectionCookie) { + UNREFERENCED_PARAMETER(ConnectionCookie); + + DbgPrint("!!! user disconnected, port=0x%p\n", commHandle->ClientPort); + + // + // Close our handle to the connection: note, since we limited max connections to 1, + // another connect will not be allowed until we return from the disconnect routine. + // + + FltCloseClientPort(commHandle->Filter, &commHandle->ClientPort); + + // + // Reset the user-process field. + // + DbgPrint("Disconnect\n"); + commHandle->CommClosed = TRUE; +} + +NTSTATUS +RWFNewMessage(IN PVOID PortCookie, IN PVOID InputBuffer, IN ULONG InputBufferLength, OUT PVOID OutputBuffer, + IN ULONG OutputBufferLength, OUT PULONG ReturnOutputBufferLength) { + UNREFERENCED_PARAMETER(PortCookie); + UNREFERENCED_PARAMETER(InputBufferLength); + + *ReturnOutputBufferLength = 0; + + COM_MESSAGE *message = static_cast(InputBuffer); + if (message == NULL) + return STATUS_INTERNAL_ERROR; // failed message type + + if (message->type == MESSAGE_ADD_SCAN_DIRECTORY) { + DbgPrint("Received add directory message\n"); + PDIRECTORY_ENTRY newEntry = new DIRECTORY_ENTRY(); + if (newEntry == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + NTSTATUS hr = CopyWString(newEntry->path, message->path, MAX_FILE_NAME_LENGTH); + if (!NT_SUCCESS(hr)) { + delete newEntry; + return STATUS_INTERNAL_ERROR; + } + *ReturnOutputBufferLength = 1; + if (driverData->AddDirectoryEntry(newEntry)) { + *((PBOOLEAN) OutputBuffer) = TRUE; + DbgPrint("Added scan directory successfully\n"); + return STATUS_SUCCESS; + } else { + delete newEntry; + *((PBOOLEAN) OutputBuffer) = FALSE; + DbgPrint("Failed to add scan directory\n"); + return STATUS_SUCCESS; + } + } else if (message->type == MESSAGE_REM_SCAN_DIRECTORY) { + PDIRECTORY_ENTRY ptr = driverData->RemDirectoryEntry(message->path); + *ReturnOutputBufferLength = 1; + if (ptr == NULL) { + *((PBOOLEAN) OutputBuffer) = FALSE; + DbgPrint("Failed to remove directory\n"); + return STATUS_SUCCESS; + } else { + delete ptr; + } + *((PBOOLEAN) OutputBuffer) = TRUE; + DbgPrint("Removed scan directory successfully\n"); + return STATUS_SUCCESS; + } else if (message->type == MESSAGE_GET_OPS) { + if (OutputBuffer == NULL || OutputBufferLength != MAX_COMM_BUFFER_SIZE) { + return STATUS_INVALID_PARAMETER; + } + driverData->DriverGetIrps(OutputBuffer, OutputBufferLength, ReturnOutputBufferLength); + return STATUS_SUCCESS; + } else if (message->type == MESSAGE_SET_PID) { + if (message->pid != 0) { + driverData->setPID(message->pid); + driverData->setSystemRootPath(message->path); + commHandle->CommClosed = FALSE; + + return STATUS_SUCCESS; + } + return STATUS_INVALID_PARAMETER; + } + // TODO: the kill code to gid + else if (message->type == MESSAGE_KILL_GID) { + if (OutputBuffer == NULL || OutputBufferLength != sizeof(LONG)) { + return STATUS_INVALID_PARAMETER; + } + *ReturnOutputBufferLength = sizeof(LONG); + NTSTATUS status = STATUS_SUCCESS; + HANDLE processHandle; + ULONGLONG GID = message->gid; + BOOLEAN isGidExist = FALSE; + ULONGLONG gidSize = driverData->GetGidSize(GID, &isGidExist); + if (gidSize == 0 || isGidExist == FALSE) { + DbgPrint("!!! FS : Gid already ended or no such gid %d\n", GID); + *((PLONG) OutputBuffer) = STATUS_NO_SUCH_GROUP; // fail to kill process + return STATUS_SUCCESS; + } + // there is gid with processes + PULONG + Buffer = (PULONG) + ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(ULONG) * gidSize, 'RW'); + if (Buffer == nullptr) { + DbgPrint("!!! FS : memory allocation error on non paged pool\n"); + *((PLONG) OutputBuffer) = STATUS_MEMORY_NOT_ALLOCATED; // fail to kill process + return STATUS_SUCCESS; + } + ULONGLONG pidsReturned = 0; + isGidExist = driverData->GetGidPids(GID, Buffer, gidSize, &pidsReturned); + if (isGidExist) { // got all irps and correct size + for (int i = 0; i < gidSize; i++) { // kill each process + CLIENT_ID clientId; + clientId.UniqueProcess = (HANDLE) Buffer[i]; + clientId.UniqueThread = 0; + + OBJECT_ATTRIBUTES objAttribs; + NTSTATUS exitStatus = STATUS_FAIL_CHECK; + + DbgPrint("!!! FS : Attempt to terminate pid: %d from gid: %d\n", Buffer[i], GID); + + InitializeObjectAttributes(&objAttribs, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); + + status = ZwOpenProcess(&processHandle, PROCESS_ALL_ACCESS, &objAttribs, &clientId); + + if (!NT_SUCCESS(status)) { + *((PLONG) OutputBuffer) = STATUS_FAIL_CHECK; // fail + DbgPrint("!!! FS : Failed to open process %d, reason: %d\n", Buffer[i], status); + continue; // try to kill others + } + status = ZwTerminateProcess(processHandle, exitStatus); + if (!NT_SUCCESS(status)) { + *((PLONG) OutputBuffer) = STATUS_FAIL_CHECK; // fail + DbgPrint("!!! FS : Failed to kill process %d, reason: %d\n", Buffer[i], status); + status = NtClose(processHandle); + continue; // try to kill others + } + NtClose(processHandle); + + DbgPrint("!!! FS : Termination of pid: %d from gid: %d succeeded\n", Buffer[i], GID); + } + } + ExFreePoolWithTag(Buffer, 'RW'); + return STATUS_SUCCESS; + } + + return STATUS_INTERNAL_ERROR; +} + +CommHandler *commHandle; \ No newline at end of file diff --git a/minifilter/snFilter/Communication.h b/minifilter/snFilter/Communication.h new file mode 100644 index 0000000..09d02ff --- /dev/null +++ b/minifilter/snFilter/Communication.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include "../SharedDefs/SharedDefs.h" +#include "DriverData.h" + +struct CommHandler { + // Server-side communicate ports. + PFLT_PORT ServerPort; + + // port for a connection to user-mode + PFLT_PORT ClientPort; + + // The filter handle that results from a call to + PFLT_FILTER Filter; + + // A flag that indicating that the filter is connected + BOOLEAN CommClosed; + + // User process that connected to the port + + ULONG UserProcess; + + CommHandler(PFLT_FILTER Filter) + : ServerPort(NULL), ClientPort(NULL), Filter(Filter), CommClosed(TRUE), UserProcess(0) { + } +}; + +extern CommHandler *commHandle; + +NTSTATUS InitCommData(); + +// close the comm handler, close both ports +void CommClose(); + +BOOLEAN IsCommClosed(); + +// AMFConnect: Handles user mode application which connects to the driver + +NTSTATUS +RWFConnect(_In_ PFLT_PORT ClientPort, _In_opt_ PVOID ServerPortCookie, + _In_reads_bytes_opt_(SizeOfContext) PVOID ConnectionContext, _In_ ULONG SizeOfContext, + _Outptr_result_maybenull_ PVOID + +*ConnectionCookie); + +// AMFConnect: handle messages received from user mode + +NTSTATUS RWFNewMessage(IN PVOID PortCookie, IN PVOID InputBuffer, IN ULONG InputBufferLength, OUT PVOID OutputBuffer, + IN ULONG OutputBufferLength, OUT PULONG ReturnOutputBufferLength); + +// AMFDisconnect: Handles user mode application which disconnects from the driver + +VOID RWFDissconnect(_In_opt_ PVOID ConnectionCookie); \ No newline at end of file diff --git a/minifilter/snFilter/DriverData.cpp b/minifilter/snFilter/DriverData.cpp new file mode 100644 index 0000000..3a1869e --- /dev/null +++ b/minifilter/snFilter/DriverData.cpp @@ -0,0 +1,538 @@ +#include "DriverData.h" + +DriverData::DriverData(PDRIVER_OBJECT DriverObject) + : FilterRun(FALSE), Filter(nullptr), DriverObject(DriverObject), pid(0), irpOpsSize(0), directoryRootsSize(0), + GidToPids(), PidToGids() { + systemRootPath[0] = L'\0'; + InitializeListHead(&irpOps); + InitializeListHead(&rootDirectories); + KeInitializeSpinLock(&irpOpsLock); // init spin lock + KeInitializeSpinLock(&directoriesSpinLock); // init spin lock + + GidCounter = 0; + KeInitializeSpinLock(&GIDSystemLock); // init spin lock + gidsSize = 0; + InitializeListHead(&GidsList); +} + +DriverData::~DriverData() { + Clear(); +} + +DriverData *driverData; + +// ####################################################################################### +// # Gid system handling +// ####################################################################################### + +/****************** Private ******************/ + +// call assumes protected code high irql +BOOLEAN DriverData::RemoveProcessRecordAux(ULONG ProcessId, ULONGLONG gid) { + BOOLEAN ret = FALSE; + PGID_ENTRY gidRecord = (PGID_ENTRY) GidToPids.get(gid); + if (gidRecord == nullptr) { // shouldn't happen + return FALSE; + } + PLIST_ENTRY header = &(gidRecord->HeadListPids); + PLIST_ENTRY iterator = header->Flink; + while (iterator != header) { + PPID_ENTRY pStrct = (PPID_ENTRY) CONTAINING_RECORD(iterator, PID_ENTRY, entry); + if (pStrct->Pid == ProcessId) { + RemoveEntryList(iterator); + delete pStrct->Path; + delete pStrct; + gidRecord->pidsSize--; + ret = TRUE; + break; + } + iterator = iterator->Flink; + } + if (ret) { + if (IsListEmpty(header)) { + GidToPids.deleteNode(gid); // remove the gidRecord from GidToPids + RemoveEntryList(&(gidRecord->GidListEntry)); // unlink from list of gids + gidsSize--; + delete gidRecord; + } + PidToGids.deleteNode(ProcessId); + } + return ret; +} + +// call assumes protected code high irql +BOOLEAN DriverData::RemoveGidRecordAux(PGID_ENTRY gidRecord) { + BOOLEAN ret = FALSE; + ASSERT(gidRecord != nullptr); + PLIST_ENTRY headerPids = &(gidRecord->HeadListPids); + PULONGLONG pidsSize = &(gidRecord->pidsSize); + PLIST_ENTRY iterator = headerPids->Flink; + while (iterator != headerPids) { // clear list + PPID_ENTRY pStrct = (PPID_ENTRY) CONTAINING_RECORD(iterator, PID_ENTRY, entry); + PLIST_ENTRY next = iterator->Flink; + RemoveEntryList(iterator); + PidToGids.deleteNode(pStrct->Pid); + pidsSize--; + delete pStrct->Path; // release PUNICODE_STRING + delete pStrct; // release PID_ENTRY + ret = TRUE; + iterator = next; + } + ASSERT(IsListEmpty(headerPids)); + return ret; +} + +/****************** Public ******************/ + +BOOLEAN DriverData::RemoveProcess(ULONG ProcessId) { + BOOLEAN ret = FALSE; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&GIDSystemLock, &irql); + ULONGLONG gid = (ULONGLONG) PidToGids.get(ProcessId); + if (gid) { // there is Gid + ret = RemoveProcessRecordAux(ProcessId, gid); + } + + KeReleaseSpinLock(&GIDSystemLock, irql); + return ret; +} + +BOOLEAN DriverData::RecordNewProcess(PUNICODE_STRING ProcessName, ULONG ProcessId, ULONG ParentPid) { + BOOLEAN ret = FALSE; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&GIDSystemLock, &irql); + ULONGLONG gid = (ULONGLONG) PidToGids.get(ParentPid); + PPID_ENTRY pStrct = new PID_ENTRY; + pStrct->Pid = ProcessId; + pStrct->Path = ProcessName; + if (gid) { // there is Gid + ULONGLONG retInsert; + if ((retInsert = (ULONGLONG) PidToGids.insertNode(ProcessId, (HANDLE) gid)) != gid) { // shouldn't happen + RemoveProcessRecordAux(ProcessId, retInsert); + } + PGID_ENTRY gidRecord = (PGID_ENTRY) GidToPids.get(gid); + InsertHeadList(&(gidRecord->HeadListPids), &(pStrct->entry)); + gidRecord->pidsSize++; + PidToGids.insertNode(ProcessId, (HANDLE) gid); + } else { + PGID_ENTRY newGidRecord = new GID_ENTRY(++GidCounter); + InsertHeadList(&(newGidRecord->HeadListPids), &(pStrct->entry)); + InsertTailList(&GidsList, &(newGidRecord->GidListEntry)); + GidToPids.insertNode(GidCounter, newGidRecord); + PidToGids.insertNode(ProcessId, (HANDLE) GidCounter); + newGidRecord->pidsSize++; + gidsSize++; + } + KeReleaseSpinLock(&GIDSystemLock, irql); + return ret; +} + +BOOLEAN DriverData::RemoveGid(ULONGLONG gid) { + BOOLEAN ret = FALSE; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&GIDSystemLock, &irql); + PGID_ENTRY gidRecord = (PGID_ENTRY) GidToPids.get(gid); + if (gidRecord) { // there is Gid list + RemoveGidRecordAux(gidRecord); // clear process list + GidToPids.deleteNode(gid); // remove the gidRecord from GidToPids + RemoveEntryList(&(gidRecord->GidListEntry)); // unlink from list of gids + gidsSize--; + delete gidRecord; + ret = TRUE; + } + + KeReleaseSpinLock(&GIDSystemLock, irql); + return ret; +} + +ULONGLONG DriverData::GetGidSize(ULONGLONG gid, PBOOLEAN found) { + ASSERT(found != nullptr); + *found = FALSE; + ULONGLONG ret = 0; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&GIDSystemLock, &irql); + PGID_ENTRY GidRecord = (PGID_ENTRY) GidToPids.get(gid); + if (GidRecord != nullptr) { // there is such Gid + *found = TRUE; + ret = GidRecord->pidsSize; + } + KeReleaseSpinLock(&GIDSystemLock, irql); + return ret; +} + +BOOLEAN DriverData::GetGidPids(ULONGLONG gid, PULONG buffer, ULONGLONG bufferSize, PULONGLONG returnedLength) { + ASSERT(buffer != nullptr); + ASSERT(returnedLength != nullptr); + *returnedLength = 0; + if (bufferSize == 0) + return FALSE; + ULONGLONG pidsSize = 0; + ULONGLONG pidsIter = 0; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&GIDSystemLock, &irql); + PGID_ENTRY GidRecord = (PGID_ENTRY) GidToPids.get(gid); + if (GidRecord != nullptr) { // there is such Gid + pidsSize = GidRecord->pidsSize; + PLIST_ENTRY PidsListHeader = &(GidRecord->HeadListPids); + PLIST_ENTRY iterator = PidsListHeader->Flink; + while (iterator != PidsListHeader && pidsIter < bufferSize) { + PPID_ENTRY pStrct = (PPID_ENTRY) CONTAINING_RECORD(iterator, PID_ENTRY, entry); + ASSERT(pStrct != nullptr); + if (pStrct != nullptr) { + buffer[pidsIter++] = pStrct->Pid; + *returnedLength += 1; + } + iterator = iterator->Flink; + } + } + KeReleaseSpinLock(&GIDSystemLock, irql); + if (GidRecord == nullptr) { + return FALSE; + } + if (pidsSize == pidsIter) { + return TRUE; + } + return FALSE; +} + +// if found return true on found else return false +ULONGLONG DriverData::GetProcessGid(ULONG ProcessId, PBOOLEAN found) { + ASSERT(found != nullptr); + *found = FALSE; + ULONGLONG ret = 0; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&GIDSystemLock, &irql); + ret = (ULONGLONG) PidToGids.get(ProcessId); + if (ret) + *found = TRUE; + KeReleaseSpinLock(&GIDSystemLock, irql); + // DbgPrint("Gid: %d %d\n", ret, *found); + return ret; +} + +// clear all data related to Gid system +VOID DriverData::ClearGidsPids() { + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&GIDSystemLock, &irql); + PLIST_ENTRY headGids = &GidsList; + PLIST_ENTRY iterator = headGids->Flink; + while (iterator != headGids) { // clear list + PGID_ENTRY pStrct = (PGID_ENTRY) CONTAINING_RECORD(iterator, GID_ENTRY, GidListEntry); + PLIST_ENTRY next = iterator->Flink; + RemoveGidRecordAux(pStrct); // clear process list and processes from PidToGids + GidToPids.deleteNode(pStrct->gid); // remove gid from GidToPids + gidsSize--; + delete pStrct; // release GID_ENTRY + iterator = next; + } + // ASSERT(headGids->Flink == headGids); + GidCounter = 0; + KeReleaseSpinLock(&GIDSystemLock, irql); +} + +ULONGLONG DriverData::GidsSize() { + ULONGLONG ret = 0; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&GIDSystemLock, &irql); + ret = gidsSize; + KeReleaseSpinLock(&GIDSystemLock, irql); + return ret; +} + +// ####################################################################################### +// # Irp handling +// ####################################################################################### + +VOID DriverData::ClearIrps() { + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&irpOpsLock, &irql); + PLIST_ENTRY pEntryIrps = irpOps.Flink; + while (pEntryIrps != &irpOps) { + LIST_ENTRY temp = *pEntryIrps; + PIRP_ENTRY pStrct = (PIRP_ENTRY) CONTAINING_RECORD(pEntryIrps, IRP_ENTRY, entry); + delete pStrct; + // next + pEntryIrps = temp.Flink; + } + irpOpsSize = 0; + InitializeListHead(&irpOps); + KeReleaseSpinLock(&irpOpsLock, irql); +} + +ULONG DriverData::IrpSize() { + ULONG ret = 0; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&irpOpsLock, &irql); + ret = irpOpsSize; + KeReleaseSpinLock(&irpOpsLock, irql); + return ret; +} + +BOOLEAN DriverData::AddIrpMessage(PIRP_ENTRY newEntry) { + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&irpOpsLock, &irql); + if (irpOpsSize < MAX_OPS_SAVE) { + irpOpsSize++; + InsertTailList(&irpOps, &newEntry->entry); + } else { + KeReleaseSpinLock(&irpOpsLock, irql); + return FALSE; + } + KeReleaseSpinLock(&irpOpsLock, irql); + return TRUE; +} + +BOOLEAN DriverData::RemIrpMessage(PIRP_ENTRY newEntry) { + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&irpOpsLock, &irql); + RemoveEntryList(&newEntry->entry); + irpOpsSize--; + + KeReleaseSpinLock(&irpOpsLock, irql); + return TRUE; +} + +PIRP_ENTRY DriverData::GetFirstIrpMessage() { + PLIST_ENTRY ret; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&irpOpsLock, &irql); + ret = RemoveHeadList(&irpOps); + irpOpsSize--; + KeReleaseSpinLock(&irpOpsLock, irql); + if (ret == &irpOps) { + return NULL; + } + return (PIRP_ENTRY) CONTAINING_RECORD(ret, IRP_ENTRY, entry); +} + +VOID DriverData::DriverGetIrps(PVOID Buffer, ULONG BufferSize, PULONG ReturnOutputBufferLength) { + *ReturnOutputBufferLength = sizeof(RWD_REPLY_IRPS); + + PCHAR OutputBuffer = (PCHAR) Buffer; + ASSERT(OutputBuffer != nullptr); + OutputBuffer += sizeof(RWD_REPLY_IRPS); + + ULONG BufferSizeRemain = BufferSize - sizeof(RWD_REPLY_IRPS); + + RWD_REPLY_IRPS outHeader; + PLIST_ENTRY irpEntryList; + + PIRP_ENTRY PrevEntry = nullptr; + PDRIVER_MESSAGE Prev = nullptr; + USHORT prevBufferSize = 0; + + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&irpOpsLock, &irql); + + while (irpOpsSize) { + irpEntryList = RemoveHeadList(&irpOps); + irpOpsSize--; + PIRP_ENTRY irp = (PIRP_ENTRY) CONTAINING_RECORD(irpEntryList, IRP_ENTRY, entry); + UNICODE_STRING FilePath = irp->filePath; + PDRIVER_MESSAGE irpMsg = &(irp->data); + USHORT nameBufferSize = FilePath.Length; + irpMsg->next = nullptr; + irpMsg->filePath.Buffer = nullptr; + if (FilePath.Length) { + irpMsg->filePath.Length = nameBufferSize; + irpMsg->filePath.MaximumLength = nameBufferSize; + } else { + irpMsg->filePath.Length = 0; + irpMsg->filePath.MaximumLength = 0; + } + + if (sizeof(DRIVER_MESSAGE) + nameBufferSize >= BufferSizeRemain) { // return to irps list, not enough space + InsertHeadList(&irpOps, irpEntryList); + irpOpsSize++; + break; + } else { + if (Prev != nullptr) { + Prev->next = PDRIVER_MESSAGE(OutputBuffer + sizeof(DRIVER_MESSAGE) + + prevBufferSize); // PrevFilePath might be 0 size + if (prevBufferSize) { + Prev->filePath.Buffer = PWCH(OutputBuffer + sizeof(DRIVER_MESSAGE)); // filePath buffer is after irp + } + RtlCopyMemory(OutputBuffer, Prev, + sizeof(DRIVER_MESSAGE)); // copy previous irp + OutputBuffer += sizeof(DRIVER_MESSAGE); + outHeader.addSize(sizeof(DRIVER_MESSAGE)); + *ReturnOutputBufferLength += sizeof(DRIVER_MESSAGE); + if (prevBufferSize) { + RtlCopyMemory(OutputBuffer, PrevEntry->Buffer, + prevBufferSize); // copy previous filePath + OutputBuffer += prevBufferSize; + outHeader.addSize(prevBufferSize); + *ReturnOutputBufferLength += prevBufferSize; + } + delete PrevEntry; + } + } + + PrevEntry = irp; + Prev = irpMsg; + prevBufferSize = nameBufferSize; + if (prevBufferSize > MAX_FILE_NAME_SIZE) + prevBufferSize = MAX_FILE_NAME_SIZE; + BufferSizeRemain -= (sizeof(DRIVER_MESSAGE) + prevBufferSize); + outHeader.addOp(); + } + KeReleaseSpinLock(&irpOpsLock, irql); + if (prevBufferSize > MAX_FILE_NAME_SIZE) + prevBufferSize = MAX_FILE_NAME_SIZE; + if (Prev != nullptr && PrevEntry != nullptr) { + Prev->next = nullptr; + if (prevBufferSize) { + Prev->filePath.Buffer = PWCH(OutputBuffer + sizeof(DRIVER_MESSAGE)); // filePath buffer is after irp + } + RtlCopyMemory(OutputBuffer, Prev, + sizeof(DRIVER_MESSAGE)); // copy previous irp + OutputBuffer += sizeof(DRIVER_MESSAGE); + outHeader.addSize(sizeof(DRIVER_MESSAGE)); + *ReturnOutputBufferLength += sizeof(DRIVER_MESSAGE); + if (prevBufferSize) { + RtlCopyMemory(OutputBuffer, PrevEntry->Buffer, + prevBufferSize); // copy previous filePath + OutputBuffer += prevBufferSize; + outHeader.addSize(prevBufferSize); + *ReturnOutputBufferLength += prevBufferSize; + } + delete PrevEntry; + } + + if (outHeader.numOps()) { + outHeader.data = PDRIVER_MESSAGE((PCHAR) Buffer + sizeof(RWD_REPLY_IRPS)); + } + + RtlCopyMemory((PCHAR) Buffer, &(outHeader), sizeof(RWD_REPLY_IRPS)); +} + +LIST_ENTRY DriverData::GetAllEntries() { + LIST_ENTRY newList; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&irpOpsLock, &irql); + irpOpsSize = 0; + newList = irpOps; + InitializeListHead(&irpOps); + + KeReleaseSpinLock(&irpOpsLock, irql); + return newList; +} + +// ####################################################################################### +// # Directory handling +// ####################################################################################### + +BOOLEAN DriverData::AddDirectoryEntry(PDIRECTORY_ENTRY newEntry) { + BOOLEAN ret = FALSE; + BOOLEAN foundMatch = FALSE; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&directoriesSpinLock, &irql); + + PLIST_ENTRY pEntry = rootDirectories.Flink; + while (pEntry != &rootDirectories) { + PDIRECTORY_ENTRY pStrct; + // + // Do some processing. + // + pStrct = (PDIRECTORY_ENTRY) CONTAINING_RECORD(pEntry, DIRECTORY_ENTRY, entry); + + if (!wcsncmp(newEntry->path, pStrct->path, wcsnlen_s(newEntry->path, MAX_FILE_NAME_LENGTH))) { + foundMatch = TRUE; + break; + } + // + // Move to next Entry in list. + // + pEntry = pEntry->Flink; + } + if (foundMatch == FALSE) { + InsertHeadList(&rootDirectories, &newEntry->entry); + directoryRootsSize++; + ret = TRUE; + } + KeReleaseSpinLock(&directoriesSpinLock, irql); + return ret; +} + +PDIRECTORY_ENTRY DriverData::RemDirectoryEntry(LPCWSTR directory) { + PDIRECTORY_ENTRY ret = NULL; + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&directoriesSpinLock, &irql); + + PLIST_ENTRY pEntry = rootDirectories.Flink; + + while (pEntry != &rootDirectories) { + PDIRECTORY_ENTRY pStrct; + // + // Do some processing. + // + pStrct = (PDIRECTORY_ENTRY) CONTAINING_RECORD(pEntry, DIRECTORY_ENTRY, entry); + + if (!wcsncmp(directory, pStrct->path, wcsnlen_s(directory, MAX_FILE_NAME_LENGTH))) { + if (RemoveEntryList(pEntry)) { + ret = pStrct; + directoryRootsSize--; + break; + } + } + // + // Move to next Entry in list. + // + pEntry = pEntry->Flink; + } + KeReleaseSpinLock(&directoriesSpinLock, irql); + return ret; +} + +/** + IsContainingDirectory returns true if one of the directory entries in our + LIST_ENTRY of PDIRECTORY_ENTRY is in the path passed as param +*/ +BOOLEAN DriverData::IsContainingDirectory(CONST PUNICODE_STRING path) { + if (path == NULL || path->Buffer == NULL) + return FALSE; + BOOLEAN ret = FALSE; + KIRQL irql = KeGetCurrentIrql(); + // DbgPrint("Looking for path: %ls in lookup dirs", path); + KeAcquireSpinLock(&directoriesSpinLock, &irql); + if (directoryRootsSize != 0) { + PLIST_ENTRY pEntry = rootDirectories.Flink; + while (pEntry != &rootDirectories) { + PDIRECTORY_ENTRY pStrct = (PDIRECTORY_ENTRY) CONTAINING_RECORD(pEntry, DIRECTORY_ENTRY, entry); + for (ULONG i = 0; i < path->Length; i++) { + if (pStrct->path[i] == L'\0') { + ret = TRUE; + break; + } else if (pStrct->path[i] == path->Buffer[i]) { + continue; + } else { + break; // for loop + } + } + + // ret = (wcsstr(path, pStrct->path) != NULL); + if (ret) + break; + // Move to next Entry in list. + pEntry = pEntry->Flink; + } + } + KeReleaseSpinLock(&directoriesSpinLock, irql); + return ret; +} + +VOID DriverData::ClearDirectories() { + KIRQL irql = KeGetCurrentIrql(); + KeAcquireSpinLock(&directoriesSpinLock, &irql); + PLIST_ENTRY pEntryDirs = rootDirectories.Flink; + while (pEntryDirs != &rootDirectories) { + LIST_ENTRY temp = *pEntryDirs; + PDIRECTORY_ENTRY pStrct = (PDIRECTORY_ENTRY) CONTAINING_RECORD(pEntryDirs, DIRECTORY_ENTRY, entry); + delete pStrct; + // next + pEntryDirs = temp.Flink; + } + directoryRootsSize = 0; + InitializeListHead(&rootDirectories); + KeReleaseSpinLock(&directoriesSpinLock, irql); +} \ No newline at end of file diff --git a/minifilter/snFilter/DriverData.h b/minifilter/snFilter/DriverData.h new file mode 100644 index 0000000..36d6544 --- /dev/null +++ b/minifilter/snFilter/DriverData.h @@ -0,0 +1,154 @@ +#pragma once + +#include + +#include "HashTable.h" +#include "KernelCommon.h" +#include "KernelString.h" + +/* DriverData: shared class across driver, hold driver D.S. */ +class DriverData { + BOOLEAN FilterRun; // true if filter currently runs + PFLT_FILTER Filter; + PDRIVER_OBJECT DriverObject; // internal + WCHAR systemRootPath[MAX_FILE_NAME_LENGTH]; // system root path, help analyze image files loaded + ULONG + pid; // pid of the current connected user mode application, set by communication + + ULONG irpOpsSize; // number of irp ops waiting in entry_list + LIST_ENTRY irpOps; // list entry bidirectional list of irp ops + KSPIN_LOCK irpOpsLock; // lock for irp list ops + + ULONG directoryRootsSize; // number of protected dirs in list + LIST_ENTRY rootDirectories; // list entry bdirectional of protected dirs + KSPIN_LOCK directoriesSpinLock; // lock for directory list + + /* GID system data members */ + ULONGLONG + GidCounter; // internal counter for gid, every new application receives a new gid + HashMap GidToPids; // mapping from gid to pids + HashMap PidToGids; // mapping from pid to gid + ULONGLONG gidsSize; // number of gids currently active + LIST_ENTRY GidsList; // list entry of gids, used to clear memory + KSPIN_LOCK GIDSystemLock; + +private: + // call assumes protected code - high IRQL + BOOLEAN RemoveProcessRecordAux(ULONG ProcessId, ULONGLONG gid); + + // call assumes protected code - high IRQL + BOOLEAN RemoveGidRecordAux(PGID_ENTRY gidRecord); + +public: + // c'tor init D.S. + explicit DriverData(PDRIVER_OBJECT DriverObject); + + ~DriverData(); + + PWCHAR GetSystemRootPath() { + return systemRootPath; + } + + // sets the system root path, received from user mode application, we copy the systemRootPath sent on the message + VOID setSystemRootPath(PWCHAR setsystemRootPath) { + RtlZeroBytes(systemRootPath, MAX_FILE_NAME_SIZE); + RtlCopyBytes(systemRootPath, setsystemRootPath, MAX_FILE_NAME_LENGTH); + RtlCopyBytes(systemRootPath + wcsnlen(systemRootPath, MAX_FILE_NAME_LENGTH / 2), L"\\Windows", + wcsnlen(L"\\Windows", MAX_FILE_NAME_LENGTH / 2)); + DbgPrint("Set system root path %ls\n", systemRootPath); + } + + // remove a process which ended from the GID system, function raise IRQL + BOOLEAN RemoveProcess(ULONG ProcessId); + + // record a process which was created to the GID system, function raise IRQL + BOOLEAN RecordNewProcess(PUNICODE_STRING ProcessName, ULONG ProcessId, ULONG ParentPid); + + // removed a gid from the system, function raise IRQL + BOOLEAN RemoveGid(ULONGLONG gid); + + // gets the number of processes in a gid, function raise IRQL + ULONGLONG GetGidSize(ULONGLONG gid, PBOOLEAN found); + + // help function, receives a buffer and returns an array of pids, returns true only if all pids are restored + BOOLEAN GetGidPids(ULONGLONG gid, PULONG buffer, ULONGLONG bufferSize, PULONGLONG returnedLength); + + // if found return true on found else return false + ULONGLONG GetProcessGid(ULONG ProcessId, PBOOLEAN found); + + // clear all data related to Gid system + VOID ClearGidsPids(); + + ULONGLONG GidsSize(); + + BOOLEAN setFilterStart() { + return (FilterRun = TRUE); + } + + BOOLEAN setFilterStop() { + return (FilterRun = FALSE); + } + + BOOLEAN isFilterClosed() { + return !FilterRun; + } + + PFLT_FILTER *getFilterAdd() { + return &Filter; + } + + PFLT_FILTER getFilter() { + return Filter; + } + + ULONG getPID() { + return pid; + } + + ULONG setPID(ULONG Pid) { + pid = Pid; + return Pid; + } + + // clears all irps waiting to report, function raise IRQL + VOID ClearIrps(); + + ULONG IrpSize(); + + BOOLEAN AddIrpMessage(PIRP_ENTRY newEntry); + + BOOLEAN RemIrpMessage(PIRP_ENTRY newEntry); + + PIRP_ENTRY GetFirstIrpMessage(); + + // Takes Irps from the driverData and copy them to a buffer, also copies the file names on which the irp occured, + // function raise IRQL + VOID DriverGetIrps(PVOID Buffer, ULONG BufferSize, PULONG ReturnOutputBufferLength); + + LIST_ENTRY GetAllEntries(); + + BOOLEAN AddDirectoryEntry(PDIRECTORY_ENTRY newEntry); + + PDIRECTORY_ENTRY RemDirectoryEntry(LPCWSTR directory); + + /** + IsContainingDirectory returns true if one of the directory entries in our LIST_ENTRY of PDIRECTORY_ENTRY is in + the path passed as param + */ + BOOLEAN IsContainingDirectory(CONST PUNICODE_STRING path); + + VOID ClearDirectories(); + + VOID Clear() { + // clear directories + ClearDirectories(); + + // clear irps + ClearIrps(); + + // clear gid system + ClearGidsPids(); + } +}; + +extern DriverData *driverData; \ No newline at end of file diff --git a/minifilter/snFilter/HashTable.h b/minifilter/snFilter/HashTable.h new file mode 100644 index 0000000..1b2fd30 --- /dev/null +++ b/minifilter/snFilter/HashTable.h @@ -0,0 +1,145 @@ +#pragma once +#define POOL_FLAG_NON_PAGED 0x0000000000000040UI64 // Non paged pool NX + +// Hashnode class +struct HashNode { + LIST_ENTRY entry; + HANDLE value; + ULONGLONG key; + + // Constructor of hashnode + HashNode(ULONGLONG skey, HANDLE svalue) { + InitializeListHead(&entry); + value = svalue; + key = skey; + } + + void *operator new(size_t size) { + void *ptr = ExAllocatePool2(POOL_FLAG_NON_PAGED, size, 'RW'); + memset(ptr, 0, size); + return ptr; + } + + void operator delete(void *ptr) { + ExFreePoolWithTag(ptr, 'RW'); + } + // fixme needs new and delete operator +}; + +// Our own Hashmap class - implemented as array of list entries +class HashMap { + // hash element array + PLIST_ENTRY arr[100]; + + ULONGLONG capacity; + // current size + ULONGLONG size; + // dummy node + +public: + HashMap() { + // Initial capacity of hash array + capacity = 100; + size = 0; + + // Initialise all elements of array as NULL + for (ULONGLONG i = 0; i < capacity; i++) { + arr[i] = new LIST_ENTRY; + InitializeListHead(arr[i]); + } + } + + ~HashMap() { + for (ULONGLONG i = 0; i < capacity; i++) { + delete (arr[i]); + } + } + + // This implements hash function to find index for a key + ULONGLONG hashCode(ULONGLONG key) { + return key % capacity; + } + + // Function to add key value pair + HANDLE insertNode(ULONGLONG key, HANDLE value) { + ULONGLONG hashIndex = hashCode(key); + + PLIST_ENTRY head = arr[hashIndex]; + PLIST_ENTRY iterator = head->Flink; + while (iterator != head) { // update + HashNode *pClass; + // + // Do some processing. + // + pClass = (HashNode *) CONTAINING_RECORD(iterator, HashNode, entry); + if (pClass->key == key) { + HANDLE val = pClass->value; + pClass->value = value; + return val; + } + iterator = iterator->Flink; + } + // insert, no key found + HashNode *temp = new HashNode(key, value); + InsertHeadList(head, &(temp->entry)); + size++; + return value; + } + + // Function to delete a key value pair + HANDLE deleteNode(ULONGLONG key) { + ULONGLONG hashIndex = hashCode(key); + + PLIST_ENTRY head = arr[hashIndex]; + PLIST_ENTRY iterator = head->Flink; + while (iterator != head) { + HashNode *pClass; + // + // Do some processing. + // + pClass = (HashNode *) CONTAINING_RECORD(iterator, HashNode, entry); + if (pClass->key == key) { + RemoveEntryList(iterator); + HANDLE value = pClass->value; + size--; + delete pClass; + return value; + } + iterator = iterator->Flink; + } + + // If not found return null + return NULL; + } + + // Function to search the value for a given key + HANDLE get(ULONGLONG key) { + ULONGLONG hashIndex = hashCode(key); + PLIST_ENTRY head = arr[hashIndex]; + PLIST_ENTRY iterator = head->Flink; + while (iterator != head) { + HashNode *pClass; + // + // Do some processing. + // + pClass = (HashNode *) CONTAINING_RECORD(iterator, HashNode, entry); + if (pClass->key == key) { + return pClass->value; + } + iterator = iterator->Flink; + } + + // If not found return null + return NULL; + } + + // Return current size + ULONGLONG sizeofMap() { + return size; + } + + // Return true if size is 0 + bool isEmpty() { + return size == 0; + } +}; \ No newline at end of file diff --git a/minifilter/snFilter/KernelCommon.cpp b/minifilter/snFilter/KernelCommon.cpp new file mode 100644 index 0000000..489ba16 --- /dev/null +++ b/minifilter/snFilter/KernelCommon.cpp @@ -0,0 +1,74 @@ +#include "KernelCommon.h" + +#define POOL_FLAG_NON_PAGED 0x0000000000000040UI64 // Non paged pool NX + +void *__cdecl operator new(size_t size) { + return ExAllocatePool2(POOL_FLAG_NON_PAGED, size, 'RW'); +} + +void __cdecl operator delete(void *data, size_t size) { + UNREFERENCED_PARAMETER(size); + if (data != NULL) + ExFreePoolWithTag(data, 'RW'); +} + +void __cdecl operator delete(void *data) { + if (data != NULL) + ExFreePoolWithTag(data, 'RW'); +} + +// FIXME: add count param for copy length, MAX_FILE_NAME_LENGTH - 1 is default value +NTSTATUS CopyWString(LPWSTR dest, LPCWSTR source, size_t size) { + INT err = wcsncpy_s(dest, size, source, MAX_FILE_NAME_LENGTH - 1); + if (err == 0) { + dest[size - 1] = L'\0'; + return STATUS_SUCCESS; + } else { + return STATUS_INTERNAL_ERROR; + } +} + +WCHAR *stristr(const WCHAR *String, const WCHAR *Pattern) { + WCHAR *pptr, *sptr, *start; + + for (start = (WCHAR *) String; *start != L'\0'; ++start) { + while (((*start != L'\0') && (RtlUpcaseUnicodeChar(*start) != RtlUpcaseUnicodeChar(*Pattern)))) { + ++start; + } + + if (L'\0' == *start) + return NULL; + + pptr = (WCHAR *) Pattern; + sptr = (WCHAR *) start; + + while (RtlUpcaseUnicodeChar(*sptr) == RtlUpcaseUnicodeChar(*pptr)) { + sptr++; + pptr++; + + if (L'\0' == *pptr) + return (start); + } + } + + return NULL; +} + +BOOLEAN startsWith(PUNICODE_STRING String, PWCHAR Pattern) { + if (String == NULL || Pattern == NULL) + return FALSE; + PWCHAR buffer = String->Buffer; + for (ULONG i = 0; i < wcslen(Pattern); i++) { + if (String->Length <= 2 * i) { + // DbgPrint("String ended before pattern, %d\n", i); + return FALSE; + } + if (RtlDowncaseUnicodeChar(Pattern[i]) != RtlDowncaseUnicodeChar(buffer[i])) { + // DbgPrint("Chars not eq: %d, %d\n", RtlDowncaseUnicodeChar(Pattern[i]), + // RtlDowncaseUnicodeChar(buffer[i])); + return FALSE; + } + // DbgPrint("Chars are eq: %d, %d\n", RtlDowncaseUnicodeChar(Pattern[i]), RtlDowncaseUnicodeChar(buffer[i])); + } + return TRUE; +} \ No newline at end of file diff --git a/minifilter/snFilter/KernelCommon.h b/minifilter/snFilter/KernelCommon.h new file mode 100644 index 0000000..196bfad --- /dev/null +++ b/minifilter/snFilter/KernelCommon.h @@ -0,0 +1,137 @@ +#pragma once + +#include + +#include "../SharedDefs/SharedDefs.h" + +// #define DEBUG_IRP +#ifdef DEBUG_IRP +#define IS_DEBUG_IRP 1 +#else +#define IS_DEBUG_IRP 0 +#endif // DEBUG_IRP + +#define POOL_FLAG_NON_PAGED 0x0000000000000040UI64 // Non paged pool NX + +// PID_ENTRY - for each process in the system we record, we get its pid and image file, those are stored in thi struct +// the struct is meant to be used in blist (LIST_ENTRY) +typedef struct _PID_ENTRY { + LIST_ENTRY entry; + PUNICODE_STRING Path; + ULONG Pid; + + _PID_ENTRY() { + Pid = 0; + Path = nullptr; + entry.Flink = nullptr; + entry.Blink = nullptr; + } + + void *operator new(size_t size) { + void *ptr = ExAllocatePool2(POOL_FLAG_NON_PAGED, size, 'RW'); + if (size && ptr != nullptr) + memset(ptr, 0, size); + return ptr; + } + + void operator delete(void *ptr) { + ExFreePoolWithTag(ptr, 'RW'); + } + + // fixme needs new and delete operator, dtor +} PID_ENTRY, *PPID_ENTRY; + +typedef struct _DIRECTORY_ENTRY { + LIST_ENTRY entry; + WCHAR path[MAX_FILE_NAME_LENGTH]; + + _DIRECTORY_ENTRY() { + InitializeListHead(&entry); + path[0] = L'\0'; + } + +} DIRECTORY_ENTRY, *PDIRECTORY_ENTRY; + +typedef struct _IRP_ENTRY { + LIST_ENTRY entry; + DRIVER_MESSAGE data; + UNICODE_STRING + filePath; // keep path to unicode string related to the object, we copy it later to user + WCHAR Buffer[MAX_FILE_NAME_LENGTH]; // unicode string buffer for file name + + _IRP_ENTRY() { + filePath.Length = 0; + filePath.MaximumLength = MAX_FILE_NAME_SIZE; + filePath.Buffer = Buffer; + RtlZeroBytes(Buffer, MAX_FILE_NAME_SIZE); + data.next = nullptr; + data.IRP_OP = IRP_NONE; + data.MemSizeUsed = 0; + data.isEntropyCalc = FALSE; + data.FileChange = FILE_CHANGE_NOT_SET; + data.FileLocationInfo = FILE_NOT_PROTECTED; + } + + void *operator new(size_t size) { + void *ptr = ExAllocatePool2(POOL_FLAG_NON_PAGED, size, 'RW'); + if (size && ptr != nullptr) + memset(ptr, 0, size); + return ptr; + } + + void operator delete(void *ptr) { + ExFreePoolWithTag(ptr, 'RW'); + } + +} IRP_ENTRY, *PIRP_ENTRY; + +void *__cdecl operator new(size_t size); + +void __cdecl operator delete(void *data, size_t size); + +void __cdecl operator delete(void *data); + +NTSTATUS CopyWString(LPWSTR dest, LPCWSTR source, size_t size); + +WCHAR *stristr(const WCHAR *String, const WCHAR *Pattern); + +BOOLEAN startsWith(PUNICODE_STRING String, PWCHAR Pattern); + +// GID_ENTRY - for each gid in the system we record, holds pids entries (PID_ENTRY) +// the struct is meant to be used in blist (LIST_ENTRY) +struct GID_ENTRY { + LIST_ENTRY GidListEntry; + ULONGLONG gid; + ULONGLONG pidsSize; + LIST_ENTRY HeadListPids; + + // gid as input + GID_ENTRY(ULONGLONG Gid) { + gid = Gid; + InitializeListHead(&HeadListPids); + InitializeListHead(&GidListEntry); + pidsSize = 0; + } + + // copy + GID_ENTRY(const GID_ENTRY &a) { + HeadListPids.Flink = a.HeadListPids.Flink; + HeadListPids.Blink = a.HeadListPids.Blink; + GidListEntry.Flink = a.GidListEntry.Flink; + GidListEntry.Blink = a.GidListEntry.Blink; + gid = a.gid; + pidsSize = a.pidsSize; + } + + const GID_ENTRY &operator=(const GID_ENTRY &a) { + HeadListPids.Flink = a.HeadListPids.Flink; + HeadListPids.Blink = a.HeadListPids.Blink; + GidListEntry.Flink = a.GidListEntry.Flink; + GidListEntry.Blink = a.GidListEntry.Blink; + gid = a.gid; + pidsSize = a.pidsSize; + this; + } +}; + +typedef GID_ENTRY *PGID_ENTRY; \ No newline at end of file diff --git a/minifilter/snFilter/KernelString.cpp b/minifilter/snFilter/KernelString.cpp new file mode 100644 index 0000000..ee0944e --- /dev/null +++ b/minifilter/snFilter/KernelString.cpp @@ -0,0 +1,60 @@ +#include "KernelString.h" + +#define POOL_FLAG_NON_PAGED 0x0000000000000040UI64 // Non paged pool NX + +NTSTATUS +FSAllocateUnicodeString(_Inout_ PUNICODE_STRING String) +/*++ + +Routine Description: + + This routine allocates a unicode string + +Arguments: + + String - supplies the size of the string to be allocated in the MaximumLength field + return the unicode string + +Return Value: + + STATUS_SUCCESS - success + STATUS_INSUFFICIENT_RESOURCES - failure + +--*/ +{ + String->Buffer = (PWCH) ExAllocatePool2(POOL_FLAG_NON_PAGED, String->MaximumLength, 'RW'); + + if (String->Buffer == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + String->Length = 0; + + return STATUS_SUCCESS; +} + +VOID FSFreeUnicodeString(_Inout_ PUNICODE_STRING String) +/*++ + +Routine Description: + + This routine frees a unicode string + +Arguments: + + String - supplies the string to be freed + +Return Value: + + None + +--*/ +{ + if (String->Buffer) { + ExFreePoolWithTag(String->Buffer, 'RW'); + String->Buffer = NULL; + } + + String->Length = String->MaximumLength = 0; + String->Buffer = NULL; +} \ No newline at end of file diff --git a/minifilter/snFilter/KernelString.h b/minifilter/snFilter/KernelString.h new file mode 100644 index 0000000..ee64595 --- /dev/null +++ b/minifilter/snFilter/KernelString.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +NTSTATUS +FSAllocateUnicodeString(_Inout_ PUNICODE_STRING String); + +VOID FSFreeUnicodeString(_Inout_ PUNICODE_STRING String); \ No newline at end of file diff --git a/minifilter/snFilter/ShanonEntropy.cpp b/minifilter/snFilter/ShanonEntropy.cpp new file mode 100644 index 0000000..ab06ff9 --- /dev/null +++ b/minifilter/snFilter/ShanonEntropy.cpp @@ -0,0 +1,37 @@ +#include "ShanonEntropy.h" + +constexpr DOUBLE +M_LOG2E = 1.4426950408889634; + +constexpr ULONG +MAX_BYTE_SIZE = 256; + +DOUBLE shannonEntropy(PUCHAR buffer, size_t size) { + if (IS_DEBUG_IRP) + DbgPrint("!!! snFilter: Calc entropy started\n"); + DOUBLE entropy = 0.0; + ULONG bucketByteVals[MAX_BYTE_SIZE] = {}; + for (ULONG i = 0; i < size; i++) { + bucketByteVals[buffer[i]]++; + } + + XSTATE_SAVE SaveState; + __try + { + KeSaveExtendedProcessorState(XSTATE_MASK_LEGACY, &SaveState); + for (ULONG i = 0; i < MAX_BYTE_SIZE; i++) { + if (bucketByteVals[i] != 0) { + DOUBLE + val = (DOUBLE) + bucketByteVals[i] / (DOUBLE) + size; + entropy += (-1) * val * log(val) * M_LOG2E; + } + } + } + __finally + { + KeRestoreExtendedProcessorState(&SaveState); + } + return entropy; +} \ No newline at end of file diff --git a/minifilter/snFilter/ShanonEntropy.h b/minifilter/snFilter/ShanonEntropy.h new file mode 100644 index 0000000..19cc182 --- /dev/null +++ b/minifilter/snFilter/ShanonEntropy.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +#include "KernelCommon.h" + +// entropy between 0.0 to 8.0 +DOUBLE shannonEntropy(PUCHAR buffer, size_t size); \ No newline at end of file diff --git a/minifilter/snFilter/snFilter.cpp b/minifilter/snFilter/snFilter.cpp new file mode 100644 index 0000000..f8bd60e --- /dev/null +++ b/minifilter/snFilter/snFilter.cpp @@ -0,0 +1,1344 @@ +#pragma clang diagnostic push +#pragma ide diagnostic ignored "UnreachableCode" +/*++ + +Module Name: + + snFilter.c + +Abstract: + + This is the main module of the snFilter miniFilter driver. + +Environment: + + Kernel mode + +--*/ + +#include "snFilter.h" + +#pragma prefast(disable : __WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers") + +#define POOL_FLAG_NON_PAGED 0x0000000000000040UI64 // Non paged pool NX + +// Structure that contains all the global data structures used throughout the driver. + +EXTERN_C_START + + NTSTATUS +DriverEntry(PDRIVER_OBJECT +DriverObject, +PUNICODE_STRING RegistryPath +); + +DRIVER_INITIALIZE DriverEntry; + +EXTERN_C_END + +// +// Constant FLT_REGISTRATION structure for our filter. This +// initializes the callback routines our filter wants to register +// for. This is only used to register with the filter manager +// + +CONST +FLT_OPERATION_REGISTRATION Callbacks[] = {//{IRP_MJ_CREATE, 0, FSPreOperation, FSPostOperation}, + {IRP_MJ_CLOSE, 0, FSPreOperation, FSPostOperation}, + {IRP_MJ_READ, 0, FSPreOperation, FSPostOperation}, + //{IRP_MJ_CLEANUP, 0, FSPreOperation, NULL}, + {IRP_MJ_WRITE, 0, FSPreOperation, NULL}, + //{IRP_MJ_SET_INFORMATION, 0, FSPreOperation, NULL}, + {IRP_MJ_OPERATION_END}}; + +/*++ + +FilterRegistration Defines what we want to filter with the driver + +--*/ +CONST FLT_REGISTRATION +FilterRegistration = { + sizeof(FLT_REGISTRATION), // Size + FLT_REGISTRATION_VERSION, // Version + 0, // Flags + NULL, // Context Registration. + Callbacks, // Operation callbacks + FSUnloadDriver, // FilterUnload + FSInstanceSetup, // InstanceSetup + FSInstanceQueryTeardown, // InstanceQueryTeardown + FSInstanceTeardownStart, // InstanceTeardownStart + FSInstanceTeardownComplete, // InstanceTeardownComplete + NULL, // GenerateFileName + NULL, // GenerateDestinationFileName + NULL // NormalizeNameComponent +}; + +//////////////////////////////////////////////////////////////////////////// +// +// Filter initialization and unload routines. +// +//////////////////////////////////////////////////////////////////////////// + +NTSTATUS + DriverEntry(PDRIVER_OBJECT +DriverObject, +PUNICODE_STRING RegistryPath +) +/*++ + +Routine Description: + + This is the initialization routine for the Filter driver. This + registers the Filter with the filter manager and initializes all + its global data structures. + +Arguments: + + DriverObject - Pointer to driver object created by the system to + represent this driver. + + RegistryPath - Unicode string identifying where the parameters for this + driver are located in the registry. + +Return Value: + + Returns STATUS_SUCCESS. +--*/ +{ +UNREFERENCED_PARAMETER(RegistryPath); +NTSTATUS status; + +// +// Default to NonPagedPoolNx for non paged pool allocations where supported. +// + +ExInitializeDriverRuntime(DrvRtPoolNxOptIn); + +// +// Register with filter manager. +// + +driverData = new DriverData(DriverObject); +if (driverData == NULL) +{ +return +STATUS_MEMORY_NOT_ALLOCATED; +} + +PFLT_FILTER *FilterAdd = driverData->getFilterAdd(); + +status = FltRegisterFilter(DriverObject, &FilterRegistration, FilterAdd); + +if (! +NT_SUCCESS(status) +) +{ +delete +driverData; +return +status; +} + +commHandle = new CommHandler(driverData->getFilter()); +if (commHandle == NULL) +{ +delete +driverData; +return +STATUS_MEMORY_NOT_ALLOCATED; +} + +status = InitCommData(); + +if (! +NT_SUCCESS(status) +) +{ +FltUnregisterFilter(driverData +-> + +getFilter() + +); +delete +driverData; +delete +commHandle; +return +status; +} +// +// Start filtering I/O. +// +status = FltStartFiltering(driverData->getFilter()); + +if (! +NT_SUCCESS(status) +) +{ +CommClose(); + +FltUnregisterFilter(driverData +-> + +getFilter() + +); +delete +driverData; +delete +commHandle; +return +status; +} +driverData-> + +setFilterStart(); + +DbgPrint("loaded scanner successfully"); +// new code +// TODO: check status and release in unload +PsSetCreateProcessNotifyRoutine(AddRemProcessRoutine, FALSE +); +return +STATUS_SUCCESS; +} + +NTSTATUS +FSUnloadDriver(_In_ FLT_FILTER_UNLOAD_FLAGS Flags) +/*++ + +Routine Description: + + This is to unload routine for the Filter driver. This unregisters the + Filter with the filter manager and frees any allocated global data + structures. + +Arguments: + + None. + +Return Value: + + Returns the final status of the de-allocation routines. + +--*/ +{ + UNREFERENCED_PARAMETER(Flags); + + // + // Close the server port. + // + driverData->setFilterStop(); + CommClose(); + + // + // Unregister the filter + // + + FltUnregisterFilter(driverData->getFilter()); + delete driverData; + delete commHandle; + PsSetCreateProcessNotifyRoutine(AddRemProcessRoutine, TRUE); + return STATUS_SUCCESS; +} + +NTSTATUS +FSInstanceSetup(_In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_SETUP_FLAGS Flags, + _In_ DEVICE_TYPE VolumeDeviceType, _In_ FLT_FILESYSTEM_TYPE VolumeFilesystemType) +/*++ + +Routine Description: + +This routine is called whenever a new instance is created on a volume. This +gives us a chance to decide if we need to attach to this volume or not. + +If this routine is not defined in the registration structure, automatic +instances are always created. + +Arguments: + +FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing +opaque handles to this filter, instance and its associated volume. + +Flags - Flags describing the reason for this attach request. + +Return Value: + +STATUS_SUCCESS - attach +STATUS_FLT_DO_NOT_ATTACH - do not attach + +--*/ +{ + UNREFERENCED_PARAMETER(FltObjects); + UNREFERENCED_PARAMETER(Flags); + UNREFERENCED_PARAMETER(VolumeDeviceType); + UNREFERENCED_PARAMETER(VolumeFilesystemType); + + DbgPrint("snFilter: Entered FSInstanceSetup\n"); + + WCHAR newTemp[40]; + + GvolumeData.MaximumLength = 80; + GvolumeData.Buffer = newTemp; + GvolumeData.Length = 0; + + NTSTATUS hr = STATUS_SUCCESS; + PDEVICE_OBJECT devObject; + hr = FltGetDiskDeviceObject(FltObjects->Volume, &devObject); + if (!NT_SUCCESS(hr)) { + return STATUS_SUCCESS; + // return hr; + } + hr = IoVolumeDeviceToDosName(devObject, &GvolumeData); + if (!NT_SUCCESS(hr)) { + // return STATUS_SUCCESS; + + return hr; + } + return STATUS_SUCCESS; +} + +NTSTATUS +FSInstanceQueryTeardown(_In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags) +/*++ + +Routine Description: + +This is called when an instance is being manually deleted by a +call to FltDetachVolume or FilterDetach thereby giving us a +chance to fail that detach request. + +If this routine is not defined in the registration structure, explicit +detach requests via FltDetachVolume or FilterDetach will always be +failed. + +Arguments: + +FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing +opaque handles to this filter, instance and its associated volume. + +Flags - Indicating where this detach request came from. + +Return Value: + +Returns the status of this operation. + +--*/ +{ + UNREFERENCED_PARAMETER(FltObjects); + UNREFERENCED_PARAMETER(Flags); + + DbgPrint("snFilter: Entered FSInstanceQueryTeardown\n"); + + return STATUS_SUCCESS; +} + +VOID FSInstanceTeardownStart(_In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags) +/*++ + +Routine Description: + +This routine is called at the start of instance teardown. + +Arguments: + +FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing +opaque handles to this filter, instance and its associated volume. + +Flags - Reason why this instance is being deleted. + +Return Value: + +None. + +--*/ +{ + UNREFERENCED_PARAMETER(FltObjects); + UNREFERENCED_PARAMETER(Flags); + + DbgPrint("snFilter: Entered FSInstanceTeardownStart\n"); +} + +VOID FSInstanceTeardownComplete(_In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags) +/*++ + +Routine Description: + +This routine is called at the end of instance teardown. + +Arguments: + +FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing +opaque handles to this filter, instance and its associated volume. + +Flags - Reason why this instance is being deleted. + +Return Value: + +None. + +--*/ +{ + UNREFERENCED_PARAMETER(FltObjects); + UNREFERENCED_PARAMETER(Flags); + DbgPrint("snFilter: Entered FSInstanceTeardownComplete\n"); +} + +FLT_PREOP_CALLBACK_STATUS +FSPreOperation(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID + +*CompletionContext) +/*++ + +Routine Description: + + Pre operations callback + +Arguments: + + Data - The structure which describes the operation parameters. + + FltObject - The structure which describes the objects affected by this + operation. + + CompletionContext - Output parameter which can be used to pass a context + from this pre-create callback to the post-create callback. + +Return Value: + + FLT_PREOP_SUCCESS_WITH_CALLBACK - If this is not our user-mode process. + FLT_PREOP_SUCCESS_NO_CALLBACK - All other threads. + +--*/ +{ +NTSTATUS hr = STATUS_SUCCESS; +// See if this create is being done by our user process. +if ( +FltGetRequestorProcessId(Data) +== 4) +return +FLT_PREOP_SUCCESS_NO_CALLBACK; // system process - skip +if ( +FltGetRequestorProcessId(Data) +== driverData-> + +getPID() + +) +{ +if (IS_DEBUG_IRP) +DbgPrint("!!! snFilter: Allowing pre op for trusted process, no post op\n"); + +return +FLT_PREOP_SUCCESS_NO_CALLBACK; +} +if (FltObjects->FileObject == NULL) +{ // no file object +return +FLT_PREOP_SUCCESS_NO_CALLBACK; +} +// create tested only on post op, can't check here +if (Data->Iopb->MajorFunction == IRP_MJ_CREATE) +{ +return +FLT_PREOP_SUCCESS_WITH_CALLBACK; +} +hr = FSProcessPreOperartion(Data, FltObjects, CompletionContext); +if (hr == FLT_PREOP_SUCCESS_WITH_CALLBACK) +return +FLT_PREOP_SUCCESS_WITH_CALLBACK; + +return +FLT_PREOP_SUCCESS_NO_CALLBACK; +} + +NTSTATUS +FSProcessPreOperartion(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID + +*CompletionContext) +{ +// no communication +if (driverData-> + +isFilterClosed() + +|| + +IsCommClosed() + +) +{ +// DbgPrint("!!! snFilter: Filter is closed or Port is closed, skipping data\n"); +return +FLT_PREOP_SUCCESS_NO_CALLBACK; +} +NTSTATUS hr = FLT_PREOP_SUCCESS_NO_CALLBACK; + +PFLT_FILE_NAME_INFORMATION nameInfo; +hr = FltGetFileNameInformation(Data, FLT_FILE_NAME_OPENED | FLT_FILE_NAME_QUERY_ALWAYS_ALLOW_CACHE_LOOKUP, + &nameInfo); +if (! +NT_SUCCESS(hr) +) +return +hr; + +BOOLEAN isDir; +hr = FltIsDirectory(Data->Iopb->TargetFileObject, Data->Iopb->TargetInstance, &isDir); +if (! +NT_SUCCESS(hr) +) +return +hr; +if (isDir) +return +FLT_PREOP_SUCCESS_NO_CALLBACK; +PIRP_ENTRY newEntry = new IRP_ENTRY(); +if (newEntry == NULL) +{ +FltReferenceFileNameInformation(nameInfo); +return +hr; +} +// reset +PDRIVER_MESSAGE newItem = &newEntry->data; +PUNICODE_STRING FilePath = &(newEntry->filePath); + +hr = GetFileNameInfo(FltObjects, FilePath, nameInfo); +if (! +NT_SUCCESS(hr) +) +{ +FltReferenceFileNameInformation(nameInfo); +delete +newEntry; +return +hr; +} + +// get pid +newItem-> +PID = FltGetRequestorProcessId(Data); + +BOOLEAN isGidFound; +ULONGLONG gid = driverData->GetProcessGid(newItem->PID, &isGidFound); +if (gid == 0 || !isGidFound) +{ +if (IS_DEBUG_IRP) +DbgPrint("!!! snFilter: Item does not have a gid, skipping\n"); +FltReferenceFileNameInformation(nameInfo); +delete +newEntry; +return +FLT_PREOP_SUCCESS_NO_CALLBACK; +} +newItem-> +Gid = gid; + +if (IS_DEBUG_IRP) +DbgPrint("!!! snFilter: Registering new irp for Gid: %d with pid: %d\n", gid, newItem->PID); + +// get file id +hr = CopyFileIdInfo(Data, newItem); +if (! +NT_SUCCESS(hr) +) +{ +FltReferenceFileNameInformation(nameInfo); +delete +newEntry; +return +hr; +} + +if ( +FSIsFileNameInScanDirs(FilePath) +) +{ +if (IS_DEBUG_IRP) +DbgPrint("!!! snFilter: File in scan area \n"); +newItem-> +FileLocationInfo = FILE_PROTECTED; +} + +if (Data->Iopb->MajorFunction == IRP_MJ_READ || Data->Iopb->MajorFunction == IRP_MJ_WRITE) +{ +CopyExtension(newItem +->Extension, nameInfo); +} + +if (IS_DEBUG_IRP) +DbgPrint("!!! snFilter: Logging IRP op: %s \n", +FltGetIrpName(Data +->Iopb->MajorFunction)); + +if (Data->Iopb->MajorFunction != IRP_MJ_SET_INFORMATION) +FltReleaseFileNameInformation(nameInfo); + +switch (Data->Iopb->MajorFunction) +{ +// create is handled on post operation, read is created here but calculated on post(data avilable +case IRP_MJ_READ: { +newItem-> +IRP_OP = IRP_READ; +if (Data->Iopb->Parameters.Read.Length == 0) // no data to read +{ +delete +newEntry; +DbgPrint("snFilter: IRP READ NOCALLBACK LENGTH IS ZERO! \n"); +return +FLT_PREOP_SUCCESS_NO_CALLBACK; +} +if (IS_DEBUG_IRP) +DbgPrint("!!! snFilter: Preop IRP_MJ_READ, return with postop \n"); +// save context for post, we calculate the entropy of read, we pass the irp to application on post op +* +CompletionContext = newEntry; +DbgPrint("snFilter: IRP READ WITH CALLBACK! ****************** \n"); +return +FLT_PREOP_SUCCESS_WITH_CALLBACK; +} +case IRP_MJ_CLEANUP: +newItem-> +IRP_OP = IRP_CLEANUP; +break; +case IRP_MJ_WRITE: { +newItem-> +IRP_OP = IRP_WRITE; +// if (newItem->FileLocationInfo == FILE_NOT_PROTECTED) { +// delete newEntry; +// return FLT_PREOP_SUCCESS_NO_CALLBACK; +// } +newItem-> +FileChange = FILE_CHANGE_WRITE; +PVOID writeBuffer = NULL; +if (Data->Iopb->Parameters.Write.Length == 0) // no data to write +{ +break; +} + +// prepare buffer for entropy calc +if (Data->Iopb->Parameters.Write.MdlAddress == NULL) +{ // there's mdl buffer, we use it +writeBuffer = Data->Iopb->Parameters.Write.WriteBuffer; +} +else +{ +writeBuffer = MmGetSystemAddressForMdlSafe(Data->Iopb->Parameters.Write.MdlAddress, + NormalPagePriority | MdlMappingNoExecute); +} +if (writeBuffer == NULL) +{ // alloc failed +delete +newEntry; +// fail the irp request +Data->IoStatus. +Status = STATUS_INSUFFICIENT_RESOURCES; +Data->IoStatus. +Information = 0; +return +FLT_PREOP_COMPLETE; +} +newItem-> +MemSizeUsed = Data->Iopb->Parameters.Write.Length; +// we catch EXCEPTION_EXECUTE_HANDLER so to prevent crash when calculating +__try +{ +newItem-> +Entropy = shannonEntropy((PUCHAR) writeBuffer, newItem->MemSizeUsed); +newItem-> +isEntropyCalc = TRUE; +} +__except (EXCEPTION_EXECUTE_HANDLER) + { + if (IS_DEBUG_IRP) + DbgPrint("!!! snFilter: Failed to calc entropy\n"); + delete newEntry; + // fail the irp request + Data->IoStatus.Status = STATUS_INTERNAL_ERROR; + Data->IoStatus.Information = 0; + return FLT_PREOP_COMPLETE; + } +} +break; +case IRP_MJ_SET_INFORMATION: { +newItem-> +IRP_OP = IRP_SETINFO; +// we check for delete later and renaming +FILE_INFORMATION_CLASS fileInfo = Data->Iopb->Parameters.SetFileInformation.FileInformationClass; + +if (fileInfo == +FileDispositionInformation && // handle delete later +(((PFILE_DISPOSITION_INFORMATION)(Data->Iopb->Parameters.SetFileInformation.InfoBuffer) +)->DeleteFile)) +{ +newItem-> +FileChange = FILE_CHANGE_DELETE_FILE; +} // end delete 1 + +else if (fileInfo == +FileDispositionInformationEx && + FlagOn( + ((PFILE_DISPOSITION_INFORMATION_EX)(Data->Iopb->Parameters.SetFileInformation.InfoBuffer))->Flags, + FILE_DISPOSITION_DELETE) +) +{ +newItem-> +FileChange = FILE_CHANGE_DELETE_FILE; +} // end delete 2 + +else if (fileInfo == FileRenameInformation || fileInfo == FileRenameInformationEx) +{ +// OPTIONAL: get new name? + +newItem-> +FileChange = FILE_CHANGE_RENAME_FILE; +PFILE_RENAME_INFORMATION renameInfo = + (PFILE_RENAME_INFORMATION) Data->Iopb->Parameters.SetFileInformation.InfoBuffer; +PFLT_FILE_NAME_INFORMATION newNameInfo; +WCHAR Buffer[MAX_FILE_NAME_LENGTH]; +UNICODE_STRING NewFilePath; +NewFilePath. +Buffer = Buffer; +NewFilePath. +Length = 0; +NewFilePath. +MaximumLength = MAX_FILE_NAME_SIZE; + +hr = FltGetDestinationFileNameInformation( + FltObjects->Instance, FltObjects->FileObject, renameInfo->RootDirectory, renameInfo->FileName, + renameInfo->FileNameLength, + FLT_FILE_NAME_QUERY_DEFAULT | FLT_FILE_NAME_REQUEST_FROM_CURRENT_PROVIDER | FLT_FILE_NAME_OPENED, + &newNameInfo); +if (! +NT_SUCCESS(hr) +) +{ +delete +newEntry; +FltReleaseFileNameInformation(nameInfo); +return +hr; +} + +NTSTATUS status = GetFileNameInfo(FltObjects, &NewFilePath, newNameInfo); +if (! +NT_SUCCESS(status) +) +{ +delete +newEntry; +FltReleaseFileNameInformation(nameInfo); +FltReleaseFileNameInformation(newNameInfo); +return +FLT_PREOP_SUCCESS_NO_CALLBACK; +} + +RtlCopyBytes(newEntry +->Buffer, Buffer, +MAX_FILE_NAME_SIZE); // replace buffer data with new file +newItem-> +FileLocationInfo = FILE_MOVED_OUT; +/* +if (FSIsFileNameInScanDirs(&NewFilePath)) { +if (newItem->FileLocationInfo == FILE_NOT_PROTECTED) { // moved in - report new file name +newItem->FileLocationInfo = FILE_MOVED_IN; +//newEntry->filePath = NewFilePath; // remember file moved in +RtlCopyBytes(newEntry->Buffer, Buffer, MAX_FILE_NAME_SIZE); // replace buffer data with new file +} // else we still report old file name so we know it was changed +} +else { // new file name not protected +if (newItem->FileLocationInfo == FILE_PROTECTED) { // moved out - report old file name +newItem->FileLocationInfo = FILE_MOVED_OUT; +} +/*else { // we dont care - rename of file in unprotected area to unprotected area +delete newEntry; +FltReleaseFileNameInformation(nameInfo); +FltReleaseFileNameInformation(newNameInfo); +return FLT_PREOP_SUCCESS_NO_CALLBACK; +} +} +*/ + +CopyExtension(newItem +->Extension, newNameInfo); +FltReleaseFileNameInformation(newNameInfo); +for ( +LONG i = 0; +i < FILE_OBJEC_MAX_EXTENSION_SIZE; i++) +{ +if (i == (nameInfo->Extension.Length / 2)) +break; +if (newItem->Extension[i] != nameInfo->Extension.Buffer[i]) +{ +newItem-> +FileChange = FILE_CHANGE_EXTENSION_CHANGED; +break; +} +} +FltReleaseFileNameInformation(nameInfo); +} // end rename +else // not rename or delete (set info) +{ +delete +newEntry; +FltReleaseFileNameInformation(nameInfo); +return +FLT_PREOP_SUCCESS_NO_CALLBACK; +} +break; +} +default: +delete +newEntry; +return +FLT_PREOP_SUCCESS_NO_CALLBACK; +} +if (IS_DEBUG_IRP) +DbgPrint("!!! snFilter: Adding entry to irps %s\n", +FltGetIrpName(Data +->Iopb->MajorFunction)); +if (!driverData-> +AddIrpMessage(newEntry) +) +{ +delete +newEntry; +} +return +FLT_PREOP_SUCCESS_NO_CALLBACK; +} + +FLT_POSTOP_CALLBACK_STATUS +FSPostOperation(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags) +/*++ + +Routine Description: + + Post operation callback. we reach here in case of IRP_MJ_CREATE or IRP_MJ_READ + +Arguments: + + Data - The structure which describes the operation parameters. + + FltObject - The structure which describes the objects affected by this + operation. + + CompletionContext - The operation context passed fron the pre-create + callback. + + Flags - Flags to say why we are getting this post-operation callback. + +Return Value: + + FLT_POSTOP_FINISHED_PROCESSING - ok to open the file or we wish to deny + access to this file, hence undo the open + +--*/ +{ + // DbgPrint("!!! snFilter: Enter post op for irp: %s, pid of process: %u\n", + // FltGetIrpName(Data->Iopb->MajorFunction), FltGetRequestorProcessId(Data)); + if (!NT_SUCCESS(Data->IoStatus.Status) || (STATUS_REPARSE == Data->IoStatus.Status)) { + // DbgPrint("!!! snFilter: finished post operation, already failed \n"); + if (CompletionContext != nullptr && Data->Iopb->MajorFunction == IRP_MJ_READ) { + delete (PIRP_ENTRY) CompletionContext; + } + return FLT_POSTOP_FINISHED_PROCESSING; + } + + if (Data->Iopb->MajorFunction == IRP_MJ_CREATE) { + return FSProcessCreateIrp(Data, FltObjects); + } else if (Data->Iopb->MajorFunction == IRP_MJ_READ) { + // return FLT_POSTOP_FINISHED_PROCESSING; + return FSProcessPostReadIrp(Data, FltObjects, CompletionContext, Flags); + } + return FLT_POSTOP_FINISHED_PROCESSING; +} + +FLT_POSTOP_CALLBACK_STATUS +FSProcessCreateIrp(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects) { + NTSTATUS hr; + if (FlagOn(Data->Iopb->OperationFlags, SL_OPEN_TARGET_DIRECTORY) || + FlagOn(Data->Iopb->OperationFlags, SL_OPEN_PAGING_FILE)) { + return FLT_POSTOP_FINISHED_PROCESSING; + } + + if (driverData->isFilterClosed() || IsCommClosed()) { + // DbgPrint("!!! snFilter: filter closed or comm closed, skip irp\n"); + return FLT_POSTOP_FINISHED_PROCESSING; + } + + BOOLEAN isDir; + hr = FltIsDirectory(Data->Iopb->TargetFileObject, Data->Iopb->TargetInstance, &isDir); + if (!NT_SUCCESS(hr)) { + return FLT_POSTOP_FINISHED_PROCESSING; + } + + PFLT_FILE_NAME_INFORMATION nameInfo; + hr = FltGetFileNameInformation(Data, FLT_FILE_NAME_OPENED | FLT_FILE_NAME_QUERY_ALWAYS_ALLOW_CACHE_LOOKUP, + &nameInfo); + if (!NT_SUCCESS(hr)) { + return FLT_POSTOP_FINISHED_PROCESSING; + } + + PIRP_ENTRY newEntry = new IRP_ENTRY(); + if (newEntry == NULL) { + FltReleaseFileNameInformation(nameInfo); + return FLT_POSTOP_FINISHED_PROCESSING; + } + PDRIVER_MESSAGE newItem = &newEntry->data; + + newItem->PID = FltGetRequestorProcessId(Data); + newItem->IRP_OP = IRP_CREATE; + newItem->FileLocationInfo = FILE_PROTECTED; + PUNICODE_STRING FilePath = &(newEntry->filePath); + + BOOLEAN isGidFound; + ULONGLONG gid = driverData->GetProcessGid(newItem->PID, &isGidFound); + if (gid == 0 || !isGidFound) { + // DbgPrint("!!! snFilter: Item does not have a gid, skipping\n"); // TODO: incase it doesnt exist we can add it + // with our method that checks for system process + FltReferenceFileNameInformation(nameInfo); + delete newEntry; + return FLT_POSTOP_FINISHED_PROCESSING; + } + newItem->Gid = gid; + DbgPrint("!!! snFilter: Registering new irp for Gid: %d with pid: %d\n", gid, + newItem->PID); // TODO: incase it doesnt exist we can add it with our method that checks for system process + + // get file id + hr = CopyFileIdInfo(Data, newItem); + if (!NT_SUCCESS(hr)) { + delete newEntry; + return FLT_POSTOP_FINISHED_PROCESSING; + } + + hr = GetFileNameInfo(FltObjects, FilePath, nameInfo); + if (!NT_SUCCESS(hr)) { + delete newEntry; + return FLT_POSTOP_FINISHED_PROCESSING; + } + + CopyExtension(newItem->Extension, nameInfo); + + FltReleaseFileNameInformation(nameInfo); + + /* + if (!FSIsFileNameInScanDirs(FilePath)) { + if (IS_DEBUG_IRP) DbgPrint("!!! snFilter: Skipping uninterented file, not in scan area \n"); + delete newEntry; + return FLT_POSTOP_FINISHED_PROCESSING; + } + */ + + if (isDir && (Data->IoStatus.Information) == FILE_OPENED) { + if (IS_DEBUG_IRP) + DbgPrint("!!! snFilter: Dir listing opened on existing directory\n"); + newItem->FileChange = FILE_OPEN_DIRECTORY; + } else if (isDir) { + if (IS_DEBUG_IRP) + DbgPrint("!!! snFilter: Dir but not listing, not important \n"); + delete newEntry; + return FLT_POSTOP_FINISHED_PROCESSING; + } else if ((Data->IoStatus.Information) == FILE_OVERWRITTEN || (Data->IoStatus.Information) == FILE_SUPERSEDED) { + newItem->FileChange = FILE_CHANGE_OVERWRITE_FILE; + } else if (FlagOn(Data->Iopb->Parameters.Create.Options, FILE_DELETE_ON_CLOSE)) { + newItem->FileChange = FILE_CHANGE_DELETE_FILE; + if ((Data->IoStatus.Information) == FILE_CREATED) { + newItem->FileChange = FILE_CHANGE_DELETE_NEW_FILE; + } + } else if ((Data->IoStatus.Information) == FILE_CREATED) { + newItem->FileChange = FILE_CHANGE_NEW_FILE; + } + if (IS_DEBUG_IRP) + DbgPrint("!!! snFilter: Adding entry to irps\n"); + if (!driverData->AddIrpMessage(newEntry)) { + delete newEntry; + } + return FLT_POSTOP_FINISHED_PROCESSING; +} + +FLT_POSTOP_CALLBACK_STATUS +FSProcessPostReadIrp(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags) { + if (CompletionContext == NULL) { + return FLT_POSTOP_FINISHED_PROCESSING; + } + + PIRP_ENTRY entry = (PIRP_ENTRY) CompletionContext; + + if (driverData->isFilterClosed() || IsCommClosed()) { + if (IS_DEBUG_IRP) + DbgPrint("!!! snFilter: Post op read, comm or filter closed\n"); + delete entry; + return FLT_POSTOP_FINISHED_PROCESSING; + } + + FLT_POSTOP_CALLBACK_STATUS status = FLT_POSTOP_FINISHED_PROCESSING; + + PVOID ReadBuffer = NULL; + + // prepare buffer for entropy calc + if (Data->Iopb->Parameters.Read.MdlAddress != NULL) { // there's mdl buffer, we use it + ReadBuffer = MmGetSystemAddressForMdlSafe(Data->Iopb->Parameters.Read.MdlAddress, + NormalPagePriority | MdlMappingNoExecute); + } else if (FlagOn(Data->Flags, FLTFL_CALLBACK_DATA_SYSTEM_BUFFER)) // safe + { + ReadBuffer = Data->Iopb->Parameters.Read.ReadBuffer; + } else { + if (FltDoCompletionProcessingWhenSafe(Data, FltObjects, CompletionContext, Flags, FSProcessPostReadSafe, + &status)) { // post to worker thread or run if irql is ok + return FLT_POSTOP_FINISHED_PROCESSING; + } else { + Data->IoStatus.Status = STATUS_INTERNAL_ERROR; + Data->IoStatus.Information = 0; + delete entry; + return status; + } + } + if (!ReadBuffer) { + delete entry; + Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; + Data->IoStatus.Information = 0; + return FLT_POSTOP_FINISHED_PROCESSING; + } + entry->data.MemSizeUsed = (ULONG) Data->IoStatus.Information; // successful read data + // we catch EXCEPTION_EXECUTE_HANDLER so to prevent crash when calculating + __try + { + entry->data.Entropy = shannonEntropy((PUCHAR) ReadBuffer, Data->IoStatus.Information); + entry->data.isEntropyCalc = TRUE; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + delete entry; + // fail the irp request + Data->IoStatus.Status = STATUS_INTERNAL_ERROR; + Data->IoStatus.Information = 0; + return FLT_POSTOP_FINISHED_PROCESSING; + } + if (IS_DEBUG_IRP) + DbgPrint("!!! snFilter: Adding entry to irps IRP_MJ_READ\n"); + if (!driverData->AddIrpMessage(entry)) { + delete entry; + } + return FLT_POSTOP_FINISHED_PROCESSING; +} + +FLT_POSTOP_CALLBACK_STATUS +FSProcessPostReadSafe(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags) { + UNREFERENCED_PARAMETER(Flags); + UNREFERENCED_PARAMETER(FltObjects); + + NTSTATUS status = STATUS_SUCCESS; + PIRP_ENTRY entry = (PIRP_ENTRY) CompletionContext; + ASSERT(entry != nullptr); + status = FltLockUserBuffer(Data); + if (NT_SUCCESS(status)) { + PVOID ReadBuffer = MmGetSystemAddressForMdlSafe(Data->Iopb->Parameters.Read.MdlAddress, + NormalPagePriority | MdlMappingNoExecute); + if (ReadBuffer != NULL) { + __try + { + entry->data.Entropy = shannonEntropy((PUCHAR) ReadBuffer, Data->IoStatus.Information); + entry->data.MemSizeUsed = Data->IoStatus.Information; + entry->data.isEntropyCalc = TRUE; + if (IS_DEBUG_IRP) + DbgPrint("!!! snFilter: Adding entry to irps IRP_MJ_READ\n"); + if (driverData->AddIrpMessage(entry)) { + return FLT_POSTOP_FINISHED_PROCESSING; + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + status = STATUS_INTERNAL_ERROR; + } + } + status = STATUS_INSUFFICIENT_RESOURCES; + } + delete entry; + return FLT_POSTOP_FINISHED_PROCESSING; +} + +BOOLEAN + FSIsFileNameInScanDirs(CONST +PUNICODE_STRING path +) +{ +// ASSERT(driverData != NULL); +return driverData-> +IsContainingDirectory(path); +} + +NTSTATUS + FSEntrySetFileName(CONST +PFLT_VOLUME Volume, PFLT_FILE_NAME_INFORMATION +nameInfo, +PUNICODE_STRING uString +) +{ +NTSTATUS hr = STATUS_SUCCESS; +PDEVICE_OBJECT devObject; +USHORT volumeDosNameSize; +USHORT finalNameSize; +USHORT volumeNameSize = nameInfo->Volume.Length; // in bytes +USHORT origNameSize = nameInfo->Name.Length; // in bytes + +WCHAR newTemp[40]; + +UNICODE_STRING volumeData; +volumeData. +MaximumLength = 80; +volumeData. +Buffer = newTemp; +volumeData. +Length = 0; + +hr = FltGetDiskDeviceObject(Volume, &devObject); +if (! +NT_SUCCESS(hr) +) +{ +return +hr; +} +/*if (KeAreAllApcsDisabled()) { +return hr; +}*/ + +if (! + +KeAreAllApcsDisabled() + +) +{ +hr = IoVolumeDeviceToDosName(devObject, &GvolumeData); +} +volumeDosNameSize = GvolumeData.Length; +finalNameSize = origNameSize - volumeNameSize + volumeDosNameSize; // not null terminated, in bytes + +// DbgPrint("Volume name: %wZ, Size: %d, finalNameSize: %d, volumeNameSize: %d\n", volumeData, volumeDosNameSize, +// finalNameSize, volumeNameSize); DbgPrint("Name buffer: %wZ\n", nameInfo->Name); + +if (uString == NULL) +{ +ObDereferenceObject(devObject); +return +STATUS_INVALID_ADDRESS; +} +if (volumeNameSize == origNameSize) +{ // file is the volume, don't need to do anything +ObDereferenceObject(devObject); +return +RtlUnicodeStringCopy(uString, &nameInfo +->Name); +} + +if ( +NT_SUCCESS(hr = RtlUnicodeStringCopy(uString, &GvolumeData) +)) +{ // prefix of volume e.g. C: + +// DbgPrint("File name: %wZ\n", uString); +RtlCopyMemory(uString +->Buffer + (volumeDosNameSize / 2), nameInfo->Name.Buffer + (volumeNameSize / 2), +((finalNameSize - volumeDosNameSize > MAX_FILE_NAME_SIZE - volumeDosNameSize) +? (MAX_FILE_NAME_SIZE - volumeDosNameSize) +: (finalNameSize - volumeDosNameSize))); +uString-> +Length = (finalNameSize > MAX_FILE_NAME_SIZE) ? MAX_FILE_NAME_SIZE : finalNameSize; +// DbgPrint("File name: %wZ\n", uString); +} +ObDereferenceObject(devObject); +return +hr; +} + +NTSTATUS +CopyFileIdInfo(_Inout_ PFLT_CALLBACK_DATA Data, PDRIVER_MESSAGE newItem) { + FILE_ID_INFORMATION fileInformation; + NTSTATUS hr = FltQueryInformationFile(Data->Iopb->TargetInstance, Data->Iopb->TargetFileObject, &fileInformation, + sizeof(FILE_ID_INFORMATION), FileIdInformation, NULL); + RtlCopyMemory(&(newItem->FileID), &fileInformation, sizeof(FILE_ID_INFORMATION)); + return hr; +} + +NTSTATUS GetFileNameInfo(_In_ PCFLT_RELATED_OBJECTS FltObjects, PUNICODE_STRING FilePath, + PFLT_FILE_NAME_INFORMATION nameInfo) { + NTSTATUS hr; + hr = FltParseFileNameInformation(nameInfo); + if (!NT_SUCCESS(hr)) { + FltReleaseFileNameInformation(nameInfo); + return hr; + } + hr = FSEntrySetFileName(FltObjects->Volume, nameInfo, FilePath); + // DbgPrint("!!!snFilter DEBUG EntryFileName %d \n", NT_SUCCESS(hr)); + if (!NT_SUCCESS(hr)) { + FltReleaseFileNameInformation(nameInfo); + } + return hr; +} + +VOID CopyExtension(PWCHAR dest, PFLT_FILE_NAME_INFORMATION nameInfo) { + if (IS_DEBUG_IRP) + DbgPrint("!!! snFilter: copying the file type extension, extension length: %d, name: %wZ\n", + nameInfo->Extension.Length, nameInfo->Extension); + RtlZeroBytes(dest, (FILE_OBJEC_MAX_EXTENSION_SIZE + 1) * sizeof(WCHAR)); + for (LONG i = 0; i < FILE_OBJEC_MAX_EXTENSION_SIZE; i++) { + if (i == (nameInfo->Extension.Length / 2)) + break; + dest[i] = nameInfo->Extension.Buffer[i]; + } +} + +static NTSTATUS GetProcessNameByHandle(_In_ HANDLE ProcessHandle, _Out_ PUNICODE_STRING + +*Name) +{ +ULONG retLength = 0; +ULONG pniSize = 512; +PUNICODE_STRING pni = NULL; +NTSTATUS status = STATUS_UNSUCCESSFUL; + +do +{ +pni = (PUNICODE_STRING) ExAllocatePool2(POOL_FLAG_NON_PAGED, pniSize, 'RW'); +if (pni != NULL) +{ +status = ZwQueryInformationProcess(ProcessHandle, ProcessImageFileName, pni, pniSize, &retLength); +if (! +NT_SUCCESS(status) +) +{ +ExFreePoolWithTag(pni, +'RW'); +pniSize *= 2; +} +} +else +status = STATUS_INSUFFICIENT_RESOURCES; +} while (status == STATUS_INFO_LENGTH_MISMATCH); + +if ( +NT_SUCCESS(status) +) +* +Name = pni; + +return +status; +} + +// new code process recording +VOID AddRemProcessRoutine(HANDLE ParentId, HANDLE ProcessId, BOOLEAN Create) { + if (commHandle->CommClosed) + return; + if (Create) { + NTSTATUS hr; + if (ZwQueryInformationProcess == NULL) { + UNICODE_STRING routineName = RTL_CONSTANT_STRING(L"ZwQueryInformationProcess"); + + ZwQueryInformationProcess = (QUERY_INFO_PROCESS) MmGetSystemRoutineAddress(&routineName); + + if (ZwQueryInformationProcess == NULL) { + DbgPrint("Cannot resolve ZwQueryInformationProcess\n"); + hr = STATUS_UNSUCCESSFUL; + return; + } + } + HANDLE procHandleParent; + HANDLE procHandleProcess; + + CLIENT_ID clientIdParent; + clientIdParent.UniqueProcess = ParentId; + clientIdParent.UniqueThread = 0; + + CLIENT_ID clientIdProcess; + clientIdProcess.UniqueProcess = ProcessId; + clientIdProcess.UniqueThread = 0; + + OBJECT_ATTRIBUTES objAttribs; + + InitializeObjectAttributes(&objAttribs, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); + + hr = ZwOpenProcess(&procHandleParent, PROCESS_ALL_ACCESS, &objAttribs, &clientIdParent); + if (!NT_SUCCESS(hr)) { + DbgPrint("!!! snFilter: Failed to open process: %#010x.\n", hr); + return; + } + hr = ZwOpenProcess(&procHandleProcess, PROCESS_ALL_ACCESS, &objAttribs, &clientIdProcess); + if (!NT_SUCCESS(hr)) { + DbgPrint("!!! snFilter: Failed to open process: %#010x.\n", hr); + hr = ZwClose(procHandleParent); + if (!NT_SUCCESS(hr)) { + DbgPrint("!!! snFilter: Failed to close process: %#010x.\n", hr); + return; + } + return; + } + + PUNICODE_STRING procName; + PUNICODE_STRING parentName; + hr = GetProcessNameByHandle(procHandleParent, &parentName); + if (!NT_SUCCESS(hr)) { + DbgPrint("!!! snFilter: Failed to get parent name: %#010x\n", hr); + return; + } + hr = GetProcessNameByHandle(procHandleProcess, &procName); + if (!NT_SUCCESS(hr)) { + DbgPrint("!!! snFilter: Failed to get process name: %#010x\n", hr); + return; + } + + DbgPrint("!!! snFilter: New Process, parent: %wZ. Pid: %d\n", parentName, (ULONG)(ULONG_PTR) + ParentId); + + hr = ZwClose(procHandleParent); + if (!NT_SUCCESS(hr)) { + DbgPrint("!!! snFilter: Failed to close process: %#010x.\n", hr); + return; + } + hr = ZwClose(procHandleProcess); + if (!NT_SUCCESS(hr)) { + DbgPrint("!!! snFilter: Failed to close process: %#010x.\n", hr); + return; + } + DbgPrint("!!! snFilter: New Process, process: %wZ , pid: %d.\n", procName, (ULONG)(ULONG_PTR) + ProcessId); + + BOOLEAN found = FALSE; + if (startsWith(procName, driverData->GetSystemRootPath()) && // process in safe area + startsWith(parentName, driverData->GetSystemRootPath()) && // parent in safe area + (driverData->GetProcessGid((ULONG)(ULONG_PTR) + ParentId, &found) == 0) && + !found) // parent is not documented, if it was there was a recursive call from not safe process which + // resulted in safe are in windows dir + { + DbgPrint("!!! snFilter: Open Process not recorded, both parent and process are safe\n"); + delete parentName; + delete procName; + return; + } + // options to reach: process is not safe (parent safe or not), process safe parent is not, both safe but before + // parent there was unsafe process + DbgPrint("!!! snFilter: Open Process recording, is parent safe: %d, is process safe: %d\n", + startsWith(procName, driverData->GetSystemRootPath()), + startsWith(parentName, driverData->GetSystemRootPath())); + driverData->RecordNewProcess(procName, (ULONG)(ULONG_PTR) + ProcessId, (ULONG)(ULONG_PTR) + ParentId); + delete parentName; + } else { + DbgPrint("!!! snFilter: Terminate Process, Process: %d pid\n", (ULONG)(ULONG_PTR) + ProcessId); + driverData->RemoveProcess((ULONG)(ULONG_PTR) + ProcessId); + } +} + +#pragma clang diagnostic pop \ No newline at end of file diff --git a/minifilter/snFilter/snFilter.filters b/minifilter/snFilter/snFilter.filters new file mode 100644 index 0000000..293fd83 --- /dev/null +++ b/minifilter/snFilter/snFilter.filters @@ -0,0 +1,74 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Driver Files + + + \ No newline at end of file diff --git a/minifilter/snFilter/snFilter.h b/minifilter/snFilter/snFilter.h new file mode 100644 index 0000000..242ee19 --- /dev/null +++ b/minifilter/snFilter/snFilter.h @@ -0,0 +1,112 @@ +#pragma once + +/*++ + +Module Name: + + snFilter.h + +Abstract: + + Header file for the kernel FS driver + +Environment: + + Kernel mode + +--*/ + +#include +#include +#include +#include +#include +#include + +#include "../SharedDefs/SharedDefs.h" +#include "Communication.h" +#include "DriverData.h" +#include "KernelString.h" +#include "ShanonEntropy.h" + +NTSTATUS +FSUnloadDriver(_In_ FLT_FILTER_UNLOAD_FLAGS Flags); + +FLT_POSTOP_CALLBACK_STATUS +FSPostOperation(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags); + +FLT_PREOP_CALLBACK_STATUS +FSPreOperation(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID + +*CompletionContext); + +NTSTATUS +FSInstanceSetup(_In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_SETUP_FLAGS Flags, + _In_ DEVICE_TYPE VolumeDeviceType, _In_ FLT_FILESYSTEM_TYPE VolumeFilesystemType); + +NTSTATUS +FSInstanceQueryTeardown(_In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags); + +VOID FSInstanceTeardownStart(_In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags); + +VOID FSInstanceTeardownComplete(_In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags); + +// handles pre operation for read, write, set info and close files +NTSTATUS +FSProcessPreOperartion(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID + +*CompletionContext); + +NTSTATUS +FSEntrySetFileName(const PFLT_VOLUME volume, PFLT_FILE_NAME_INFORMATION nameInfo, PUNICODE_STRING uString); + +FLT_POSTOP_CALLBACK_STATUS +FSProcessPostReadIrp(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags); + +FLT_POSTOP_CALLBACK_STATUS +FSProcessPostReadSafe(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags); + +// handles IRP_MJ_CREATE irps on post op +FLT_POSTOP_CALLBACK_STATUS +FSProcessCreateIrp(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects); + +// compares unicode string file name to the directories in protected areas in driverData object +// return true if the file is in one of the dirs +BOOLEAN +FSIsFileNameInScanDirs(CONST PUNICODE_STRING path); + +// ZwQueryInformationProcess - dynamic loaded function which query info data about already opened processes +typedef NTSTATUS (*QUERY_INFO_PROCESS)(__in HANDLE ProcessHandle, __in PROCESSINFOCLASS ProcessInformationClass, + __out_bcount(ProcessInformationLength) PVOID ProcessInformation, + __in ULONG ProcessInformationLength, __out_opt PULONG ReturnLength); + +QUERY_INFO_PROCESS ZwQueryInformationProcess; + +// copy the file id info from the data argument (FLT_CALLBACK_DATA) to DRIVER_MESSAGE class allocated +NTSTATUS +CopyFileIdInfo(_Inout_ PFLT_CALLBACK_DATA Data, PDRIVER_MESSAGE newItem); + +// receives a pointer to allocated unicode string, FLT_RELATED_OBJECTS and FILE_NAME_INFORMATION class. +// function gets the file name from the name info and flt objects and fill the unicode string with it +NTSTATUS GetFileNameInfo(_In_ PCFLT_RELATED_OBJECTS FltObjects, PUNICODE_STRING FilePath, + PFLT_FILE_NAME_INFORMATION nameInfo); + +// copy extension info from FILE_NAME_INFORMATION class to null terminated wchar string +VOID CopyExtension(PWCHAR dest, PFLT_FILE_NAME_INFORMATION nameInfo); + +// AddRemProcessRoutine is the function hooked to the processes creation and exit. +// When a new process enter we add it to parent gid if there is any. +// if parent doesn't have a gid and both are system process, new process isn't recorded +// else we create a new gid for process + +VOID AddRemProcessRoutine(HANDLE +ParentId, +HANDLE ProcessId, BOOLEAN +Create); + +UNICODE_STRING GvolumeData; \ No newline at end of file diff --git a/minifilter/snFilter/snFilter.inf b/minifilter/snFilter/snFilter.inf new file mode 100644 index 0000000..a7d9e9d --- /dev/null +++ b/minifilter/snFilter/snFilter.inf @@ -0,0 +1,108 @@ +; ----------------------------------------------------------------------- +; snFilter +; ----------------------------------------------------------------------- + +[Version] +Signature = "$Windows NT$" +; Change the Class and ClassGuid to match the Load Order Group value, see https://msdn.microsoft.com/en-us/windows/hardware/gg462963 +Class = "ActivityMonitor" ;This is determined by the work this filter driver does +ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2} ;This value is determined by the Load Order Group value +Provider = %ManufacturerName% +DriverVer = 08/07/2022,1.0.0.0 +CatalogFile = snFilter.cat +PnpLockDown = 1 + +; ----------------------------------------------------------------------- + +[DestinationDirs] +DefaultDestDir = 12 +; MiniFilter.DriverFiles = 12 ;%windir%\system32\drivers +MiniFilter.CopyDriverFiles = 12 ;%windir%\system32\drivers +MiniFilter.DeleteDriverFiles = 12 ;%windir%\system32\drivers +MiniFilter.UserFiles = 10,FltMgr + +; Default install sections ---------------------------------------------- + +[DefaultInstall.NTamd64] +OptionDesc = %ServiceDescription% +CopyFiles = MiniFilter.CopyDriverFiles + +[DefaultInstall.NTamd64.Services] +AddService = %ServiceName%,,MiniFilter.Service + +[DefaultInstall.NTx86] +OptionDesc = %ServiceDescription% +CopyFiles = MiniFilter.CopyDriverFiles + +[DefaultInstall.NTx86.Services] +AddService = %ServiceName%,,MiniFilter.Service + +; Default uninstall sections -------------------------------------------- + +[DefaultUninstall.NTamd64] +DelFiles = MiniFilter.DeleteDriverFiles +LegacyUninstall = 1 + +[DefaultUninstall.NTamd64.Services] +DelService = %ServiceName%,0x200 ;Ensure service is stopped before deleting + +[DefaultUninstall.NTx86] +DelFiles = MiniFilter.DeleteDriverFiles +LegacyUninstall = 1 + +[DefaultUninstall.NTx86.Services] +DelService = %ServiceName%,0x200 ;Ensure service is stopped before deleting + +; Services Section ------------------------------------------------------ + +[MiniFilter.Service] +DisplayName = %ServiceName% +Description = %ServiceDescription% +ServiceBinary = %12%\%DriverName%.sys ;%windir%\system32\drivers\ +Dependencies = FltMgr +ServiceType = 2 ;SERVICE_FILE_SYSTEM_DRIVER +StartType = 3 ;SERVICE_DEMAND_START +ErrorControl = 1 ;SERVICE_ERROR_NORMAL +; Change the Load Order Group value +LoadOrderGroup = "snFilter Activity Monitor" +AddReg = MiniFilter.AddRegistry + +; Registry Modifications ------------------------------------------------ + +[MiniFilter.AddRegistry] +HKR,,"DebugFlags",0x00010001 ,0x0 +HKR,,"SupportedFeatures",0x00010001,0x3 +HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance% +HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude% +HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags% + +; Copy Delete Files ----------------------------------------------------- + +[MiniFilter.DeleteDriverFiles] +%DriverName%.sys,,,0x00010001 ;(DELFLG_IN_USE | DELFLG_IN_USE1) + +[MiniFilter.CopyDriverFiles] +%DriverName%.sys,,,0x00002000 ;COPYFLG_NOPRUNE + +[SourceDisksFiles] +snFilter.sys = 1,, + +[SourceDisksNames] +1 = %DiskId1%,,, + +; String Section -------------------------------------------------------- + +[Strings] +ManufacturerName = "sn99" +ServiceDescription = "snFilter Mini-Filter Driver" +ServiceName = "snFilter" +DriverName = "snFilter" +DiskId1 = "snFilter Device Installation Disk" + +; Instances specific information ---------------------------------------- + +DefaultInstance = "snFilter Instance" +Instance1.Name = "snFilter Instance" +; Change the altitude value, see https://msdn.microsoft.com/en-us/windows/hardware/drivers/ifs/load-order-groups-and-altitudes-for-minifilter-drivers +Instance1.Altitude = "378781" +Instance1.Flags = 0x0 ; Allow all attachments diff --git a/minifilter/snFilter/snFilter.rc b/minifilter/snFilter/snFilter.rc new file mode 100644 index 0000000..ace6688 --- /dev/null +++ b/minifilter/snFilter/snFilter.rc @@ -0,0 +1,9 @@ +#include +#include + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_DRV_SYSTEM +#define VER_FILEDESCRIPTION_STR "snFilter Filter Driver" +#define VER_INTERNALNAME_STR "snFilter.sys" + +#include "common.ver" \ No newline at end of file diff --git a/minifilter/snFilter/snFilter.vcxproj b/minifilter/snFilter/snFilter.vcxproj new file mode 100644 index 0000000..b781997 --- /dev/null +++ b/minifilter/snFilter/snFilter.vcxproj @@ -0,0 +1,240 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + + + + + + + + + + + {DF8682E7-17C1-4450-A68C-D7CEF8780D8F} + {f2f62967-0815-4fd7-9b86-6eedcac766eb} + v4.5 + 12.0 + Debug + Win32 + snFilter + snFilter + 10.0.22621.0 + + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + WDM + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + WDM + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + WDM + x64 + Spectre + true + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + WDM + true + Spectre + x64 + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + WDM + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + WDM + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + WDM + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + WDM + + + + + + + + + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + true + true + + + DbgengKernelDebugger + true + true + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + fltmgr.lib;%(AdditionalDependencies) + + + + + fltmgr.lib;%(AdditionalDependencies) + + + + + fltmgr.lib;$(DDK_LIB_PATH)\libcntpr.lib;%(AdditionalDependencies) + true + UseLinkTimeCodeGeneration + + + Level4 + MultiThreaded + false + stdcpp17 + MaxSpeed + true + Speed + true + + + SHA1 + + + + + fltmgr.lib;$(DDK_LIB_PATH)\libcntpr.lib;%(AdditionalDependencies) + UseLinkTimeCodeGeneration + + + stdcpp17 + MaxSpeed + true + Level4 + true + Speed + MultiThreaded + + + SHA1 + + + + + fltmgr.lib;%(AdditionalDependencies) + + + + + fltmgr.lib;%(AdditionalDependencies) + + + + + fltmgr.lib;%(AdditionalDependencies) + + + + + fltmgr.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/readme_resources/example.gif b/readme_resources/example.gif new file mode 100644 index 0000000..82706e1 Binary files /dev/null and b/readme_resources/example.gif differ diff --git a/readme_resources/shared_def.png b/readme_resources/shared_def.png new file mode 100644 index 0000000..a52f60f Binary files /dev/null and b/readme_resources/shared_def.png differ diff --git a/src/bin/minifilter.rs b/src/bin/minifilter.rs new file mode 100644 index 0000000..93be051 --- /dev/null +++ b/src/bin/minifilter.rs @@ -0,0 +1,46 @@ +use fsfilter_rs::driver_comm; +use fsfilter_rs::shared_def::{CDriverMsgs, IOMessage}; +use std::io::Write; +use std::sync::mpsc::channel; +use std::time::Duration; +use std::{io, thread}; + +fn main() { + let driver = driver_comm::Driver::open_kernel_driver_com() + .expect("Cannot open driver communication (is the mini-filter started?)"); + driver + .driver_set_app_pid() + .expect("Cannot set driver app pid"); + let mut vecnew: Vec = Vec::with_capacity(65536); + + let (tx_iomsgs, rx_iomsgs) = channel::(); + + thread::spawn(move || loop { + if let Some(reply_irp) = driver.get_irp(&mut vecnew) { + if reply_irp.num_ops > 0 { + let drivermsgs = CDriverMsgs::new(&reply_irp); + for drivermsg in drivermsgs { + let iomsg = IOMessage::from(&drivermsg); + if tx_iomsgs.send(iomsg).is_ok() { + } else { + panic!("Cannot send iomsg"); + } + } + } else { + thread::sleep(Duration::from_millis(5)); + } + } else { + panic!("Can't receive Driver Message?"); + } + }); + + { + let mut lock = io::stdout().lock(); + loop { + if let Ok(mut io_message) = rx_iomsgs.recv() { + io_message.exepath(); + writeln!(lock, "{:?}", io_message); + } + } + } +} diff --git a/src/driver_comm.rs b/src/driver_comm.rs new file mode 100644 index 0000000..e34e25a --- /dev/null +++ b/src/driver_comm.rs @@ -0,0 +1,272 @@ +//! Low-level communication with the minifilter. + +use core::ffi::c_void; +use std::mem; +use std::os::raw::*; +use std::ptr; + +use sysinfo::{get_current_pid, Pid, PidExt}; +use wchar::wchar_t; +use widestring::U16CString; +use windows::core::{HRESULT, PCSTR, PCWSTR}; +use windows::Win32::Foundation::{CloseHandle, HANDLE}; +use windows::Win32::Storage::FileSystem::GetDriveTypeA; +use windows::Win32::Storage::InstallableFileSystems::{ + FilterConnectCommunicationPort, FilterSendMessage, +}; + +use crate::driver_comm::DriveType::{ + DriveCDRom, DriveFixed, DriveNoRootDir, DriveRamDisk, DriveRemote, DriveRemovable, DriveUnknown, +}; +use crate::driver_comm::IrpMajorOp::{IrpCreate, IrpNone, IrpRead, IrpSetInfo, IrpWrite}; +use crate::shared_def::ReplyIrp; + +type BufPath = [wchar_t; 520]; + +/// The user-mode app (this app) can send several messages types to the driver. See [`DriverComMessageType`] +/// for details. +/// Depending on the message type, the *pid*, *gid* and *path* fields can be optional. +#[derive(Debug)] +#[repr(C)] +struct DriverComMessage { + /// The type message to send. See [`DriverComMessageType`]. + r#type: c_ulong, + /// The pid of the process which triggered an i/o activity; + pid: c_ulong, + /// The gid is maintained by the driver + gid: c_ulonglong, + path: BufPath, +} + +/// Messages types to send directives to the minifilter, by using te [`DriverComMessage`] struct. +#[repr(C)] +#[allow(dead_code)] +enum DriverComMessageType { + /// Not used yet. The minifilter has the ability to monitor a specific part of the fs. + AddScanDirectory, + /// Not used yet. The minifilter has the ability to monitor a specific part of the fs. + RemScanDirectory, + /// Ask for a [`ReplyIrp`], if any available. + GetOps, + /// Set this app pid to the minifilter (related IRPs will be ignored); + SetPid, + /// Instruct the minifilter to kill all pids in the family designated by a given gid. + KillGid, +} + +/// A minifilter is identified by a port (know in advance), like a named pipe used for communication, +/// and a handle, retrieved by [`open_kernel_driver_com`](Self::open_kernel_driver_com). +#[derive(Debug)] +#[repr(C)] +pub struct Driver { + handle: HANDLE, +} + +impl Driver { + /// Can be used to properly close the communication (and unregister) with the minifilter. + /// If this fn is not used and the program has stopped, the handle is automatically closed, + /// seemingly without any side-effects. + pub fn close_kernel_communication(&self) -> bool { + unsafe { CloseHandle(self.handle).as_bool() } + } + + /// The user-mode running app (this one) has to register itself to the driver. + pub fn driver_set_app_pid(&self) -> Result<(), windows::core::Error> { + let buf = Driver::string_to_commessage_buffer(r"\Device\harddiskVolume"); + + let mut get_irp_msg: DriverComMessage = DriverComMessage { + r#type: DriverComMessageType::SetPid as c_ulong, + pid: get_current_pid().unwrap().as_u32() as c_ulong, + gid: 140713315094899, + path: buf, //wch!("\0"), + }; + let mut tmp: u32 = 0; + + unsafe { + FilterSendMessage( + self.handle, + ptr::addr_of_mut!(get_irp_msg) as *mut c_void, + mem::size_of::() as c_ulong, + None, + 0, + &mut tmp as *mut u32, + ) + } + } + + /// Try to open a com canal with the minifilter before this app is registered. This fn can fail + /// is the minifilter is unreachable: + /// + /// * if it is not started (try `sc start snFilter` first + /// * if a connection is already established: it can accepts only one at a time. + /// + /// In that case the Error is raised by the OS (windows::Error) and is generally readable. + pub fn open_kernel_driver_com() -> Result { + let _com_port_name = U16CString::from_str("\\snFilter").unwrap().into_raw(); + let _handle; + unsafe { + _handle = FilterConnectCommunicationPort(PCWSTR(_com_port_name), 0, None, 0, None)? + } + let res = Driver { handle: _handle }; + Ok(res) + } + + /// Ask the driver for a [ReplyIrp], if any. This is a low-level function and the returned object + /// uses C pointers. Managing C pointers requires a special care, because of the Rust timelines. + /// [ReplyIrp] is optional since the minifilter returns null if there is no new activity. + pub fn get_irp(&self, vecnew: &mut Vec) -> Option { + let mut get_irp_msg = Driver::build_irp_msg( + DriverComMessageType::GetOps, + get_current_pid().unwrap(), + 0, + "", + ); + let mut tmp: u32 = 0; + + unsafe { + FilterSendMessage( + self.handle, + ptr::addr_of_mut!(get_irp_msg) as *mut c_void, + mem::size_of::() as c_ulong, + Option::from(vecnew.as_ptr() as *mut c_void), + 65536_u32, + ptr::addr_of_mut!(tmp) as *mut u32, + ) + .expect("Cannot get driver message from driver"); + } + + if tmp != 0 { + let reply_irp: ReplyIrp; + unsafe { + reply_irp = std::ptr::read_unaligned(vecnew.as_ptr() as *const ReplyIrp); + } + return Some(reply_irp); + } + None + } + + /// Ask the minifilter to kill all pids related to the given *gid*. Pids are killed in driver-mode + /// by calls to NtClose. + pub fn try_kill(&self, gid: c_ulonglong) -> Result { + let mut killmsg = DriverComMessage { + r#type: DriverComMessageType::KillGid as c_ulong, + pid: 0, //get_current_pid().unwrap() as u32, + gid, + path: [0; 520], + }; + let mut res: i32 = 0; + let mut res_size: u32 = 0; + + unsafe { + FilterSendMessage( + self.handle, + ptr::addr_of_mut!(killmsg) as *mut c_void, + mem::size_of::() as c_ulong, + Option::from(ptr::addr_of_mut!(res) as *mut c_void), + 4_u32, + ptr::addr_of_mut!(res_size) as *mut u32, + )?; + } + + Ok(HRESULT(res)) + } + + fn string_to_commessage_buffer(bufstr: &str) -> BufPath { + let temp = U16CString::from_str(&bufstr).unwrap(); + let mut buf: BufPath = [0; 520]; + for (i, c) in temp.as_slice_with_nul().iter().enumerate() { + buf[i] = *c as wchar_t; + } + buf + } + + // TODO: move to ComMessage? + fn build_irp_msg( + commsgtype: DriverComMessageType, + pid: Pid, + gid: u64, + path: &str, + ) -> DriverComMessage { + DriverComMessage { + r#type: commsgtype as c_ulong, // SetPid + pid: pid.as_u32() as c_ulong, + gid, + path: Driver::string_to_commessage_buffer(path), + } + } +} + +/// See [`IOMessage`](crate::shared_def::IOMessage) struct and +/// [this doc](https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-major-function-codes). +#[repr(C)] +pub enum IrpMajorOp { + /// Nothing happened + IrpNone, + /// On read, any time following the successful completion of a create request. + IrpRead, + /// On write, any time following the successful completion of a create request. + IrpWrite, + /// Set Metadata about a file or file handle. In that case, [`FileChangeInfo`](crate::shared_def::FileChangeInfo) indicates + /// the nature of the modification. + IrpSetInfo, + /// Open a handle to a file object or device object. + IrpCreate, + /// File object handle has been closed + IrpCleanUp, +} + +impl IrpMajorOp { + pub fn from_byte(b: u8) -> IrpMajorOp { + match b { + 0 => IrpNone, + 1 => IrpRead, + 2 => IrpWrite, + 3 => IrpSetInfo, + 4 => IrpCreate, + 5 => IrpCreate, + _ => IrpNone, + } + } +} + +/// See [`IOMessage`](crate::shared_def::IOMessage) struct and +/// [this doc](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdrivetypea). +#[repr(C)] +pub enum DriveType { + /// The drive type cannot be determined. + DriveUnknown, + /// The root path is invalid; for example, there is no volume mounted at the specified path. + DriveNoRootDir, + /// The drive has removable media; for example, a floppy drive, thumb drive, or flash card reader. + DriveRemovable, + /// The drive has fixed media; for example, a hard disk drive or flash drive. + DriveFixed, + /// The drive is a remote (network) drive. + DriveRemote, + /// The drive is a CD-ROM drive. + DriveCDRom, + /// The drive is a RAM disk. + DriveRamDisk, +} + +impl DriveType { + pub fn from_filepath(filepath: String) -> DriveType { + let mut drive_type = 1u32; + if !filepath.is_empty() { + let drive_path = &filepath[..(filepath.find('\\').unwrap() + 1)]; + unsafe { + drive_type = GetDriveTypeA(PCSTR(String::from(drive_path).as_ptr())); + } + } + match drive_type { + 0 => DriveUnknown, + 1 => DriveNoRootDir, + 2 => DriveRemovable, + 3 => DriveFixed, + 4 => DriveRemote, + 5 => DriveCDRom, + 6 => DriveRamDisk, + _ => DriveNoRootDir, + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..267f146 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,138 @@ +//! # minifilter-rs +//! +//! **Use `cargo doc --no-deps --document-private-items --open` to read Documentation** +//! +//! ## Table of Contents +//! +//!
+//! Table of Contents +//! +//! - [Minifilter Driver](https://!github.com/sn99/fsfilter-rs#minifilter-driver) +//! - [Building Driver](https://!github.com/sn99/fsfilter-rs#building-driver) +//! - [Installing Driver](https://!github.com/sn99/fsfilter-rs#building-driver) +//! - [Loading/Removing Driver](https://!github.com/sn99/fsfilter-rs#loadingremoving-driver) +//! - [Rust Application](https://!github.com/sn99/fsfilter-rs#rust-application) +//! - [Building Rust App](https://!github.com/sn99/fsfilter-rs#building-rust-app) +//! - [Running Rust App](https://!github.com/sn99/fsfilter-rs#running-rust-app) +//! - [What and the How](https://!github.com/sn99/fsfilter-rs#what-and-the-how) +//! +//!
+//! +//! ## Minifilter Driver +//! +//! ### Building Driver +//! +//! 1. Open `VS 2022` +//! 2. Goto `minifilter-rs -> minifilter -> RWatch.sln` +//! 3. Build solution in `Release` mode with `x64` +//! +//! **NOTE: Enable Loading of Test Signed Drivers by executing `Bcdedit.exe -set TESTSIGNING ON` in administrative cmd** +//! +//! ### Installing Driver +//! +//! 1. Open Powershell or command prompt as Administrator +//! 2. `RUNDLL32.EXE SETUPAPI.DLL,InstallHinfSection DefaultInstall 132 \minifilter-rs\minifilter\x64\Debug\snFilter.inf` +//! +//! You should be able to see the driver at `"C:\Windows\System32\drivers\snFilter.sys"` +//! +//! ### Loading/Removing Driver +//! +//! 1. Open Powershell or command prompt as Administrator +//! 2. Start the driver using `sc start snFilter`, expected output: +//! ```ignore +//! SERVICE_NAME: snFilter +//! TYPE : 2 FILE_SYSTEM_DRIVER +//! STATE : 4 RUNNING +//! (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN) +//! WIN32_EXIT_CODE : 0 (0x0) +//! SERVICE_EXIT_CODE : 0 (0x0) +//! CHECKPOINT : 0x0 +//! WAIT_HINT : 0x0 +//! PID : 0 +//! FLAGS : +//! ``` +//! 3. Stop the driver using `sc stop snFilter`, should give the following output: +//! ```ignore +//! SERVICE_NAME: snFilter +//! TYPE : 2 FILE_SYSTEM_DRIVER +//! STATE : 1 STOPPED +//! WIN32_EXIT_CODE : 0 (0x0) +//! SERVICE_EXIT_CODE : 0 (0x0) +//! CHECKPOINT : 0x0 +//! WAIT_HINT : 0x0 +//! ``` +//! 4. Remove it by `sc delete snFilter`, should give the following output: +//! ```ignore +//! [SC] DeleteService SUCCESS +//! ``` +//! +//! You can also run `Fltmc.exe` to see the currently loaded drivers: +//! +//! ```ignore +//! Filter Name Num Instances Altitude Frame +//! ------------------------------ ------------- ------------ ----- +//! bindflt 1 409800 0 +//! snFilter 4 378781 0 //! our minifilter driver +//! WdFilter 5 328010 0 +//! storqosflt 0 244000 0 +//! wcifs 0 189900 0 +//! CldFlt 0 180451 0 +//! FileCrypt 0 141100 0 +//! luafv 1 135000 0 +//! npsvctrig 1 46000 0 +//! Wof 3 40700 0 +//! FileInfo 5 40500 0 +//! ``` +//! +//! ## Rust Application +//! +//! ### Building Rust App +//! +//! Simply use `cargo build --release` to build the application +//! +//! ### Running Rust App +//! +//! Use `cargo run --bin minifilter --release` to run the application +//! +//! The program starts to print the `IOMessage` which is defined like: +//! +//! ```ignore +//! #[repr(C)] +//! pub struct IOMessage { +//! pub extension: [wchar_t; 12], +//! pub file_id_vsn: c_ulonglong, +//! pub file_id_id: [u8; 16], +//! pub mem_sized_used: c_ulonglong, +//! pub entropy: f64, +//! pub pid: c_ulong, +//! pub irp_op: c_uchar, +//! pub is_entropy_calc: u8, +//! pub file_change: c_uchar, +//! pub file_location_info: c_uchar, +//! pub filepathstr: String, +//! pub gid: c_ulonglong, +//! pub runtime_features: RuntimeFeatures, +//! pub file_size: i64, +//! } +//! ``` +//! +//! We end the process using `ctrl + c` in the example video: +//! ![video](https://!github.com/sn99/fsfilter-rs/readme_resources/example.gif) +//! +//! #### NOTE: +//! +//! - Might fail if not ran with administrative privileges +//! - You need to [load and start the driver]((https://!github.com/sn99/fsfilter-rs#loadingremoving-driver)) before running +//! the program or else it will error out +//! +//! ## What and the How +//! +//! We basically share definition between the mini-filter and Rust using `#[repr(C)]` +//! +//! ![shared_def](https://!github.com/sn99/fsfilter-rs/readme_resources/shared_def.png) +//! +//! We use [channels](https://!doc.rust-lang.org/std/sync/mpsc/fn.channel.html) to process +//! all [IRPs](https://!docs.microsoft.com/en-us/windows-hardware/drivers/ifs/irps-are-different-from-fast-i-o). + +pub mod driver_comm; +pub mod shared_def; diff --git a/src/shared_def.rs b/src/shared_def.rs new file mode 100644 index 0000000..208bcdc --- /dev/null +++ b/src/shared_def.rs @@ -0,0 +1,352 @@ +//! Contains all definitions shared between this user-mode app and the minifilter in order to +//! communicate properly. Those are C-representation of structures sent or received from the minifilter. + +use std::fmt; +use std::os::raw::{c_uchar, c_ulong, c_ulonglong, c_ushort}; +use std::path::PathBuf; + +use num_derive::FromPrimitive; +use serde::{Deserialize, Serialize}; +use wchar::wchar_t; +use windows::Win32::Foundation::{CloseHandle, GetLastError}; +use windows::Win32::Storage::FileSystem::FILE_ID_INFO; +use windows::Win32::System::ProcessStatus::K32GetProcessImageFileNameA; +use windows::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ}; + +/// See [`IOMessage`] struct. Used with [`IrpSetInfo`](crate::driver_comm::IrpMajorOp::IrpSetInfo) +#[derive(FromPrimitive)] +#[repr(C)] +pub enum FileChangeInfo { + FileChangeNotSet, + FileOpenDirectory, + FileChangeWrite, + FileChangeNewFile, + FileChangeRenameFile, + FileChangeExtensionChanged, + FileChangeDeleteFile, + /// Temp file: created and deleted on close + FileChangeDeleteNewFile, + FileChangeOverwriteFile, +} + +/// See [`IOMessage`] struct. +#[derive(FromPrimitive)] +#[repr(C)] +pub enum FileLocationInfo { + FileNotProtected, + FileProtected, + FileMovedIn, + FileMovedOut, +} + +/// Low-level C-like object to communicate with the minifilter. +/// The minifilter yields ReplyIrp objects (retrieved by [`get_irp`](crate::driver_comm::Driver::get_irp) to +/// manage the fixed size of the *data buffer. +/// In other words, a ReplyIrp is a collection of [`CDriverMsg`] with a capped size. +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct ReplyIrp { + /// The size od the collection. + pub data_size: c_ulonglong, + /// The C pointer to the buffer containing the [`CDriverMsg`] events. + pub data: *const CDriverMsg, + /// The number of different operations in this collection. + pub num_ops: u64, +} + +impl ReplyIrp { + /// Iterate through ```self.data``` and returns the collection of [`CDriverMsg`] + fn unpack_drivermsg(&self) -> Vec<&CDriverMsg> { + let mut res = vec![]; + unsafe { + let mut msg = &*self.data; + res.push(msg); + for _ in 0..(self.num_ops) { + if msg.next.is_null() { + break; + } + msg = &*msg.next; + res.push(msg); + } + } + res + } +} + +/// This class is the straight Rust translation of the Win32 API +/// [`UNICODE_STRING`](https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_unicode_string), +/// returned by the driver. +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct UnicodeString { + pub length: c_ushort, + pub maximum_length: c_ushort, + pub buffer: *const wchar_t, +} + +impl UnicodeString { + /* + pub fn to_string(&self) -> String { + unsafe { + let str_slice = std::slice::from_raw_parts(self.buffer, self.length as usize); + let mut first_zero_index = 0; + for (i, c) in str_slice.iter().enumerate() { + if *c == 0 { + first_zero_index = i; + break; + } + } + String::from_utf16_lossy(&str_slice[..first_zero_index]) + } + } + */ + + /// Get the file path from the UnicodeString path and the extension returned by the driver. + pub fn to_string_ext(&self, extension: [wchar_t; 12]) -> String { + unsafe { + let str_slice = std::slice::from_raw_parts(self.buffer, self.length as usize); + let mut first_zero_index = 0; + let mut last_dot_index = 0; + let mut first_zero_index_ext = 0; + + // Filepath + for (i, c) in str_slice.iter().enumerate() { + if *c == 46 { + last_dot_index = i + 1; + } + if *c == 0 { + first_zero_index = i; + break; + } + } + + if first_zero_index_ext > 0 && last_dot_index > 0 { + // Extension + for (i, c) in extension.iter().enumerate() { + if *c == 0 { + first_zero_index_ext = i; + break; + } else if *c != str_slice[last_dot_index + i] { + first_zero_index_ext = 0; + break; + } + } + String::from_utf16_lossy( + &[ + &str_slice[..last_dot_index], + &extension[..first_zero_index_ext], + ] + .concat(), + ) + } else { + String::from_utf16_lossy(&str_slice[..first_zero_index]) + } + } + } +} + +impl fmt::Display for UnicodeString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + unsafe { + let str_slice = std::slice::from_raw_parts(self.buffer, self.length as usize); + let mut first_zero_index = 0; + for (i, c) in str_slice.iter().enumerate() { + if *c == 0 { + first_zero_index = i; + break; + } + } + write!( + f, + "{}", + String::from_utf16_lossy(&str_slice[..first_zero_index]) + ) + } + } +} + +/// Represents a driver message. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[repr(C)] +pub struct IOMessage { + /// The file extension + pub extension: [wchar_t; 12], + /// Hard Disk Volume Serial Number where the file is saved (from [`FILE_ID_INFO`]) + pub file_id_vsn: c_ulonglong, + /// File ID on the disk ([`FILE_ID_INFO`]) + pub file_id_id: [u8; 16], + /// Number of bytes transferred (`IO_STATUS_BLOCK.Information`) + pub mem_sized_used: c_ulonglong, + /// (Optional) File Entropy calculated by the driver + pub entropy: f64, + /// Pid responsible for this io activity + pub pid: c_ulong, + /// Windows IRP Type caught by the minifilter: + /// - NONE (0) + /// - READ (1) + /// - WRITE (2) + /// - SETINFO (3) + /// - CREATE (4) + /// - CLEANUP (5) + pub irp_op: c_uchar, + /// Is the entropy calculated? + pub is_entropy_calc: u8, + /// Type of i/o operation: + /// - FILE_CHANGE_NOT_SET (0) + /// - FILE_OPEN_DIRECTORY (1) + /// - FILE_CHANGE_WRITE (2) + /// - FILE_CHANGE_NEW_FILE (3) + /// - FILE_CHANGE_RENAME_FILE (4) + /// - FILE_CHANGE_EXTENSION_CHANGED (5) + /// - FILE_CHANGE_DELETE_FILE (6) + /// - FILE_CHANGE_DELETE_NEW_FILE (7) + /// - FILE_CHANGE_OVERWRITE_FILE (8) + pub file_change: c_uchar, + /// The driver has the ability to monitor specific directories only (feature currently not used): + /// - FILE_NOT_PROTECTED (0): Monitored dirs do not contained this file + /// - FILE_PROTECTED (1) + /// - FILE_MOVED_IN (2) + /// - FILE_MOVED_OUT (3) + pub file_location_info: c_uchar, + /// File path on the disk + pub filepathstr: String, + /// Group Identifier (maintained by the minifilter) of the operation + pub gid: c_ulonglong, + /// see class [`RuntimeFeatures`] + pub runtime_features: RuntimeFeatures, + /// Size of the file. Can be equal to -1 if the file path is not found. + pub file_size: i64, +} + +impl IOMessage { + pub fn from(c_drivermsg: &CDriverMsg) -> IOMessage { + IOMessage { + extension: c_drivermsg.extension, + file_id_vsn: c_drivermsg.file_id.VolumeSerialNumber, + file_id_id: c_drivermsg.file_id.FileId.Identifier, + mem_sized_used: c_drivermsg.mem_sized_used, + entropy: c_drivermsg.entropy, + pid: c_drivermsg.pid, + irp_op: c_drivermsg.irp_op, + is_entropy_calc: c_drivermsg.is_entropy_calc, + file_change: c_drivermsg.file_change, + file_location_info: c_drivermsg.file_location_info, + filepathstr: c_drivermsg.filepath.to_string_ext(c_drivermsg.extension), + gid: c_drivermsg.gid, + runtime_features: RuntimeFeatures::new(), + file_size: match PathBuf::from( + &c_drivermsg.filepath.to_string_ext(c_drivermsg.extension), + ) + .metadata() + { + Ok(f) => f.len() as i64, + Err(_e) => -1, + }, + } + } + + pub fn exepath(&mut self) { + let pid = self.pid as u32; + unsafe { + let r_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid); + if let Ok(handle) = r_handle { + if !(handle.is_invalid() || handle.0 == 0) { + let mut buffer: Vec = Vec::new(); + buffer.resize(1024, 0); + let res = K32GetProcessImageFileNameA(handle, buffer.as_mut_slice()); + + CloseHandle(handle); + if res == 0 { + let _errorcode = GetLastError().0; + } else { + let pathbuf = PathBuf::from( + String::from_utf8_unchecked(buffer).trim_matches(char::from(0)), + ); + self.runtime_features.exe_still_exists = true; + self.runtime_features.exepath = pathbuf; + } + // dbg!(is_closed_handle); + } + } + } + } +} + +/// Stores runtime features that come from our application (and not the minifilter). +#[derive(Debug, Clone, Serialize, Deserialize)] +#[repr(C)] +pub struct RuntimeFeatures { + /// The path of the gid root process + pub exepath: PathBuf, + /// Did the root exe file still existed (at the moment of this specific *DriverMessage* operation)? + pub exe_still_exists: bool, +} + +impl RuntimeFeatures { + pub fn new() -> RuntimeFeatures { + RuntimeFeatures { + exepath: PathBuf::new(), + exe_still_exists: true, + } + } +} + +impl Default for RuntimeFeatures { + fn default() -> Self { + Self::new() + } +} + +/// The C object returned by the minifilter, available through [`ReplyIrp`]. +/// It is low level and use C pointers logic which is not always compatible with RUST (in particular +/// the lifetime of `*next`). That's why we convert it asap to a plain Rust [`IOMessage`] object. +/// +/// `next` is null `(0x0)` when there is no [`IOMessage`] remaining. +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct CDriverMsg { + pub extension: [wchar_t; 12], + pub file_id: FILE_ID_INFO, + pub mem_sized_used: c_ulonglong, + pub entropy: f64, + pub pid: c_ulong, + pub irp_op: c_uchar, + pub is_entropy_calc: u8, + pub file_change: c_uchar, + pub file_location_info: c_uchar, + pub filepath: UnicodeString, + pub gid: c_ulonglong, + /// null (0x0) when there is no [`IOMessage`] remaining + pub next: *const CDriverMsg, +} + +/// To iterate easily over a collection of [`IOMessage`] received from the minifilter, before they are +/// converted to [`IOMessage`]. +#[repr(C)] +pub struct CDriverMsgs<'a> { + drivermsgs: Vec<&'a CDriverMsg>, + index: usize, +} + +impl CDriverMsgs<'_> { + pub fn new(irp: &ReplyIrp) -> CDriverMsgs { + CDriverMsgs { + drivermsgs: irp.unpack_drivermsg(), + index: 0, + } + } +} + +impl Iterator for CDriverMsgs<'_> { + type Item = CDriverMsg; + + fn next(&mut self) -> Option { + if self.index == self.drivermsgs.len() { + None + } else { + let res = *self.drivermsgs[self.index]; + self.index += 1; + Some(res) + } + } +}