Build your own Host Intrusion Detection System with WMI and Powershell

 Edit: As correctly pointed out by Lee Holmes this isn't the first and only implementation of a WMI / Powershell based HIDS. There's WMI-IDS and Uproot which take things a step further but please do read on for a basic introduction of the concepts

Hi there, welcome to another post in the series of DIY Powershell – WMI scripting. This time we’ll try to build a modest personalized Host-based Intrusion Detection System (HIDS) called the ‘Paranoid Security Guy’ using nothing but WMI and Powershell. Why paranoid? Well let’s say he’s quite the pessimist…

HIDS

HIDS can play an important role in identifying malware and can be a serious addition to your security arsenal. For those of you who get confused about all the HIDS,NIDS,HIPS and NIPS out there : they are devices or software that Detect(s) or Prevent(s) intrusions by inspecting Host-based or Network-based information. Typically, your average HIDS will monitor behavior of executing processes on the local system. If a program acts like malware there’s a good chance that it is indeed malware.

The goal of this post and the previous ones is not to deliver a program or script that can compete with top notch tools out there( which would be a little optimistic), but too encourage people to try out some of the stuff and hopefully learn some new things about WMI – Powershell scripting and / or security in general .

First off, do realize that the techniques I’ll be showing here to catch events with WMI to build your HIDS are ironically the same techniques some malware authors use to gain persistence on exploited systems. It just shows that every technology can be used for good or evil, it just depends on the intentions of the programmer. Lately however, in some security circles, Powershell and WMI seem to be getting frowned upon because lots of malware is making use of it. Well let me tell you a little secret: the reason the bad guys are using it is because they realize how powerful it is, but the reason people are getting compromisedby that same malware is very likely due to some major security fail and not to the allowing of the 2 technologies on their network.

That being said: the HIDS that we’ll be building will allow you to monitor the following ‘classic’ malware related events:

1)      Stopped services

2)      Changes to the current user’s run key and runonce key

3)      Processes started from the %USERFOLDER%\appdata\local\temp

4)      Processes started from the $Recycle.bin folder

5)      Network logon (type 3) to the system

6)      File creation in \Windows\System32 folder (not recursively)

7)      File deletion in \Windows\System32 folder (not recursively)

8)      File renaming in \Windows\System32 folder(not recursively)

I know they’re quite cliché but hey, you’ve got to start somewhere …

 Whether or not you’re really going to detect malware with these couple of events is not the point, I just need them to demonstrate the techniques. In reality your HIDS would trigger on a lote mor events.

I’ll give some examples how the aforementioned events can be related to malware:

1)                Malware sometimes pro-actively stops anti-virus or automatic update services

2)               Modifying the run keys is the ‘granny persistence mechanism’

3 & 4)             A lot of malware executes from these locations, legit processes (often) do not

5)               Could be indicative of lateral movement, or mapping of administrative shares

6 &7 & 8)  It is very rare to see modifications in this folder (except for updates or malware)

For each of these events our script will give us a notification in the system tray. It will not offer the possibility to block the process that caused the notification. Hence the ‘D’ in HIDS: we detect but do not prevent so whether or not action should be taken is all up to the interpretation of the user, but the mere fact that you are getting these notifications can make a huge difference.

For example: probably everybody enables ‘logon event auditing’ in the event logs (if not you’d better), but who checks his event logs on a daily basis for signs of lateral movement? Right, so if you see a message popping up that a network logon (type = 3) was made to your system from let’s say the subnet of your company’s sales department you might start asking some questions.

Also know that your HIDS is going to give you some false positives. For instance, Windows services are being stopped by the operating system itself on a regular basis, so don’t freak out if you suddenly see a service like ‘osppsvc.exe’ being stopped. In fact, deciding whether a single event on its own could be considered as evil is quite hard .But if you see your antivirus being stopped, followed by a file being dropped in the Windows\System32 folder and then a modification to your run key you might start suspecting something’s amiss.

In the current implementation it is left up to the user to link the events together but it shouldn’t be too much trouble to improve the script by defining a set of granular events that get registered (what the script does now) and instead of reacting to a sole event we create combinations of events that cannot occur within a certain time frame. So if the scripts registers a run key modification and it has registered AV being stopped and a file being created in the system32 folder all during a period of a minute then we give a popup notification!  -> Keep checking the blog !

