Skip to content

About

Magic-number file-type identification and mismatch detection for defensive triage.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

5 Commits

Folders and files

Repository files navigation

🛂 Byte Bouncer

Checks every file's ID at the door — magic numbers over filenames.

python license tests interface transmits

Every file claims to be something. Byte Bouncer asks for ID.


An extension is a label the user (or an attacker) types. A magic number is byte-level evidence baked into the file's header. Byte Bouncer reads the header, identifies what the file actually is, and flags any file whose extension and content disagree — with special attention to the classic malware-delivery move: an executable wearing a document's clothing (invoice.pdf that is really a Windows PE, photo.jpg that is really an ELF binary, a plain ZIP renamed report.docx).

It is built for Kali Linux / Parrot OS triage, help-desk intake, and DLP investigations where a renamed executable or a fake image should be noticed before someone double-clicks their way into a bad afternoon.


Why it exists

Threat What the filename says What the bytes say Byte Bouncer verdict
Malware delivery cat.jpg Windows PE executable CRITICAL
Malware delivery notes.txt ELF / PE executable CRITICAL
Data exfil / evasion quarterly.jpg PDF document WARNING
Container fraud report.docx plain ZIP (no Word structure) WARNING
Corruption / truncation logo.png header too short to match WARNING
Everything normal memo.docx verified Word (OOXML) OK

The verdict is explainable — every finding carries the detected type, the expected type, a confidence level, a SHA-256, and a one-line reason you can paste into a ticket or hand to an auditor without interpretive dance.


How it works

                 ┌─────────────┐
  file  ───────▶ │ read header │  first 512 bytes only
                 └──────┬──────┘
                        ▼
                 ┌─────────────────┐
                 │ match signatures│  longest/strongest magic wins
                 └──────┬──────────┘
                        ▼
             is it a ZIP (PK\x03\x04)?
                        │ yes
                        ▼
                 ┌───────────────────────┐
                 │ peek inside (zipfile) │  central directory only — no bulk
                 │ docx? xlsx? jar? apk? │  extraction, one tiny member read
                 │ epub? odt?            │
                 └──────┬────────────────┘
                        ▼
                 ┌──────────────────────────┐
                 │ compare vs. extension    │  + lure/masquerade + double-ext
                 └──────┬───────────────────┘
                        ▼
       verdict: ok · warning · critical · error   (+ SHA-256, size, reason)
  1. Read the header. Only the first 512 bytes are read for identification, so scanning a directory of multi-gigabyte files is cheap.
  2. Match a signature. Byte Bouncer carries a hand-maintained database of ~50 magic numbers (signatures.py). When several match, the one with the most distinctive bytes wins. Signature length also drives confidence: an 8-byte PNG magic is high; a 2-byte MZ/BM is medium.
  3. Refine ZIP containers. docx, xlsx, pptx, jar, apk, epub, and OpenDocument files are all ZIPs. Byte Bouncer reads the ZIP central directory (no decompression of untrusted data) to tell a genuine Word document from a plain archive that was merely renamed.
  4. Judge the mismatch and its severity.
    • Executable / script content under a trusted, inert-looking extension (.jpg, .pdf, .txt, .docx…) → CRITICAL (masquerade).
    • Any other extension/header disagreement → WARNING.
    • A correctly-named executable with a fake inner extension (invoice.pdf.exe) → WARNING (double-extension lure).
    • Agreement, or an unknown-but-harmless file → OK.
  5. Hash for evidence. A streaming SHA-256 is recorded for every file (skip it with --no-hash for faster bulk triage).

Data model

The single result object is FileAnalysis:

field meaning
path, extension, size_bytes, sha256 file identity
detected_type, detected_mime, category what the bytes say
expected_types what the extension implies
mismatch (bool) do they disagree?
severity ok · warning · critical · error
confidence high · medium · low
reason plain-English explanation
error set when a file could not be read

Install

cd byte-bouncer
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -r requirements.txt  # flask, rich, pytest
pip install -e .                 # optional: exposes the `byte-bouncer` command
cp .env.example .env             # optional: for the web dashboard

There is no editable install required to run from source — set PYTHONPATH=src:

PYTHONPATH=src python -m byte_bouncer.cli scan README.md

CLI usage

Every command prints a compact branded header and honors --plain / --no-color / the NO_COLOR env var. When output is not a TTY (piped or redirected) it automatically degrades to plain, unstyled text. --json is always pure JSON with zero decoration, safe to pipe into jq.

scan — inspect a file or directory

byte-bouncer scan suspicious.jpg
byte-bouncer scan ~/Downloads --recursive
byte-bouncer scan ./intake --recursive --only-mismatches
byte-bouncer scan ./bulk --recursive --no-hash          # faster: skip SHA-256
byte-bouncer scan ~/Downloads --recursive --json > report.json
flag effect
-r, --recursive recurse through subdirectories
--json pure JSON to stdout
--no-hash skip SHA-256 hashing (faster)
--only-mismatches show only files that failed inspection
--follow-symlinks follow symlinks while recursing (off by default)
--plain / --no-color plain, unstyled text

Sample output:

┌─────────────────────────────────────────────────────────────────────────────┐
│  🛂 byte-bouncer                                                             │
└───────── checks every file's ID at the door — magic numbers over filenames ─┘
┌─────────┬──────────────────────┬──────┬─────────────────────────┬──────────────────────┬────────┐
│ Verdict │ File                 │ Ext  │ Detected                │ Expected             │  Conf  │
├─────────┼──────────────────────┼──────┼─────────────────────────┼──────────────────────┼────────┤
│ OK      │ badge.png            │ png  │ PNG image  image        │ PNG image            │  high  │
│ CRITICAL│ cat.jpg              │ jpg  │ Windows PE executable   │ JPEG image           │ medium │
│ OK      │ memo.docx            │ docx │ Word document (OOXML)   │ Word document (OOXML)│  high  │
│ CRITICAL│ notes.txt            │ txt  │ Windows PE executable   │ —                    │ medium │
│ WARNING │ quarterly_selfie.jpg │ jpg  │ PDF document  document  │ JPEG image           │  high  │
│ WARNING │ report.docx          │ docx │ ZIP archive  archive    │ Word document (OOXML)│  high  │
└─────────┴──────────────────────┴──────┴─────────────────────────┴──────────────────────┴────────┘
┌───────────── Possible masquerade — inspect the critical files ──────────────┐
│ 3 ok   2 warning   2 critical   0 error                                     │
└───────────────────────────── 7 files inspected ─────────────────────────────┘

Exit codes (unchanged contract, script-friendly):

  • 0 — scan completed, everything matched
  • 1 — operational error (bad path) or a file could not be read
  • 2 — at least one extension/header mismatch was found
# CI/pre-ingest gate: fail the pipeline if anything is disguised
byte-bouncer scan ./upload_staging --recursive --json > out.json || echo "flagged!"

signatures — list what Byte Bouncer can recognize

byte-bouncer signatures          # pretty table grouped by category
byte-bouncer signatures --json   # the whole database as JSON

serve — local upload dashboard

byte-bouncer serve                 # http://127.0.0.1:5061  (loopback only)
byte-bouncer serve --port 8080

A small Flask UI: upload one file, get the same verdict rendered as a web report.

Method Path Purpose
GET / upload form
POST /scan analyze an uploaded file and render a report

Safety & authorization

Byte Bouncer is a defensive tool for files on systems you own or are authorized to inspect. It never executes, opens, or renders the files it analyzes — it only reads bytes.

  • Loopback by default. serve binds to 127.0.0.1. Exposing it on a routable address is an upload endpoint on your network; only do so on hosts/networks you are authorized to run it on, behind your own authentication. Byte Bouncer warns loudly if --host is not loopback.
  • Hostile-input handling. Untrusted ZIPs are treated as hostile: only the central directory is listed and at most one tiny (<4 KB) mimetype member is read — archive contents are never extracted, so ZIP/decompression bombs are a non-event.
  • Safe file walking. Symlinks are skipped during recursion by default (a link can point outside the tree you meant to scan, or loop forever). Opt in with --follow-symlinks. Non-regular files (devices, FIFOs, sockets) are skipped.
  • Resilient scans. One locked or vanished file reports as an error row instead of aborting the whole run.
  • Upload limits. The dashboard clamps upload size (BYTE_BOUNCER_MAX_UPLOAD_MB, default 16 MB), sanitizes filenames, and inspects uploads only inside a temporary directory that is deleted immediately after.
  • No secrets in output. Byte Bouncer emits file metadata and hashes only; it never logs credentials or environment values.
  • --debug on serve enables Werkzeug's interactive debugger (arbitrary code execution by design) — use it on trusted local machines only. Off by default.

Testing

cd byte-bouncer
PYTHONPATH=src python -m pytest -q

The suite is fast and dependency-light (no network, no sleeps) and covers signature detection, container-aware ZIP inspection, severity classification, the masquerade / double-extension paths, symlink skipping, error resilience, and the CLI's JSON purity and exit-code contract.


Project layout

byte-bouncer/
  src/byte_bouncer/
    signatures.py   # magic-number database + categories
    analyzer.py     # header reading, ZIP refinement, mismatch & severity logic, hashing
    ui.py           # shared rich Theme + Console + banner (suite look)
    cli.py          # argparse CLI: scan / signatures / serve
    app.py          # Flask upload dashboard
    templates/      # HTML UI
  tests/            # pytest coverage

The signature database is deliberately separate from the CLI and web app: you can extend signatures.py without touching either.

Limitations (honest ones). This is a curated magic-number table, not libmagic — plain text, unknown/custom binaries, and truncated files won't match and are reported as Unknown. Some short magics (MZ, BM) are collision-prone and reported at medium confidence. For exhaustive coverage, pair Byte Bouncer with file/libmagic; this tool optimizes for a fast, explainable, dependency-light first-pass control.


🏷️ Why "Byte Bouncer"?

A bouncer doesn't care what's printed on your shirt — he checks the ID against your face. This does the same thing with the first 512 bytes of a file. The extension is what a file says it is. The magic number is what it is. When those two disagree somebody is lying, and the genuinely interesting question isn't that they lied — it's how much you should care. An executable wearing a photo's extension is a bad afternoon waiting to happen. A PDF mislabelled as a JPEG is probably just someone's export button misbehaving.


🔬 How this tool was built

One of ten defensive tools rebuilt to a single standard — understand, reimagine, build the CLI, harden, document, verify. The goal was never to change what these tools are, but to take each one as far as it could honestly go on correctness, safety and the experience of the person actually running it at 2am.

Purpose. A file bouncer that checks every file's ID at the door — magic numbers over filenames — and tells you not just that a file lied about what it is but how much that lie should worry you. An executable wearing a photo's extension is CRITICAL; a PDF mislabeled as a JPEG is a WARNING.

How it works. It reads only the first 512 bytes to match a curated magic-number database, refines ZIP-based containers by reading the ZIP central directory (Word/Excel/PowerPoint OOXML, JAR, APK, EPUB, OpenDocument) without extracting anything, then compares the detected type against the extension and classifies the mismatch by severity. A streaming SHA-256 is recorded as evidence.

Standout CLI. A color-coded results table (OK=green, WARNING=yellow, CRITICAL=red, ERROR=grey) with paths shown relative to the scanned root, a transient progress bar over multi-file scans, a summary panel whose headline changes by worst severity (Clean / Mismatches found / Possible masquerade), and a signatures subcommand that renders the whole detection DB grouped by category.

Key improvements. Functional: container-aware ZIP refinement flags a plain archive renamed .docx or a Word file under an .xlsx name; the severity model (ok/warning/critical) with executable/script masquerades marked CRITICAL; a curated LURE_EXTENSIONS check that closes a real gap — an executable renamed to a signatureless extension (malware.exe → notes.txt) used to pass silently; double-extension lure detection (invoice.pdf.exe); the signature DB expanded from 21 to 54 entries. Security: untrusted ZIPs treated as hostile (central directory only, at most one <4 KB size-checked mimetype member read — decompression bombs are a non-event); attacker-controlled filenames escaped before entering rich markup (closes a crash / markup-injection vector); symlinks and non-regular files skipped by default during walks; per-file error resilience so one locked file doesn't abort the scan.

Example.

PYTHONPATH=src python -m byte_bouncer.cli scan ./intake --recursive


⚖️ Authorized use & safety charter

These are defensive tools for systems, files, networks, and people you own or are explicitly authorized to assess. Read this before you run anything:

  • Authorization is not optional. Phishing simulations, network scans, IP reputation lookups, and web-app probing all touch other people's systems or data. Get written authorization first. Several tools refuse to act until you assert it (--yes, --authorized-training, --i-have-authorization, --i-am-authorized, and friends).
  • Safe by default. Every web UI binds to 127.0.0.1 (loopback). Egress, live sends, and active scans are gated behind explicit flags — dry-run, passive, or refuse-and-warn is always the default.
  • No accidental foot-guns. Flask --debug (the Werkzeug interactive debugger is remote code execution on any traceback) is warned about loudly and refused entirely off loopback. Untrusted input is size-capped, validated, and rendered inertly so a hostile file or log line can't crash — or take over — your terminal.
  • No secrets in output. API keys stay in request headers, passwords come from the environment, and nothing sensitive is logged or printed.

These are not offensive tooling. They contain no exploits, no credential harvesters, and no payloads. If a tool could be misused, it is built to resist that misuse by construction.


🧪 Development

python -m venv .venv && source .venv/bin/activate
pip install -e .
pytest -q                 # 26 tests, no network, no sleeps
byte-bouncer --help

📄 License

Released under the MIT License. See LICENSE.

Defensive tooling. No exploits, no payloads, no credential harvesters.

About

Magic-number file-type identification and mismatch detection for defensive triage.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages