Skip to main content

Modern internet-services emulation suite for malware analysis labs

Project description

Lyrebird

Modern internet-services emulation suite for malware analysis labs.

CI License: GPL v3 Python

Lyrebird stands up fake-but-believable network services — HTTP/HTTPS, DNS (UDP + TCP), SMTP, POP3/IMAP, FTP, TFTP, SSH, Telnet, IRC, NTP, TLS, and a generic TCP sink — so that a malware sample running in an isolated sandbox behaves as if it were online. Every interaction is recorded as a structured JSON event, and every payload the sample sends is captured to disk. It's a spiritual successor to INetSim (last release 1.3.2, 2020), rebuilt async-first in Python with detection telemetry as a first-class output.

⚠️ Authorized, isolated lab use only. Lyrebird emulates benign services to observe malware in containment. It is a defensive/research tool. It contains no implant, no command-and-control capability, and no evasion tooling. Run it on a segmented, non-routable network. Do not expose it to the internet.

Demo

A lab boots, a stand-in sample talks to the emulated services, and every interaction lands as structured JSONL with detections firing as tags:

Lyrebird demo: a sample checks in over HTTP and DNS while Lyrebird captures each interaction as structured JSONL with detection tags

The "sample" is a benign curl/dig — Lyrebird only observes. The recording is generated from demo/lyrebird.tape with VHS; see demo/ to reproduce it.

With the AI model layer

Optionally, Lyrebird can call a model to (1) improvise a believable, inert reply for an endpoint no static rule anticipated — so an unfamiliar sample keeps talking — and (2) triage the captured session into a verdict and candidate Sigma detections:

Lyrebird AI demo: the model improvises a benign response for an un-ruled endpoint, then triages the captured session into a verdict and suggested detections

Off by default. The responder is constrained to generic placeholder content — never payloads, scripts, or tasking (see SCOPE.md and src/lyrebird/models/responder.py).

Why

INetSim is still the reference tool, but it's Perl-based, synchronous, config is a custom format, and its logging was designed for human reports rather than SIEM ingestion. Lyrebird keeps the proven model and modernizes it:

  • Async core (asyncio / FastAPI / aiosmtpd / dnslib)
  • YAML config with sane defaults — an empty file still gives a working lab
  • Structured JSONL events — one normalized object per interaction, tailable straight into a SIEM (this is the backbone everything keys off)
  • Artifact capture — uploads, mail bodies, and raw socket data stored + hashed
  • Auto lab CA — TLS handshakes just work; certs are minted on first run
  • Container-nativedocker compose up on an internal (no-egress) network
  • Paired Sigma detections — every service ships with detection content

Quick start

pip install -r requirements.txt
python -m lyrebird --config config/lyrebird.yaml

Or containerized (recommended — the compose network is internal: true, so the lab has no outbound route by default):

cd docker && docker compose up --build

Point your malware analysis VM's default gateway / DNS at the Lyrebird host, then detonate the sample and watch the events stream in.

What gets emitted

Each interaction is one line of JSON:

{"schema":"1.0","ts":"2026-06-29T12:00:00.000+00:00","session":"...",
 "service":"dns","transport":"udp","src_ip":"10.13.37.66","src_port":51000,
 "dst_port":53,"event_type":"request","summary":"A evil.example",
 "request":{"qname":"evil.example.","qtype":"A"},
 "response":{"rcode":0,"answer":"10.13.37.1"},"artifacts":[],"tags":[]}

Events land in labdata/events/<session>.jsonl; captured payloads in labdata/artifacts/<service>/.

The full field-by-field event schema and the complete detection catalog are in REFERENCE.md — generated from events.py and the Sigma rules, so it never drifts. Regenerate with python scripts/gen_reference.py.

Services

