PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은...

40
PE 날날날날 ( Flying PE ) http://kernelx.pe.kr 2006 날 12 날 [목목] 1. 날날 ..................................................................... ...............2 2. PE 날날날날 날날날날.............................................................2

Transcript of PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은...

Page 1: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

PE 날개달기 ( Flying PE )

http://kernelx.pe.kr

2006 년 12 월

[목차]

1. 여는 글....................................................................................2

2. PE 파일포맷 기본지식.............................................................2

        2.1 PE 요약....................................................................2

        2.2 PE 구조....................................................................3

3. VC++ PE 구조체 살펴보기....................................................8

        3.1 IMPORT 해부..........................................................8

        3.2 EXPORT 해부..........................................................11

Page 2: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

4. 파서코드 작성 (PEKits.cpp)...................................................14

        4.1 print_pe_import() - IMPORT 정보 출력...............14

        4.2 fix_entrypoint() - EntryPoint 패칭.......................17

        4.3 fix_perm_sections() - Section 퍼미션 패칭..........19

5. 맺음말

        5.1 참고자료 ( References ).........................................21

        5.2 마치며 ( Conclusion ).............................................22

        

1. 여는 글

  역어셈블리를 공부하고 활용하는데 PE 파일포맷을 분명하게 이해하지 못하거나 또는

활용하지 못한다는 것은 한걸음 앞으로 나아가는데 있어서 커다란 걸림돌이 됨을 의미합니다.

현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와

있으며, 관심을 가진 여러 리버스엔지니어, 개발자, 해커들에 의해서 그 파일포맷의 구성이

설명되어지고, 또 정리되어져 있습니다. 그러나 아직까지 그 구조를 쉽고 명쾌하게 설명함과

동시에 PE 구조체를 이용해 코딩하는 방법에 대해서도 함께 다룬 한글자료는 보질 못했습니다.

본 문서는 그러한 점을 염두하고 한글문서로 PE 를 활용할 수 있는 방법을 쉽게 표현하고자

작성되었습니다. 문서내에서는 간결한 설명을 위해 경어체를 사용하지 않습니다. 양해

바랍니다.

학습을 위해 필요한 도구 :

Page 3: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

        - MS Windows XP SP2

        - PEView.exe ( 공개 PE 뷰어 )

        - Visual C++ 6.0 컴파일러

        - putty.exe ( SSH 클라이언트 )

2. PE 파일포맷 기본지식

2.1 PE(Portable Executable) 요약

[1] 윈도우즈 NT, XP, 2000 등에서 사용하는 실행 가능한 코드를 포함할 수

    있는 파일의 포맷이다.

[2] PE 포맷을 사용하는 파일은 .EXE .DLL .OCX 가 있다.

[3] 윈도우즈 98 에서는 NE 라는 PE 와는 다른 파일포맷이 사용되었다.

[4] 64 비트 윈도우즈에서는 PE+ 라는 파일포맷을 사용한다.

    ( 본 문서는 32 비트 윈도우즈의 PE 포맷을 다룬다 )

[5] PE 파일은 헤더정보들과 실제 데이터(코드, 데이터, IMPORT 테이블들,

    EXPORT 테이블들, 리소스 데이터 등이 포함된다)로 구성된다.

[6] RVA(Relative Virtual Address) 라고 해서 베이스주소에서 오프셋

    을 더한 값으로 주소를 표현하기도 한다. 예를 들어 한 PE 파일의

    ImageBase 가 0x00400000 일 때, .text 섹션(코드)의 RVA 값이

    0x1000 이라면 0x00401000 이 실제 주소가 되는 것이다. 이처럼

    오프셋과 같은 개념으로 쓰이는 것이 RVA 주소 값이다.

Page 4: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

<PEView.exe 로 본 putty.exe 구조>

2.2 PE 구조

PE 는 위 그림에서 보는 것과 같은 기본적인 골격을 가진다. 이번 장에서는 PE 코딩을 위해서

숙지하고 있어야 할 사항들을 설명한다.

--

+IMAGE_DOS_HEADER

+MS-DOS Stub Program

