# Script editor

The script editor is accessed using the `F8` shortcut and allows you to edit and run user script and leverage the full power of Malcat’s Scripting engine. You also have access to the analysis error log inside the Console window.

## Interface

The script editor is composed of two panes:

- The left pane is the script text editor and allows you to edit python scripts

- The right pane is the console window and displays script outputs (and the analysis log)

Between the two panes you will find a few buttons to open and run scripts, or hide the left or right pane:

The script editor interface

### Text editor

The left pane is a standard text editor with python syntax highlighting based on the Scintilla text editor. It has most of the features you would expect from a small code editor:

- Text editing with standard keyboard and mouse shortcuts

- Syntax highlighting

- Unlimited undo / redo with modified/saved text highlighting

- Line numbering

By default, the text editor displays Malcat’s toy script located at `static/sample_script.py`. This script demonstrates most of Malcat’s Scripting capabilities and is meant to be run on a PE file. You can modify this script as you wish to play with Malcat bindings, but keep in mind it is read-only: you won’t be able to save it in-place (only under a different name).

You may load different scripts using ``Ctrl`+`U`` (Malcat comes with a bunch of them, sorted by file type) or create your own. Saving is performed using ``Ctrl`+`S``. In addition tot he usual text editor shortcuts, you have access to the ones listed below:

Shortcut | Action
--- | ---
``Ctrl`+`U`` | Load a User script
``Ctrl`+`S`` | Save current script
``Ctrl`+`Z`` | Undo last change
``Ctrl`+`Y`` | Redo change
``Ctrl`+`Enter`` | Run current script

### Console window

The console window has two main usages. When no script has been run, the console windows displays the analysis output log. This is pretty useful if you are modifying parts of Malcat (cf. What can be overridden / changed). In case of an error, e.g. a faulty Yara rule or a badly written anomaly, you will be able to see the error / stack trace there. Additionally, if you print something to stdout from a python file (from an anomaly function or a file type parser), it will also be displayed in this window as a warning.

The script output window displaying the output of the CAPA script

When a script is run, the content of the console window is first cleared and then used to display the standard output of the script (everything that you print). If an exception is thrown, its stack trace will also be displayed in red. Note that using the Additional GUI bindings, Malcat scripts can also display formatted text. Those text features color and more importantly clickable addresses: a virtual or physical address will be displayed as a clickable field. If the user clicks on it he will be taken to the corresponding address in one of Malcat’s Views.

## The scripting menu

Some objects/annotations in Malcat have a Scripting entry in their context menu. This context menu should assist you when writing scripts in the script editor. For instance, if you right-click on a field of structure, the Scripting context menu will offer you to:

- copy the code responsible for accessible this field in python into the script editor

- open the documentation for this object

This should make writing scripts a little bit easiers for people new to the Scripting engine. Currently, we have added it for structure fields and functions, but we’ll add support for more scriptable objects in future releases.

The scripting menu for a structure field

## Extend Malcat

### Write & run script

In order to write scripts, please refer to our Scripting documentation. Running your script is just a matter of clicking on the run button or hitting ``Ctrl`+`Enter``. The script will be run in a background thread to keep the user interface reactive.

A running gauge will then appear left of the script that indicates the overall progress script of the script (if the script uses `bindings.UI.progress()` accordingly). The run script button will also be changed to a stop script button that allows you to ask the script for its termination.

Note

For technical reasons, Malcat’s GUI is not able to always enforce the script termination (because of threading and potential memory leaks). The stop button will only work if the user script calls one of the `bindings.UI` functions on a regular basis. Otherwise, you’ll have to close Malcat to force the script to close. But we are working on this!

### Additional GUI bindings

In addition to Malcat’s Scripting interface, user scripts have access to one addition python objects: the GUI bindings (`gui`). This instance of `bindings.UI` is only available to scripts (not to anomalies or file parsers for instance) and allows the script to somewhat interact with the user interface. Please note that our UI bindings are work in progress and will change (for the better) in the future.

gui: malcat.UI

  The `gui` object gives user script access to Malcat’s user interface

Currently the bindings are a bit limited. You can find their definition below:

class malcat.UI

  This class give you access to additional functionnalities exclusive to user scripts, mainly user interface bindings.

  msgbox(text)

    Display a message box containing the text text

        gui.msgbox("Hello world")

    Parameters:

      name (str) – name of the function you are looking for

    Return type:

  progress(advancement)

    Set the value of the gauge control in the script editor. Advancement must be a value between 0 and 100 and can’t go backward.

        gui.msgbox("Hello world")

    Parameters:

      advancement (int) – a number between 0 (script just started) and 100 (script finished)

    Return type:

  idle()

    If you never call `progress()` in your script, you should call `idle()` on a regular basic (if your script runs for a long time at least). This gives control back to the user interface in order to check if the user requested the script to stop

        for i in range(1000000):

            # ... something CPU intensive

            if i % 1000 == 0:
                gui.idle()

    Return type:

  print(what, format=True, **kwargs)

    This function works like the usual python print method, but it accepts an additional format parameter. When set to true, the string can contain additional wiki like formatting blocks that will be rendered with special code in the console window:

    - `[rva]<hex number>[/rva]`: make an RVA address clickable

    - `[va]<hex number>[/va]`: make an virtual address clickable

    - `[fa]<hex number>[/fa]`: make a file offset clickable

    - `[url]<text>[/url]`: make an http url clickable (link will open in default browser)

    - `[colorN]<text>[/colorN]`: N = 0…7 : will print the text with Malcat’s theme color N foreground (c.f. Palette colors)

    - `[blockN]<text>[/blockN]`: N = 0…7 : will print the text with Malcat’s theme color N background (c.f. Palette colors)

    Example:

        # you can use gui.print instead of print to add formatting
        ep_rva = malcat.struct['OptionalHeader']['AddressOfEntryPoint']
        if ep_rva is not None:
            gui.print(f"RVA @[rva]{ep_rva:x}[/rva] has offset=#[fa]{malcat.map.to_phys(malcat.map.from_rva(ep_rva)):x}[/fa] ", format=True)

    Parameters:

      - what (str) – the text to print

      - format (bool) – if True, wiki-like blocks will be interpreted

      - kwargs (dict) – any other parameter accepted by python’s print function

    Return type:

  open_after(data, nice_name='from_script', forced_filetype='')

    Open a buffer as a new file in Malcat once the script has finished. Only one file can be opened this way: only the latest buffer will be open. This is how our unpack script are working. For instance, this is the office decryption plugin (based on msoffcrypto):

        for password in ['VelvetSweatshop', '123', '1234', '12345', '123456', '4321', 'Password1234_']:
            print("Trying password {} ...".format(password))
            try:
                inp = BytesIO(malcat.file.read(0, malcat.file.size))
                f = msoffcrypto.OfficeFile(inp)
                f.load_key(password=password)
                outp = BytesIO()
                f.decrypt(outp)
                gui.open_after(outp.getvalue(), "Decrypted")
                break
            except BaseException as e:
                print(e)

    Parameters:

      - data (bytes) – name of the function you are looking for

      - what (str) – the text to print

      - format (bool) – if True, wiki-like blocks will be interpreted

    Return type: