
Framework de desarrollo de shellcode en modo usuario de Windows (WUMSDF)
v1.1El proyecto SILVERPICK es un Windows User-Mode Shellcode Development Framework (WUMSDF) cuyo único propósito es permitir a los desarrolladores de capacidades construir blobs de Position Independent Code (PIC) para Windows x64 usando C/C++ de manera sencilla, reduciendo así los costes de desarrollo de dicha tarea.
Deriva del proyecto WILDBEAST y, como tal, utiliza:
Visual Studio Code como editor de códigoMinGW-w64 como compiladorGNU Make como sistema de compilaciónPuedes encontrar las instrucciones de configuración aquí: GCC-Clang-Setup-Windows
Ten en cuenta que este proyecto utiliza MSYS2.
Escribir shellcode en lenguajes de programación de alto nivel no es nada nuevo, y desde 2010 se han publicado innumerables entradas de blog y artículos de investigación sobre el tema. Entonces, ¿qué hay de nuevo en SILVERPICK?
Bueno, me alegra que lo preguntes.
SILVERPICK tiene un buen puñado de trucos escondidos en la manga, pero sobre todo, esto es mi enfoque sobre el tema.
Así que, sin más preámbulos, te presento mi primer truco.
Desde que Matt Graeber popularizó la escritura de shellcode en C, la mayoría de la gente ha estado usando su stub de alineación de pila de 16 bytes escrito en lenguaje Assembly.
Aunque esto no es un problema, dado que no somos IKEA, el ensamblador no debería ser necesario, y de hecho no lo es.
Existe un GCC Function Attribute que generará el stub de alineación de pila por ti.
Conoce el atributo de función force_align_arg_pointer en forma de una útil macro ALIGN_STACK, que genera el siguiente ensamblador:
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
¿Qué es la sección .init, te preguntarás? Bueno, eso sirve como una buena transición a mi segundo truco.
Matt Graeber pudo haber popularizado la escritura de shellcode en C en su momento, pero en realidad fue Paul Ungur quien revivió este arte oscuro con Stardust.
Ahora bien, Stardust utiliza un script de enlazado de Binutils para controlar la colocación de funciones y datos en la sección PE apropiada y en el orden correcto. Esta técnica a su vez deriva del trabajo de Austin Hudson, y mucha gente usa una variante de sus scripts de enlazado.
Aunque los scripts de enlazado son excelentes para ordenar las secciones del enlazador, si todo lo que necesitas es colocar una función determinada al comienzo de la sección de código, no son necesarios.
Ahí entra el atributo de función section con un nombre de sección especial llamado .init, que indica al enlazador que la función contiene código de inicialización de tiempo de ejecución previo a main() y que debe ser primero en el orden de enlazado.
Con este fin, se ha creado la macro CODE_BEGIN.
Para mi tercer truco, te presento la macro STACK_STRING.
En C, puedes crear una cadena de pila (una cadena que se construye dinámicamente en la pila) declarando el literal de cadena como un array de caracteres ANSI:
char charrHelloKitty[] = { 'H', 'e', 'l', 'l', 'o', 'K', 'i', 't', 't', 'y', '\0' };
En C++, puedes crear una cadena de pila simplemente marcando un array char como constexpr:
constexpr char charrHelloKitty[]{ "HelloKitty" };
Sin embargo, ambas técnicas quedan inutilizables frente a las optimizaciones del compilador si los literales de cadena son lo suficientemente grandes, a diferencia de nuestra solución, que funcionará independientemente de la longitud de la cadena y del nivel de optimizaciones del compilador, gracias a un ingenioso truco de metaprogramación con plantillas de C++ cortesía de Can Bölük.
Usar esta macro es bastante sencillo:
STACK_STRING(sstrText, "an extra long hello world!");
STACK_STRING(sstrCaption, "Demo");
MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK);
Esto generará el siguiente ensamblador:
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'
Hablando de C++, te presento el hash de cadenas en tiempo de compilación para mi cuarto truco.
Aunque no es un concepto novedoso, SILVERPICK ofrece algunas mejoras sobre las implementaciones públicas existentes.
En primer lugar, usamos la variante de 64 bits de la popular función hash no criptográfica FNV-1a para reducir la probabilidad de un ataque exitoso de colisión de hash.
En segundo lugar, usamos un parámetro modificado para la función hash para defendernos de búsquedas en tablas hash precalculadas, como HashDB. Es crucial que esto no cambia las propiedades de la función hash.
Para hacer hash de una cadena corta en tiempo de ejecución, simplemente usa la macro HASH_STRING_RUN_TIME.
Para hacer hash de un literal de cadena corta en tiempo de compilación, simplemente usa la macro HASH_STRING_COMPILE_TIME. La evaluación exclusivamente en tiempo de compilación está garantizada mediante consteval.
Resulta que puedes implementar un buen número de funciones de la C Runtime Library (CRT) usando instrucciones de cadena x86. Así que, por supuesto, tuve que implementarlas usando una mezcla de intrínsecos del compilador y ensamblador en línea.
¿Quieres usar la función msvcrt!memset en tu código? Usa en su lugar la macro ZERO_MEMORY, que utiliza la instrucción rep stosb emitida mediante un intrínseco del compilador.
¿Y qué me dices de la función msvcrt!memcpy o de la función msvcrt!memmove? Conoce la macro COPY_MEMORY como reemplazo, que utiliza la instrucción rep movsb emitida mediante un intrínseco del compilador.