'PE 구조'에 해당되는 글 2건

  1. 2008.11.18 PE 파일 분석 - 헤더분석
  2. 2008.11.18 악성코드 분석가 입장에서 본 PE 구조

개요

 윈도우 시스템 프로그래밍한다는 사람치고 PE 파일에 대해서 모르는 사람은 아마 거의 없을 것이다. 윈도우 실행 파일 및 DLL, 그리고 드라이버 파일까지도 PE 파일 형태를 따르고 있으니 뭘 해도 따라다니는게 이 PE(Portable Executable) 파일 포맷이니까 말이다. PE 파일 포맷은 크게 헤더, 섹션, 데이터의 세부분으로 나뉘는데 기존 DOS 시절 사용하던 COFF(Common Object File Format)과 거의 비슷한 구조를 가지며 기본 뼈대에서 확장된 듯한 형태를 가진다.

 PE 실행파일이 가지고 있는 헤더를 분석함으로써 실제 데이터가 있는 위치를 파일에서 찾고 해당 영역을 분석할 수 있다.

 PE 파일 구조에 대해서 자세히 알아보기 전에 참고할 좋은 프로그램 몇가지를 소개한다.


  • PE Explorer :  유료다. ㅡ,.ㅡ;;; 공짜 버전도 있는데 30일 한정이라서... 그렇지만 강력하다 @0@)/~
  • PE Browser : 공짜다. 하지만 역시 뭔가 부족하다는 거... 그냥 쓰기에는 괜찮다.

 앞으로 Relative Virtual Address(RVA)라는 용어가 많이 나올텐데, 잠깐 알아보자.

 RVA는 실행파일이 메모리에 로드되었을 때, 그 시작 주소를 0으로 생각하고 계산하는 주소이다. 즉 RVA의 값이 0x40 이고 실행파일이 로드되었을 때, 그 시작위치가 0x1000 이라면 실제 그 영역이 메모리에 로드되었을 때 위치는 0x1040이 된다. 실행파일 시작 위치를 0으로 하는 상대적 주소라는 것만 알면 같단하다. 뒤에 설명하면서 계속 사용될 용어이므로 알아두자.

 

사용자 삽입 이미지


PE 파일 포맷 전체 구조

 PE 파일 포맷은 크게 아래와 같이 구성된다.

 

사용자 삽입 이미지


 위에서 보는 것과 같이 크게는 붉은 색 부분과  푸른색 부분으로 나눌 수 있다. 붉은 색 부분은 헤더나 데이터가 위치하는 영역의 속성과 크기 등등을 나타내는 정보이고, 푸른 색 부분은 실제 데이터들이 위치하는 영역을 나타낸다.


  • IMAGE_DOS_HEADER : PE 파일의 처음에 위치하며 뒷부분에 DOS에서 실행했을 때,  에러 메시지(This program cannot be run in DOS mode)를 표시하는 스텁(Stub) 코드를 포함하고 있음. MAGIC Number와 다음에 오는 IMAGE_NT_HEADER의 위치를 표시
  • IMAGE_NT_HEADER : PE 파일 포맷에 대한 정보를 포함. 아래의 두 부분으로 구성
  • IMAGE_FILE_HEADER :  Section의 수 및 속성과 같은 정보 포함
  • IMAGE_OPTIONAL_HEADER : PE 파일에 대한 속성 또는 이미지 베이스와 같은 정보 포함
    • Data Directory : 어떤 영역의 Virtual Address와 Size 정보를 포함

  • IMAGE_SECTION_HEADER : 섹션에 대한 실질적인 정보를 포함
  • Section(섹션) : 실제 데이터가 위치하는 영역

 각 영역에 대해 세부적으로 알아보자.


IMAGE_DOS_HEADER

 IMAGE_DOS_HEADER는 PE 실행파일 첫부분에 위치하며 아래와 같이 WinNT.h에 정의되어 있다.


#define IMAGE_DOS_SIGNATURE                 0x4D5A      // MZ
#define IMAGE_OS2_SIGNATURE                 0x4E45      // NE

#define IMAGE_OS2_SIGNATURE_LE              0x4C45      // LE

#define IMAGE_NT_SIGNATURE                  0x50450000  // PE00

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header

    WORD   e_magic;                     // Magic number <MZ>
    WORD   e_cblp;                      // Bytes on last page of file

    WORD   e_cp;                        // Pages in file

    WORD   e_crlc;                      // Relocations

    WORD   e_cparhdr;                   // Size of header in paragraphs

    WORD   e_minalloc;                  // Minimum extra paragraphs needed

    WORD   e_maxalloc;                  // Maximum extra paragraphs needed

    WORD   e_ss;                        // Initial (relative) SS value

    WORD   e_sp;                        // Initial SP value

    WORD   e_csum;                      // Checksum

    WORD   e_ip;                        // Initial IP value

    WORD   e_cs;                        // Initial (relative) CS value

    WORD   e_lfarlc;                    // File address of relocation table

    WORD   e_ovno;                      // Overlay number

    WORD   e_res[4];                    // Reserved words

    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)

    WORD   e_oeminfo;                   // OEM information; e_oemid specific

    WORD   e_res2[10];                  // Reserved words

    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

  크게 주의해서 볼 부분은 실행파일인지 판단하는데 사용되는 e_magic 부분과 다음에 오는 IMAGE_NT_HEADER의 위치를 표시해 주는 e_lfanew 부분이다. 다른 부분은 크게 중요한 정보를 가지고 있지 않으니 일단 패스~


IMAGE_NT_HEADER

 IMAGE_NT_HEADER는 실제 PE 파일 포맷에 대한 정보를 포함하는 헤더로써 IMAGE_FILE_HEADER와 IMAGE_OPTIONAL_HEADER로 구성된다.


typedef struct _IMAGE_NT_HEADERS {

    DWORD Signature; <PE00>
    IMAGE_FILE_HEADER FileHeader;

    IMAGE_OPTIONAL_HEADER32 OptionalHeader;

} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

 IMAGE_NT_HEADER는 위와 같이 Signature와 File 헤더, 그리고 Optional Header로 구성되어있다. Signature는 IMAGE_NT_SIGNATURE 로 <PE00>의 값을 가진다. 그럼 첫번째에 해당하는 FileHeader를 알아보자.


IMAGE_FILE_HEADER


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;

 IMAGE_FILE_HEADER는 위와 같이 구성된다. 각 항목에 대해서 알아보면 아래와 같다.


  • Machine : CPU ID를 나타내는데, 간단히 보면 Intel 인지, MIPS 인지 등등의 정보가 들어있음
  • NumberOfSections : PE 파일에 포함된 총 섹션의 수를 나타냄
  • TimeDateStamp : 컴파일러 또는 링커가 파일을 생성한 시간. 1970년 1월 1일 GMT 기준으로 지나온 초
  • PointerToSymbolTable :  COFF 파일의 심볼 테이블의 오프셋을 나타냄. 없는 경우가 대부분
  • NumberOfSymbols : 심볼의 개수를 나타냄
  • SizeOfOptionalHeader : 뒤에 이어서 나오는 Optional Header의 크기를 나타낸다. 32Bit/64Bit에 따라서 그 크기가 다름
  • Characteristics : 파일의 특성

 Machine에 대한 매크로는 WinNT.h에 정의되어있는데 아래와 같다. Intel32 or Intel64가 대부분일테니까 위의 파란색만 보면 될것 같다.


#define IMAGE_FILE_MACHINE_UNKNOWN           0

#define IMAGE_FILE_MACHINE_I386              0x014c  // Intel 386.
#define IMAGE_FILE_MACHINE_R3000             0x0162  // MIPS little-endian, 0x160 big-endian

#define IMAGE_FILE_MACHINE_R4000             0x0166  // MIPS little-endian

#define IMAGE_FILE_MACHINE_R10000            0x0168  // MIPS little-endian

#define IMAGE_FILE_MACHINE_WCEMIPSV2         0x0169  // MIPS little-endian WCE v2

#define IMAGE_FILE_MACHINE_ALPHA             0x0184  // Alpha_AXP

#define IMAGE_FILE_MACHINE_SH3               0x01a2  // SH3 little-endian

#define IMAGE_FILE_MACHINE_SH3DSP            0x01a3

#define IMAGE_FILE_MACHINE_SH3E              0x01a4  // SH3E little-endian

#define IMAGE_FILE_MACHINE_SH4               0x01a6  // SH4 little-endian

#define IMAGE_FILE_MACHINE_SH5               0x01a8  // SH5

#define IMAGE_FILE_MACHINE_ARM               0x01c0  // ARM Little-Endian

#define IMAGE_FILE_MACHINE_THUMB             0x01c2

#define IMAGE_FILE_MACHINE_AM33              0x01d3

#define IMAGE_FILE_MACHINE_POWERPC           0x01F0  // IBM PowerPC Little-Endian

#define IMAGE_FILE_MACHINE_POWERPCFP         0x01f1

#define IMAGE_FILE_MACHINE_IA64              0x0200  // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16            0x0266  // MIPS

#define IMAGE_FILE_MACHINE_ALPHA64           0x0284  // ALPHA64

#define IMAGE_FILE_MACHINE_MIPSFPU           0x0366  // MIPS

#define IMAGE_FILE_MACHINE_MIPSFPU16         0x0466  // MIPS

#define IMAGE_FILE_MACHINE_AXP64             IMAGE_FILE_MACHINE_ALPHA64

#define IMAGE_FILE_MACHINE_TRICORE           0x0520  // Infineon

#define IMAGE_FILE_MACHINE_CEF               0x0CEF

#define IMAGE_FILE_MACHINE_EBC               0x0EBC  // EFI Byte Code

#define IMAGE_FILE_MACHINE_AMD64             0x8664  // AMD64 (K8)

#define IMAGE_FILE_MACHINE_M32R              0x9041  // M32R little-endian

#define IMAGE_FILE_MACHINE_CEE               0xC0EE

 다음은 Characteristics에 대한 부분인데 WinNT.h에 아래와 같이 정의되어있다. 역시나 파란색 부분만 보면 될 것 같다.


#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file.

#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // File is executable  (i.e. no unresolved externel references).

#define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // Line nunbers stripped from file.

#define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // Local symbols stripped from file.

#define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Agressively trim working set

#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // App can handle >2gb addresses

#define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // Bytes of machine word are reversed.

#define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // Debugging info stripped from file in .DBG file

#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400  // If Image is on removable media, copy and run from the swap file.

#define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800  // If Image is on Net, copy and run from the swap file.

#define IMAGE_FILE_SYSTEM                    0x1000  // System File.
#define IMAGE_FILE_DLL                       0x2000  // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // File should only be run on a UP machine

#define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  // Bytes of machine word are reversed.

IMAGE_OPTIONAL_HEADER


