Java でのコードの難読化

Mohammad Irfan 2023年10月12日
  1. 難読化とは何ですか
  2. コード難読化手法
  3. Java 用のコード難読化ツール
  4. Java での難読化
  5. まとめ
Java でのコードの難読化

このチュートリアルでは、Java でのコードの難読化を紹介し、トピックを理解するためのサンプルコードもいくつかリストします。

難読化は、コードを理解しにくくするために使用されるプログラミング手法です。これは、悪意のあるユーザーからソースコードを保護するために行われます。このチュートリアルでは、難読化について詳しく学習します。

難読化とは何ですか

  • 難読化とは、コードの一部を読みにくく、理解しにくく、時間がかかるようにするプロセスです。
  • 難読化はコードの実行に影響を与えず、コードは意図したとおりに動作し続けます。
  • コードの難読化は、ハッカーや攻撃者からコードを保護するために使用されます。これらの悪意のあるユーザーは、コードをリバースエンジニアリングして、コードのいくつかの欠点を利用しようとする可能性があります。コードのリバースエンジニアリングを防ぐことは、難読化の主なユースケースの 1つです。
  • ハッカーや攻撃者は、難読化を使用して、ウイルス対策ツールが敵対的なコードを検出するのを防ぎます。

コード難読化手法

多くのコード難読化ツールが存在し、攻撃を防ぐためにさまざまな手法を使用しています。一言で言えば、難読化は冗長なロジックを使用し、不要なコードを追加して、リーダーまたはリバースエンジニアリングツール(逆コンパイラーなど)を迷わせます。

最も一般的なコード難読化手法のいくつかについて説明しましょう。

  • 変数とメソッド名の名前の変更:変数とメソッドの名前を読みにくい名前に変更したり、出力できない文字や非表示の文字を使用したりすることは、一般的に使用される難読化手法です。
  • 制御フローの変更:難読化ツールは、コードの制御フローを変更することがよくあります。これにより、コードは同じタスクを実行しますが、従うのが難しくなります。
  • ダミーコードの使用:一部のツールには、元のロジックとは関係のない偽のコードが含まれていることがよくあります。これにより、コードのリバースエンジニアリングが困難になります。
  • 難読化されたコードを定期的に変更する:難読化されたコードを定期的に更新すると、前のコードでのハッカーの進行状況がすべて失われます。攻撃者は、新しいコードを最初から作成する必要があります。
  • 暗号化:データ(文字列など)の暗号化は、ハッカーがコードの真の意味を理解するのを防ぎます。コードを実行する必要がある場合は、復号化を実行できます。

Java 用のコード難読化ツール

Java は、他のプログラミング言語と同様に、リバースエンジニアリング攻撃を受けやすい傾向があります。コンパイルされた Java バイトコードを簡単にリバースエンジニアリングできる Java 用の逆コンパイラが利用可能です。以下は、最も人気のある Java 難読化ツールの一部です。

  • ProGuard
  • JODE
  • JavaGuard
  • RetroGuard
  • jarg
  • yGuard

Java での難読化

難読化ツールを使用して、以下に示す簡単なコードを難読化してみましょう。

public class StringAddition {
  public static void main(String args[]) {
    String s1 = "a";
    String s2 = "b";
    String s3 = s1 + s2;
  }
}

コードは次のコードに難読化されています。不要なダミー変数とロジックは、難読化されたコードに配置されます。元のコードロジックを理解するのは困難です。

