Hunting for Ransomware with Powershell


This is part 2 of the series on remote IOC scanning with Powershell.

We left off with our script being able to scan remotely for the existence of files and registry keys. The limitation we had so far was that when we specified registry keys in the HKEY_CURRENT_USER hive (HKCU) it would only scan the user hive of the user under which we ran the script. Not really practical since most systems contain at least a couple of user accounts. Furthermore we needed administrative privileges on all the remote systems if we wanted to scan remotely so typically you would perform the script running under a domain administrator account but if you implement least-privilege practices that account should in fact be the last one to contain indicators of compromise. And last but not least, the user hives are one of the favorite locations for malware authors to hide their data and they probably are the number one location to search for persistence.

So in this post I’ll show you an implementation I wrote which allows you to load all the user hives on the system you’re scanning and search for user specific keys in each of those hives. At the end of the post I’ll show you an example how to use the script to scan for a ransomware infection on your network.

The scripts have been tested on Windows 7 (Powershell v3.0), Windows 2012, Windows 2008 but if you do encounter any problems or bugs please let me know and just saying: these posts are meant as a proof of concept, I’m not expecting to win a Pulitzer Prize for the code…

And I do know that the code layout on the blog is horrible but for the moment we do not have any other option so just copy paste it and view it in your favorite Powershell editor.

A word on Powershell for malware hunting

Powershell is really a great scripting language and I think a lot of people do not really know or see the potential it has. As I mentioned before the remoting capabilities are really awesome but it even allows you to perform .Net code in your scripts without much effort. In fact Powershell is entirely built on the .Net framework and if you wanted to you could incorporate all your custom-made C# functions in your scripts.

Alas this is also the downside of using Powershell for malware hunting: your Powershell scripts will translate to .Net code which in turn will translate to some Windows API system calls which can be subverted by malware (by rootkits in particular). I’m not going to explain the concept of rootkits in full detail but just know that rootkits have the ability to hook system calls, intercept the result and modify it. So if your computer is infected with a rootkit and you’re querying the system for a key “EvilMalwareKey” using the Windows API it might seem as though it’s not there.

So why bother using Powershell for registry or file-based IOC scanning? Well, writing a rootkit which does not crash the system is not that easy and your average script kiddie will probably not be able to accomplish it. So a lot of malware just hides in plain sight.  And for the purpose of this blog post: most ransomware does not care about being detected, in fact you’re going to find out pretty soon that you’ve been hit by one. To mitigate the damage you would most likely want to localize the infection(s) as fast as possible and kill the process(es).

So in short, if your deep dive analysis does not show any signs of a rootkit you can still use Powershell to scan your network.

Just show me the damn script!

If you’re just interested in the code you can download a zip file in our downloads section.

UPDATE : To make sure your restriction policy does not mess up the scripts because the content was downloaded from the internet, execute the following commands to unblock the downloaded content (after you verified that the scripts do not contain any evil code of course )

Unblock-File .\PS_IOC.ps1
Unblock-File .\IOCFunctions.ps1

