start() method and building your shellcode.
Overview
Creating custom shellcode involves:- Modifying the
start()method insrc/main.cc - Using the
symbol()function for raw strings - Calling resolved Windows APIs
- Building and testing your changes
The Entry Point
The main shellcode logic lives in thestart() method of the instance class:
src/main.cc
Using Raw Strings
The symbol() Function
Stardust provides thesymbol() template function for handling raw strings in position-independent code:
include/common.h
Using Strings
Always wrap string literals withsymbol():
symbol() function works with both narrow and wide strings:
Calling Windows APIs
Pre-Resolved APIs
APIs defined in the instance struct can be called directly:Runtime API Resolution
For APIs not in the instance struct, useRESOLVE_API:
src/main.cc
The RESOLVE_API Macro
RESOLVE_API is a wrapper that handles hashing and type casting:
include/resolve.h
- Hashes the function name at compile time
- Calls
resolve::api()to find the function - Casts the result to the correct function pointer type
Example Shellcode
Simple MessageBox
src/main.cc
Getting Process Information
Access PEB and TEB structures:include/native.h:
Loading and Calling Multiple APIs
Building Your Shellcode
Release Build
Build optimized shellcode without debug symbols:bin/stardust.x64.bin- 64-bit shellcodebin/stardust.x86.bin- 32-bit shellcode
Debug Build
Build with debug output enabled:DBG_PRINTFmacro outputntdll.DbgPrintAPI resolution- Larger binary size due to debug strings
Build Flags
The Makefile uses these optimization flags:-Os: Optimize for size-nostdlib: No standard library-fPIC: Position independent code-fno-exceptions: No C++ exceptions
Testing Your Shellcode
Using the Stomper Loader
Stardust includes a test loader that uses module stomping:Direct Execution
You can also execute shellcode directly:Common Patterns
Checking Shellcode Information
Access shellcode metadata from thebase struct:
base struct is populated in the constructor:
src/main.cc
Conditional Compilation
Use preprocessor directives for debug-only code:Error Handling
Always validate API calls:Best Practices
1. Always Use symbol() for Strings
Never use raw string literals:2. Check Return Values
Validate all API calls:3. Keep It Small
Minimize shellcode size:- Only resolve APIs you need
- Avoid large data structures
- Use compile-time computation
4. Test Both Architectures
Build and test both x86 and x64:Troubleshooting
Shellcode Crashes
- Ensure all strings use
symbol() - Check API resolution succeeded
- Validate module handles
- Enable debug build:
make debug
Position Independence Issues
If shellcode works in one location but not another:- Global variables may not be position-independent
- Check for hardcoded addresses
- Verify all data uses
symbol()or relative addressing
Size Issues
If shellcode is too large:- Build release mode:
make(notmake debug) - Remove unused APIs from instance struct
- Minimize string usage
- Check compiler flags