Page 5: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

        ( 도스모드에서 윈도우프로그램 수행시, 오류메시지 표시하는 코드 )

        ( “This program cannot to be run in DOS mode" )

+IMAGE_NT_HEADERS

  - Signature

  - IMAGE_FILE_HEADER

  - IMAGE_OPTIONAL_HEADER

+IMAGE_SECTION_HEADER[4]

+.text 섹션 ( 코드포함 )

+.rdata 섹션 ( IMPORT, EXPORT 테이블들 포함 )

+.data 섹션 ( 전역, 정적 변수 포함 )

+.rsrc 섹션 ( 리소스 데이터 포함 - 아이콘, 문자열, 다이얼로그정보 등 )

--

(1) NTHeader 위치를 구하는 방법

 NTHeader 가 시작하는 지점을 파일의 시작지점에서 알아보려면, 먼저 DOS 시절과 호환성을

유지하기위해서 남겨진 PE 의 맨 첫 부분인 DOS 헤더의 한 필드를 살펴보면 된다.

DWORD file_base = (메모리상에 PE 파일을 맵핑한 후 그 베이스주소를 담는다);

PIMAGE_NT_HEADERS pinh;

PIMAGE_DOS_HEADER pidh;

// e_lfanew 필드는 NT 헤더의 시작점을 지정하고 있다.

pinh = file_base + pidh->e_lfanew;       // 파일베이스 + pidh->e_lfnew = NT

헤더

Page 6: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

(2) NT 헤더의 3 가지 구성

 NT 헤더(IMAGE_NT_HEADERS 구조체)는 3 가지 필드로 구성되어 있다.

typedef struct _IMAGE_NT_HEADERS {

    DWORD Signature;

    IMAGE_FILE_HEADER FileHeader;

    IMAGE_OPTIONAL_HEADER32 OptionalHeader;

} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

..

typedef IMAGE_NT_HEADERS32         IMAGE_NT_HEADERS;

typedef PIMAGE_NT_HEADERS32        PIMAGE_NT_HEADERS;

- Signature : NT 의 시그너처 4 바이트를 담는 필드이다. ( "PE\0\0" )

- FileHeader : 파일헤더 구조체 ( ImageBase, 코드영역주소, 데이터영역주소 등 포함 )

- OptionalHeader : 옵셔날 헤더

( OptionalHeader 는 맨 아래에 IMAGE_DATA_DIRECTORY 구조체 배열을 내포하고 있다. 이

구조체 필드배열은 각각 각 섹션들의 RVA 와 크기를 담고 있다. )

(3) PE 파일의 섹션 개수 알아보기

 PE 파일은 여러 가지 섹션들을 포함한다. 코드를 포함하는 .text 섹션, 초기화되지 않은

데이터와 전역변수 정보를 담는 .bss / .data, 리소스데이터를 담는 .rsrc 섹션, 읽기전용

데이터들을 담고 있는 .rdata 섹션 등이 있다. 이런 섹션의 개수가 파일마다 다를 수 있기

때문에 개수를 담고 있는 필드가 있다.

WORD NumSections;

Page 7: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

DWORD file_base = (파일베이스 주소);

PIMAGE_DOS_HEADER pidh;

PIMAGE_NT_HEADER pinh;

pinh = file_base + pidh->e_lfanew;                       // NT 헤더 구하기

NumSections = pinh->FileHeader.NumberOfSections;       // 섹션 개수

(4) 섹션 헤더 ( IMAGE_SECTION_HEADER )

 섹션헤더들은 NT 헤더를 뒤 따른다. 이 섹션헤더들은 각 섹션에 대한 정보를 담는 필드들로

이루어져 있는데, 이 자체가 섹션은 아니다. 이를테면, .text(코드)는 실제코드가 포함된 영역이

섹션데이터이며, 이 섹션데이터가 존재하는 위치(RVA)와 그 밖의 정보들을 포함하는 것이 .text

섹션헤더이다. 섹션헤더의 주요 필드를 살펴보자.

typedef struct _IMAGE_SECTION_HEADER {

    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];

    union {

            DWORD   PhysicalAddress;

            DWORD   VirtualSize;

    } Misc;

    DWORD   VirtualAddress;

    DWORD   SizeOfRawData;

    DWORD   PointerToRawData;

    DWORD   PointerToRelocations;

    DWORD   PointerToLinenumbers;

    WORD    NumberOfRelocations;

Page 8: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

    WORD    NumberOfLinenumbers;

    DWORD   Characteristics;

} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

- Name[8] : ".text", ".rdata" 와 같은 섹션 명을 담는다. 8 바이트가 최대 길이이고, 보통

dot(.) 으로 시작하지만, 꼭 그렇게 정해야 하는 것은 아니다. 섹션명은 바꿀 수도 있다. VC++

에서 이 섹션 명을 바꾸려면 아래와 같은 키워드를 사용할 수 있다.

<---------- ReDefine_SegmentName -------->

#pragma data_seg(".mydata");   // .data 를 .mydata 로 선언한다.

#pragma code_seg(".mycode"); // .text 를 .mycode 로 선언한다.

<---------- EOF ------------------------>

- VirtualAddress : 섹션데이터 RVA

  (ImageBase + 섹션데이터 RVA = 섹션주소)

- Characteristics : 섹션의 퍼미션 ( 여러 가지 퍼미션을 OR 로 적용함 )

        0x00000020 : 코드를 포함

        0x00000040 : 초기화된 데이터를 포함 ( .rdata, .data )

        0x00000080 : 초기화되지 않은 데이터를 포함 ( .bss )

        0x00000200 : Comment ( 코멘트 )

        0x10000000 : Shareable ( 공유가능; 보통 DLL 에서만 사용 )

        0x20000000 : 실행가능

        0x40000000 : 읽기가능

        0x80000000 : 쓰기가능

Page 9: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

        * 여기서 잠깐, putty.exe(putty ssh 클라이언트의 각 섹션의 퍼미션을 알아보자)

        --     putty.exe       --

                .text = 0x60000020 ( 코드포함 | 실행가능 | 읽기가능 )

                .rdata = 0x40000040 ( 초기화된 데이터포함 | 읽기가능 )

                .data = 0xC0000040 ( 초기화된 데이터포함 | 읽기가능 | 쓰기가능 )

                .rsrc = 0x40000040 ( 초기화된 데이터포함 | 읽기가능 )

        ---------------------

이 퍼미션들 역시 VC++에서 키워드로 재지정 할 수 있다.

#pragma comment(linker, "/section:.text, RWE");

.text 섹션은 보통 읽기(R)와 실행퍼미션(E)의 조합이 기본이다. 그러나 코드섹션의 데이터를

메모리상에서 수정하려면 쓰기(W)이 필요하다. 자신이 만드는 프로그램이 코드섹션의 수정을

허용하도록 하려면, 컴파일 시에 위 키워드를 소스코드 상단에 선언해주면 된다.

(5) 다른 모듈의 함수호출하는 과정 ( IMPORT )

Page 10: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

- 위 그림에서 보이는 것처럼 IMPORT 된 API 함수들은 두 단계의 과정을 거쳐 실제 DLL 의 API

함수가 호출되게 된다.

        (1) CALL 0x000144408 ; IAT 엔트리로 가는 점프루틴 주소 호출

        (2) JMP 0x00040042 ; 호출된 점프루틴은 실제 메모리에 맵핑된 DLL

           API 함수주소를 담고 있는 주소를 포함하는 곳으로 점프한다.

        (3) 메모리상에 구성된 IAT(Import Address Table) 테이블의 엔트리에 있는

            실제 API 함수주소를 통해 DLL 의 API 함수를 호출한다.

참고. IMPORT

Page 11: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

* IMPORT 정보를 담는 주요 테이블은 3 가지 종류이다. 나중에 자세히 다루겠지만, 알아두고

넘어가자.

- IMPORT Directory Table

- Import Address Table

- Import Hints/Names & DLL Names

 

주의: PE 파일을 수정하여 IAT 테이블의 API 함수후킹을 시도하는 것은 때때로 무의미하다.

왜냐하면 로더가 프로그램을 메모리에 맵핑시킨 다음, 이 IAT 테이블을 메모리상에서

재구성하기 때문이다. 후킹을 제대로 하기위해서는 프로세스를 실행시킨 다음, EIP

레지스터가 엔트리 포인트로 진입한 후(즉 IAT 가 재구성된 다음)에 스레드를 멈추고,

메모리상의 IAT 를 수정하는 것이 바람직하다.

(6) 작성한 함수를 외부에서 호출할 수 있게 만들기 ( EXPORT )

 IMPORT 가 외부 DLL 의 함수들을 끌어와서 사용하고자 하는 목적이라면, 작성한 함수를 외부

프로그램에서 끌어가서 사용할 수 있도록 만들고자 하는 목적으로 사용되는 것이 EXPORT

이다. IMPORT 테이블과 정보들은 .idata 또는 .rdata 섹션에 포함되어 있었다. EXPORT 에

대한 테이블정보 역시 같은 섹션에 포함되어 있다. 자세한 설명은 본 문서의 뒤편에서 구조체를

다루면서 설명하고 있다.

참고. EXPORT

Page 12: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

* EXPORT 정보를 담는 주요 테이블은 4 가지가 있다.

- EXPORT Address Table

- EXPORT Name Pointer Table

- EXPORT Ordinal Table

- EXPORT Names

IMPORT 와는 다르게 EXPORT 된 함수들은 오디날(Ordinal) 번호를 가질 수 있다. 따라서

함수 명으로 EXPORT 된 함수들은 나중에 IMPORT 될 때, 함수 명을 통해서 실제 주소를 구할

수 있고, 오디날번호로 EXPORT 된 함수들은 그 번호를 통해서 실제함수 주소를 구할 수 있다.

(7) 스레드 지역 저장고 ( .TLS 섹션 ; Thread Local Storage )

 한 프로세스는 여러 개의 스레드를 만들어서 동작할 수 있다. 따라서 각 스레드별 개별적인

전역변수 저장창고가 필요하다. 이런 이유로 .TLS 섹션은 이것을 가능하게 한다. 프로그램 작성

시에 __declspec(thread) int tva; 와 같이 TLS 키워드를 붙여서 선언하게 되면, .TLS 섹션이

링크될 때 만들어지게 되고, 공간이 할당되어 실행될 때 사용되어 진다. 이 TLS 영역의 변수들을

할당하고 다루는 함수들이 개별적으로 존재한다. (TlsAllocXxX())

참고. TLS 섹션

한계: TLS 는 명시적으로 불러들여진 DLL 에서는 사용할 수 있지만, LoadLibrary() API 를

통해서 동적으로 불러들여진 DLL 에서는 사용할 수 없다.

 

* tls 섹션이 할당된 것을 확인하려면, “winhex.exe" 를 PEView 로 살펴보길 바란다.

Page 13: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

(8) 디버그 정보 ( .debug 섹션 )

 디버그섹션은 VC++ 컴파일 시에 Release 가 아닌, Debug 옵션으로 컴파일 할 때

생성되어진다. 이 디버그 섹션은 NULL 로 종결되는 파일경로(FilePath) 를 하나 담고 있다.

이것은 디버깅 시에 사용될 수 있는 심볼정보들(CodeView)을 담고 있는 파일인 .pdb

파일명이다.

3. VC++ PE 구조체 살펴보기

  이제 본격적으로 본 문서의 주제인 PE 파서코드 작성에 대해서 알아보자. PE 구조를 이해하고

있지 않으면, PE 구조를 변경하거나 또는 구성할 수 없다. 그러므로 충분히 PE 에 대해서 공부한

다음, 이 “4. 파서코드 작성 ( PEKits )” 장을 통해 실습할 수 있다.

PE 를 다루기 위한 구조체들은 winnt.h 에 모두 선언되어 있다. 따라서 이 헤더파일을 파서의

상단에 선언해주어야 사용할 수 있다.

#include "winnt.h"

PE 를 구성하는 구조체들은 두 가지 타입으로 정의되어 있다. 첫째는 구조체이고, 또 하나는

구조체포인터이다. 구조체포인터는 자료형의 맨 앞에 대문자 ‘P’ 가 붙는다.

아래는 구조체로 살펴본 PE 구성이다. ‘-’ 는 포함되어 있다는 의미이다.

Page 14: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

|+IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

|+MS-DOS STUB_PROGRAM (명시적 선언; 자료형 없음)

|+IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

|       -IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

|       -IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

|       -IMAGE_DATA_DIRECTORY DataDirectory[16];

|+IMAGE_SECTION_HEADER[4], *PIMAGE_SECTION_HEADER;

|

|+.text 섹션 데이터

|+.rdata 섹션 데이터

|       -IMAGE_THUNK_DATA;         // IAT 로 사용됨.

|       -IMAGE_IMPORT_DESCRIPTOR; // IMPORT DIRECTORY Table

|       -IMAGE_THUNK_DATA;         // IMPORT Name Table

|       -IMAGE_IMPORT_HINTS;         // 직접 선언함 ( 아래 )

|       -IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

|       -IMAGE_THUNK_DATA;         // EXPORT Address Table

|       -IMAGE_THUNK_DATA;         // EXPORT Name Pointer Table

|       -Ordinals[];                      // EXPORT Ordinal Table 직접 선언함 ( 아래 )

|       -EXPORT_NAMES              // 직접 선언함 ( 아래 )

|+.data 섹션 데이터

|+.rsrc 섹션 데이터

|       -IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;

|       -IMAGE_RESOURCE_DIRECTORY_ENTRY,

|        *PIMAGE_RESOURCE_DIRECTORY_ENTRY;

|        .. (리소스를 구성하는 것은 뒷장에서 다룬다)

<VC++ 구조체로 본 PE 구조>

Page 15: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

위 구조는 대표적인 자료 형들을 순서에 맞게 표현한 것이다. 여기에 빠져 있지만 PE 파서를

다룰 때 사용될 수도 있는 구조체들도 winnt.h 에 선언되어 있으므로 참고하길 바란다. 위

구조체들과 PE 구조편의 그림을 비교해보면 특별히 복잡한 것은 없지만, .rdata 섹션에 포함된

IMPORT 구조체가 분명하게 이해가 되지 않을 것이다. 이 부분에 대해서 지금 분명하게

설명하겠다.

3.1 IMPORT 해부

먼저 IMAGE_NT_HEADER->OptionalHeader.DataDirectory[] 이 배열로부터 IMPORT 는

시작된다. 이 배열의 두 세 개의 항목들은 IAT(Import Address Table)과 INT(Import Name

Table), 그리고 IMPORT Table 의 주소와 크기를 표현하고 있다.

IAT 와 INT 는 위의 구조에서 보아서 알겠지만, IMAGE_THUNK_DATA 구조체로 표현되고,

IMPORT Table 은 IMAGE_IMPORT_DESCRIPTOR 구조체로 표현된다.

IMAGE_IMPORT_DESCRIPTOR 구조체는 PE 구조 편에서 PEView 로 살펴본 그림에서의

"IMPORT Directory Table"를 표현하는 엔트리의 구조체이다.

그렇다면, IMPORT 된 “USER32.dll" 파일의 IAT 함수주소를 구하는 방법을 알아보자.

아래 코드는 이해를 돕기 위해서 작성된 것으로, 실제로 컴파일하기 위해서는 수정이 많이

필요한 코드이다.

Page 16: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

/*

        Import Table = IMPORT Directory Table

        = IMAGE_IMPORT_DESCRIPTOR(엔트리모음; 마지막은 NULL 엔트리)

*/

/* 1. Import Table(IMPORT Directory Table 과 같은 것) 의 RVA 와 크기를 구한다 */

PIMAGE_IMPORT_DESCRIPTOR pIID =  (IMAGE_NT_HEADER)-

>OptionalHeader.DataDirectory[1].VirtualAddress;

DWORD pIID_size = (IMAGE_NT_HEADER)-

>OptionalHeader.DataDirectory[1].Size;

PIMAGE_THUNK_DATA IAT;

DWORD DllName;

/*

        IMAGE_IMPORT_DESCRIPTOR 의 필드인 FirstThunk 와 OriginalFirstThunk

        IAT 테이블의 시작 RVA 를 가지고 있다. 그리고 Name 필드는 해당 IMPORT 된

        DLL 파일명(“ADVAPI32.dll", "USER32.dll", "KERNEL32.dll" ..) 이 저장된

        위치의 RVA 를 가지고 있다. 이 구조체는 말 그대로 디렉터리다. 하나의 DLL

        파일별로 하나의 IMAGE_IMPORT_DESCRIPTOR 구조체 엔트리를 가지는 것이다.

*/

pIID = pIID + ImageBase;       // 메모리상의 실 주소 구함

Page 17: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

while(pIID->Name){      // PIMAGE_IMPORT_DESCRIPTOR 가 없을 때까지 반복

        

        if(strcmp(pIID->Name, "USER32.dll") == 0){ // 원하는 DLL 엔트리 발견!

                // IAT 테이블 안의 USER32.dll 가 선언된 시작 RVA 은 구함.

                HAT = Imageable + pipit->FirstThunk;

                break;

        }

        pIID++; // 다음 엔트리로 이동

}

아래는 winnt.h 에 선언되어 있는 IMAGE_IMPORT_DESCRIPTOR 구조체이다.

주석이 달려 있는 부분을 주의 깊게 기억해두길 바란다. 주석이 달리지 않은 부분은 0 으로

초기화하면 된다.

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

    union {

        DWORD   Characteristics;

        DWORD   OriginalFirstThunk;            // 아래 FirstThunk 와 같이 IAT RVA

    };

    DWORD   TimeDateStamp;

    DWORD   ForwarderChain;

    DWORD   Name;                           // DLL 파일명을 가리키는 포인터

    DWORD   FirstThunk;                       // IAT 의 RVA ( 각 DLL 별 )

Page 18: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

} IMAGE_IMPORT_DESCRIPTOR;

typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED

*PIMAGE_IMPORT_DESCRIPTOR;

마지막으로 IMPORT 에 대해서 한 가지 사실을 더 알아보고 EXPORT 구조체를 설명하도록

하겠다.

PEView.exe 로 살펴본 PE 구조 그림에서는 아래 4 가지의 주요 IMPORT 항목이 있었다.

- Import Address Table ( IAT )

- IMPORT Directory Table

- IMPORT Name Table ( INT )

- Import Hints/Names & DLL Names ( Import Hints )

IMPORT Directory Table 은 바로 위에서 설명한 IMAGE_IMPORT_DESCRIPTOR 이다.

그리고 이것은 IAT 에서의 해당 DLL 에서 IMPORT 된 API 함수 명에 대한 포인터를 가지고

있다고 설명했다. 그리고 IAT 와 INT 는 완전하게 동일한 구조를 가지고 있다. 이 IAT, INT 에서

가리켜진 각 API 함수 엔트리별 주소 값은 Import Hints 영역에 있는 아래와 같은 문자열을

가리키고 있다.

“[두바이트 오디날번호][API 함수명][널 바이트]”

“[0x21F0][MessageBoxA][\0][0x23C0][CreateWindow][\0]" .....

Page 19: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

3.2 EXPORT 해부

putty.exe 는 DLL 이 아니기 때문에, 외부로 EXPORT 한 함수가 존재하지 않아. EXPORT 에

관련된 정보가 나오지 않았다. PEView 로 DLL(동적 라이브러리)파일을 열어보면 아래와 같은

4 가지 EXPORT 관련 테이블이 나오는 것을 확인할 수 있다.

- IMAGE_EXPORT_DIRECTORY

- EXPORT Address Table

- EXPORT Name Pointer Table

- EXPORT Ordinal Table

- EXPORT Names

<PEView.exe 로 열어본 DLL 파일>

Page 20: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

typedef struct _IMAGE_EXPORT_DIRECTORY {

    DWORD   Characteristics;

    DWORD   TimeDateStamp;

    WORD    MajorVersion;

    WORD    MinorVersion;

    DWORD   Name;                   // 익스포트된 DLL 파일명이 있는 RVA

    DWORD   Base;                    // 시작 오디날번호

    DWORD   NumberOfFunctions;      // EXPORT 된 함수 개수

    DWORD   NumberOfNames;         // NumberOfFunctions 와 같은 값을 사용

    DWORD   AddressOfFunctions;      // EXPORT Address Table 의 RVA

    DWORD   AddressOfNames;        // EXPORT Name Pointer Table 의 RVA

    DWORD   AddressOfNameOrdinals;  // EXPORT Ordinal Table 의 RVA

} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

EXPORT Address Table, EXPORT Name Pointer Table 이 두 개의 테이블은 IMPORT

에서와 마찬가지로 연속된 4 바이트 주소를 담고 있는 테이블이다. 따라서 IMPORT 를 다룰

때에 사용했던 IMAGE_THUNK_DATA 구조체를 여기서도 사용한다. 그리고 EXPORT Ordinal

Table 은 2 바이트 오디날 번호의 연속이다. 오디날 번호는 0x0000 번부터 시작해서 중복되지

않게 할당되어야 한다. 이 테이블은 달리 선언되어 있지 않고 아래처럼 선언한 다음 사용할 수

있다.

PIMAGE_EXPORT_DIRECTORY ped = (이미지 익스포트 디렉토리의 주소값);

WORD Ordinals[ped->NumberOfFunctions];

Ordinals[0] = 0x0000;

Page 21: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

Ordinals[1] = 0x0001;

Ordinals[2] = 0x0002;

 EXPORT 는 IMPORT 에 비해서 이렇게 단순하다. 하지만 EXPORT Address Table 과

EXPORT Name Pointer Table 을 구성하고 있는 엔트리 주소들이 어디를 가리키는지는 모를

것이다. 위 “PEView 로 열어본 DLL 파일” 그림에서 하나 설명하지 않은 EXPORT Names 가

바로 그 열쇠이다. 여기에는 IMPORT 에서의 IMPORT Hints 영역과 마찬가지로 문자열정보를

