PowerShell for Incident Responders - The Basics
PowerShell for Incident Responders - How to automate your work
What are the things you do every day over and over again? These are things you should automate.
Sometimes the best automation is to write a script - often times the best automation is to learn how to use a scripting language like PowerShell to do it.
The hints below are designed for the world of Incident Response.
1) How to get help
If you are unsure of what a commands name is you can use get-command
PS > get-command *write*
will list all commands that contain 'write'
To find out what a particular command does and what parameters it excepts
PS > man <command>
or
PS > get-help <command>
or
PS > help <command>
man and help are aliases for get-help
if you run a command like
PS > ps (or get-process)
it will display a list of running processes but that is only a small part of it's capabilities
Run
PS > ps | gm (or get-member)
and it will display a long list of other information it can provide as well as some methods it can run against the data
After running ps, I can choose a process by either its name or ID
PS > ps | where name -eq 'Services'
or
PS > ps -id 1256
Once I have located the service I want I can stop the process with the kill command
PS > (ps -id 1256).kill()
The ps command is enclosed in parenthis to signify that the output of the command is what the kill() function is referencing
2) Working with select-string (grep) output
A common function when doing incident response is to use select-string (or sls) to search text files for information.
sls uses regular expressions (REGEX) to do its searches. REGEX is an obtuse but extremely powerfull language - it is worth all the time you spend learning it.
Everything in PowerShell is an object - when we ran the gm command earlier it listed all the properties and methods of the ps object
when you run a search with sls it also returns an object - understanding this object unlocks its power.
If you want to search one file the syntax is as follows:
PS > sls 'somestring' temp.txt
of course you can use the same syntax to search multiple files
PS > sls 'somestring' *.csv
The output of either of these commands will be in the format
Filename:LineNumber:Line
If you want to know what the parts of sls object is you can use:
PS > sls 'somestring' *.txt | gm
If you want to pick just parts of the standard output
PS > sls 'somestring' *.txt | select filename,line
or if just one part
PS > (sls 'somestring' *.txt).line
or to display all the data
PS> sls 'somestring' *.csv | select *
What if you only want to know the filenames that string resides in
PS > (sls 'somestring' *.txt).filename
oh but if the word appears multiple times in a file it will list the filename for each time - to make a list of only the unique filenames
PS > (sls 'somestring' *.txt).filename | select -unique
If you want to look at all the files in a directory and subdirectories
PS > dir * -recurse | sls 'somestring'
or only csv and txt files
PS > dir *.csv,*.txt -recurse | sls 'somestring'
or a particular file
PS > sls *system.csv -recurse | sls 'somestring'
Fancy PowerShell commands are built with small ones
PS > (dir *.csv,*.txt -recurse | sls 'somestring').path
path returns the full path
PS > (dir *.csv,*.txt -recurse | sls 'somestring').path | select -unique
or even better open each file
PS > (dir *.csv,*.txt -recurse | sls 'somestring').path | select -unique | %{& $_}
A little explanation for %{& $_}
% = foreach
$_ = current piece of data - in this case the path to the file
& = open the file with the associated program
If you wonder what kind of data .path is
PS > (dir *.csv,*.txt -recurse | sls 'somestring').path | gm
Using different combinations of these commands
Open all the *systems.csv files in all subdirectories
PS > dir *systems.csv -r | %{& $_}
If you only want a listing of the files
PS > dir *.systems.csv | select filename
3) Filtering output
Output from commands can be done in two ways depending on what you need
PowerShell mostly emits objects - frequently what is returned is an array of objects.
For instance if you use the get-content (gc) command to read in a text file what results is an array of string objects with each line an object and then entire file an array of lines.
If you run
PS > $s = gc test.txt
$s will contain an array with each element representing one line
You can address each line with $s[4] or whatever the line number is
This array starts with 0 so the first line is $s[0]
Note that the output of any command can be saved in a variable (here $s) you create a variable by just naming it.
The variable thus created takes on the type of the first object you stick in it or you can specifically type the variable with either
[int]$s or specifying an empty type like $s = @() which creates an array of objects
You do need to be careful if you create a variable and add data to it which types it. If you try to add data of a different type it my cause issues
Back to filtering output - the import-csv command can be used for a good example
Lets be specific and work with some output from Volcano Surge.
PS > $s = import-csv DC1~networkconnections.csv
If you display $s
PS > $s
it returns a series of records like
EntryID : 53
dwState : 3
dwLocalAddr : 127.0.0.1
dwLocalPort : 56545
dwRemoteAddr : 127.0.0.1
dwRemotePort : 9050
dwOwningPid : 19784
EntryID : 54
dwState : 5
dwLocalAddr : 192.168.88.193
dwLocalPort : 56546
dwRemoteAddr : 178.128.242.134
dwRemotePort : 3333
dwOwningPid : 3616
EntryID : 55
dwState : 3
dwLocalAddr : 127.0.0.1
dwLocalPort : 56547
dwRemoteAddr : 127.0.0.1
dwRemotePort : 9050
dwOwningPid : 8780
Here we have records with fields: EntryID, dwState, dwLocalAddr, dwLocalPort, dwRemoteAddr, dwRemotePort, dwOwningPid
if we want to retrieve all the dwRemoteAddr
PS > $s.dwRemoteAddr
will do it. You can save the output in a file with
PS > $s.dwRemoteAddr > ips.txt
or save it in a variable with
PS > $ip = $s.dwRemoteAddr
Once it is in a variable you can get only the unique entries with
PS > $ip | select -unique > ips.txt
or if you want to get fancy you can create a histogram with
PS > $ip | group | select count, name | sort count -descending
If you want to retrieve all the records containing a specific address you use the where command
PS > $s | where dwRemoteAddr -eq '169.198.0.13'
If you want to filter on more than one field
PS > $s | where {$_.dwLocalAddr -eq '169.198.0.13' -or $_.dwRemoteAddr -eq '169.198.0.13'}