
Framework de Desenvolvimento de Shellcode em Modo de Usuário do Windows (WUMSDF)
v1.1O projeto SILVERPICK é um Windows User-Mode Shellcode Development Framework (WUMSDF) cujo único propósito é capacitar desenvolvedores de capacidades a construir blocos de Position Independent Code (PIC) para Windows x64 usando C/C++ de maneira fácil, de modo a reduzir os custos de desenvolvimento de tal empreendimento.
Ele deriva do projeto WILDBEAST e, como tal, utiliza:
Visual Studio Code como editor de códigoMinGW-w64 como toolchain de compilaçãoGNU Make como sistema de buildVocê pode encontrar as instruções de configuração aqui: GCC-Clang-Setup-Windows
Por favor, note que este projeto está usando MSYS2.
Escrever shellcode em linguagens de programação de alto nível não é novidade, e inúmeros posts de blog e artigos de pesquisa foram publicados sobre o assunto desde 2010. Então, o que há de novo no SILVERPICK?
Bem, fico feliz que você tenha perguntado.
SILVERPICK tem um belo repertório de truques escondidos na manga, mas, acima de tudo, esta é a minha visão sobre o assunto.
Então, sem mais delongas, apresento a vocês meu primeiro truque.
Desde que Matt Graeber popularizou escrever shellcode em C, a maioria das pessoas tem usado seu stub de alinhamento de pilha de 16 bytes escrito em linguagem Assembly.
Embora isso não seja um problema, já que não somos a IKEA, assembly não deveria ser necessário, e de fato não é.
Existe um GCC Atributo de Função que emitirá o stub de alinhamento de pilha para você.
Conheça o atributo de função force_align_arg_pointer na forma de uma útil macro ALIGN_STACK, que gera o seguinte 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
O que é a seção .init, você pergunta? Bem, isso serve como uma boa transição para o meu segundo truque.
Matt Graeber pode ter popularizado escrever shellcode em C em algum momento, mas, na verdade, foi Paul Ungur quem reviveu essa arte negra com Stardust.
Agora, Stardust usa um script de linker do Binutils para controlar o posicionamento de funções e dados na seção PE apropriada na ordem correta. Essa técnica em si é derivada do trabalho de Austin Hudson, e muitas pessoas usam uma variante de seus scripts de linker.
Embora scripts de linker sejam ótimos para ordenar seções do linker, se tudo o que você precisa é colocar uma determinada função no início da seção de código, eles são desnecessários.
Entre o atributo de função section com um nome de seção especial chamado .init, que indica ao linker que a função contém código de inicialização em tempo de execução pré-main() e deve estar primeiro na ordem de link.
Para esse efeito, a macro CODE_BEGIN foi criada.
Para o meu terceiro truque, apresento a vocês a macro STACK_STRING.
Em C, você pode criar uma stack string (uma string construída dinamicamente na pilha) declarando o literal de string como um array de caracteres ANSI:
char charrHelloKitty[] = { 'H', 'e', 'l', 'l', 'o', 'K', 'i', 't', 't', 'y', '\0' };
Em C++, você pode criar uma stack string simplesmente marcando um array char como constexpr:
constexpr char charrHelloKitty[]{ "HelloKitty" };
No entanto, ambas as técnicas se tornam inúteis diante de otimizações do compilador se os literais de string forem suficientemente grandes, ao contrário da nossa solução, que funcionará independentemente do comprimento da string e do nível de otimizações do compilador, graças a um engenhoso hack de metaprogramação com templates C++ cortesia de Can Bölük.
Usar essa macro é bastante simples:
STACK_STRING(sstrText, "an extra long hello world!");
STACK_STRING(sstrCaption, "Demo");
MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK);
Isso gerará o seguinte 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'
Falando em C++, apresento a vocês hashing de strings em tempo de compilação para o meu quarto truque.
Embora este não seja um conceito novo, SILVERPICK oferece algumas melhorias em relação às implementações públicas existentes.
Primeiramente, usamos a variante de 64 bits da popular função hash não criptográfica FNV-1a para reduzir a probabilidade de um ataque bem-sucedido de colisão de hash.
Em segundo lugar, usamos um parâmetro modificado para a função hash para nos defendermos contra consultas de tabelas hash pré-calculadas, como o HashDB. Crucialmente, isso não altera as propriedades da função hash.
Para fazer hash de uma string curta em tempo de execução, basta usar a macro HASH_STRING_RUN_TIME.
Para fazer hash de um literal de string curta em tempo de compilação, basta usar a macro HASH_STRING_COMPILE_TIME. A avaliação somente em tempo de compilação é garantida via consteval.
Acontece que você pode implementar um bom punhado de funções da C Runtime Library (CRT) usando instruções de string x86. Então, é claro, eu tive que implementá-las usando uma mistura de intrínsecos do compilador e assembly inline.
Você quer usar a função msvcrt!memset no seu código? Use a macro ZERO_MEMORY em vez disso, que usa a instrução rep stosb emitida por meio de um intrínseco do compilador.
E quanto à função msvcrt!memcpy ou à função msvcrt!memmove, você pergunta? Conheça a macro COPY_MEMORY como substituta, que usa a instrução rep movsb emitida por meio de um intrínseco do compilador.