본문 바로가기

세상사는 이야기/이야기들

[펌] 자바 역컴파일과 난독처리

전공이야기
자바 역컴파일과 난독처리

이수현 교수님

Reverse engineering is the process of analyzing a subject system to identify the system's components and their interrelationships and create representations of the system in another form or at a higher level of abstraction.
- Chikofsky & Cross


자바 언어의 중요한 특징 중의 하나는 어떤 기계에서든지 실행될 수 있다는 점이다. 이러한 기계 독립성은 자바 프로그램이 바이트 코드 형태로 번역되기에 가능한 일이다. 바이트 코드는 특정 기계에 종속되지 않은 자바가상기계(Java virtual machine)의 코드이므로 자바가상기계를 지원하는 곳이면 어디에서든지 실행 가능하다. 그런데 바이트 코드로 번역된 코드에는 자바 소스코드의 정보가 그대로 포함되어 있는데, 이로 인해서 바이트 코드에서 자바 소스코드로의 역컴파일(decompile)이 쉽게 이루어지는 특징이 있다. 본 고에서는 자바 바이트 코드의 역컴파일에 관해서 살펴보고, 바이트 코드를 역컴파일 되기 어려운 형태로 만드는 기술인 코드 난독처리(code obfuscation) 기법에 관해서 설명한다.

1. 역컴파일
1.1 역엔지니어링
역엔지니어링(reverse engineering)은 대상이 되는 시스템을 분석하여, 그 시스템의 구성요소와 구성요소사이의 관계를 밝히는 작업으로 전산학 뿐만 아니라 경영학이나 공학의 여러 분야에서 자주 사용되는 용어이다. 역컴파일은 역엔지니어링이 프로그래밍 언어 분야에 적용된 사례이다.
전통적으로 역엔지니어링은 매우 어려운 것으로 알려져 있다. 그 이유로는 역엔지니어링 해야 할 대상이 매우 크고, 배포되어 실행되는 목적코드(object code)에는 소스코드의 정보가 숨겨서 보관되기 때문이다. 정보를 숨기는 방법으로는 변수 이름과 같은 정보를 제거하는 방법과 라이브러리 함수를 호출하여 숨기는 방법 등이 있다. 예를 들어, C 언어에서 다음과 같은 함수호출이 있을 때,
    printf("%d", i);

어셈블리어로는 다음과 같이 번역될 것이며,
    push @f
    push %a
    call printf

기계어로 번역된 결과는 아마도 다음과 같을 것이다:
    19D319B678EC55

위와 같은 기계어 코드를 보고 C 소스코드를 알아내는 것은 대단히 어려운 작업이 된다. 이런 이유로 대부분의 상용 프로그램이 목적코드의 형태로 배포되며, 누구도 소스코드가 어떻게 생겼는지에 관심을 두지 않는다. ("누구도"라는 말은 사실은 과장된 것이다. 누군가가 어두운 골방에서 Visual Basic이나 Oracle DBMS의 실행파일을 가져다 역컴파일 하고 있는지도 모른다.)

1.2 자바의 역컴파일
자바의 등장은 역엔지니어링이 심각한 문제가 될 수 있음을 드러내게 되었다. 자바의 소스코드는 바이트 코드로 컴파일 되게 되는데, 이 바이트 코드는 이식성을 강조하였기 때문에 사용자가 바이트 코드의 제어에 개입할 여지를 없애 버렸다. 이의 결과로 바이트 코드에는 자바 소스코드가 가지고 있는 대부분의 정보를 가지고 있게 되었고, 역컴파일은 간단한 조작만으로 쉽게 이루어지게 되었다. 이는 만약 자바로 만들어진 프로그램의 컴파일된 바이트 코드만이 배포되었다고 하더라도 쉽게 소스코드를 만들어 낸다는 것을 의미하므로 심각한 문제를 유발하게 된다. 예를 들어, 셰어웨어 프로그램 중에 30일간만 사용하게 하는 프로그램을 많이 보았을 것이다. 이런 프로그램이 자바로 만들어졌다면 이를 자바로 역컴파일 하여 자바 소스코드를 만들어 낸 다음에 이를 편집하여 30일 제한을 풀어 버리고 다시 컴파일 하여 사용하면 된다.
대표적인 자바 역컴파일러로는 다음과 같은 것들이 있다. 이들에 대한 소개는 다음의 URL을 참고하면 된다.
http://Meurrens.ML.org/ip-Links/Java/codeEngineering/decomp.html
▷ Mocha - 1996년 발표된 역컴파일러. Borland사의 JBuilder에 ocha를 포함하기로 결정하였다.
▷ Jive - IBM에서 만든 상용 제품이다.
▷ IceBreaker - 자바 소스와 바이트 코드를 한꺼번에 보여주기 때문에, 소스가 어떻게 번역되는 지를 보면서 역컴파일할 수 있다.
▷ WingDis - 성능이 좋은 상용 제품이다.
▷ DejaVu - Innovative Software사의 자바 개발환경의 일부이다. 난독처리가 된 프로그램을 역컴파일하는 특징이 있다.
▷ Jad - 다른 역컴파일러들과는 달리 C++로 작성되어 실행속도가 빠르다. 출력되는 소스 코드에 바이트 코드를 주석으로 달아 주기 때문에, 자바의 문장이나 수식이 어떻게 컴파일 되는지를 알아 볼 수 있다.

