From fbdcd1c709cb04eebe88df85b74efcef406faa2e Mon Sep 17 00:00:00 2001 From: ghidraffe <108089404+ghidraffe@users.noreply.github.com> Date: Thu, 8 May 2025 15:47:15 -0400 Subject: [PATCH] GP-3579 Added docker image generation to Ghidra distribution. Use from development source repo is not supported. --- docker/Dockerfile | 62 ++++++++++ docker/Dockerfile.dockerignore | 1 + docker/README.md | 203 +++++++++++++++++++++++++++++++++ docker/build.gradle | 28 +++++ docker/certification.manifest | 4 + docker/entrypoint.sh | 55 +++++++++ settings.gradle | 1 + 7 files changed, 354 insertions(+) create mode 100644 docker/Dockerfile create mode 100644 docker/Dockerfile.dockerignore create mode 100644 docker/README.md create mode 100644 docker/build.gradle create mode 100644 docker/certification.manifest create mode 100755 docker/entrypoint.sh diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000..9edff23874 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,62 @@ +############################################################### +# Dockerfile +# This is the Dockerfile for a release of Ghidra. +# It should be built from the root directory of +# a ghidra release using the command +# docker build -f docker/Dockerfile -t +############################################################### +FROM alpine:3.20 as base + +LABEL org.opencontainers.image.title="ghidra" \ + org.opencontainers.image.description="Docker image for Ghidra" \ + org.opencontainers.image.source="https://github.com/NationalSecurityAgency/ghidra" \ + org.opencontainers.image.licenses="Apache 2.0" + +# Configure user, entrypoint, and some env vars first, before making the image larger with dependencies +# so that we can keep the image size as small as possible. +RUN addgroup -g 1001 -S ghidra && adduser -u 1001 -S ghidra -G ghidra +ENTRYPOINT ["/bin/bash", "/ghidra/docker/entrypoint.sh"] +# Set JAVA_HOME so that we don't need to do this manually when Ghidra is first started. +ENV JAVA_HOME /usr/lib/jvm/java-21-openjdk +ENV LD_LIBRARY_PATH /usr/lib/jvm/java-21-openjdk/lib/:/usr/lib/jvm/java-21-openjdk/lib/server/ +WORKDIR /ghidra + +# validate build context is correct. an error will happen if this file is not present. +COPY ./ghidraRun ./ghidraRun + +# update and install dependencies used to both build and run ghidra +RUN apk update \ + && apk add openjdk21 python3 \ + bash gcompat \ + fontconfig msttcorefonts-installer \ + linux-headers libressl-dev \ + && update-ms-fonts + +FROM base as build + +# install additional dependencies used to build ghidra +RUN apk add gradle \ + python3-dev py-pip \ + alpine-sdk \ + build-base \ + gcc g++ make libc-dev zlib-dev musl-dev \ + zip readline-dev +# copy the contents of the release into the current working dir. +COPY . . + +# build postgres and install pyghidra +RUN /ghidra/Ghidra/Features/BSim/support/make-postgres.sh \ + && python3 -m venv /ghidra/venv \ + && /ghidra/venv/bin/python3 -m pip install --no-index -f /ghidra/Ghidra/Features/PyGhidra/pypkg/dist pyghidra \ + && mkdir /ghidra/repositories && mkdir /ghidra/bsim_datadir + + +FROM base as runtime + +# install additional dependencies needed for running ghidra +RUN apk add openssl openssh-client \ + xhost musl-locales musl-locales-lang + +USER ghidra +WORKDIR /ghidra +COPY --chown=ghidra:ghidra --from=build /ghidra /ghidra diff --git a/docker/Dockerfile.dockerignore b/docker/Dockerfile.dockerignore new file mode 100644 index 0000000000..8ea08e2e8b --- /dev/null +++ b/docker/Dockerfile.dockerignore @@ -0,0 +1 @@ +docker/Dockerfile* diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000..8181147412 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,203 @@ +# Dockerized Ghidra + +## Build + +From the root directory of your Ghidra release, run the following command with the correct version for your release. + +``` +docker build -f docker/Dockerfile -t ghidra/ghidra[:] . +``` + +The image tag is optional, but highly recommended. + + +## The MODE environment variable + +The Ghidra Docker Container supports the following `MODE`'s of execution: +- gui +- headless +- ghidra-server +- bsim +- bsim-server +- pyghidra + +The `MODE` environment variable designates which entrypoint of Ghidra to execute. + +The `entrypoint.sh` script is executed upon container startup. + +## Configuring a Container + +Configuration of a container is done just as any other docker container would be configured. +Volumes can be mounted, environment variables can be set, ports can be mapped from container to the host, and so on. +Configuration steps vary a lot based on what MODE the container is started with. + +The base directory for Ghidra within the container is located at `/ghidra`. +All of ghidra's default locations for files, configs, etc., are the same within that. +Ghidra is run as the user `ghidra` within the container. + +The `ghidra` user only has permissions to the following directories inside the container: +- `/ghidra` +- `/home/ghidra` + + +When a container does not receive any arguments passed to it with the `docker run` command, +the corresponding Command Line Interface (CLI) for the `MODE` executed will display it's usage statement. + +### Mapping Local Volumes to a Container + +Volumes within the container may run into permission issues if the volumes are not accessible by users in the group id `1001`. + +The default uid and guid for the container is `1001:1001`. Volumes that get mapped to the container should be accessible by this uid/guid. + +Adding the host machine's user to the group `1001` on the host helps manage volumes that will be used in the container. +This can easily be done by executing `sudo usermod -aG 1001 ` on Linux. + +### Example of Headless Mode + +``` +docker run \ + --env MODE=headless \ + --rm \ + --volume /path/to/myproject:/home/ghidra/myproject \ + --volume /path/to/mybinary:/home/ghidra/mybinary \ + ghidra/ghidra: \ + /myproject programFolder -import /mybinary +``` + +Breaking this down line by line: +- `docker run` is going to start a docker container using the image `ghidra/ghidra<:` +- `--env MODE=headless` configures the environment variable `MODE` within the container to be the value `headless` +- `--rm` removes the container after the command is complete +- `--volume /path/to/myproject:/myproject` mounts the local volume `/path/to/myproject` on the host to `/myproject` within the container +- `--volume /path/to/mybinary:/mybinary` mounts the local volume `/path/to/mybinary` on the host to `/mybinary` within the container +- `ghidra/ghidra:` is the full reference for the docker image, where `ghidra/ghidra` is the group and name of the image, and `` is the tag. +- `/myproject programFolder -import /mybinary` are arguments being passed to Ghidra's headless analyzer's command line interface + +Passing no arguments will result in the usage of the headless analyzer being displayed. + +### Example of Gui Mode + +Running Ghidra's Graphical User Interface (GUI) in the docker container is not a recommended method for running Ghidra. +GUI's are not a typical use case for dockerized applications. + +``` +docker run \ + --env MODE=gui \ + -it \ + --rm \ + --net host \ + --env DISPLAY \ + --volume="$HOME/.Xauthority:/home/ghidra/.Xauthority" \ + ghidra/ghidra: +``` + +In this mode, the container relies on X11 forwarding to display the GUI. Configuration of X11 can vary, but in this case, +the host's Xauthority file is mounted into the container, the container is configured to use the host's network, and the DISPLAY +environment variable is passed to the container. This enables forwarding the GUI back to the host machine's display. Volumes +containing binaries would still need to be mounted to the container as well as volumes for ghidra projects. + +The host's `.Xauthority` file must have appropriate permissions - assigned the group`:1001` with `rw` group permissions. + + +### Example of Ghidra Server Mode + +``` +docker run \ + --env MODE=ghidra-server \ + --rm \ + -it \ + --volume /path/to/my/repositories:/ghidra/repositories \ + --volume /path/to/my/configs/server.conf:/ghidra/server/server.conf \ + -p 13100:13100 \ + -p 13101:13101 \ + -p 13102:13102 \ + ghidra/ghidra: +``` + +Volumes would need to be mounted to the server container to save the repositories, users, and also to configure the server as well. + +To utilize svrAdmin, exec into the running ghidra server container (`docker exec -it bash`) for a bash shell in the container. +After exec'ing into the container, administration and management of the Ghidra server is the same as outside of a containerized environment. + +To stop the container, execute the command `docker stop `. + +## Example of BSIM Server Mode + +``` +export DATADIR_PATH=/home/ghidrausr/datadir +docker run \ + --env MODE=bsim-server \ + --rm \ + -it \ + --volume /path/to/my/datadir:/ghidra/bsim_datadir \ + -p 5432:5432 \ + ghidra/ghidra: \ + /ghidra/bsim_datadir +``` + +`/ghidra/bsim_datadir` is the directory used to store bsim's data in the container. Other directories could be used on the container, +but make sure that the folder on the host machine has appropriate permissions, assigned the group `:1001`. + +This example simply starts a bsim server. Configuring the bsim server and populating it with data +could be done post start within the container in a similar way that ghidra server administration is done. +An administrator would have to exec into the running bsim server container (`docker exec -it bash`), +and after exec'ing into the container, administration and management of the Bsim server is the same as outside of a containerized environment. + +To stop the container, execute the command `docker stop `. + +## Example of BSIM CLI Mode +``` +docker run \ + --env MODE=bsim \ + --rm \ + -it \ + ghidra/ghidra:RELEASE \ + generatesigs ghidra://ghidrasvr/demo /home/ghidra \ + --bsim postgresql://bsimsvr/demo \ + --commit --overwrite \ + --user ghidra +``` + +In this example, the bsim CLI is used to connect to a ghidra server hosted on `ghidrasvr`, +generate signatures for the `demo` repository in that ghidra server and save them to `/home/ghidra`. +and then commit the signatures to the BSIM server hosted on `bsimsvr` in the `demo` database. + + +## Example of Pyghidra Gui Mode + +Running Ghidra's Graphical User Interface (GUI) in the docker container is not a recommended method for running Ghidra. +GUI's are not a typical use case for dockerized applications. + +``` +docker run \ + --env MODE=pyghidra \ + -it \ + --rm \ + --net host \ + --env DISPLAY \ + --volume="$HOME/.Xauthority:/home/ghidra/.Xauthority:rw" \ + ghidra/ghidra: -c +``` +In this mode, the container relies on X11 forwarding to display the GUI. Configuration of X11 can vary, but in this case, +the host's Xauthority file is mounted into the container, the container is configured to use the host's network, and the DISPLAY +environment variable is passed to the container. This enables forwarding the GUI back to the host machine's display. Volumes +containing binaries would still need to be mounted to the container as well as volumes for ghidra projects. + +The host's `.Xauthority` file must have appropriate permissions - owned by `:1001` with `rw` group permissions. + + +## Example of Pyghidra Headless Mode + +``` +docker run \ + --env MODE=pyghidra \ + --rm \ + --volume /path/to/myproject:/myproject \ + --volume /path/to/mybinary:/mybinary \ + ghidra/ghidra: -H \ + /myproject programFolder -import /mybinary +``` +Passing no arguments to the pyghidra headless analyzer will result in the help menu being displayed, just like the headless analyzer. + +This use case is very similar to the headless mode's example with the added benefit of being able to utilize python3 for Ghidra Scripts. + diff --git a/docker/build.gradle b/docker/build.gradle new file mode 100644 index 0000000000..04d8eff183 --- /dev/null +++ b/docker/build.gradle @@ -0,0 +1,28 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +rootProject.assembleDistribution { + from (ROOT_PROJECT_DIR + "/docker") { + into "docker" + exclude "build.gradle" + } +} + +rootProject.assembleMarkdownToHtml { + from ("${this.projectDir}/README.md") { + into "." + } +} diff --git a/docker/certification.manifest b/docker/certification.manifest new file mode 100644 index 0000000000..6d3b1fe641 --- /dev/null +++ b/docker/certification.manifest @@ -0,0 +1,4 @@ +##VERSION: 2.0 +Dockerfile||GHIDRA||||END| +Dockerfile.dockerignore||GHIDRA||||END| +README.md||GHIDRA||||END| diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000000..7e16af8427 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,55 @@ +#!/bin/bash +## ### +# IP: GHIDRA +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +## + +MODE=${MODE:="gui"} + +echo "$@" +MAXMEM=${MAXMEM:=2G} + +if [[ $MODE == "gui" ]] then + /ghidra/support/launch.sh bg jdk Ghidra "${MAXMEM}" "" ghidra.GhidraRun "$@" + # need to do this since the launched process is not blocking terminal exit + while ! tail -f ~/.config/ghidra/ghidra_*/application.log; do sleep 1 ; done +elif [[ $MODE == "headless" ]] then + LAUNCH_MODE=${LAUNCH_MODE:=fg} + DEBUG_ADDRESS=${DEBUG_ADDRESS:=127.0.0.1:13002} + VMARG_LIST=${VMARG_LIST:="-XX:ParallelGCThreads=2 -XX:CICompilerCount=2 -Djava.awt.headless=true "} + DEBUG_ADDRESS=${DEBUG_ADDRESS} /ghidra/support/launch.sh "${LAUNCH_MODE}" jdk Ghidra-Headless "${MAXMEM}" "${VMARG_LIST}" ghidra.app.util.headless.AnalyzeHeadless "$@" +elif [[ $MODE == "ghidra-server" ]] then + # Note, for svrAdmin, you will need to exec into the container running the ghidra server and use the CLI there. + /ghidra/server/ghidraSvr console +elif [[ $MODE == "bsim" ]] then + LAUNCH_MODE=${LAUNCH_MODE:=fg} + VMARG_LIST=${VMARG_LIST:="-Djava.awt.headless=true "} + /ghidra/support/launch.sh $LAUNCH_MODE jdk "BSim" "${MAXMEM}" "" ghidra.features.bsim.query.ingest.BSimLaunchable "$@" +elif [[ $MODE == "bsim-server" ]] then + LAUNCH_MODE=${LAUNCH_MODE:=fg} + VMARG_LIST=${VMARG_LIST:="-Djava.awt.headless=true -Xshare:off"} + mkdir -p $DATADIR_PATH + /ghidra/support/launch.sh "$LAUNCH_MODE" jdk BSimControl "$MAXMEM" "$VMARG_LIST" ghidra.features.bsim.query.BSimControlLaunchable start $@ + # need to do this since the launched process is not blocking terminal exit + while ! tail -f $DATADIR_PATH/logfile; do sleep 1 ; done +elif [[ $MODE == "pyghidra" ]] then + # Add optional JVM args inside the quotes + VMARG_LIST=${VMARG_LIST:=""} + PYGHIDRA_LAUNCHER="/ghidra/Ghidra/Features/PyGhidra/support/pyghidra_launcher.py" + set -e + source /ghidra/venv/bin/activate + /ghidra/venv/bin/python3 "${PYGHIDRA_LAUNCHER}" "/ghidra" ${VMARG_LIST} "$@" +else + echo "Unknown MODE: $MODE. Valid MODE's are gui, headless, ghidra-server, bsim, bsim_ctl, or pyghidra." +fi diff --git a/settings.gradle b/settings.gradle index ed753c9816..b890a0bcd3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,6 +33,7 @@ includeProjects('GPL') /******************************************************************************************* * Include miscellaneous support modules *******************************************************************************************/ +includeProject('docker', '.', true) includeProject('Doclets', 'GhidraBuild/BuildFiles', true) includeProject('LaunchSupport', 'GhidraBuild', true) includeProject('MarkdownSupport', 'GhidraBuild', true)