- PowerShell Script Fragments
- Crypto
- Encrypt with AES & Runtime.InteropServices.Marshal (Secure String)
- Zipping/Compressing and Unzipping/Decompressing
- Object Conversions
- Convert Module
- .NET Objects Introspection and Metadata
- Obtain the values of an enum
- Active Directory Ops
- Test Credentials in the Domain
- Translate Domain User Name to SID with Powershell
- Translate Domain User Name to SID using WMI
- Translate SID to Domain User Name
- Local User to SID
- Other methods
- List the current user’s security groups and resolve SIDs to NT names
- Test whether the current user is running as admin
- Search AD Users created between dates
- Find Users or Groups by their Identity | No AD Module required
- Find a Group
- Find a User
- Local System Info
- Check whether a user is a member of a local group
- Running Scripts: Some Techniques
- Run Scripts remotely leveraging a simple internal HTTP server
- Microsoft Exchange and O365 PowerShell Modules
- Installing Old Exchange Online Module
- Installing New Exchange Online PowerShell V2 (EXO V2)
- Troubleshooting Exchange Online Authentication Issues
- Exchange PowerShell References
- Start-HistoricalSearch
- Useful PowerShell Modules
- Plaster: PowerShell Module Generator
- PSReflect
- Generic PowerShell Lore
- Working with PowerShell Classes
- Microsoft Authentication Library (MSAL) for .NET
- General PowerShell References
PowerShell Script Fragments
This is a collection of fragmentary lore about PowerShell tricks
Crypto
Encrypt with AES & Runtime.InteropServices.Marshal (Secure String)
$MyScriptBlock = { "Write-Host ""Awesome, you decrypted the message :)""" }
$SecureString = ConvertTo-SecureString [String]
$MyScriptBlock -AsPlainText -Force
$SecureStringKey = @(185,133,55,194,97,127,251,94,145,18,160,62,87,37,188,22,223,58,93,26,170,129,228,193,236,98,16,128,225,239,41,174)
# If an encryption key is specified by using the Key or SecureKey parameters, the Advanced Encryption Standard (AES) encryption algorithm is used
$AESStringText = $SecureString | ConvertFrom-SecureString -Key $SecureStringKey
# Now let's decrypt the text with the provided key
([Runtime.InteropServices.Marshal]::PtrToStringUni( [Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode( $( $AESStringText | ConvertTo-SecureString -Key $SecureStringKey) )))
Another way to encrypt and decrypt without specifying an AES Key
$SecureString = ConvertTo-SecureString "I am a Secret Message" -AsPlainText -Force
# Obtain pointer to memory region where string is stored
$Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($SecureString)
# Dereference pointer and convert to unicode string
$ClearText = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr)
# Free up unmanaged string pointer
[System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr)
Zipping/Compressing and Unzipping/Decompressing
# Compress a string
### Load string as bytes in a memory stream
$string = 'This is a test string man'
$UncompressedMemStream = [System.IO.MemoryStream]::new()
$StandardWriter = [System.IO.StreamWriter]::new($UncompressedMemStream)
$UncompressedMemStream.Position = 0
$StandardWriter.Write($string)
$StandardWriter.Flush()
$StandardWriter.Close()
# NOTE: If a file is to be compressed instead of a string, just read the file's bytes:
# $FileBytes = [System.IO.File]::ReadAllBytes('C:\somewhere\file.dll')
### COMPRESS
[System.IO.MemoryStream]$CompressedMemStream = [System.IO.MemoryStream]::new()
$GZipCompressionStream = [System.IO.Compression.GZipStream]::new($CompressedMemStream, ([System.IO.Compression.CompressionMode]::Compress))
$GZipCompressionStream.Write($UncompressedMemStream.ToArray(), 0, $UncompressedMemStream.ToArray().Length)
$GZipCompressionStream.Close()
$CompressedMemStream.Close()
Write-Output "Compressed Stream:", $CompressedMemStream.ToArray()
### DECOMPRESS
$InputMemStream = New-Object System.IO.MemoryStream( , $CompressedMemStream.ToArray() )
$DecompressedMemStream = [System.IO.MemoryStream]::new()
$GZipDecompressionStream = [System.IO.Compression.GZipStream]::new($InputMemStream, [System.IO.Compression.CompressionMode]::Decompress)
$GZipDecompressionStream.CopyTo($DecompressedMemStream)
Write-Output "Decompressed Stream (in ASCII):", $([System.Text.Encoding]::ASCII.GetString($DecompressedMemStream.ToArray()))
$GZipDecompressionStream.Close()
$CompressedMemStream.Close()
$InputMemStream.Close()
Object Conversions
Convert Module
Great repository for PowerShell module convert
: https://github.com/austoonz/Convert/tree/master/src/Convert/Public
Convert is a PowerShell Module that simplifies object conversions by exposing common requirements as standard PowerShell Functions. For example, this module includes functions for converting to and from Base64 encoded strings, MemoryStream objects, or Clixml output.
.NET Objects Introspection and Metadata
Obtain the values of an enum
# [System.Enum]::GetNames("enum")
# Example:
[System.Enum]::GetNames([System.Security.Principal.WindowsBuiltInRole])
Active Directory Ops
More fragmentary knowledge for the cyberhunter.
Test Credentials in the Domain
NOTE: be careful when using this since your account, or the account you are trying to test credentials for, might get locked out depending on your domain policy.
Function Test-ADCredential {
Param($UserName, $Password)
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain
$PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new($ContextType, $UserDomain)
$PrincipalContext.ValidateCredentials($UserName,$Password)
}
Translate Domain User Name to SID with Powershell
If you want to obtain a user’s SID from it’s domain SamAccountName do this:
$objUser = New-Object System.Security.Principal.NTAccount("DOMAIN_NAME", "USER_NAME")
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
$strSID.Value
Translate Domain User Name to SID using WMI
wmic useraccount where (name='UserName' and domain=′yourdomain′) get sid
Translate SID to Domain User Name
$objSID = New-Object System.Security.Principal.SecurityIdentifier("ENTER-SID-HERE")
$objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
$objUser.Value
Local User to SID
$objUser = New-Object System.Security.Principal.NTAccount("LOCAL_USER_NAME")
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
$strSID.Value
Other methods
List the current user’s security groups and resolve SIDs to NT names
([Security.Principal.WindowsIdentity]::GetCurrent()).Groups.Value | % { ([System.Security.Principal.SecurityIdentifier]::new($_)).Translate([System.Security.Principal.NTAccount]) }
Test whether the current user is running as admin
$principal = New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())$principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
Alternatively, the following lines can be added to the top of your PowerShell script
#requires -version 4.0
#requires –runasadministrator
Another method is to use plain cmd whoami /groups
. It can be further filtered like: whoami /groups | Where-Object {$_ -MATCH 'BUILTIN\\Administrators'}
Search AD Users created between dates
Function Get-ADAccountsBetweenDates {
<#
.SYNOPSIS
Function to find Active Directory accounts created within certain date ranges
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[string]
$FromDate,
[Parameter(Mandatory=$True)]
[string]
$ToDate,
[Parameter(Mandatory=$False)]
[ValidateSet('whenCreated', 'whenChanged')]
$DateRangeType='whenCreated',
[Parameter(Mandatory=$False)]
[bool]
$OutputGridView = $True
)
# We need to convert the string to a datetime object
$from = [datetime]::parseexact($FromDate, 'dd/MM/yyyy', $null)
$to = [datetime]::parseexact($ToDate, 'dd/MM/yyyy', $null)
$ADProperties = @('Name', 'UserPrincipalName', 'SamAccountName', 'Description', 'whenCreated', 'whenChanged', 'Title', 'LastLogonDate', 'badPwdCount')
$Results = Get-ADUser -Filter {($DateRangeType -gt $from) -and ($DateRangeType -lt $to)} -Properties $ADProperties | Select-Object $ADProperties
if ($OutputGridView -eq $True) {
$Results | Out-GridView
}
else {
return $Results
}
}
Find Users or Groups by their Identity | No AD Module required
Find a Group
It can be a local or Domain group, depending on the [System.DirectoryServices.AccountManagement.ContextType]
value
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$GroupPrincipalType = ([System.DirectoryServices.AccountManagement.GroupPrincipal]) -as [type]
$GroupPrincipal = $GroupPrincipalType::FindByIdentity([System.DirectoryServices.AccountManagement.ContextType]::Machine,[System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName,"BUILTIN\Administrators")
Find a User
$UserPrincipalType = ([System.DirectoryServices.AccountManagement.UserPrincipal]) -as [type]
$UserPrincipal = $UserPrincipalType::FindByIdentity([System.DirectoryServices.AccountManagement.ContextType]::Domain,[System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName,"domain\rubberduckie")
Local System Info
Check whether a user is a member of a local group
Example, test whether a user is a member of the “BUILTIN\Administrators” group.
$UserName = "domain\rubberduckie"
foreach($User in ((Get-LocalGroupMember -Group "Administrators").Name)){
if($User -eq $UserName){
$IsLocalAdmin = $True
Write-Host "[+] User $User already member of BUILTIN\Administrators. Skipping..." -ForeGroundColor Green
break
}
else {
$IsLocalAdmin = $False
}
}
Running Scripts: Some Techniques
Run Scripts remotely leveraging a simple internal HTTP server
### STEP 1: Initiate Simple HTTP Server to serve files internally
$RemoteScriptToRun = @'
$System = 'SYSTEMNAME01'
$SessOption = New-PSSessionOption
$SessOption.NoMachineProfile = $True # Let's not create a user profile, not required and leaves fewer traces of blue team actions
[scriptblock]$RemoteScript = {
$B64Content = (New-Object System.Net.WebClient).DownloadString('http://REMOTESERVERIP:80/MyScript.ps1') ; Invoke-Expression ([System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($B64Content)))
}
Invoke-Command -ScriptBlock $RemoteScript -ComputerName $System -SessionOption $SessOption -Credential $Creds -ErrorAction SilentlyContinue
'@
$MyLocalIP = (Test-Connection -ComputerName ($env:COMPUTERNAME) -Count 1).IPV4Address.IPAddressToString
Write-Host "`n`nYour IP is $MyLocalIP. Make sure you connect to this IP from the remote host. Also make sure that the Firewall allows traffic to Port 80, otherwise select a different port"
# Let's now produce the script that should be run to connect back to this server and get a file
$RemoteScriptToRun = $RemoteScriptToRun.Replace('REMOTESERVERIP', $MyLocalIP)
Write-Host "`n`nTo connect to this server and fetch the file, use this script: `n`n$RemoteScriptToRun`n`n" -ForegroundColor Cyan
$Hso=New-Object Net.HttpListener;$Hso.Prefixes.Add("http://+:80/");$Hso.Start();While ($Hso.IsListening){$HC=$Hso.GetContext();$HRes=$HC.Response;$HRes.Headers.Add("Content-Type","text/plain");$Buf=[Text.Encoding]::UTF8.GetBytes((Get-Content (Join-Path $Pwd ($HC.Request).RawUrl)));$HRes.ContentLength64=$Buf.Length;$HRes.OutputStream.Write($Buf,0,$Buf.Length);$HRes.Close()};$Hso.Stop()
### STEP 2: Convert your script to Base64 and write the resulting string to a file ###
# This is an intermediary step since the HTTP socket above relies on Get-Content to encode ASCII bytes and does not return a well formed string when invoked remotely. It can also aid in making the script easier to transfer by other means.
[Convert]::ToBase64String([System.IO.File]::ReadAllBytes('C:\Path\To\MyScript.ps1'))
### STEP 3: Read and Execute the script on the target machine ###
# If running the script from an intermediate jump host, use WinRM so as to connect to the remote host in a secure way without leaving credential material on the target machine.
# This script should have been produced in STEP 1 too, placed here as a reference
$System = 'SYSTEMNAME01'
$SessOption = New-PSSessionOption
$SessOption.NoMachineProfile = $True # Let's not create a user profile, not required and leaves fewer traces of blue team actions
[scriptblock]$RemoteScript = {
$B64Content = (New-Object System.Net.WebClient).DownloadString('http://10.85.20.140:80/MyScript.ps1') ; Invoke-Expression ([System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($B64Content)))
}
Invoke-Command -ScriptBlock $RemoteScript -ComputerName $System -SessionOption $SessOption -Credential $Creds -ErrorAction SilentlyContinue
Microsoft Exchange and O365 PowerShell Modules
Installing Old Exchange Online Module
To install the online administration modules you need to download the Microsoft application that installs the module for you. Use your browser to go to https://cmdletpswmodule.blob.core.windows.net/exopsmodule/Microsoft.Online.CSE.PSModule.Client.application
NOTE: In order to initiate a connection using this module you will have to enable basic auth in WinRM. Usually this is disabled by Group Policy.
To allow you to connect using basic auth follow these steps:
- Check the current value of the property in the registry
Get-ItemPropertyValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client\" -Name "AllowBasic"
- Set it to 1
Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client\" -Name "AllowBasic" -Value 1
- You should now be able to initiate a connection with the older module and use its commandlets:
Connect-EXOPSSession
Installing New Exchange Online PowerShell V2 (EXO V2)
On September 21, Microsoft released V2.0.3 of the Exchange Online Management PowerShell module to general availability. This is an update of what’s sometimes called Exchange Online PowerShell V2.
The Exchange Online PowerShell V2 module (abbreviated as the EXO V2 module) uses modern authentication and works with multi-factor authentication (MFA) for connecting to all Exchange-related PowerShell environments in Microsoft 365: Exchange Online PowerShell, Security & Compliance PowerShell, and standalone Exchange Online Protection (EOP) PowerShell.
In order to install this module follow the next steps:
- Open an administrative PowerShell command prompt
- Run
Install-Module -Name ExchangeOnlineManagement
. It might ask you to update your NuGet PowerShell module to get newer packages. - Import the module into your session
Import-Module ExchangeOnlineManagement
- Connect to Exchange Online –>
Connect-ExchangeOnline
. You will be prompted for your credentials and 2FA.
Troubleshooting Exchange Online Authentication Issues
If you see error create powershell session failed using OAuth
then you may need to check WinRM allowing Basic authentication
Solution
# check whether Basic authentication is disabled
winrm get winrm/config/client/auth
# if Basic = false [Source="GPO"] then
winrm set winrm/config/service/auth '@{Basic="true"}'
If the setting is controlled by GPO you will get an error when attempting to modify via winrm
command. You will need to modify the registry directly, but bear in mind that this will get overriden by Group Policy on the machine’s next policy sync anyways. However, it might get you enough time to achieve what you wanted :)
Set-ItemProperty "HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client\" -Name AllowBasic -Value 1
Exchange PowerShell References
Start-HistoricalSearch
Start-HistoricalSearch BlogPost. This commandlet can provide interesting report types:
- ATPReport: Advanced Threat Protection File Types Report and Advanced Threat Protection Message Disposition Report
- ATPV2: Exchange Online Protection and Advanced Threat Protection E-mail Malware Report.
- ATPDocument: Advanced Threat Protection Content Malware Report for files in SharePoint, OneDrive and Microsoft Teams.
- Malware: Malware Detections Report.
- DLP: Data Loss Prevention report.
Useful PowerShell Modules
Plaster: PowerShell Module Generator
Plaster is a template-based file and project generator written in PowerShell. Its purpose is to streamline the creation of PowerShell module projects via scaffolding, Pester tests, DSC configurations, and more. File generation is performed using crafted templates which allow the user to fill in details and choose from options to get their desired output.
You can think of Plaster as Yeoman for the PowerShell community.
A good article describing Plaster use: https://www.pipehow.tech/new-plastermodule/
PSReflect
This module (https://github.com/mattifestation/PSReflect) by Matt Graeber, lets you define C types in-memory using PowerShell and .NET. It is simply ingenious in its simplicity and how it lowers the bar for people like myself, to play with Win32 APIs using PowerShell.
Generic PowerShell Lore
Working with PowerShell Classes
It’s not always straightforward how to work with PowerShell classes. Here are some good blogs about it:
Microsoft Authentication Library (MSAL) for .NET
The MSAL library for .NET is part of the Microsoft identity platform for developers (formerly named Azure AD) v2.0. It enables you to acquire security tokens to call protected APIs.
- Microsoft Graph using MSAL with PowerShell. Good blog post on how to setup MSAL with PowerShell
General PowerShell References
- Using .NET to create a PowerShell Menu that looks native: https://adamtheautomator.com/build-powershell-menu/#Using_NET_to_Create_a_Powershell_Menu
- Understanding PowerShell V5 classes: https://xainey.github.io/2016/powershell-classes-and-concepts/
- Loading PowerShell classes from modules