Skip to content

Commit

Permalink
treewide: Refactor headless container tooling and scripts.
Browse files Browse the repository at this point in the history
  • Loading branch information
xlauko committed Sep 5, 2024
1 parent 6fe89a8 commit 67e1874
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 58 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,5 @@ jobs:
- name: Test ${{ matrix.build-type }} with sanitizers set ${{ matrix.sanitizers }}
run: |
cmake --preset ci
cmake --build ./builds/ci/ --target decompile_headless_docker
ctest --preset ci --build-config ${{ matrix.build-type }}
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
# 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
Expand All @@ -18,14 +16,6 @@ INPUT_PATH=$1
FUNCTION_NAME=$2
OUTPUT_PATH=$3

# 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}
fi

# Create a new Ghidra project and import the file
${GHIDRA_HEADLESS} ${GHIDRA_PROJECTS} patchestry-decompilation \
-readOnly -deleteProject \
Expand All @@ -34,7 +24,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
28 changes: 4 additions & 24 deletions scripts/ghidra/decompile-headless.dockerfile
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"]
131 changes: 115 additions & 16 deletions scripts/ghidra/decompile-headless.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,125 @@
# This source code is licensed in accordance with the terms specified in
# the LICENSE file found in the root directory of this source tree.
#
SCRIPTS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )

if [ "$#" -lt 3 ]; then
echo "Usage: $0 <input_file> <function_name> <output_file>"
show_help() {
echo "Usage: $0 [OPTIONS]"
echo
echo "Options:"
echo " -h, --help Show this help message and exit"
echo " -i, --input Path to the input file"
echo " -f, --function Name of the function to decompile"
echo " -o, --output Path to the output file where results will be saved"
echo " -v, --verbose Enable verbose output"
echo " -t, --interactive Start Docker container in interactive mode"
echo
}

INPUT_PATH=""
FUNCTION_NAME=""
OUTPUT_PATH=""
VERBOSE=false
INTERACTIVE=false

while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
show_help
exit 0
;;
-i|--input)
INPUT_PATH="$2"
shift 2
;;
-f|--function)
FUNCTION_NAME="$2"
shift 2
;;
-o|--output)
OUTPUT_PATH="$2"
shift 2
;;
-v|--verbose)
VERBOSE=true
shift
;;
-t|--interactive)
INTERACTIVE=true
shift
;;
*)
echo "Unknown option: $1"
show_help
exit 1
;;
esac
done

if [ -z "$INPUT_PATH" ]; then
echo "Error: Missing required option: -i, --input <input_file>"
exit 1
fi

if [ -z "$FUNCTION_NAME" ]; then
echo "Error: Missing required option: -f, --function <function_name>"
exit 1
fi

INPUT_PATH=$1
FUNCTION_NAME=$2
OUTPUT_PATH=$3
if [ -z "$OUTPUT_PATH" ]; then
echo "Error: Missing required option: -o, --output <output_file>"
exit 1
fi

if [ "$VERBOSE" = true ]; then
echo "Input file: $INPUT_PATH"
echo "Function name: $FUNCTION_NAME"
echo "Output file: $OUTPUT_PATH"
fi

if [ ! -e "$OUTPUT_PATH" ]; then
if [ "$VERBOSE" = true ]; then
echo "Creating output file: $OUTPUT_PATH"
fi
touch "$OUTPUT_PATH"
fi

if [ "$VERBOSE" = true ]; then
echo "Running Docker container..."
fi

absolute_path() {
if [[ "$1" = /* ]]; then
echo "$1"
else
echo "$(pwd)/$1"
fi
}

INPUT_ABS_PATH=$(absolute_path "$INPUT_PATH")
OUTPUT_ABS_PATH=$(absolute_path "$OUTPUT_PATH")

RUN="docker run --rm \
-v \"$INPUT_ABS_PATH:/input.o\" \
-v \"$OUTPUT_ABS_PATH:/output.json\" \
trailofbits/patchestry-decompilation:latest"

if file "$INPUT_PATH" | grep -q "Mach-O"; then
FUNCTION_NAME="_$FUNCTION_NAME"
fi

if [ "$INTERACTIVE" = true ]; then
RUN=$(echo "$RUN" | sed 's/docker run --rm/docker run -it --rm --entrypoint \/bin\/bash/')
else
RUN="$RUN /input.o \"$FUNCTION_NAME\" /output.json"
fi

if [ "$VERBOSE" = true ]; then
echo "Running Docker container with the following command:"
echo "$RUN"
fi

# Make sure $OUTPUT_PATH exists and is empty so that it can be
# mounted to the container
if [ ! -f $OUTPUT_PATH ]; then
touch $OUTPUT_PATH
if [ "$VERBOSE" = true ]; then
echo "Starting Docker container..."
fi
truncate -s 0 $OUTPUT_PATH

docker run --rm \
-v $INPUT_PATH:/input \
-v $OUTPUT_PATH:/output \
trailofbits/patchestry-decompilation:latest \
/input $FUNCTION_NAME /output
eval "$RUN"

0 comments on commit 67e1874

Please sign in to comment.