PowerShell7 minute(s) de lecture

PowerShell est un langage très puissant, qu’il s’agisse de Red Team ou bien d’administration. PowerShell est un incontournable de la sécurité et même de l’univers Microsoft Windows.

Historique

PowerShell a été initialement publié en Novembre 2006, il était alors qu’à ses débuts, très peu de fonctionnalité pour cette première version mais qui a néanmoins posée les bases. Il fut inclus avec Windows XP, Windows Vista, ainsi que Windows Server 2003. Etrangement il était un composant optionnel pour Windows Server 2008. La version 2 de PowerShell est sortie en Octobre 2008 incluant plusieurs nouvelles options comme le “PSRemoting”, les modules, PowerShell ISE (qui est un IDE pour PowerShell), les fonctions avancées, des nouveaux opérateurs, les commentaires sur plusieurs lignes et bien d’autres. C’est à partir de cette version que PowerShell est inclus par défaut dans toutes les solutions Microsoft. PowerShell 3.0 (Septembre 2012) n’a pas apporté énormément de nouveautés (détection des cmdlets appartenant à des modules même quand ces derniers ne sont pas chargés, pour PowerShell ISE une meilleure autocomplétions grâce à “l’intellisense”), il est désormais installé par défaut sur Windows 8 ainsi que Windows Server Server 2012. Après la version 4 (2013), 5 (2016), Microsoft publiera pour la dernière fois PowerShell 5.1 (2017), avant d’en faire un système multiplateforme et “Open Source” avec la version 6.0. PowerShell en est aujourd’hui à la version 7.0.3 et disponible sur github ici.

Fonctionnement

Le cœur de PowerShell est basé sur la dll System.Management.Automation. Cette dernière définit la très grande majorité du fonctionnement des commandes. En PowerShell une commande est appelée cmdlet et est la contraction de commandlet (en français, applet de commande). Ces dernières sont entièrement programmées en C#, et tous les outils nécessaires pour en programmer sont situés dans la même dll. Elles possèdent une structure syntaxique en 2 parties (sans compter les éventuelles arguments), le verbe: qui détermine le type d’action à mener (usuellement ils sont classés par catégories: Security, Data, Common, Lifecycle, Communications, Diagnostic, Other), et le nom qui indique ce sur quoi l’action sera menée. Pour avoir une liste des verbes disponibles, une cmdlet y est toute destinée Get-Verb.

Par exemple prenons la cmdlet Get-Process, il facile de comprendre que le résultat de la commande sera une liste des processus qui sont actuellement en cours d’exécution. Bien que le système de “commandlet” peut s’avérer pratique pour les retenir, pour certaines, il existe des alias qui permettent d’une part de plus facilement les mémoriser, mais aussi de pouvoir plus aisément transitionner d’un environnement “bash/zsh/sh” vers PowerShell. Pour obtenir la liste d’alias disponible, Get-Alias est la commande à utiliser. Il y est possible de retrouver ls, cd, pwd et bien d’autres qui vous seront assurément familières.

Attention cependant, il est impossible d’utiliser les mêmes arguments (exemple: ls -ail en PowerShell sera ls -force).

Le fonctionnement d’une cmdlet repose sur 3 différentes parties, BeginProcessing (théoriquement cette méthode permet d’initialiser les différents objets dont la fonction/cmdlet peut avoir besoin), ProcessRecord (Il s’agit du cœur des instructions de la cmdlet), EndProcessing (théoriquement cette partie sert à retourner, ou nettoyer après l’exécution d’une cmdlet), certaines cmdlets permettent une définition similaire de bloc de contrôle, c’est le cas de ForEach-Object avec les arguments -Begin, -Process et -End (ces derniers doivent être suivis de “scriptblock”), enfin lors de la création de fonction avancée (fonction qui simule un comportement d’une cmdlet), ces blocs apparaissent sous la forme suivante: BEGIN{}, PROCESS{}, END{}. Contrairement aux environnements “Linux/OpenBSD” et le “CMD”, la “pipe” de PowerShell a un comportement quelque peu différent. En effet, elle ne passe pas une chaîne de caractère représentative de la sortie de la commande. Ici elle passera l’objet entier ainsi que les données qui y sont associées: Get-Process | % {$_.GetType()}. Le résultat de Get-Process est envoyé au travers d’une pipe, puis utilisé dans ForEach-Object (qui est une boucle foreach qui attend une entrée par une pipe), une variable est créée automatiquement à cette occasion $_, enfin la méthode .GetType() permet de donner le type.

