Skip to content

Format reference

The .txt grammar for the implemented protocols on one page: every line, every field, every assert. Written to be read by a language model.

This page is the complete grammar for the protocols the runtime implements today. It is written so a language model can produce a correct file from this page alone. Nothing else is needed for the implemented set; the vocabulary is open and grows toward every protocol in the Identity list.

The file

  • A solution is a text file with a .txt extension.
  • Lines starting with # are comments and are ignored.
  • Blank lines separate sections. Two-space indentation marks a block.
  • A step is one or more English sentence directives, one per line. The directive starts with a verb word (a method like get/post, or send, receive, run, read, write, expect, capture, with, timeout, port, close); the rest of the line is the value. Quoted values use "...".
  • The header lines and the vars:, set:, headers:, cookies:, and oob: blocks are the only name: value lines. There is no other syntax.

The header

Lines before the first step:

linemeaning
id: <name>required, names the attack, in vendor/product style
summary: <text>optional, one sentence about the attack
ref: <ref>optional, repeat the line for more, for example CVE-2014-0160
param: <name>optional, the HTTP input parameter name
vars:optional block, one name: value per line, starting values
oob:optional block, host: and port: lines; port: 0 picks a free one

The names flag_path, oob_host, and oob_port are reserved and cannot be declared.

A step

A step is a block. The first line is <identity> <step-name>. Indented lines below it are directives, one per line.

<identity> <step-name>
  <directive>
  expect ...

The identity and the step name are always required. A step name is one word, unique within the file, and cannot be a directive keyword (a sentence verb such as get, post, send, expect, or capture).

Identity

The runtime implements eighteen protocols today, the ones that appear most often in real exploits. Use only these to produce a valid file; the vocabulary is open and grows toward every protocol in the Identity list:

identityrequired (English directive)other directives
http, httpsa method directive (get/post/put/patch/delete/head/options)with the header, with the cookie, set, capture, expect
websocketsendreceive, receive until, timeout, close the connection, capture, expect
tcp, tls, ssh, ftp, smtp, ldap, redis, mysql, postgresql, smb, telnetsendreceive, receive until, port, timeout, close the connection, capture, expect
dns, snmpsendreceive, receive until, timeout, close the connection, capture, expect
processrunexpect
fileread or writeexpect

The “required” column is the directive that opens the step. The “other directives” column lists the sub-lines that follow.

http, https

An http step is a set of directives; the program does the heavy lifting. The directives are:

directivemeaning
get <path>GET the path; the path may be quoted (get "/login")
get with <value>GET the target itself with the value in a query parameter
get <path> with <value>GET the path with the value in a query parameter
put, patch, delete, head, options <path>the verb is the HTTP method; the same forms as get apply
post "<path>" [as <shape>] [with <value>]POST the path (quote it like get). as a form field <name> posts one form field, as json a JSON object with the value, as a raw form the body verbatim with form content-type (the default), as a raw json the body verbatim with JSON content-type, as a raw multipart the body verbatim as a multipart/form-data body (the runtime reads the boundary from the body’s first line)
with the header "<name>" set to <value>set one request header
with the cookie "<name>" set to <value>add one cookie, joined into the Cookie header
expect ...the assert: expect "<text>" in the response, expect the response to match "<regex>", expect the target to call back "<expect>", expect a flag, expect no response

A value may be written base64 of "<text>" to base64-encode it after {var} substitution. Captures use the English forms above. The example solution reads entirely as sentences:

http sso
  get "/login"
  expect "SAMLRequest" in the response
  capture the form field "SAMLRequest" as saml_b64

http deliver
  post "/acs" as a form field SAMLResponse with base64 of "<samlp:Response InResponseTo=\"{reqid}\"/>"
  expect "accepted" in the response

The target is a base URL; https is the same exchange over TLS, and the http identity also covers both schemes.

A solution-level headers: or cookies: block (before the first step, like vars:) sets defaults applied to every http step. A step’s own with the header/with the cookie lines, or its own headers:/cookies: block, add to or override them. Values accept {var} references, filled when each step runs. This is how a multi-step session replays the same cookies on every request without repeating them:

cookies:
  AUTH_SESSION_ID: "{sid}"
  KC_AUTH_SESSION_HASH: "\"{hash}\""

The response an assert or capture sees is the full exchange: the status line and headers of every response in the redirect chain (so a redirect Location is assertable), then the final body.

websocket

A websocket step reads as sentences: send "<text>" sends the payload, receive <n> bytes reads at most that many bytes, receive until "<hex>" stops at a hex marker, timeout <n> seconds bounds the read, close the connection ends the session, and expect ... checks the answer:

websocket ws-rpc
  send "{\"jsonrpc\":\"2.0\",\"id\":1}"
  receive 65536 bytes
  timeout 5 seconds
  expect "226b22" in the response

The payload is sent as-is (text, wrapped into a masked frame by the runtime); the send hex "..." form does not apply. The connection stays open across steps.

tcp, tls, and the byte-family services

A byte step reads as sentences: send hex "<hex>" sends raw bytes (or send hex of "<text>" hex-encodes readable text), receive <n> bytes reads at most that many, receive until "<hex>" stops at a marker, port <n> reconnects the session to another port, timeout <n> seconds bounds the read, close the connection ends the session, and expect ... checks the hex response:

tls malformed-heartbeat
  send hex "1803030003014000"
  receive 70000 bytes
  timeout 5 seconds
  expect the response to match "18030[123]40"

The send hex of "<text>" form interprets the usual \r, \n, \t, \", \\ escapes plus \xHH for one arbitrary byte, so control characters stay visible (send hex of "HELO x\r\n"). tls sends and receives raw TLS record bytes, so a solution authors the handshake itself. Each service (ssh, ftp, smtp, ldap, redis, mysql, postgresql, smb, telnet) has a default port when the target omits one.

dns, snmp

dns and snmp speak raw datagrams over UDP; the fields are the byte-session fields above. The target is host:port; the default port is 53 for dns and 161 for snmp.

process

A process step reads as run "<command>", with an optional in "<directory>" clause for the working directory:

process enum
  run "python ssh_enum.py 127.0.0.1" in "C:\\Temp\\prototest"
  expect the response to match "root is a valid user"

file

A file step reads as read "<path>" or write "<path>" with "<data>":

file write-it
  write "C:\\Temp\\exploit.mvg" with "push graphic-context\nfill 'url(x|id)'\npop graphic-context\n"

Capture

A capture pulls a value out of the response for later steps, and it reads as English. The program does the heavy lifting: it knows page shapes and encodings, so the author names the value and the runtime finds the bytes.

The shape form names a thing the page holds, and the runtime finds it by shape:

capture the <shape> "<name>" [of <source>] [base64] as <var>
shapewhat the runtime finds
form fieldthe value of an HTML input whose name is <name>
attributethe value of an XML or HTML attribute <name>="..."
json fieldthe value of a JSON string field "<name>": "...", raw or HTML-escaped (&quot;<name>&quot;:&quot;...&quot; inside an attribute value)
query parameterthe value of a URL query parameter <name>=<value> (raw ?/& or HTML-escaped &amp;)
cookiethe value of the <name> cookie from the response’s Set-Cookie headers
value afteron a byte-family step, the run of hex after the hex <name> marker, hex-decoded (how a server-assigned port id is pulled out of the hex stream)
input name afterthe tail of an HTML input’s name after the prefix <name> (how the Next.js Server Action reference index $ACTION_REF_1 is read from the input’s name attribute)

For a SAML page that hands its request over base64-encoded:

http page
  get "/sso"
  capture the form field "SAMLRequest" as saml_b64
  capture the attribute "ID" of saml_b64 base64 as reqid
  capture the attribute "AssertionConsumerServiceURL" of saml_b64 base64 as acs

Line by line: capture the SAMLRequest form value as saml_b64; then, from its base64-decoded value, capture the ID attribute as reqid and the AssertionConsumerServiceURL attribute as acs. of <source> names the variable to search (the response by default), and base64 decodes it first. The author never writes the markup; the shape defines it.

The template form matches literal text with one <name> placeholder where the value appears:

capture <var> [from <source>] [base64] where "<text with one <name> placeholder>"

The literal text is matched exactly and <name> captures whatever appears there. The same example in this form:

capture saml_b64 where "name=\"SAMLRequest\" value=\"<value>\""
capture reqid from saml_b64 base64 where "ID=\"<value>\""

The regex form is the same capture with a pattern instead of a template:

capture <var>: <regex> [from <var>] [base64]

On http the regex runs over the full response text (status line, headers, and body); on tcp/tls/websocket over the received hex, and group 1 is hex-decoded. Later steps use {var}. A step may carry several capture lines, one value each, applied in order.

All three forms share the source clauses: from/of <var> runs the pattern over an earlier variable’s value instead of this step’s response, and base64 decodes the source first. The source names a variable declared before the step (a solution var, a built-in, an earlier step’s set or capture, or an earlier capture on the same step).

Set

set: is a block, one name: value per line. The values go to later steps. A later write on the same name wins. A step’s own set applies to later steps only.

A set line can also compute a value from a deterministic data operation, the transform sentence:

set <var> = aesgcm of "<plaintext>" with "<base64-key>"

It runs after the step’s captures (so the plaintext may reference a value the step just learned, like {action_id}), and its result is stored in <var> for later steps, exactly like a set: value. aesgcm seals the plaintext with AES-GCM under the base64 key and stores base64(iv + ciphertext) with a 16-byte iv, the encoding Next.js Server Actions use for bound arguments. If a referenced value never arrived (the target did not leak it), the transform is skipped and a later step’s assert reports not verified.

Assert

The assert reads as expect ...; each form is one of the five test kinds:

directivetypepasses when
expect "<text>" in the responsecontainsthe response contains the exact text
expect the response to match "<regex>"regexthe response matches the pattern
expect a flagflagthe response contains a flag-shaped string such as FLAG{...}
expect the target to call back "<expect>"oobthe target contacted the out-of-band listener
expect no responseemptythe response is empty: the target never answered

Target

The target argument is shaped by the solution’s protocols: tcp, tls, and the byte-family services (including dns and snmp) take host:port, http, https, and websocket take a base URL, process and file ignore it.

Full example

id: openssl/heartbleed-mem-leak
summary: CVE-2014-0160 Heartbleed
ref: CVE-2014-0160

tls clienthello-heartbeat
  send hex "1603030125010001210303..."
  receive until "0e000000"
  receive 65536 bytes
  expect "0e000000" in the response
  # server completes the handshake

tls malformed-heartbeat
  send hex "1803030003014000"
  receive 70000 bytes
  expect the response to match "18030[123]40"

Result

The runtime prints one line per step (name PASS|FAIL test) and a result line: verified when every test passes, not verified when one fails. Parse errors name the line number. The runtime never interprets: the file is what runs.

Give exploit research a common language.

Bring a PoC, a writeup, or an AI-generated draft into an Exploitmatic solution: inspect it, validate it, and replay it against a target you own. The runtime and the corpus are open source.