To start off the scripting part, in my previous post I mentioned that your host list could contain hostnames or ip addresses (in fact the nmap scan delivered a list of ips). Well it turns out that for the invoke-command to accept an ip address the ip address of the remote computer must be included in the WinRm Trustedhosts list on the local computer,  ( and in my testing environment this condition was met without me being aware of it. Since I did not want to modify the Trustedhost list each time an ip address was used I added a function readhostfile which would perform a dns lookup if an IP address was specified instead of a hostname.

function readHostFile ( $fileName ){
    Get-Content $fileName | ? { $_ -notmatch "^#.*" } `
                          | %{ if ($_ -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}") {[]::GetHostByAddress($_).HostName.Split('.')[0]}

So now for the “heavy lifting” part of the script: the loading of all the user hives.

Again I’m not going to explain every line of code word for word, I’m using Powershell, WMI, some .Net calls and windows command line functionality.  If you have some questions: mail me or ask your friend Google. I’m just going to give an idea of how I approached the problem.

I’m using WMI to get a list of all the user accounts on the system. I’m especially interested in the NTUSER.DAT location and the corresponding SIDs. Just know that WMI might also give you accounts that do not have a corresponding NTUSER.DAT on the system for which the script will give a warning.

Windows allows you to mount a user hive using the corresponding NTUSER.DAT file (you need admin privileges !) in your HKEY_USERS (HKU) hive. If you watch your HKU hive in regedit you’ll probably see the hives for the local system/ service and network service (S-1-5-18, S-1-5-19, S-1-5-20) loaded and one or more user hives. The HKU will contain the user hive for each user that is currently logged on or has logged on during the time the system was powered on. So if you switch accounts to another account and then switch back to your original user and restart your regedit( important) you’ll see that another account was added to HKU. (For each user SID you will also see an entry like S-1-5-XXXXXXXXX_Classes which we will not use.)

To perform a complete user registry scan we have to:

1)      Search all the user hives which are currently loaded in HKU and scan them for the IOCs

2)      For all the user profiles that weren’t already loaded in HKU we will load the NTUSER.DAT file under a temporary mount point. Scan that temporary mount point using our IOCs and then unload the hive.

So in our IOC list we will differentiate between user specific keys and all the others (keys from the Software, System hives…). The user specific keys will get a special treatment.

If a registry key / value is found it will be outputted to the screen together with the machine name on which it was found. In the name of the key that is displayed you will be able to see the SID of the user hive in which the key was located. A key in which the SID is preceded by ‘TEMP_’ is located in a hive which was temporary loaded on the system.

function checkRegIOCsAllUsers([string[]] $Reg_IOC_List){
  "`nScanning for registry IOCs on $env:COMPUTERNAME.`n"
  #If NO HKCU entries present ? Just do normal handling with checkRegIOCs
  if ( (@($Reg_IOC_List -match 'HKCU:*')).Length -le 0 ){

  #Otherwise we split them up in a user specific list and all the others...
     $non_HKCU_KEYS = $Reg_IOC_List | Where-Object {$_.ToUpper() -notmatch "HKCU:*"}
     $HKCU_KEYS     = $Reg_IOC_List | Where-Object {$_.ToUpper() -match    "HKCU:*"}
     #Normal handling for non_HKCU keys with checkregiocs
 Function name : checkUserSpecificKeysAllUsers
 Parameters    : $HKCU_KEYS , array of strings in the format required by checkRegIOCs. No error checking is performed whether the correct format is used !
 This function is not meant to be called directly by the user only by checkRegIOCsAllUsers function

 The function will scan the "user hives" of all users which have an NTUSER.DAT file on the system for the keys and values specified in the parameter

 function checkUserSpecificKeysAllUsers([string[]] $HKCU_KEYS ){
  #We query Wmi to get all user profiles on the system
  #We add sid and localpath to our working set ($userWorkingSet)
  $allUserInfoObjects = (Get-WmiObject -Class Win32_UserProfile -Namespace "root\cimv2"  | select sid,localpath)
  #this is list of psObjects , we'll be converting to a hash table to make it easier to work with
  $userWorkingSet = @{}
  $allUserInfoObjects | %  {$userWorkingSet.Add($_.sid,$_.localpath) }
  #We query the registry to see which hives are allready loaded in HKU ($loadedUserHives)
  $loadedUserHives = gci registry::hku
  #we exclude hives with S......_Classes in the name
  $loadedUserHives = $loadedUserHives | Select-String -NotMatch "S.*_Classes" `
                                      | select -ExpandProperty line
  #We will remove all entries from $userWorkingSet that re already loaded in HKU
  $loadedUserHives | % {
    $currentSid = $_.substring(11)
    if ($userWorkingSet.containskey($currentSid) ){

  #So nowe we have 1) A list of all user hives that are allready loaded in memory : $loadedUserHives
  #                2) A set of hives that we have to load and unload manually     : $userWorkingSet

  #1)  ----Scan the allready loaded hives on the system-------------
  #replace for powershell compatability
  $loadedUserHives = $loadedUserHives -replace "HKEY_USERS" ,"registry::HKU" 
  #From our original IOC list $HKCU_KEYS we create a new IOC list where we substitute HKCU with the SID of all the loaded users
  #We will use an Arraylist for performance reasons
  $HKCU_KEYS_ALL_LOADED_USERS = New-Object System.Collections.ArrayList
  $HKCU_KEYS | %{
    $currentKey = $_
    $loadedUserHives | % {
      [void]  $HKCU_KEYS_ALL_LOADED_USERS.add( ($currentKey -replace "HKCU:" , ($_+'\') ))

  #All we have to do now is pass our newly created IOC list to our function

  ##2) ---- Load / Scan /unload the hives from our workingset -------------
  $userWorkingSet.keys | %{

     #We must mount new hives under HKU or HKLM
     $tempHive = 'HKU\temp_' + $_
        $userHiveLocation = $userWorkingSet.item($_)  +"\NTUSER.DAT"
        if ( -not (test-path $userHiveLocation) ){
            Write-Error("<WARNING>Trying to load unexisting hive - $userHiveLocation on $env:COMPUTERNAME - SKIPPING <WARNING>") -ErrorAction stop
        #"loading" + $tempHive + " with " + $userHiveLocation
        $cmd = "reg load $tempHive `"$userHiveLocation`""
        [void] (Invoke-Command  -ScriptBlock {cmd /c $cmd} -ErrorAction Stop)
        #We replace HKCU with special notation
        $New_KEYS = $HKCU_KEYS -replace 'HKCU:' , ("registry::" + $tempHive + '\')
        #For each loaded hive we perform our scan
        #Unloading hives
        $cmd = "reg unload $tempHive"
        [void] (Invoke-Command -ScriptBlock {cmd /c $cmd} -ErrorAction Stop)

Hunting for ransomware

As an example for the practical use of these scripts I’ll show you how to hunt for rampaging crypto infections on your network. As everybody knows ransomware is really on the rise these days so every form of mitigation would be welcome. Beside the preventive side which I’m not going to get into right now it’s also key to be able to localize the process(es) that are performing the encryption as fast as possible. It’s also important to identify all of the infections. It’s not unlikely that a company was subjected to a crypto / spam attack and several employees clicked on an “evil” e-mail. So before you start restoring your backups you need to make sure that all infections have been identified and stopped.

If you’re lucky enough to not get hit by a zero-day crypto variant a simple lookup on the internet might give you more information about what you’re dealing with and give you a list of IOCs to scan for. For example if you see a lot of .locky extensions popping up it doesn’t take a genius to figure out you’ve been hit by Locky ransomware.

So the first hit on Google shows us:

So your Registry IOC file that you would create for your script would be:






And you start the script with your host list and regIoc list as parameter.

Of course this method of scanning the registry does not work for ransomware which stores data in random generated key names. But still, not all entries can be truly random because otherwise it would be impossible for the malware to locate them. It could for example be using a function that scrambles your hostname in a specific way but that would require reverse engineering of the malware to discover the method. But that’s another story …

A lot of ransomware runs from‘random-name.exe’ in your %temp% folder. You could add the following ioc entry to your fileIoc file to get a quick overview of all executables in all user profiles and try to spot something dodgy.


See you next month !


      1. Tom Asselman

        Hey Jon ,
        If you want to run the script without making use of WinRm you’ve got to modify the main code of the ps_ioc.ps1 script. Instead of building the session and calling invoke-command just call checkFileIocs and checkRegiocsAllUsers like a normal function. Of course this will only allow local execution. Hope this helps

Leave a Reply

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