Skip to content

File Infector Analysis

File infectors are the oldest malware architecture still alive: viruses that modify host executables — appending, prepending, or hiding their code inside ordinary programs — so that every infected file becomes a carrier. The classic families (Sality, Virut, Neshta, Ramnit [verify current prevalence]) still infect fleets via USB shares and network drives because the mechanism needs no exploit: users run programs, infected programs run the virus first. This module dissects the PE infection mechanics — entrypoint surgery, section games, slackspace cavity infections, Entry Point Obscuring — and the analysis workflow that reverses them: how to find the virus inside a legitimate-looking host, extract it, and write detections that survive the host’s own updates. The simulator + detector pair you build here also teaches the general skill behind M7/M8 (rootkits patch code too) and M14 (packers are prepend-infectors by another name).

A file infector (parasitic virus) inserts its code into host executables such that running the host runs the virus — which then replicates by infecting other executables. The infection strategies, in PE terms:

StrategyMechanismTell
Append + entrypoint redirectVirus body appended in new/last section; AddressOfEntryPoint → virusEP inside last section
PrependVirus written before original image; entry adjustedFirst section oversized, shifted headers
Cavity infectionCode written into slack space (alignment padding)EP unchanged or orphaned; slackspace entropy
OverwriteHost code destroyed, virus written over itHost breaks; crude era
EPO (Entry Point Obscuring)Virus patches a called function mid-host, not the EPNo EP anomaly; call-site patching
APPEND + REDIRECT (the canonical infection):
BEFORE: AFTER:
┌──────────────┐ ┌──────────────┐
│ PE headers │ │ PE headers │◄─ EP ──┐
│ .text │ │ .text │ │
│ .rdata │ │ .rdata │ │
│ .data │ │ .data │ │
└──────────────┘ │ .virus ◄────│────────┘
(host only) │ [virus body]│ new section:
└──────────────┘ host code runs
(host + parasite) after virus done

The infection marker completes the cycle: the virus stamps a byte/field in the header (an unused PE field, a section name, a checksum) so it never re-infects its own work — and that marker is, symmetrically, the fastest static tell for analysts.

For the analyst: file infectors poison everything they touch — an incident’s “legitimate” binaries are compromised witnesses, and cleaning means distinguishing virus code from host code inside thousands of files. The skill of carving a parasite out of a host (where does the host end, where does the parasite begin, what did it patch?) is the foundational RE dissection exercise, and it generalizes to every code-patching threat (kernel rootkits M8, bootkits M9, hooking malware M7). For the defender: infected-file detection is a well-posed structural problem (EP anomalies, section games, slackspace entropy, markers) that YARA and imphash handle well — file infectors are the malware family your static tooling is best at catching, which makes them the perfect teaching case for detection engineering. For the historian-turned-operator: understanding prepend-infection explains packers (M14) and droppers — every “stub + payload” architecture is an infection mechanic with a delivery chain.

An append-infection performs this surgery on a target PE:

  1. Read the host: DOS header, NT headers, section table, SizeOfImage.
  2. Add a section (.virus-named or innocuous): extend the section table (room permitting — the classic constraint: section table must stay before SizeOfHeaders; without room, infectors use header-cavity or EPO), compute new VirtualAddress/SizeOfRawData.
  3. Write the virus body into the new section: decryptor stub first (if packed), payload after.
  4. Patch AddressOfEntryPoint → new section, saving the original EP inside the virus body (the return address).
  5. Fix SizeOfImage, set section characteristics (0xE0000040 — code, executable, readable, writable [verify flag combination per loader strictness]), and stamp the marker.
  6. At runtime: virus runs first (decrypts/spreads), then restores execution to the host — the host behaves normally, which is why infected hosts persist for months.

EPO complicates step 4: instead of the EP, the virus patches a called function’s prologue somewhere in the host (a function that runs early but isn’t the entry) — defeating the “EP in last section” heuristic. Sality is the canonical modern example: EPO + polymorphic decryptor + anti-AV routines, still circulating [verify current prevalence]. Analysis consequence: the virus may be found anywhere; the reliable anchor is the patch site (a function whose first bytes jump into a data section).

