Java의 코드 난독화

Mohammad Irfan 2023년10월12일
  1. 난독화란 무엇입니까?
  2. 코드 난독화 기술
  3. Java용 코드 난독화 도구
  4. Java의 난독화
  5. 요약
Java의 코드 난독화

이 자습서에서는 Java의 코드 난독화를 소개하고 주제를 이해하기 위한 몇 가지 예제 코드를 나열합니다.

난독화는 코드를 이해하기 어렵게 만드는 데 사용되는 프로그래밍 기술입니다. 악의적인 사용자로부터 소스 코드를 보호하기 위해 수행됩니다. 이 자습서에서는 난독화에 대해 자세히 알아봅니다.

난독화란 무엇입니까?

  • 난독화는 코드의 일부를 가독성이 떨어지고 이해하기 어렵고 시간이 많이 소요되도록 만드는 프로세스입니다.
  • 난독화는 코드 실행에 영향을 미치지 않으며 코드는 의도한 대로 계속 수행됩니다.
  • 코드 난독화는 해커와 공격자로부터 코드를 보호하는 데 사용됩니다. 이러한 악의적인 사용자는 코드를 리버스 엔지니어링하고 코드의 일부 단점을 이용하려고 할 수 있습니다. 코드의 리버스 엔지니어링을 방지하는 것은 난독화의 주요 사용 사례 중 하나입니다.
  • 해커와 공격자는 난독화를 사용하여 안티바이러스 도구가 적대적인 코드를 탐지하지 못하도록 합니다.

코드 난독화 기술

많은 코드 난독화 도구가 존재하며 다양한 기술을 사용하여 공격을 방지합니다. 간단히 말해서, 난독화는 중복 논리를 사용하고 원치 않는 코드를 추가하여 리더나 역설계 도구(예: 디컴파일러)를 흐리게 합니다.

가장 일반적인 코드 난독화 기술 몇 가지에 대해 논의해 보겠습니다.

  • 변수 및 메서드 이름 바꾸기: 변수와 ​​메서드의 이름을 읽기 어려운 이름으로 바꾸거나 인쇄할 수 없거나 보이지 않는 문자를 사용하는 것은 일반적으로 사용되는 난독화 기술입니다.
  • 제어 흐름 변경: 난독화 도구는 종종 코드의 제어 흐름을 변경합니다. 이렇게 하면 코드가 동일한 작업을 수행하지만 따르기 어려워집니다.
  • 더미 코드 사용: 일부 도구에는 원래 논리와 관련이 없는 가짜 코드가 포함되는 경우가 많습니다. 이것은 코드를 리버스 엔지니어링하기 어렵게 만듭니다.
  • 난독화된 코드를 정기적으로 변경: 난독화된 코드를 정기적으로 업데이트하면 이전 코드에 대한 해커의 모든 진행 상황이 손실됩니다. 공격자는 새 코드에서 처음부터 시작해야 합니다.
  • 암호화: 데이터(문자열과 같은)의 암호화는 해커가 코드의 진정한 의미를 이해하는 것을 방지합니다. 코드를 실행해야 할 때 암호 해독을 수행할 수 있습니다.

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;
        }
      }
      }
    }
  }
}

obfuscator는 프로그램을 실행하기 전에 해결해야 하는 추가 파일과 종속성도 생성하기 때문에 위의 코드는 IDE에서 직접 실행되지 않을 수 있습니다.

요약

코드 난독화는 리버스 엔지니어링으로 인한 공격을 방지하기 위해 수행됩니다. 공격자와 해커는 난독화를 사용하여 바이러스 백신 소프트웨어 및 기타 보호 도구를 속이기도 합니다. 코드 난독화 도구로 코드를 리버스 엔지니어링하는 것이 불가능하지는 않습니다. 그들은 그것을 매우 어렵고 시간이 많이 걸립니다.