- Python 49.4%
- Dockerfile 22.8%
- Rust 19.4%
- Shell 8.4%
| apps | apps: Adding selection of Rust apps | |
| binaries | apps: Adding binary build artifacts | |
| dockerfiles | Add support for output language verification | |
| minimal | Rearrange directory structure | |
| scripts | Add support for output language verification | |
| .dockerignore | scripts: Wrap runner in dockerfile | |
| .gitattributes | Enable LFS, push compressed RO-0000 | |
| .gitignore | Enable LFS, push compressed RO-0000 | |
| .gitmodules | apps: Adding selection of Rust apps | |
| build-all.sh | scripts: Add dockerfiles and build script | |
| LICENSE | Add RO-000 | |
| README.md | Add support for output language verification | |
Testing ReOxide
This repository has tools for automated testing and evaluation of Ghidra and ReOxide. We give a base set of arbitrarily chosen Rust projects in a somewhat reproducible binary form. The repository also has minimal Rust examples for testing specific edge cases in Rust decompilation. We will expand this selection as we identify more edge cases, but due to the large size of Rust binaries, we do not plan to host a real large-scale data set.
Instead, we offer tools for creating data sets on your own. This
includes a way to build Rust projects across different Rust
versions and a template for running Ghidra and ReOxide in
headless mode across many binaries. We use Python scripts and
Docker; to use them, you need the Docker Python SDK, provided by
the docker pip package:
$ pip install docker
To build both the headless runner and the build images, you can
use the build-images.py script:
$ ./scripts/build-images.py
reoxide-runner:latest OK: sha256:8a33d36b77b8082264ba189debe011b3b97805d9588518243452c19aa69e4835
rbuild:1.87 OK: sha256:118ad7eafc65b75be821397c19d7b7b64e63cec354be46fa14ef525fa15c3f9b
rbuild:1.86 OK: sha256:11463e4c1d2239a13ea884f829f4f9af0539290bef54b0e3eefe5dc2af757ebc
rbuild:1.85 OK: sha256:c564fd9af74fdc6fbefe57f967f867b50df7fbac053597f595a3d8ff59f06d45
rbuild:1.84 OK: sha256:bfd386bf02490bc8366c628e4036c88d5d43d701ff79ca74fc26d66d04c8f397
rbuild:1.83 OK: sha256:ec160081047aa2d5e7aa8f06b4a74abfbd60e155c52c9eec7e1a3503f9578821
rbuild:1.82 OK: sha256:6171b77c4dcbe90a3e4940191275eef39f7846179cbc73cf1c545bf6c18395de
Building binaries
To allow building of binaries across different Rust versions, the
repository has Dockerfiles for some versions in the dockerfiles
folder. The build-images.py script identifies the build Dockerfiles
by the pattern build-$VERSION.Dockerfile. It tags the final Docker
images as rbuild:$VERSION. When you then use the build-app.py
script, it will look for rbuild Docker images on your local system
and then compile the app for every $VERSION tag it finds. That means,
if you have images called rbuild:1.82 and rbuild:1.83, the script
will build your app for the 1.82 and 1.83 version of Rust, given
the matching Dockerfiles use these versions.
In the simplest case, where you have a Rust project that produces
one binary and the binary has the final name you want, you can use
build-app.py this way:
$ ./scripts/build-app.py path-to-rust-project name-of-binary
The script places the files in the binaries folder at the root of
the testsuite repository and the folders for the specific Rust
version. It outputs the build and location information in the log,
e.g. for ./scripts/build-app.py minimal/ro-0000 ro-0000:
2025年06月15日T11:48:55 INFO - 1.82: Building "ro-0000"
2025年06月15日T11:48:55 INFO - 1.82: cargo build --locked --release --target-dir target/1.82 && zstd -f target/1.82/release/ro-0000
2025年06月15日T11:48:55 INFO - 1.82: Finished /home/user/testsuite/binaries/1.82/ro-0000.zst
2025年06月15日T11:48:55 INFO - 1.83: Building "ro-0000"
2025年06月15日T11:48:55 INFO - 1.83: cargo build --locked --release --target-dir target/1.83 && zstd -f target/1.83/release/ro-0000
2025年06月15日T11:48:56 INFO - 1.83: Finished /home/user/testsuite/binaries/1.83/ro-0000.zst
After build, the script compresses the binary using zstd to save
space. Because not all Rust projects follow this layout,
build-app.py allows passing extra arguments. The build-all.sh
script at the root of the repository has an example making use
of all arguments:
./scripts/build-app.py ./apps/bevy bevy-hello_world --extra_args "--features wayland --example hello_world" --artifact_path release/examples/hello_world --lockfile_path apps/bevy.lock
Because the Bevy repository has a framework rather
than an application, we do not want to build the main project. Instead
we build selected examples, by using the --extra_args parameter of
build-app.py. Because we now build an example, in this case
hello_world, we also need to adjust the path to the final build
artifact by using --artifact_path. We specify this path relative to
the target folder created by cargo, in this case
release/examples/hello_world. Finally, because Bevy does not have a
Cargo.lock file and build-app.py requires one, we need to add our
own lock file and point to it using --lockfile_path.
Running headless Ghidra/ReOxide
The reoxide-runner container image wraps the run-analysis.py script
from the scripts folder with a containerized Ghidra and ReOxide setup.
If you built the images by using build-images.py, you can run it this way:
docker run --rm -it -v $(pwd):/data reoxide-runner:latest project-name relative-path-to-binary1 relative-path-to-binary2 ...
The reoxide-runner container uses /data as its working directory.
You need to mount a directory, in this case the current working
directory, to /data inside the container for the runner to access it.
You can select the project-name arbitrarily. You need to specify the
relative-path-to-binary relative to the /data directory inside
the container. This means that the example command specifies the path
relative to the current working directory outside of docker, since
it binds $(pwd) outside the container to /data inside the container.
The reoxide-runner container will create a folder called
project-name with the Ghidra project and folders for every binary
inside. After completing the analysis it writes the Ghidra and
ReOxide log to files inside the folder named after the binary. This
folder can also store other artifact. The default run-analysis.py
script, found in the scripts folder, extracts the decompiled code
of the main function from the binary and then stores it in the folder:
def analyze(launcher, out_folder: Path, project_name: str, binary: Path):
bin_folder = out_folder / project_name / binary.name
bin_folder.mkdir(exist_ok=True, parents=True)
with reoxide.start_background(Path('./reoxide.toml')) as r:
r.print_language = 'rust-language'
with open_program(binary.resolve(), out_folder, project_name) as fapi:
program = fapi.getCurrentProgram()
fm = program.getFunctionManager()
for fun in fm.getFunctions(True):
if fun.getName() != "main" or fun.isGlobal():
continue
# Find the first function with name "main" and decompile it
options = DecompileOptions()
ifc = DecompInterface()
ifc.setOptions(options)
ifc.openProgram(program)
res = ifc.decompileFunction(fun, 60, None)
dfun = res.getDecompiledFunction()
with (bin_folder / 'main.c').open('w') as f:
f.write(dfun.getC())
break
The run-analysis.py script and especially the analyze function
serves as a template for implementing custom analysis through the
Ghidra API on top. Do not forget to rebuild the Docker image by using
build-images.py after changing the run-analysis.py script. You
can also run run-analysis.py outside of the Docker container. When
running outside of Docker, run-analysis.py takes the output folder
as another argument before specifying the project name and binaries.
You also need to make sure to:
- Activate the virtual environment where you installed PyGhidra.
- Make sure you installed ReOxide with the official instructions. Ideally you install it into the PyGhidra environment.
- Set the
GHIDRA_INSTALL_DIRenvironment variable to the root directory of your Ghidra installation. - Set the
LD_LIBRARY_PATHenvironment variable to the output of thereoxide print-ld-library-pathcommand.
If you face any difficulties you can inspect the
dockerfiles/runner.Dockerfile and scripts/runner-entrypoint.sh
files to compare the setup in Docker with your own.