
Windows User-Mode Shellcode Development Framework (WUMSDF)
v1.1Project SILVERPICK is a Windows User-Mode Shellcode Development Framework (WUMSDF) whose sole purpose is to empower capability developers to build Position Independent Code (PIC) blobs for Windows x64 using C/C++ in an easy manner so as to reduce the development costs of such an endeavour.
It derives from project WILDBEAST and, as such, leverages:
Visual Studio Code as the code editorMinGW-w64 toolchain as the compiler toolchainGNU Make as the build systemYou may find the setup instructions from here: GCC-Clang-Setup-Windows
Please note that this project is using MSYS2.
Writing shellcode in higher-level programming languages is nothing new, and countless blog posts and research papers have been published on the same since 2010. So, what is new in SILVERPICK?
Well, I am glad that you asked.
SILVERPICK has a nice little bag of tricks hidden up its sleeve, but most of all, this is my take on the subject.
So, without further ado, I present to you my first trick.
Ever since Matt Graeber popularised writing shellcode in C, most people have been using his 16-byte stack alignment stub written in Assembly language.
While this is not a problem, seeing as how we aren't IKEA, assembly should not be necessary, and indeed it is not.
There exists a GCC Function Attribute that will emit the stack alignment stub for you.
Meet the force_align_arg_pointer function attribute in the form of a helpful ALIGN_STACK macro, which generates the following 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
What's the .init section, you ask? Well, that serves as a nice segue into my second trick.
Matt Graeber might have popularised writing shellcode in C at some point, but really, it was Paul Ungur who revived this black art with Stardust.
Now, Stardust uses a Binutils linker script to control the placement of functions and data into the appropriate PE section in the correct order. This technique itself is derived from Austin Hudson's work, and many people use a variant of his linker scripts.
While linker scripts are great for linker section ordering, if all you need is to place a certain function at the beginning of the code section, they are unnecessary.
Enter the section function attribute with a special section name called .init, which indicates to the linker that the function contains pre-main() runtime initialization code and must be first in the link order.
To this effect, the CODE_BEGIN macro has been created.
For my third trick, I present to you the STACK_STRING macro.
In C, you can create a stack string (a string that is dynamically built on the stack) by declaring the string literal as an array of ANSI characters:
char charrHelloKitty[] = { 'H', 'e', 'l', 'l', 'o', 'K', 'i', 't', 't', 'y', '\0' };
In C++, you can create a stack string by simply marking a char array as constexpr:
constexpr char charrHelloKitty[]{ "HelloKitty" };
However, both of these techniques are rendered useless in the face of compiler optimizations if the string literals are sufficiently large, unlike our solution, which will work regardless of the string length and the level of compiler optimizations, thanks to some clever C++ template metaprogramming hack courtesy of Can Bölük.
Using this macro is pretty straightforward:
STACK_STRING(sstrText, "an extra long hello world!");
STACK_STRING(sstrCaption, "Demo");
MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK);
This will generate the following 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'
Speaking of C++, I present to you compile-time string hashing for my fourth trick.
While this is not a novel concept, SILVERPICK offers some improvements over existing public implementations.
Firstly, we use the 64-bit variant of the popular FNV-1a non-cryptographic hash function in order to reduce the probability of a successful hash collision attack.
Secondly, we use a modified parameter for the hash function to defend against precalculated hash table lookups, such as HashDB. Crucially, this does not change the properties of the hash function.
To hash a short string at run time, simply use the HASH_STRING_RUN_TIME macro.
To hash a short string literal at compile time, simply use the HASH_STRING_COMPILE_TIME macro. Compile-time only evaluation is guaranteed via consteval.
It turns out that you can implement quite a handful of C Runtime Library (CRT) functions using x86 string instructions. So, of course, I had to implement them using a mix of compiler intrinsics and inline assembly.
Do you want to use the msvcrt!memset function in your code? Use the ZERO_MEMORY macro instead, which uses the rep stosb instruction emitted via a compiler intrinsic.
How about the msvcrt!memcpy function or the msvcrt!memmove function, you ask? Meet the COPY_MEMORY macro as a replacement, which uses the rep movsb instruction emitted via a compiler intrinsic.
But what about an alternative to the msvcrt!memcmp function? It turns out that there isn't exactly a compiler intrinsic available to emit the repe cmpsb instruction. So, we write a compare_memory function using inline assembly instead.