Pro.Shell — API for the Cerbero Shell language

Overview

The Pro.Shell module embeds the Cerbero Shell — the small, read-only command language described in the language guide — in a plugin or standalone script.

The single entry point is the Shell class. A shell is wired to the object being analyzed (a scan provider, a loaded object, and/or a Carbon disassembler context), after which lines of the shell language are executed with Pro.Shell.Shell.runLine(). Plugins can also extend the language with their own functions.

Because the language never executes Python code and every function is read-only unless it explicitly asks for write consent, a shell is safe to expose to an untrusted caller such as an automated agent.

This page documents only the API most useful to plugin authors. The functions callable from within the language (pe.*, scan.*, and so on) are documented by the help command and the language guide, not here.

Creating and Wiring a Shell

A shell is created with no arguments and then attached to the current analysis context. The most common case is to attach a scan provider:

from Pro.Core import Report, LocalSystem, ScanEngine, ScanProvider
from Pro.Shell import Shell

def openShell(fname):
    r = Report()
    r.openDatabase("")
    s = LocalSystem()
    s.addFile(fname)
    se = ScanEngine(r)
    se.setSystem(s)
    se.scan()
    sp = se.getScanProvider(ScanProvider.createObjectIdentifier(fname))
    sh = Shell()
    sh.setScanProvider(sp)
    return sh

The wiring methods are:

The set of function domains exposed to the language is derived from this context automatically the next time a line is run.

Running Commands

Pro.Shell.Shell.runLine() executes a single line and returns a (result, error) tuple. Exactly one element is set:

  • On success, error is None and result holds the value. With formatted=True the value is a display string (integers annotated with hex, strings quoted, and so on); with formatted=False it is the raw Python value.

  • On failure, result is None and error is a message string. Pro.Shell.Shell.runLine() never raises for a malformed command — the error is always returned.

out, err = sh.runLine("scan.hashes", formatted=True)
if err:
    print("error:", err)
else:
    print(out)

# unformatted: the actual dict
hashes, _ = sh.runLine("scan.hashes")

The wo keyword argument accepts a wait object for long-running commands so the operation can report progress and be cancelled.

Auto-Completion

For interactive front-ends, Pro.Shell.Shell.complete() computes completions for the cursor position pos within line. It returns a dictionary with the word boundaries to replace and the candidate list, or None for invalid input. Pro.Shell.Shell.getCompletions() is a lower-level helper returning the candidate names for a bare prefix.

Extending the Language with Custom Functions

A plugin can register its own functions, optionally grouped into a new domain. An implementation receives the shell as its first argument and returns an ordinary value (string, integer, list, dict, …); it must be read-only with respect to the analyzed object.

def _read_u32(shell, offset):
    obj = shell.getObject()
    return obj.ReadUInt32(offset)

sh.describeDomain("mytool", "custom analysis helpers")
sh.registerFunction(
    "mytool.read_u32",
    [{"name": "offset", "type": "integer", "descr": "byte offset"}],
    _read_u32,
    "Reads a 32-bit little-endian integer at an offset",
    returns={"type": "integer", "descr": "the value at the offset"})

The parameters of Pro.Shell.Shell.registerFunction() are:

  • name — the callable name, domain.function for a domain member.

  • params — an ordered list of parameters. Each entry is a dictionary with name and optional type, default and descr keys (a parameter without a default is required).

  • impl — the implementation, called as impl(shell, *args, **kwargs).

  • descr — the one-line description shown by help.

  • returns — an optional dictionary describing the return value (type, descr and, for structured results, nested fields).

  • attrs — trailing flags; set abort=True for a cancellable command and progress=True for one that reports progress.

Pro.Shell.Shell.describeDomain() attaches the one-line description shown next to a domain in the top-level help listing. Call it once per domain you register.

Pro.Shell.Shell.resetFunctions() removes all functions that are not built in, preserving the built-ins and variables.

User Configuration: shell.cfg

Registration always happens through Pro.Shell.Shell.registerFunction() — what varies is who owns the shell it is called on. When embedding, the registrar owns the shell: you call the method on the instance you created. With the shell.cfg file in the user configuration directory, functions are instead registered on instances owned by the host — the shell view, the workspaces, the cshell tool: each section of the file declares a provider module, and every shell whose context matches imports that module and passes itself to the module’s init function — which then registers the functions on the shell it received, exactly as shown above. The file uses the INI format; each section declares one provider:

[MyPEHelpers]
context = obj
formats = pe
file = mypehelpers.py
init = initShell

[MyTriage]
context = scan
file = mytriage.py
init = initShell

The keys are:

  • context (mandatory) — when the provider applies. One of:

    • obj — an object of a matching format is loaded;

    • scan — a scan provider is present;

    • carbon — a Carbon disassembly context is present;

    • ui — a user interface is present;

    • fs — the loaded object exposes a file system.

  • formats (mandatory for obj, optional filter for scan) — a |-separated list of format names, matched case-insensitively against the loaded object’s format (e.g. pe|elf|macho).

  • file (mandatory) — the Python module with the provider. A .py or .pyc suffix is accepted and stripped; the module is imported by name, so it must be importable (for instance from a package or the user directory).

  • init (mandatory) — the name of a function in that module. It is called with the shell as its only argument and registers the provider’s functions:

# mypehelpers.py
def _entry_rva(shell):
    return shell.getObject().EntryPoint()

def initShell(shell):
    shell.describeDomain("mype", "my PE helpers")
    shell.registerFunction("mype.entry_rva", [], _entry_rva,
                           "Returns the entry-point RVA",
                           returns={"type": "integer", "descr": "the RVA"})

Sections whose context does not match the current state are skipped, so a provider registered for pe objects simply does not exist while an ELF is loaded. The file is re-read automatically whenever its modification time changes, and on every reload the previously cfg-registered functions are removed first — editing shell.cfg never requires restarting the host.

Reloading the Configuration

The shell loads the user configuration (and re-derives the exposed domains from the current context) lazily, on the first command after the configuration becomes stale. Two methods control this:

Write Permissions

Most domains are strictly read-only. A domain that offers mutating operations (such as the Carbon rename or make_code functions) must gate every mutation behind a consent check, so that an agent driving the shell cannot change the analyst’s database without approval.

An implementation calls Pro.Shell.Shell.requestWrite() and proceeds only if it returns True:

def _rename(shell, address, name):
    if not shell.requestWrite("carbon", 'rename %s to "%s"' % (hex(address), name)):
        return False
    # ... perform the rename ...
    return True

The per-domain policy is "ask" (the default, prompt for each command), "always" or "never":

These methods are host-side API only — they are never registered as shell functions, so a caller of the language cannot authorize its own writes. Any error from the prompter, or the absence of an interactive console, denies the write (fail-safe).

Context Access for Function Implementations

Inside a function implementation, fetch the live context from the shell on every call — never capture it — so that a context change cannot leave a dangling reference:

Two instance attributes are also available to implementations:

  • shell.context — a dictionary for per-domain caches that survives across calls. It is cleared when the underlying context changes.

  • shell.wo — the wait object for the current command (also available as Pro.Shell.Shell.getWaitObject()), for progress reporting and cancellation checks in long-running functions.

Module API

Pro.Shell module API.

Classes:

Shell()

The Cerbero Shell: a safe command language for computations and for inspecting the object being analyzed.

class Shell

The Cerbero Shell: a safe command language for computations and for inspecting the object being analyzed.

A shell is created without arguments, wired to the current analysis context (setScanProvider(), setObject(), setCarbon()) and driven with runLine(). The set of function domains exposed to the language is derived from the context automatically.

Methods:

complete(line, pos)

Computes auto-completion candidates for the cursor position pos within line.

describeDomain(domain, descr)

Attaches the one-line description shown next to a domain in the top-level help listing.

enableUI([enable])

Signals that a user interface is present.

enableVariables([enable])

Enables or disables variable assignment in the language.

getCarbon()

Retrieves the current Carbon disassembler context.

getCompletions(prefix)

Retrieves the completion candidates for a bare prefix.

getObject()

Retrieves the current loaded object.

getScanProvider()

Retrieves the current scan provider.

getWaitObject()

Retrieves the wait object of the command currently being executed.

getWritePolicy(domain)

Retrieves the write-consent policy of a domain.

refreshConfig()

Reloads the user configuration immediately and re-derives the exposed domains from the current context.

registerFunction(name, params, impl[, …])

Registers a function callable from the language.

requestWrite(domain, description)

Asks for consent to perform a mutating operation.

resetConfig()

Marks the configuration stale so that it is reloaded on the next command.

resetFunctions()

Removes all registered functions which are not built-in, preserving the built-ins and the variables.

runLine(line, *[, wo, formatted])

Executes a single line of the shell language.

setCarbon(carbon)

Attaches a Carbon disassembler context, enabling the carbon.* domain.

setObject(obj)

Attaches a bare loaded object when no scan provider is available.

setScanProvider(sprovider)

Attaches a scan provider.

setVerbose([enable])

Enables or disables diagnostic warnings.

setWritePolicy(domain, policy)

Sets the write-consent policy for a domain: "ask" (the default; prompt for each command), "always" or "never".

setWritePrompter(prompter)

Installs a custom consent prompter, called as prompter(domain, description) and expected to return "yes", "no", "always" or "never".

Attributes:

context

Dictionary for per-domain caches which survives across commands.

wo

The wait object of the command currently being executed, or None.

complete(line: str, pos: int)Optional[dict]

Computes auto-completion candidates for the cursor position pos within line.

Parameters
  • line (str) – The current input line.

  • pos (int) – The cursor position within the line.

Returns

Returns a dictionary with the keys start, end, word and matches, identifying the word boundaries to replace and the candidate list; or None for invalid input.

Return type

Optional[dict]

context: dict

Dictionary for per-domain caches which survives across commands. It is cleared when the underlying context changes.

describeDomain(domain: str, descr: str)bool

Attaches the one-line description shown next to a domain in the top-level help listing.

Parameters
  • domain (str) – The domain name.

  • descr (str) – The description.

Returns

Returns True.

Return type

bool

enableUI(enable: bool = True)None

Signals that a user interface is present. When enabled, the shell can resolve the current scan provider from the UI and write-consent prompts are shown as dialogs instead of console prompts.

Parameters

enable (bool) – Whether UI support is available.

enableVariables(enable: bool = True)None

Enables or disables variable assignment in the language. Disable it to restrict an agentic caller to function calls only.

Parameters

enable (bool) – Whether variables can be assigned.

getCarbon()Optional[Pro.Carbon.Carbon]

Retrieves the current Carbon disassembler context. The context may be instantiated on first use.

Returns

Returns the Carbon instance or None.

Return type

Optional[Carbon]

getCompletions(prefix: str)list

Retrieves the completion candidates for a bare prefix.

Parameters

prefix (str) – The prefix to complete.

Returns

Returns the list of candidate names.

Return type

list

getObject()Optional[Pro.Core.CFFObject]

Retrieves the current loaded object.

Returns

Returns the object or None.

Return type

Optional[CFFObject]

getScanProvider()Optional[Pro.Core.ScanProvider]

Retrieves the current scan provider. Function implementations must fetch the context from the shell on every call instead of capturing it.

Returns

Returns the scan provider or None.

Return type

Optional[ScanProvider]

getWaitObject()Optional[Pro.Core.NTIWait]

Retrieves the wait object of the command currently being executed.

Returns

Returns the wait object or None.

Return type

Optional[NTIWait]

getWritePolicy(domain: str)str

Retrieves the write-consent policy of a domain.

Parameters

domain (str) – The domain name.