It might not be wise to create popup-notifications for too many events, because if the number of popup messages starts exceeding a certain number you’ll probably just start ignoring them but if you trigger too less events you might have ‘false negatives’ which basically means that you’re missing attacks. So you’d want to spend some time deciding what to trigger on and what not depending on your environment and try to figure out what can be considered as normal and what as abnormal.

Before we get to the implementation let me repeat: as I mentioned in my previous post, WMI and Powershell are susceptible to Rootkit manipulation, but we won’t let that ruin the fun. There’s plenty of malware that doesn’t involve rootkits and it’s hard to cover up all of your “behavior”.

The WMI implementation.

For completeness I’ll have to mention that there is a newer way for monitoring events with Powershell than using WMI. From Powershell version v3.0 on you can use CIM-cmdlets. The WMI and CIM approach do not differ that much, in fact WMI is an implementation of the “CIM standard”. One of the differences is that WMI uses DCOM for remoting (which isn’t really firewall friendly) and CIM uses WINRM (which is the same as Powershell). But since our HIDS is only querying for local system information I chose to use WMI and make the the script Powershell V2.0 compatible.

First I’ll show you the building blocks to detect events using WMI in Powershell. We define a WMI Event by assigning a Query, Action and SourceIdentifier field through splatting.

query contains your WQL (WMI Query Language) query. Here we define the conditions we use to trigger an event.

action contains the action you want to perform when the event is triggered (I feel so smart typing this)

sourceIdentifier is just a name you assign for future referencing

In Powershell you register your event using register-wmiEvent .

Here’s an example to detect stopped services:

$wmiEvent = @{

     Query ="SELECT * FROM __InstanceModificationEvent WITHIN 2 WHERE TargetInstance Isa 'Win32_Service' AND TargetInstance.State = 'Stopped'"

     Action = { write-hostA service was stopped”  }

     SourceIdentifier = "Service.Stopped"
}

$Null = Register-WMIEvent @wmiStoppedService 

Now I could try to look really smart and start explaining all about WMI , WQL, WMI providers and just be literally copy-pasting content from Technet  and MSDN but guess what ? I’m not going to do that, those pages were written just for that sole purpose …

I think the best approach in understanding all of this would be a top-down approach where you start by running the script and look at what it does , try to invoke some events (by for example stopping a service manually) . Then go look at the differences in how every wmiEvent is defined and try to find the subtle differences in each WMI query.  Lots and lots of pages have been written on WMI and Powershell (I guess everybody knows the scripting guy ).

FYI: for the monitoring of the \Windows\System32 folder I’m not using WMI but a .Net class called FileSystemWatcher. Note that in this case we have to use Register-ObjectEvent to subscribe to events generated by the FileSystemWatcher.

Before I’ll show you the script a last word on how I implemented the notifications in Powershell. I’m using the System.Windows.Forms.NotifyIcon class to represent the pop-up messages. When the user clicks the icon of the message once the message will re-appear (in case you missed some notifications). When you double-click the icon it will disappear. All notifications will disappear from the tray when your Powershell session ends.

Tom Asselman

For those interested you can follow me on twitter

Download full script here (don’t forget to unblock-file in Powershell after download)

 "
 ##############################################################################################
 # Paranoid Security Guy HIDS
 # © Tom Asselman
 # Cyberforce
 # www.cyberforce.be
 # This script is free to use but please do not use, reuse, build new scripts using this code 
 # or spread without giving credit to the author.
 #
 ##############################################################################################
 
 "
 ##SOME WMI EVENTS NEED ADMIN PRIVELEGES TO BE REGISTERED !!!  


 <################################  INITIALIZATION  #####################################>


 #If you're not running from powershell_ise these assemblies need to be loaded explicitly
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.NotifyIcon")

#Getting current script path
#powershell 2 compliant
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition

$balloonTipInterval = 10000

#Hash tables that link notifications and there corresponding timer objects
$notification_Timer = @{}
$timer_Notification = @{}