PowerShell est un langage orienté objet, dont les types sont les mêmes que ceux en .NET. Il y a donc une très forte interaction entre le Framework .NET et PowerShell, ce qui permet plusieurs choses:

  • utiliser les classes et namespace les plus communs (par extension chargé n’importe quel “bytecode” C#, grâce à l’espace de nom System.Reflection).
  • ajouter des types C# directement en runtime avec Add-Type, pour les utilisations les plus commune.

L’exécution de commande ou de script dans PowerShell passe par la création d’objets déjà mentionnés, les “scriptblock”, ils sont constitués d’accolades de part et d’autres et peuvent contenir n’importe quel code PowerShell. Ces derniers sont invoqués avec la méthode .Invoke().

# invocation d'un scriptblock 
{Get-ChildItem | % {$_.Name}}.Invoke()
# utilisation de System.Management.Automation.PowerShell
$powershell = [powershell]::Create()
$powershell.AddScript('Get-ChildItem | % {$_.Name}')
$powershell.Invoke()

Une fois la commande ou le script exécutés, toute donnée ou trace de son passage est oubliée, pour palier à cela, il existe les RunSpaces qui permettent de créer une session pour maintenir une persistance des variables ou fonctions injectées dans la mémoire. Il est possible d’utiliser PowerShell sans PowerShell, en utilisant la dll System.Management.Automation dans un code C#. Par exemple:

using System;
using System.Management.Automation; 
using System.Management.Automation.Runspaces;
 
namespace CustomPowerShell
{
        class Program
	{
		public static void Main(string[] args)
		{
                        Runspace runspace = RunspaceFactory.CreateRunspace();
                        runspace.Open();
                        RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
                        Pipeline pipeline = runspace.CreatePipeline();
                        pipeline.Commands.AddScript("ls -force");
                        pipeline.Commands.Add("Out-Default");
                        pipeline.Invoke();
                 }
        }
}

Comme vous pouvez le constater, le code C# ressemble un peu au deuxième exemple d’exécution de “scriptblock” avec PowerShell.

Sécurité

Au vue de l’évolution des méthodologies d’attaques sur PowerShell, beaucoup de sécurité a progressivement été mise en place autour de notre ami. On note par exemple depuis PowerShell v3 l’implémentation du module logging, c’est une protection rudimentaire qui permet d’enregistrer les différents événements d’exécution de commande, l’enregistrement partiel des sorties et des données d’entrées, ainsi que l’exécution des scripts. PowerShell v4 introduit le script block logging, ce dernier est une forme évoluée du premier, il enregistre chaque bloc d’exécution, et propose dans les journaux d’événement un rapport détaillé avec le résultat et les entrées de la commande. PowerShell v5 implémente toutes les défenses modernes qu’il existe autour de PowerShell (PowerShell <3 BlueTeam) mise en place du “Transcript” qui enregistre chaque commande entrée dans PowerShell, (si activée, la clé de registre EnableTranscripting de chemin HKLM:\Software\Policies\Microsoft\Windows\PowerShell\Transcription est égal à 1, il est possible de spécifier dans les registres le fichier de sorties, OutputDirectory). Introduction au DeepScriptBlockLogging la différence majeure avec la précédente version est que cette fois-ci même si l’appel est offusqué, étant donné qu’il enregistre chaque phase, la commande sera donc loggé entièrement. Pour défendre au mieux les implants via PowerShell (entièrement en PowerShell ou l’utilisant comme support pour exécuter du .NET en mémoire), il est créée l’AMSI (AntiMalware ScanInterface) qui permet de faire valider une commande avant son exécution par un antivirus (Defender ou autre). Une autre lourde protection est la possibilité de changer le LangageMode pour restreindre au maximum l’utilisation de PowerShell. Plusieurs contournements existent autour de ces solutions que nous verrons dans un futur article.

Quelques Cmdlets

  • Write-Host permet d’afficher un message. On peut en changer la couleur et le fond avec les paramètres -ForeGroundColor et -BackGroundColor.
  • Write-Verbose permet d’afficher un message dans une fonction uniquement si le paramètre -Verbose est présent, également pour Write-Debug.
  • Get-Process permet d’accéder aux différents processus en cours.
  • Get-Service permet d’accéder aux différents services.
  • Get-ComputerInfo permet d’obtenir des informations sur le système local.
  • Get-ChildItem permet de lister un dossier.
  • Set-Location permet de changer de dossier.
  • Get-Location permet d’obtenir le chemin actuel.
  • Get-PSDrive permet d’obtenir les “PSDrives” actuellement montés.
  • New-PSDrive permet de monter un “PSDrive”.
  • Where-Object permet d’appliquer une condition et d’en ressortir les éléments la remplissant.
  • New-Object permet la création d’objet .NET en indiquant au choix le type ou le chemin.
  • Sort-Object permet de trier les objets selon certains ordres ou arrangements passé en argument comme -Unique qui permet de filtrer les propriétés et les retournes uniquement une seule fois.

En combinant ces cmdlets et des “pipelines” il est possible de faire des choses très complexes avec une seule ligne.

Astuces

Plusieurs astuces et ont été mises à disposition pour faciliter la vie des programmeurs. Les commentaires par exemple sont créés pour une ligne avec # sur plusieurs il faut utiliser la structure <# #>. Les chemins vers les types .NET sont parfois long, pour les raccourcir il existe les accélérateurs de types, par exemple [System.Management.Automation.PSCredential] possède un accélérateur de type [PSCredential] vous pouvez trouver une liste d’accélérateur de type ici. A propos des PSCredential, ces derniers permettent l’utilisation d’identifiants alternatifs pour l’action de la cmdlet (généralement ils sont attendus avec le paramètre -Credential), couplés avec Start-Process, ils peuvent se transformer en un runAs très puissant et naturel. Il existe plusieurs manières de définir un PSCredential: la cmdlet Get-Credential peut être utilisé dans ce but, une interface graphique demandera les identifiants, cette méthode est pratique pour du Scripting, mais peu intéressante lors de manipulation sans ce genre d’interface ou nécessitant une automatisation plus importante, un PSCredential à besoin d’un SecureString.

$pass = ConvertTo-SecureString -AsPlainText -Force "passw0rd"
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList "domain\user",$pass
Start-Process powershell.exe -Credential $cred

Si l’article vous a plu je vous invite à aller regarder la série sur la programmation avec PowerShell.

Leave a Reply