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:
Pro.Shell.Shell.setScanProvider()— attach a scan provider. This is the usual entry point; the object, format domains, hashes and scan metadata all follow from it.Pro.Shell.Shell.setObject()— attach a bare loaded object when there is no scan provider.Pro.Shell.Shell.setCarbon()— attach a Carbon disassembler context, enabling thecarbon.*domain.Pro.Shell.Shell.enableUI()— signal 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.Pro.Shell.Shell.enableVariables()— enable or disable variable assignment. Disable it to restrict an agent to function calls only.Pro.Shell.Shell.setVerbose()— enable or disable diagnostic warnings.
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,
errorisNoneandresultholds the value. Withformatted=Truethe value is a display string (integers annotated with hex, strings quoted, and so on); withformatted=Falseit is the raw Python value.On failure,
resultisNoneanderroris 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.functionfor a domain member.params— an ordered list of parameters. Each entry is a dictionary withnameand optionaltype,defaultanddescrkeys (a parameter without adefaultis required).impl— the implementation, called asimpl(shell, *args, **kwargs).descr— the one-line description shown byhelp.returns— an optional dictionary describing the return value (type,descrand, for structured results, nestedfields).attrs— trailing flags; setabort=Truefor a cancellable command andprogress=Truefor 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 forobj, optional filter forscan) — 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.pyor.pycsuffix 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:
Pro.Shell.Shell.refreshConfig()— reload immediately.Pro.Shell.Shell.resetConfig()— mark the configuration stale so it reloads on the next command. UnlikePro.Shell.Shell.refreshConfig()it does no work itself, so it is safe to call from any context, for example right after the scan provider or object changes.
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":
Pro.Shell.Shell.setWritePolicy()/Pro.Shell.Shell.getWritePolicy()— read or set the policy.Pro.Shell.Shell.setWritePrompter()— install a callbackprompter(domain, description)that returns"yes","no","always"or"never". PassingNonerestores the default prompter (a UI dialog when a user interface is present, otherwise a console prompt)."always"and"never"are remembered for the rest of the session.
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:
Pro.Shell.Shell.getScanProvider()— the current scan provider, orNone.Pro.Shell.Shell.getObject()— the current loaded object, orNone.Pro.Shell.Shell.getCarbon()— the current Carbon disassembler context, orNone. This may instantiate the context on first use.
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 asPro.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 withrunLine(). 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
poswithinline.
describeDomain(domain, descr)Attaches the one-line description shown next to a domain in the top-level
helplisting.
enableUI([enable])Signals that a user interface is present.
enableVariables([enable])Enables or disables variable assignment in the language.
Retrieves the current Carbon disassembler context.
getCompletions(prefix)Retrieves the completion candidates for a bare prefix.
Retrieves the current loaded object.
Retrieves the current scan provider.
Retrieves the wait object of the command currently being executed.
getWritePolicy(domain)Retrieves the write-consent policy of a domain.
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.
Marks the configuration stale so that it is reloaded on the next command.
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:
Dictionary for per-domain caches which survives across commands.
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
poswithinline.
- Parameters
line (str) – The current input line.
pos (int) – The cursor position within the line.
- Returns
Returns a dictionary with the keys
start,end,wordandmatches, identifying the word boundaries to replace and the candidate list; orNonefor 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
helplisting.
- 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
paramsis a dictionary with anamekey and optionaltype,defaultanddescrkeys; a parameter without adefaultis required. The implementation is called asimpl(shell, *args, **kwargs)and must be read-only with respect to the analyzed object unless it requests write consent (seerequestWrite()).
- Parameters
name (str) – The function name; use
domain.functionfor 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,descrand nestedfields).attrs – Trailing flags: set
abort=Truefor a cancellable command andprogress=Truefor one that reports progress.See also
describeDomain()andresetFunctions().
- 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
Trueif the write may proceed.- Return type
bool
See also
setWritePolicy()andsetWritePrompter().
- 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
Nonefor the error), on failureNoneand 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
Trueif 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. PassingNonerestores 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.