역컴파일러

위키백과, 우리 모두의 백과사전.

역컴파일러(영어: decompiler)는 컴파일러와 반대의 역할을 하는 컴퓨터 프로그램이다. 즉, 이것은 상대적으로 저수준의 추상에 있는 프로그램의 코드를 고수준의 추상으로 변형한다. 역컴파일러는 보통 원본 소스코드로 완벽하게 재구성될 수 없으며, 결과가 매우 다양할 수 있다. 그럼에도 불구하고 이것은 소프트웨어 리버스 엔지니어링에서 매우 중요한 도구이다. 역컴파일러는 실행 파일을 입력으로 갖고, 같은 기능을 하는 소스 코드 파일 즉, 고급 언어로 만든다.

도입[편집]

역컴파일링은 잃어버린 소스 코드를 되찾거나, 컴퓨터 보안 그리고 오류 검출 정정 등의 과정에서 유용하게 사용된다.[1] 역컴파일링의 성공은 현재 코드에 존재하는 정보와 얼마나 세련된 분석을 했는지에 달려 있다. 바이트코드 형식은 종종 확장된 메타데이터와 높은 수준의 특징들을 포함하기 때문에 역컴파일의 성공이 가능할 수 있다. 디버그 데이터의 존재는 원본 변수와 구조체 이름들 그리고 줄 번호까지도 재생산 하는 것을 가능케 할 수 있다. 이런 메타데이터나 디버그 데이터 없이 기계어를 역컴파일링하는 것은 훨씬 어렵다.[2]

몇몇 컴파일러들은 난독화 코드를 만드는데 이걸로 인해서 실행 파일을 리버스 엔지니어링하는 것은 매우 어려워 진다.

디자인[편집]

역컴파일러는 역컴파일 과정에서 각각의 특별한 과정을 수행하는 것들의 집합으로 생각될 수 있다.

로더[편집]

첫 번째 역컴파일 단계는 입력된 기계어나 중간 언어 프로그램의 바이너리 형식을 로드하고 분석하는 것이다. 이것은 펜티엄, 파워PC 같은 아키텍처와 엔트리 포인트 같은 입력된 프로그램의 기본 사실들을 발견하는 것이 가능해야 한다. 많은 경우에 C의 main 함수(유저가 쓴 코드의 시작점) 같은 것을 찾는 것이 가능해야 한다. 이것은 런타임 초기화 코드를 역컴파일하지 않고 배제해야 한다. 만약 심볼 테이블과 디버그 데이터가 사용 가능하다면 이것들 또한 로드되어야 한다. 프론트엔드는 코드와 링크된 라이브러리들을 식별할 수 있다. 이것은 라이브러리 인터페이스를 제공할 수 있다. 만약 사용된 컴파일러를 결정할 수 있다면 코드 관용구들을 식별하는데 유용한 정보가 될 것이다.[3]

디스어셈블리[편집]

다음 단계는 기계어 명령어들의 디스어셈블리를 기계와 독립적인 중간 표현 형식 (IR: intermediate representation)으로 변환하는 것이다.

 mov    eax, [ebx+0x04]

은 IR로 변환될 수 있다.

eax  := m[ebx+4];

관용구[편집]

관용적 기계어 코드 결과들은 결합된 의미들이 명령어들의 개별 의미로 봤을 때 명백하지 않은 코드의 결과물들이다. 디스어셈블리 과정 또는 다음의 분석 과정에서 이러한 관용적인 결과물들은 알려진 IR과 동등한 것으로 번역될 필요가 있다. 다음은 x86 기계어 코드이다.

    cdq    eax             ; edx is set to the sign-extension of eax
    xor    eax, edx
    sub    eax, edx

이것은 다음과 같이 변환될 수 있다.

eax  := abs(eax);

몇몇 관용적 결과들은 머신에 독립적이다. 즉 오직 하나의 명령어에 관련된다. 예를 들면, xor eax, eax는 eax 레지스터를 비우는 명령이다. 이것은 a xor a = 0 같은 머신에 독립적인 간단한 규칙으로 간소화될 수 있다.