Slackspace and the cavity infection

Between a section’s VirtualSize (what the loader maps) and SizeOfRawData (what the file holds) lies alignment padding — slack. A cavity infection writes code into slack without touching any header field except possibly the EP, leaving section tables, sizes, and timestamps untouched. Detection is therefore negative-space: compute per-section slack regions and scan them for executable content — a section whose padding contains code where it should contain zeros. It is the cleanest infection against naive tooling and the reason serious PE scanners (and this module’s detector) do slackspace analysis instead of trusting the section table.

The analysis workflow: find, carve, verify

Section titled “The analysis workflow: find, carve, verify”

Given a suspect host executable:

  1. Structural scan: section table vs file layout — a section whose raw data extends past the file, an EP pointing into a data-like section, a new section with no name or a random name.
  2. Marker check: known family markers (Sality’s, Neshta’s — community-documented [verify current markers per family]) in header fields.
  3. Slackspace sweep: entropy-scan alignment padding for executable bytes.
  4. Disassembly from the EP: does it reference the saved original EP? Does it enumerate directories and infect? The replication loop — FindFirstFile/FindNextFile → filter (.exe, .scr) → open → infect → marker — is the virus’s signature behavior in static code.
  5. Carve: extract the virus body (section or slackspace range), reconstruct as a standalone sample, and verify: does it infect a sacrificial lab file? (The replication test is the proof of classification — “infector” is a behavioral claim.)
  6. Clean: rebuild the host — restore the original EP from the virus body’s save slot, truncate the parasite section, fix SizeOfImage, and verify the host still runs and hashes clean against its original (if available).
Analysis stepWhat it findsTool classNotes
Section/EP auditAppend infectionsPE parser (this module’s)EP-in-last-section heuristic
Marker scanKnown familiesYARA/parseCommunity-documented markers
Slackspace entropyCavity infectionsPE parserNegative-space analysis
Call-site diffEPO patchesDisassembler + compareNeeds clean reference
Replication testBehavioral proofLab VM + sacrificial filesThe classification proof
Host rebuildRemediationCarve + header restoreVerify by run + hash

infectsim.py — a lab PE infector simulator: takes a benign PE you own, performs the canonical append-infection (new section, EP redirect, marker), and emits the infected file — purely so you can study the surgery on known ground truth. It refuses to touch anything but the file you name, writes nothing without --out, and exists to generate lab samples for the detector below.

