DBG_PRINTF macro and Windows DbgPrint API. This guide shows you how to debug your shellcode effectively.
Debug Mode
Enabling Debug Mode
Compile with debug symbols enabled:-D DEBUG compiler flag, which:
- Enables the
DBG_PRINTFmacro - Resolves
ntdll.DbgPrintAPI - Includes debug strings in the binary
- Increases binary size
Build Output Comparison
Release mode (make):
make debug):
The DBG_PRINTF Macro
Definition
TheDBG_PRINTF macro is defined in include/common.h:
include/common.h
- Calls
ntdll.DbgPrintwith formatted output - Prepends
[DEBUG::function::line]prefix - Uses
symbol()for position-independent strings - Supports printf-style formatting
- Compiles to no-op (empty statement)
- Zero overhead
- No strings in binary
DbgPrint API Resolution
In debug mode,ntdll.DbgPrint is automatically resolved:
include/common.h
Using DBG_PRINTF
Basic Usage
Formatting Specifiers
Supports standard printf format specifiers:Real Examples from main.cc
Loading a Module
src/main.cc
Process Information
src/main.cc
Shellcode Metadata
src/main.cc
Viewing Debug Output
Using DebugView
-
Download DebugView:
- Get from Sysinternals
- Free tool from Microsoft
-
Run DebugView:
-
Enable Kernel Capture:
- Capture → Capture Kernel
- Capture → Capture Global Win32
-
Run Your Shellcode:
- View Output: Debug messages appear in real-time in DebugView
Example DebugView Output
Using WinDbg
You can also view DbgPrint output in WinDbg:Common Debugging Scenarios
Debugging API Resolution
Debugging Module Resolution
Add debug output to the constructor:src/main.cc
Debugging String Issues
Check if strings are correctly resolved:Debugging Memory Issues
Debug vs Release Differences
Code Size
| Build | x64 Size | x86 Size |
|---|---|---|
| Release | ~750 bytes | ~670 bytes |
| Debug | ~1200 bytes | ~1100 bytes |
Performance
- Debug builds have minimal performance impact
DBG_PRINTFcalls add slight overhead- String formatting takes CPU cycles
Features
| Feature | Release | Debug |
|---|---|---|
DBG_PRINTF | No-op | Active |
ntdll.DbgPrint | Not resolved | Resolved |
| Debug strings | Excluded | Included |
| Binary size | Minimal | Larger |
| Evasion | Better | Worse |
Advanced Debugging
Conditional Debug Output
Add custom debug macros:Debug Levels
Implement debug levels:Hex Dump Function
Create a debug hex dump helper:Troubleshooting
No Debug Output
Problem: No output in DebugView Solutions:- Ensure compiled with
make debug(notmake) - Check DebugView is capturing kernel messages
- Run DebugView as Administrator
- Verify
ntdll.DbgPrintresolved successfully - Check shellcode is actually executing
Garbled Output
Problem: Debug output shows garbage characters Solutions:- Ensure using
symbol()for all string literals - Check format specifiers match argument types
- Verify null-terminated strings
- Check for buffer overflows
Missing Debug Symbols
Problem: Function names missing from output Solutions:- Compile with debug mode:
make debug - Check
__FUNCTION__macro is supported - Verify compiler flags include debug info
Crashes with Debug Build
Problem: Shellcode crashes only in debug mode Possible causes:- Stack space exhausted by debug strings
- Debug string references not position-independent
symbol()not used for debug format strings
DBG_PRINTF macro already uses symbol() internally, so this should be rare.
Best Practices
1. Always Use symbol() in DBG_PRINTF
The macro handles this for you, but if you create custom debug macros:2. Remove Debug Output for Production
Always build release mode for production:3. Check Resolution Before Use
4. Use Meaningful Messages
5. Include Context
TheDBG_PRINTF macro automatically includes function name and line number:
