[조립하면서 배우는 PE] 세번째 이야기 : PE Header(1) Reverse Engineering

리버싱 2017. 1. 10. 16:29
사용자 삽입 이미지

번째 이야기입니다. 이번에는 PE 헤더에 대해서 알아보도록 하겠습니다. PE 헤더는 좌측의 [그림 1]과 같이 크게 3부분으로 이루어져 있습니다. 첫번째 부분은 PE signature 로 이 값은 "50 45 00 00" 으로 고정되어 있습니다. 두번째 부분은 20bytes 고정 사이즈를 가지는 파일 헤더(IMAGE_FILE_HEADER) 입니다. 세번째 부분은  옵셔널 헤더(IMAGE_OPTIONAL_HEADER)로 보통은 224bytes 사이즈를 가지지만 원칙적으로 사이즈는 가변적입니다.

        [그림 1] PE 헤더의 구조





보다 자세하게 알아보도록 하겠습니다. 다음은 PE 헤더(IMAGE_NT_HEADERS)의 정의입니다. 

  typedef struct _IMAGE_NT_HEADERS {
      DWORD Signature;
      IMAGE_FILE_HEADER FileHeader;
      IMAGE_OPTIONAL_HEADER32 OptionalHeader;
  } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;


PE Signature(Signature)
이미 언급한 대로 4byte 사이즈의 데이터로 이 값은 항상 "50 45 00 00(PE\0\0)"으로 고정되어 있습니다. 

FileHeader(IMAGE_FILE_HEADER)
20bytes 사이즈의 구조체입니다. 파일 헤더는 주로 PE 파일의 물리적 모양에 대한 정보를 가지고 있습니다. 파일 헤더는 모두 7개의 필드로 구성되어 있는데 이 중에서 알아두어야 할 필드는 모두 4개 입니다. 구조체 선언을 먼저 살펴보도록 하겠습니다.

  typedef struct _IMAGE_FILE_HEADER {
      WORD    Machine;
      WORD    NumberOfSections;
      DWORD   TimeDateStamp;
      DWORD   PointerToSymbolTable;
      DWORD   NumberOfSymbols;
      WORD    SizeOfOptionalHeader;
      WORD    Characteristics;
  } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
 

위 필드 중 알아두어야 할 것은 [그림 1]에 명시되어 있는 Machine, NumberOfSections, SizeOfOptionalHeader, Characteristics 뿐입니다. 나머지 필드는 무시하여도 좋으며 직접 생성 시 모두 0으로 채울 것입니다. 

  • Machine은 CPU ID를 나타내는 데 IA32의 경우 이 값은 0x14c가 되어야 하며 IA64인 경우 0x200이 되어야 합니다.
  • NumberOfSections은 말 그대로 섹션의 개수를 의미합니다. 리버싱 중 섹션을 생성하거나 제거할 때는 반드시 이 값을 변경해 주어야 합니다.
  • SizeOfOptionalHeader는 파일 헤더 뒤에 오는 옵셔널 헤더의 사이즈를 의미합니다. 이 필드의 존재 자체만으로도 옵셔널 헤더의 사이즈가 고정되어 있지 않다는 사실을 알 수 있습니다. 옵셔널 헤더의 사이즈에 영향을 끼치는 것은 옵셔널 헤더에 포함되어 있는 데이터 디렉토리입니다. 일반적인 경우 데이터 디렉토리가 포함된 옵셔널 헤더는 224bytes를 차지하지만 데이터 디렉토리는 필요없는 경우 생략이 가능하기 때문에 데이터 디렉토리를 포함하지 않는 옵셔널 헤더는  96bytes(데이터 디렉토리의 사이즈는 128bytes 입니다.) 사이즈를 가지게 됩니다.
  • Characteristics는 말 그대로 PE 파일의 속성을 의미합니다. 이 필드는 PE 파일이 EXE 파일인지 DLL 파일인지, 재배치가 가능한가와 같은 정보를 담게 됩니다. 일반적인 실행 파일의 경우 0x10F 값을 가집니다. 이는 PE 파일이 EXE 파일이며 재배치 정보를 가지고 있지 않음을 의미합니다. 여기서 잠깐 재배치란 옵셔널 헤더에 지정되어 있는 ImageBase에 PE 파일을 로드할 수 없는 경우 로드 가능한 주소에 PE 파일을 로드하고 실행 코드 내에서 절대 주소 값 등을 변경하는 작업입니다. EXE 파일의 경우 가상 메모리 공간에 가장 먼저 로드되므로 항상 ImageBase에 로드가 가능하여 재배치가 절대로 발생하지 않습니다. 반면 DLL은 상황에 따라 ImageBase에 로드될 수 없는 경우가 발생하기 때문에 (ImageBase에 이미 다른 DLL 등이 로드된 경우) 재배치가 발생할 수 있습니다. MS의 재배치로 인한 성능 저하를 막기 위해 윈도우의 주요 DLL의 ImageBase 값을 세밀하게 조정해 놓았다는 것도 기억해 두면 좋을 것 같습니다.

