Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: refactor decompilation script testing framework and use lit runner #41

Merged
merged 12 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,36 @@ ARG LLVM_VERSION
# CMake reinstall choices: none, 3.21.5, 3.22.2, or versions from https://cmake.org/download/
ARG REINSTALL_CMAKE_VERSION_FROM_SOURCE="3.29.2"

# Optionally install the cmake for vcpkg
# Optionally install CMake for vcpkg
COPY ./reinstall-cmake.sh /tmp/
RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \
chmod +x /tmp/reinstall-cmake.sh && /tmp/reinstall-cmake.sh ${REINSTALL_CMAKE_VERSION_FROM_SOURCE}; \
fi \
&& rm -f /tmp/reinstall-cmake.sh
chmod +x /tmp/reinstall-cmake.sh && \
/tmp/reinstall-cmake.sh ${REINSTALL_CMAKE_VERSION_FROM_SOURCE}; \
fi && \
rm -f /tmp/reinstall-cmake.sh

COPY ./dependencies.txt /tmp/
RUN apt-get update && \
export DEBIAN_FRONTEND=noninteractive && \
xargs -a /tmp/dependencies.txt apt-get -y install --no-install-recommends && \
apt-get clean --yes && \
xargs -a /tmp/dependencies.txt apt-get install -y --no-install-recommends && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Install Docker
RUN curl -fsSL https://get.docker.com -o get-docker.sh && \
sh get-docker.sh && \
rm get-docker.sh

# Remove old versions of LLVM and Clang
RUN apt-get -y purge llvm-14 clang-14

# Install Python packages
RUN pip3 install lit codespell jinja2 Pygments

COPY ./install-llvm.sh /tmp/
RUN chmod +x /tmp/install-llvm.sh && /tmp/install-llvm.sh ${LLVM_VERSION} all
RUN chmod +x /tmp/install-llvm.sh && \
/tmp/install-llvm.sh ${LLVM_VERSION} all

# Add docker group and add root to it
RUN groupadd docker || true && \
usermod -aG docker root
2 changes: 1 addition & 1 deletion .devcontainer/dependencies.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
software-properties-common ninja-build python3-pip ccache cmake lld lcov doxygen libzstd-dev
software-properties-common ninja-build python3-pip ccache cmake lld lcov doxygen libzstd-dev file
15 changes: 11 additions & 4 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
"name": "C++",
// to run local build of devcontainer
"build": { "dockerfile": "./Dockerfile" },
"runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],

"runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined", "--privileged"],
"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
],
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
Expand All @@ -18,10 +20,15 @@
}
},

