Skip to the content.

Scripting

scry embeds a Lua 5.1 runtime (gopher-lua). Scripts declare metadata and a run(host, port) function that’s called on every matching open port. A fresh Lua state is created per invocation, so scripts cannot leak state between calls.

Shape

description = "one-line summary"
ports       = {80, 443}   -- or the string "any"
function run(host, port)
  -- return "finding" on success, (nil, "reason") on error, nothing for no-op
end

scry.* API

Function Purpose
scry.tcp.request(host, port, payload, opts) One-shot: connect, write, read up to max_bytes.
scry.tcp.connect(host, port, opts) Stateful conn userdata: :send, :read(n), :close.
scry.udp.send(host, port, payload, opts) One datagram + optional reply (expect_reply=false for fire-and-forget).
scry.tls.request(host, port, payload, opts) Same as tcp.request over TLS; opts.verify defaults to false.
scry.tls.cert(host, port, opts) Leaf cert: {subject, issuer, not_before, not_after, dns_names}.
scry.dns.lookup(host) Forward A/AAAA lookup → table of strings.
scry.dns.reverse(ip) PTR lookup.
scry.log.info / .warn / .error Structured log at source=script.
scry.util.hex(bytes) / .unhex(hex) Hex encode/decode arbitrary bytes.

Option tables commonly accept timeout (milliseconds) and max_bytes (int). All network calls respect a per-script wall-clock timeout from --script-timeout (default 5s).

Error conventions

Binary payloads & Lua 5.1

gopher-lua is Lua 5.1, which lacks the \xNN string escape added in 5.2. Build binary payloads via scry.util.unhex("…") or decimal escapes (\255). scripts/smb-version.lua is the reference example.

Bundled scripts

File Ports What it reports
scripts/http-title.lua 80, 8080, 8000, 8888 HTML <title>
scripts/ssh-banner.lua 22, 2222 SSH identification string
scripts/tls-cert-info.lua 443, 8443, 9443 Cert subject, issuer, expiry, SANs
scripts/redis-ping.lua 6379 +PONG / auth-required marker
scripts/smb-version.lua 139, 445 SMB1 dialect index or SMB2 fallthrough

Running scripts

scry 10.0.0.0/24 -p 22,80,443,6379,445 \
  --script scripts/ssh-banner.lua \
  --script scripts/tls-cert-info.lua \
  --script scripts/redis-ping.lua \
  --script scripts/smb-version.lua

Use --list-scripts to print metadata (name, ports, description) without running a scan:

scry --list-scripts --script scripts/*.lua

Writing a new script

Target: under 20 lines. Example — detect a bare Memcached server:

description = "memcached stats"
ports = {11211}
function run(host, port)
  local body, err = scry.tcp.request(host, port, "stats\r\n", {timeout=1000, max_bytes=4096})
  if err then return nil, err end
  local version = body:match("STAT version (%S+)")
  if version then return "memcached " .. version end
end