Returns

Returns "ask", "always" or "never".

Return type

str

refreshConfig()None

Reloads the user configuration immediately and re-derives the exposed domains from the current context.

See also resetConfig().

registerFunction(name: str, params: list, impl: Callable, descr: str = '', returns: Optional[dict] = None, **attrs)None

Registers a function callable from the language.

Each parameter in params is a dictionary with a name key and optional type, default and descr keys; a parameter without a default is required. The implementation is called as impl(shell, *args, **kwargs) and must be read-only with respect to the analyzed object unless it requests write consent (see requestWrite()).

Parameters
  • name (str) – The function name; use domain.function for a domain member.

  • params (list) – The ordered parameter list.

  • impl (Callable) – The implementation callable.

  • descr (str) – The one-line description shown by help.

  • returns (dict) – Optional description of the return value (type, descr and nested fields).

  • attrs – Trailing flags: set abort=True for a cancellable command and progress=True for one that reports progress.

See also describeDomain() and resetFunctions().

requestWrite(domain: str, description: str)bool

Asks for consent to perform a mutating operation. A mutating function implementation must call this method and proceed only when it returns True.

Depending on the per-domain policy the request is granted, denied, or submitted to the user (as a dialog when a UI is present, otherwise as a console prompt). Any error while prompting denies the request.

Parameters
  • domain (str) – The domain requesting the write.

  • description (str) – A short description of the operation, shown to the user.

Returns

Returns True if the write may proceed.

Return type

bool

See also setWritePolicy() and setWritePrompter().

resetConfig()None

Marks the configuration stale so that it is reloaded on the next command. Unlike refreshConfig() this method does no work itself, which makes it safe to call from any context, for instance right after the scan provider or the object changes.

resetFunctions()None

Removes all registered functions which are not built-in, preserving the built-ins and the variables.

runLine(line: str, *, wo: Optional[Pro.Core.NTIWait] = None, formatted: bool = False)Tuple[Any, Optional[str]]

Executes a single line of the shell language.

Exactly one element of the returned tuple is set: on success the result (and None for the error), on failure None and the error message. This method never raises for a malformed command.

Parameters
  • line (str) – The command line to execute.

  • wo (NTIWait) – Optional wait object for progress and cancellation.

  • formatted (bool) – If True, the result is returned as a display string; otherwise the raw value is returned.

Returns

Returns a (result, error) tuple.

Return type

tuple

setCarbon(carbon: Pro.Carbon.Carbon)None

Attaches a Carbon disassembler context, enabling the carbon.* domain.

Parameters

carbon (Carbon) – The Carbon instance.

setObject(obj: Pro.Core.CFFObject)None

Attaches a bare loaded object when no scan provider is available.

Parameters

obj (CFFObject) – The object.

setScanProvider(sprovider: Pro.Core.ScanProvider)None

Attaches a scan provider. This is the usual entry point: the loaded object, the format domains, the hashes and the scan metadata all follow from it.

Parameters

sprovider (ScanProvider) – The scan provider.

setVerbose(enable: bool = True)bool

Enables or disables diagnostic warnings.

Parameters

enable (bool) – Whether warnings are printed.

Returns

Returns True.

Return type

bool

setWritePolicy(domain: str, policy: str)bool

Sets the write-consent policy for a domain: "ask" (the default; prompt for each command), "always" or "never".

Parameters
  • domain (str) – The domain name.

  • policy (str) – The policy.

Returns

Returns True if the policy is valid.

Return type

bool

setWritePrompter(prompter: Optional[Callable])None

Installs a custom consent prompter, called as prompter(domain, description) and expected to return "yes", "no", "always" or "never". "always" and "never" are remembered for the rest of the session. Passing None restores the default prompter.

Parameters

prompter (Optional[Callable]) – The prompter callable, or None.

wo: Optional[Pro.Core.NTIWait]

The wait object of the command currently being executed, or None. Long-running function implementations use it to report progress and check for cancellation.