#Messages for the paraoid security guy 🙂
$ParanoidMsgs =        @("Are we under attack?",
                         "They're hacking the sh*t out of you!", 
                         "They're probably exfiltrating by now!", 
                         "You are getting owned!", 
                         "Do NOT keep calm !", 
                         "Jeeeeeez !", 
                         "Tight security in here...",
                         "DUDE !!!" ,
                         "You might as well give them your password...",
                         "Your security <= 0",
                         "Kiss your data goodbye!" 
                        )


<########################################  FUNCTIONS FOR NOTIFICATIONS ###########################################

<#
createNotification
    Creates windows notifications that pop up in the system tray. 
    When the icon of the notifications is clicked once the message re-appears.
    When the icon of the notifications is double-clicked the notification is removed

#>
function createNotification($text){

    $objNotifyIcon = New-Object System.Windows.Forms.NotifyIcon 
    $objNotifyIcon.Icon = "$scriptPath\psg.ico"
    $objNotifyIcon.BalloonTipIcon = "info" 
    $objNotifyIcon.BalloonTipText = $text
    $objNotifyIcon.BalloonTipTitle = "Paranoid Security Guy"
    $objNotifyIcon.Visible = $True 
    $objNotifyIcon.ShowBalloonTip($balloonTipInterval)
    
    #Timer is used to be able to differentiate between single and double mouse clicks
    #We use the timer to create a delay for the message to re-appear
    #Without the delay from the timer the system only registers single clicks
    $timer = New-Object System.Timers.Timer
    $timer.Interval = 100
    $timer.AutoReset = $False
    $notification_Timer.add($timer.GetHashCode(),$objNotifyIcon)
    $timer_Notification.add($objNotifyIcon.GetHashCode(), $timer)
    
    
    Register-ObjectEvent -InputObject $objNotifyIcon -EventName MouseClick -Action {
        $t = $timer_Notification.item($Sender.getHashCode())
        $t.Start()
    }

    Register-ObjectEvent -InputObject $objNotifyIcon -EventName DoubleClick -Action {
        #remove the notification  + cleanup hashtables
        $t = $timer_Notification.item($Sender.getHashCode())
        $notification_Timer.remove($t.getHashCode())
        $timer_Notification.remove($Sender.getHashCode())
        $Sender.Dispose()
    }

    Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action {
        $notif = $notification_Timer.item($Sender.getHashCode())
        $notif.ShowBalloonTip($balloonTipInterval)
                  
    }
}


<#
showNotification
    Display's the notification text and add's some paranoid interpretations   
#>
function global:showNotification($text){
    $random = Get-Random($ParanoidMsgs.Length)
    createNotification((get-date).ToString() +" " + $text + "`r`n" + $ParanoidMsgs[$random])
}


<###################################  WMI - EVENTS  ############################################



<#
 #
 # Monitoring of services
 # All services who stop cause a popup
 #
 #>


$wmiStoppedService = @{
     Query ="SELECT * FROM __InstanceModificationEvent WITHIN 2 WHERE TargetInstance Isa 'Win32_Service' AND TargetInstance.State = 'Stopped'"

     Action = {
        showNotification($Event.SourceEventArgs.NewEvent.TargetInstance.Name + " service was stopped.")
    }
    SourceIdentifier = "Service.Stopped"
}
$Null = Register-WMIEvent @wmiStoppedService
"Monitoring for stopped services."



<#
 #
 Monitoring of network logons
 Notifications about logons of type 3 get 
 #get-help
 #>


$wmiNetworkLogon = @{
     Query ="select * from __InstanceCreationEvent where TargetInstance ISA 'Win32_NTLogEvent' and TargetInstance.eventcode = 4624 "

     Action = {
        $msg = $Event.SourceEventArgs.NewEvent.TargetInstance.message
        $logtype3 = ($msg.Split( [environment]::NewLine) | Select-String("Logon Type:") -SimpleMatch) -like "*3*"  
        
        if ($logtype3) {
            
            $source =  ($msg.Split( [environment]::NewLine) | Select-String("Source Network Address") -SimpleMatch)
         
            showNotification("A network logon was performed." + $source)
        }
                     
    }
    SourceIdentifier = "network.Logon"
}

$Null = Register-WMIEvent @wmiNetworkLogon
"Monitoring for network logons."




<#
 
 Monitoring of processes
 Processes who start from %temp% folder or $recycle.bin get flagged.

#>


