Simple (Unwinding) Object Loader
This project extends Simple Object Loader to register stack unwinding data with Windows
Project Files
Notes
Win32 x64 stack unwinding (e.g., for exceptions) is table based. These tables are normally generated by the compiler and live in .pdata and .xdata PE sections. The .pdata section is an array of RUNTIME_FUNCTION structs that map the bounds of non-leaf functions and point to their unwind information. The .xdata section is a mix of UNWIND_INFO data (one per function) and UNWIND_CODEs which describe how to unwind a frame from anywhere in a function. The canonical source on all of this is x64 exception handling in Microsoft's documentation. As a researcher, you haven't truly lived and tasted excitement until you've spent days or weeks with this information.
Crystal Palace strips .pdata and .xdata from COFFs, because this information becomes stale after our program transforms. Crystal Palace has tools to generate this information. make object +unwind generates unwind data for PICOs. linkpost "section" "unwind" generates unwind data for PIC. And, make coff +unwind outputs COFF with re-generated .xdata and .pdata sections.
This example generates stack unwind data for its PIC and PICO components and uses RtlAddFunctionTable to register this information with the operating system. Try it out:
./link /path/to/simpleunwind/loader.spec demo/test.x64.o out.bin
Crystal Palace doesn't support exceptions. Restoring the unwind data doesn't change this. The goal of this feature is to aid experiments operationalizing stack unwind data to find alternatives to call stack spoofing.
Prepare for madness
I'd like to set some expectations. Win32 x64 stack walking is not a unified process, where different components of the system do things the same way. Data registered dynamically with RtlAddFunctionTable is stored and enumerated separately from module .pdata. And, some tools/libraries query this dynamically registered data. Others don't. klezVirus' Fantastic unwind information and where to find them discusses some of the lookup paths, caches, and other headaches involved with making this data tell one story.
As the person generating unwind information for your use, I most care that the data I'm giving you is sane and valid. To validate that RtlAddFunctionTable took and the current process can unwind stack, I put together a verify.spec configuration for this project:
./link /path/to/simpleunwind/loader.spec demo/test.x64.o out.bin @/path/to/simpleunwind/verify.spec
@verify.spec inserts an int3 before a reference to USER32$MessageBoxA and sets up a VEH to OutputDebugStringA the call stack when the break is hit. I've also used WinDbg to validate the same data.
I've also spent some quality time with SystemInformer to "see" the effects of RtlAddFunctionTable. SystemInformer calls StackWalkEx to dump a call stack from a remote process. To lookup dymamic function tables (e.g., unwind data registered with RtlAddFunctionTable), SystemInformer calls NTDLL!GetFunctionTableListHead in the local process to find the head of this dynamic function table list. It then walks this list in the remote process, via ReadProcessMemory, attempts to find the right RUNTIME_FUNCTION, and from there the .pdata. If all of this works, everything is great, and you see a happy call stack with the dynamically registered data. If it doesn't, you get a broken call stack or an infinite frame gathering loop. I called a hard stop on this debugging rabbit hole to ship code. If I learn there's a bug on my end, I'll address it and update these notes. I didn't see this behavior with a new thread.

I also spent some time with ProcMon to view stack traces connected to different events. This dynamic function table data does not seem to change how ProcMon presents its stack traces.
RtlAddFunctionTable seems a good "Hello World" for this data, but I am not seeing it as a robust mechanism to use and find impacts with unwind data.
Other uses
Crystal Palace has two other demonstrations that use its generated unwind data.
COFF Mixing uses Crystal Palace's features to disguise capability and output a link-ready COFF with .xdata/.pdata sections. The benefit here is there's no need for fancy memory evasions, as the capability is not injected into memory. This is a great example of playing within the system's rules/expectations rather than fighting them.
Module Stomping demonstrates a module stomping PICO loader. This example stomps a temporary module with Crystal Palace's generated unwind data. This gives us the walkable stacks without the same "maybe it works?" of dynamically registered data.
The big idea: The value of unwind data is in the way that you use it.
Frame Pointers - Recommended
I recommend you compile PIC with -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer to restore the framepointer in your programs. Crystal Palace's unwind generator expects fixed stack frames or else it requires a frame pointer. Crystal Palace's PIC ergonomic tools (e.g., dfr, fixbss) will make modified functions dynamic.
Conversation
- A deep dive into modern Windows Structured Exception Handler (SEH) (2024) by Elma walks through how stack unwinding for exceptions works
- Dreamwalkers (2025) by Maxime Dcb discusses RtlAddFunctionTable in a module stomping context
- Fantastic unwind information and where to find them (2026) by klezVirus surveys different possibilities with unwind data. The post also describes two parallel unwind data resolution paths for dynamic code and module data and fills in some information about why one vs. the other fails.
- Unwind Data Can't Sleep - Introducing InsomniacUnwinding (2026) by Lorenzo Meacci questions why we're spoofing call stacks, when we have perfectly good unwind data in our shellcode runner or stomped module. This shows how sleep masking TTPs can use this data.
License
This project is licensed under the BSD License.