
Windows-Benutzermodus-Shellcode-Entwicklungsframework (WUMSDF)
v1.1Das Projekt SILVERPICK ist ein Windows-User-Mode-Shellcode-Entwicklungsframework (WUMSDF), dessen alleiniger Zweck es ist, Fähigkeitsentwicklern zu ermöglichen, Position Independent Code (PIC)-Blobs für Windows x64 auf einfache Weise mit C/C++ zu erstellen, um die Entwicklungskosten eines solchen Vorhabens zu senken.
Es leitet sich vom Projekt WILDBEAST ab und nutzt daher:
Visual Studio Code als Code-EditorMinGW-w64-Toolchain als Compiler-ToolchainGNU Make als Build-SystemDie Einrichtungsanweisungen findest du hier: GCC-Clang-Setup-Windows
Bitte beachte, dass dieses Projekt MSYS2 verwendet.
Shellcode in höheren Programmiersprachen zu schreiben, ist nichts Neues, und unzählige Blogbeiträge und Forschungsarbeiten wurden seit 2010 darüber veröffentlicht. Was ist also neu an SILVERPICK?
Nun, ich bin froh, dass du fragst.
SILVERPICK hat ein nettes kleines Bündel an Tricks im Ärmel, aber vor allem ist dies meine Herangehensweise an das Thema.
Also, ohne weitere Umschweife, präsentiere ich dir meinen ersten Trick.
Seitdem Matt Graeber das Schreiben von Shellcode in C populär gemacht hat, verwenden die meisten Leute seinen 16-Byte-Stack-Ausrichtungs-Stub, geschrieben in Assembly.
Obwohl dies kein Problem darstellt, da wir nicht IKEA sind, sollte Assembly nicht notwendig sein, und tatsächlich ist es das auch nicht.
Es gibt ein GCC-Funktionsattribut, das den Stack-Ausrichtungs-Stub für dich erzeugt.
Hier ist das force_align_arg_pointer-Funktionsattribut in Form eines hilfreichen ALIGN_STACK-Makros, das die folgende Assembly erzeugt:
Disassembly of section .init:
<PicEntry>:
push rbp
mov rbp,rsp
and rsp,0xfffffffffffffff0
sub rsp,0x20
call <PicEntry+0x11> IMAGE_REL_AMD64_REL32 .text$payload
leave
ret
Was ist die .init-Sektion, fragst du? Nun, das dient als schöner Übergang zu meinem zweiten Trick.
Matt Graeber hat vielleicht irgendwann das Schreiben von Shellcode in C populär gemacht, aber wirklich war es Paul Ungur, der diese schwarze Kunst mit Stardust wiederbelebt hat.
Nun verwendet Stardust ein Binutils-Linker-Skript, um die Platzierung von Funktionen und Daten in der entsprechenden PE-Sektion in der richtigen Reihenfolge zu steuern. Diese Technik selbst stammt aus der Arbeit von Austin Hudson, und viele Leute verwenden eine Variante seiner Linker-Skripte.
Während Linker-Skripte großartig für die Linker-Sektionsreihenfolge sind, sind sie unnötig, wenn du nur eine bestimmte Funktion an den Anfang der Code-Sektion setzen musst.
Hier kommt das section-Funktionsattribut mit einem speziellen Sektionsnamen namens .init ins Spiel, das dem Linker mitteilt, dass die Funktion Vor-main()-Laufzeitinitialisierungscode enthält und als erstes in der Link-Reihenfolge stehen muss.
Zu diesem Zweck wurde das CODE_BEGIN-Makro erstellt.
Für meinen dritten Trick präsentiere ich dir das STACK_STRING-Makro.
In C kannst du einen Stack-String (einen String, der dynamisch auf dem Stack aufgebaut wird) erstellen, indem du das String-Literal als ein Array von ANSI-Zeichen deklarierst:
char charrHelloKitty[] = { 'H', 'e', 'l', 'l', 'o', 'K', 'i', 't', 't', 'y', '\0' };
In C++ kannst du einen Stack-String erstellen, indem du ein char-Array einfach als constexpr markierst:
constexpr char charrHelloKitty[]{ "HelloKitty" };
Beide Techniken werden jedoch angesichts von Compiler-Optimierungen nutzlos, wenn die String-Literale ausreichend groß sind, anders als unsere Lösung, die unabhängig von der String-Länge und dem Grad der Compiler-Optimierungen funktioniert, dank eines cleveren C++-Template-Metaprogrammierungs-Hacks, mit freundlicher Genehmigung von Can Bölük.
Die Verwendung dieses Makros ist ziemlich einfach:
STACK_STRING(sstrText, "an extra long hello world!");
STACK_STRING(sstrCaption, "Demo");
MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK);
Dies erzeugt die folgende Assembly:
mov [rsp+58h+var_23], 61h ; 'a'
mov [rsp+58h+var_22], 6Eh ; 'n'
mov [rsp+58h+var_21], 20h ; ' '
mov [rsp+58h+var_20], 65h ; 'e'
mov [rsp+58h+var_1F], 78h ; 'x'
mov [rsp+58h+var_1E], 74h ; 't'
mov [rsp+58h+var_1D], 72h ; 'r'
mov [rsp+58h+var_1C], 61h ; 'a'
mov [rsp+58h+var_1B], 20h ; ' '
mov [rsp+58h+var_1A], 6Ch ; 'l'
mov [rsp+58h+var_19], 6Fh ; 'o'
mov [rsp+58h+var_18], 6Eh ; 'n'
mov [rsp+58h+var_17], 67h ; 'g'
mov [rsp+58h+var_16], 20h ; ' '
mov [rsp+58h+var_15], 68h ; 'h'
mov [rsp+58h+var_14], 65h ; 'e'
mov [rsp+58h+var_13], 6Ch ; 'l'
mov [rsp+58h+var_12], 6Ch ; 'l'
mov [rsp+58h+var_11], 6Fh ; 'o'
mov [rsp+58h+var_10], 20h ; ' '
mov [rsp+58h+var_2F], 0
mov [rsp+58h+var_F], 77h ; 'w'
mov [rsp+58h+var_E], 6Fh ; 'o'
mov [rsp+58h+var_D], 72h ; 'r'
mov [rsp+58h+var_C], 6Ch ; 'l'
mov [rsp+58h+var_B], 64h ; 'd'
mov [rsp+58h+var_A], 21h ; '!'
mov [rsp+58h+var_33], 44h ; 'D'
mov [rsp+58h+var_32], 65h ; 'e'
mov [rsp+58h+var_31], 6Dh ; 'm'
mov [rsp+58h+var_30], 6Fh ; 'o'
Da wir gerade von C++ sprechen, präsentiere ich dir für meinen vierten Trick Compile-Zeit-String-Hashing.
Obwohl dies kein neuartiges Konzept ist, bietet SILVERPICK einige Verbesserungen gegenüber bestehenden öffentlichen Implementierungen.
Erstens verwenden wir die 64-Bit-Variante der beliebten nicht-kryptografischen Hash-Funktion FNV-1a, um die Wahrscheinlichkeit eines erfolgreichen Hash-Kollisionsangriffs zu verringern.
Zweitens verwenden wir einen modifizierten Parameter für die Hash-Funktion, um uns gegen vorberechnete Hash-Tabellen-Lookups wie HashDB zu verteidigen. Entscheidend ist, dass dies die Eigenschaften der Hash-Funktion nicht verändert.
Um einen kurzen String zur Laufzeit zu hashen, verwende einfach das HASH_STRING_RUN_TIME-Makro.
Um ein kurzes String-Literal zur Compile-Zeit zu hashen, verwende einfach das HASH_STRING_COMPILE_TIME-Makro. Die reine Compile-Zeit-Auswertung wird über consteval garantiert.
Es stellt sich heraus, dass man eine ganze Reihe von C-Runtime-Library (CRT)-Funktionen mit x86-String-Anweisungen implementieren kann. Also musste ich sie natürlich mit einer Mischung aus Compiler-Intrinsics und Inline-Assembly implementieren.
Möchtest du die Funktion msvcrt!memset in deinem Code verwenden? Verwende stattdessen das ZERO_MEMORY-Makro, das die über ein Compiler-Intrinsic emittierte rep stosb-Anweisung verwendet.
Wie wäre es mit der Funktion msvcrt!memcpy oder der Funktion msvcrt!memmove, fragst du? Lerne das COPY_MEMORY-Makro als Ersatz kennen, das die über ein Compiler-Intrinsic emittierte rep movsb-Anweisung verwendet.