1.3 역컴파일을 막는 방법
소스코드의 역엔지니어링을 막는 한가지 방법으로 프로그램에 대한 물리적인 접근을 제한하는 방법이 있다. 즉, 사용자가 직접 목적코드를 보는 것을 허용하지 않는 것이다. 대신에 적절한 유저 인터페이스를 제공하고 사용자는 이 인터페이스만을 통해서 프로그램을 사용하도록 하는 것이다. 클라이언트-서버 모델이 이렇게 작동되는 대표적인 예이다. 불행히도 이 방법은 네트워크의 속도와 지연 때문에 성능에 많은 문제점이 있다. 이러한 문제점에 대한 한가지 부분적인 해결책으로는 중요한 코드는 서버에 두고 덜 중요한 코드는 클라이언트에서
수행하도록 하여 네트워크의 부담을 줄이는 방법이 있다.

코드의 암호화도 한가지 방법이 될 수 있다. 즉, 목적코드를 암호화하여 배포하는 것이다. 그러나 암호화/복호화가 완전히 하드웨어적으로 해결되지 아니하고는 나쁜 의도를 가진 사용자에 의해서 강제적으로 복호화 될 수 있다. 불행히도 특수 하드웨어의 사용은 프로그램의 이식성을 떨어뜨리는 요인이 된다.

역컴파일이 어려운 코드를 배포하는 것이 하나의 해결책이 될 수 있다. 자바 바이트 코드 대신에 기계 고유의 코드를 사용하게 하는 방법은 역컴파일을 어렵게 만들지만, 실현가능성이 높지는 않다. 그 이유는 기계고유의 코드로 쓰여진 프로그램은 바이트 코드 검증기의 대상이 될 수 없어 더 위험한 코드를 내포할 수 있기 때문이다. 전자서명은 이 문제를 경감하는 하나의 방안이 될 수 있다.

만약 역엔지니어링을 막는 방법이 없다면, 역엔지니어링이 대단히 어렵게 하거나 비용이 많이 들게 만드는 것이 차선책이 될 수 있다. 코드 난독처리기는 프로그램을 역컴파일이 대단히 어렵게 만드는 기술이다. 코드 난독처리기에 의해서 만들어진 코드는 원래의 코드와 기능적으로는 변함이 없어서 같은 결과를 내지만, 프로그램의 실행을 느리게 만들 가능성이 있다.


2. 자바 난독처리기

2.1 난독처리의 방법
자바 난독처리기의 구현 방법에는 여러 가지가 있으나 여기에서는 대표적으로 사용되는 몇 가지 방법들을 소개한다. 자세한 내용은 참고문헌 [1]을 참고하기 바란다.

(1) 이름의 변경
변수 이름이나 주석 등은 프로그램의 실행과는 상관이 없는 부분인데, 이들 정보를 변경하여 역컴파일을 어렵게 한다. 변수 이름은 프로그램을 읽기 쉽게 하는 중요한 요소가 되므로 이를 난해한 단어로 바꿈으로서 역컴파일로 생성된 소스코드를 읽기 어렵게 만든다.

(2) 프로그램의 자료구조를 변경
프로그램은 데이터를 조작함으로써 실행된다. 따라서 데이터의 구조를 모르면 프로그램의 실행도 이해할 수 없다. 사용되는 자료구조를 변경하는 것은 난독처리의 중요한 부분이 된다.

데이터의 형태나 위치를 변경할 수 있는데, 지역변수는 비지역변수로 바꾸는 예를 다음에 보였다.

난독처리전

난독처리후
  void P() {      int i;      ... i ..;  }  void Q() {      int k;      ... k ...;  }
int C;  void P() {  ... C ...;  }  void Q() {  ... C ...;  }

또한 자료 객체의 형태를 변경한 예로 다음과 같은 방법이 있다.

난독처리전

난독처리후

 int i = 1; while (i < 9) {    ... A[i] ...;    I++; }
 Int i = new Int(1); while (i.value < 9) {    ... A[i.value] ..;    i.value++; }

변수 값을 변경한다. 예를 들어, 변수 i를 모든 위치에서 c1*i+ c2의 형태로 바꾸어도 프로그램의 실행에는 지장이 없다. 아래의 예는 c1이 8이고 c2가 3인 경우이다.

난독처리전

난독처리후

 int i = 1; while (i < 100) {    ... A[i] ...;    i++; }
 int i = 11; while (i < 803) {    ... A[(i-3)/8] ...;      i += 8; }