typedef struct _IMAGE_OPTIONAL_HEADER {

    //

    // Standard fields.

    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;

    BYTE    MinorLinkerVersion;

    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //

    // NT additional fields.

    //

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;

    WORD    MajorOperatingSystemVersion;

    WORD    MinorOperatingSystemVersion;

    WORD    MajorImageVersion;

    WORD    MinorImageVersion;

    WORD    MajorSubsystemVersion;

    WORD    MinorSubsystemVersion;

    DWORD   Win32VersionValue;

    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;

    DWORD   CheckSum;

    WORD    Subsystem;

    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;

    DWORD   SizeOfStackCommit;

    DWORD   SizeOfHeapReserve;

    DWORD   SizeOfHeapCommit;

    DWORD   LoaderFlags;

    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;


 Optional Header는 꽤나 중요한 정보를 가지고 있다. 위에서 보면 알 수 있듯이 PE 파일의 전반적이 내용들에 대한 정보를 포함한다. 항목이 꽤나 많은데 중요한 정보만 추리면 아래와 같다.


  • Magic : Signature로 32Bit의 경우 0x10b를 가짐
  • SizeOfCode : 섹션 중에 IMAGE_SCN_CNT_CODE 속성을 가진 섹션들 전체의 합
  • SizeOfInitializedData : 섹션 중에 IMAGE_SCN_CNT_INITIALIZED_DATA 속성을 가진 섹션들 전체의 합
  • SizeOfUninitializedData : 섹션 중에 IMAGE_SCN_CNT_UNINITIALIZED_DATA 속성을 가진 섹션들 전체의 합
  • AddressOfEntryPoint : Entry Point의 주소. 실제 로더가 제일 먼저 실행할 코드의 시작점
  • BaseOfCode : 코드가 시작되는 상대 주소(RVA)
  • BaseOfData : 데이터가 시작되는 상대 주소(RVA)
  • ImageBase : 이미지가 로딩되는 메모리의 Base 주소. 일반적으로 실행파일의 경우 0x400000(4Mbyte) 위치에 로딩
  • SectionAlignment : 섹션이 정렬되는 크기. PE 파일 자체가 메모리 맵 파일이기 때문에 0x1000(4Kbyte) 보다 크거나 같아야 함
  • SizeOfImage : 모든 섹션들의 합. 이미지 실행을 위해 메모리를 할당해야 하는 총 크기
  • NumberOfRvaAndSizes : 뒤에 오는 DataDirectory의 개수. 무조건 16개
  • Data Directory : 총 16개가 있으며 각 항목은 특정 데이터에 대한 정보를 가지고 있음. 뒤에서 설명

 Magic은 아래와 같이 WinNT.h에 정의되어있다.


#define IMAGE_NT_OPTIONAL_HDR32_MAGIC      0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC      0x20b

#define IMAGE_ROM_OPTIONAL_HDR_MAGIC       0x107


IMAGE_DATA_DIRECTORY

typedef struct _IMAGE_DATA_DIRECTORY {

    DWORD   VirtualAddress;

    DWORD   Size;

} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

 데이터 디렉토리는 위와 같은 구조로 이루어져있으며 IMAGE_OPTIONAL_HEADER에 총 16개가 있다. 각각에 Index에 대한 매크로는 WinNT.h에 아래와 같이 정의되어있다.


#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory

#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory

#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table

#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory

//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)

#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data

#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP

#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory

#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors

#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor



 이미지 디렉토리 정보는 굉장히 중요하다. 경우에 따라서 섹션이 합쳐질 수 있기 때문에 통합된 섹션에서 원하는 정보를 찾는 방법은 이미지 디렉토리에 포함된 정보를 이용하는 방법 밖에는 없다. 여러모로 많이 쓰이는 인덱스는 아래와 같은 역할을 한다.


  • IMAGE_DIRECTORY_ENTRY_EXPORT : Export 함수들에 대한 Export Table의 시작 위치와 크기를 나타냄
  • IMAGE_DIRECTORY_ENTRY_IMPORT : Import 함수들에 대한 Import Table의 시작 위치와 크기를 나타냄
  • IMAGE_DIRECTORY_ENTRY_RESOURCE : IMAGE_RESOURCE_DIRECTORY 구조체의 시작 위치를 나타냄
  • IMAGE_DIRECTORY_ENTRY_TLS : Thread Local Storage에 대한 포인터
  • IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG : IMAGE_LOAD_CONFIG_DIRECTORY 구조체애 대한 포인터
  • IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT : IMAGE_BOUND_IMPORT_DESCRIPTOR 구조체의 배열을 가리키는 포인터
  • IMAGE_DIRECTORY_ENTRY_IAT : Import Address Table의 시작 위치를 나타냄

 실제 위의 값중에서 변경하면 OS의 로더에 의해 로딩이 되지 않는 부분이 있는데, 붉은 색으로 표시된 IMAGE_DIRECTORY_ENTRY_TLSIMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 부분이다. 이 부분은 미리 로더가 읽어서 초기 작업을 실행하는 부분이라 이부분이 포함된 영역을 이상하게 조작하게되면 로더가 로딩에 실패하게 된다(PE파일 암호화 과제를 하면서 온몸으로 느꼈다.. ㅡ_ㅡ;;;). 조작을 하려면 신중히 해야될 듯 싶다.


 여기까지 IMAGE_NT_HEADER에 대해서 알아보았다. 일단 지금은 특정영역의 크기와 위치를 표시한다는 정도만 알아놓고 다음으로 넘어가자.


IMAGE_SECTION_HEADER

 PE 헤더의 뒷부분에 연속해서 IMAGE_SECTION_HEADER가 위치하게 된다. 섹션은 뒤에 올 코드나 데이터가 위치하는 영역에 대한 구체적인 정보를 포함하고 있으므로 굉장히 중요하다. 섹션의 개수는 앞서 IMAGE_FILE_HEADER에 포함된 NumberOfSections에서 얻을 수 있으며 해당 개수만큼 얻어오면 된다. IMAGE_SECTION_HEADER는 WinNT.h에 아래와 같이 정의되어있다.



#define IMAGE_SIZEOF_SHORT_NAME              8


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;

    WORD    NumberOfLinenumbers;

    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;


  중요한 항목에 대한 의미는 아래와 같다.


  • VirtualSize : 실제 코드나 데이터 영역의 크기를 표시
  • VirtualAddress : 메모리에 로드되었을 때 RVA를 표시
  • SizeOfRawData : VirtualSize의 크기를 IMAGE_OPTIONAL_HEADER에 포함된 FileAlignment의 단위로 올림한 크기
  • PointerToRawData : 실제 섹션 데이터가 파일 내에 존재하는 오프셋. Virtual Address와 같을 수도 있고 다를 수도 있음
  • Characteristics : 섹션의 속성 표시. 자세한 것은 뒤를 참조

 위의 VirtualSize와 SizeOfRawData는 영역의 크기를 나타낸다는 공통점이 있으나 라운드 업된 크기와 실제 크기를 나타낸다는 차이가 있다. 만약 섹션의 크기를 조작했다면 위의 두부분 모두 손을 봐야 한다.

 Virtual Address와 Pointer To Raw Data의 값이 다를 수 있다고 했는데, 왜그럴까? 이것은 실행 파일의 크기를 줄이기 위해서이다. 만약 로드 되었을 때 크기가 0x2000 정도인 섹션이 있다고 하자. 그런데 이 섹션은 메모리의 값이 초기화 될 필요도 없고 값도 들어있지 않다면? 실행 시에 영역만 할당해주면 끝이라면? 이런 경우라면 굳이 이 섹션이 실행파일에서 영역을 가지고 있을 필요가 없다. 따라서 Virtual Address는 0이 아닌 값을 갖겠지만 파일 내에 위치를 의미하는 Pointer To Raw Data의 값은 0이 된다.

 즉 실제 파일 내에는 존재하지 않는 영역이 생김으로써 Virtual Address와 Pointer To Raw Data의 값이 달라질 수 있으며, 기타 다른 이유로도 충분히 다를 수 있다. 따라서 실행파일을 조작하기위해서는 Pointer To Raw Data의 값을 위주로 작업을 해야 한다.


 Characteristics는 해당 영역의 속성을 나타내는데, WinNT.h에 정의되어있고 아주 흥미로운 값을 가지고 있다.

//      IMAGE_SCN_TYPE_REG                   0x00000000  // Reserved.

//      IMAGE_SCN_TYPE_DSECT                 0x00000001  // Reserved.

//      IMAGE_SCN_TYPE_NOLOAD                0x00000002  // Reserved.

//      IMAGE_SCN_TYPE_GROUP                 0x00000004  // Reserved.

#define IMAGE_SCN_TYPE_NO_PAD                0x00000008  // Reserved.

//      IMAGE_SCN_TYPE_COPY                  0x00000010  // Reserved.

#define IMAGE_SCN_CNT_CODE                   0x00000020  // Section contains code.
#define IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040  // Section contains initialized data.

#define IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080  // Section contains uninitialized data.

#define IMAGE_SCN_LNK_OTHER                  0x00000100  // Reserved.

#define IMAGE_SCN_LNK_INFO                   0x00000200  // Section contains comments or some other type of information.

//      IMAGE_SCN_TYPE_OVER                  0x00000400  // Reserved.

#define IMAGE_SCN_LNK_REMOVE                 0x00000800  // Section contents will not become part of image.

#define IMAGE_SCN_LNK_COMDAT                 0x00001000  // Section contents comdat.

//                                           0x00002000  // Reserved.

//      IMAGE_SCN_MEM_PROTECTED - Obsolete   0x00004000

#define IMAGE_SCN_NO_DEFER_SPEC_EXC          0x00004000  // Reset speculative exceptions handling bits in the TLB entries for this section.

#define IMAGE_SCN_GPREL                      0x00008000  // Section content can be accessed relative to GP

#define IMAGE_SCN_MEM_FARDATA                0x00008000

//      IMAGE_SCN_MEM_SYSHEAP  - Obsolete    0x00010000

#define IMAGE_SCN_MEM_PURGEABLE              0x00020000

#define IMAGE_SCN_MEM_16BIT                  0x00020000

#define IMAGE_SCN_MEM_LOCKED                 0x00040000

#define IMAGE_SCN_MEM_PRELOAD                0x00080000

#define IMAGE_SCN_ALIGN_1BYTES               0x00100000  //

#define IMAGE_SCN_ALIGN_2BYTES               0x00200000  //

#define IMAGE_SCN_ALIGN_4BYTES               0x00300000  //

#define IMAGE_SCN_ALIGN_8BYTES               0x00400000  //

#define IMAGE_SCN_ALIGN_16BYTES              0x00500000  // Default alignment if no others are specified.

#define IMAGE_SCN_ALIGN_32BYTES              0x00600000  //

#define IMAGE_SCN_ALIGN_64BYTES              0x00700000  //

#define IMAGE_SCN_ALIGN_128BYTES             0x00800000  //

#define IMAGE_SCN_ALIGN_256BYTES             0x00900000  //

#define IMAGE_SCN_ALIGN_512BYTES             0x00A00000  //

#define IMAGE_SCN_ALIGN_1024BYTES            0x00B00000  //

#define IMAGE_SCN_ALIGN_2048BYTES            0x00C00000  //

#define IMAGE_SCN_ALIGN_4096BYTES            0x00D00000  //

#define IMAGE_SCN_ALIGN_8192BYTES            0x00E00000  //

// Unused                                    0x00F00000

#define IMAGE_SCN_ALIGN_MASK                 0x00F00000

#define IMAGE_SCN_LNK_NRELOC_OVFL            0x01000000  // Section contains extended relocations.

#define IMAGE_SCN_MEM_DISCARDABLE            0x02000000  // Section can be discarded.
#define IMAGE_SCN_MEM_NOT_CACHED             0x04000000  // Section is not cachable.

#define IMAGE_SCN_MEM_NOT_PAGED              0x08000000  // Section is not pageable.

#define IMAGE_SCN_MEM_SHARED                 0x10000000  // Section is shareable.
#define IMAGE_SCN_MEM_EXECUTE                0x20000000  // Section is executable.

#define IMAGE_SCN_MEM_READ                   0x40000000  // Section is readable.

#define IMAGE_SCN_MEM_WRITE                  0x80000000  // Section is writeable.



  중요한 플래그 별로 의미를 보면 아래와 같다.


  • IMAGE_SCN_CNT_CODE : 섹션에 코드가 포함되어있음. IMAGE_SCN_MEM_EXECUTE와 보통 같이 지정됨
  • IMAGE_SCN_CNT_INITIALIZED_DATA : 섹션이 초기화된 데이터를 포함하고 있음
  • IMAGE_SCN_CNT_UNINITIALIZED_DATA : 섹션이 초기화 되지 않은 데이터를 포함하고 있음
  • IMAGE_SCN_MEM_DISCARDABLE : 섹션이 버려질 수 있음. 한번 사용되고 필요없는 섹션들(relocation 데이터 같은 경우)이 이 속성을 가짐
  • IMAGE_SCN_MEM_SHARED : 섹션이 이 모듈을 사용하는 모든 프로세스에 의해서 공유될 수 있음을 의미
  • IMAGE_SCN_MEM_EXECUTE : 섹션이 실행 가능함
  • IMAGE_SCN_MEM_READ : 섹션이 읽기 가능함
  • IMAGE_SCN_MEM_WRITE : 섹션이 쓰기 가능함

 위의 값을 보면 섹션에 대한 속성이 미리 정의되어있다는 것을 알 수 있다. 즉 데이터 섹션 같은 경우 IMAGE_SCN_MEM_READ/WRITE 속성을 가지고 있으리라 유추할 수 있고, 코드가 포함된 섹션의 경우 IMAGE_SCN_MEM_EXECUTE 속성을 가지고 있다고 유추할 수 있다.

 섹션의 경우 섹션 이름을 가지고 있는데, VC로 실행파일을 만들면 .text, .data, .idata와 같은 이름의 섹션들이 생긴다. 이름 그대로 코드, 데이터와 같은 정보가 포함된 섹션이라는 것을 알 수 있는데, 여기서 속지 말아야 할 것은 섹션 이름은 권장값이므로 섹션 이름으로 섹션이 포함하는 내용을 판단하면 안된다는 것이다. 특히 파일의 크기를 줄이는 릴리즈 옵션 같은 경우는 섹션들이 합쳐져서 하나의 섹션으로 존재하는 경우도 있기 때문에 섹션 이름을 이용해서 찾아서는 안되며 IMAGE_NT_HEADER에 있는 Data Directory의 값을 참조해서 찾도록 해야 한다.


실제 구현

 설명이 굉장히 길었다. 이제 실제로 이 헤더 정보를 분석하는 간단한 코드를 작성해 보자. RVA와 PointerOfRawData의 관계를 생각하면 약간 복잡한데, 이것은 추후에 다시 보도록 하고 헤더 정보만 표시해 보자.

 분석하는 클래스를 작성하여 간단히 헤더 정보를 추출하고 이를 화면에 표시하는 테스트 프로그램을 작성하였다.

  • PEAnalyzer.h : PE 파일을 분석하는 클래스의 헤더 파일
  • PEAnalyzer.cpp : PE 파일을 분석하는 클래스의 소스 파일
  • main.cpp : 실제 사용하는 예제

    첨부파일은 본문의 제일 아래쪽을 확인 바란다.

 아래는 실행 결과이다.


사용자 삽입 이미지


<IMAGE_DOS_HEADER의 값>


사용자 삽입 이미지


<IMAGE_SECTION_HEADER의 값>

첨부 파일


출처 : (http://kkamagui.tistory.com, http://kkamagui.springnote.com)
Posted by skensita

(1) 악성코드 분석가 입장에서 본 PE 구조

1. 악성코드와 PE(Portable Executable)파일의 조우

PE 파일이라 부르는 형식은 플랫폼에 관계없이 Win32 운영체제 시스템이면 어디든 실행 가능한 프로그램을 뜻한다. PE 파일(*.EXE, *.DLL)의 실행을 위해서는 운영체제에 실행 파일의 정보를 제공할 필요가 있는데, 예를 들면 실행 파일의 기계어 코드 위치, 아이콘 및 그림파일 등의 위치, 해당 파일이 실행될 수 있는 플랫폼의 종류, 운영체제가 파일을 실행 시킬 때 첫 시작 코드의 위치 등 수많은 정보를 제공하여야 한다. 이와 같은 다양한 정보들이 저장된 곳이 PE 파일의 처음에 위치한 PE 헤더 구조체이다.

악성 코드를 분석하여 보면 PE 헤더 구조체에 흥미로운 정보들이 많이 존재하고 있음을 알 수 있다. 악성 코드의 크기를 줄이기 위해 헤더 정보를 속이거나, 바이러스에 감염되어 원본 파일의 헤더가 변경 되기도 한다. 또한 특정 악성코드만의 고유한 정보가 숨어 있기도 하며, 일반 정상파일에서는 있을 수 없는 값들이 들어 있기도 하다.

2. 현재의 악성코드가 DOS 시절의 헤더를 이용한다?

PE 파일은 IMAGE_DOS_HEADER 구조체로 시작한다. 이는 DOS 시절에 사용되던 실행 파일의 헤더로서 실행 파일이 DOS에서 실행 되었을 때 DOS 운영체제에 알려줘야 할 정보들이 담겨 있다. 다음은 IMAGE_DOS_HEADER 구조체이다.


typedef struct _IMAGE_DOS_HEADER // DOS .EXE header
{
   WORD e_magic; // Magic number
   WORD e_cblp; // Bytes on last page of file
   WORD e_cp; // Pages in file
   WORD e_crlc; // Relocations
   WORD e_cparhdr; // Size of header in paragraphs
   WORD e_minalloc; // Minimum extra paragraphs needed
   WORD e_maxalloc; // Maximum extra paragraphs needed
   WORD e_ss; // Initial (relative) SS value
   WORD e_sp; // Initial SP value
   WORD e_csum; // Checksum
   WORD e_ip; // Initial IP value
   WORD e_cs; // Initial (relative) CS value
   WORD e_lfalc; // File address of relocation table
   WORD e_ovno; // Overlay number
   WORD e_oemid; // OEM identifier (for e_oeminfo)
   WORD e_oeminfo; // OEM information; e_oemid specific
   WORd e_res2[10]; // Reserved words
   LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;



그러나, 이것은 DOS 운영체제를 위해 제공하는 정보들이기 때문에 현재 Windows 환경에서는 필요치 않다. 대다수의 필드들은 무시되며 e_magic 및 e_lfanew 필드만이 유용한 정보가 된다. e_magic 필드는 IMAGE_DOS_HEADER의 시작을 나타내는 것으로 항상 'MZ'라는 문자열로 시작을 한다. 그리고 e_lfanew 필드는 IMAGE_DOS_HEADER 다음에 나오는 헤더 파일의 오프셋 값을 가지고 있다. 그 외의 필드들은 Windows에서 파일을 실행시켰을 때 이용되지 않는다. 다음 [그림 3-1]은 일반적인 실행 파일의 IMAGE_DOS_HEADER의 값이다.

 

[그림 3-1] IMAGE_DOS_HEADER 정보



이 위의 값들은 만일 DOS Mode 실행 시 Dos Stub 실행을 목적으로 지정된 값들로 이 헤더 뒤에 따르는 문자열 "This program cannot be run in DOS mode"을 출력해 주기 위해 지정된 값들이다. Dos가 아닌 Windows 모드에서 실행 시켰을 경우에는 이용되지 않는다. 그러나 몇몇 악성코드를 분석하다 보면 실제 사용되지 않는 필드 값들이 악성코드에 의해 사용 되고 있는 흥미로운 사실을 발견할 수 있다. 다음 [그림 3-2]는 악성코드에서 많이 사용되는 실행압축 방식의 하나인 Upack의 IMAGE_DOS_HEADER 부분이다.

 

[그림 3-2] Upack의 IMAGE_DOS_HEADER 정보



앞서 설명한 IMAGE_DOS_HEADER 와는 확연한 차이를 보인다. e_magic 및 e_lfanew 필드 이외의 값들이 'KERNEL32.DLL', 'LoadLibraryA', 'GetProcAddress' 등의 문자열로 채워진 것을 확인 할 수 있다. 이들 문자열들은 동적링크를 위한 함수 호출에 필요한 문자열들로 실행압축인 Upack에서 필요한 라이브러리 함수(DLL)들을 가져다 쓰는 루틴을 위해 존재한다. 여기서는 범용 실행압축 모듈인 Upack을 통해 실제 이용되지 않는 필드들을 활용하는 사례를 다루었지만, 악성코드들 역시 필요한 정보들의 보관을 위해 IMAGE_DOS_HEADER의 빈 공간을 적절히 이용하고 있다.

3. e_lfanew 필드가 IMAGE_DOS_HEADER 범위 안을 가리키고 있다?

IMAGE_DOS_HEADER의 e_lfanew 필드는 다음에 올 헤더 위치의 파일 오프셋을 가리키고 있다. 즉, 실제 PE 파일의 시작이라고 할 수 있는 IMAGE_NT_HEADER의 시작 오프셋 값을 가지고 있다. 다음 [그림 3-3]은 일반적인 실행 파일의 e_lfanew 필드의 값인 0x0000000E 위치의 값을 보여준다.

 

[그림 3-3] IMAGE_DOS_HEADER의 e_lfanew 정보



위 그림과 같이 일반적인 실행 파일은 다음에 올 헤더 위치의 파일 오프셋을 가리키고 있다. 0x000000E0 위치에 있는 헤더는 IMAGE_NT_HEADER로써 다음과 같은 구조체로 정의가 되어 있다.


typedef struct _IMAGE_NT_HEADERS
{
   DWORD Signature;
   IMAGE_FILE_HEADER FileHeader;
   IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADER32, *PIMAGE_NT_HEADER32;



첫 필드인 Signature는 PE 헤더의 시작을 알리는 값으로 'PE'라는 문자열이 들어가게 된다. 그렇기 때문에 IMAGE_DOS_HEADER의 e_lfanew 필드가 가리키는 위치를 가보면 'PE\0\0'과 같은 문자열이 나타나게 된다. IMAGE_FILE_HEADER와 IMAGE_OPTIONAL_HEADER32는 PE 구조체의 세부적인 값들로 지정된 구조체이다. 그럼 e_lfanew 필드는 언제나 자신보다 뒤에 있는 영역을 가리키고 있는 것일까? 일반적인 실행파일의 경우 그렇다고 할 수 있다. 그러나 몇몇 악성코드들에서는 다음 [그림 3-4]와 같은 모습이 보여지기도 한다.

 
[그림 3-4] 악성코드에서의 특이한 e_lfanew 정보의 예



일반적인 실행파일과 다르게 IMAGE_DOS_HEADER의 e_lfanew 필드가 자신보다 앞의 영역을 가리키고 있다. 이와 같이 다소 일반적이지 않은 형태로 구조체가 정의 되더라도 각 필드에 맞는 값들이 채워진다면 운영체제가 파일을 읽어 들여서 실행하는 것에는 문제가 되지 않는다. 특히 IMAGE_DOS_HEADER의 첫 필드와 마지막 필드를 제외하고는 현재 이용되지 않기 때문에 전혀 문제가 되지 않는다. 단, IMAGE_NT_HEADER 구조체의 시작이 앞으로 이동하게 되면 e_lfanew 필드 값이 PE 헤더 값과 겹치게 되므로, e_lfanew 필드 값과 매칭되는 PE 헤더의 값(BaseOfData)에 문제가 없어야만 한다.

다음 [그림 3-5]를 살펴보면 IMAGE_DOS_HEADER의 e_lfanew 필드는 파일 오프셋으로 0x00000010을 가리키고 있다. 그리고 IMAGE_NT_HEADER의 앞부분에 위치한 이용되지 않는 IMAGE_DOS_HEADER의 영역에 'KERNEL32.DLL' 문자열을 숨겨 두었다. IMAGE_NT_HEADER를 보면 PE 헤더의 시작 뒤를 보면 파일 오프셋 0x0000002A 부분부터 'LoadLibraryA' 문자열이 있는 것을 확인할 수 있다.

 

[그림 3-5] 악성코드가 삽입한 IMAGE_DOS_HEADER 내의 문자열 정보



해당 영역은 IMAGE_NT_HEADER에 멤버로 등록된 구조체인 IMAGE_OPTIONAL_HEADER의 영역으로 각 대칭되는 필드는 다음 그림 [3-6]과 같다.

 

[그림 3-6] IMAGE_OPTIONAL_HEADER 정보



Major Linker Version, Minor Linker Version, SizeOfCode, SizeOfInitializedData, SizeOfUninitializedData의 값이 된다. Major 및 Minor Linker Version 필드는 실행파일을 만든 링커의 버전을 담고 있으며, SizeOfCode 필드는 모든 코드 섹션들의 사이즈를 합한 크기이다. SizeOfInitializedData 필드는 코드 섹션을 제외한 초기화된 데이터 섹션의 전체 크기를 나타내며, SizeOfUninitializedData 필드는 초기화되지 않은 데이터 섹션의 바이트 수를 나타낸다. 일반적인 실행파일의 경우 이러한 값들은 거의 쓰이지 않는다.

4. 다른 컴퓨터의 정상적인 실행 파일과 내 컴퓨터의 정상적인 실행 파일이 다르다?

일반적으로 같은 플랫폼에서의 같은 실행 파일의 경우 직접 링커를 통해 실행 파일을 만들지 않는 이상(즉, 일반적으로 제공되는 어플리케이션을 설치했을 경우에는) 같은 속성을 가지게 된다. 그러나 이 실행 파일 헤더의 특정 몇몇 부분이 다를 경우에는 실행 파일이 감염 되었음을 의심해 볼 수 있다. [그림 3-7]과 [그림 3-8]에서 진단명 Tufic.C에 감염 전후의 explorer.exe 파일의 차이를 확인해볼 수 있다.

 

[그림 3-7] Tufic.C 감염 전 Number of Sections 정보



 

 

[그림 3-8] Tufic.C 감염 후 Number of Sections 정보



[그림 3-7]은 Tufic.C 감염되기 이전의 IMAGE_NT_HEADER에 속한 IMAGE_FILE_HEADER의 값이며, [그림 3-8]은 감염된 이후의 모습이다. 박스안의 NumberOfSections 필드를 살펴보면 감염된 이후에 값이 1만큼 증가한 것을 확인 할 수 있다. Tufic.C는 원본 파일의 맨 뒤에 바이러스를 첨가 시키는 Appending 바이러스의 한 유형으로 바이러스를 추가할 부분을 만들기 위해 섹션의 수를 하나 증가시켜 놓은 것이다.

 

[그림 3-9] Tufic.C 감염후 추가된 섹션 정보



[그림 3-9]는 추가된 섹션 헤더의 모습이다. 여기에서 상당히 많은 악성코드 정보를 얻을 수 있으며, 이러한 정보는 악성코드 분석에 큰 도움을 준다. 우선 섹션의 이름은 .adate임을 알 수 있다. Tufic.C와 같이 바이러스 감염 시 섹션 이름을 특정 문자열로 주게되면, 바이러스 감염여부에 대한 기초적인 판단 정보가 되기도 한다. VirtualSize는 감염 파일이 운영체제에 의해 로드 되었을 때, 이 섹션의 이미지상의 크기를 나타내어 준다.

 

이것은 바이러스 코드의 길이를 짐작 할 수 있다. RVA는 실행 파일이 운영체제에 의해 로드 되었을 때 메모리상의 위치 정보이며, 감염 파일을 분석할 때 바이러스 코드를 확인 할 수 있다. SizeOfRawData는 파일상에서 섹션의 크기에 대해 알 수 있으며, 치료할 때 사용 된다. PointerToRawData는 파일상의 섹션 위치를 알 수 있으며 이것 또한 치료할 때 사용 된다. Characteristics은 해당 섹션의 속성을 나타내는 플래그의 집합이다.

 

바이러스가 생성한 섹션의 플래그는 IMAGE_SCN_CNT_CODE, _EXECUTE, _READ, _WRITE 등의 속성이 지정되어 있으며 각각은 '코드를 포함하고 있다', '실행 가능한 섹션이다', '읽기 가능 섹션이다', '쓰기 가능 섹션이다'는 것을 의미한다. [그림 3-10]에서와 같이 섹션 정보 이외에 수정되는 것이 더 있는지 확인을 해 보면 중간 부분에서와 같이 몇 개의 필드가 수정된 것을 확인할 수 있다.

 

[그림 3-10] Tufic.C 감염 전/후의 IMAGE_OPTIONAL_HEADER 정보



IMAGE_NT_HEADER에 속해 있는 IMAGE_OPTIONAL_HEADER32 구조체의 필드 값들이다. 왼쪽이 감염 이전의 explorer.exe 파일이며, 오른쪽이 감염 이후의 explorer.exe 파일이다. 감염 전/후를 비교해 보면, 세 부분이 바뀐 것을 확인 할 수 있는데, 첫 번째 SizeOfCode는 앞서 설명과 같이 모든 코드 섹션의 사이즈를 합한 크기이며 그 크기가 증가한 것을 확인 할 수 있다. 또한 맨 마지막의 SizeOfImage는 운영체제가 이 실행 파일을 로드할 때 확보/예약해야 할 메모리상의 크기를 가리킨다. 이 또한 SizeOfCode가 증가한 크기만큼 증가한 것을 확인 할 수 있다.

중요한 것은 AddressOfEntryPoint 인데 이 부분은 운영체제가 실행 파일을 로드 했을 때, 이 실행 파일의 코드 시작점을 나타낸다. Tufic.C에 감염 되었을 경우에는 이와 같이 코드 진입점이 변경되며 변경된 코드 진입점은 바이러스의 시작 주소를 가리키게 된다. 이것은 바이러스 분석에 결정적 자료가 된다.(물론 이처럼 AddressOfEntryPoint 필드를 변경하지 않고, 실제 시작 코드를 변경하는 바이러스들도 존재한다.) 변경된 원본 AddressOfEntryPoint는 바이러스가 나중에 원본 파일을 정상 실행 시키기 위해서 임의의 위치에 백업을 해 둔다.

5. Import Address Table 정보를 이용한 악성코드 분석

DLL(Dynamic Link Library)은 서로 다른 프로그램이 실행된 후 공통적으로 사용하는 함수들을 하나로 묶어두고 이를 필요로 할 때 동적으로 링크하여 사용하는 공유 라이브러리 파일이다. 각각의 프로그램에서 하나의 함수를 공통적으로 사용하게 되므로 메모리가 절약되고, 주 프로그램과 함께 컴파일 되는 정적 링크에 비해 상대적으로 작은 사이즈를 가지는 잇점이 있다.

 

최대한 간략하게 만들어야 하는 악성코드 역시 필요한 함수를 동적 링크 하는 것이 일반적이므로, 함께 링크되는 DLL 함수 정보를 살펴보면 악성코드가 의도하는 목적을 유추할 수 있다. PE 구조에서 해당 정보를 보관하고 있는 Import Address Table을 찾아가 보기로 한다.

1) PE 파일의 시작(e_magic)에서 0x3C 만큼 이동하면 IMAGE_NT_HEADERS 시작 오프셋 값(e_lfanew)을 찾을 수 있다

2) IMAGE_NT_HEADERS 시작 오프셋(Signature)에서 0x80 만큼 이동한 지점이 Import Directory RVA 구조체 정보이다. 구조체 엔트리에 대한 의미는 WinNT.H 파일에 다음과 같이 정의되어 있다.


typedef struct _IMAGE_DATA_DIRECTORY {
   DWORD VirtualAddress;
   DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;



 

 

[그림 3-11] Import Directory RVA 정보



3) Import Directory RVA에 대한 FileOffset으로 이동하면 동적으로 링크하는 DLL 파일들의 구조체(IMAGE_IMPORT_DESCRIPTOR)정보를 확인할 수 있다. 다음은 Win-Trojan/Backdoor.40960.B 에서 동적으로 링크하는 DLL 및 그에 따른 함수(Import Address Table) 정보를 일부 발췌한 것이며, 이를 통해 해당 악성코드는 URL 접속 및 정보유출, 불특정 파일 다운로드 등의 증상을 유추해 볼 수 있겠다.

● urlmon.dll: Internet Explorer의 구성요소로, 웹사이트에서 반환된 URL과 정보를 처리한다
● WS2_32.dll: Windows Socket API, WSAStartup 함수를 통해 DLL을 로딩하여 사용한다

 

[그림 3-12] IMAGE_IMPORT_DESCRIPTOR 정보 (Win-Trojan/Backdoor.40960.B)



 

 

[그림 3-13] Import Address Table 정보 (Win-Trojan/Backdoor.40960.B)



6. Import Address Table 정보를 은닉한 악성코드 분석

레지스트리 및 파일 I/O, 소켓연결, 프로세스 관련 함수 호출은 악성코드들이 즐겨 사용하며,이를 통하여 안티바이러스 프로그램 및 분석가들도 악성 여부를 판단하는 기초 자료로 사용한다.


이러한 특성을 인지한 악성코드 제작자들은 치료백신 업데이트 및 분석 지연을 목적으로 DLL 정보 및 호출함수 정보를 은닉하게 되는데 실행압축(Packer), 암호화(Encrypt)등을 사용하는 것이 일반적이며, 일부 악성코드는 PE 구조체의 Import Address Table 정보를 메인루틴 에서 생성하기도 한다.

1) GetProcAddress() 함수주소 획득


호출 함수들의 주소정보 획득을 위해서 DLL 핸들, 함수명을 인자값으로 GetProcAddress()를 호출하며, GetProcAddress() 함수에 대한 주소정보는 메인루틴 상의 kernel32.dll 에서 획득한다. 다음은 Win32/MaDang 에서 GetProcAddress() 함수 주소를 획득하는 과정을 보여준다.


 

 

[그림 3-14] GetProcAddress 함수에 대한 주소획득 과정 (Win32/MaDang)



2) 함수명 추출을 위한 서브루틴 호출