"remoteUser": "vscode",
"remoteUser": "root",
"features": {
"git": "os-provided",
"python": "os-provided"
"python": "os-provided",
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"version": "latest",
"moby": true,
"dockerDashComposeVersion": "v2"
}
},
"containerEnv": {
"CMAKE_PREFIX_PATH": "/usr/lib/llvm-18/lib/cmake/mlir/;/usr/lib/llvm-18/lib/cmake/clang/",
Expand Down
21 changes: 16 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,18 @@ jobs:
image:
ghcr.io/lifting-bits/patchestry-ubuntu-${{ matrix.image-version }}-llvm-${{ matrix.llvm-version }}-dev:latest

services:
docker:
image: docker:20.10-dind
options: --privileged
ports:
- 2375:2375

env:
CMAKE_PREFIX_PATH: "/usr/lib/llvm-${{ matrix.llvm-version }}/lib/cmake/mlir/;/usr/lib/llvm-${{ matrix.llvm-version }}/lib/cmake/clang/"
LLVM_EXTERNAL_LIT: "/usr/local/bin/lit"
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR: ${{ matrix.sanitizers }}
ENABLE_SANITIZER_ADDRESS: ${{ matrix.sanitizers }}
CMAKE_PREFIX_PATH: "/usr/lib/llvm-${{ matrix.llvm-version }}/lib/cmake/mlir/;/usr/lib/llvm-${{ matrix.llvm-version }}/lib/cmake/clang/"
LLVM_EXTERNAL_LIT: "/usr/local/bin/lit"
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR: ${{ matrix.sanitizers }}
ENABLE_SANITIZER_ADDRESS: ${{ matrix.sanitizers }}

steps:
- name: Clone the Patchestry repository
Expand All @@ -54,5 +61,9 @@ jobs:
- name: Build ${{ matrix.build-type }} with sanitizers set ${{ matrix.sanitizers }}
run: cmake --build --preset ci --config ${{ matrix.build-type }} -j $(nproc)

- name: Build the headless docker image
run: bash ./scripts/ghidra/build-headless-docker.sh

- name: Test ${{ matrix.build-type }} with sanitizers set ${{ matrix.sanitizers }}
run: ctest --preset ci --build-config ${{ matrix.build-type }}
run: |
lit ./builds/ci/test -v -DCI_OUTPUT_FOLDER=${{ github.workspace }}/builds/ci/test/ghidra/Output
2 changes: 0 additions & 2 deletions scripts/ghidra/.dockerignore

This file was deleted.

10 changes: 5 additions & 5 deletions scripts/ghidra/PatchestryScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private void serializeToFile(Path file, Function function) throws Exception {
println("Serializing function: " + function.getName() + " @ " + function.getEntryPoint());
serializer.serialize(function).close();
}

private void runHeadless() throws Exception {
final var args = getScriptArgs();
if (args.length != 2) {
Expand All @@ -123,10 +123,10 @@ private void runHeadless() throws Exception {
println("\tOUTPUT_FILE");
return;
}

final var functionName = args[0];
final var outputPath = args[1];

final var functions = getGlobalFunctions(functionName);
if (functions.isEmpty()) {
println("Function not found: " + functionName);
Expand All @@ -144,12 +144,12 @@ private void runGUI() throws Exception {
final var curFunction = getFunctionContaining(currentAddress);
final var functionName = curFunction.getName();
final var jsonPath = Files.createTempFile(functionName + '.', ".patchestry.json");

serializeToFile(jsonPath, curFunction);

final var mlirPath = Files.createTempFile(functionName + '.', ".patchestry.out");
final var binaryPath = "patchestry";

final var cmd = new ArrayList<String>();
cmd.add(binaryPath);
cmd.add(jsonPath.toString());
Expand Down
41 changes: 7 additions & 34 deletions scripts/ghidra/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# Decompilation and Testing Framework
# Decompilation Framework

The directory includes a decompilation script that runs Ghidra in headless mode
to extract pcode for specific functions from binary files. It also features a
testing framework that compiles the source files, generates the corresponding
pcode for the specified function, and validates the output using the FileCheck
verifier.
The directory includes a decompilation script that runs Ghidra in headless mode to extract pcode for specific functions from binary files.

We support two decompilation modes:

Expand All @@ -16,39 +12,16 @@ We support two decompilation modes:
Before running the scripts, make sure you have the following installed:

- **Docker**: The scripts use a Docker container to run Ghidra in headless mode.
- **FileCheck**: The `FileCheck` tool is installed and available in your PATH. It is typically part of LLVM suite.

To perform headless decompilation, you need to build a Docker container (`decompile-headless.dockerfile`) configured to run Ghidra in headless mode. You can do this by running the `build-headless-docker.sh` script.

## Usage

### Run Headless Decompilation Script
## Running Headless Decompilation Script

To decompile and extract pcode for a specific function from a binary file, use
the `decompile-headless.sh` script. This script extracts the pcode for the
specified function and writes the output to a file named `patchestry.out.json`
in the output directory.

```bash ./decompile-headless.sh <binary> <function-name> <output-file> ```

### Testing the Output

#### Using Test Script

You can run all the tests using `decompile-headless-test.sh` script:

```
./decompile-headless-test.sh
```

#### Testing with CMake

Alternatively, you can use CMake to configure, build, and run the tests.
specified function and writes the json output to a file named `<output-file>`.

```
cmake -B /path/to/build -S /path/to/test
cmake --build /path/to/build
ctest --test-dir /path/to/build
```
```sh ./decompile-headless.sh --input <binary> --function <function-name> --output <output-file> ```

## Running Patchestry via Ghidra GUI

Expand All @@ -66,4 +39,4 @@ ctest --test-dir /path/to/build

4. Run `PatchestryScript.java`.

**Note:** Ghidra scripts must be installed. See the [build](build.md) section for details.
**Note:** Ghidra scripts must be installed. See the [build](build.md) section for details.
17 changes: 17 additions & 0 deletions scripts/ghidra/build-headless-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

SCRIPTS_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"

echo "Using SCRIPTS_DIR: $SCRIPTS_DIR"

docker build \
-t trailofbits/patchestry-decompilation:latest \
-f "${SCRIPTS_DIR}/decompile-headless.dockerfile" \
"${SCRIPTS_DIR}"

if [ $? -eq 0 ]; then
echo "Docker image built successfully."
else
echo "Error: Docker build failed."
exit 1
fi
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,30 @@
# the LICENSE file found in the root directory of this source tree.
#

set -x

if [ "$#" -lt 3 ]; then
echo "Usage: $0 <input_file> <function_name> <output_file>"
exit 1
fi

INPUT_PATH=$1
if [ ! -f $INPUT_PATH ]; then
echo "Input file does not exist"
exit 1
fi

FUNCTION_NAME=$2
OUTPUT_PATH=$3
if [ ! -f $OUTPUT_PATH ]; then
echo "Output file does not exist"
exit 1
fi

# Running with non-root user may cause permission issue on ubuntu
# because binded directory will have root permission.
# This is a hacky fix to avoid the issue. It can be avoided
# by switching to using docker volume.
if [ ! -w ${OUTPUT_PATH} ]; then
sudo chown ${USER}:${USER} ${OUTPUT_PATH}
if [ ! -w "$OUTPUT_PATH" ]; then
sudo chmod 777 "$OUTPUT_PATH" 2>/dev/null
if [ $? -ne 0 ]; then
echo "Error: Failed to change permissions on output file '$OUTPUT_PATH'."
exit 1
fi
fi

# Create a new Ghidra project and import the file
Expand All @@ -34,7 +41,6 @@ ${GHIDRA_HEADLESS} ${GHIDRA_PROJECTS} patchestry-decompilation \
$FUNCTION_NAME \
$OUTPUT_PATH


# Check if the decompile script was successful
if [ $? -ne 0 ]; then
echo "Decompilation failed"
Expand Down
18 changes: 0 additions & 18 deletions scripts/ghidra/decompile-headless-test.sh

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,19 @@ FROM eclipse-temurin:17 AS base

FROM base AS build

# Set environment variables for Ghidra
ENV GHIDRA_VERSION=11.1.2
ENV GHIDRA_RELEASE_TAG=20240709
ENV GHIDRA_PACKAGE=ghidra_${GHIDRA_VERSION}_PUBLIC_${GHIDRA_RELEASE_TAG}
ENV GHIDRA_SHA256=219ec130b901645779948feeb7cc86f131dd2da6c36284cf538c3a7f3d44b588
ENV GHIDRA_REPOSITORY=https://github.com/NationalSecurityAgency/ghidra

# Update and install necessary packages
RUN apt-get update && apt-get install -y \
wget \
ca-certificates \
unzip \
--no-install-recommends && \
apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives

# Download and verify Ghidra package
RUN wget --progress=bar:force -O /tmp/ghidra.zip ${GHIDRA_REPOSITORY}/releases/download/Ghidra_${GHIDRA_VERSION}_build/${GHIDRA_PACKAGE}.zip && \
echo "${GHIDRA_SHA256} /tmp/ghidra.zip" | sha256sum -c -

Expand All @@ -27,7 +24,6 @@ RUN unzip /tmp/ghidra.zip -d /tmp && \
chmod +x /ghidra/ghidraRun && \
rm -rf /var/tmp/* /tmp/* /ghidra/docs /ghidra/Extensions/Eclipse /ghidra/licenses

# Clean up
RUN apt-get purge -y --auto-remove wget ca-certificates unzip && \
apt-get clean

Expand All @@ -46,36 +42,20 @@ RUN adduser --shell /sbin/nologin --disabled-login --gecos "" user && \
adduser user sudo && \
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# Switch to the newly created user
USER user

# Set working directory for the user
WORKDIR /home/user/

# Copy Ghidra from the build stage
COPY --from=build /ghidra ghidra

# Create projects and scripts directory
RUN mkdir ghidra_projects ghidra_scripts
RUN mkdir -p /home/user/ghidra_projects /home/user/ghidra_scripts

# Copy the Java and script files into the appropriate directories
COPY --from=build /ghidra ghidra
COPY PatchestryScript.java ghidra_scripts/
COPY decompile.sh decompile.sh

# Make the decompile script executable
USER root
RUN chmod +x decompile.sh
USER user

# Copy the .dockerignore file (if necessary)
COPY .dockerignore .dockerignore
COPY --chown=user:user --chmod=755 decompile-entrypoint.sh .

# Set environment variable for Ghidra home directory
ENV GHIDRA_HOME=/home/user/ghidra
ENV GHIDRA_SCRIPTS=/home/user/ghidra_scripts
ENV GHIDRA_PROJECTS=/home/user/ghidra_projects
ENV GHIDRA_HEADLESS=${GHIDRA_HOME}/support/analyzeHeadless
ENV USER=user

# Set the entrypoint
ENTRYPOINT ["/home/user/decompile.sh"]
ENTRYPOINT ["/home/user/decompile-entrypoint.sh"]
Loading