일반적으로 다음 단계에서 명령어 순서에 영향을 덜 미치게 하기 위해서 가능한 한 관용적 결과들에 대한 탐지는 늦추는게 좋다. 예를 들면, 컴파일러의 명령어 스케줄 단계에서는 다른 명령어를 관용적 결과에 삽입하거나 순서를 바꾸는 경우가 있다. 디스어셈블리 단계의 패턴 매칭 과정에서 바뀐 패턴을 인식하지 못할 수도 있다. 다음 단계에서 명령어 집합 표현들은 더 더 복잡한 표현들로 바뀌고 고전적인 형태로 수정하며 바뀐 관용구는 고급 패턴에 맞게 된다.

컴파일러 관용구를 서브루틴 호출, 예외 처리 그리고 Switch 문으로 인식하는 것은 특히 중요하다. 또한 몇몇 언어들은 문자열이나 정수형에 대한 광범위한 지원을 가진다.

프로그램 분석[편집]

다양한 프로그램 분석이 IR에 적용될 수 있다. 특히, 표현 전달은 여러 명령어들의 의미들을 더 복잡한 표현으로 통합한다. 예를 들면, 

    mov   eax,[ebx+0x04]
    add   eax,[ebx+0x08]
    sub   [ebx+0x0C],eax

은 표현 전달 이후로 아래와 같은 IR로 바뀔 수 있다.

m[ebx+12]  := m[ebx+12] - (m[ebx+4] + m[ebx+8]);

결과를 표현한 것은 더 고급 언어와 닮았으며 기계어 레지스터 eax의 사용을 제거하였다. 분석 이후 ebx 레지스터도 제거될 것이다.

데이터 흐름 분석[편집]

레지스터 내용들이 정의되고 사용되는 곳은 데이터 흐름 분석을 통해 추적될 수 있어야 한다. 같은 분석은 임시 변수 그리고 로컬 데이터가 사용하는 위치에도 적용될 수 있다. 그 후 다른 이름은 값 정의 집합과 사용에 연결되어 구성된다. 같은 지역 변수의 위치는 원본 프로그램의 다른 부분의 변수들과 함께 사용될 수 있다. 더 심한 경우는, 실제로 절대 일어나지 않거나 현실에서는 중요하지 않지만, 데이터 흐름 분석 시에 값이 이러한 두 사용 사이에서 흐르는 경로를 인식하는 것이 가능할 수 있다. 좋지 않은 경우, 이것은 타입들의 결합으로서 위치에 대한 정의의 필요성으로 이어질 수 있다. 역컴파일러는 사용자에게 명백히 이러한 비자연스러운 의존성을 깨게 허용함으로써 더 깨끗한 코드로 만들 수 있다. 이것은 물론 변수가 잠재적으로 초기화 없이 사용됐다는 것이고, 원본 프로그램에서 문제가 나타나게 된다.

타입 분석[편집]

좋은 기계어 코드 역컴파일러는 타입 분석을 수행한다. 레지스터나 메모리 위치들이 사용되는 방식은 위치의 가능한 타입에 대한 제약으로 귀결된다. 예를 들면, and 명령어는 피연산자가 정수라는 것을 의미한다. 즉, 프로그램은 부동소수점 값이나 포인터에서 동작하는 명령을 사용하지 않는다. add 명령어는 3가지 제약으로 귀결되는데, 피연산자가 정수거나 아닐 수 있기 때문이다. 나머지 하나는 두 피연산자가 다를 경우에 생기는 제한이다.[4]

다양한 고급 표현들은 구조체나 배열들의 인식을 촉발하는 것으로 인식될 수 있다. 그러나 기계어나 C 같은 고급 언어들의 포인터 연산을 허용하는 자유 때문에 많은 가능성을 구별하는 것은 어려운 일이다.

앞의 섹션에서의 예는 다음과 같은 급 언어로 귀결될 수 있다. 

 struct T1 *ebx;
    struct T1 {
        int v0004;
        int v0008;
        int v000C;
    };
  ebx->v000C -= ebx->v0004 + ebx->v0008;

구조화[편집]

