1
0
Fork
You've already forked testsuite
0
Documentation of special test cases for Rust decompilation.
  • Python 49.4%
  • Dockerfile 22.8%
  • Rust 19.4%
  • Shell 8.4%
2026年01月26日 06:36:42 +01:00
apps apps: Adding selection of Rust apps 2025年06月13日 18:09:30 +02:00
binaries apps: Adding binary build artifacts 2025年06月13日 18:18:16 +02:00
dockerfiles Add support for output language verification 2026年01月26日 06:36:42 +01:00
minimal Rearrange directory structure 2025年05月22日 15:46:09 +02:00
scripts Add support for output language verification 2026年01月26日 06:36:42 +01:00
.dockerignore scripts: Wrap runner in dockerfile 2025年06月13日 23:54:01 +02:00
.gitattributes Enable LFS, push compressed RO-0000 2025年02月09日 20:40:07 +01:00
.gitignore Enable LFS, push compressed RO-0000 2025年02月09日 20:40:07 +01:00
.gitmodules apps: Adding selection of Rust apps 2025年06月13日 18:09:30 +02:00
build-all.sh scripts: Add dockerfiles and build script 2025年06月13日 18:21:35 +02:00
LICENSE Add RO-000 2025年02月09日 20:34:15 +01:00
README.md Add support for output language verification 2026年01月26日 06:36:42 +01:00

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_DIR environment variable to the root directory of your Ghidra installation.
  • Set the LD_LIBRARY_PATH environment variable to the output of the reoxide print-ld-library-path command.

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.