SEC504: Hacker Tools, Techniques, and Incident Handling

Experience SANS training through course previews.
Learn MoreLet us help.
Contact usConnect, learn, and share with other cybersecurity professionals
Engage, challenge, and network with fellow CISOs in this exclusive community of security leaders
Become a member for instant access to our free resources.
Sign UpMission-focused cybersecurity training for government, defense, and education
Explore industry-specific programming and customized training solutions
Sponsor a SANS event or research paper
We're here to help.
Contact UsFrom the things I wish someone told me when I started with PowerShell department: using the PowerShell grouping operator!
When I first started working with PowerShell, commands like this were confusing:
PS C:\temp> (Get-Date -Date ((Get-Date).ToUniversalTime()) -UFormat %s)
1657915725.84934
PowerShell uses parentheses for a few different functions, well-documented online. In this article we'll look at the grouping operator [code]()[/code].
Many languages use parentheses to specify operator precedence in expressions:
PS C:\temp> (3 + 5) * 2
16
When PowerShell sees something inside [code]()[/code], it evaluates that first, then it evaluates the remainder of the expression. This is pretty intuitive, since we use it for lots of math equations too. The grouping operator works the same way: statements inside the parentheses are executed first (within the current statement in the pipeline).
Let's look at a practical example.
I have two image files with similar file names. File size and date/time are a match too, but I want to know if the files are exactly the same. This is a job for [code]Get-FileHash[/code]:
PS C:\temp> Get-ChildItem
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 7/15/2022 8:01 PM 453684 IMG_3306 copy.jpg
-a---- 7/15/2022 8:01 PM 453684 IMG_3306.jpg
PS C:\temp> Get-FileHash .\IMG_3306.jpg
Algorithm Hash Path
--------- ---- ----
SHA256 41F6DDF12CA703FCB428ED8FF702130613A905DE297FB5EBB577A06CC394C440 C:...
PS C:\temp> Get-FileHash '.\IMG_3306 copy.jpg'
Algorithm Hash Path
--------- ---- ----
SHA256 9A239588772945419F4D6BBC3DDBF870A60A30C028269B6FF5D20EF4E3A8D357 C:...
We see that the different SHA256 hashes indicates that the files are not the same, but what if we wanted to test this condition in a script? We could do something like this:
PS C:\temp> $hash1 = Get-FileHash .\IMG_3306.jpg
PS C:\temp> $hash2 = Get-FileHash '.\IMG_3306 copy.jpg'
PS C:\temp> $hash1.Hash -EQ $hash2.Hash
False
By declaring the [code]Get-FileHash[/code] output as variables, we can access the [code]Hash[/code] property for each file and test if they are equal using the PowerShell [code]-Eq[/code] operator. The hash values are different, so we get the [code]False[/code] result.
This approach is fine, and in a script it's perfectly legible, but it declared two variables, [code]$hash1[/code] and [code]$hash2[/code]. This is not necessary, since we can use the grouping operator instead:
PS C:\temp> (Get-FileHash IMG_3306).Hash -EQ (Get-FileHash '.\IMG_3306 copy.jpg').Hash
False
In this example we surround the [code]Get-FileHash[/code] steps in parentheses, and access the [code]Hash[/code] property using dot notation. PowerShell processes the expressions from left to right, executing the statements within the parentheses first before accessing the [code]Hash[/code] property and comparing the two values with [code]-EQ[/code].
Once you get the hang of using the grouping operator, you start to realize you don't need to declare variables to access the properties or methods of a PowerShell command. For example, all PowerShell arrays have a property called [code]Count[/code] which is the number of elements in the array. You can access this value with any command that returns an array type:
PS C:\temp> Get-ChildItem
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 7/15/2022 8:01 PM 453684 IMG_3306 copy.jpg
-a---- 7/15/2022 8:01 PM 453684 IMG_3306.jpg
PS C:\temp> (Get-ChildItem).Count
2
In my part 4 article on working with the event log I used an example where I counted the number of event logs available on Windows:
PS C:\temp> (Get-WinEvent -ListLog *).Count
442
Using the grouping operator we can execute the specified command ([code]Get-WinEvent -Listlog *[/code]), then access a member property using dot notation. Neat!
When we work with dates, we use a special object type for date and time data. In PowerShell, it's common to identify the object type using the grouping operator and the [code]GetType()[/code] method:
PS C:\Temp> Get-Date
Saturday, July 16, 2022 10:42:03 AM
PS C:\Temp> (Get-Date).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True DateTime System.ValueType
Here we see the type is [code]DateType[/code]. We can use [code]Get-Member[/code] to identify the properties and methods available for a [code]DateType[/code] object:
PS C:\Temp> Get-Date | Get-Member
TypeName: System.DateTime
Name MemberType Definition
---- ---------- ----------
Add Method datetime Add(timespan value)
AddDays Method datetime AddDays(double value)
AddHours Method datetime AddHours(double value)
AddMilliseconds Method datetime AddMilliseconds(double value)
AddMinutes Method datetime AddMinutes(double value)
AddMonths Method datetime AddMonths(int months)
AddSeconds Method datetime AddSeconds(double value)
AddTicks Method datetime AddTicks(long value)
AddYears Method datetime AddYears(int value)
CompareTo Method int CompareTo(System.Object value), int CompareTo(dat...
Equals Method bool Equals(System.Object value), bool Equals(datetim...
GetDateTimeFormats Method string[] GetDateTimeFormats(), string[] GetDateTimeFo...
GetHashCode Method int GetHashCode()
...
Let's say you want to get a date/time for 2 days from now. You can use the grouping operator to invoke [code]Get-Date[/code], then call the [code]AddHours()[/code] method:
PS C:\temp> (Get-Date).AddHours(48)
Monday, July 18, 2022 10:47:19 AM
Remember, statements inside the parentheses are executed first. Here, [code]Get-Date[/code] is executed first, which returns a [code]DateType[/code] object. The [code]48[/code] inside of the next set of parentheses is also evaluated, but since it's a literal and not an expression nothing is executed. Then the result of calling [code]Get-Date[/code] (the [code]DateType[/code] object) is used to invoke the [code]AddHours()[/code] method.
You could have accomplished this same task (getting the date/time 2 days from now) using variables instead:
PS C:\temp> $now = Get-Date
PS C:\temp> $twodaysfromnow = $now.AddHours(48)
PS C:\temp> $twodaysfromnow
Monday, July 18, 2022 10:51:48 AM
This is is also fine. PowerShell purists may point out that this solution declares an unnecessary variable, but it is a perfectly acceptable way of solving the problem at hand.
In my article on threat hunting with PowerShell differential analysis I showed how you can build a baseline of configuration data for a system in a known-good state, then compare the current state of a system under investigation to identify a threat actor. In my SEC504 class we go a bit further in our analysis of how a Command & Control (C2) framework looks when it migrates into an existing process to hide its presence on the system, but we apply the same differential analysis process.
Let's take a look. First, on Windows I'll get a list of the DLLs associated with the Windows Explorer process, saving the output to [code]explorer-modules-baseline.txt[/code]:
PS C:\Temp> Get-Process -name explorer -Module | Select-Object ModuleName | Out-File explorer-modules-baseline.txt
PS C:\Temp> Get-Content .\explorer-modules-baseline.txt -First 10
ModuleName
----------
Explorer.EXE
ntdll.dll
KERNEL32.DLL
KERNELBASE.dll
msvcp_win.dll
ucrtbase.dll
combase.dll
This file has a list of all the DLLs normally associated with the Explorer process. Next, as an attacker I'll use Metasploit Meterpreter as my C2 to migrate to the Explorer process, leaving the initial exploit process and blending in to an otherwise legitimate process:
meterpreter > migrate -N explorer.exe
[*] Migrating from 8268 to 4332...
[*] Migration completed successfully.
Now, I want to use differential analysis to identify if the normal DLL list associated with the Explorer process has changed using [code]Compare-Object[/code]. I'll create a new file called [code]explorer-modules-current.txt[/code] with the current DLL information:
PS C:\Temp> Get-Process -Name explorer -Module | Select-Object Modulename | Out-File explorer-modules-current.txt
PS C:\Temp>
To use [code]Compare-Object[/code], I need to supply [code]-ReferenceObject[/code] and [code]-DifferenceObject[/code] arguments. [code]Compare-Object[/code] won't read the files directly, but I can use the grouping operator with [code]Get-Content[/code] to read the files and return the object data:
PS C:\Temp> Compare-Object -ReferenceObject (Get-Content .\explorer-modules-baseline.txt) -DifferenceObject (Get-Content .\explorer-modules-current.txt)
InputObject SideIndicator
----------- -------------
PSAPI.DLL =>
cdprt.dll =>
Windows.Globalization.dll =>
execmodelclient.dll =>
execmodelproxy.dll =>
These 5 DLLs are added by Metasploit Meterpreter during the [code]migrate[/code] operation, a valuable Indicator of Compromise (IoC).
In this article we looked at how the PowerShell grouping operator works. We can use the grouping operator instead of declaring a temporary variable to access an object's properties and methods (e.g., [code](Get-Date).AddHours(12)[/code]), and as a way to eliminate temporary variables (e.g., [code](Get-FileHash IMG_3306).Hash -EQ (Get-FileHash '.\IMG_3306 copy.jpg').Hash[/code]).
Remember: statements inside the parentheses are executed first (within the current statement in the pipeline). Next time you see a confusing PowerShell statement with lots of parentheses, just look to the inner-most parentheses first and work your way out to understand the order of execution. Once you grasp that process, complex PowerShell statements become a lot easier to break down.
-Joshua Wright
Return to Getting Started With PowerShell
Joshua Wright is the author of SANS SEC504: Hacker Tools, Techniques, and Incident Handling, a faculty fellow for the SANS Institute, and a senior technical director at Counter Hack.
As Senior Technical Director at Counter Hack and SANS Faculty Fellow, Joshua has advanced cybersecurity through ethical penetration testing, uncovering critical vulnerabilities across Fortune 500 companies and national infrastructure providers.
Read more about Joshua Wright