LoadLibrary()를 통해 DLL핸들이 확보되면, GetProcAddress() 인자값으로 사용할 함수명을 추출해야 하는데, Win32/MaDang 에서는 다소 변칙적인 함수호출 과정을 이용한다. 일반적인 함수 호출은 서브루틴이 호출되면 복귀주소(Return Address)가 스택에 쌓이고(PUSH) 서브루틴 종료시 복귀주소를 스택에서 꺼내어(POP) 메인루틴으로 복귀하는 과정을 밟는다. 다음은 Win32/MaDang 에서 위와 같은 함수호출 과정으로 스택에 쌓인 값이 복귀주소를 가리키지 않고 함수명을 가리킬 수 있음을 보여준다.


 

 

[그림 3-15] 함수명 추출을 위한 서브루틴 호출 과정 (Win32/MaDang)



3) 호출 함수들의 주소정보 획득


다소 변칙적인 함수호출 과정을 통해 필요한 함수명이 스택에 저장되었고 DLL핸들 또한 확보되었다. 최종적으로 GetProcAddress() 함수 호출을 통해 메인루틴에서 사용될 함수들의 주소정보를 모두 획득하면 Import Address Table 이 완성된다.


 

 

[그림 3-16] 호출 함수들의 주소정보 획득 과정 (Win32/MaDang)



7. PE(Portable Executable) 구조체와 악성 코드 분석가의 조우

악성코드를 분석하다 보면 이 이외에도 많은 흥미로운 것들을 발견할 수 있다. 그러나 앞에서 언급을 했듯이 모든 악성코드들이 이러한 특성들을 가지고 있는 것은 아니며, 정상 파일은 이러한 특성을 가지지 않는 것은 아니다. 다만 악성코드들에서 발생 빈도가 더 높을 뿐이다.

 

이러한 정보들은 악성코드의 악의적인 동작들과는 관계가 없는 것들은 많지만, 악성코드를 분석하고 악성/정상을 판단하는 것에 결정적인 힌트가 되어 주기도 한다. 또한 각 악성코드만의 PE 헤더 특성을 악성코드 진단에 이용할 수 있으며 바이러스와 같은 경우에는 정상파일을 감염시켜 PE 헤더의 내용을 바꾸는 동작을 수행할 가능성이 높기 때문에 치료함수를 제작하기 위해서 이러한 PE 헤더의 바뀐 부분에 대한 파악이 중요하다. 그러므로 PE 구조체와 악성코드 분석은 떼어낼래야 뗄 수 없는 관계이다.

 

 

[출처] 안철수연구소 ASEC

Posted by skensita