Study/Reversing

PE 헤더

blupine 2019. 2. 18. 14:53

1. PE 헤더 (PE Header)?

  Portable Executable File Format의 약자로 이식 가능한 다른 곳에서도 실행이 가능한 포맷이다.


2. PE 파일 생성 과정

 

- 소스코드(cpp)를 컴파일 하면 어셈블리 코드가 만들어지고 어셈블러에 의해 어셈블리 코드들은 오브젝트 코드로 만들어짐

- 이렇게 만들어진 오브젝트 코드들은 정적으로 라이브러리 코드를 모두 포함하여 만들어지지 않는 이상 동적 링킹을 거치게 된다.

- 동적 라이브러리나 각종 리소스 데이터와 Import, Export 테이블을 처리할 수 있는 정보를 어딘가에 적어둠

- 윈도우는 exe 파일을 만들 때 약속된 규약에 따라 정보를 기입하고 이렇게 만들어진 파일이 PE 파일이다.

- EXE 또는 DLL을 실행하면 개발자가 만든 코드가 실행되기 전에 PE 정보부터 읽어와서 바이너리를 메모리에 올리기 위한 각종 데이터를 설정하는 작업을 함



3. PE 파일 구조

- 소스코드(cpp)를 컴파일 하면 어셈블리 코드가 만들어지고 어셈블러에 의해 어셈블리 코드들은 오브젝트 코드로 만들어짐

- PE 헤더는 여러 구조체로 이루어져 있으며 PE viewer를 이용하여 직접 구조체 살펴볼 수 있음 (eg : VX PE-Viewer)
- VX PE-Viewer는 현재 2.0 버전까지 배포됐으나 2.0 버전에서는 런타임 에러가 발생하여 실행이 불가능했음, 따라서 1.1 버전으로 분석을 진행함


  3-1. IMAGE_DOS_HEADER
   - 크기는 0x3D 바이트이며 가장 먼저 위치하여 PE 파일의 맨 처음 부분에 해당한다. Hex 에디터를 이용하여 PE 파일을 열어도 해당 구조체를 가장 먼저 찾아볼 수 있다.         - 가장 첫 번째 필드인 (e_magic)과 마지막 필드(e_lfanew)가 중요하다
 

    


     

   - e_magic 필드는 PE 파일의 시그니처로 최초 개발자의 이름을 딴 "MZ"로 시작한다. 위에 그림에서도 확인해볼 수 있다.

   - e_lfanew 필드는 다음 구조체인 IMAGE_NT_HEADER의 구조체 위치를 알아내는 데 사용되는 값

  

 3-2. IMAGE_NT_HEADER

   - IMAGE_DOS_HEADER의 마지막 필드인 e_lfanew 필드를 이용하여 다음과 같이 주소를 알아낼 수 있음

    

     

  

    - 다음과 같이 구성되어 있으며 Signatrue는 "PE\0\0"을 나타내는 4바이트 값(50 45 00 00)이다.

    - 요즘 윈도우에서는 해당 자리에 50 45 00 00이 아닌 다른 값이 자리하게되면 해당 파일은 윈도우에서 실행되지 않음,

      과거엔 이를 확인하지 않아 바이러스나 악성코드에 의한 감염 표식용으로 사용됨


    



    



  3-3. IMAGE_FILE_HEADER

   - IMAGE_DOS_HEADER의 마지막 필드인 e_lfanew 필드를 이용하여 다음과 같이 주소를 알아낼 수 있음

 

    

   - Machine

     : 어떤 CPU에서 이 파일이 실행될 수 있는지를 알려줌

   - NumberOfSections 

     : 섹션이 몇 개 있는지를 알려줌 (.text, .data 등)

       일반적으로 비주얼 스튜디오에서 MFC로 별다른 옵션 변경 없이 빌드한 경우 .text, .rdata, .data, .rsrc로 4개의 섹션이 존재함

       패킹이나 프로텍팅 등의 이유로 섹션 수가 증가하면 이 값도 같이 증가함

   - TimeDateStamp

     : 해당 파일이 빌드된 날짜 정보

   - SizeOfOptionalHeader

     : IMAGE_OPTIONAL_HEADER32의 구조체 크기, 해당 구조체는 PE를 로딩하기 위한 중요한 정보를 갖고 있는데, 이 구조체는 운영체제마다

       크기가 다를 수 있기 때문에 PE 로더에서는 SizeOfOptionalHeader값을 먼저 확인한 뒤 IMAGE_OPTIONAL_HEADER32 구조체의 크기를 처리함 

   - Characteristics

     : 현재 파일이 어떤 형식인지 알려주며 DLL인지 EXE인지 구분하는 용도로 활용이 가능하다.

    



  3-4. IMAGE_OPTIONAL_HEADER
    - PE 구조체 중 중요한 값을 가장 많이 담긴 헤더

    


    - MAGIC

     : 시그니처, 32비트의 경우 0x10B가 들어오며 64비트의 경우 0x20B가 된다.

    - SizeOfCode

     : 코드 양의 전체 크기를 가리킴, 실제 개발자가 만든 코드의 양이 해당 필드에 들어가며, 바이러스나 악성코드는 이 필드를 읽어서 

       악성 코드를 복제할 위치를 계산하기도 하며, 보안 솔루션에서 코드 섹션의 무결성 검사를 위한 크기도 해당 필드를 통해 정함 

    - MajorLinkerVersion, MinorLinkerVersion

     : 어떤 버전의 컴파일러로 빌드했는지를 알려줌 (비주얼 스튜디오 6.0일 경우 6.0이 자리함)

    - ImageBase

     : 해당 파일이 실행될 경우 실제 가상 메모리에 올라가는 번지를 가리킴, EXE 파일의 경우 번지 지정을 별도로 해주지 않는 이상 0x400000에 올라감.

       DLL의 경우 기본 이미지 베이스 주소가 0x10000000 번지로 정해져 있지만 재배치 속성에 따라 재할당되기도 함

    - AddressOfEntryPoint

     : 실제 파일이 메모리에서 실행되는 시작 지점을 말함. 디버거를 통해 파일을 실행했을 때 디버거는 첫 실행 지점을 이곳과 ImageBase를 합산한 위치에 지정해서 멈춤

    - BaseOfCode

     : 실제 코드가 실행되는 번지, 코드 영역의 시작 주소는 ImageBase에 BaseOfCode를 더한 위치이다. 기본적으로 0x1000의 값을 가짐

    - SectionAlignment, FileAlignment

     : 각 섹션을 정렬하기 위한 저장 단위, 보통 0x1000이 지정되며 섹션의 크기가 0x1000이하일 경우에도 패딩을 통해 0x1000을 채움

    - SizeOfImage

     : EXE나 DLL이 메모리에 로딩됐을 때의 전체 크기, 로더가 해당 필드를 참조하여 메모리 공간을 할당함

    - SizeOfHeaders

     : PE 헤더의 크기를 알려주는 필드

    - Subsystem

     : 해당 프로그램이 GUI(0x02)인지 CLI(0x03)인지를 알려줌

    - DataDirectory

     : IMAGE_DATA_DIRECTORY의 구조체로서, VirtualAddress와 Size라는 필드가 포함돼있음

       Export 디렉토리나 Import 디렉토리, 리소스 디렉토리, IAT 등 각각의 가상 주소와 크기를 이 필드를 통해 알 수 있음