
Framework di sviluppo di shellcode in modalità utente per Windows (WUMSDF)
v1.1Il progetto SILVERPICK è un Windows User-Mode Shellcode Development Framework (WUMSDF) il cui unico scopo è consentire agli sviluppatori di capability di creare blob di Position Independent Code (PIC) per Windows x64 usando C/C++ in modo semplice, così da ridurre i costi di sviluppo di un'impresa del genere.
Deriva dal progetto WILDBEAST e, in quanto tale, si avvale di:
Visual Studio Code come editor di codiceMinGW-w64 come toolchain di compilazioneGNU Make come sistema di buildPuoi trovare le istruzioni di configurazione qui: GCC-Clang-Setup-Windows
Nota: questo progetto utilizza MSYS2.
Scrivere shellcode in linguaggi di programmazione ad alto livello non è una novità, e innumerevoli post di blog e articoli di ricerca sono stati pubblicati sull'argomento dal 2010. Quindi, cosa c'è di nuovo in SILVERPICK?
Beh, sono lieto che tu lo chieda.
SILVERPICK ha un bel po' di trucchi nascosti nella manica, ma soprattutto, questa è la mia interpretazione dell'argomento.
Quindi, senza ulteriori indugi, ti presento il mio primo trucco.
Da quando Matt Graeber ha reso popolare la scrittura di shellcode in C, la maggior parte delle persone ha usato il suo stub di allineamento dello stack di 16 byte scritto in linguaggio Assembly.
Anche se questo non è un problema, visto che non siamo IKEA, l'assembly non dovrebbe essere necessario, e in effetti non lo è.
Esiste un GCC Function Attribute che emette per te lo stub di allineamento dello stack.
Ecco l'attributo di funzione force_align_arg_pointer sotto forma di un'utile macro ALIGN_STACK, che genera il seguente assembly:
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
«Che cos'è la sezione .init?», chiederai. Beh, è una perfetta transizione verso il mio secondo trucco.
Matt Graeber potrebbe aver reso popolare la scrittura di shellcode in C a un certo punto, ma in realtà è stato Paul Ungur a far rivivere quest'arte nera con Stardust.
Ora, Stardust usa uno script di linker Binutils per controllare il posizionamento di funzioni e dati nella sezione PE appropriata nell'ordine corretto. Questa tecnica deriva a sua volta dal lavoro di Austin Hudson, e molte persone usano una variante dei suoi script di linker.
Sebbene gli script di linker siano ottimi per l'ordinamento delle sezioni del linker, se tutto ciò che serve è porre una determinata funzione all'inizio della sezione del codice, non sono necessari.
Entra in gioco l'attributo di funzione section con un nome di sezione speciale chiamato .init, che indica al linker che la funzione contiene codice di inizializzazione runtime pre-main() e deve essere per prima nell'ordine di collegamento.
A questo scopo, è stata creata la macro CODE_BEGIN.
Per il mio terzo trucco, ti presento la macro STACK_STRING.
In C, puoi creare una stringa di stack (una stringa costruita dinamicamente sullo stack) dichiarando la stringa letterale come un array di caratteri ANSI:
char charrHelloKitty[] = { 'H', 'e', 'l', 'l', 'o', 'K', 'i', 't', 't', 'y', '\0' };
In C++, puoi creare una stringa di stack semplicemente marcando un array char come constexpr:
constexpr char charrHelloKitty[]{ "HelloKitty" };
Tuttavia, entrambe queste tecniche vengono rese inutili di fronte alle ottimizzazioni del compilatore se le stringhe letterali sono sufficientemente grandi, a differenza della nostra soluzione, che funzionerà indipendentemente dalla lunghezza della stringa e dal livello di ottimizzazioni del compilatore, grazie a un abile hack di metaprogrammazione con template C++ per gentile concessione di Can Bölük.
Usare questa macro è piuttosto semplice:
STACK_STRING(sstrText, "an extra long hello world!");
STACK_STRING(sstrCaption, "Demo");
MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK);
Questo genererà il seguente 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'
Parlando di C++, per il mio quarto trucco ti presento l'hashing delle stringhe a tempo di compilazione.
Anche se non è un concetto nuovo, SILVERPICK offre alcuni miglioramenti rispetto alle implementazioni pubbliche esistenti.
In primo luogo, usiamo la variante a 64 bit della popolare funzione hash non crittografica FNV-1a per ridurre la probabilità di un attacco di collisione hash riuscito.
In secondo luogo, usiamo un parametro modificato per la funzione hash per difenderci dalle ricerche in tabelle hash precalcolate, come HashDB. Fondamentalmente, questo non cambia le proprietà della funzione hash.
Per calcolare l'hash di una stringa corta a runtime, basta usare la macro HASH_STRING_RUN_TIME.
Per calcolare l'hash di una stringa letterale corta a tempo di compilazione, basta usare la macro HASH_STRING_COMPILE_TIME. La valutazione esclusivamente a tempo di compilazione è garantita tramite consteval.
Si scopre che puoi implementare un bel po' di funzioni della C Runtime Library (CRT) usando le istruzioni stringa x86. Quindi, ovviamente, ho dovuto implementarle usando un mix di intrinsic del compilatore e assembly inline.
Vuoi usare la funzione msvcrt!memset nel tuo codice? Usa invece la macro ZERO_MEMORY, che usa l'istruzione rep stosb emessa tramite un intrinsic del compilatore.
E per quanto riguarda la funzione msvcrt!memcpy o la funzione msvcrt!memmove, chiederai? Ecco la macro COPY_MEMORY come sostituto, che usa l'istruzione rep movsb emessa tramite un intrinsic del compilatore.