Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/0x-unkwn0wn/simterm/llms.txt

Use this file to discover all available pages before exploring further.

Each TargetNode can carry a filesystem — a tree of FsNode values that the player navigates during the post-exploitation phase using familiar shell commands (ls, cd, cat, find). Beyond plain text files, the VFS supports mechanical loot rewards, offline hash-cracking puzzles, binary reversing challenges, and encoded blobs — all defined declaratively in RON.

FsNode variants

There are exactly two variants:

Dir

A directory that contains child nodes.
Dir(name: "etc", children: [
    Dir(name: "nginx", children: [
        File(name: "nginx.conf", content: ["worker_processes 4;"]),
    ]),
]),

File

A file with optional content, access control, loot, binary metadata, and encoding.
File(
    name: "shadow",
    content: ["root:$6$rounds=5000$xyz$HASH:19000:0:99999:7:::"],
    root: true,
    loot: Some((
        hash: Some((
            algo: "sha512crypt",
            strength: 7,
            needs_wordlist: true,
            yields: Token("admin-token"),
        )),
    )),
)

File struct fields

FieldTypeDefaultDescription
namestringrequiredFile name shown by ls.
contentstring list[]Lines shown by cat. For encoded/binary files this is the plaintext the player must recover.
rootboolfalseRequires root access to read. Files marked root: true are shown in ls output but cat is blocked until the player has escalated.
lootSome(Loot) or NoneNoneReward granted the first time the file is successfully read or decoded.
binarySome(Binary) or NoneNoneMarks the file as a reversible binary. cat is blocked; the player uses strings/disasm/solve.
encodingSome(Encoding) or NoneNoneMarks the file as encoded. cat shows the encoded blob; the player uses base64 or xor to recover the plaintext.

Loot

Loot is the reward the engine grants the first time a file is read (or decoded). All fields are optional — a Loot block with no fields is valid and simply signals “this file was read” without any mechanical effect.
FieldTypeDefaultDescription
skillf320.0Persistent skill bonus applied for the rest of the campaign.
credentialSome("string") or NoneNoneCredential saved to the player’s inventory (narrative/flavor).
noteSome("string") or NoneNoneHint line displayed when the loot is collected.
privesc_keyboolfalseWhen true, reading this file enables deterministic privesc.
foothold_tokenSome("token") or NoneNoneToken saved to inventory; reusable with login on hosts whose accepts_token matches.
wordlistboolfalseWhen true, reading this file grants the campaign wordlist, enabling hash attacks that needs_wordlist: true.
hashSome(LootHash) or NoneNoneEmbedded hash crackable with john/hashcat. See Hash puzzles below.

privesc_key

Set privesc_key: true on a file that is readable without root. When the player reads it, privesc becomes deterministic for that host. This is the standard route to root: hide the key in a non-root file the player has to find, then protect the objective behind root access.

foothold_token

A foothold_token is a string value stored in the player’s inventory. On any subsequent host where TargetNode.accepts_token equals that string, the player can run login to get a foothold without going through the probabilistic exploit path.

Hash puzzles

Add a hash block inside Loot to create an offline cracking puzzle. The player uses john or hashcat; the attack costs clock ticks but adds no network trace.
loot: Some((
    hash: Some((
        algo: "sha512crypt",
        strength: 6,
        needs_wordlist: true,
        yields: Token("relay-token"),
    )),
))
FieldTypeDefaultDescription
algostringrequiredAlgorithm label shown to the player (e.g. "ntlm", "bcrypt", "sha512crypt").
strengthu85Cracking difficulty 1..=10. Higher means lower probability per attempt.
needs_wordlistboolfalseWhen true, the attack is infeasible without a wordlist the player must find elsewhere in the campaign.
yieldsRewardrequiredWhat the player receives on success.

Binary reversing

Set binary on a file to make it a reversing puzzle. cat is blocked — the player must use strings to view printable strings, disasm (also objdump, r2) to view the pseudo-disassembly, and solve <path> <secret> to submit the extracted secret.
binary: Some((
    strings: ["usage: authd [TOKEN]", "cmp_key: verify token"],
    disasm: ["cmp eax, 0x1f3b", "jne 0x40112c"],
    secret: "AX29",
    yields: PrivescKey,
    hint: Some("The key is embedded in the comparison immediate."),
)),
FieldTypeDefaultDescription
stringsstring list[]Output of strings (printable strings, some decoys).
disasmstring list[]Output of disasm/objdump/r2 (pseudo-disassembly or decompiled fragment).
secretstringrequiredThe answer the player submits with solve. Case-insensitive comparison.
yieldsRewardrequiredReward granted on a correct solve.
hintSome("string") or NoneNoneHint line shown alongside the strings output.

Encoded files

Set encoding to make a file’s content retrievable only by decoding. cat shows the encoded blob; the player must use base64 or xor to recover the plaintext.
// Base64-encoded file
encoding: Some(Base64),

// XOR-encoded file with a known key
encoding: Some(Xor("key")),
Commands:
  • base64 <path> — decodes a Base64-encoded file.
  • xor <path> <key> — decodes an Xor-encoded file with the correct key.
Using the wrong tool, or the wrong key for XOR, returns an error without revealing the content.

Reward enum

All puzzle rewards (hash.yields, binary.yields) use the same Reward type as Loot fields:
VariantEffect
Skill(f32)Adds a persistent skill bonus.
Credential(String)Saves a credential string to inventory.
Token(String)Saves a foothold token; usable with login on matching hosts.
PrivescKeyEnables deterministic privesc on the current host.

VFS layout example

The following is adapted from the sample campaign’s first mission and shows how Dir/File nesting, loot, and root interact:
filesystem: [
    Dir(name: "home", children: [
        Dir(name: "alumno", children: [
            Dir(name: ".ssh", children: [
                File(
                    name: "id_rsa",
                    content: ["-----BEGIN OPENSSH PRIVATE KEY-----"],
                    // Readable without root — this IS the path to root.
                    root: false,
                    loot: Some((
                        privesc_key: true,
                        note: Some("Local key: enables safe privesc."),
                    )),
                ),
            ]),
        ]),
    ]),
    Dir(name: "var", children: [
        Dir(name: "www", children: [
            File(
                name: "notes.txt",
                content: ["TODO: change default password demo:demo"],
                loot: Some((
                    skill: 0.05,
                    credential: Some("demo:demo"),
                )),
            ),
        ]),
    ]),
    Dir(name: "root", children: [
        File(
            name: "flag.txt",
            content: ["FLAG{mission_complete}"],
            // Requires root to read — this is the objective.
            root: true,
        ),
    ]),
]
Place privesc_key loot in a file that does not have root: true. The purpose of the key is to grant root — it must be readable before the player has escalated. A common pattern is a private key or config file in a user home directory. If you accidentally set root: true on the privesc_key file, the player will be stuck in a deadlock where root is needed to get the key needed for root.

Build docs developers (and LLMs) love