# Maat: a dynamic symbolic execution engine for binaries

> Maat is a C++ binary analysis framework that explores a program's paths symbolically rather than only one path at a time, with taint analysis, constraint solving, environment simulation and Python bindings. It lifts instructions with Ghidra's sleigh library, which is where its multi-architecture reach comes from, and its last tagged release dates from 2022.

**trailofbits/maat** — Open-source symbolic execution framework: https://maat.re

- Repository: https://github.com/trailofbits/maat
- Stars: 651 · Forks: 45
- Language: C++
- License: NOASSERTION
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/trailofbits-maat

## Exploration rather than tracing

Maat is described as an open-source Dynamic Symbolic Execution and Binary Analysis framework, and the list of functionalities in the README gives the scope: symbolic execution, taint analysis, constraint solving, binary loading, environment simulation, and lifting of assembly through Ghidra's sleigh library, which the README describes as a feature of the framework.

The distinction that matters is between tracing and exploring. A debugger follows one path through a program and shows you what happened. A symbolic execution engine makes the program's inputs symbolic, so the conditions in the program become constraints over those inputs, and a constraint solver decides which paths are reachable. The result is that you can ask questions about a binary that a single run cannot answer, such as what input reaches a given branch, or which values make a comparison succeed.

That capability is why such frameworks exist in the security research world: they support the analysis of binaries when the source is not available, and they let a researcher reason about input handling rather than guess at it.

The implementation is C++, which the README explains is for runtime performance, and it notes that there are hardly any runtime dependencies and most of them are optional. The API is described as flexible and debugger-like, with its features configurable to adapt to different use cases, and it ships with Python bindings, which the README jokes is what any self-respecting modern framework does. The project site is maat.re, and the tutorials and both the Python and C++ API documentation live there.

The audience is therefore people who already know binary analysis: reverse engineers, security researchers and the developers of analysis tools who want an engine rather than another tool.

## Two installation routes, and only one of them is quick

The install section is short because there are exactly two paths, and they have very different costs.

The first is the Python module, and it is a single command:

```bash
python3 -m pip install pymaat
```

That gets you the pymaat package, which is how most users meet Maat. The bindings are the intended interface for scripting analysis, and the README's own example is Python.

The second route is the native SDK, for using the C++ API directly, and the README sends you to BUILDING.md rather than to a command. That asymmetry is deliberate and worth understanding before you choose. The Python route is a packaged distribution, so somebody has already done the work of building the engine, bundling it and making it importable. The native route means compiling the engine yourself, with all the dependencies that entails.

The repository layout shows the shape of that work: src/ for the C++ sources, bindings/ for the language bindings, cmake/ and CMakeLists.txt with CMakePresets.json for the build system, generate/ for code generation, tests/ for the test suite, resources/ for data, and a .gitmodules file indicating that at least one dependency is pulled in as a git submodule. There is a PACKAGING.md for the distribution side, a Dockerfile, and a Version.txt holding the current version string.

So the decision is simple: if you want to script analysis, install pymaat and start there. If you need the C++ API, or you need to change the engine itself, you are building from source, and the README's build documentation is the place to spend your time.

## The example shows the whole API in one page

The example in the README is worth reading in full, because the shape of the API is the whole design of the framework.

The engine is created for a target architecture and operating system:

```Python
from maat import *

# Create a symbolic engine for Linux X86-32bits
engine = MaatEngine(ARCH.X86, OS.LINUX)

# Load a binary with one command line argument
engine.load("./some_binary", BIN.ELF32, args=[engine.vars.new_symbolic_buffer("some_arg", 20)])

# Get current eax value
engine.cpu.eax

# Read 4 bytes at the top of the stack
engine.mem.read(engine.cpu.esp, 4)
```

Notice how the symbolic input is created. The command line argument is not a string, it is a symbolic buffer of 20 bytes, which means the engine will treat its contents as unknown and constrain them as the program uses them. The engine exposes a CPU model, so engine.cpu.eax reads a register, and a memory model, so engine.mem.read reads from the stack pointer.

The event system is the second half of the API, and it is where the debugger-like behaviour lives:

```Python
# Set a callback displaying every memory read
def show_mem_access(engine):
    mem_access = engine.info.mem_access
    print(f"Instruction at {engine.info.addr} reads {mem_access.size} bytes at {mem_access.addr}")

engine.hooks.add(EVENT.MEM_R, WHEN.BEFORE, callbacks=[show_mem_access])
```

Hooks are registered for an event type, MEM_R for a memory read, and a timing, WHEN.BEFORE, with a list of callbacks. A callback receives the engine and reads contextual information from engine.info.

And the last piece is state management, which is what makes path exploration practical:

```Python
# Take and restore snapshots
snap = engine.take_snapshot()
engine.restore_snapshot(snap)

# Run the binary
engine.run()
```

Snapshots let the analysis backtrack to a point and try something else, which is the operation that a path-exploring engine performs constantly and that no ordinary debugger offers.

## Sleigh is the reason the engine scales across architectures

The multi-architecture claim rests on one decision, and the README names it: Maat uses Ghidra's sleigh library for assembly lifting. Sleigh is Ghidra's specification language for processors, and the sleigh specification for a given processor is what makes it possible to decode and emulate that processor's instructions.

The README puts the claim carefully, saying Maat has the potential to emulate many architectures, including exotic ones, rather than asserting a list. That is the right way to state it, because supporting an architecture means more than decoding instructions: it means the memory model, the system calls, the calling convention and the binary format all have to be modelled as well. The ARCH and OS arguments to MaatEngine in the example reflect exactly that split, since the architecture and the operating system are chosen separately.

The practical consequence is that the breadth of Maat is inherited from Ghidra's processor specifications rather than written from scratch, which is a very large advantage for a binary analysis framework.

It also creates a dependency worth understanding. The Dockerfile shows the build configuring Maat with -Dmaat_USE_EXTERNAL_SLEIGH=OFF, and the .gitmodules file in the repository root indicates that sleigh is fetched as a git submodule. The Dockerfile comment explains the reason for the extra CMake version: a new version is needed for compiling sleigh. So an external sleigh can be substituted, but by default the framework builds its own.

For a team evaluating the project, the honest summary is that the architecture coverage is as good as the sleigh specifications available, and that the cost of adding an architecture is the cost of modelling its environment, not of writing a decoder.

## The native build pulls in LIEF, Z3 and a recent CMake

The Dockerfile in this repository is described in its own first comment as being mainly for demonstration and documentation purposes, on how to obtain and build the dependencies for Maat. That makes it the best single description of what a from-source build needs.

The base image is ubuntu:20.04, and the apt line installs wget, findutils, build-essential, git, libgmp-dev, python3-dev, curl and libz3-dev. The Z3 development package is the constraint solver, libgmp is the arbitrary precision arithmetic library, and Python is needed for the bindings.

LIEF is fetched as a prebuilt archive rather than from apt, with a pinned version:

```dockerfile
FROM ubuntu:20.04 as base

RUN apt-get update && apt-get -y upgrade && \
  apt-get install -y wget findutils build-essential git libgmp-dev python3-dev curl libz3-dev && \
  wget -O - -c https://github.com/lief-project/LIEF/releases/download/0.12.3/LIEF-0.12.3-Linux-x86_64.tar.gz | tar xz -C /usr/local --strip-components=1
```

Then CMake is installed from a Kitware release, version 3.22.2, with the note that a new version is needed for compiling sleigh. The build itself configures with CMAKE_BUILD_TYPE=RelWithDebInfo, which is the sensible choice for an analysis tool, since you want symbols and line information in your debugger, and then builds and installs into the user site base.

Two things stand out for anyone planning to reproduce this. Everything is pinned to specific versions, LIEF 0.12.3 and CMake 3.22.2, which dates the build recipe. And the build is a multi-stage Docker image, with a dev stage that configures git for sleigh and then compiles, so the toolchain and the sleigh submodule are prepared in a container rather than on your machine.

If you only need the Python module, none of this is your problem. If you need the native SDK, expect to work through this recipe once and then pin it.

## A 2022 release line and an unknown licence

Two facts in the repository metadata should shape your expectations.

The first is the release history. The tagged versions are v0.6.7 on 2022-08-25, v0.6.8 on 2022-09-06 and v0.6.9 on 2022-09-21. The last push to the repository was on 2026-05-22 and the repository is not archived, so work continues, but the released line stopped in September 2022. In practice that means the pymaat package you install is roughly as old as its newest tag, and anything you need that is not in the source tree on master is not in the distribution.

The second is the licence. GitHub reports the licence as a custom one that it cannot classify, and the repository holds a file named LICENCE, spelled the British way, which is the authoritative text. The README does not summarise the terms, so anyone planning to use Maat in a commercial product or to contribute code back should read that file rather than assume a permissive licence from the surrounding conventions of security tooling.

The alternative approach is worth stating plainly. Ghidra itself, which the README names as the source of the sleigh specifications, is a complete binary analysis environment with its own debugger, decompiler and scripting. The difference in approach is that Ghidra observes a program, while Maat makes the program's inputs symbolic and lets a solver decide which paths are reachable, so Maat can answer input-dependent questions that observation alone cannot. If your question is what the binary does, observation is cheaper and more complete. If your question is what makes it reach a particular branch, Maat is the tool and Ghidra alone will not answer it.

The two also compose in practice, since Maat was built on Ghidra's specifications, but the choice is not between them so much as between tracing a program and exploring it.

## Conclusion

Maat fits a reverse engineer or security researcher who needs to explore a binary's possible behaviours rather than trace one execution, and who is comfortable with a C++ toolchain, sleigh and constraint solving. It is the wrong choice for a team that needs a maintained release cadence, since the newest tags are v0.6.7, v0.6.8 and v0.6.9 from August and September 2022 even though the last push was on 2026-05-22, and it is the wrong choice if Python bindings are not acceptable because the native SDK build is the harder path. Before adopting, read the LICENCE file, since GitHub reports a custom licence, confirm the pymaat package on PyPI matches the commit you intend to build, and expect to build sleigh and Z3 yourself when using the native SDK.

## FAQ

### How do I install the Maat Python module?

Run python3 -m pip install pymaat. To use the native SDK and the C++ API instead, the README points to BUILDING.md, which covers building the engine from source with CMake.

### What can Maat do?

The README lists symbolic execution, taint analysis, constraint solving, binary loading and environment simulation, with assembly lifting through Ghidra's sleigh library. It is a dynamic symbolic execution and binary analysis framework written in C++ with Python bindings.

### How does Maat support multiple architectures?

Lifting and emulation are based on Ghidra's sleigh library, so the README says Maat has the potential to emulate many architectures, including exotic ones. The Dockerfile builds with -Dmaat_USE_EXTERNAL_SLEIGH=OFF and the repository contains a .gitmodules file for sleigh.

### What dependencies does building Maat from source need?

The Dockerfile installs build-essential, git, libgmp-dev, python3-dev, curl, wget, findutils and libz3-dev, downloads LIEF 0.12.3, and installs CMake 3.22.2 because a new version is needed for compiling sleigh. The build then configures with CMAKE_BUILD_TYPE=RelWithDebInfo, builds and installs.

## Sources

- [Issues](https://github.com/trailofbits/maat/issues)
- [README](https://github.com/trailofbits/maat/blob/master/README.md)
- [Releases](https://github.com/trailofbits/maat/releases)
- [trailofbits/maat on GitHub](https://github.com/trailofbits/maat)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/trailofbits-maat
