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).
Definition
Section titled “Definition”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:
| Strategy | Mechanism | Tell |
|---|---|---|
| Append + entrypoint redirect | Virus body appended in new/last section; AddressOfEntryPoint → virus | EP inside last section |
| Prepend | Virus written before original image; entry adjusted | First section oversized, shifted headers |
| Cavity infection | Code written into slack space (alignment padding) | EP unchanged or orphaned; slackspace entropy |
| Overwrite | Host code destroyed, virus written over it | Host breaks; crude era |
| EPO (Entry Point Obscuring) | Virus patches a called function mid-host, not the EP | No 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 doneThe 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.
Why It Matters
Section titled “Why It Matters”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.
Detailed Explanation
Section titled “Detailed Explanation”PE infection mechanics, precisely
Section titled “PE infection mechanics, precisely”An append-infection performs this surgery on a target PE:
- Read the host: DOS header, NT headers, section table,
SizeOfImage. - Add a section (
.virus-named or innocuous): extend the section table (room permitting — the classic constraint: section table must stay beforeSizeOfHeaders; without room, infectors use header-cavity or EPO), compute newVirtualAddress/SizeOfRawData. - Write the virus body into the new section: decryptor stub first (if packed), payload after.
- Patch
AddressOfEntryPoint→ new section, saving the original EP inside the virus body (the return address). - Fix
SizeOfImage, set section characteristics (0xE0000040— code, executable, readable, writable [verify flag combination per loader strictness]), and stamp the marker. - 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:
- 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.
- Marker check: known family markers (Sality’s, Neshta’s — community-documented [verify current markers per family]) in header fields.
- Slackspace sweep: entropy-scan alignment padding for executable bytes.
- 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. - 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.)
- 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).
Common Techniques
Section titled “Common Techniques”| Analysis step | What it finds | Tool class | Notes |
|---|---|---|---|
| Section/EP audit | Append infections | PE parser (this module’s) | EP-in-last-section heuristic |
| Marker scan | Known families | YARA/parse | Community-documented markers |
| Slackspace entropy | Cavity infections | PE parser | Negative-space analysis |
| Call-site diff | EPO patches | Disassembler + compare | Needs clean reference |
| Replication test | Behavioral proof | Lab VM + sacrificial files | The classification proof |
| Host rebuild | Remediation | Carve + header restore | Verify by run + hash |
Full Working Code
Section titled “Full Working Code”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 structurePure stdlib. Python 3.8+. Run only on files you own."""import structimport sysfrom 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 EPSTUB = 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}")Code Walkthrough
Section titled “Code Walkthrough”parse_headers(lines 17–38) — the surgery’s map: every field the infection touches (section table bounds, EP, alignments,SizeOfImage), parsed once. Thesec_table_endcomputation matters: appending a section entry requires room between the table andSizeOfHeaders— 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, patchNumberOfSections/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.
How It Reaches The Target
Section titled “How It Reaches The Target”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.
Exploitation Chain
Section titled “Exploitation Chain”- Delivery — one dropper execution (mail/USB/share) seeds the infector onto a host
- Enumeration — FindFirstFile/FindNextFile sweeps executables: local dirs, shares, removable drives
- Infection — per-target surgery: section add or cavity, EP/EPO patch, marker stamp
- Persistence — the fleet itself: every infected file re-runs the virus on execution (no other persistence needed)
- Payload — the virus’s actual job: downloader (fetch stage two), backdoor, or pure replication (era-dependent)
- Detection — structural tells surface: EP anomalies, markers, slackspace entropy, family YARA
- Remediation — reverse surgery at scale: restore EPs, truncate parasites, or reimage; verify by hash + run
Building Your Own Tool
Section titled “Building Your Own Tool”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 argparseimport jsonimport mathimport structimport sysfrom 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())Walkthrough
Section titled “Walkthrough”- 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 tosuspicious, silence isclean-by-structure— deliberately not “clean” (structural cleanliness is not behavioral innocence; detonation still applies).
Compilation and test procedure
Section titled “Compilation and test procedure”$ 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 fireThen 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.
Detection considerations
Section titled “Detection considerations”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.
Improvements to add
Section titled “Improvements to add”- EPO detector: disassemble called-function prologues and flag jumps into non-code sections (needs the S2 disassembler integration).
- Cavity extraction: dump flagged slack regions as standalone files for RE.
- Clean-rebuild mode: restore original EP (from lab metadata or virus-body save slot) and truncate the parasite — the remediation half.
- Family-marker DB: load community marker sets (YARA-backed) into the marker check.
- Mount-aware sweep: scan removable drives on mount events — the USB-borne infector’s real entry vector.
Detection — Defender View
Section titled “Detection — Defender View”| Source | ID / Signal | What fires | Notes |
|---|---|---|---|
| Defender AV | 1116/1117 | Virus-family detections (Sality/Neshta-class names) | The scoring layer’s home turf |
| Sysmon | 11 → 1 | FileCreate then execution of newly-written PEs | Infection loop’s shadow |
| Sysmon | 15 | FileCreateStreamHash (ADS/motw-adjacent) | Zone.Identifier gaps on shares |
| YARA | family rules | Marker/stub matches at scan points | Community-maintained sets |
| EDR behavioral | — | Mass-PE-write bursts by one process | The enumeration stage is loud |
| Wazuh | AV + Sysmon rules [verify IDs] | Share/USB infection correlation | The entry-vector control |
Evasion Techniques
Section titled “Evasion Techniques”- 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.
Lab Practice
Section titled “Lab Practice”- Run
infectsim.pyon a benign lab PE;--inspectboth files; diff the section tables — the surgery, read field by field. - Run
infector_detect.pyover 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. - 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.
- 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.
- 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.
Interview Questions
Section titled “Interview Questions”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.
Related Modules
Section titled “Related Modules”M1 (Malware Classification) · M14 (Executable Packing Analysis) · S2 (Reverse Engineering) · BT8 (Malware Analysis)