import b.n;
public class StringAddition {
  private static final String[] d;
  public static void main(final String[] array) {
  Label_0054: {
    break Label_0054;
  Label_0003:
    while (true) {
      final String str = StringAddition.d[Integer.parseInt("1", 22) << 10017];
      try {
        new StringBuilder()
            .append(str)
            .append(StringAddition.d[24 << Integer.parseInt("9k1", 30)])
            .toString();
        return;
        final String x = StringAddition.d[8388608 >>> 4183];
        System.out.println(x);
      }
      // iftrue(Label_0003:, x.hashCode() == 1033634479)
      // monitorenter(array)
      // monitorenter(array)
      catch (EnumConstantNotPresentException i) {
        throw n.i = i;
      }
    }
  }
  }
  static {
    final char[] charArray =
        "\u0e47\u0e59\u0093\u0cc1¥£®£®\u00d1®®®®½£®A\u00da\u00dc®£®®\u00d1®®®\u00c2\u00dc®A\u00da£\u00d1£®®®\u00d1®®\u00c2£\u00d1A\u00da£®\u00dc®®®®\u00d1®\u00c2£®>\u00da£®£\u00d1®®®®\u00d1\u00c2£®A¥£®£®®®®®®½\u00f6\u00d1b\u00f9\u009e\u00d1\u0080\u008d\u008d\u0083\u00d1\u008d\u008d\u00e1\u009d\u00d1b\u00f9\u0080\u009f\u00dc\u008d\u008d\u008d\u0092\u00d1\u008d\u00e1\u0080\u0099>\u00f9\u0080\u008d\u0090\u00d1\u008d\u008d\u008d\u009e\u00d1\u00e1\u0080\u008d}¥\u0080\u008d\u0080\u009a\u00d1\u008d\u008d\u008d\u00df½\u0080\u008db\u00eb\u00dc\u008d\u0080\u008d\u0094\u00d1\u008d\u008d\u008d\u00e9\u00dc\u008db\u00f9\u00dc\u00d1\u00dc\u00d1\u00d1\u00d1\u00d1\u008d\u008d\u0097\u00dc\u008db\u00da£\u008d\u0080\u008d®®\u008d\u008d\u008d\u00c2£\u008db\u00f9£®\u0080\u008d\u008d®®\u008d\u008d\u00e1£®b\u00f9\u0080®£\u008d\u008d\u008d®®\u008d\u00e1\u0080®A\u00f9\u0080\u008d£®\u008d\u008d\u008d®®\u00e1\u0080\u008dA\u00da\u0080\u008d\u0080®®\u008d\u008d\u008d®\u00c2\u0080\u008db\u00da£®£®®®\u008d\u008d\u00fb½\u0080\u00deA\u00da \u008d\u00d3®®\u00ad\u008d\u00de®\u00c2 \u008d1\u00da£\u00ad\u0080\u00de®®\u00ad\u008d\u00de\u00c2£\u00adbª£® \u008d\u00de®®\u00ad\u008d²£®B\u00f9\u00d3®£\u00ad\u008d\u00de®®\u00ad\u00e1\u00d3®A\u00d9\u0080\u00de£®\u00ad\u008d\u00de®®\u00c1\u0080\u00deA\u00da£®£®®\u00ad\u008d\u00fb\u00d1½£®A\u00da\u00dc®£®®\u00d1®®®\u00c2\u00dc®A\u00da£\u00d1£®®®\u00d1®®\u00c2£\u00d1A\u00da£®\u00dc®®®®\u00d1®\u00c2£®>\u00da£®£\u00d1®®®®\u00d1\u00c2£®A¥£®£®\u00d1®®®®½£®A\u00da\u00dc®£®®\u00d1\u00fb\u00d1\u008d\u00e1\u0093\u00d1b\u00f9\u0080\u0093\u00dc\u008d\u008d\u008d\u0097\u00d1\u008d\u00e1\u0080\u0084>\u00f9\u0080\u008d\u008f\u00d1\u008d\u008d\u008d\u0092\u00d1\u00e1\u0080\u008d\u007f¥\u0080\u008d\u0080\u0085\u00d1\u008d\u008d\u008d\u009e½\u0080\u008db\u00f7\u00dc\u008d\u0080\u008d\u00dc\u00d1\u008d\u008d\u008d\u00e9\u00dc\u008db\u00f9\u008e\u00d1\u0080\u008d\u008d\u0098\u00d1\u008d\u008d\u00e1\u009d\u00d1b\u00f9\u0080\u009d\u00dc\u008d\u008d\u00fb\u00d1\u008d\u008d\u00c2£\u008db\u00f9£®\u0080\u008d\u008d®®\u008d\u008d\u00e1£®b\u00f9\u0080®£\u008d\u008d\u008d®®\u008d\u00e1\u0080®A\u00f9\u0080\u008d£®\u008d\u008d\u008d®®\u00e1\u0080\u008dA\u00da\u0080\u008d\u0080®®\u008d\u008d\u008d®\u00c2\u0080\u008db\u00da£\u008d\u0080\u008d®®\u008d\u008d\u008d\u00c2£\u008db\u00f9£®\u0080\u008d\u00fb\u00d1\u008d\u00de®\u00c2 \u008d1\u00da£\u00ad\u0080\u00de®®\u00ad\u008d\u00de\u00c2£\u00adbª£® \u008d\u00de®®\u00ad\u008d²£®B\u00f9\u00d3®£\u00ad\u008d\u00de®®\u00ad\u00e1\u00d3®A\u00d9\u0080\u00de£®\u00ad\u008d\u00de®®\u00c1\u0080\u00deA\u00da \u008d\u00d3®®\u00ad\u008d\u00de®\u00c2 \u008d1\u00da£\u00ad\u0080\u00fb\u00d1\u00fb\u0e59\u0090"
            .toCharArray();
    int n = 64 << 5658;
    final StackTraceElement stackTraceElement;
    final int n2 = (stackTraceElement = new Throwable().getStackTrace()[107 >>> 11463])
                       .getMethodName()
                       .hashCode()
        & Integer.parseInt("171h3c0", 22) - 149806781;
    final char[] charArray2 = stackTraceElement.getClassName().toCharArray();
    final char[] array = charArray;
    final int n3 = 101 >>> 10951;
    ++n;
    d = new String[array[n3] ^ Integer.parseInt("1g1nna6", 28) - 758393825 ^ n2];
    int n4 = 26 >>> Integer.parseInt("gk9", 31);
  Label_0101:
    while (true) {
      int n5;
      final char[] value =
          new char[n5 = (charArray[n++] ^ Integer.parseInt("ifjj061", 22) - 2122795820 ^ n2)];
      int n6 = 72 << 11485;
    Label_0334_Outer:
      while (true) {
      Label_0272: {
        if (n5 <= 0) {
          break Label_0272;
        }
        int n7 = charArray[n];
      Label_0334:
        while (true) {
        Label_0388: {
          switch (charArray2[n % charArray2.length] ^ (0xC999A060 ^ 0xC999A0AE)) {
            case 160: {
              break Label_0334;
            }
            case 162: {
              break Label_0334;
            }
            case 131: {
              break Label_0334;
            }
            case 163: {
              break Label_0334;
            }
            case 167: {
              break Label_0334;
            }
            case 136: {
              break Label_0388;
            }
            case 170: {
              break Label_0388;
            }
            case 186: {
              break Label_0388;
            }
            case 139: {
              break Label_0388;
            }
            case 171: {
              break Label_0388;
            }
          }
          while (true) {
            value[n6] = (char) n7;
            try {
              ++n6;
              ++n;
              --n5;
              // monitorexit(charArray)
              n7 ^= -400944133 + 400944374;
              continue Label_0334_Outer;
              n7 ^= Integer.parseInt("83bdf8k", 22) - 925222572;
              continue Label_0334_Outer;
              continue Label_0334;
              StringAddition.d[n4++] = new String(value).intern();
              // iftrue(Label_0101:, n < charArray.length)
              return;
              n7 ^= Integer.parseInt("6j", 23) << 3840;
              continue Label_0334_Outer;
              n7 ^= 61440 >>> 11787;
              continue Label_0334_Outer;
              n7 ^= 364965749 - 364965616;
            } catch (Throwable t) {
              break;
            }
          }
        } break;
        }
      }
      }
    }
  }
}

難読化ツールは、プログラムを実行する前に解決する必要のある追加のファイルと依存関係も生成するため、上記のコードは IDE で直接実行されない場合があることに注意してください。

まとめ

コードの難読化は、リバースエンジニアリングによる攻撃を防ぐために行われます。攻撃者やハッカーは、難読化を使用してウイルス対策ソフトウェアやその他の保護ツールをだまします。コードのリバースエンジニアリングは、コード難読化ツールによって不可能にならないことに注意してください。それらはそれを非常に困難で時間のかかるものにします。