Service Transport Status Notes
HTTP / HTTPS TCP ✅ implemented catch-all any method/path; auto-TLS; body capture
DNS UDP ✅ implemented sinkhole responder; logs every lookup; optional realistic NXDOMAIN mode (off by default)
SMTP TCP ✅ implemented accepts + captures mail; logs envelope
POP3 TCP ✅ implemented fake mailbox; logs credentials/commands
FTP TCP ✅ implemented passive + active (PORT) mode; captures STOR uploads
TFTP UDP ✅ implemented captures WRQ uploads; per-transfer TID
IRC TCP ✅ implemented observes bot C2 — nick, channels, PRIVMSG tasking
NTP UDP ✅ implemented answers time; configurable faketime delta
TCP sink TCP ✅ implemented logs all data to extra ports (INetSim "Dummy")
IMAP TCP ✅ implemented fake mailbox; logs LOGIN credentials; IDLE push (mailbox-C2 long-poll) → imap-idle
DNS over TCP TCP ✅ implemented sinkhole over TCP transport
TLS (fingerprint + serve) TCP ✅ implemented JA3/JA4 + SNI, terminates & serves, same-connection SNI-vs-Host (off by default)
TLS fingerprint tap TCP ✅ implemented JA3/JA4 + SNI capture then close (off by default)
SSH TCP ✅ implemented asyncssh honeypot; captures brute-force credentials, then a fake shell logs commands (ssh-bruteforce, ssh-payload-pull)
Telnet TCP ✅ implemented plaintext IoT/Mirai honeypot; brute-force creds → fake shell logs commands (telnet-bruteforce, telnet-payload-pull)
QUIC / HTTP-3 UDP ✅ implemented aioquic h3 server; captures each HTTP/3 request and answers benignly (http3-transport)

The SSH honeypot uses the asyncssh library (a required dependency). If asyncssh is unavailable the SSH service is skipped and the rest of the emulator keeps running.

Customizing responses

