Lua Scripting Engine

The RTFMv2 Console includes a powerful Lua scripting engine that allows you to extend functionality, create custom commands, automate workflows, and hook into the console's lifecycle events.

Overview

The Lua engine uses MoonSharp (a Lua interpreter for .NET) to provide a sandboxed scripting environment. Scripts are loaded automatically from the Plugins/Lua/ directory and can register custom commands, provide tab completions, and interact with the console.

Key Features

  • Custom Commands: Register new CLI commands with Lua functions
  • Lifecycle Hooks: Hook into console events (prompts, command execution, etc.)
  • Tab Completion: Provide dynamic tab completion suggestions
  • Command Chaining: Execute other RTFMv2 commands from Lua
  • Session Integration: Access current session information
  • Sandboxed Execution: Safe, restricted environment with timeouts

Getting Started

Script Location

All Lua scripts must be placed in:

Plugins/Lua/

Scripts are automatically loaded when the console starts in interactive mode.

Basic Script Template

Create a new file myscript.lua:

function init()
    -- Called when the script loads
    host:log("My script loaded successfully!")

    -- Register a custom command
    host:register_command("mycommand", "Description of my command")

    -- Add tab completion
    host:add_completion("my", {"mycommand"})
end

function on_command_invoke(name, tokens)
    -- Called when your registered command is executed
    if name == "mycommand" then
        print("Hello from my custom command!")

        -- Get current session ID
        local sid = host:session_id()
        print("Current session: " .. sid)

        -- Execute another RTFM command
        local exitCode = host:exec("list --session")
        print("Command returned: " .. exitCode)
    end
end

Running Your Script

  1. Save your .lua file to Plugins/Lua/
  2. Start the console in interactive mode: bash RTFMv2.Console.exe
  3. Your script loads automatically
  4. Use your custom command: ```

    mycommand Hello from my custom command! Current session: test-abc123 ```


Lua API Reference

Host Object Functions

The host object provides access to the RTFMv2 console. All functions are called using colon syntax (:)

host:log(message)

Log a message to the console.

Parameters: - message (string): Message to log

Example:

host:log("Script initialized")
host:log("Found " .. count .. " results")

host:session_id()

Get the current session identifier.

Returns: - (string) Current session ID, or empty string if no session loaded

Example:

local sid = host:session_id()
if sid ~= "" then
    print("Active session: " .. sid)
else
    print("No session loaded")
end

host:exec(commandLine)

Execute an RTFMv2 command synchronously.

Parameters: - commandLine (string): Complete command with arguments

Returns: - (number) Exit code (0 = success, non-zero = error)

Example:

-- Run a list command
local result = host:exec("list --session")

-- Run a custom shell command
local result = host:exec("run --custom 'nmap -sV 192.168.1.1'")

if result == 0 then
    print("Command succeeded")
else
    print("Command failed with code: " .. result)
end

host:add_completion(forToken, items)

Add tab completion suggestions for a specific token.

Parameters: - forToken (string): The partial token to complete - items (table): Array of completion suggestions

Example:

-- Add completions for "scan" prefix
host:add_completion("scan", {"scan-quick", "scan-full", "scan-stealth"})

-- Add completions for command options
host:add_completion("--target", {"192.168.1.1", "10.0.0.1"})

host:register_command(name, description)

Register a new CLI command that will be handled by Lua.

Parameters: - name (string): Command name - description (string): Command description (shown in help)

Example:

function init()
    host:register_command("quick-scan", "Perform a quick network scan")
    host:register_command("save-finding", "Save a security finding")
end

host:register_option(commandName, optionName, description, completions)

Register an option for a command with optional tab completion values.

Parameters: - commandName (string): Name of the command - optionName (string): Option name (e.g., "--target") - description (string): Option description - completions (table): Array of completion values (can be nil)

Example:

function init()
    host:register_command("scan", "Network scanner")
    host:register_option("scan", "--type", "Scan type", {"quick", "full", "stealth"})
    host:register_option("scan", "--target", "Target IP", {"192.168.1.1", "10.0.0.1"})
end

host:get_session_data(key)

Get session data for safe keys.

Parameters: - key (string): Data key to retrieve (currently only "id" is supported)

Returns: - (string) Session data value

Example:

local sessionId = host:get_session_data("id")
print("Session: " .. sessionId)

The standard Lua print() function is available and writes to the console.

Example:

print("This is output to the console")
print("Found " .. itemCount .. " items")

Lifecycle Hooks