PE 조립하기 2: PE Signature + FileHeader 생성 
옵셔널 헤더는 다음 글에서 알아보도록 하겠습니다. 이번 글에서는 파일 헤더까지만 생성해 보도록 하죠. 먼저 두번째 이야기에서 생성한 MyFirstPE.bin을 WinHex로 열어야 하겠죠.

Step 1. PE 시그너춰와 FileHeader를 위한 공간을 추가합니다. 각각 4bytes와 20bytes이므로 총 24bytes 공간을 추가하면 되겠습니다. WinHex의 경우 Ctrl+0을 누르면 추가 공간을 할당할 수 있습니다. 그림은 생략합니다. 

Step 2. [그림 2]에서 처럼 PE 시그너춰를 입력합니다. 

사용자 삽입 이미지


   







[그림 2] PE 시그너춰 입력 (클릭 후 확대해서 보세요)

Step 3. FileHeader를 완성해야 합니다. 제일 먼저 Machine 값을 입력합니다. Machine 값은 IA32일 경우 0x14c로 입력하면 되겠습니다. 입력할 때는 리틀 엔디언임을 고려해서 입력해야 하며 Machine은 2bytes 사이즈를 가지므로 차례대로 4c 01을 입력하면 되겠습니다. 

사용자 삽입 이미지


    







[그림 3] Machine 필드 채움(클릭 후 확대해서 보세요)

Step 4. NumberOfSections 필드를 채울 차례군요. 이 필드는 총 2bytes입니다. 우리가 최종적으로 생성할 실행 파일은 코드를 저장하기 위한 .text 섹션과 상수 데이터를 저장하기 위한 .rdata 섹션 그리고 임포트 한 API에 대한 정보를 담고 있는 .idata 섹션 이렇게 3개의 섹션을 가지게 될 것입니다. 그러므로 이 필드는 리틀 엔디언 방식임을 고려해서 차례대로 03 00 으로 채우면 되겠습니다.

사용자 삽입 이미지


  







[그림 4] NumberOfSections 필드 채움(클릭 후 확대해서 보세요)

Step 5. TimeDateStamp와 PointerToSymbolTable, NumberOfSymbols 필드를 채웁니다. 이 필드는 각각 4bytes 사이즈를 가집니다. 일단 심볼과 관련된 두 개의 필드(PointerToSymbolTable, NumberOfSymbols)는 거의 사용되지 않는 필드이며 0으로 채워도 무방합니다. TimeDateStamp는 PE 파일이 생성된 시간을 1970년 1월 1일 00시를 기준으로하여 초단위로 기록해야 하는데 Windows XP에서 실험한 결과로는 역시 사용되지 않는 필드인 것 같습니다. 즉 0으로 채워도 실행하는 데는 아무런 지장이 없습니다. 이 필드 역시 0으로 채우겠습니다. 

사용자 삽입 이미지

 [그림 5] TimeDateStamp, PointerToSymbolTable, NumberOfSymbols 필드 채움

Step 6. SizeOfOptionalHeader를 채울 차례입니다. Optional header는 데이터 디렉토리가 있는 경우 224 bytes, 데이터 디렉토리가 필요없는 경우 96bytes라고 이미 말한 바 있습니다. 우리는 임포트 섹션을 사용해야 하는데 이 경우 IAT 테이블에 대한 정보가 데이터 디렉토리에 기록되어야 하므로 (이에 대해서는 다음 글에서 알아볼 것입니다) 데이터 디렉토리가 필요합니다. 따라서 우리가 만드는 PE 파일의 Optional header 사이즈는 224bytes(0xE0)가 됩니다. 이 필드의 사이즈는 2bytes 이므로 차례대로 "E0 00"으로 채우면 되겠습니다. 

사용자 삽입 이미지


  







[그림 6] SizeOfOptionalHeader 필드 채움


Step 7.  이제 마지막입니다. ^^; Characteristics 필드를 채울 차례군요. 2bytes 사이즈를 가지구요, 대체로 일반적인 애플리케이션은 0x10F 값을 가진다는 사실을 앞에서 알아보았습니다. 0x10F는 
Relocation Stripped, Executable Image, Line number stipped, 32bit machine expected 속성을 체크한 결과입니다. ^^; 자세한 내용은 Google 신에게 기도를 드려보세요~ (책임회피성발언...^^)
그렇다면 차례대로 "0F 01"을 채워나가면 되겠군요.

사용자 삽입 이미지


  







[그림 7] Characteristics 필드 채움


맺음말 
그럭저럭 해볼만하지 않은가요? ^^; 다음 이야기는 PE 헤더 중 OptionalHeader에 관한 것입니다. 그럼 즐핵~하세요. 



출처 - http://zesrever.tistory.com/57

: