Lion Cipher Lab
Take an ELF binary apart and watch dynamic linking happen
- Foundations
- Advanced
- ~90 min
- Free · no account required
Why it matters
Every compiled program you will ever analyze -- a piece of malware, a CTF binary, a service you are defending, a crash you are debugging -- arrives as an ELF file, and the single biggest gap between a beginner and someone who can read one is understanding what happens between `./program` and `main()` actually running. For a dynamic executable that gap is enormous: the kernel doesn't run your program at all, it runs the dynamic linker, which finds libc and every other shared library, maps them in, and rewrites a table of pointers so that your `printf` call lands in the real libc code. Miss…
What you'll learn
- reading an ELF header (readelf -h): class, type (EXEC vs DYN/PIE), entry point, and where the program/section header tables live
- the segments-vs-sections split: why the LOADER maps PT_LOAD segments and never reads section headers, and why .bss (NOBITS) takes zero bytes on disk but real bytes in memory
- the dynamic section (readelf -d): NEEDED libraries, symbol versioning, RUNPATH, and the FLAGS/FLAGS_1 that record RELRO + BIND_NOW hardening
- how a call to a libc function actually reaches libc: the PLT stub, the .got.plt slot, and the JUMP_SLOT relocation that fills it in
- watching ld.so work with LD_DEBUG=libs,bindings,reloc, and telling LAZY binding (a GOT slot pointing back into its own PLT stub) from EAGER binding (LD_BIND_NOW / the distro default)
- function interposition with LD_PRELOAD: writing a -shared -fPIC shim that overrides malloc/free, its legitimate uses (profiling, fault injection, sandboxing) and its offense use (userland hooking/persistence)
- why secure-execution mode strips LD_PRELOAD for setuid/file-capability binaries, and how RELRO + BIND_NOW harden the GOT
- runtime loading with dlopen/dlsym, and the named failure modes: ldd "not found", symbol-version mismatch, and writable-RUNPATH hijack
Steps in this lab
- The one idea: the kernel does not run your dynamic program -- it runs a linker that wires it up first
- Get a Linux x86-64 shell and the binutils toolchain
- Compile one program two ways: dynamic (default) vs -static
- Read the ELF header: the 64-byte cover page every ELF starts with
- Segments vs sections: the loader reads segments, the linker reads sections -- and .bss is a beautiful lie
- Symbols and the dynamic section: which libraries, which functions, which versions
- The PLT and GOT: how a call to puts actually reaches libc
- Watch the dynamic linker work: ldd and LD_DEBUG
- Lazy vs eager binding: catch a GOT slot pointing back at its own PLT stub
- The payoff: function interposition with LD_PRELOAD
- The programmatic side: load a plugin at runtime with dlopen/dlsym
- Named failure modes: "not found", version mismatch, and RUNPATH hijack
- The judgment: what you can now read, and the labs this unlocks