Java Decompiler - 자바 클래스의 코드 내용을 확인하는 도구

From_A_To_Z·2024년 2월 13일
0

Java Decompiler?

  • Java 런타임의 경우, 빌드된 결과물을 클래스 혹은 JAR 형태로 압축하여 관리하고 있음
  • 난독화된 경우는 어쩔수 없으나 대부분 Java Decompiler 툴을 통해 코드 내용을 확인할 수 있음

Solution

1. JD-GUI로 JAR 디컴파일 및 소스 확인

  • Java Decompiler (https://java-decompiler.github.io/) 사이트에서 GUI 기반 디컴파일러 제공
  • 라이센스: GNU GPL v3
  • JD-GUI의 파일 탐색기에서 JAR 파일을 선택하면 해당 JAR 파일이 디컴파일되고 Java 코드를 확인 가능

2. JD-CLI로 JAR 디컴파일 및 소스 확인

  • JD-CLI는 JD-GUI의 core library를 기반으로 개발된 Command-line 툴 (https://github.com/intoolswetrust/jd-cli)
  • 라이센스: GNU GPL v3
  • 개인 사용자가 개발하였으며 공식으로 릴리즈된 소프트웨어는 아님
  • Windows 환경 혹은 Linux/Unix 환경에서 사용 가능하며 JRE 설치가 필수
D:\jd-cli-dist>jd-cli.bat -od test javafunction.jar
13:20:52.850 INFO  com.github.kwart.jd.cli.Main - Decompiling javafunction.jar
13:20:52.854 INFO  com.github.kwart.jd.output.DirOutput - Directory output will be initialized for path test
13:20:52.930 INFO  com.github.kwart.jd.output.DirOutput - Finished with 1 class file(s) and 1 resource file(s) written.
  • -od로 입력한 out folder에 디컴파일된 소스 파일들이 저장되며 해당 Java 코드를 확인 가능
D:\jd-cli-dist\test>type com\example\Handler.java
package com.example;

import com.google.gson.JsonObject;

public class Handler {
  public JsonObject handleRequest(JsonObject request) {
    JsonObject response = new JsonObject();
    response.addProperty("request", request.toString());
    response.addProperty("response", "Hello Serverless World!");
    return response;
  }
}

3. JD-CORE 기반의 디컴파일러 개발

구현방법: JD-Core 라이브러리를 추가하여 Java 기반 디컴파일러를 개발

(1) Loader 인터페이스 구현

Loader loader = new Loader() {
    @Override
    public byte[] load(String internalName) throws LoaderException {
        InputStream is = this.getClass().getResourceAsStream("/" + internalName + ".class");

        if (is == null) {
            return null;
        } else {
            try (InputStream in=is; ByteArrayOutputStream out=new ByteArrayOutputStream()) {
                byte[] buffer = new byte[1024];
                int read = in.read(buffer);

                while (read > 0) {
                    out.write(buffer, 0, read);
                    read = in.read(buffer);
                }

                return out.toByteArray();
            } catch (IOException e) {
                throw new LoaderException(e);
            }
        }
    }

    @Override
    public boolean canLoad(String internalName) {
        return this.getClass().getResource("/" + internalName + ".class") != null;
    }
};

(2) Printer 인터페이스 구현

Printer printer = new Printer() {
    protected static final String TAB = "  ";
    protected static final String NEWLINE = "\n";

    protected int indentationCount = 0;
    protected StringBuilder sb = new StringBuilder();

    @Override public String toString() { return sb.toString(); }

    @Override public void start(int maxLineNumber, int majorVersion, int minorVersion) {}
    @Override public void end() {}

    @Override public void printText(String text) { sb.append(text); }
    @Override public void printNumericConstant(String constant) { sb.append(constant); }
    @Override public void printStringConstant(String constant, String ownerInternalName) { sb.append(constant); }
    @Override public void printKeyword(String keyword) { sb.append(keyword); }
    @Override public void printDeclaration(int type, String internalTypeName, String name, String descriptor) { sb.append(name); }
    @Override public void printReference(int type, String internalTypeName, String name, String descriptor, String ownerInternalName) { sb.append(name); }

    @Override public void indent() { this.indentationCount++; }
    @Override public void unindent() { this.indentationCount--; }

    @Override public void startLine(int lineNumber) { for (int i=0; i<indentationCount; i++) sb.append(TAB); }
    @Override public void endLine() { sb.append(NEWLINE); }
    @Override public void extraLine(int count) { while (count-- > 0) sb.append(NEWLINE); }

    @Override public void startMarker(int type) {}
    @Override public void endMarker(int type) {}
};

(3) "decompile(loader, Printer, InternalTypeName);" 메소드를 호출

ClassFileToJavaSourceDecompiler decompiler = new ClassFileToJavaSourceDecompiler();

decompiler.decompile(loader, printer, "path/to/YourClass");

String source = printer.toString();

Comparision

JD-GUIJD-CLIJD-CORE
라이센스GPL-3.0GPL-3.0GPL-3.0
공식 여부공식비공식공식
지원 범위Java 1.1.8 ~ Java 12.0Java 1.1.8 ~ Java 12.0Java 1.1.8 ~ Java 12.0
장점다양한 OS 지원, GUI 지원현재 요건에 맞는 서비스 개발 가능현재 요건에 맞는 서비스 개발 가능
단점CLI 제공 X비공식 툴, OS Command 기반의 호출 필요별도 디컴파일러에 대한 별도 개발 공수
profile
What goes around comes around.

0개의 댓글