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
- Save your
.luafile toPlugins/Lua/ - Start the console in interactive mode:
bash RTFMv2.Console.exe - Your script loads automatically
- 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)
Print Function
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
- Explore included example scripts in
Plugins/Lua/ - See Console Commands for commands you can execute via
host:exec() - Learn about Node-RED Integration for visual automation
- Return to Console Overview