├── .github └── workflows │ └── main.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md └── src └── init /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | IMAGE_NAME: ${{ github.repository }} 11 | 12 | jobs: 13 | build-and-push-image: 14 | name: Build and push image 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: read 18 | packages: write 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v2 22 | 23 | - name: Log in to the Container registry 24 | uses: docker/login-action@v1 25 | with: 26 | registry: ${{ env.REGISTRY }} 27 | username: ${{ github.actor }} 28 | password: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | - name: Extract metadata (tags, labels) for Docker 31 | id: meta 32 | uses: docker/metadata-action@v3 33 | with: 34 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 35 | tags: | 36 | type=raw,value=latest 37 | 38 | - name: Build and push Docker image 39 | uses: docker/build-push-action@v2 40 | with: 41 | context: . 42 | push: true 43 | tags: ${{ steps.meta.outputs.tags }} 44 | labels: ${{ steps.meta.outputs.labels }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | infiles/ 2 | libfiles/ 3 | outfiles/ 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/library/debian AS buildbase 2 | 3 | RUN set -xe \ 4 | && apt-get update \ 5 | && apt-get install -y openjdk-11-jdk \ 6 | && apt-get clean 7 | 8 | FROM docker.io/library/debian AS runtimebase 9 | 10 | RUN set -xe \ 11 | && apt-get update \ 12 | && apt-get install -y openjdk-11-jre python2 python3 procyon-decompiler unzip astyle \ 13 | && apt-get clean 14 | 15 | FROM buildbase AS dependencies 16 | 17 | ARG GRADLE_VERSION=7.4.2 18 | ARG GRADLE_URL=https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip 19 | 20 | ARG CFR_VERSION=0.152 21 | ARG CFR_URL=https://github.com/leibnitz27/cfr/releases/download/${CFR_VERSION}/cfr-${CFR_VERSION}.jar 22 | 23 | ARG FERNFLOWER_URL=https://github.com/fesh0r/fernflower/archive/master.tar.gz 24 | 25 | ARG JRT_EXTRACTOR_URL=https://github.com/Storyyeller/jrt-extractor/archive/master.tar.gz 26 | 27 | ARG KRAKATAU_URL=https://github.com/Storyyeller/Krakatau/archive/master.tar.gz 28 | 29 | ARG JADX_VERSION=1.3.5 30 | ARG JADX_URL=https://github.com/skylot/jadx/releases/download/v${JADX_VERSION}/jadx-${JADX_VERSION}.zip 31 | 32 | ARG ENJARIFY_URL=https://github.com/Storyyeller/enjarify/archive/master.tar.gz 33 | 34 | RUN set -xe \ 35 | && apt-get update \ 36 | && apt-get install -y curl unzip 37 | 38 | # A recent version of Gradle is needed to build Fernflower. 39 | RUN set -xe \ 40 | && curl -q -s -S -L --create-dirs -o ./gradle.zip $GRADLE_URL \ 41 | && unzip ./gradle.zip \ 42 | && mv ./gradle-${GRADLE_VERSION}/lib/* /usr/local/lib \ 43 | && mv ./gradle-${GRADLE_VERSION}/bin/* /usr/local/bin 44 | 45 | WORKDIR /dependencies 46 | 47 | RUN set -xe \ 48 | && curl -q -s -S -L --create-dirs -o ./out/cfr.jar $CFR_URL \ 49 | && curl -q -s -S -L --create-dirs -o ./fernflower.tgz $FERNFLOWER_URL \ 50 | && mkdir -p ./fernflower && tar -C ./fernflower --strip-components=1 -xf fernflower.tgz \ 51 | && (cd fernflower && gradle jar) \ 52 | && mv ./fernflower/build/libs/fernflower.jar ./out \ 53 | && curl -q -s -S -L --create-dirs -o ./jrt-extractor.tgz $JRT_EXTRACTOR_URL \ 54 | && mkdir -p ./jrt-extractor && tar -C ./jrt-extractor --strip-components=1 -xf jrt-extractor.tgz \ 55 | && (cd ./jrt-extractor && javac JRTExtractor.java && java -ea JRTExtractor) \ 56 | && mv ./jrt-extractor/rt.jar ./out \ 57 | && curl -q -s -S -L --create-dirs -o ./krakatau.tgz $KRAKATAU_URL \ 58 | && mkdir -p ./krakatau && tar -C ./krakatau --strip-components=1 -xf krakatau.tgz \ 59 | && mv ./krakatau ./out \ 60 | && curl -q -s -S -L --create-dirs -o ./jadx.zip $JADX_URL \ 61 | && mkdir ./out/jadx \ 62 | && (cd ./out/jadx && unzip ../../jadx.zip) \ 63 | && curl -q -s -S -L --create-dirs -o ./enjarify.tgz $ENJARIFY_URL \ 64 | && mkdir -p ./enjarify && tar -C ./enjarify --strip-components=1 -xf enjarify.tgz \ 65 | && mv ./enjarify ./out 66 | 67 | FROM runtimebase AS runtime 68 | 69 | COPY --from=dependencies /dependencies/out /opt 70 | 71 | ENV INFILES="/infiles" \ 72 | LIBFILES="/libfiles" \ 73 | OUTFILES="/outfiles" \ 74 | JAVA_XMX="2G" 75 | 76 | COPY ./src/init /init 77 | 78 | RUN chmod 540 /init 79 | 80 | ENTRYPOINT ["/init"] 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License (ISC) 2 | 3 | Copyright 2021 eikendev 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | IMAGE := java-decompiler 2 | 3 | .PHONY: build 4 | build: 5 | podman build \ 6 | -t \ 7 | local/${IMAGE} . 8 | 9 | .PHONY: run_debug 10 | run_debug: 11 | podman run \ 12 | -ti \ 13 | --rm \ 14 | -v ${PWD}/infiles:/infiles:Z,ro \ 15 | -v ${PWD}/libfiles:/libfiles:Z,ro \ 16 | -v ${PWD}/outfiles:/outfiles:Z,rw \ 17 | local/${IMAGE} 18 | 19 | .PHONY: run 20 | run: 21 | podman pull ghcr.io/eikendev/java-decompiler:latest 22 | podman run \ 23 | -ti \ 24 | --rm \ 25 | -v ${PWD}/infiles:/infiles:Z,ro \ 26 | -v ${PWD}/libfiles:/libfiles:Z,ro \ 27 | -v ${PWD}/outfiles:/outfiles:Z,rw \ 28 | ghcr.io/eikendev/java-decompiler:latest 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Java Decompiler

3 |

4 | Have your decompilers ready when you need them most. 5 |

6 |

Java Decompiler combines the strength of four popular Java decompilers.

7 |
8 | 9 |

10 | License  11 |

12 | 13 | ## 📄 Usage 14 | 15 | First, create a directory `./infiles` that contains all your JAR and APK files you want to decompile. 16 | 17 | Then, in case the targeted files depend on any external library, put a copy of these libraries in JAR format into a directory `./libfiles`. 18 | Some decompilers depend on this to work properly. 19 | 20 | Next, prepare an empty directory `./outfiles`, which is where the output of the decompilers will be written to. 21 | 22 | Lastly, run the Docker image via the following command. 23 | 24 | ```bash 25 | docker run \ 26 | -ti \ 27 | --rm \ 28 | -v "$PWD/infiles:/infiles:Z,ro" \ 29 | -v "$PWD/libfiles:/libfiles:Z,ro" \ 30 | -v "$PWD/outfiles:/outfiles:Z,rw" \ 31 | ghcr.io/eikendev/java-decompiler:latest 32 | ``` 33 | 34 | If you want to use [Podman](https://podman.io/), simply switch `docker` to `podman` at the start of the command. 35 | 36 | ## 💡 Background 37 | 38 | This Docker image is equipped with four Java decompilers: 39 | - [CFR](https://www.benf.org/other/cfr/) 40 | - [Fernflower](https://github.com/JetBrains/intellij-community/tree/master/plugins/java-decompiler/engine) 41 | - [Krakatau](https://github.com/Storyyeller/Krakatau) 42 | - [Procyon](https://github.com/mstrobel/procyon) 43 | 44 | It also includes [Enjarify](https://github.com/Storyyeller/enjarify) and [jadx](https://github.com/skylot/jadx) for the decompilation of APK files. 45 | 46 | For more information on all these tools, check out [my related blog post](https://eiken.dev/blog/2021/02/how-to-break-your-jar-in-2021-decompilation-guide-for-jars-and-apks/). 47 | -------------------------------------------------------------------------------- /src/init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | count=0 6 | 7 | function decompile_jars { 8 | local infiles 9 | local decompiler 10 | local outdir 11 | 12 | infiles="$1" 13 | 14 | if [ "$(ls -A $infiles/*.jar)" ]; then 15 | for jarfile in $infiles/*.jar; do 16 | jarcount=0 17 | 18 | decompiler='cfr' 19 | outdir="$OUTFILES/$(basename $jarfile)-$decompiler" 20 | if [[ -e "$outdir" ]]; then 21 | printf "Directory '%s' already exists. Skipping...\n" "$outdir" >&2 22 | else 23 | ((jarcount+=1)) 24 | mkdir -p "$outdir" 25 | java "-Xmx$JAVA_XMX" -jar /opt/cfr.jar "$jarfile" --outputdir "$outdir" 26 | fi 27 | 28 | decompiler='fernflower' 29 | outdir="$OUTFILES/$(basename $jarfile)-$decompiler" 30 | if [[ -e "$outdir" ]]; then 31 | printf "Directory '%s' already exists. Skipping...\n" "$outdir" >&2 32 | else 33 | ((jarcount+=1)) 34 | mkdir -p "$outdir" 35 | java "-Xmx$JAVA_XMX" -jar /opt/fernflower.jar "$jarfile" "$outdir" 36 | (cd "$outdir" && unzip *.jar && rm *.jar) 37 | fi 38 | 39 | decompiler='krakatau' 40 | outdir="$OUTFILES/$(basename $jarfile)-$decompiler" 41 | if [[ -e "$outdir" ]]; then 42 | printf "Directory '%s' already exists. Skipping...\n" "$outdir" >&2 43 | else 44 | ((jarcount+=1)) 45 | mkdir -p "$outdir" 46 | libs="$(find "$LIBFILES" -type f -iname '*.jar' -print0 | tr '\0' ';')" 47 | rt='/opt/rt.jar' 48 | paths="$libs$rt" 49 | /opt/krakatau/decompile.py -out "$outdir" -skip -nauto -path "$paths" "$jarfile" 50 | fi 51 | 52 | decompiler='procyon' 53 | outdir="$OUTFILES/$(basename $jarfile)-$decompiler" 54 | if [[ -e "$outdir" ]]; then 55 | printf "Directory '%s' already exists. Skipping...\n" "$outdir" >&2 56 | else 57 | ((jarcount+=1)) 58 | mkdir -p "$outdir" 59 | procyon -jar "$jarfile" -o "$outdir" 60 | fi 61 | 62 | if [[ "$jarcount" -gt 0 ]]; then 63 | ((count+=1)) 64 | fi 65 | done 66 | fi 67 | } 68 | 69 | function decompile_apks { 70 | local infiles 71 | local decompiler 72 | local outdir 73 | 74 | infiles="$1" 75 | 76 | if [ "$(ls -A $INFILES/*.apk)" ]; then 77 | for apkfile in $INFILES/*.apk; do 78 | apkcount=0 79 | 80 | decompiler='jadx' 81 | outdir="$OUTFILES/$(basename $apkfile)-$decompiler" 82 | if [[ -e "$outdir" ]]; then 83 | printf "Directory '%s' already exists. Skipping...\n" "$outdir" >&2 84 | else 85 | ((apkcount+=1)) 86 | mkdir -p "$outdir" 87 | /opt/jadx/bin/jadx --deobf -d "$outdir" --log-level INFO "$apkfile" 88 | fi 89 | 90 | if [[ "$apkcount" -gt 0 ]]; then 91 | ((count+=1)) 92 | fi 93 | done 94 | fi 95 | } 96 | 97 | mkdir -p ~/enjarify 98 | 99 | if [ "$(ls -A $INFILES/*.apk)" ]; then 100 | for apkfile in $INFILES/*.apk; do 101 | /opt/enjarify/enjarify.sh -o "$HOME/enjarify/$(basename $apkfile).jar" "$apkfile" 102 | done 103 | fi 104 | 105 | decompile_jars "$INFILES" 106 | decompile_jars "$HOME/enjarify" 107 | decompile_apks "$INFILES" 108 | 109 | if [[ "$count" -eq 0 ]]; then 110 | printf "No files were processed.\n" >&2 111 | exit 1 112 | else 113 | find "$OUTFILES" -type f -iname '*.java' -print0 | xargs -0 --max-procs=8 astyle --suffix=none --style=java --indent=tab 114 | fi 115 | --------------------------------------------------------------------------------