Overview
The default Stardust implementation demonstrates the fundamental concepts of position-independent shellcode by displaying a MessageBox. This example covers module resolution from the PEB, dynamic library loading, API resolution, and working with raw strings.Source Code Walkthrough
Let’s examine the default implementation insrc/main.cc:
Entry Point and Initialization
src/main.cc
entry function is the shellcode entry point. It creates a stardust::instance object and calls its start method. The declfn attribute ensures the function is placed in the .text$B section for proper position-independent execution.
Constructor: Resolving Modules
src/main.cc
-
Base Address Calculation:
RipStart()andRipData()are assembly functions that return instruction pointer-relative addresses, allowing the shellcode to determine its location in memory. -
Module Resolution:
resolve::module()walks the PEB’sInLoadOrderModuleListto find loaded modules by hash:- Uses FNV-1a hashing at compile time via
expr::hash_string - Returns the base address of the module
- Case-insensitive matching
- Uses FNV-1a hashing at compile time via
-
API Resolution:
RESOLVE_IMPORT()macro automatically resolves all API pointers defined in the module struct by:- Iterating through struct members
- Calling
resolve::_api()for each function hash - Storing the resolved addresses back into the struct
The Main Logic
src/main.cc
Understanding Key Components
The symbol() Function
Thesymbol() function converts hardcoded strings to position-independent addresses:
include/common.h
symbol<const char*>("user32.dll")- Returns the runtime address of the string- Works by calculating the offset from a known position (RipData) to the string
- Essential for accessing any hardcoded data in position-independent shellcode
The RESOLVE_API Macro
include/resolve.h
- Takes a module base address
mand function names - Hashes the function name at compile time using
expr::hash_string - Resolves the function address by:
- Parsing the module’s PE export directory
- Iterating through exported function names
- Matching against the hash
- Returning the function address
- Casts the result to the correct function pointer type using
decltype
Debug Output
include/common.h
- Only active when compiled with
make debug - Uses
ntdll.DbgPrintfor kernel-level debug output - Automatically includes function name and line number
- All strings passed through
symbol()for position independence - View output with DebugView or kernel debugger
Complete Example
Here’s a minimal complete example:Building and Testing
Build in release mode:bin/stardust.x64.bin- 64-bit shellcodebin/stardust.x86.bin- 32-bit shellcode
Key Takeaways
- Module Resolution: Always resolve from PEB before using APIs
- String Handling: Use
symbol<>()for all hardcoded strings - API Resolution: Use
RESOLVE_API()macro for type-safe resolution - Error Checking: Always validate module and API resolution results
- Position Independence: All addresses must be calculated at runtime
Next Steps
- Learn about Loading Additional Libraries
- Explore Advanced Techniques
- Review the API Reference