Lua scripts can implement lifecycle hooks to execute code at specific points in the console's operation.

init()

Called once when the script is first loaded.

Use for: - Registering commands - Setting up initial state - Adding tab completions

Example:

function init()
    host:log("Initializing custom commands...")
    host:register_command("mycommand", "My custom command")

    -- Initialize script state
    findings = {}
    scanResults = {}
end

on_prompt()

Called before each command prompt is displayed.

Use for: - Updating dynamic state - Refreshing data - Displaying status information

Example:

function on_prompt()
    -- Update prompt with custom info
    local sid = host:session_id()
    if sid ~= "" then
        print("[Session: " .. sid .. "]")
    end
end

on_input_changed(text)

Called whenever the user types (very frequent).

Parameters: - text (string): Current input text

Use for: - Dynamic syntax highlighting - Real-time validation - Input suggestions

Example:

function on_input_changed(text)
    -- Warn if command might be dangerous
    if text:match("^rm %-rf") then
        print("âš  Warning: Dangerous command!")
    end
end

on_before_execute(line)

Called before a command is executed.

Parameters: - line (string): The command line about to be executed

Use for: - Command validation - Pre-execution logging - Parameter modification

Example:

function on_before_execute(line)
    host:log("Executing: " .. line)

    -- Track command history
    table.insert(commandHistory, {
        cmd = line,
        timestamp = os.time()
    })
end

on_after_execute(line, exitCode)

Called after a command completes execution.

Parameters: - line (string): The command that was executed - exitCode (number): Exit code (0 = success)

Use for: - Post-execution logging - Result processing - Error handling

Example:

function on_after_execute(line, exitCode)
    if exitCode == 0 then
        host:log("✓ Command succeeded")
    else
        host:log("✗ Command failed with code: " .. exitCode)
    end
end

on_output(chunk)

Called when output is written to the console.

Parameters: - chunk (string): Output text chunk

Use for: - Output filtering - Pattern detection - Data extraction

Example:

function on_output(chunk)
    -- Detect IPs in output
    for ip in chunk:gmatch("%d+%.%d+%.%d+%.%d+") then
        print("Found IP: " .. ip)
        table.insert(foundIPs, ip)
    end
end

on_error(message)

Called when an error occurs.

Parameters: - message (string): Error message

Use for: - Error logging - Error recovery - User notifications

Example:

function on_error(message)
    host:log("ERROR: " .. message)
    table.insert(errorLog, {
        msg = message,
        timestamp = os.time()
    })
end

on_command_invoke(name, tokens)

Called when a registered command is executed.

Parameters: - name (string): Command name - tokens (table): Array of command arguments

Use for: - Handling custom commands - Parsing arguments - Executing command logic

Example:

function on_command_invoke(name, tokens)
    if name == "quick-scan" then
        local target = tokens[2]  -- First argument after command
        if not target then
            print("Usage: quick-scan <target>")
            return
        end

        print("Scanning " .. target .. "...")
        host:exec("run --custom 'nmap -F " .. target .. "'")
    end
end

on_complete(fullLine, position)

Called to provide tab completion suggestions.

Parameters: - fullLine (string): Full input line - position (number): Cursor position

Returns: - (table) Array of completion suggestions

Use for: - Dynamic tab completion - Context-aware suggestions

Example:

function on_complete(fullLine, position)
    if fullLine:match("^scan %-%-target ") then
        -- Return list of known targets
        return {"192.168.1.1", "10.0.0.1", "example.com"}
    end
    return {}
end

Example Scripts

Example 1: Note-Taking System

-- notes.lua
local notes = {}
local findings = {}

function init()
    host:log("Note-taking system loaded")
    host:register_command("note", "Add a note")
    host:register_command("finding", "Record a security finding")
    host:register_command("show-notes", "Display all notes")
end

function on_command_invoke(name, tokens)
    if name == "note" then
        local noteText = table.concat(tokens, " ", 2)
        table.insert(notes, {
            text = noteText,
            timestamp = os.date("%Y-%m-%d %H:%M:%S")
        })
        print("Note saved!")

    elseif name == "finding" then
        local findingText = table.concat(tokens, " ", 2)
        table.insert(findings, {
            text = findingText,
            timestamp = os.date("%Y-%m-%d %H:%M:%S"),
            session = host:session_id()
        })
        print("Finding recorded!")

    elseif name == "show-notes" then
        print("=== Notes ===")
        for i, note in ipairs(notes) do
            print(note.timestamp .. ": " .. note.text)
        end
        print("\n=== Findings ===")
        for i, finding in ipairs(findings) do
            print(finding.timestamp .. " [" .. finding.session .. "]: " .. finding.text)
        end
    end
end

Usage:

>> note Remember to check port 8080
Note saved!
>> finding SQL injection in login form
Finding recorded!
>> show-notes
=== Notes ===
2024-11-14 10:30:15: Remember to check port 8080
=== Findings ===
2024-11-14 10:31:22 [test-abc123]: SQL injection in login form

Example 2: Workflow Automation

-- workflow.lua
function init()
    host:register_command("recon-workflow", "Run full reconnaissance workflow")
end

function on_command_invoke(name, tokens)
    if name == "recon-workflow" then
        local target = tokens[2]
        if not target then
            print("Usage: recon-workflow <target>")
            return
        end

        print("=== Starting Reconnaissance Workflow ===")
        print("Target: " .. target)
        print("")

        print("[1/4] Port scan...")
        host:exec("run --custom 'nmap -sV " .. target .. "'")

        print("[2/4] Web enumeration...")
        host:exec("run --custom 'whatweb " .. target .. "'")

        print("[3/4] Directory brute force...")
        host:exec("run --custom 'dirb http://" .. target .. "'")

        print("[4/4] Vulnerability scan...")
        host:exec("template nikto --ip " .. target)

        print("")
        print("=== Workflow Complete ===")
    end
end

Usage:

>> recon-workflow 192.168.1.100
=== Starting Reconnaissance Workflow ===
Target: 192.168.1.100

[1/4] Port scan...
[... nmap output ...]
[2/4] Web enumeration...
[... whatweb output ...]
...

Example 3: Enhanced Tab Completion

-- completions.lua
local commonPorts = {"21", "22", "23", "25", "80", "443", "3389", "8080"}
local scanTypes = {"quick", "full", "stealth", "aggressive"}

function init()
    host:register_command("scan", "Network scan with options")
    host:register_option("scan", "--type", "Scan type", scanTypes)
    host:register_option("scan", "--port", "Target port", commonPorts)

    -- Add general completions
    host:add_completion("--port", commonPorts)
    host:add_completion("--type", scanTypes)
end

function on_complete(fullLine, position)
    -- Provide context-aware completions
    if fullLine:match("scan .* %-%-target ") then
        return {"192.168.1.1", "10.0.0.1", "localhost"}
    end

    if fullLine:match("^run %-%-custom ") then
        return {"nmap", "nikto", "dirb", "gobuster"}
    end

    return {}
end

Security and Limitations

Sandbox Restrictions

Lua scripts run in a hardened sandbox with the following restrictions:

  • No file I/O: Cannot read or write files directly
  • No network access: Cannot make external network connections
  • No process execution: Cannot spawn processes (except via host:exec())
  • No library loading: Cannot use require() or load external Lua modules
  • Limited OS access: Only os.date() and time functions available

Execution Timeout

Each Lua function call has a 5-second timeout. If a function runs longer, it will be terminated.

Safe Session Data Access

The host:get_session_data() function only allows access to safe, non-sensitive session properties.


Best Practices

1. Initialize in init()

function init()
    -- Register all commands here
    -- Set up global state
    -- Add tab completions
end

2. Handle Missing Arguments

function on_command_invoke(name, tokens)
    if name == "mycommand" then
        if #tokens < 2 then
            print("Usage: mycommand <argument>")
            return
        end
        -- Process command
    end
end

3. Use Global State for Persistence

-- Global variables persist across function calls
local myData = {}

function on_command_invoke(name, tokens)
    if name == "save-data" then
        table.insert(myData, tokens[2])
    elseif name == "show-data" then
        for i, item in ipairs(myData) do
            print(i .. ": " .. item)
        end
    end
end

4. Log Important Events

function on_before_execute(line)
    host:log("Executing: " .. line)
end

5. Provide User Feedback

function on_command_invoke(name, tokens)
    if name == "process-data" then
        print("Processing data...")
        -- Do work
        print("✓ Complete!")
    end
end

Debugging Lua Scripts

Enable Verbose Logging

function init()
    host:log("Script version 1.2")
    host:log("Registered commands: mycommand1, mycommand2")
end

Check for Errors

If your script doesn't load, check the console output for error messages:

Error loading Lua script 'myscript.lua': [line 5] syntax error near '='

Test Individual Functions

function init()
    host:log("Testing session ID: " .. host:session_id())
    local result = host:exec("help")
    host:log("Exec test result: " .. result)
end

Next Steps