Wednesday, February 22, 2017

Remote IOC scanning with powershell





The next couple of weeks I’m going to post some short articles about Windows based (Powershell – batch - wmi ) scripting that will show you some tips and tricks on how to create your own custom scripts to help you perform some basic IOC scanning over the network DIY-style. This is not going to be a Powershell 101 course, so it requires some prior knowledge on basic Powershell syntax but there are literally thousands of web pages devoted to the subject.
When doing incident response, being able to query the existence of certain files, processes or registry keys on XXX systems at once over the network can really save you a lot of time and becomes almost mandatory if you’re dealing with larger networks. To be clear, most of the time we’re dealing with small to medium sized networks (< 300 hosts) but you’ll see that some of these techniques should work on a larger scale too and it never hurts to have another tool under your belt, does it?
So a possible scenario in which you might use this is when you have done some analysis of a couple of compromised systems and you’ve found some IOC‘s that you’d like to scan for on all other hosts in the network to try to determine the full scope of the attack.
I’m pretty sure that all the tools from FireEye, Countertack, F-Response and many others outclass the capability of these scripts by a million miles but if you’re like me working for a rather small company which simply can’t afford those high-end tools then read on !
Before kicking off, I would like to inform you that I’m not reinventing the wheel here and many free tools already exist but I just didn’t find any that really satisfied my needs... For example the Kansa framework from Dave Hull is really great for incident response but the Get-Loki module that’s provided for IOC scanning seems to be really slow ( he even mentions a 10 hour scanning time in the coding comments:).

MAPPING THE NETWORK
So you want to do some remote scanning for IOC’s! Great! But which systems are you going to scan? No matter what tool you’re going to use for the job, you’re going to need to provide a list of hosts or ip addresses to scan. If you’re lucky enough, the local IT people from the involved company can provide you with such a list but most of the time it will be up to you to figure it out.
A quick solution uses Powershell but requires the ActiveDirectory module to be installed. This module is installed by default on domain controllers but not on other systems.
This powershell two liner will query AD and list all computers on the user’s Domain and write them to ‘uphosts.txt’

Import-Module ActiveDirectory
Get-ADComputer –Filter * | Select –Expand Name | Tee-Object uphostst.txt 

The filter parameter is mandatory so we pass a wildcard and we specify we only want the ‘Name’ property.
 If you don’t want or can’t use AD I’ll show you another approach using Nmap and some old school scripting.
Nmap  is probably the number one tool for network mapping and there’s a Windows version too! I’ll show you how to use this wonderful tool in a windows batch script to generate your host list.
"C:\Program Files (x86)\Nmap\nmap.exe" -oG _upHosts -sP -n 192.100.1.101-200
for /F "tokens=1-5" %%A IN ('type _upHosts ^| find "Status: Up"') DO echo %%B >> upHosts.txt
del _upHosts

The first line is just a call to the nmap executable. If it’s in a different location on your computer change the path!
 –oG _uphosts : lets you output the result to the temporary file _upHosts in a “grepable format”
-sP:  specifies a ping scan. A ping scan includes, icmp echo request, TCP SYN to port 443 and TCP ACK to port 80 and an icmp timestamp request. Just make sure that at least one of them is going to make it. For example, on some networks ICMP is not allowed. You can always change the type of scan and the port you are using (see Nmap help)
-n : Don’t do reverse DNS resolution. We don’t need hostnames at this stage, it would just waste our time
192.168.2.1-255: the ip range we’re scanning. Here we are scanning from 192.168.2.1 to 192.168.2.255. See Nmap help for the syntax to scan multiple subnets

The second line is some command line shizzle:
for /F "tokens=1-2" %%A IN ('type _upHosts ^| find "Status: Up"') DO echo %%B >> upHosts.txt
Basically were filtering the _upHosts file for all the lines that contain “Status: Up” in the part that’s between brackets.
The for loop will split up those lines (using a space for delimiter) and will send the ip address to the uphosts.txt file
del _upHosts
This will delete the temporary file

Just know that running this script may trigger some AV / firewall or IDS saying that ports are being scanned.
Notice that our list doesn’t discriminate between operating systems, or even the type of device (computers, printers, hubs…). Any device that has an ip and responds to the Nmap scan will be in the list. You can modify the Nmap query to do some additional filtering but the next steps will filter out the unwanted hosts anyway.

THE REMOTING PART
We will be using Powershell for the remote stuff. This of course limits the usability of the scripts to environments who can actually run Powershell (duh) and allow it. I’m just saying because if you encounter old XP boxes or even Windows 2000 systems (believe me they still exist) you can forget about it. You need at least Powershell v.2 and you need to make sure that port 5985, 5986 are not blocked by windows firewall etc.
One of the main advantages of using Powershell is that it has the ability to send commands in parallel to remote computers and the processing is done on the remote systems and then sent back. This means that the size of the network you’re dealing with does not really affect the total execution time. Powershell and WMI are also safe regarding protecting your credentials over the network (just watch out using CredSSP with powershell !).
All the methods and scripts require administrative privileges on the remote hosts so you would want to run these commands from a SAFE (non-compromised) machine with for example a domain admin account.
The only downside is that Powershell remoting is not enabled by default (except for systems >= Windows Server 2012). But since Powershell remoting will be at the core of Windows Nano Server management things will be changing soon I guess…
So worst case you have to enable it for all the systems and preferably over the network in an automated way but you probably figured out that we can’t use Powershell remoting (chicken and the egg thing).
You can use Group Policies, Sysinternals PsExec or WMI to get the job done.
If you’re using older versions of PsExec( < v2.1) then watch out !  I advise you to take a look at this article concerning protecting your domain accounts. 
Using PsExec:
Psexec @hostlist.txt powershell enable-psremoting -force
Or using WMI:
wmic /node:@hostlist.txt process call create "powershell enable-psremoting -force"
These will run the powershell command to automatically enable-psremoting on the remote host. The –force parameter takes care of the message boxes that would pop up asking for your consent. Just know that the command “disable-psremoting” will not undo all the steps taken by its counterpart, you have to undo some of the steps manually.
It’s quite possible that for some machines you will receive error messages, which means that they’re not powershell compliant.  Remember that I said we would eventually filter out some devices from our Nmap scan?  You can run the following Powershell script once to test which machines of your host-list are reachable using Powershell remoting and write the results to a file “Ps-Remoting-Enabled-Hosts.txt”.


#CheckPsRemoting.ps1
#© Tom Asselman
#

[CmdletBinding()]
#One mandatory parameter, the hostfile we’re going to test
#Each line has one hostname or ip address 
#Warning no error checking , we assume the file to be correct
Param(
   [ Parameter(Mandatory=$True,Position=1)]
   [string]$hostFile
)

# IF our output file exists we remove it
$outputFile = "Ps-Remoting-Enabled-Hosts.txt"
If (Test-Path $outputFile){
    Remove-Item $outputFile
}

Write-Host "Checking for Ps-Remoting enabled hosts : "
# For all the entries in hostlist .
get-content $hostFile | ForEach-Object{
    $machine = $_
    try{
        Write-Host $machine
        #Remote execution on $machine , -ErrorAction Stop used for neatly handling errors in try catch block
        Invoke-Command -ComputerName $machine -ScriptBlock{Write-Host " - PS Remoting Enabled" } -ErrorAction Stop
        Add-Content $outputFile $machine
    }
    catch{
        Write-Host "PS Remoting Failed " 
    }
}
Write-Host "Test Finished." 
The essence of the script is the Invoke-Command rule. The content of ScriptBlock will be executed on the remote machine that’s passed with the –ComputerName parameter.
Here, we try to write a text message on the remote machine. If this succeeds, the hostname is added to the output file.
Note that we’re deliberately handling the list of hosts in a for loop so we can add the hostname to our output file on success of invoke-command.  In the next script I’ll show you how to pass the host list directly to invoke-command.
The try-catch block is added to neatly handle execution when the invoke-command fails.
So this gives us our host list that we’ll feed to the IOC scanning script. Just make a quick comparison between your original host list and the “Ps-Remoting-Enabled-Hosts.txt” list. If there’s a big difference between the two something might have gone wrong.

IOC SCANNING SCRIPT
Now we know how to execute remote commands we just need a script that will perform the IOC checking on each host.
For this blog post were going to keep it simple and limit our IOC scanning to checking for the existence of files and registry keys that we will be passing to the script in the form of text files.
As I mentioned before I’m not going to give a full line by line breakdown. This will just show you how I approached the problem (I am not saying this is the best approach). The code however contains enough documentation to get an idea of what is happening but if you do have questions: contact me and ask away!
Both the file scan and registry scan accept wildcards, read the documentation for details.
Before you check out the scripts there's one important last thing I should mention about the registry scanning:  when you specify a key in HKLM (HIVE_KEY_LOCAL_MACHINE) the script will scan the user hive of the user that it’s running under on the remote machine.
If you add the following line to your registry IOC file (this syntax is needed for the HKU hive, it differs from the other hives):
Registry::\HKEY_USERS\*
You will see that only the currently logged on users + local system accounts appear in the output
My next post will show how to scan all the user hives for keys but for now it’s enough I guess …

You run the script as follows :

.\PS_IOC.ps1 -hostFile .\hosts.txt -fileIOC_File .\fileioc.txt -regIOC_File .\regiocs.txt


PS_IOC.ps1 script (main script)

<#
PS_IOC.ps1
© Tom Asselman
This script is free to use but please do not reuse or spread without giving credit to the author.

Arguments : 
    $hostFile     =>  File with each line containing one hostname or ip adress.
    $fileIOC_File =>  File with each line containing file or directory to search for , wildcards are accepted.
                      
                      Example:

                      c:\temp\EvilFile.exe
                      c:\temp
                      c:\Users\*\ntuser.dat


    $regIOC_File  =>  File with each line containing registry key (wildcards are accepted) and optionally a value (no wildcards allowed here) to search for in that key.
                      Keys and values need to be seperated by a tab.
                      You need to give the powershell notation for the registry keys 

                      Example:
                      HKLM:SOFTWARE\7-Zip   Path
                       
                       --> checks whether value Path exists for a key HKLM:SOFTWARE\7-Zip on the system

                      HKLM:SOFTWARE\*\IBM
                       
                       --> example of wildcard use , NO value specified

                      HKCU:Software\Microsoft\Windows\CurrentVersion\Run\   EvilPersistenceValue

                       --> Checks if EvilPersistenceValue appears in Software\Microsoft\Windows\CurrentVersion\Run\ 
                           in the user hive of the user executing the script !! 
                           Next blog post will include version that scans for all users on the machine
#>

[CmdletBinding()]
Param(
  [ Parameter(Mandatory=$False,Position=1)]
   [string]$fileIOC_File,
  
  [ Parameter(Mandatory=$False,Position=2)]
   [string]$regIOC_File,

   [ Parameter(Mandatory=$False,Position=3)]
   [string]$hostFile

 )


####################Main##############

#load iocfunctions
. .\IocFunctions.ps1

if( -not $fileIOC_File -and -not $regIOC_File){
    "Warning: No IOCs were passed to script. Exiting."
    Exit
}

#Check if outputfile exists, if so delete
$outputFile = "ioc-scan-results.txt"
If (Test-Path $outputFile){
    Remove-Item $outputFile
}

# List all the IOCs were going to check
$fileIocArray = @()
if( $fileIOC_File){
    $fileIocArray = Get-Content $fileIOC_File
    "Scanning for File IOCs: " | Tee-Object $outputFile -Append
        foreach ($i in $fileIocArray){
              $i | Tee-Object $outputFile -Append
        }
}

$RegIocArray = @()
if( $regIOC_File){
    $regIocArray = Get-Content $regIOC_File
    "Scanning for Registry IOCs: " | Tee-Object $outputFile -Append
        foreach ($i in $regIocArray){
              $i | Tee-Object $outputFile -Append
        }
}

#no hostfile ? -> check only local system
if (-not $hostFile){
    write-host "NO HOSTFILE, checking on localhost only."
    $hosts = @('localhost')
 }
 else{
    $hosts = (get-content $hostFile)
 }


#Building the remote session
$psSessions = New-PSSession -ComputerName $hosts
#add the functions to our session
Invoke-Command  -Session $psSessions -FilePath .\IocFunctions.ps1
#remote invocation of checkFileIocs as a Job
$Job = Invoke-Command  -Session $psSessions -ScriptBlock ${function:checkFileIOCs} -ArgumentList (,$fileIocArray) -AsJob 
Wait-Job $Job
#remote invocation of checkRegIocs as a Job
$Job = Invoke-Command  -Session $psSessions -ScriptBlock ${function:checkRegIOCs} -ArgumentList (,$RegIocArray) -AsJob 
Wait-Job $Job

#After waiting for execution we collect output from the jobs
foreach($job in Get-Job){
    Receive-Job -Job $job | Tee-Object $outputFile -Append
}
#Close the session (important)
Remove-PSSession $psSessions
 

<#
 © Tom Asselman
 This script is free to use but please do not reuse or spread without giving credit to the author.
#>



<#
Checks file system for existence of files / directories .
Hidden file's will be included

$File_IOC_List =>  Array with each entry containing file or directory to search for , wildcards are accepted.
                      
                      Example of entries:

                      c:\temp\EvilFile.exe
                      c:\temp
                      c:\Users\*\ntuser.dat
#>

function checkFileIOCs([string[]] $File_IOC_List ){

    "`r`n<<<<<<<<<Checking filesystem " +  $env:COMPUTERNAME + " user : " + $env:USERDOMAIN +'\' + $env:USERNAME + ">>>>>>>>>"

    foreach ($ioc in $File_IOC_List) { 
       try{
             "`r`n*********SCANNING " + $env:COMPUTERNAME + " FOR " + $ioc.ToUpper() + "    ***********"
             Get-ChildItem $ioc -force  -ErrorAction Stop
             "`r`nHIT FOUND !`r`n" 
        }
        catch{
            #do nothing , just to hide errors from the screen
        }
    }
}


<#
 Reg_IOC_List =>      Array with each entry containing registry key (wildcards are accepted in the keyname) and optionally a value (no wildcards allowed here) to search for in that key.
                      
                      Keys and values need to be seperated by a TAB character.
                      You need to give the powershell notation for the registry keys 

                      Example:
                      HKLM:SOFTWARE\7-Zip   Path
                       
                       --> checks whether value Path exists for a key HKLM:SOFTWARE\7-Zip on the system

                      HKLM:SOFTWARE\*\IBM
                       
                       --> example of wildcard use , NO value specified

                      HKCU:Software\Microsoft\Windows\CurrentVersion\Run\   EvilPersistenceValue

                       --> Checks if EvilPersistenceValue appears in Software\Microsoft\Windows\CurrentVersion\Run\ 
                           in the user hive of the user executing the script !! 
                           Next blog post will include version that scans users registry of ALL the users on the remote machine.
#>
function checkRegIOCs([string[]] $Reg_IOC_List ){

    "`r`n<<<<<<<<<Checking registry " +  $env:COMPUTERNAME + " as user : " + $env:USERDOMAIN +'\' + $env:USERNAME + ">>>>>>>>>"

     foreach ($ioc in $Reg_IOC_List) { 
       try{
             #split Using tab as delimmiter
             $regKey ,$regValue= $ioc.split("`t",2)
             
             #check if key ends on "\" else add it , otherwise lookups may not be correct
             if (-not $regKey.EndsWith("\")){
                 $regKey+= '\'
             }

             "`r`n*********SCANNING " + $env:COMPUTERNAME + " FOR -  Reg Key: " + $regKey.ToUpper() + "  - Value : "+ $regValue +" **********"
             if($regValue -eq $null){
                #If no Reg value specified (only a key)
                ($foundValue = Get-ChildItem $regKey -force  -ErrorAction Stop)
                if ( $foundValue -eq $null) {
                    # If it's a key with no more subkeys we need to use Get-ItemProperty to list any values
                    ($foundValue = Get-ItemProperty -ErrorAction stop $regKey)
                    #no values found ?
                    if ( $foundValue -eq $null) {
                        Write-Error("No Hit") -ErrorAction stop
                    }
                } 
                
             }
             else{
                 #Reg Key + value specified
                 ($foundValue = Get-ItemProperty -ErrorAction stop $regKey   | Select-Object -ExpandProperty $regValue -ErrorAction stop)
                 if ( $foundValue -eq $null) {Write-Error("No Hit") -ErrorAction stop}
             }
             "`r`nHIT FOUND !" 
        }
        catch{
            #do nothing , just to hide errors from the screen
            
        }
    }
}

No comments:

Post a Comment