Tailor what any service returns without touching code, via a responses block per service (the modern take on INetSim's fakefiles). Resolution order is operator rule → fakefile → model responder (if enabled) → built-in default, and the chosen source is recorded on every event (response.source).

services:
  http:
    responses:
      http:
        - path: "/gate.php"        # glob over the URL path
          method: "POST"
          status: 200
          content_type: "application/json"
          body: '{"status":"ok","task":"none"}'
        - path: "/*.exe"
          body_file: "fakefiles/stub.bin"   # relative to data_dir
      fakefiles_dir: "fakefiles"   # also serve real files by URL path
  dns:
    responses:
      dns:
        - qname: "*.evil-c2.com"   # point a family at a specific sink host
          qtype: "A"
          answer: "10.13.37.66"

Fakefile serving is path-traversal protected — paths are confined to the configured directory.

Enabling / disabling services

Per-service enabled: true|false in config, or override at launch:

python -m lyrebird --disable smtp,ntp
python -m lyrebird --enable http,dns --no-banner

Model layer (frontier + local)

Lyrebird talks to models through one vendor-agnostic interface, so you can use a frontier API or a fully local model interchangeably. Selecting local keeps all data on-host for air-gapped analysis.

provider backend
anthropic Claude (ANTHROPIC_API_KEY)
openai OpenAI (OPENAI_API_KEY)
gemini Google Gemini (GEMINI_API_KEY)
local any OpenAI-compatible local server — Ollama, LM Studio, llama.cpp, vLLM
mock offline deterministic stub (tests / dry-runs)

Primary use — session triage into detections. Point it at a captured session and get a structured verdict + candidate Sigma ideas:

python -m lyrebird.analyze --session labdata/events/<id>.jsonl --provider local
python -m lyrebird.analyze --session <file> --provider anthropic --model claude-sonnet-4-6

Optional use — response generation. When models.respond.enabled: true, an HTTP request with no matching rule gets a model-generated benign placeholder body so unfamiliar samples keep talking. It's off by default (static templates are preferred), and it's deliberately constrained: captured input is sanitized first, the model is restricted to generic inert content (never payloads, scripts, or commands), and output is canary-checked and length-capped — any failure falls back to the static default.

Untrusted input is treated as untrusted

Captured traffic is adversary-controlled, so anything that reaches a model is a prompt-injection surface. models/sanitize.py defangs injection markers, frames captured data as inert observations behind a canary, and validates model output against a schema. Nothing captured is ever executed — services only serve or log bytes.

Adding a service

Subclass BaseService, implement start() / stop(), emit events with self.emit(...), and register the class in orchestrator.REGISTRY. That's the whole contract — see services/dns.py for a compact example.

Detections

detections/sigma/ holds Sigma rules that key off the JSONL schema (logsource.product: lyrebird). Shipped so far: DNS long-label/DGA, HTTP missing-User-Agent beacon, SMTP bulk recipients. The principle is that every emulated technique ships with its paired detection.

Detection analytics

Beyond single-event Sigma rules, three analytics run over a captured session for the statistical / behavioural cases:

  • python -m lyrebird.beacons --session <jsonl> — beaconing, jitter (via inter-arrival CV), and channel rotation. The defensive pair to Phase 2.
  • python -m lyrebird.mimicry --session <jsonl> [--data-dir labdata] — traffic-mimicry and encryption tells: protocol-on-unexpected-port, domain- fronting heuristics, browser-UA-but-bot, and high-entropy (encrypted) bodies. The defensive pair to Phase 3.
  • python -m lyrebird.dns_tunnel --session <jsonl> — DNS tunneling / data-exfil channels: high-entropy, near-all-unique subdomains streamed under one parent domain, distinct from the single-query long-label DGA rule.

Layout

src/lyrebird/
  events.py        # structured event model + JSONL sink  (the backbone)
  config.py        # YAML loading + defaults
  base.py          # BaseService plugin contract
  certs.py         # lab CA / leaf certs
  tls.py           # ClientHello parsing + JA3 / JA4
  profiles.py      # operator response templates
  orchestrator.py  # loads config, runs enabled services
  cli.py           # `python -m lyrebird`
  analyze.py       # model-assisted session triage
  beacons.py       # beacon / jitter / channel-rotation analytic
  mimicry.py       # traffic-mimicry / encryption-tell analytic
  dns_tunnel.py    # DNS tunneling / exfil analytic
  services/        # http, dns, dns_tcp, smtp, pop3, imap, ftp, tftp,
                   #   irc, ntp, tls_capture, tcp_sink
  models/          # anthropic, openai, gemini, local, mock + sanitize
config/lyrebird.yaml
detections/sigma/
scripts/lint_sigma.py
docker/
tests/

Status

All sixteen services and all three session analytics are implemented, tested (113 tests, green on Python 3.10–3.12), and runnable; the suite boots end to end and every service emits telemetry. Recent additions — SSH and Telnet credential-capture honeypots, IMAP IDLE emulation, FTP active-mode (FTP-bounce) hardening, TLS JA3/JA4 fingerprinting, NTP mode-6/7 MONLIST detection, and a DNS tunneling/exfil analytic — each ship with their paired detection. The plugin contract is stable; further work is additive. See SCOPE.md for positioning and the roadmap.

Contributing

PRs welcome — see CONTRIBUTING.md for setup, the bar for changes, and a step-by-step walkthrough for adding a new service. CI (GitHub Actions) runs the test suite on Python 3.10–3.12, lints the Sigma rules, and smoke-tests the CLI on every push and PR.

PYTHONPATH=src python -m pytest tests/ -q
PYTHONPATH=src python scripts/lint_sigma.py

License

GPL-3.0-or-later (see LICENSE). Chosen to keep Lyrebird a copyleft, fork-friendly community tool in the same spirit as INetSim (GPLv2). Every source file carries an SPDX header.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lyrebird_emulator-0.2.0.tar.gz (95.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lyrebird_emulator-0.2.0-py3-none-any.whl (90.9 kB view details)

Uploaded Python 3

File details

Details for the file lyrebird_emulator-0.2.0.tar.gz.

File metadata

  • Download URL: lyrebird_emulator-0.2.0.tar.gz
  • Upload date:
  • Size: 95.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for lyrebird_emulator-0.2.0.tar.gz
Algorithm Hash digest
SHA256 d070bb3fc9413e0620a0a62178fe0b47b44e61f74f201f311185b75a1d6ace65
MD5 3c20fab182309afaa59465c44fd58e9d
BLAKE2b-256 7755f0724875d85f4279a2cdf405eeabeff04838fb46a89d206075e88edfd470

See more details on using hashes here.

Provenance

The following attestation bundles were made for lyrebird_emulator-0.2.0.tar.gz:

Publisher: publish.yml on cac0ns3c/Lyrebird

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lyrebird_emulator-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for lyrebird_emulator-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3c0ace679023e7d54231be336fc5074128ada8cfb2b38255511ed43a9e876798
MD5 4a8e0e8feb4c2bb3718ede00e408a547
BLAKE2b-256 75179d1a5da0f36cd7a629ebc9f63ccb531da68742c988b50f159a5b7f3cb200

See more details on using hashes here.

Provenance

The following attestation bundles were made for lyrebird_emulator-0.2.0-py3-none-any.whl:

Publisher: publish.yml on cac0ns3c/Lyrebird

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page