{1-6}
"""
infectsim.py — lab PE infection simulator (append + EP redirect).
Generates INFECTED LAB SAMPLES from PEs you own, for detector study.
Usage:
python infectsim.py host.exe --out infected.exe # perform surgery
python infectsim.py host.exe --inspect # show structure
Pure stdlib. Python 3.8+. Run only on files you own.
"""
import struct
import sys
from pathlib import Path
VIRUS_SECTION = b".labvirus"
MARKER = b"LABINF" # infection marker (unused header space)
def parse_headers(data: bytes) -> dict:
e = struct.unpack_from("<I", data, 0x3C)[0]
nsec = struct.unpack_from("<H", data, e + 6)[0]
osz = struct.unpack_from("<H", data, e + 20)[0]
opt = e + 24
ep = struct.unpack_from("<I", data, opt + 16)[0]
sec_align = struct.unpack_from("<I", data, opt + 32)[0]
file_align = struct.unpack_from("<I", data, opt + 36)[0]
sizeofimage = struct.unpack_from("<I", data, opt + 56)[0]
s0 = e + 24 + osz
sections = []
for i in range(nsec):
o = s0 + i * 40
name = data[o:o + 8].rstrip(b"\x00")
vs, va, rs, ra = struct.unpack_from("<IIII", data, o + 8)
chars = struct.unpack_from("<I", data, o + 36)[0]
sections.append({"off": o, "name": name, "vsize": vs,
"vaddr": va, "rsize": rs, "raddr": ra,
"chars": chars})
return {"e": e, "nsec": nsec, "opt": opt, "ep": ep,
"sec_align": sec_align, "file_align": file_align,
"sizeofimage": sizeofimage, "sections": sections,
"sec_table_off": s0, "sec_table_end": s0 + nsec * 40}
def align_up(v: int, a: int) -> int:
return (v + a - 1) // a * a
def infect(host: bytes, virus_body: bytes) -> bytes:
h = parse_headers(host)
# 1. new section table entry (append after existing)
new_vaddr = align_up(max(s["vaddr"] + s["vsize"]
for s in h["sections"]), h["sec_align"])
new_raddr = align_up(max(s["raddr"] + s["rsize"]
for s in h["sections"]), h["file_align"])
entry = {
"name": VIRUS_SECTION,
"vsize": len(virus_body), "vaddr": new_vaddr,
"rsize": align_up(len(virus_body), h["file_align"]),
"raddr": new_raddr,
"chars": 0xE0000040, # code|exec|read|write (lab default)
}
entry_bytes = (entry["name"].ljust(8, b"\x00")
+ struct.pack("<IIIIII", entry["vsize"],
entry["vaddr"], entry["rsize"],
entry["raddr"], 0, entry["chars"]))
out = bytearray(host)
# 2. extend section table (check room: headers region)
if h["sec_table_end"] + 40 > h["e"] + 0x400:
raise RuntimeError("no room in section table (lab: abort)")
out[h["sec_table_end"]:h["sec_table_end"]] = entry_bytes
# 3. patch counts + EP + SizeOfImage
struct.pack_into("<H", out, h["e"] + 6, h["nsec"] + 1)
struct.pack_into("<I", out, h["opt"] + 16, new_vaddr) # EP -> virus
struct.pack_into("<I", out, h["opt"] + 56,
align_up(new_vaddr + len(virus_body),
h["sec_align"]))
# 4. stamp marker into unused header space (after section table)
marker_off = h["sec_table_end"] + 40
out[marker_off:marker_off + len(MARKER)] = MARKER
# 5. append virus body at its file offset
out.extend(b"\x00" * (new_raddr - len(out)))
out.extend(virus_body)
return bytes(out), {"orig_ep": h["ep"], "new_ep": new_vaddr,
"marker_off": marker_off}
# the "virus body" for labs: a stub that just returns to the saved EP
STUB = bytes([
0x55, # push rbp
0x48, 0x89, 0xE5, # mov rbp, rsp
0x90, 0x90, 0x90, 0x90, # (lab: analysis marker NOPs)
0x5D, # pop rbp
0xC3, # ret
])
def inspect(path: str):
data = Path(path).read_bytes()
h = parse_headers(data)
print(f"machine sections: {h['nsec']}, EP={h['ep']:#x}")
for s in h["sections"]:
print(f" {s['name'].decode(errors='replace'):<10} "
f"va={s['vaddr']:#010x} rs={s['rsize']:<8} "
f"ra={s['raddr']:#010x} chars={s['chars']:#010x}")
if MARKER in data[:0x400]:
print(f" MARKER {MARKER!r} present -> infected (by lab tool)")
if __name__ == "__main__":
if "--inspect" in sys.argv:
inspect(sys.argv[sys.argv.index("--inspect") + 1])
sys.exit(0)
host_path = Path(sys.argv[1] if len(sys.argv) > 1 else "host.exe")
out_path = Path(sys.argv[sys.argv.index("--out") + 1]
if "--out" in sys.argv else "infected.exe")
info = infect(host_path.read_bytes(), STUB)
inf_bytes, meta = info
out_path.write_bytes(inf_bytes)
print(f"[lab] infected {host_path} -> {out_path}")
print(f" original EP: {meta['orig_ep']:#x}")
print(f" new EP : {meta['new_ep']:#x} (.labvirus)")
print(f" marker : {MARKER!r} at {meta['marker_off']:#x}")
  • parse_headers (lines 17–38) — the surgery’s map: every field the infection touches (section table bounds, EP, alignments, SizeOfImage), parsed once. The sec_table_end computation matters: appending a section entry requires room between the table and SizeOfHeaders — the constraint real infectors solve with cavity/EPO when it’s absent.
  • infect (lines 45–88) — the canonical surgery in six numbered steps: compute new section VA/RA from existing maxima, emit the 40-byte section entry, patch NumberOfSections/EP/SizeOfImage, stamp the marker, append the body file-aligned. Every patch is reversible because the original EP is derivable — the property real viruses rely on (they store it in the body) and cleaners exploit (recover it from the body).
  • STUB (lines 91–98) — the lab “virus”: push/pop/ret with NOP markers. It does nothing malicious by design — the point is the surgery, and the stub keeps the lab sample loadable (the host runs normally; the virus section is visited first).
  • inspect (lines 101–112) — the analyst’s first look: section table dump + marker check. Run it on host and infected files and diff — the structural delta is the infection.

In the wild: the infector lands (mail, USB, drive-by), runs once, then enumerates executable files (local dirs, network shares, removable drives), infects each (strategy per family), stamps its marker, and patches hosts to spawn it first. Users keep running “their” programs; each run re-arms the virus. Detection catches the surgery (this module’s structural tells), cleaning requires the reverse surgery (restore EP, truncate, rebuild), and the incident’s blast radius is measured in files, not hosts — a file infector incident is “every executable touched by the infected share,” which is why share/USB hygiene (BT-modules) is the practical control.

  1. Delivery — one dropper execution (mail/USB/share) seeds the infector onto a host
  2. Enumeration — FindFirstFile/FindNextFile sweeps executables: local dirs, shares, removable drives
  3. Infection — per-target surgery: section add or cavity, EP/EPO patch, marker stamp
  4. Persistence — the fleet itself: every infected file re-runs the virus on execution (no other persistence needed)
  5. Payload — the virus’s actual job: downloader (fetch stage two), backdoor, or pure replication (era-dependent)
  6. Detection — structural tells surface: EP anomalies, markers, slackspace entropy, family YARA
  7. Remediation — reverse surgery at scale: restore EPs, truncate parasites, or reimage; verify by hash + run

Goal: infector_detect.py — the analyst-side scanner: runs the structural detection workflow (EP-location heuristic, marker scan, slackspace entropy sweep, section-name anomalies, replicated-section detection) over a file or directory, and emits a triage report classifying each PE as clean/suspicious/infected-lab. ~150 lines, stdlib, pairs with infectsim.py’s output as ground truth.

Design decisions: (1) structural heuristics with reasons — every finding cites the field it read (teachable output, tunable thresholds); (2) slackspace analysis as the flagship check (the cavity-infection catcher naive tools skip); (3) lab-marker whitelist so infectsim.py’s own samples classify confidently (the ground-truth loop).

{1-3}
"""
infector_detect.py — PE file-infector structural scanner.
Usage: python infector_detect.py <file-or-dir> [--json out]
Pure stdlib. Python 3.8+. Findings cite the evidence that fired.
"""
import argparse
import json
import math
import struct
import sys
from pathlib import Path
from infectsim import parse_headers, MARKER, VIRUS_SECTION
def shannon(data: bytes) -> float:
if not data:
return 0.0
freq = [0] * 256
for b in data:
freq[b] += 1
n = len(data)
return -sum((f / n) * math.log2(f / n) for f in freq if f)
def scan_pe(path: Path) -> dict:
data = path.read_bytes()
findings = []
h = parse_headers(data)
# --- check 1: EP inside last section (append-infection tell) ---
last = h["sections"][-1]
if h["sections"] and last["vaddr"] <= h["ep"] < \
last["vaddr"] + max(last["vsize"], 1):
if last["name"] != b".text":
findings.append(
f"EP {h['ep']:#x} inside last section "
f"{last['name'].decode(errors='replace')!r} "
"(append-infection shape)")
# --- check 2: lab marker (ground truth from infectsim) ---
if MARKER in data[:0x400]:
findings.append(f"lab marker {MARKER!r} present "
"(infected by infectsim.py)")
if VIRUS_SECTION.lstrip(b"\x00") in data[:0x400]:
findings.append("lab virus section name present")
# --- check 3: slackspace executable content (cavity tell) ---
for s in h["sections"]:
slack_start = s["raddr"] + min(s["rsize"], s["vsize"])
slack_end = s["raddr"] + s["rsize"]
if slack_end <= slack_start:
continue
slack = data[slack_start:slack_end]
if len(slack) > 64 and shannon(slack) > 6.0:
findings.append(f"section {s['name'].decode(errors='replace')} "
f"slackspace entropy "
f"{shannon(slack):.2f} over "
f"{len(slack)} bytes (cavity shape)")
# --- check 4: nameless / random-name trailing section ---
if h["sections"]:
nm = last["name"]
if nm == b"\x00" * 8 or (nm[:1].isalpha() is False
and nm != b""):
findings.append("trailing section with anomalous name")
verdict = ("infected-lab" if any("lab" in f for f in findings)
else "suspicious" if findings
else "clean-by-structure")
return {"file": str(path), "verdict": verdict,
"findings": findings, "ep": hex(h["ep"]),
"sections": len(h["sections"])}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("target")
ap.add_argument("--json", dest="out")
args = ap.parse_args()
t = Path(args.target)
files = ([p for p in t.rglob("*") if p.is_file()]
if t.is_dir() else [t])
report = []
for f in files:
try:
if f.read_bytes()[:2] != b"MZ":
continue
r = scan_pe(f)
report.append(r)
print(f"[{r['verdict']:<18}] {f}")
for finding in r["findings"]:
print(f" - {finding}")
except Exception as e:
print(f"[error] {f}: {e}")
if args.out:
Path(args.out).write_text(json.dumps(report, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())
  • Check 1 (lines 28–35) — the EP heuristic with its precision guard: EP-in-last-section is only interesting when the last section isn’t .text (legitimate linkers put EP in code). The finding cites the EP value and section name — evidence, not vibes.
  • Check 3 (lines 43–53) — the cavity catcher: for each section, compute the slack range (raddr + min(rsize,vsize) → raddr + rsize), entropy-scan it. Padding should be zeros (entropy 0); executable junk there (entropy > 6) is the cavity-infection shape. This check finds what header-trusting tools miss.
  • Verdict layering (lines 62–64) — lab-ground-truth beats heuristics (infected-lab), findings promote to suspicious, silence is clean-by-structure — deliberately not “clean” (structural cleanliness is not behavioral innocence; detonation still applies).
$ python infectsim.py benign.exe --out infected.exe
[lab] infected benign.exe -> infected.exe
original EP: 0x14a0
new EP : 0x6000 (.labvirus)
marker : b'LABINF' at 0x2c8
$ python infector_detect.py .
[infected-lab ] infected.exe
- EP 0x6000 inside last section '.labvirus' (append-infection shape)
- lab marker b'LABINF' present (infected by infectsim.py)
[clean-by-structure] benign.exe
# the ground-truth loop: simulate -> detect -> verify the tells fire

Then the real drill: run the detector over C:\Windows\System32 (or /usr/bin equivalents ported) and study the false positives — EP-in-last-section fires on legitimate packers/installers; the tuning lesson (which heuristic needs which guard) is the actual skill, practiced on ground truth you control.

The scanner is read-only — benign. The defender content: file-infector detection belongs at share and endpoint boundaries (Sysmon EID 11 file-writes followed by EID 1 execution of the same file by new parents [verify joins]), YARA rules on family markers/stubs (community-maintained), and quarantine-then-rebuild policy (cleaning infected files is a specialist task; reimage is the operational default). For BT8’s analysis pipeline: the simulate→detect→carve→verify loop this module builds is the file-infector section of the malware-analysis workbench — reuse it for M7/M8 patch analysis.

  1. EPO detector: disassemble called-function prologues and flag jumps into non-code sections (needs the S2 disassembler integration).
  2. Cavity extraction: dump flagged slack regions as standalone files for RE.
  3. Clean-rebuild mode: restore original EP (from lab metadata or virus-body save slot) and truncate the parasite — the remediation half.
  4. Family-marker DB: load community marker sets (YARA-backed) into the marker check.
  5. Mount-aware sweep: scan removable drives on mount events — the USB-borne infector’s real entry vector.
SourceID / SignalWhat firesNotes
Defender AV1116/1117Virus-family detections (Sality/Neshta-class names)The scoring layer’s home turf
Sysmon11 → 1FileCreate then execution of newly-written PEsInfection loop’s shadow
Sysmon15FileCreateStreamHash (ADS/motw-adjacent)Zone.Identifier gaps on shares
YARAfamily rulesMarker/stub matches at scan pointsCommunity-maintained sets
EDR behavioral—Mass-PE-write bursts by one processThe enumeration stage is loud
WazuhAV + Sysmon rules [verify IDs]Share/USB infection correlationThe entry-vector control
  • EPO over EP-redirect: patching a called function defeats the EP heuristic — but every EPO site is a patch that slackspace/memory comparison can find; no structural evasion is free.
  • Marker discipline vs detection: markers prevent re-infection but are static gold; real families use header fields that look plausible — and analysts index them anyway. The lesson cuts both ways: your marker is someone’s YARA string.
  • Cavity infections hide from tables, not from entropy: slack-space code is entropy-visible; the counter-counter is zero-padding your slack and hiding code elsewhere — each move costs another artifact.
  • Polymorphic decryptors (Sality-class): mutate the stub per infection so stub-YARA decays — the classic arms race, and the reason emulator/memory scanning (E1’s layers) replaced pure static.
  • Host-signature preservation: infectors that keep the host’s hash-able regions intact (cavity) survive hash-allowlisting — and break the “known-good file” model that whitelisting estates (E13’s Bit9 lineage) run on, which is precisely why infectors are whitelisting’s nightmare class.
  • Enumeration noise is the tell: FindFirstFile sweeps over shares generate file-open bursts — behavior the file server sees regardless of static stealth.

⚠️ What caught me: my lab infector stamped its marker in a “unused” header field that I’d verified was unused on my build toolchain — and corrupted builds made by a different linker that used the same field for something real. Infected files stopped running, which made the infection very visible. The lesson: “unused” is a property of a toolchain, not a format — every PE field you repurpose needs a compatibility survey across the hosts you’ll touch, or your stealth becomes a crash log.

  1. Run infectsim.py on a benign lab PE; --inspect both files; diff the section tables — the surgery, read field by field.
  2. Run infector_detect.py over the lab pair; confirm both tells fire on the infected file only; then break the EP heuristic’s guard and watch the false positive appear — heuristic tuning, practiced.
  3. Slackspace lab: hexdump the infected file’s slack regions; manually entropy-check one; then write the cavity extraction improvement and carve the region to a file.
  4. Replication test: in a snapshot VM, place the lab-infected file next to sacrificial copies and confirm the real-world flow (execution → enumeration → infection) — then map each step to the structural tells the detector uses.
  5. Family study: read a documented Sality/Neshta analysis (community writeups), and map its infection strategy onto this module’s table — then check which of its tells your detector implements and which it needs.

Technical: Walk the append-infection surgery field by field — which PE header fields change, and in what order must they be patched? Technical: What is slackspace, how do cavity infections use it, and why does header-trusting tooling miss them? Deep-dive: Design the EPO detection: what do you compare a called function’s prologue against, and what false-positive classes does the comparison face? Conceptual: “An infected fleet is persistence without persistence mechanisms” — unpack for both defender (detection surface) and operator (opsec cost). Scenario: An estate runs hash-allowlisting (E13’s strict mode). Why are file infectors its worst-case malware class, and what workflow catches them? Practical: Given infectsim.py’s infected output, write the clean-rebuild steps (fields to restore, ranges to truncate) and the verification for success.

M1 (Malware Classification) · M14 (Executable Packing Analysis) · S2 (Reverse Engineering) · BT8 (Malware Analysis)