담고 있고, 이곳을 앞서 설명한 두 엔트리 테이블에서 가리키게 된다.

자 그럼, EXPORT Names 의 예제를 살펴보자.

설명이 더 필요 없을 듯하지만, 혹 모르실 분을 위해 자세히 정리하고 넘어가자.

(CIAT_hook.. 으로 시작되는 함수는 실제 개발자가 EXPORT 한 함수가 아니다)

처음으로 나오는 문자열은 EXPORT 한 DLL 파일명이다. 그리고 NULL 한바이트로 끝난다.

뒤이어 Mine_recv 함수와 Mine_Send 함수명이 나온다. 역시 NULL 한 바이트로 끝난다. 즉

여기에는 어떠한 오디날번호도 포함되지 않은 순수한 문자열로만 이루어져 있다.

Page 22: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

“[DLL 파일명][EXPORT 된 함수명 1][EXPORT 된 함수명 2]”

TCHAR EXPORT_NAMES[]={"IAT_hook.dll\0...생략.\0Mine_recv\0Mine_send\

0"};

 자 그럼 첫 번째로 EXPORT 된 함수는 Mine_recv 이며, 오디날번호는 0x0001 이다.

오디날번호는 0x0000 부터 시작했지만, CIAT_hook 과 같은 컴파일러에 의해서 만들어진

문자열값은 제외하면 0x0001 이 맞다. 이 오디날의 시작번호는

(PIMAGE_EXPORT_DIRECTORY)->Base 에 넣어야 한다.

PIMAGE_EXPORT_DIRECTORY ped = (이미지 익스포트 디렉토리의 주소값);

ped->Base = 0x0001;   // Mine_recv()

 여기까지 유심히 읽어 내려온 독자라면 분명히 PE 의 구조가 머릿속에 그려지고, PE 를 알 것만

같은 느낌을 받을 것이다. 그러나 어떻게 코딩을 시작해야할지, 코딩을 할 때 유의해야 할

사항들은 무엇인지.. 궁금할 것이다. 그것은 다음 장에 첨부된 몇 가지 예제 PE 파서

소스코드들을 통해서 유추, 분석해보길 바란다.

4. 파서코드 작성 ( PEKits )

Page 23: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

아래 소스들은 VC++ WIN32 콘솔어플리케이션 프로젝트로 생성한 다음, 사용할 수 있는

함수들이다. 그리고 이 함수들을 사용하기 위해서는 아래 두 세가지 헤더를 반드시 선언해야

한다.

#include "windows.h"

#include "winnt.h"

#include "stdio.h"

4.1 print_pe_import() - IMPORT 정보 출력

/*

- 호출방법 : print_pe_import("c:\\putty.exe");

IMPORT_DESCRIPTOR : 003F0DD8

+DLL: ADVAPI32.dll

        - RegCloseKey

        - RegCreateKeyA

        - RegSetValueExA

        - RegOpenKeyA

        - RegQueryValueExA

        - RegDeleteKeyA

        - RegEnumKeyA

+DLL: COMCTL32.dll

        - ORD#  14

        - ORD#  15

 ...

Page 24: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

*/

int print_pe_import(char *filename)

{

PIMAGE_DOS_HEADER pIDH;

PIMAGE_NT_HEADERS pINH;

PIMAGE_IMPORT_DESCRIPTOR pIID;

PIMAGE_THUNK_DATA pITD;

PIMAGE_IMPORT_BY_NAME pIIBName;

        

HANDLE hFile, hFileMap;

DWORD file_size;

PCHAR file_base;

DWORD idei;

DWORD idei_size;

DWORD p;

int ret = 0;

hFile = CreateFile(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL, NULL);

if(hFile == INVALID_HANDLE_VALUE){

        printf("file open failed %s\n", filename);

        ret = -1;

        goto RET;

}

Page 25: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

file_size = GetFileSize(hFile, NULL);

hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);

if(hFileMap == NULL){

        printf("file mapping failed %s\n", filename);

        ret = -2;

        goto RET;

}

file_base = (PCHAR) MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, file_size);

if(file_base == NULL){

        printf("file MapView Failed %s\n", filename);

        ret = -3;

        goto RET;

}

pIDH = (PIMAGE_DOS_HEADER) file_base;

pINH = (PIMAGE_NT_HEADERS) ((DWORD)pIDH + pIDH->e_lfanew);

if(pIDH->e_magic != IMAGE_DOS_SIGNATURE){

        printf("not found MZ DOS Magic [ %s ]\n", filename);

        ret = -4;

        goto RET;

Page 26: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

}

if(pINH->Signature != IMAGE_NT_SIGNATURE){

        printf("not found PE NT Signature [ %s ]\n", filename);

        ret = -5;

        goto RET;

}

/* 

        IMPORT_DIRECTORY_TABLE =

                file_base +

                IMAGE_LOAD_CONFIG_DIRECTORY +

                72(IMAGE_LOAD_CONFIG_DIRECTORY 크기)

winnt.h:

#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory

*/

idei = pINH-

>OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddres

s;

idei_size = pINH-

>OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size;

pIID = (PIMAGE_IMPORT_DESCRIPTOR) ((DWORD) file_base + idei);

Page 27: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