$wmiProcessStarted = @{
    #we don't use Win32_ProcessStartTrace , sometimes it seems to miss really short living processes
    Query ="select * from __InstanceCreationEvent within 1 where TargetInstance ISA 'Win32_process' "

    Action = {
        $process = Get-Process -Id $Event.SourceEventArgs.NewEvent.TargetInstance.processid | Select-Object id,path
        if ($process.path -and ( (Get-Item -LiteralPath $process.path).FullName -like (Get-Item -LiteralPath $env:temp).FullName + '*' `
                                                            -or $process.path -like "*\`$Recycle.Bin\*") )
        {
            showNotification("Process with pid " + $process.id + " runs from       " + $process.path)
        }
        
    }
    SourceIdentifier = "Process.Started"
}

$Null = Register-WMIEvent @wmiProcessStarted
"Monitoring for processes starting from $env:temp and \`$Recycle.Bin."




<#
 #
 Monitoring for changes in runkeys of the active user
 # 
 #>


#We monitor the hive of the current user so we search his SID
$userSid = ((Get-WmiObject -Class Win32_UserProfile -Namespace "root\cimv2"  | select sid,localpath) | Where-Object localpath -like "*$env:USERNAME*" ).sid
$wmiRegistryKeys = @{
    Query ="SELECT * FROM RegistryKeyChangeEvent WHERE Hive='HKEY_USERS' AND (KeyPath='$userSid\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce' `
                                                                         OR  KeyPath='$userSid\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' )"
    
    Action = { showNotification("Current user's run keys were modified.") }
    
    SourceIdentifier = "RegistryKey.Changed"
}

$Null = Register-WMIEvent @wmiRegistryKeys
"Monitoring for registry changes in current user's run key and runonce key."



<#
 
 Monitoring of System32 folder
 We do not monitor recursivly, to avoid an overload of events
 We monitor for creation,deletion,renaming

#>

$fsWatcher = New-Object System.IO.FileSystemWatcher
$fsWatcher.Path = $env:windir+"\system32"
$fsWatcher.IncludeSubdirectories = $false
$fsWatcher.EnableRaisingEvents = $true

"Monitoring for file creation in " + $env:windir +"\system32."
$System32Created = Register-ObjectEvent $fsWatcher "Created" -Action {
   showNotification("File created in system32 folder.           " +$($eventArgs.FullPath))
}

"Monitoring for file deletion in " + $env:windir +"\system32."
$System32Deleted = Register-ObjectEvent $fsWatcher "Deleted" -Action {
   showNotification("File deleted in system32 folder.           " +$($eventArgs.FullPath))
}

"Monitoring for file renaming in " + $env:windir +"\system32."
$System32Renamed = Register-ObjectEvent $fsWatcher "Renamed" -Action {
   showNotification("File renamed in system32 folder.           "+$($eventArgs.FullPath))
}



#########################  Main  ###########"##############

"
##############################################################################################
If You see errors during initialization make sure you have admin privileges.
Paranoid Security Guy is ready!
Hit ctrl-c to exit "


try{
   
    #We stay in an endless loop to keep the session open
    while($True){
        Wait-Event  
    }
}
catch{

}
finally{
    write-host "Thanks for using The Paraoid Security Guy!"
    
    unregister-event -sourceIdentifier "Service.Stopped"   -ErrorAction Stop
    unregister-event -sourceIdentifier "Network.Logon" -ErrorAction Stop
    unregister-event -sourceIdentifier "Process.Started"   -ErrorAction Stop 
    unregister-event -sourceIdentifier "RegistryKey.Changed"  -ErrorAction Stop
    Unregister-Event $System32Created.id
    Unregister-Event $System32Deleted.id
    Unregister-Event $System32Renamed.id
    
    write-host "All bindings are unloaded."
    
}

One Comment

  1. Mahima

    I am receiving this type of error upon running the same code:

    Register-WmiEvent : Unparsable query.
    At C:\Users\user\Documents\SQL Server Management Studio\Untitled1.ps1:73 char:9
    + $Null = Register-WmiEvent @wmiStoppedService
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Register-WmiEvent], Managemen
    tException
    + FullyQualifiedErrorId : System.Management.ManagementException,Microsoft.
    PowerShell.Commands.RegisterWmiEventCommand

Leave a Reply

Your email address will not be published. Required fields are marked *