메모리에 저장되는 방법을 변경한다. 예를 들어, 2차원 배열을 1차원 배열로 바꾸거나 하나의 배열을 여러 개의 작은 배열로 분할하는 방법 등이 있을 수 있다.
데이터의 순서를 조정한다. 예를 들어 1차원 배열의 순서를 바꾼 다음에 A[i]라고 쓰는 대신에 f(i)처럼 배열의 원소를 참조하는 함수호출로 바꾸어 쓴다.

(3) 프로그램의 제어를 변경
프로그램의 실행은 제어흐름이므로, 제어를 어지럽게 하면 프로그램은 그만큼 더 읽기 어려워지게 된다.
프로그램의 문장이 묶이는 단위를 조절한다. 예를 들어, 프로시저를 인라인(inline)화하는 방법이 있다. 이 방법에서 프로시저 호출 문장은 프로시저 자체로 대치된다. 아웃라인(outline)화하는 방법도 있다. 프로그램의 일부분을 떼어내어 독립된 하나의 프로시저로 구성하고 떼어낸 부분은 프로시저 호출 문장이 들어간다.
프로그램이 실행되는 순서를 바꾼다. 예를 들어, 1에서 10까지 증가하는 카운트는 10에서 1로 감소하는 카운트로 변경한다. 이 방법은 전통적인 컴파일러의 최적화의 기법으로 많이 사용되는 방법이다. (컴파일러의 최적화 기법은 참고문헌 [2]를 참고하세요.) 하나의 루프(loop)를 여러 개의 루프로 나누거나, 여러 개의 루프를 하나로 합치는 등의 테크닉도 컴파일러의 최적화 기법이기도 하면서 난독처리에서도 사용될 수 있다.
실행되지 않거나 실행되더라도 무의미한 결과를 산출하는 불필요한 코드를 일부러 집어넣어 읽기 어렵게 만든다. 실행되지 않는 코드를 데드코드(deadcode)라고 한다.
자바에 없는 코드를 삽입한다. goto문은 자바에는 없지만 자바 바이트 코드에는 존재한다. 따라서 goto가 존재하는 바이트 코드는 자바로 역컴파일 될 수가 없다.
루프 조건의 확장하여 프로그램을 어렵게 만들 수 있다. 아래의 예에서 (j*j*(j+1)*(j+1)%4==0) 부분은 항상 참이 되는 것을 주목하기 바란다.

난독처리전

난독처리후

 int i = 1; while (i < 100)  {     ...;     i++; }
 int i=1, j=100; while((i<100)&&(j*j*(j+1)*(j+1)%4==0)) {    ...     i++;    j = j*i+3; }


2.2 자바 난독처리기
앞 절에서 소개한 난독처리의 방법은 많은 난독처리기에서 구현되었다. 대표적인 난독처리기는 다음과 같은 것들이 있다. 이들에 대한 소개는 다음의 URL을 참고하면 된다.
http://Meurrens.ML.org/ip-Links/Java/codeEngineering/obfusc.html
▷ Crema - Mocha 역컴파일러를 만든 사람이 만들었다. Mocha와 함께 JBuilder로 넘어간 이후에 Crema는 인터넷에서 사라졌다.
▷ Jobe - UCSD 대학에서 만든 공개 난독처리기이다.
▷ HoseMocha - Mocha의 대항하여 만들어졌다.
▷ HashJava - SBKTech사의 쉐어웨어 제품이다.
▷ jShrink - 난독처리와 코드압축을 동시에 하는 상용 제품이다.
▷ SoftSeal - 각각의 바이트를 전자서명방식에 의해서 암호화하므로 역컴파일이 불가능 하다. 그러나 난독처리된 프로그램을 실행하기 위해서는 별도의 프로그램이 필요하다.


3. 결론
자바 프로그램은 컴파일된 코드만 배포한다고 하여도 그로부터 소스코드를 쉽게 만들어 낼 수 있으므로 소프트웨어의 보호라는 측면에서 많은 문제점을 가지고 있다. 자신이 작성한 자바 프로그램을 얼굴없는 도둑으로부터 보호하려면 자바 난독처리기를 사용하는 것이 한 방법이 될 것이다.
자바 난독처리기와 역컴파일러는 밀접한 관련이 있다. 자바 역컴파일러를 만든 사람은 그 원리를 모두 이해하기 때문에, 역컴파일을 어렵게 만드는 자바 난독처리기를 만들기가 그만큼 쉬워진다. 우리에게 독자적인 자바 난독처리기가 필요한 이유도 이 때문이다. 자바 난독처리기와 역컴파일러에 대한 우리 학생들의 많은 관심을 기대해 본다.

참고문헌

[1] C. Collberg, C. Thomborson and D. Low, A Taxonomy of Obfuscating Transformations, Technical Report #148,
Department of Computer Science, University of Auckland, New Zealand, 1997.
[2] A. Aho, R. Sethi and J. Ullman, Compilers: Principles, Techniques, and Tools, Code Optimization Chapter, pp. 585-722, Addison-Sweley, 1986.


copyright(c),1999.2 문화부 system,
but all rights reserved.