// PEView.exe 에서의 IMPORT Directory Table 과 IMPORT_DESCRIPTOR 는 같다.

printf("IMPORT_DESCRIPTOR : %p\n", pIID);

while(pIID->Name){

        if(pIID->Name != NULL){

        /* 

           pIID->Name 은 RVA 값이다. 이것은 DLL 파일명이 있는 위치의 RVA

           를 가지고 있다. 참조하기위해서는 이미지베이스주소(file_base) 를 더해야

             유효한 메모리상의 주소가 된다.

        */

                printf("+DLL: %s\n", ((DWORD) file_base + pIID->Name));

                // pITD = IAT 안의 해당 DLL 첫 API 시작엔트리;

                pITD = (PIMAGE_THUNK_DATA) (file_base + pIID->FirstThunk); 

                        

                while(1){

                        if(pITD->u1.AddressOfData == 0) // 엔트리 끝났음!

                                        break;

                        if(pITD->u1.Ordinal & IMAGE_ORDINAL_FLAG){ // 오디날!!

                                printf("  - ORD#%4u\n", IMAGE_ORDINAL(pITD->u1.Ordinal));

                        } else{  // 아니면 함수명으로 IMPORT 된것!!

                                p = (DWORD) pITD->u1.AddressOfData;

                                pIIBName = (PIMAGE_IMPORT_BY_NAME) ((DWORD) file_base +

p);

                                printf("  - %s\n", pIIBName->Name);

Page 28: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

                        }

                        pITD++;

                }

        }

        pIID++;

}

        

RET:

        UnmapViewOfFile(file_base);

        CloseHandle(hFile);

        return(ret);

}

4.2 fix_entrypoint() - EntryPoint 수정

/*

- 호출방법 : fix_entrypoint("c:\\putty.exe", 0x40000000);

Entry Point changed(belows are not RVAs)!!

Fixed Filename: c:\putty.exe_new_ep.exe

EntryPoint Address: 0044265F => 40000000

*/

/*

        putty.exe 를 이 함수로 엔트리포인트를 바꾸면..

        엔트리포인트가 바뀐 PE 파일 putty.exe_new_ep.exe 가 생성된다

Page 29: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

*/

int fix_entrypoint(char *filename, DWORD new_entrypoint)

{

PIMAGE_DOS_HEADER pIDH;

PIMAGE_NT_HEADERS pINH;

        

HANDLE hFile, hFileMap;

        

DWORD file_size;

PCHAR file_base;

        

DWORD org_entrypoint;

DWORD new_ep_rva;

        

char out_filename[128] = { 0 };

int ret = 0;

/* 

        파일출력을 위해서 복사본 생성

        (별도로 파일 출력을 하는게 아니라, 사본을 만들어서 맵핑함으로써

         두개의 파일을 열 필요가 없게 된다)

        

*/

sprintf(out_filename, "%s_new_ep.exe", filename);

Page 30: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

CopyFile(filename, out_filename, false);

hFile = CreateFile(out_filename, GENERIC_READ | GENERIC_WRITE, 0, NULL,

OPEN_EXISTING,                         FILE_ATTRIBUTE_NORMAL, NULL);

if(hFile == INVALID_HANDLE_VALUE){

        printf("file open failed %s\n", out_filename);

        ret = -1;

        goto RET;

}

file_size = GetFileSize(hFile, NULL);

hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);

if(hFileMap == NULL){

        printf("file mapping failed %s\n", out_filename);

        ret = -2;

        goto RET;

}

file_base = (PCHAR) MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE,

0, 0, file_size);

if(file_base == NULL){

Page 31: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

        printf("file MapView Failed %s\n", out_filename);

        ret = -3;

        goto RET;

}

pIDH = (PIMAGE_DOS_HEADER) file_base;

pINH = (PIMAGE_NT_HEADERS) ((DWORD)pIDH + pIDH->e_lfanew);

if(pIDH->e_magic != IMAGE_DOS_SIGNATURE){

        printf("not found MZ DOS Magic [ %s ]\n", out_filename);

        ret = -4;

        goto RET;

}

if(pINH->Signature != IMAGE_NT_SIGNATURE){

        printf("not found PE NT Signature [ %s ]\n", out_filename);

        ret = -5;

        goto RET;

}

/* 

        메모리상의 실제 엔트리포인트 =

                pINH->OptionalHeader.ImageBase +

                pINH->OptionalHeader.AddressOfEntryPoint

        이미지베이스에 엔트리포인트 RVA 를 더한 값이 실제 엔트리포인트인 것은

Page 32: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

        쉽게 납득이 갈것이다. 이 함수는 사용자에게 메모리상의 새로운 엔트리포인트

        를 입력받을 것이므로, 입력받은 엔트리포인트 주소에서 이미지베이스를 뺀 다음

        AddressOfEntryPoint 에 그 값을 채우고, 메모리상의 이미지 새 파일로 저장시켜야 한다.

*/

new_ep_rva = new_entrypoint - pINH->OptionalHeader.ImageBase;

org_entrypoint = (DWORD)pINH->OptionalHeader.AddressOfEntryPoint +

(DWORD)pINH->OptionalHeader.ImageBase;

// file_base + 0x120 = &OptionalHeader.AddressOfEntryPoint

printf("\nEntry Point changed(belows are not RVAs)!!\n");

printf("Fixed Filename: %s\n", out_filename);

printf("EntryPoint Address: %p => %p\n", org_entrypoint, new_entrypoint);

pINH->OptionalHeader.AddressOfEntryPoint = new_ep_rva;       // 새 엔트리포인트 RVA

저장

RET:

        UnmapViewOfFile(file_base);

        CloseHandle(hFile);

        return(ret);

}

Page 33: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

4.3 fix_perm_sections() - Section 퍼미션 수정

/*

// READ | WRITE 만 허용된 .text 섹션에 WRITE 권한을 적용

호출방법: fix_perm_sections("c:\\putty.exe", ".text",

IMAGE_SCN_MEM_EXECUTE |                                                 

IMAGE_SCN_MEM_READ |                                                     

IMAGE_SCN_MEM_WRITE);

.text Section Permission fixed by E0000000

이제 이 .text 섹션은 수정이 가능한 퍼미션을 가지게 되었다.

*/

int fix_perm_sections(char *filename, char *section, DWORD new_permission)

{

PIMAGE_DOS_HEADER pIDH;

PIMAGE_NT_HEADERS pINH;

PIMAGE_SECTION_HEADER pISH;

        

HANDLE hFile, hFileMap;

        

DWORD file_size;

PCHAR file_base;

DWORD first_sh;

Page 34: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

char out_filename[128] = { 0 };

        

int ret = 0;

int i = 0;

bool is_found = false;

/*

        파일출력을 위해서 복사본 생성

        (별도로 파일 출력을 하는게 아니라, 사본을 만들어서 맵핑함으로써

         두개의 파일을 열 필요가 없게 된다)

*/

sprintf(out_filename, "%s_new_%s_perm.exe", filename, section);

CopyFile(filename, out_filename, false);

hFile = CreateFile(out_filename, GENERIC_READ | GENERIC_WRITE, 0, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

if(hFile == INVALID_HANDLE_VALUE){

        printf("file open failed %s\n", out_filename);

        ret = -1;

        goto RET;

}

Page 35: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

file_size = GetFileSize(hFile, NULL);

hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);

if(hFileMap == NULL){

        printf("file mapping failed %s\n", out_filename);

        ret = -2;

        goto RET;

}

file_base = (PCHAR) MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE,

0, 0, file_size);

if(file_base == NULL){

        printf("file MapView Failed %s\n", out_filename);

        ret = -3;

        goto RET;

}

pIDH = (PIMAGE_DOS_HEADER) file_base;

pINH = (PIMAGE_NT_HEADERS) ((DWORD)pIDH + pIDH->e_lfanew);

if(pIDH->e_magic != IMAGE_DOS_SIGNATURE){

        printf("not found MZ DOS Magic [ %s ]\n", out_filename);

        ret = -4;

Page 36: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

        goto RET;

}

if(pINH->Signature != IMAGE_NT_SIGNATURE){

        printf("not found PE NT Signature [ %s ]\n", out_filename);

        ret = -5;

        goto RET;

}

// section 인자와 같은 섹션명을 가진 섹션헤더를 탐색후 퍼미션을 변경

// 첫 섹션헤더 = file_base + 0x1F0

first_sh = (DWORD) file_base + 0x1F0;

pISH = (PIMAGE_SECTION_HEADER) first_sh;

        

// 사용자가 원하는 섹션헤더를 탐색

for(i = 0; i < pINH->FileHeader.NumberOfSections; i++){

        if(strcmp((char *)pISH->Name, section) == 0){ // 발견!!

                pISH->Characteristics = new_permission;      // 새 퍼미션 저장!!

                is_found = true;

        }

        pISH++;

}

Page 37: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

if(is_found == true)

        printf("\n%s Section Permission fixed by %p\n", section, new_permission);

else

        printf("\nnot found %s Section\n", section);

RET:

        UnmapViewOfFile(file_base);

        CloseHandle(hFile);

        return(ret);       

}

5. 맺음말

5.1 참고자료 ( References )

 주제에서 벗어나 미처 자세히 다루지 못한 PE 기본에 대해서는 아래 자료들을 참고하십시오.

[1] Matt Pietrek, 1994

Peering Inside the PE: A Tour of the Win32 Portable Executable File Format

Page 38: PE 날개달기 ( Flying PE ) · Web view현재 PE 파일포맷의 세부스펙을 명시해 놓은 MS 사의 영문 명세서(Specification) 가 나와 있으며, 관심을 가진

( http://msdn2.microsoft.com/en-us/library/ms809762.aspx )

[2] Matt Pietrek, 2002

An In-Depth Look into the Win32 Portable Executable File Format

( http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspx )

[3] Ashkbiz Danehkar, 2005

Make your owner PE Protector Part 1: Your first EXE Protector

( http://www.codeproject.com/cpp/peprotector1.asp )

5.2 마치며 ( Conclusion )

 여기까지 새로울 것 없는 PE 구조와 코딩 법을 알아보았습니다. 문서작성 초기에는 PE Code

Injector 를 작성하여 첨부 할 예정이었으나, 조금 더 문서작성 기간을 앞당기고자 포함하지

못했습니다. 아쉬운 감이 없진 않지만, 잘 보셨길 바라며 PE 를 이해하는 것을 넘어서 다루는

방법을 익히는데 작은 보탬이 되셨으면 합니다.