ElfPack: ELF Binary Section Docking for Stageless Payload Delivery

ElfPack: ELF Binary Section Docking for Stageless Payload Delivery

Highlights

  • Overview of payload bundling mechanisms: compilation, linking, and loading.
  • Binary compatibility and creation of loosely coupled payloads to their delivery mechanism.
  • Avoiding automatic memory loading of sections.
  • Use of structured section types.
  • In-field payload (re-) attachment to loaders via ELF sections. Bring your own payload.
  • Signature evasion with detached precompiled ELF sections.
  • Drive-by payload attachments to loaders with payload generation pipeline.
  • Creation of fat payload binaries and the case for the avoidance of binary packers.
  • Packing complex payloads.
  • Payload obfuscation and key protection options.
  • Static and dynamic payload loading tracing resistance. Binwalk and eBPF.

ELF Section Docking

So far we were able to create a section, and avoid loading it in memory by the OS loader. The section is effectively dormant in the ELF image at the moment. We will discuss how we load it a bit later. However, a more pressing question is the fact that we are still operating at the compiler and linker level, and a section is an object that gets woven into the structure of the final ELF, creating relationships and memory addresses from the loader code that references it’s content.

What if we were able to create an ELF section with embedded payload outside of the loader compilation workflow, and attach that section at a later time to the loader binary.

This would break the relationship of the loader code with the section interaction. Then we teach the loader how to find and load it’s foreign data section, effectively “docking” a payload to a loader in a loosely coupled manner.

Conceptually, our goals would be:

  • Loader should not be entangled with payload semantics
  • Loading and executing payload :
    • Without modifying loader code at all?
    • Without using OS loader ld.so (ELF loader) which is loading segments of payload in memory automatically.
  • In-field payload (re-)attachment.

The loader/payload (in section) relationship would now look like this:

We can then create an injector that will introduce a payload section to the loader without either one operating at the code level, only binary compatibility (and the loader being aware of how to load any payload section)

Some outcomes from such generic ELF section docking setup:

  1. Static Elf loader can then be shipped on its own, devoid of payloads, only mechanisms to load a section on-demand and bootstrap the payload from it.

  2. Payload can be packaged separately and bundled with a loader at any time as a static stage, or at a later time with an injector. The payload can often be encrypted, often be an ELF executable itself if needed, as long as the loader knows not the structure of the payload but it’s packaging capabilities only.

  3. Injector can broker attachment of sections from several binaries (dormant stages) to construct a section and inject into the loader.

  4. There are advantages for section level construction vs. packing of multiple resources in executable. There is no overhead on detection for packer processing and code. There are wins in terms of carrying multiple sections with other tooling that relies on in-memory launch and cannot be easily packed due to how packers have to extract binaries into filesystem. (Far binaries section further)

ELF docking components:

Let’s discuss the ELF section docking components in greater detail.

Sectional ELF injector:

Disposition: rear or in-field Advantages:

  • Agnostic loader to payload proxy
  • Streamlined payload generation pipeline
  • In field payload to loader attachment without compilers if needed

Sectional ELF loader:

Disposition: in-field Advantages:

  • Agnostic to attached payload
  • Loads full ELFs or shellcode (more possibilities) from reading and parsing its own binary.
  • If you need shellcode you can create a running elf out of it (e.g. Metasploit’s mettle)
  • Tracing does not see mprotect()’s
  • Airgapped separation between where the payload is and normal .DATA arrays.
  • This achieves abstraction for tracers.
  • Ability to accept and forward arguments to payloads themselves

Binary Payload:

Advantages:

  • Payload is a fully functional program with less constraints, data, segments LDD intact.
  • It can be uniquely obfuscated without regard to space (.NOTE records are variable size)
  • It can be extracted to FS or run as part of a table of contents (fat payload loaders).
  • It does not need to be relocated, can be chained to other loaders.
  • Example of cross-attachment and detection evasion: Loader A reads Loader B’s payload.

Evasion opportunities

Strengthening ELF sectional injector/packer:

  • XOR’d payload but AES may be implemented.
  • XOR key metadata stored out in out of band watermark.
  • XOR keys are not disclosed.
  • Additional XOR’d data obfuscation possible.

Strengthening ELF loader:

  • XOR’d payload default, but AES may be implemented.
  • XOR key metadata is mined in out of band watermark.
  • Separation of time of loader launch != time of payload load if needed.
  • Facility for daemonization (ability to work with userland exec amd memfd_create)
  • Possibility of evasion for payload entropy calculation and anti-carving: Binwalk does not see payload by default, can’t carve (example in demo: packing msfvenom’d payload)

Download & Use