The Power of the Shell
The Power of the Shell

The Power of the Shell

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:

  1. Check the current value of the property in the registry Get-ItemPropertyValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client\" -Name "AllowBasic"
  2. Set it to 1 Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client\" -Name "AllowBasic" -Value 1
  3. 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:

  1. Open an administrative PowerShell command prompt
  2. Run Install-Module -Name ExchangeOnlineManagement. It might ask you to update your NuGet PowerShell module to get newer packages.
  3. Import the module into your session Import-Module ExchangeOnlineManagement
  4. 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

Create powershell session failed using OAuth
Create powershell session failed using OAuth

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.

General PowerShell References