diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c286eb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +SkipList.txt +ProtectList.txt +IncludeList.txt +ExcludePaths.txt \ No newline at end of file diff --git a/BlockFileExtensionsGPO.ps1 b/BlockFileExtensionsGPO.ps1 deleted file mode 100644 index 1b8bde0..0000000 --- a/BlockFileExtensionsGPO.ps1 +++ /dev/null @@ -1,154 +0,0 @@ -# -------------------------------------------------- -# BlockFileExtensionsGPO.ps1 -# -------------------------------------------------- -# This script creates a GPO to block certain questionable file extensions using a software restriction policy -# GPO Name and extensions to block can be modified below. By default, the GPO is linked to the domain - -$blockedFileExtensions = "VBS,JS,COM,BAT,SCR,PIF" - -Import-Module ActiveDirectory - -Function ConvertTo-WmiFilter([Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject) -{ - $gpDomain = New-Object -Type Microsoft.GroupPolicy.GPDomain - $ADObject | ForEach-Object { - $path = 'MSFT_SomFilter.Domain="' + $gpDomain.DomainName + '",ID="' + $_.Name + '"' - - try - { - $filter = $gpDomain.GetWmiFilter($path) - } - catch { } - - if ($filter) - { - [Guid]$guid = $_.Name.Substring(1, $_.Name.Length - 2) - $filter | Add-Member -MemberType NoteProperty -Name Guid -Value $Guid -PassThru | Add-Member -MemberType NoteProperty -Name Content -Value $_."msWMI-Parm2" -PassThru - } - } -} - -Function New-SoftwareRestrictionGPO($GpoName, $ParanoidExtensions, $WmiFilter) -{ - Set-StrictMode -Version 2 - - # Just in case GPMC modules are missing.. - Import-Module ServerManager - Add-WindowsFeature GPMC - Import-Module GroupPolicy - - $existingGpo = Get-GPO -Name $GpoName - - if ($existingGPO -ne $null) - { - Remove-GPO -Name $GpoName - } - - $newGPO = New-GPO -Name $GpoName - - $newGPO.WmiFilter = $WmiFilter - - $nLevel = 0 - $settingsKey = "HKLM\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers" - - $fileTimeNow = (Get-Date).ToFileTime() - - # Set global parameters - Set-GPRegistryValue -Name $GpoName -Key "$settingsKey" ` - -Type MultiString -ValueName "ExecutableTypes" -Value "" | Out-Null - - Set-GPRegistryValue -Name $GpoName -Key "$settingsKey" ` - -Type DWord -ValueName "DefaultLevel" -Value 262144 | Out-Null - - Set-GPRegistryValue -Name $GpoName -Key "$settingsKey" ` - -Type DWord -ValueName "PolicyScope" -Value 0 | Out-Null - - Set-GPRegistryValue -Name $GpoName -Key "$settingsKey" ` - -Type DWord -ValueName "TransparentEnabled" -Value 1 | Out-Null - - Set-GPRegistryValue -Name $GpoName -Key "$settingsKey" ` - -Type DWord -ValueName "AuthenticodeEnabled" -Value 0 | Out-Null - - Set-GPRegistryValue -Name $GpoName -Key "$settingsKey" ` - -Type QWord -ValueName "LastModified" -Value $fileTimeNow | Out-Null - - $ParanoidExtensionsSplit = $ParanoidExtensions.Split(",") - foreach ($paranoidExtension in $ParanoidExtensionsSplit) - { - $newPathGUID = [System.Guid]::NewGuid() - $newPathGUID = "{$newPathGUID}" - - Set-GPRegistryValue -Name $GpoName -Key "$settingsKey\$nLevel\Paths\$newPathGUID" ` - -Type String -ValueName "ItemData" -Value "*.$paranoidExtension" | Out-Null - - Set-GPRegistryValue -Name $GpoName -Key "$settingsKey\$nLevel\Paths\$newPathGUID" ` - -Type DWord -ValueName "SaferFlags" -Value 0 | Out-Null - - Set-GPRegistryValue -Name $GpoName -Key "$settingsKey\$nLevel\Paths\$newPathGUID" ` - -Type QWord -ValueName "LastModified" -Value $fileTimeNow | Out-Null - } - - $domain = (Get-ADDomain).DistinguishedName - New-GPLink -Name "$GpoName" -Target "$domain" -} - -Function New-WMIFilter($FilterName, $FilterDescription, $FilterNamespace, $FilterExpression) -{ - $guid = [System.Guid]::NewGuid() - $defaultNamingContext = (Get-ADRootDSE).DefaultNamingContext - $msWMIAuthor = (Get-ADUser $env:USERNAME).UserPrincipleName - $msWMICreationDate = (Get-Date).ToUniversalTime().ToString("yyyyMMddhhmmss.ffffff-000") - $wmiGUID = "{$guid}" - $wmiDistinguishedName = "CN=$wmiGUID,CN=SOM,CN=WMIPolicy,CN=System,$defaultNamingContext" - $msWMIParm1 = "$FilterDescription " - $msWMIParm2 = $FilterExpression.Count.ToString() + ";" - - $FilterExpression | ForEach-Object { - $msWMIParm2 += "3;" + $FilterNamespace.Length + ";" + $_.Length + ";WQL;" + $FilterNamespace + ";" + $_ + ";" - } - - $existingWmiFilter = Get-ADObject -Filter 'objectClass -eq "msWMI-Som"' -Properties "msWMI-Name", "msWMI-Parm1" | Where-Object { $_."msWMI-Name" -eq $FilterName } | Select -First 1 - - - if ($existingWmiFilter -ne $null) - { - Remove-ADObject -Identity $existingWmiFilter -Confirm:$false - } - - $attributes = @{ - "msWMI-Name" = $FilterName; - "msWMI-Parm1" = $msWMIParm1; - "msWMI-Parm2" = $msWMIParm2; - "msWMI-Author" = $msWMIAuthor; - "instanceType" = 4; - "msWMI-ID" = $wmiGUID; - "showInAdvancedViewOnly" = "TRUE"; - "distinguishedname" = $wmiDistinguishedName; - "msWMI-ChangeDate" = $msWMICreationDate; - "msWMI-CreationDate" = $msWMICreationDate; - } - - $wmiPath = ("CN=SOM,CN=WMIPolicy,CN=System,$defaultNamingContext") - - $adObject = New-ADObject -Name $wmiGUID -Type "msWMI-Som" -Path $wmiPath -OtherAttributes $attributes -PassThru - - ConvertTo-WmiFilter $adObject | Write-Output -} - -$workstationFilterName = "Workstations" -$2K3TSFilterName = "2003 Terminal Servers" -$2K8TSFilterName = "2008+ Terminal Servers" - -# Create WMI filters for workstations, and 200 -$workstationFilter = New-WMIFilter -FilterName $workstationFilterName -FilterDescription "Filter on workstations" ` --FilterNamespace "ROOT\CIMV2" -FilterExpression "SELECT * FROM Win32_ComputerSystem WHERE DomainRole = 0 OR DomainRole = 1" - -$2K3TSFilter = New-WMIFilter -FilterName $2K3TSFilterName -FilterDescription "Filter on 2003 terminal servers" ` --FilterNamespace 'ROOT\CIMV2' -FilterExpression 'SELECT * FROM Win32_TerminalServiceSetting WHERE LicensingType > 1' - -$2K8TSFilter = New-WMIFilter -FilterName $2K8TSFilterName -FilterDescription "Filter on 2008+ terminal servers" ` --FilterNamespace 'ROOT\CIMV2\TerminalServices' -FilterExpression 'SELECT * FROM Win32_TerminalServiceSetting WHERE LicensingType > 1' - -New-SoftwareRestrictionGPO -GpoName "Block File Extensions - Workstations" -ParanoidExtensions $blockedFileExtensions -WmiFilter $workstationFilter -New-SoftwareRestrictionGPO -GpoName "Block File Extensions - 2K3 TS" -ParanoidExtensions $blockedFileExtensions -WmiFilter $2K3TSFilter -New-SoftwareRestrictionGPO -GpoName "Block File Extensions - 2K8+ TS" -ParanoidExtensions $blockedFileExtensions -WmiFilter $2K8TSFilter diff --git a/DeployCryptoBlocker.ps1 b/DeployCryptoBlocker.ps1 index 07fe111..3465b8a 100644 --- a/DeployCryptoBlocker.ps1 +++ b/DeployCryptoBlocker.ps1 @@ -1,99 +1,128 @@ -# DeployCryptoBlocker.ps1 -# -# This script performs the following actions: -# 1) Checks for network shares -# 2) Install File Server Resource Manager (FSRM) if missing -# 3) Creates Batch and PowerShell scripts used by FSRM -# 4) Creates a File Group within FSRM containing malicious extensions to screen on -# 5) Creates a File Screen Template utilising this File Group, with an Event notification and Command notification -# to run the scripts created in Step 3) -# 6) Creates File Screens utilising this template for each drive containing network shares +# DeployCryptoBlocker.ps1 +# Version: 1.1 +##### -################################ Functions ################################ +################################ USER CONFIGURATION ################################ -Function PurgeNonAdminDirectoryPermissions([string] $directory) -{ - $acl = Get-Acl $directory - - if ($acl.AreAccessRulesProtected) - { - $acl.Access | % { $acl.PurgeAccessRules($_.IdentityReference) } - } - else - { - $acl.SetAccessRuleProtection($true, $true) - } +# Names to use in FSRM +$fileGroupName = "CryptoBlockerGroup" +$fileTemplateName = "CryptoBlockerTemplate" +# set screening type to +# Active screening: Do not allow users to save unathorized files +$fileTemplateType = "Active" +# Passive screening: Allow users to save unathorized files (use for monitoring) +#$fileTemplateType = "Passiv" + +# Write the email options to the temporary file - comment out the entire block if no email notification should be set +$EmailNotification = $env:TEMP + "\tmpEmail001.tmp" +"Notification=m" >> $EmailNotification +"To=[Admin Email]" >> $EmailNotification +## en +"Subject=Unauthorized file from the [Violated File Group] file group detected" >> $EmailNotification +"Message=User [Source Io Owner] attempted to save [Source File Path] to [File Screen Path] on the [Server] server. This file is in the [Violated File Group] file group, which is not permitted on the server." >> $EmailNotification +## de +#"Subject=Nicht autorisierte Datei erkannt, die mit Dateigruppe [Violated File Group] übereinstimmt" >> $EmailNotification +#"Message=Das System hat erkannt, dass Benutzer [Source Io Owner] versucht hat, die Datei [Source File Path] unter [File Screen Path] auf Server [Server] zu speichern. Diese Datei weist Übereinstimmungen mit der Dateigruppe [Violated File Group] auf, die auf dem System nicht zulässig ist." >> $EmailNotification + +# Write the event log options to the temporary file - comment out the entire block if no event notification should be set +$EventNotification = $env:TEMP + "\tmpEvent001.tmp" +"Notification=e" >> $EventNotification +"EventType=Warning" >> $EventNotification +## en +"Message=User [Source Io Owner] attempted to save [Source File Path] to [File Screen Path] on the [Server] server. This file is in the [Violated File Group] file group, which is not permitted on the server." >> $EventNotification +## de +#"Message=Das System hat erkannt, dass Benutzer [Source Io Owner] versucht hat, die Datei [Source File Path] unter [File Screen Path] auf Server [Server] zu speichern. Diese Datei weist Übereinstimmungen mit der Dateigruppe [Violated File Group] auf, die auf dem System nicht zulässig ist." >> $EventNotification + +################################ USER CONFIGURATION ################################ - $ar = New-Object System.Security.AccessControl.FileSystemAccessRule("SYSTEM","FullControl","Allow") - $acl.AddAccessRule($ar) - $ar = $ar = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Administrators","FullControl","Allow") - $acl.AddAccessRule($ar) - Set-Acl -AclObject $acl -Path $directory -} +################################ Functions ################################ -function ConvertFrom-Json20([Object] $obj) +Function ConvertFrom-Json20 { + # Deserializes JSON input into PowerShell object output + Param ( + [Object] $obj + ) Add-Type -AssemblyName System.Web.Extensions $serializer = New-Object System.Web.Script.Serialization.JavaScriptSerializer return ,$serializer.DeserializeObject($obj) } -Function New-CBArraySplit { - +Function New-CBArraySplit +{ + <# + Takes an array of file extensions and checks if they would make a string >4Kb, + if so, turns it into several arrays + #> param( - $extArr, - $depth = 1 + $Extensions ) - $extArr = $extArr | Sort-Object -Unique - - # Concatenate the input array - $conStr = $extArr -join ',' - $outArr = @() - - # If the input string breaks the 4Kb limit - If ($conStr.Length -gt 4096) { - # Pull the first 4096 characters and split on comma - $conArr = $conStr.SubString(0,4096).Split(',') - # Find index of the last guaranteed complete item of the split array in the input array - $endIndex = [array]::IndexOf($extArr,$conArr[-2]) - # Build shorter array up to that indexNumber and add to output array - $shortArr = $extArr[0..$endIndex] - $outArr += [psobject] @{ - index = $depth - array = $shortArr - } + $Extensions = $Extensions | Sort-Object -Unique + + $workingArray = @() + $WorkingArrayIndex = 1 + $LengthOfStringsInWorkingArray = 0 + + # TODO - is the FSRM limit for bytes or characters? + # maybe [System.Text.Encoding]::UTF8.GetBytes($_).Count instead? + # -> in case extensions have Unicode characters in them + # and the character Length is <4Kb but the byte count is >4Kb + + # Take the items from the input array and build up a + # temporary workingarray, tracking the length of the items in it and future commas + $Extensions | ForEach-Object { + + if (($LengthOfStringsInWorkingArray + 1 + $_.Length) -gt 4000) + { + # Adding this item to the working array (with +1 for a comma) + # pushes the contents past the 4Kb limit + # so output the workingArray + [PSCustomObject]@{ + index = $WorkingArrayIndex + FileGroupName = "$Script:FileGroupName$WorkingArrayIndex" + array = $workingArray + } + + # and reset the workingArray and counters + $workingArray = @($_) # new workingArray with current Extension in it + $LengthOfStringsInWorkingArray = $_.Length + $WorkingArrayIndex++ - # Then call this function again to split further - $newArr = $extArr[($endindex + 1)..($extArr.Count -1)] - $outArr += New-CBArraySplit $newArr -depth ($depth + 1) - - return $outArr + } + else #adding this item to the workingArray is fine + { + $workingArray += $_ + $LengthOfStringsInWorkingArray += (1 + $_.Length) #1 for imaginary joining comma + } } - # If the concat string is less than 4096 characters already, just return the input array - Else { - return [psobject] @{ - index = $depth - array = $extArr - } + + # The last / only workingArray won't have anything to push it past 4Kb + # and trigger outputting it, so output that one as well + [PSCustomObject]@{ + index = ($WorkingArrayIndex) + FileGroupName = "$Script:FileGroupName$WorkingArrayIndex" + array = $workingArray } } ################################ Functions ################################ -# Add to all drives -$drivesContainingShares = Get-WmiObject Win32_Share | Select Name,Path,Type | Where-Object { $_.Type -eq 0 } | Select -ExpandProperty Path | % { "$((Get-Item -ErrorAction SilentlyContinue $_).Root)" } | Select -Unique -if ($drivesContainingShares -eq $null -or $drivesContainingShares.Length -eq 0) -{ - Write-Host "No drives containing shares were found. Exiting.." - exit -} - -Write-Host "The following shares needing to be protected: $($drivesContainingShares -Join ",")" +################################ Program code ################################ +# Identify Windows Server version, PowerShell version and install FSRM role $majorVer = [System.Environment]::OSVersion.Version.Major $minorVer = [System.Environment]::OSVersion.Version.Minor +$powershellVer = $PSVersionTable.PSVersion.Major +if ($powershellVer -le 2) +{ + Write-Host "`n####" + Write-Host "ERROR: PowerShell v3 or higher required." + exit +} + +Write-Host "`n####" Write-Host "Checking File Server Resource Manager.." Import-Module ServerManager @@ -102,183 +131,212 @@ if ($majorVer -ge 6) { $checkFSRM = Get-WindowsFeature -Name FS-Resource-Manager - if ($minorVer -ge 2 -and $checkFSRM.Installed -ne "True") + if (($minorVer -ge 2 -or $majorVer -eq 10) -and $checkFSRM.Installed -ne "True") { - # Server 2012 - Write-Host "FSRM not found.. Installing (2012).." - Install-WindowsFeature -Name FS-Resource-Manager -IncludeManagementTools + # Server 2012 / 2016 + Write-Host "`n####" + Write-Host "FSRM not found.. Installing (2012 / 2016).." + + $install = Install-WindowsFeature -Name FS-Resource-Manager -IncludeManagementTools + if ($? -ne $True) + { + Write-Host "Install of FSRM failed." + exit + } } elseif ($minorVer -ge 1 -and $checkFSRM.Installed -ne "True") { # Server 2008 R2 - Write-Host "FSRM not found.. Installing (2008 R2).." - Add-WindowsFeature FS-FileServer, FS-Resource-Manager + Write-Host "`n####" + Write-Host "FSRM not found.. Installing (2008 R2).." + $install = Add-WindowsFeature FS-FileServer, FS-Resource-Manager + if ($? -ne $True) + { + Write-Host "Install of FSRM failed." + exit + } + } elseif ($checkFSRM.Installed -ne "True") { # Server 2008 - Write-Host "FSRM not found.. Installing (2008).." - &servermanagercmd -Install FS-FileServer FS-Resource-Manager + Write-Host "`n####" + Write-Host "FSRM not found.. Installing (2008).." + $install = &servermanagercmd -Install FS-FileServer FS-Resource-Manager + if ($? -ne $True) + { + Write-Host "Install of FSRM failed." + exit + } } } else { # Assume Server 2003 - Write-Host "Other version of Windows detected! Quitting.." + Write-Host "`n####" + Write-Host "Unsupported version of Windows detected! Quitting.." return } -$fileGroupName = "CryptoBlockerGroup" -$fileTemplateName = "CryptoBlockerTemplate" -$fileScreenName = "CryptoBlockerScreen" - -$webClient = New-Object System.Net.WebClient -$jsonStr = $webClient.DownloadString("https://fsrm.experiant.ca/api/v1/get") -$monitoredExtensions = @(ConvertFrom-Json20($jsonStr) | % { $_.filters }) - -# Split the $monitoredExtensions array into fileGroups of less than 4kb to allow processing by filescrn.exe -$fileGroups = New-CBArraySplit $monitoredExtensions -ForEach ($group in $fileGroups) { - $group | Add-Member -MemberType NoteProperty -Name fileGroupName -Value "$FileGroupName$($group.index)" +## Enumerate shares +Write-Host "`n####" +Write-Host "Processing ProtectList.." +### move file from C:\Windows\System32 or whatever your relative path is to the directory of this script +if (Test-Path .\ProtectList.txt) +{ + Move-Item -Path .\ProtectList.txt -Destination $PSScriptRoot\ProtectList.txt -Force } -$scriptFilename = "C:\FSRMScripts\KillUserSession.ps1" -$batchFilename = "C:\FSRMScripts\KillUserSession.bat" -$eventConfFilename = "$env:Temp\cryptoblocker-eventnotify.txt" -$cmdConfFilename = "$env:Temp\cryptoblocker-cmdnotify.txt" - -$scriptConf = @' -param([string] $DomainUser) - -Function DenySharePermission ([string] $ShareName, [string] $DomainUser) +if (Test-Path $PSScriptRoot\ProtectList.txt) { - $domainUserSplit = $DomainUser.Split("\") - - $trusteeClass = [wmiclass] "ROOT\CIMV2:Win32_Trustee" - $trustee = $trusteeClass.CreateInstance() - $trustee.Domain = $domainUserSplit[0] - $trustee.Name = $domainUserSplit[1] - - $aceClass = [wmiclass] "ROOT\CIMV2:Win32_ACE" - $ace = $aceClass.CreateInstance() - $ace.AccessMask = 2032127 - $ace.AceType = 1 - $ace.Trustee = $trustee + $drivesContainingShares = Get-Content $PSScriptRoot\ProtectList.txt | ForEach-Object { $_.Trim() } +} +Else { + $drivesContainingShares = @(Get-WmiObject Win32_Share | + Select Name,Path,Type | + Where-Object { $_.Type -match '0|2147483648' } | + Select -ExpandProperty Path | + Select -Unique) +} - $shss = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$ShareName'" - $sd = Invoke-WmiMethod -InputObject $shss -Name GetSecurityDescriptor | Select -ExpandProperty Descriptor - $sclass = [wmiclass] "ROOT\CIMV2:Win32_SecurityDescriptor" - $newsd = $sclass.CreateInstance() - $newsd.ControlFlags = $sd.ControlFlags +if ($drivesContainingShares.Count -eq 0) +{ + Write-Host "`n####" + Write-Host "No drives containing shares were found. Exiting.." + exit +} - foreach ($oace in $sd.DACL) - { - $newsd.DACL += [System.Management.ManagementBaseObject] $oace - } +Write-Host "`n####" +Write-Host "The following shares needing to be protected: $($drivesContainingShares -Join ",")" - $newsd.DACL += [System.Management.ManagementBaseObject] $ace +# Download list of CryptoLocker file extensions +Write-Host "`n####" +Write-Host "Dowloading CryptoLocker file extensions list from fsrm.experiant.ca api.." - $share = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$ShareName'" - $setResult = $share.SetSecurityDescriptor($newsd) +$jsonStr = Invoke-WebRequest -Uri https://fsrm.experiant.ca/api/v1/get +$monitoredExtensions = @(ConvertFrom-Json20 $jsonStr | ForEach-Object { $_.filters }) - return $setResult.ReturnValue +# Process SkipList.txt +Write-Host "`n####" +Write-Host "Processing SkipList.." +### move file from C:\Windows\System32 or whatever your relative path is to the directory of this script +if (Test-Path .\SkipList.txt) +{ + Move-Item -Path .\SkipList.txt -Destination $PSScriptRoot\SkipList.txt -Force } +If (Test-Path $PSScriptRoot\SkipList.txt) +{ + $Exclusions = Get-Content $PSScriptRoot\SkipList.txt | ForEach-Object { $_.Trim() } + $monitoredExtensions = $monitoredExtensions | Where-Object { $Exclusions -notcontains $_ } -# Let's try altering share permissions.. -$Username = $DomainUser.Split("\")[1] - -$affectedShares = Get-WmiObject -Class Win32_Share | - Select Name, Path, Type | - Where { $_.Type -eq 0 } - -$affectedShares | % { - Write-Host "Denying [$DomainUser] access to share [$($_.Name)].." - DenySharePermission -ShareName $_.Name -DomainUser $DomainUser } - -Write-Host $affectedShares +Else +{ + $emptyFile = @' +# +# Add one filescreen per line that you want to ignore +# +# For example, if *.doc files are being blocked by the list but you want +# to allow them, simply add a new line in this file that exactly matches +# the filescreen: +# +# *.doc +# +# The script will check this file every time it runs and remove these +# entries before applying the list to your FSRM implementation. +# '@ + Set-Content -Path $PSScriptRoot\SkipList.txt -Value $emptyFile +} -$batchConf = @" -@echo off -powershell.exe -ExecutionPolicy Bypass -File "$scriptFilename" -DomainUser %1 -"@ - -$scriptDirectory = Split-Path -Parent $scriptFilename -$batchDirectory = Split-Path -Parent $batchFilename - -if (-not (Test-Path $scriptDirectory)) +# Check to see if we have any local patterns to include +Write-Host "`n####" +Write-Host "Processing IncludeList.." +### move file from C:\Windows\System32 or whatever your relative path is to the directory of this script +if (Test-Path .\IncludeList.txt) { - Write-Host "Script directory [$scriptDirectory] not found. Creating.." - New-Item -Path $scriptDirectory -ItemType Directory + Move-Item -Path .\IncludeList.txt -Destination $PSScriptRoot\IncludeList.txt -Force } - -if (-not (Test-Path $batchDirectory)) +If (Test-Path $PSScriptRoot\IncludeList.txt) { - Write-Host "Batch directory [$batchDirectory] not found. Creating.." - New-Item -Path $batchDirectory -ItemType Directory + $includeExt = Get-Content $PSScriptRoot\IncludeList.txt | ForEach-Object { $_.Trim() } + $monitoredExtensions = $monitoredExtensions + $includeExt } -# FSRM stipulates that the command directories/files can only be accessible by SYSTEM or Administrators -# As a result, we lock down permissions for SYSTEM and local admin only -Write-Host "Purging Non-Admin NTFS permissions on script directory [$scriptDirectory].." -PurgeNonAdminDirectoryPermissions($scriptDirectory) -Write-Host "Purging Non-Admin NTFS permissions on batch directory [$batchDirectory].." -PurgeNonAdminDirectoryPermissions($batchDirectory) - -Write-Host "Writing defensive PowerShell script to location [$scriptFilename].." -$scriptConf | Out-File -Encoding ASCII $scriptFilename -Write-Host "Writing batch script launcher to location [$batchFilename].." -$batchConf | Out-File -Encoding ASCII $batchFilename - -$eventConf = @" -Notification=E -RunLimitInterval=0 -EventType=Warning -Message=User [Source Io Owner] attempted to save [Source File Path] to [File Screen Path] on the [Server] server. This file is in the [Violated File Group] file group, which is not permitted on the server. An attempt has been made at blocking this user. -"@ - -$cmdConf = @" -Notification=C -RunLimitInterval=0 -Command=$batchFilename -Arguments=[Source Io Owner] -MonitorCommand=Enable -Account=LocalSystem -"@ - -Write-Host "Writing temporary FSRM Event Viewer configuration to location [$eventConfFilename].." -$eventConf | Out-File $eventConfFilename -Write-Host "Writing temporary FSRM Command configuration to location [$cmdConfFilename].." -$cmdConf | Out-File $cmdConfFilename +# Split the $monitoredExtensions array into fileGroups of less than 4kb to allow processing by filescrn.exe +$fileGroups = @(New-CBArraySplit $monitoredExtensions) # Perform these steps for each of the 4KB limit split fileGroups +Write-Host "`n####" +Write-Host "Adding/replacing File Groups.." ForEach ($group in $fileGroups) { - Write-Host "Adding/replacing File Group [$($group.fileGroupName)] with monitored file [$($group.array -Join ",")].." - &filescrn.exe filegroup Delete "/Filegroup:$($group.fileGroupName)" /Quiet + #Write-Host "Adding/replacing File Group [$($group.fileGroupName)] with monitored file [$($group.array -Join ",")].." + Write-Host "`nFile Group [$($group.fileGroupName)] with monitored files from [$($group.array[0])] to [$($group.array[$group.array.GetUpperBound(0)])].." + &filescrn.exe filegroup Delete "/Filegroup:$($group.fileGroupName)" /Quiet &filescrn.exe Filegroup Add "/Filegroup:$($group.fileGroupName)" "/Members:$($group.array -Join '|')" } -Write-Host "Adding/replacing File Screen Template [$fileTemplateName] with Event Notification [$eventConfFilename] and Command Notification [$cmdConfFilename].." +# Create File Screen Template with Notification +Write-Host "`n####" +Write-Host "Adding/replacing [$fileTemplateType] File Screen Template [$fileTemplateName] with eMail Notification [$EmailNotification] and Event Notification [$EventNotification].." &filescrn.exe Template Delete /Template:$fileTemplateName /Quiet -# Build the argument list with all required fileGroups -$screenArgs = 'Template','Add',"/Template:$fileTemplateName" +# Build the argument list with all required fileGroups and notifications +$screenArgs = 'Template', 'Add', "/Template:$fileTemplateName", "/Type:$fileTemplateType" ForEach ($group in $fileGroups) { $screenArgs += "/Add-Filegroup:$($group.fileGroupName)" } -$screenArgs += "/Add-Notification:E,$eventConfFilename","/Add-Notification:C,$cmdConfFilename",'/Type:Passive' +If ($EmailNotification -ne "") { + $screenArgs += "/Add-Notification:m,$EmailNotification" +} +If ($EventNotification -ne "") { + $screenArgs += "/Add-Notification:e,$EventNotification" +} &filescrn.exe $screenArgs +# Create File Screens for every drive containing shares +Write-Host "`n####" Write-Host "Adding/replacing File Screens.." -$drivesContainingShares | % { - Write-Host "`tAdding/replacing File Screen for [$_] with Source Template [$fileTemplateName].." +$drivesContainingShares | ForEach-Object { + Write-Host "File Screen for [$_] with Source Template [$fileTemplateName].." &filescrn.exe Screen Delete "/Path:$_" /Quiet &filescrn.exe Screen Add "/Path:$_" "/SourceTemplate:$fileTemplateName" } -Write-Host "Removing temporary FSRM Event Viewer configuration file [$eventConfFilename].." -Write-Host "Removing temporary FSRM Event Viewer configuration file [$cmdConfFilename].." -Remove-Item $eventConfFilename -Remove-Item $cmdConfFilename +# Add Folder Exceptions from ExcludeList.txt +Write-Host "`n####" +Write-Host "Processing ExcludeList.." +### move file from C:\Windows\System32 or whatever your relative path is to the directory of this script +if (Test-Path .\ExcludePaths.txt) +{ + Move-Item -Path .\ExcludePaths.txt -Destination $PSScriptRoot\ExcludePaths.txt -Force +} +If (Test-Path $PSScriptRoot\ExcludePaths.txt) { + Get-Content $PSScriptRoot\ExcludePaths.txt | ForEach-Object { + If (Test-Path $_) { + # Build the argument list with all required fileGroups + $ExclusionArgs = 'Exception', 'Add', "/Path:$_" + ForEach ($group in $fileGroups) { + $ExclusionArgs += "/Add-Filegroup:$($group.fileGroupName)" + } + &filescrn.exe $ExclusionArgs + } + } +} + +# Cleanup temporary files if they were created +Write-Host "`n####" +Write-Host "Cleaning up temporary stuff.." +If ($EmailNotification -ne "") { + Remove-Item $EmailNotification -Force +} +If ($EventNotification -ne "") { + Remove-Item $EventNotification -Force +} + +Write-Host "`n####" +Write-Host "Done." +Write-Host "####" + +################################ Program code ################################ diff --git a/PoC/README.md b/PoC/README.md new file mode 100644 index 0000000..024ce9c --- /dev/null +++ b/PoC/README.md @@ -0,0 +1,63 @@ +CryptoBlocker +============== + +This solution allows for numerous types of storage devices to be monitored from a machine running a Windows OS. + + How it Works + +If a user attempts to modify or create a malicious file on a monitored storage device, the main script of this proof of concept (PoC) will notify the defined administrator via email of the attempted malicious activity. If the monitored storage device is distributed from a FreeNAS server, the Samba_server service will be stopped to prevent futher malicious activity. In addition, all activity occurring on the monitored device will be written to a log file. + + + +Prerequisites + +There are two known prerequitesites that must be installed on the machine where the main script will be executed from. [Prerequitesites can be found here.](http://www.powershelladmin.com/wiki/SSH_from_PowerShell_using_the_SSH.NET_library#Downloads) + +1. Windows Management Framework version 5 or higher +2. PowerShell module SSHSessions, available in the PowerShell gallery + + +Script Definitions + +There are three scripts included in the PoC. + +1. RegisterScheduledTasks.ps1 + + This script will create two separate scheduled tasks within Window's Task Scheduler. + +2. downloadExeriantList.ps1 + + This script will execute once daily. It will pull an updated file extension list from [Experiant Consulting](https://fsrm.experiant.ca/api/v1/get) and save it to a file that will later be used to determine if saved files are malicious in nature. + +3. monitorFileshare.ps1 + + This script will execute at each system startup and continue running while the system is on. It monitors a predefined storage locations for changes and analyzes the changes for malicious content. + +Implementation of Proof of Concept + +1. Download PoC folder to the computer that will be used for monitoring +2. Update the variables within the scripts to match the environment the scipt will be ran in. + * downloadExperiantList.ps1 contains two variables that require envirnoment-spcicific values + * monitorFileshare.ps1 contains numerous environment-specific variables that must be provided. +3. Execute the 'RegisterScheduledTasks.ps1' script. +4. Execute the 'downloadExperiantList.ps1' script to initialize the extension file. +5. Restart the computer to activate the scheduled start-up event - to start the monitor script. + + +Usage + +This proof of concpet does not stop a ransomware infection from occurring or spreading to other systems. + +These scripts are provided as-is. This proof of concept does not prevent the loss of data. It creates an opportunity for an administrator to respond to suspicious activity. + +Acknowledgements + + Portions of this script were modeled from the following sources: + + https://superuser.com/a/844034/413983 + http://www.jonathanmedd.net/2013/08/using-ssh-to-access-linux-servers-in-powershell.html + http://www.powershelladmin.com/wiki/SSH_from_PowerShell_using_the_SSH.NET_library + https://stackoverflow.com/questions/23953926/how-to-execute-a-powershell-script-automatically-using-windows-task-scheduler + https://stackoverflow.com/a/36355678/1885954_ + https://blogs.technet.microsoft.com/heyscriptingguy/2015/10/08/playing-with-json-and-powershell/ + diff --git a/PoC/RegisterScheduledTasks.ps1 b/PoC/RegisterScheduledTasks.ps1 new file mode 100644 index 0000000..a226fda --- /dev/null +++ b/PoC/RegisterScheduledTasks.ps1 @@ -0,0 +1,19 @@ +#------------------------------------- +# Script: RegisterScheduledTasks.ps1 +# Version: v20170717.1.1 +# Comments: Creates Scheduled Tasks +# Author: John Murphy +#------------------------------------- +##Scheduled Task for updataing extension list daily +## $filePath1 must be the full path of the 'downloadExperiantList.ps1' script included in the repository +$filePath1 = "downloadExperiantList.ps1" +$trigger1 = New-ScheduledTaskTrigger -Daily -At 5am + +Register-ScheduledJob -Name DownloadList -FilePath $filePath1 -Trigger $trigger1 + +##Scheduled Task for starting monitor script during startup of computer +## $filePath2 must be the full path of the 'monitorFileShare.ps1' script included in the repository +$filePath2 = "monitorFileShare.ps1" +$trigger2 = New-ScheduledTaskTrigger -AtStartup + +Register-ScheduledJob -Name MonitorFileShare -FilePath $filePath2 -Trigger $trigger2 \ No newline at end of file diff --git a/PoC/downloadExperiantList.ps1 b/PoC/downloadExperiantList.ps1 new file mode 100644 index 0000000..228944d --- /dev/null +++ b/PoC/downloadExperiantList.ps1 @@ -0,0 +1,18 @@ +#------------------------------------- +# Script: downloadExperiantList.ps1 +# Version: v20170717.1.1 +# Comments: Downloads current list of extensions from Experiant Consulting +# Author: John Murphy +#------------------------------------- +##Website to download file extensions from## +$website = "https://fsrm.experiant.ca/api/v1/get" +##Location of flat file of extensions## +$filePath = "" +##Name of flat file## +$fileName ="" + +$webclient = New-Object System.Net.WebClient +$jsonString = $webclient.DownloadString($website) + +$filePath = $filePath + $fileName +$jsonString | Out-File $filePath diff --git a/PoC/monitorFileShare.ps1 b/PoC/monitorFileShare.ps1 new file mode 100644 index 0000000..40063e0 --- /dev/null +++ b/PoC/monitorFileShare.ps1 @@ -0,0 +1,103 @@ +<#---------------------------------------------------------- + Script: monitorFileShare.ps1 + Version: v20170717 + Comments: Sets up FileSystemWatchers and responds + to potentially harmful file changes + Author: John Murphy +----------------------------------------------------------#> +<#VARIABLE DECLARATION#> +## A FileSystemWatcher is defined for each storage area that requires monitoring. + $watcher = New-Object System.IO.FileSystemWatcher ## Defines Storage area to monitor + $watcher.Path = "" ## Path to storage area to monitor + $watcher.Filter = "*.*" ## Defines what files to monitor within Storage area + $watcher.IncludeSubdirectories = $true ## Watcher monitoring depth + $watcher.EnableRaisingEvents = $true ## Allows $watcher to take action on events +## ------------------------------------------------------------------------------------------------ +## JSON extension file loading - Loads extension to monitor for into local variable + $jsonFile = "" ## Complete path to file saved by script 'downloadExperiantList.ps1' + $jsonString = Get-Content -Raw -Path $jsonFile ## Converts file to string + $json = $jsonString | ConvertFrom-json ## Converts string to object containing the extensions to monitor +## ------------------------------------------------------------------------------------------------ +## Email notification - Used to send email notification to defined administrator + $senderEmail = "" ## From email address + $senderPassword = "" ## Password for 'From' email address + $receiverEmail = "" ## Email of administrator to notify + $smtpServer = "" ## SMTP server to use when sending email + $smtpSendPort = "" ## port used to send SMTP email (587) +## ------------------------------------------------------------------------------------------------ +## FreeNAS Samba Server - Used to disable Samba server on FreeNAS + $freenasRoot = "root" ## FreeNAS user with access to shutting down a service + $rootPassword = "" ## Password of above FreeNAS user + $freenasName = "" ## Server name, FQDN, or IP address of FreeNAS server +## ------------------------------------------------------------------------------------------------ +## Log file - used to log events detected by $watcher + $logFile = "" ## Full path to location where log file will be stored +<#END VARIABLE DECLARATION#> +##------------------------------------------- +## Function: Send-ToEmail +## Purpose: Sends email to predefined administrator +## using credentials provided in variable declaration +##------------------------------------------- +function Send-ToEmail($extension, $path){ + $emailMessage = New-Object Net.Mail.MailMessage + $emailMessage.From = $senderEmail + $emailMessage.To.Add($receiverEmail) + $emailMessage.Subject = "Suspicious File Detected" + $emailMessage.Body = "A suspicious file was detected located at $path" + + $smtpClient = New-Object Net.Mail.SmtpClient($smtpServer,$smtpSendPort) + $smtpClient.EnableSsl = $true + $smtpClient.Credentials = New-Object System.Net.NetworkCredential($senderEmail,$senderPassword) + $smtpClient.Send($emailMessage) + Write-Host "Message Sent" +} +##------------------------------------------- +## Function: Disable-FreeNAS-Samba +## Purpose: Stops Samba-Server service on FreeNAS server +## Prerequisites: Requires WMF v5 and higher +## Requires Module = SshSessions +##------------------------------------------- +function Disable-FreeNAS-Samba(){ + New-SshSession -ComputerName $freenasName -Username $freenasRoot -Password $rootPassword + Invoke-SshCommand -ComputerName $freenasName -Command "service samba_server stop" + Remove-SshSession -ComputerName $freenasName + Write-Host "Samba has been stopped" +} +##------------------------------------------ +## Define Actions to take after an event is detected +##------------------------------------------ +$action = { $path = $Event.SourceEventArgs.FullPath + $fileName = Split-Path $path -Leaf + $changeType = $Event.SourceEventArgs.ChangeType + $user = $env:USERNAME + $logline = "$(Get-Date), $changeType, $path, $user, $computer" + Add-content $logFile -value $logline + Write-Host "The file '$fileName' was '$changeType' by '$user'" + foreach($obj in $json.filters) + { + if("$fileName" -like "$obj") + { + Disable-FreeNAS-Samba + Send-ToEmail($obj,$path) + } + } + Write-Host "Action Complete" +} +##------------------------------------------ +## Register events to monitor +##------------------------------------------ +Register-ObjectEvent $watcher "Created" -Action $action +Register-ObjectEvent $watcher "Changed" -Action $action +Register-ObjectEvent $watcher "Deleted" -Action $action +Register-ObjectEvent $watcher "Renamed" -Action $action +while($true){Start-Sleep 5} + + +<# Portions of this script were modeled from the following sources: + https://superuser.com/a/844034/413983 + http://www.jonathanmedd.net/2013/08/using-ssh-to-access-linux-servers-in-powershell.html + http://www.powershelladmin.com/wiki/SSH_from_PowerShell_using_the_SSH.NET_library + https://stackoverflow.com/questions/23953926/how-to-execute-a-powershell-script-automatically-using-windows-task-scheduler + https://stackoverflow.com/a/36355678/1885954_ + https://blogs.technet.microsoft.com/heyscriptingguy/2015/10/08/playing-with-json-and-powershell/ +#> \ No newline at end of file diff --git a/README.md b/README.md index 84c53b7..80cf320 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ The script will install File Server Resource Manager (FSRM), and set up the rele Script Deployment Steps +NOTE: Before running, please add any known good file extensions used in your environment to SkipList.txt, one per line. This will ensure that if a filescreen is added to the list in the future that blocks that specific file extension, your environment won't be affected as they will be automatically removed. If SkipList.txt does not exist, it will be created automatically. + 1. Checks for network shares 2. Installs FSRM 3. Create batch/PowerShell scripts used by FSRM @@ -16,13 +18,7 @@ The script will install File Server Resource Manager (FSRM), and set up the rele How it Works -If the user writes a malicious file (as contained in the file group) to a network share, FSRM will run the deployed script which will add a Deny permission for that user against every share. - -This has been tested fairly thoroughly, and I find that at most ransomware ends up encrypting one directory before the user is blocked. - -The script has now been modified to pull the list of extensions from a JSON API. Credit to https://fsrm.experiant.ca/ for this list. -Make sure you review the list (https://fsrm.experiant.ca/api/v1/get) before deploying, in case any false positives are listed (e.g. I have seen CAD software legitimately use *.encrypted before). -When this list is updated, review it and simply run the script again to redeploy. +If the user attempts to write a malicious file (as described in the filescreen) to a protected network share, FSRM will prevent the file from being written and send an email to the configured administrators notifying them of the user and file location where the attempted file write occured. NOTE: This will NOT stop variants which use randomised file extensions, don't drop README files, etc @@ -32,6 +28,14 @@ Just run the script. You can easily use this script to deploy the required FSRM An event will be logged by FSRM to the Event Viewer (Source = SRMSVC, Event ID = 8215), showing who tried to write a malicious file and where they tried to write it. Use your monitoring system of choice to raise alarms, tickets, etc for this event and respond accordingly. +ProtectList.txt + +By default, this script will enumarate all the shares running on the server and add protections for them. If you would like to override this, you can create a ProtectList.txt file in the script's running directory. The contents of this file should be the folders you would like to protect, one per line. If this file exists, only the folders listed in it will be protected. If the file is empty or only has invalid entries, there will be no protected folders. + +IncludeList.txt + +Sometimes you have file screens that you want to add that are not included in the download from Experiant. In this case, you can simply create a file named IncludeList.txt and put the screens you would like to add, one per line. If this file does not exist, only the screens from Experiant are included. + Disclaimer This script is provided as is. I can not be held liable if this does not thwart a ransomware infection, causes your server to spontaneously combust, results in job loss, etc.