mirror of
https://github.com/winterknife/SILVERPICK
synced 2026-06-08 18:21:15 +00:00
281 lines
15 KiB
Markdown
281 lines
15 KiB
Markdown
# SILVERPICK
|
|
|
|
## VERSION
|
|
|
|
- `v1.0`
|
|
|
|
## BRIEF
|
|
|
|
Project `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](https://github.com/winterknife/WILDBEAST) and, as such, leverages:
|
|
1. `Visual Studio Code` as the code editor
|
|
2. `MinGW-w64` toolchain as the compiler toolchain
|
|
3. `GNU Make` as the build system
|
|
|
|
## SETUP
|
|
|
|
You may find the setup instructions from here: [GCC-Clang-Setup-Windows](https://gist.github.com/winterknife/0b177a75a55bad895b19aad64cffa14f)
|
|
|
|
Please note that this project is using [MSYS2](https://www.msys2.org/).
|
|
|
|
## FEATURES
|
|
|
|
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.
|
|
|
|
### TRICK 01
|
|
|
|
Ever since Matt Graeber popularised writing shellcode in `C`, most people have been using his [16-byte stack alignment stub written in `Assembly` language](https://github.com/mattifestation/PIC_Bindshell/blob/master/PIC_Bindshell/AdjustStack.asm).
|
|
|
|
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](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) 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](/Inc/Common.h#L84) macro, which generates the following assembly:
|
|
```asm
|
|
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.
|
|
|
|
### TRICK 02
|
|
|
|
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](https://github.com/Cracked5pider/Stardust).
|
|
|
|
Now, `Stardust` uses a [Binutils linker script](https://sourceware.org/binutils/docs/ld/Scripts.html) 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](/Inc/Common.h#L81) macro has been created.
|
|
|
|
### TRICK 03
|
|
|
|
For my third trick, I present to you the [STACK_STRING](/Inc/StackString.h#L35) 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:
|
|
```c
|
|
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`:
|
|
```cpp
|
|
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:
|
|
```c
|
|
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:
|
|
```asm
|
|
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'
|
|
```
|
|
|
|
### TRICK 04
|
|
|
|
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](https://cloud.google.com/blog/topics/threat-intelligence/precalculated-string-hashes-reverse-engineering-shellcode/), such as [HashDB](https://github.com/OALabs/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](/Inc/HashString.h#L45) macro.
|
|
|
|
To hash a short string literal at compile time, simply use the [HASH_STRING_COMPILE_TIME](/Inc/HashString.h#L42) macro. Compile-time only evaluation is guaranteed via `consteval`.
|
|
|
|
### TRICK 05
|
|
|
|
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](/Inc/Common.h#L123) 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](/Inc/Common.h#L126) 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](/Inc/Common.h#L141) function using inline assembly instead.
|
|
|
|
Finally, if you seek a replacement for the `msvcrt!memchr` function, meet the [scan_memory](/Inc/Common.h#L167) function, which once again uses inline assembly since there is no compiler intrinsic available to emit the `repne scasb` instruction.
|
|
|
|
Oh, and did I forget to mention that you could write your own safer version of the `msvcrt!strlen` function using the `scan_memory` routine like so:
|
|
```cpp
|
|
DWORD_PTR dwptrExportNameLength = std::min(BIT_CAST(DWORD_PTR, scan_memory(strExportName, 0x00, MAX_EXPORTED_SYMBOL_NAME_LEN)) - BIT_CAST(DWORD_PTR, strExportName), MAX_EXPORTED_SYMBOL_NAME_LEN);
|
|
```
|
|
|
|
Please note that these implementations may not produce the most performant code, depending on the target `CPU` microarchitecture. However, they are guaranteed to get the job done.
|
|
|
|
### TRICK 06
|
|
|
|
Interested in more parlour tricks?
|
|
|
|
There are a lot of other small macros in [Common.h](/Inc/Common.h) that exist to abstract away some of the complexities of massaging the compiler.
|
|
|
|
A dependency-less implementation of the [GetModuleHandle](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlea) function is provided in [UserModuleBase.cpp](/Src/UserModuleBase.cpp). To simplify ease-of-use, a helpful macro named [GET_USER_MODULE_BASE](/Inc/UserModuleBase.h#L36) has been created.
|
|
|
|
Similarly, a dependency-less implementation of the [GetProcAddress](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress) function is provided in [PEParse.cpp](/Src/PEParse.cpp), which is then wrapped in a handy macro aptly named [GET_EXPORTED_SYMBOL_ADDRESS](/Inc/PEParse.h#L36). Furthermore, two more macros have been provided to aid in run-time dynamic linking - [INITIALIZE_FUNCTION_POINTER](/Inc/PEParse.h#L39) to declare and initialize a function pointer to 0, and [RESOLVE_FUNCTION_POINTER](/Inc/PEParse.h#L42) to resolve said function pointer.
|
|
|
|
[Visual Studio Code integration](/.vscode/) is built into the project so that developers may use the `Ctrl+Shift+B` keyboard shortcut for a no-fuss build process.
|
|
|
|
[GitHub Actions integration](/.github/workflows/build.yml) is also built into the project to enable `CI` builds.
|
|
|
|
The project takes some pride in its well-organized structure, as well as the thoroughly commented and relatively clean code.
|
|
|
|
Finally, take a gander at the project [Makefile](/Makefile), which contains the choicest selection of compiler and linker flags that will generate small, secure, and `OPSEC`-friendly code. Meanwhile, verbose logging and a generated linker map file will provide visibility into the build process to facilitate a deeper understanding of the toolchain. Additionally, each translation unit also produces a disassembly file that, upon inspection, will often make you say things like "the compiler did what now?", etc.
|
|
|
|
## USAGE
|
|
|
|
If you are sold on the framework, this section describes how you would make use of it.
|
|
|
|
The following is an excerpt taken from [PicMain.cpp](/Src/PicMain.cpp):
|
|
```cpp
|
|
/// @brief PIC start function
|
|
/// @param None
|
|
/// @return None
|
|
EXTERN_C NO_INLINE VOID __stdcall payload(
|
|
VOID
|
|
) {
|
|
// Init local variables
|
|
PVOID pKernel32 = nullptr;
|
|
INITIALIZE_FUNCTION_POINTER(LoadLibraryA);
|
|
HMODULE hUser32 = nullptr;
|
|
STACK_STRING(sstrUser32, "user32.dll");
|
|
INITIALIZE_FUNCTION_POINTER(MessageBoxA);
|
|
STACK_STRING(sstrText, "an extra long hello world!");
|
|
STACK_STRING(sstrCaption, "Demo");
|
|
|
|
// Get the image base address of kernel32.dll
|
|
pKernel32 = GET_USER_MODULE_BASE("kernel32.dll");
|
|
if (pKernel32 == nullptr)
|
|
goto cleanup;
|
|
|
|
// Resolve kernel32!LoadLibraryA
|
|
RESOLVE_FUNCTION_POINTER(pKernel32, LoadLibraryA);
|
|
if (LoadLibraryA == nullptr)
|
|
goto cleanup;
|
|
|
|
// Load User32.dll into the process VAS
|
|
hUser32 = LoadLibraryA(sstrUser32.data());
|
|
if (hUser32 == nullptr)
|
|
goto cleanup;
|
|
|
|
// Resolve user32!MessageBoxA
|
|
RESOLVE_FUNCTION_POINTER(hUser32, MessageBoxA);
|
|
if (MessageBoxA == nullptr)
|
|
goto cleanup;
|
|
|
|
// Display a message box
|
|
MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK);
|
|
|
|
// Cleanup
|
|
cleanup:
|
|
return;
|
|
}
|
|
```
|
|
|
|
Seems easy enough, no?
|
|
|
|
While writing `PIC` in `C/C++` using the `SILVERPICK` framework, you have to take into consideration the following rules:
|
|
1. Treat the `payload` function as you would the `main` function of a traditional program, i.e., as the _(pseudo) entry point_.
|
|
2. All string literals must be declared as _stack strings_.
|
|
3. _Global variables_ may not be used anywhere in the code.
|
|
4. `Windows API` or `Native API` functions may be used only via _run-time dynamic linking_ after ensuring that the function prototype is available in the corresponding header file.
|
|
|
|
## FUTURE ENHANCEMENTS
|
|
|
|
The section contains a non-exhaustive list of planned enhancements that will be integrated into a future project.
|
|
|
|
- [ ] Switch to `Clang/LLVM` toolchain.
|
|
- [ ] Switch to a different build system.
|
|
- [ ] Compile-time string obfuscation that works with stack strings and is resistant to [FLOSS](https://github.com/mandiant/flare-floss).
|
|
- [ ] Compile-time string hashing using seeded non-cryptographic custom hash function.
|
|
- [ ] Alternate method to retrieve `GS` segment base address.
|
|
- [ ] Capability to bypass `Export Address Filtering (EAF)` exploit mitigation.
|
|
|
|
## REFERENCES
|
|
|
|
The following is a list of references arranged in chronological order that proved invaluable to me during my research and were extensively used as inspiration for this project:
|
|
|
|
1. [Writing Shellcode with a C Compiler](https://nickharbour.wordpress.com/2010/07/01/writing-shellcode-with-a-c-compiler/) by Nick Harbour (2010)
|
|
|
|
2. [Shellcode with a C-compiler](https://blog.didierstevens.com/programs/shellcode/) by Didier Stevens (2010)
|
|
|
|
3. [Writing Optimized Windows Shellcode in C](https://web.archive.org/web/20201202085848/http://www.exploit-monday.com/2013/08/writing-optimized-windows-shellcode-in-c.html) by Matt Graeber (2013)
|
|
|
|
4. [Shellcode the better way, or how to just use your compiler](https://phrack.org/issues/69/4) by Justin Fisher (2016)
|
|
|
|
5. [ShellcodeStdio](https://winternl.com/shellcodestdio/) by Jack Ullrich (2016)
|
|
|
|
6. [Shellcode: A Windows PIC using RSA-2048 key exchange, AES-256, SHA-3](https://web.archive.org/web/20240316160314/https://modexp.wordpress.com/2016/12/26/windows-pic/) by Odzhan (2016)
|
|
|
|
7. [Writing Optimized Windows Shellcode](https://dimitrifourny.github.io/2017/04/28/optimized-shellcode.html) by Dimitri Fourny (2017)
|
|
|
|
8. [Writing and Compiling Shellcode in C](https://www.ired.team/offensive-security/code-injection-process-injection/writing-and-compiling-shellcode-in-c) by Aleksandra Doniec and Mantvydas Baranauskas (2021)
|
|
|
|
9. [Creating Shellcode from any Code Using Visual Studio and C++](https://www.codeproject.com/articles/Creating-Shellcode-from-any-Code-Using-Visual-Stud#comments-section) by Hamid Memar (2021)
|
|
|
|
10. [Writing Optimized Windows Shellcode in C](https://phasetw0.com/malware/writing-optimized-windows-shellcode-in-c/) by Philip Woldhek (2021)
|
|
|
|
11. [From C, with inline assembly, to shellcode](https://steve-s.gitbook.io/0xtriboulet/archive/notice/just-malicious/from-c-with-inline-assembly-to-shellcode) by Steve Salinas (2023)
|
|
|
|
12. [How To Craft Your Own Windows x86/64 Shellcode with Visual Studio](https://xacone.github.io/custom_shellcode.html) by Yazid Benjamaa (2023)
|
|
|
|
13. [Modern implant design: position independent malware development](https://5pider.net/blog/2024/01/27/modern-shellcode-implant-design) by Paul Ungur (2024)
|
|
|
|
14. [From C to shellcode (simple way)](https://print3m.github.io/blog/from-c-to-shellcode) by Print3M (2024)
|
|
|
|
15. [relocatable](https://github.com/tijme/relocatable) by Tijme Gommers (2025)
|
|
|
|
16. [PIC Development Crash Course](https://player.vimeo.com/video/1100089433) by Raphael Mudge (2025) |