├── .gitignore
├── Makefile
├── LICENSE
├── .github
└── workflows
│ └── main.yml
├── README.md
├── Dockerfile
└── src
└── init
/.gitignore:
--------------------------------------------------------------------------------
1 | infiles/
2 | libfiles/
3 | outfiles/
4 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------