Bindings

Styx offers API bindings for both Python and C.

Python Bindings

Styx provides Python bindings of a portion of its API to enable developers to quickly develop on top of the Styx Emulator library.

Installation

Styx Python bindings are available as a Python wheel. Use your favorite python package manager to install from sources, preferably in a virtual environment. Bulding the wheel requires rust, clang, cmake, protoc, and libprotobuf-dev to be installed.

python3 -m virtualenv venv
. venv/bin/activate
pip install ./styx/bindings/styx-py-api/

Examples

Two examples are available in ./styx/bindings/styx-py-api/examples/.

  • simple-stm32f107

    • Building and starting an ARM processor with a single code hook.

  • uart-kinetis21

    • ARM processor with UART communication, instruction counting, and basic block tracking.

Getting Started

The styx_emulator package is organized into modules. The root styx_emulator module has no functions or classes associated with it. The following modules are available:

processor
  - Processor, ProcessorBuilder
cpu
  - CpuBackend, ArchEndian
cpu.hooks
  - hook definitions
cpu.arch
  - Arch enum
cpu.arch.arm
  - ARM registers and variants
cpu.arch.ppc32
  - PowerPC32 registers and variants
cpu.arch.blackfin
  - Blackfin registers and variants
cpu.arch.superh
  - SuperH registers and variants
executor
  - executors
loader
  - firmware/program loaders
plugin
  - processor plugins
peripherals
  - peripheral clients
angr
  - angr integration

For a minimal example, we’ll first import some basic classes.

from styx_emulator.cpu import ArchEndian, Backend, CpuBackend
from styx_emulator.cpu.hooks import CodeHook
from styx_emulator.processor import ProcessorBuilder, Target
from styx_emulator.loader import RawLoader
from styx_emulator.executor import DefaultExecutor
from styx_emulator.arch.arm import ArmVariant
from styx_emulator.plugin import ProcessorTracingPlugin

To create a processor, use the ProcessorBuilder class. The api is similar to the Rust api, instead using setters on attributes. The builder allows you to configure aspects of the cpu and how it will connect to outside peripherals.

TARGET_PROGRAM = "../../../../../data/test-binaries/arm/kinetis_21/bin/freertos_hello/freertos_hello_debug.bin"

# create a new processor builder
builder = ProcessorBuilder()

builder.endian = ArchEndian.LittleEndian
builder.target_program = TARGET_PROGRAM
builder.ipc_port = 16001
builder.loader = RawLoader()
builder.executor = DefaultExecutor()
builder.variant = ArmVariant.ArmCortexM4
builder.backend = Backend.Unicorn
builder.add_plugin(ProcessorTracingPlugin())
# build the processor
proc = builder.build(Target.Kinetis21)

proc now holds a built Processor for you to use for emulation. At this point you can use proc.start() to start emulating. We have no hooks yet though so it would be uninteresting.

This target program is a FreeRTOS example that sends “Hello World\r\n” through the serial debug port (UART port 5 to be exact). To see this in action, let’s define a hook callback on the IO_Transfer function to print the character that’s being sent. Hook callbacks are simply python functions with specific signatures that are called when an event happens in the processor. They are analogous to hooks in the Rust api.

The signature for a code hook is.

CodeHook(start: int, end: int, callback: Any)

Styx will execute the passed callback with a single argument CpuBackend when the cpu executes an instruction in the address range (start, end].

Let’s add our code hook.

IO_TRANSFER_ADDR = 0x00001AE8
def io_transfer(cpu: CpuBackend):
    """
    Code hook, called when target transfers serial character data.

    Used to show when target sends UART message over the line.
    """
    # character to send stored in r2
    c = cpu.read_register("r2")
    if c:
        print(f"target sent {c.to_bytes(1)}")
    else:
        print("no register r2 in target")


proc.add_hook(CodeHook(IO_TRANSFER_ADDR, IO_TRANSFER_ADDR, io_transfer))

This made a code hook that triggers when the processor’s program counter is in the range (0x00001AE8, 0x00001AE8], which simplifies to the single address 0x00001AE8. CodeHook is just one example of a hook, the other hooks currently available in the python bindings are listed below:

  • CodeHook

    • Triggered on execution in a range of program counter addresses.

  • BlockHook

    • Triggered on hitting a new basic block.

  • MemWriteHook

    • Called on a memory write to a range of addresses.

  • MemReadHook

    • Called on a memory read to a range of addresses.

  • InterruptHook

    • Called on an cpu interrupt triggered.

  • InvalidInsnHook

    • Called on an invalid instruction encountered

The hook callbacks can have different signatures. ( To run the processor:

proc.start()

This blocks the current python execution thread but allows multithreaded Python code to run while the cpu is executing. This code and more is available in the uart-kinetis21 styx-py-api example.

Peripherals

Some styx peripherals are included as python bindings for easy use. Currently only the UartClient is available.

DEBUG_UART_PORT = 5
IPC_PORT = 16001
client = UartClient(f"http://127.0.0.1:{IPC_PORT}", DEBUG_UART_PORT)

# check if any bytes are received
num_bytes_to_check = 1
recv_bytes = client.recv_nonblocking(num_bytes_to_check)
if recv_bytes != None:
    print("got a byte!")

# send some bytes over the line
client.send(b'Hello again\n').

The UartClient will throw an exception if it could not connect.

Symbion

Angr Symbion allows interleaved analysis of concrete and symbolic execution. In this setup, Styx can complete concrete execution of of complex initialization or external state (peripherals) and then use angr’s symbolic execution to complete the analysis.

Symbion support for Styx in an experimental stage. Check out styx/bindings/styx-py-api/styx_symbion and the current example styx/bindings/styx-py-api/styx_symbion/tests/test1.py.