WCC: Binary Unlinking and Runtime Manipulation on Linux
Hook
What if you could reverse the linker’s work—taking a compiled executable and transforming it back into a relocatable object file, without source code? The Witchcraft Compiler Collection makes this possible.
Context
Traditional compilation workflows are one-way streets: source code becomes object files, object files become executables, and there’s no turning back. Once an executable is linked, its relocations are resolved, symbols are stripped or hidden, and the binary becomes difficult to manipulate programmatically. Binary analysis tools like Ghidra and radare2 excel at disassembly and reverse engineering, but they don’t help you reuse executable code as a library or programmatically invoke functions from compiled binaries at runtime.
The Witchcraft Compiler Collection (WCC) attacks this problem from a different angle. Rather than focusing on reverse engineering for human understanding, WCC treats binaries as first-class programmable artifacts. It provides three core tools that manipulate ELF binaries at the format level: wld transforms executables into shared libraries, wcc ‘unlinks’ binaries back into relocatable object files, and wsh loads binaries into a Lua-scriptable shell that provides reflection-like capabilities for native code. This enables workflows that would otherwise require source code access or extensive manual binary patching.
Technical Insight
WCC’s architecture revolves around deep ELF format manipulation using libbfd for binary parsing and capstone for disassembly. Each tool in the collection tackles a specific transformation primitive.
The wld linker performs what the documentation calls ‘libification’—converting an ELF executable into a shared library by modifying ELF headers. Given an executable like /bin/ls, wld transforms it into a shared library:
# Copy the executable and transform it in-place
cp /bin/ls /tmp/ls.so
wld -libify /tmp/ls.so
# The executable is now a shared library
The -noinit flag allows you to skip constructor and destructor execution, useful when you want to cherry-pick specific functions without running the binary’s full initialization logic.
The wcc compiler performs the inverse operation—‘unlinking’ a binary back into a relocatable object file. When you run:
# Unlink /bin/ls into a relocatable object file
wcc -c /bin/ls -o /tmp/ls.o
# The output can be linked like any compiler-generated .o file
gcc /tmp/ls.o -o /tmp/ls.so -shared
wcc analyzes the ELF sections and reconstructs symbol information from the binary’s remaining metadata (dynamic symbols, debug info if present), generating a new ELF relocatable file with proper section headers. The README notes that rebuilding relocations is currently supported only for Intel ELF x86_64 binaries, though wcc can process binaries from other architectures using libbfd.
The wsh shell is where WCC’s design philosophy becomes most apparent. It’s an embedded Lua interpreter that loads binaries into its own address space, exposing their functions as callable objects. The README example demonstrates loading Apache:
# Load apache2 and invoke its functions from Lua
wsh /usr/sbin/apache2
Once loaded, you can call ap_get_server_banner() or other functions directly from the Lua REPL:
> a = ap_get_server_banner()
> print(a)
Apache/2.4.7
This provides reflection-like capabilities—runtime introspection of binary structure, dynamic function invocation, and memory inspection—without requiring debug symbols or source code. The shell also includes memory manipulation primitives: memory searching (grep() through loaded binary memory), symbol resolution (symbols(), functions()), and dynamic loading of additional binaries (loadbin()).
WCC uses libbfd as its parsing foundation, which supports PE and COFF files alongside ELF. However, the deep transformations (unlinking, relocation reconstruction) are ELF-specific and currently limited to x86_64. The architecture is source-independent and works purely on compiled artifacts.
Gotcha
WCC’s most significant limitation is architectural scope: relocation reconstruction only supports Intel x86_64 ELF binaries. While wcc will parse ARM, SPARC, or PE executables using libbfd, the README explicitly states that “rebuilding relocations is currently supported only for Intel ELF x86_64 binaries.” You can transform a PE into an ELF and invoke pure functions through wsh, but full unlinking with proper relocations requires x86_64.
The second major limitation is that WCC currently only works on ELF binaries for wld, though it can process executables “irrelevant of their architecture or operating system” to transform them into “non relocatable shared libraries.” The README notes that wld “can process ELF executables irrelevant of their architecture or operating system” including “Intel, ARM or SPARC executables from Android, Linux, BSD or UNIX operating systems.”
WCC assumes familiarity with ELF internals, linker behavior, and binary formats. There’s no GUI, and the failure modes can be cryptic. The wld -noinit flag demonstrates this—you need to understand constructor/destructor semantics to know when ignoring them is safe versus when it will break initialization-dependent code. This isn’t a tool for casual reverse engineering; it’s for developers who already understand binary formats and linking mechanics.
Verdict
Use WCC if you’re working with x86_64 Linux binaries and need to perform unconventional binary manipulation—converting executables to libraries for code reuse without source access, extracting relocatable objects from compiled binaries for custom linking workflows, or scripting interactions with native code through the Lua shell for runtime instrumentation. It’s particularly valuable for binary instrumentation research, creating composite binaries from multiple executables, or situations where you have compiled artifacts but no build system. Skip WCC if you need full cross-architecture support for relocation reconstruction (currently only x86_64 is supported for unlinking), want a polished reverse engineering toolkit with comprehensive documentation, or lack experience with ELF internals and linker mechanics. The tool requires deep understanding of binary formats and provides limited error messaging for troubleshooting.