역컴파일링의 끝에서 두 번째 단계는 IR을 구조화하여  while 루프와 if/then/else 조건부 선언 같은 고급 구조체로 만드는 것이다. 예를 들면 기계어는 아래와 같다.

    xor eax, eax
 l0002:
    or  ebx, ebx
    jge l0003
    add eax,[ebx]
    mov ebx,[ebx+0x4]
    jmp l0002
 l0003:
    mov [0x10040000],eax

이것은 다음과 같이 변환될 수 있다.

    eax = 0;
    while (ebx < 0) {
        eax += ebx->v0000;
        ebx = ebx->v0004;
    }
    v10040000 = eax;

구조화되지 않은 코드를 구조화된 코드로 변환하는 것은 이미 구조화된 코드를 변환하는 것보다 훨씬 어렵다. 해결법으로는 몇몇 코드를 자기복제하거나 불리언 변수들을 추가하는 것이 있다.[5]

코드 생성[편집]

마지막 단계는 역컴파일러의 백엔드에서 고급 언어를 생성하는 것이다. 컴파일러가 여러 다른 아키텍처에 따른 여러 다른 기계어를 생성하는 백엔드를 가지듯이 역컴파일러도 여러 고급 언어 별로 생성할 수 있는 백엔드를 갖는다.

코드 생성 바로 직전에 GUI 형태를 사용하여 IR의 상호적인 수정을 허용하는 것이 바람직하다. 이것은 사용자에게 주석을 달거나 포괄적이지 않은 변수와 함수 이름들을 집어넣게 할 수 있다. 그러나 이것들은 역컴파일링 후에 하는 것처럼 쉽게 할 수 있다. 사용자는 구조적인 면을 바꾸고 싶어할 수도 있다(while 루프를 for 루프로 바꾼다든지). 이것들은 간단한 텍스트 에디터로는 쉽게 수정될 수 없다. 비록 소스 코드 리팩토링 도구들이 이 과정에서 도와준다고 하더라도 말이다. 마지막으로 결과 코드를 더욱 읽기 쉽게 하기 위하여, 부정확한 IR은 고쳐지거나 바뀌어야 한다.

합법성[편집]

대부분의 컴퓨터 프로그램들은 저작권 법들에 포함된다. 비록 지역마다 어느 정도가 저작권에 포함되는지는 다 다르지만, 일반적으로 개발자에게 프로그램에 대한 배타적인 권한을 준다.[6] 이러한 권리들은 복사본에 대한 권리를 포함한다. (심지어 RAM으로 만들어지는 복사도 포함한다.[7]) 역컴파일 과정이 여러 복사본을 만드는 것과 관련되어 있기 때문에 역컴파일은 허가 없이는 법적으로 금지되어 있다. 그러나 소프트웨어 상호운용성을 달성하는데 이것이 꼭 필요한 단계이므로 저작권 법(미국과 유럽 모두)은 한정된 범위 까지는 허가한다.

각주[편집]

  1. “Why Decompilation”. Program-transformation.org. 2005년 4월 11일. 2016년 2월 11일에 확인함. 
  2. Miecznikowski, Jerome; Hendren, Laurie (2002). 〈Decompiling Java Bytecode: Problems, Traps and Pitfalls〉. R Nigel Horspool. 《Compiler Construction: 11th International Conference, proceedings / CC 2002》. Springer-Verlag. 111–127쪽. ISBN 3-540-43369-4. 
  3. Cifuentes, Cristina; Gough, K. John (July 1995). “Decompilation of Binary Programs”. 《Software Practice and Experience》 25 (7): 811–829. doi:10.1002/spe.4380250706. CiteSeerX: 10.1.1.14.8073. 
  4. Mycroft, Alan (1999). 〈Type-Based Decompilation〉. S. Doaitse Swierstra. 《Programming languages and systems: 8th European Symposium on Programming Languages and Systems》. Springer-Verlag. 208–223쪽. ISBN 3-540-65699-5. 
  5. C. Cifuentes.
  6. Rowland, Diane (2005). 《Information technology law》 3판. Cavendish. ISBN 1-85941-756-6. 
  7. http://www.copyright.gov/title17/92chap1.html#106

외부 링크[편집]