PEView – 실행 파일 구조, PE Header 분석

실행 파일 구조, PE Header 분석

과거 리버스 엔지니어링은 프로그래머의 전유물처럼 여겨졌는데, 지금은 바이러스와 핵툴과 같은 악성 프로그램들의 끊임없는 등장으로, 보안의 핵심/종착지와 같은 중요한 분야가 되었다. 여기서는 일반인과 보안 담당자에게 필요한 분석 기술중 가장 기본이 되는 PE Header라는 구조체에서 이해하여 파일에서 필요한 정보들을 얻고, 리버스 엔지니어링의 재미를 보여주고자 한다. J

1. 프로그램 분석의 기초

PE 파일 포맷

먼저 PE HEADER 이해를 돕기 위해 PE 파일 포맷이 무엇인지 이해할 필요가 있다.

PE 파일 포맷은 Windows 운영체제에서 정의한 파일 포맷으로 Portable Executable의 약자이다.

PE 파일 포맷의 구조만 가지고도 책한 권을 쓸 수 있는 분량이기에 여기서는 필요한 정보만 다르며, 더 깊이 있게 공부해보고 싶으신 분은 MSDN과 관련 책들을 참고하기를 미리 알리는 바이다.

우리가 흔히 윈도우에서 접하는 확장자중 PE 파일 포맷으로 작성된 파일은 EXE, SCR, DLL, OCX, SYS, OBJ가 있으며, 직접 실행 가능한 EXE를 제외한 나머지 파일 종류중 OBJ를 제외하고는 간접적(서비스, 디버깅, 레지스트리등)으로 실행이 가능한 파일들이다.

다음 그림은 오래전 MSDN에 공개되었던 PE 파일의 일반적은 구조다.

[그림] : PE Header 구조 출처 : MSDN

다른곳에서 제공하는 그림과 약간 모양이 다를수 있지만, 필수 Header들은 동일하다. 위 왼쪽의 그림은 파일상태의 그림으로 필수로 있어야 하는 부분이고, 각 구조체를 구분하는 방법은 각 끝부분은 Null Padding을 통해 구분할 수 있도록 되어있다.

그럼 HEX 에디터로 내용을 더욱 자세히 살펴보자.

그리고 [그림] PE Header 구조의 오른쪽 그림의 메모리에 로딩되었을 때 위치가 변경되는 것은 파일에서는 offset으로 메모리는VA(Virtual Address)로 표현하는데, Section 크기와 위치를 PE Header에 로드될 메모리 위치를 지정하게 되는데, 최소 기본 단위에 맞춰 메모리에서 위치가 변경되게 된다.

[그림] PE Header 구조의 많은 구조체를 크게 DOS Header 그리고, NT Header(위 MSDN 그림상의 PE Header이나 구분을 위해 앞으로 NT Header로 표기), Sections Table을 합쳐서 보통 PE Header라고 부르며 그 위 Section들을 PE Body라고 한다.

그리고 여기서는 PE 구조체를 사람이 구분하기 쉽게 하기 위해, PEView와 PEBrowse를 통해 그림 및 설명을 추가할 것이니, 독자 여러분도 이번 기회에 한번 다루어 보는것도 좋겠다.

이렇게 PE 파일 포맷의 전체적인 구조를 간략히 살펴보았다, 이제부터 여기서 다루어야할 PE Header 그리고 중요한 몇몇 DataDirectory(IAT, EAT등등)에 대해 보다 자세히 알아보자.

DOS Header

DOS Header를 실제 파일에서 어떻게 작성되는지 이해를 위해 위 그림을 다시 참고 하자.

[그림] HxD를 이용하여 확인한 DOS Header의 HEX값

위 그림의 내용 중 네모 박스 안에 내용들이 DOS Header이다. 그 안에는 DOS Stub가 40h부터 포함되어 있는데, 이는 DOS모드에서 실행시, 안내창 같은 역할을 해주며, 없어도 상관이 없는 부분이다. DOS Stub에서는 DOS환경에서 실행시 동작할 내용을 저 공간에 16Bit로 정의한다. 그리고 DOS Header에서는 밑줄 그은 2개의 멤버가 중요한데, MZ코드(ASCII)는 맴버 e_magic의 값으로써 DOS Signature로 모든 PE 파일의 시작하는 코드이다. d0h부터 16Byte가 NULL Padding인 이유가 e_lfanew값와 관계가 있다. 그리고 구조체의 끝은 000000E0는 DOS Header의 멤버 e_lfanew으로, NT Header의 Offset값 즉 시작위치를 표시한다. 그리고 PE코드(ASCII)는 바로 오늘 관심있게 봐야 할 PE Signature다. PE Header 구조체로 Windows에서 실행에 필요한 정보를 저곳에 담고 있다.

DOS Header를 넘어가기 앞서 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_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;

[내용] IMAGE_DOS_HEADER의 구조체 출처: Microsoft SDK WinNT.h

이 정도까지 DOS Header에 대해서는 알아보고, 크게 중요한 내용은 아니기 때문에 여기서는 더 다루지 않겠다. 그럼 다음인 NT Header로 가보자

NT Header

이제부터 본격적으로 PE Header, 즉 IMAGE_NT_HEADERS에 대해 알아보도록 하자.

그럼 먼저 NT Header의 구조체를 확인해 보자.

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

[내용] IMAGE_NT_HEADER의 구조체 출처: Microsoft SDK WinNT.h

 

NT Header에는 1개의 멤버와 2개의 헤더가 존재하며, 각 멤버/헤더에 대해 계속 확인해 보자.

첫번째 멤버인 1. Signature는 DOS Header의 Signature와 같이, PE(ASCII)코드로 NT Header시작을 알리는 것이다, 그럼 계속 File Header의 멤버의 구조체도 확인해 보도록 하자.

typedef struct _IMAGE_FILE_HEADER {
1 WORD Machine;
2 WORD NumberOfSections;
3 DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
4 WORD SizeOfOptionalHeader;
5 WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

[내용] _IMAGE_FILE_HEADER의 구조체 출처: Microsoft SDK WinNT.h

 

1. Machine은 본 파일이 동작 가능한 머신의 종류를 코드에 담고 있으며, 코드의 내용별 의미는 WinNT.h에 정의되어 있으므로, 아래 내용을 참고하기 바란다.

#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

[내용] IMAGE_FILE_MACHINE의 정의 출처: Microsoft SDK WinNT.h

 

2. NumberOfSection의 현재 PE 파일이 가지고 있는 Section 개수를 의미한다.

3. TimeDateStamp는 파일을 빌드한 시간을 나타내는 값이 된다.

4. SizeOfOptionalHeader는 이 다음에 설명드릴 IMAGE_OPTIONAL_HEADER32 구조체의 크기를 나타내는 값이며, 마지막으로 5. Charcteristics 멤버는 PE 파일 속성에 대한 정보를 가지고 있으며 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_FILE의 정의 출처: Microsoft SDK WinNT.h

 

이해를 돕기 위해 REGEDIT의 실제 코드를 확인해 보자

[그림] PEBrowse를 통해 확인한 File Header 구조체

REGEDIT.EXE의 Characteristics의값인 102h는 0x0100, 0x0002를 표현한 값으로 위 그림과 같이 32bit machine에 실행파일이라는 정보를 얻을 수 있다. 앞에 설명한 Machine의 값인 14ch는 Intel 386 호환 모델이라는 것을 나타낸다.

이렇게 FileHeader를 지나, 이제부터 중요한데, IMAGE_OPTIONAL_HEADER32의 구조체를 살펴 보도록 하자.

typedef
struct _IMAGE_OPTIONAL_HEADER {

//

// Standard fields.

//
1 
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
2 DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
3 DWORD AddressOfEntryPoint;
4 DWORD BaseOfCode;
5 DWORD BaseOfData;

//

// NT additional fields.

//
6 DWORD ImageBase;
7 DWORD SectionAlignment;
8 DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
9 DWORD SizeOfImage;
10 DWORD SizeOfHeaders;
DWORD CheckSum;
11 WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
12 DWORD NumberOfRvaAndSizes;
13
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

[내용] IMAGE_OPTIONAL_HEADER32의 구조체 출처: Microsoft SDK WinNT.h

 

1. Magic는 IMAGE_OPTIONAL_HEADER32(32Bit), IMAGE_OPTIONAL_HEADER64(64Bit)인지를 구분하는 값으로, 32Bit인 경우 10bh, 64Bit인 경우 20bh의 값을 가지게 된다. 2. SizeOfCode는 .text Section의 파일 크기를 나타내는데, 이후 나오는 .text Section의 SizeOfRawData와 같은 값을 가진다.

3. AddressOfEntryPoint는 많이 접하게 되는 값으로, 프로그램 시작점(Entry Point)를 위치하며 상대주소(Relative Virtual Address) 값을 나타낸다. 메모리에서는 로딩이 완료후 ImageBase + AddressOfEntryPoint값을 EIP 레지스터에 대입해 프로그램을 시작하게 된다.

4. BaseOfCode, 5, BaseOfData는 Code와 Data의 시작 주소를 가진다.

6. ImageBase는 메모리 내에 파일이 로딩이 되는 시작 주소를 가진다.

7. SectionAlignment, 8. FileAlignment는 메모리와 파일에서 세션의 최소 단위를 나타내는 값으로 해당 세션 크기는 반드시 SectionAlignment, FileAlignment의 배수로 되어야 하므로 남는 바이트는 NULL로 채워지게 된다.

9. SizeOfImage는 메모리에 로딩될 전체 크기이고, 10. SizeOfHeader는 파일에서 PE Header의 전체 크기를 나타낸다.

11. Subsystem는 본 프로그램의 구동하는 기반 환경을 나타낸 값으로 WinNT.h에 정의되어있다.

#define IMAGE_SUBSYSTEM_UNKNOWN            0 // Unknown subsystem.
#define IMAGE_SUBSYSTEM_NATIVE            1 // Image doesn't require a subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_GUI        2 // Image runs in the Windows GUI subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_CUI        3 // Image runs in the Windows character subsystem.
#define IMAGE_SUBSYSTEM_OS2_CUI            5 // image runs in the OS/2 character subsystem.
#define IMAGE_SUBSYSTEM_POSIX_CUI            7 // image runs in the Posix character subsystem.
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS        8 // image is a native Win9x driver.
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI        9 // Image runs in the Windows CE subsystem.
#define IMAGE_SUBSYSTEM_EFI_APPLICATION        10 //
#define IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER 11 //
#define IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER    12 //
#define IMAGE_SUBSYSTEM_EFI_ROM            13
#define IMAGE_SUBSYSTEM_XBOX            14
#define IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION 16

[내용] Subsystem의 정의 출처 : Microsoft SDK WinNT.h

 

12. NumberOfRvaAndSizes는 DataDirectory 배열의 개수를 표시한다. WinNT.h에 10h(16)으로 정의되어 있지만, 실제로는 PE Header의 값을 참조한다.

13. DataDirectory에 조금 길기 때문에 아래 설명 하겠다.

그럼 여기서 실제 REGEDIT의 Optional Header값을 가지고 확인해보자

[그림] PEBrowse의 Optional Header 정보를 확인한 화면

REGEDIT의 Magic값은 10bh로 IMAGE_OPTIONAL_HEADER32(32bit)임을 나타내고 있다.

SizeOfCode의 크기는 1b600h(112128)이며, Code의 시작 위치는 RVA(상대주소)로 1000+1000000(ImageBase) = 1001000h 이며, 세션과 파일의 기본 단위는 1000h(4096)/200h(512)가 된다.

REGEDIT의 전체 크기는 67d78h(425336)이고, Header의 전체 크기는 400h(1024)로 구성되어 있고, Subsystem은 2h로써 Windows GUI 기반 응용프로그램임을 나타낸다.

DataDirectory 배열의 개수는 10h(16)로 되어 있다.

이제 13. DataDirectory의 IMAGE_DATA_DIRECTORY 구조체를 살펴보자.

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

[내용] IMAGE_DATA_DIRECTORY 구조체 출처 : Microsoft SDK WinNT.h

 

아래 16개의
Directory Entry중 중요한 항목은 아래 굵게 표시했다. 본 4가지 항목은 PE Header에서 필수로 이해가 필요한 핵심부분으로 IAT(Import Address Table), EAT(Export Address Table)과 큰 연관이 있다. IAT, EAT에 대해서는 아래 Section Header을 설명한 이후 다루도록 하겠다. 이외 몇가지 더 있지만 이는 따로 공부해보기 바란다. 미리 간략히 애기하자면, PE 파일이 어떤 라이브러리를 제공하고/필요한지를 정의한 테이블의 위치와 크기가 DataDirectory에 위치한다.

DataDirectory[0] - IMAGE_DIRECTORY_ENTRY_EXPORT
(+0x60) VirtualAddress: 0x00000000
(+0x64) Size: 0x00000000
DataDirectory[1] - IMAGE_DIRECTORY_ENTRY_IMPORT
(+0x68) VirtualAddress: 0x0001A564
(+0x6C) Size: 0x00000154
DataDirectory[2] - IMAGE_DIRECTORY_ENTRY_RESOURCE
(+0x70) VirtualAddress: 0x0005F000
(+0x74) Size: 0x00003488
DataDirectory[3] - IMAGE_DIRECTORY_ENTRY_EXCEPTION
DataDirectory[4] - IMAGE_DIRECTORY_ENTRY_SECURITY
DataDirectory[5] - IMAGE_DIRECTORY_ENTRY_BASERELOC
DataDirectory[6] - IMAGE_DIRECTORY_ENTRY_DEBUG
DataDirectory[7] - IMAGE_DIRECTORY_ENTRY_ARCHITECTURE
DataDirectory[8] - IMAGE_DIRECTORY_ENTRY_GLOBALPTR
DataDirectory[9] - IMAGE_DIRECTORY_ENTRY_TLS
DataDirectory[10] - IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG
DataDirectory[11] - IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
DataDirectory[12] - IMAGE_DIRECTORY_ENTRY_IAT
(+0xC0) VirtualAddress: 0x00001000
(+0xC4) Size: 0x00000580
DataDirectory[13] - IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
DataDirectory[14] - IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR
DataDirectory[15]

[내용] PEBrowse로 확인한 DataDirectory의 구조체

 

Section Header

이렇게 IMAGE_OPTIONAL_HEADER32로 NT Header을 지나, PE Header의 마지막인 Section Header의 구조체를 확인하자.

typedef struct
_IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
1 DWORD VirtualSize;
} Misc;
2 DWORD VirtualAddress;
3 DWORD SizeOfRawData;
4 DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
5 DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

[내용] IMAGE_SECTION_HEADER 구조체 출처 : Microsoft SDK WinNT.h

 

설명을 드리자면,

1. VirtualSize는 메모리에서 해당 세션이 차지하는 크기이고, 2. VirtualAddress는 메모리에서 해당 Section이 시작하는 주소값을 확인할수 있다.

3. SizeOfRawData는 파일에서 세션이 차지하는 크기이며,

4. PointerToRawData는 파일에서의 세션이 시작하는 위치 값이다.

5. Characteristics는 Section의 특징이 나타내는데 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.

[내용] Characteristics의 정의 출처 : Microsoft SDK WinNT.h

 

예를 들어 REGEDIT의 Characteristics값인 6000020h이라면, 20h(코드영역)으로 40000000h(읽기), 20000000h(쓰기)가 가능하다는 의미이다.

아래는 REGEDIT를 Ollydbg로 Section Header의 값을 확인한 결과이다. 보는데 크게 무리가 없을 것은 생각된다.

2E 74 65 78    ASCII ".text"    ; SECTION
7CB40100    DD 0001B47C     ; VirtualSize = 1B47C (111740.)
00100000    DD 00001000    ; VirtualAddress = 1000
00B60100    DD 0001B600    ; SizeOfRawData = 1B600 (112128.)
00040000    DD 00000400    ; PointerToRawData = 400
00000000    DD 00000000    ; PointerToRelocations = 0
00000000    DD 00000000    ; PointerToLineNumbers = 0
0000        DW 0000        ; NumberOfRelocations = 0
0000        DW 0000        ; NumberOfLineNumbers = 0
20000060    DD 60000020    ; Characteristics = CODE|EXECUTE|READ
2E 64 61 74    ASCII ".data"    ; SECTION
F0120400     DD 000412F0    ; VirtualSize = 412F0 (266992.)
00D00100    DD 0001D000    ; VirtualAddress = 1D000
00080400    DD 00040800    ; SizeOfRawData = 40800 (264192.)
00BA0100    DD 0001BA00    ; PointerToRawData = 1BA00
00000000    DD 00000000    ; PointerToRelocations = 0
0000000        DD 00000000     ; PointerToLineNumbers = 0
0000        DW 0000        ; NumberOfRelocations = 0
0000        DW 0000        ; NumberOfLineNumbers = 0
400000C0    DD C0000040    ; Characteristics = INITIALIZED_DATA|READ|WRITE
2E 72 73 72    ASCII ".rsrc"    ; SECTION
88340000    DD 00003488    ; VirtualSize = 3488 (13448.)
00F00500    DD 0005F000    ; VirtualAddress = 5F000
00360000    DD 00003600    ; SizeOfRawData = 3600 (13824.)
00C20500    DD 0005C200    ; PointerToRawData = 5C200
00000000    DD 00000000    ; PointerToRelocations = 0
00000000    DD 00000000    ; PointerToLineNumbers = 0
0000        DW 0000        ; NumberOfRelocations = 0
0000        DW 0000        ; NumberOfLineNumbers = 0
4000004        DD 40000040    ; Characteristics = INITIALIZED_DATA|READ
2E 72 65 6C    ASCII ".reloc"    ; SECTION
801A0000    DD 00001A80    ; VirtualSize = 1A80 (6784.)
00300600    DD 00063000    ; VirtualAddress = 63000
001C0000    DD 00001C00    ; SizeOfRawData = 1C00 (7168.)
00F80500    DD 0005F800    ; PointerToRawData = 5F800
00000000    DD 00000000    ; PointerToRelocations = 0
00000000    DD 00000000    ; PointerToLineNumbers = 0
0000        DW 0000        ; NumberOfRelocations = 0
0000        DW 0000        ; NumberOfLineNumbers = 0
40000042    DD 42000040    ; Characteristics = INITIALIZED_DATA|DISCARDABLE|READ

[내용] Ollydbg로 확인한 Section Header의 구조체

 

이제 앞서 애기하였던 중요한 IAT, EAT에 대해 확인해 보자

IAT(Import Address Table), EAT(Export Address Table)

IAT는 PE Header 이해에 있어 제일 중요한 부분이라고 할 수 있다, 그래서 이 부분은 필수로 이해하여야 한다.

IAT는 프로그램이 필요로 하는 라이브러리의 함수들의 기술한 테이블이다.

Windows 운영체제에서 PE 파일내에 사용되는 함수들을 파일에 포함하지 않고, 여러 프로세스가 공유하도록 설계되었다. 이로써 메모리 낭비를 막을 수 있고, 파일의 크기도 줄일 수 있는데, 쉽게 셜명 하자면,

CreateFile을 하기 위해, 해당 함수를 파일내에 포함하는 것이 아닌, 별도 라이브러리(DLL)화된 파일의 해당 함수 위치를 기억해 놓았다가 필요 인자값들과 함께 해당 함수를 호출하게 된다.

그리하여 DLL(Dynamic Linked Library)라는 개념이 나오게 되었으며, 프로그램을 실행할 떄 IAT에기술된 함수의 위치를 확인하는데, DLL의 함수 위치를 고정하지 않은 이유는 Windows 운영체제 마다 위치가 다를수 있고, DLL이 메모리에 로딩된 위치가 변경될수 있기 때문에, 프로그램 실행시, PE 파일에서 필요로 하는 DLL의 함수들의 주소값들을 조사하여 갱신하고, 프로그램 종료시 주소값을 저장하지 않고, 해제되게 된다.

예로, REGEDIT의 GetStartupInfoA를 호출하는 과정을 보자면, IAT의 특정 주소에 GetStartupInfoA라는 함수의 주소값을 지정되어, 프로그램은 단지 IAT의 주소값을 호출하면 프로그램이 실행시 실행중인 Windows의 해당 함수 위치를 찾아 IAT를 갱신하게 된다.

OLLYDBG를 위해 실제 CALL 명령문을 확인해 보았다. 프로그램 내에서는 단지 특정 IAT의 주소값을 가리키는 것으로 실행되는 것을 확인할 수 있다.

[그림] Ollydbg로 확인한 GetStartupInfoA를 호출하는 주소

EAT(Export Address Table)은 반대로 자신이 라이브러리가 되어, 다른 프로그램들에게 자신의 함수를 제공하게 된다.

그럼 IAT의 사용되는 구조체 IMAGE_IMPORT_DESCRIPTOR와 IMAGE_IMPORT_BY_NAME를 확인해 보자.

typedef
struct _IMAGE_IMPORT_DESCRIPTOR {

union {
DWORD Characteristics;                // 0 for terminating null import descriptor
2 DWORD OriginalFirstThunk;            // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp;                // 0 if not bound,
        // -1 if bound, and real date\time stamp
        // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
        // O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain;         // -1 if no forwarders
1 DWORD Name;
3 DWORD FirstThunk;         // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

[내용] IMAGE_IMPORT_DESCRIPTOR 구조체 출처 : Microsoft SDK WinNT.h

 

IMAGE_IMPORT_DESCRIPTOR 구조체는 다른 이름으로도 불리는데, IMPORT Directory Table이라고는 이름으로도 불리니, 용어에 혼동이 없기를 바란다.

1. Name는 라이브러리(DLL)의 이름값이 들어 있는 주소를 확인 할 수 있습니다.

2. OriginalFirstThunk의 값을 통해 아래 IMAGE_IMPORT_BY_NAME 구조체를 가르켜, 함수 이름 문자열을 확인하고, 각 값들을 얻어오게 되는데, IMAGE_IMPORT_BY_NAME의 구조체 정보는 아래와 같다.

typedef
struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

[내용] IMAGE_IMPORT_BY_NAME 구조체 출처 : Microsoft SDK WinNT.h

 

3. FirstThunk는 라이브러리의 주소 값이 들어있는 주소를 가리키게 되며 NULL을 만날 때까지 해당 라이브러리 안의 함수 주소 값을 가져오게 된다

다음 그림을 통해 이해를 조금 쉽게 했으면 한다.

OriginalFirstThunk를 통해 함수 이름을 확인하고, FirstThunk를 통해
해당 함수를 주소를 확인하다는것이 핵심이 있다.

[그림] PEview로 확인한 kernel32.dll의 IAT 정보

여기서 EAT에 대해서는 자세히 언급하지는 않지만, 기본적으로 IAT와 크게 다르지 않으므로 IAT를 이해 했다면, EAT를 이해하는건 크게 어렵지 않을 것이다.

그럼 EAT의 구조체를 확인해 보자.

typedef
struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
1 DWORD Name;
DWORD Base;
2 DWORD NumberOfFunctions;
3 DWORD NumberOfNames;
4 DWORD AddressOfFunctions;            // RVA from base of image
5 DWORD AddressOfNames;                // RVA from base of image
6 DWORD AddressOfNameOrdinals;            // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

[내용] IMAGE_EXPORT_DIRECTORY 구조체 출처 : Microsoft SDK WinNT.h

 

먼저 1. Name을 통해 해당 DLL의 이름을 얻는다, 그리고 2. NumberOfFunctions을 통해 제공하는 함수의 개수를 확인하고, 3. NumberOfNames를 통해 이름있는 함수의 개수를 확인한다.

그후, 4. AddressOfFunctions를 통해 함수의 주소 위치 값을 RVA(상대주소)로 확인하고, 해당 주소의 이름은 5. AddressOfNames를 통해 확인하게 된다. 마지막 6. AddressOfNameOrdinals은 제공함수들의 번호로 마지막 번호가 NumberOfNames와 같다.

그럼 예제를 통해 확인해 보자

AddressOfNames를 통해 함수 이름을 확인하고, AddressOfFunctions를 통해
해당 함수를 주소를 확인하다는것이 핵심이 있다.

[그림] PEview로 확인한 kernel32.dll의 EAT 정보

이제 PE Header를 통해 프로그램의 전체적인 정보를 확인할 수 있게 되었다. 이로써 프로그램 분석을 위한 기본적인 준비가 완료 된 것이다. 여기서 확인한 내용들은 차후 파일 분석이 큰 도움이 될 것이다. 이제 실제 프로그램을 통해, 지금 배운 내용을 사용해 보도록 하자. J

2. 프로그램 분석의 준비 및 실전 분석

분석 준비

프로그램 분석에 있어 먼저 분석에 필요 프로그램을 준비할 필요가 있다

설명하자면, 프로그램 코드 이해를 위해 만든 컴파일러에 대해 알아야 하고, PE Header 구조를 파악하고, 디버거를 통해 파일 내용을 분석하여야 할 것 이다.

그리고 추가로, 시스템 환경 변화를 감시하기 위해 포랜식 도구들에 대해서도 확인하여야 한다

그럼 다음 내용에 기입된 툴들을 준비해 보자

1. 컴파일러 확인 – PEiD

http://www.peid.info/

2. PE Header 구조 확인 – PEview, PEBrowse

http://www.magma.ca/~wjr/

http://www.smidgeonsoft.com/

3. 디버거 – IDA, Ollydbg

http://www.datarescue.com/

http://www.ollydbg.de/

4. 시스템 레지스티리, 파일의 변화 확인 – ProcMon

http://technet.microsoft.com/en-us/sysinternals/bb896645

[내용] 실전 분석에 필요한 도구들

이외에도 여러 도구들이 있다. 여기서 언급한 툴 외에도 많은 도구들이 사용되지만, 여기서는 일반적으로 사용되는 툴로 예제 분석에 필요한 도구들만 언급하였으니, 그외 툴들에 대해서는 본인들이 찾아보기를 권장한다.그리고, 대다수 툴들이 설치가 필요 없을 툴들이며, 설치 역시 어렵지 않다.

위 툴들을 준비하였다면, 이제 본격적으로 분석을 진행하도록 하자

실전 분석

분석전 미리 애기하자면, 실제 프로그램 분석에 있어서는 PE Header만으로는 부족하다,

리버스 엔지니어링 기술을 사전에 이해하여야 실전 프로그램 분석을 원활히 진행할 수 있다.

하지만 여기서는 해당 부분에 대해서 자세히 다루지 않을 예정이니, 이점을 이해해주기를 바란다.

여기서 분석할 프로그램은 ntsec.exe로, NT 보안 설정용으로 만든 간단한 프로그램이다.

따라서 초기 분석용도로 사용하기에 적합하다.

이 프로그램은 4가지 보안 설정 기능을 제공인데, 다음과 같다.

TCP SYN Protect

Root Share Disable

Login Banner Enable

Last Login User Diable

이 프로그램의 실행 구조를 우리가 배운 PE Header를 이용하여 역분석을 진행해보자.

앞서 다른 PE Header 값들에 대해서도 확인하여야 하지만, 그렇게 진행하기에는 지면을 많이 차지하므로, 개인의 몫으로 남기고, 여기서는 바로 해당 프로그램 분석을 위해 ntsec.exe가 사용하는 API를 확인하기 위해, PEiD 확인후, PEBrowse로 해당 파일을 열어, IAT를 확인해 보도록 하자.

[그림] PEiD로 확인한 ntsec의 PE Header 구조

PEiD를 사용하는 이유는 프로그램에 사용된 언어를 사전에 확인하여, 언어별 코드의 차이점을 사전에 인지하고자 함에 있다. 그외의 유용한 정보들도 많이 얻을수 있으므로, 파일분석시에 적용 활용하도록 하자.

그럼 이제 IAT를 확인해 보자.

Structure for IAT
ADVAPI32.DLL
(+0x0000) 0x00027396 (356, RegDeleteValueA)
(+0x0004) 0x00027366 (347, RegCloseKey)
(+0x0008) 0x00027374 (390, RegSetValueExA)
(+0x000C) 0x00027386 (370, RegOpenKeyExA)
KERNEL32.DLL
(+0x003C) 0x00027348 (282, GetLastError)
(+0x0040) 0x000276F0 (636, SetStdHandle)
(+0x0044) 0x000276DE (618, SetFilePointer)
(+0x0048) 0x000273D2 (202, GetCommandLineA)
(+0x004C) 0x000273E4 (372, GetVersion)
(+0x0050) 0x000273F2 (125, ExitProcess)
(+0x0054) 0x00027400 (81, DebugBreak)
(+0x0058) 0x0002740E (338, GetStdHandle)
(+0x005C) 0x0002741E (735, WriteFile)
(+0x0060) 0x0002742A (429, InterlockedDecrement)
(+0x0064) 0x00027442 (501, OutputDebugStringA)
(+0x0068) 0x00027458 (318, GetProcAddress)
(+0x006C) 0x0002746A (450, LoadLibraryA)
(+0x0070) 0x0002747A (432, InterlockedIncrement)
(+0x0074) 0x00027492 (292, GetModuleFileNameA)
(+0x0078) 0x000274A8 (670, TerminateProcess)
(+0x007C) 0x000274BC (247, GetCurrentProcess)
(+0x0080) 0x000274D0 (685, UnhandledExceptionFilter)
(+0x0084) 0x000274EC (178, FreeEnvironmentStringsA)
(+0x0088) 0x00027506 (179, FreeEnvironmentStringsW)
(+0x008C) 0x00027520 (722, WideCharToMultiByte)
(+0x0090) 0x00027536 (262, GetEnvironmentStrings)
(+0x0094) 0x0002754E (264, GetEnvironmentStringsW)
(+0x0098) 0x00027568 (621, SetHandleCount)
(+0x009C) 0x0002757A (277, GetFileType)
(+0x00A0) 0x00027588 (336, GetStartupInfoA)
(+0x00A4) 0x0002759A (413, HeapDestroy)
(+0x00A8) 0x000275A8 (411, HeapCreate)
(+0x00AC) 0x000275B6 (415, HeapFree)
(+0x00B0) 0x000275C2 (703, VirtualFree)
(+0x00B4) 0x000275D0 (559, RtlUnwind)
(+0x00B8) 0x000275DC (440, IsBadWritePtr)
(+0x00BC) 0x000275EC (437, IsBadReadPtr)
(+0x00C0) 0x000275FC (423, HeapValidate)
(+0x00C4) 0x0002760C (484, MultiByteToWideChar)
(+0x00C8) 0x00027622 (577, SetConsoleCtrlHandler)
(+0x00CC) 0x0002763A (447, LCMapStringA)
(+0x00D0) 0x0002764A (448, LCMapStringW)
(+0x00D4) 0x0002765A (191, GetCPInfo)
(+0x00D8) 0x00027666 (185, GetACP)
(+0x00DC) 0x00027670 (305, GetOEMCP)
(+0x00E0) 0x0002767C (409, HeapAlloc)
(+0x00E4) 0x00027688 (699, VirtualAlloc)
(+0x00E8) 0x00027698 (418, HeapReAlloc)
(+0x00EC) 0x000276A6 (170, FlushFileBuffers)
(+0x00F0) 0x000276BA (339, GetStringTypeA)
(+0x00F4) 0x000276CC (342, GetStringTypeW)
(+0x00F8) 0x00027700 (27, CloseHandle)
SHELL32.DLL
(+0x014C) 0x000273B6 (114, ShellExecuteA)

[내용] PEBrowse로 확인한 ntsec의 IAT 구조체

 

많은 API가 사용되는 것을 확인할 수 있다.

이중 KERNEL32.DLL은 프로그램 실행에 기본적으로 필요한 함수들이 많다,

이를 전부 분석하기란 매우 큰 작업이 될 것이다. 이를 위해 분석할 API를 정해야 하는데,

여기서는 ADVAPI32.DLL, SHELL32.DLL을 먼저 확인 하도록 하겠다.

ADVAPI32.DLL

(+0x0000) 0x00027396 (356, RegDeleteValueA)

(+0x0004) 0x00027366 (347, RegCloseKey)

(+0x0008) 0x00027374 (390, RegSetValueExA)

(+0x000C) 0x00027386 (370, RegOpenKeyExA)

SHELL32.DLL

(+0x014C) 0x000273B6 (114, ShellExecuteA)

위 API들을 MSDN에서 확인 하면 해당 API를 이해하는데 큰 도움이 된다.

(+0x0000) 0x00027396 (356, RegDeleteValueA)
Removes a named value from the specified registry key. Note that value names are not case sensitive.
LONG WINAPI RegDeleteValue(
__in HKEY hKey,
__in_opt LPCTSTR lpValueName
);
(+0x0008) 0x00027374 (390, RegSetValueExA)
Sets the data and type of a specified value under a registry key.
LONG WINAPI RegSetValueEx(
__in HKEY hKey,
__in_opt LPCTSTR lpValueName,
__reserved DWORD Reserved,
__in DWORD dwType,
__in_opt const BYTE *lpData,
__in DWORD cbData
);
(+0x014C) 0x000273B6 (114, ShellExecuteA)
Performs an operation on a specified file.
HINSTANCE ShellExecute(
__in_opt HWND hwnd,
__in_opt LPCTSTR lpOperation,
__in LPCTSTR lpFile,
__in_opt LPCTSTR lpParameters,
__in_opt LPCTSTR lpDirectory,
__in INT nShowCmd
);

[내용] RegDeleteValueA RegSetValueExA ShellExecuteA API 함수 출처 : MSDN

 

물론 이름만으로도 용도를 확인이 가늠할 정도이다.

RegDeleteValueA 는 registry의 값을 지우는 것이고, RegSetValueExA은 registry의 값을 설정하는 API 함수 이다. 그리고 ShellExecuteA는 CLI에서 처럼 특정 파일을 실행할 수 있는 API이다.

따라서 ntsec.exe은 레지스트리의 값을 바꾸고, 특정 명령을 실행하는 기능이 들어있다고 추측할 수 있다. 이렇게 추측으로 분석을 완료 할 수도 있지만, 확실히 어떤 값을 쓰고, 실행하는지 확인할 필요가 있다. 그리고 이렇게 확인한 정보는 디버깅시 등대와 같은 역할을 해줄 것이다.

이제 본격적으로 파일을 분석을 해보자, IDA, Ollydbg등을 통해 정적 혹은 동적 분석을 진행하고 Procmon등을 통한 실행상태를 확인하면서 분석을 진행하면 된다.

여기서는 많은 API중 RegSetValueExA, ShellExecuteA에 브레이크포인트를 걸어놓고, Ollydbg로 프로그램을 구동해 보도록 하자.

[그림] Ollydbg로 RegSetvalueExA, ShellExecuteA에 브레이크포인트 설정

위 그림과 같이 설정후 프로그램을 실행하면 아래와 같은 내용에서 브레이크포인트가 걸리며, 실행내용을 확인할 수 있다.

004010BB . 6A 04 PUSH 4                ; /BufSize = 4
004010BD . 8D4D FC LEA ECX,DWORD PTR SS:[EBP-4]        ; |
004010C0 . 51 PUSH ECX                ; |Buffer
004010C1 . 6A 04 PUSH 4                ; |ValueType = REG_DWORD
004010C3 . 6A 00 PUSH 0                ; |Reserved = 0
004010C5 . 68 1C214200 PUSH 42211C            ; |ValueName = "SynAttackProtect"
004010CA . 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-8]        ; |
004010CD . 52 PUSH EDX                ; |hKey
004010CE . FF15 D4714200 CALL DWORD PTR DS:[4271D4]    ; \RegSetValueExA
0040120F . 6A 04 PUSH 4                ; /BufSize = 4
00401211 . 8D4D FC LEA ECX,DWORD PTR SS:[EBP-4]        ; |
00401214 . 51 PUSH ECX                ; |Buffer
00401215 . 6A 04 PUSH 4                ; |ValueType = REG_DWORD
00401217 . 6A 00 PUSH 0                ; |Reserved = 0
00401219 . 68 C4224200 PUSH 4222C4            ; |ValueName = "AutoShareServer"
0040121E . 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-8]        ; |
00401221 . 52 PUSH EDX                ; |hKey
00401222 . FF15 D4714200 CALL DWORD PTR DS:[4271D4]    ; \RegSetValueExA
00401234 . 6A 04 PUSH 4                ; /BufSize = 4
00401236 . 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4]        ; |
00401239 . 50 PUSH EAX                ; |Buffer
0040123A . 6A 04 PUSH 4                ; |ValueType = REG_DWORD
0040123C . 6A 00 PUSH 0                ; |Reserved = 0
0040123E . 68 B4224200 PUSH 4222B4            ; |ValueName = "AutoShareWrk"
00401243 . 8B4D F8 MOV ECX,DWORD PTR SS:[EBP-8]        ; |
00401246 . 51 PUSH ECX                ; |hKey
00401247 . FF15 D4714200 CALL DWORD PTR DS:[4271D4]    ; \RegSetValueExA
00401278 ~ 0040132C
00401263 . 6A 05 PUSH 5                ; /IsShown = 5
00401265 . 6A 00 PUSH 0                ; |DefDir = NULL
00401267 . 68 98224200 PUSH 422298            ; |Parameters = "share Admin$ /delete"
0040126C . 68 94224200 PUSH 422294            ; |FileName = "net"
00401271 . 68 8C224200 PUSH 42228C            ; |Operation = "open"
00401276 . 6A 00 PUSH 0                ; |hWnd = NULL
00401278 . FF15 18734200 CALL DWORD PTR DS:[427318]        ; \ShellExecuteA
.
.
.
00401317 . 6A 05 PUSH 5                ; /IsShown = 5
00401319 . 6A 00 PUSH 0                ; |DefDir = NULL
0040131B . 68 14224200 PUSH 422214            ; |Parameters = "share %g$ /delete"
00401320 . 68 94224200 PUSH 422294            ; |FileName = "net"
00401325 . 68 8C224200 PUSH 42228C            ; |Operation = "open"
0040132A . 6A 00 PUSH 0                ; |hWnd = NULL
0040132C . FF15 18734200 CALL DWORD PTR DS:[427318]        ; \ShellExecuteA
0040149B . 6A 04 PUSH 4                ; /BufSize = 4
0040149D . 8D4D FC LEA ECX,DWORD PTR SS:[EBP-4]        ; |
004014A0 . 51 PUSH ECX                ; |Buffer
004014A1 . 6A 04 PUSH 4                ; |ValueType = REG_DWORD
004014A3 . 6A 00 PUSH 0                ; |Reserved = 0
004014A5 . 68 D0234200 PUSH 4223D0            ; |ValueName = "DontDisplayLastUserName"
004014AA . 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-8]        ; |
004014AD . 52 PUSH EDX                ; |hKey
004014AE . FF15 D4714200 CALL DWORD PTR DS:[4271D4]    ; \RegSetValueExA
00401652 . 68 80000000 PUSH 80                ; /BufSize = 80 (128.)
00401657 . 8D45 80 LEA EAX,DWORD PTR SS:[EBP-80]        ; |
0040165A . 50 PUSH EAX                ; |Buffer
0040165B . 6A 01 PUSH 1                ; |ValueType = REG_SZ
0040165D . 6A 00 PUSH 0                ; |Reserved = 0
0040165F . 68 20254200 PUSH 422520            ; |ValueName = "LegalNoticeCaption"
00401664 . 8B8D FCFEFFFF MOV ECX,DWORD PTR SS:[EBP-104]    ; |
0040166A . 51 PUSH ECX                ; |hKey
0040166B . FF15 D4714200 CALL DWORD PTR DS:[4271D4]    ; \RegSetValueExA
00401680 . 68 80000000 PUSH 80                ; /BufSize = 80 (128.)
00401685 . 8D95 00FFFFFF LEA EDX,DWORD PTR SS:[EBP-100]    ; |
0040168B . 52 PUSH EDX                ; |Buffer
0040168C . 6A 01 PUSH 1                ; |ValueType = REG_SZ
0040168E . 6A 00 PUSH 0                ; |Reserved = 0
00401690 . 68 0C254200 PUSH 42250C            ; |ValueName = "LegalNoticeText"
00401695 . 8B85 FCFEFFFF MOV EAX,DWORD PTR SS:[EBP-104]    ; |
0040169B . 50 PUSH EAX                ; |hKey
0040169C . FF15 D4714200 CALL DWORD PTR DS:[4271D4]    ; \RegSetValueExA

[내용] Ollydbg로 확인한 RegSetvalueExA, ShellExecuteA의 진행 내용

 

이제 실제 실행하는 코드들을 확인하여, 프로그램이 어느 레지스트리를 쓰는지 어떤 실행 명령을 사용하는지 위 내용과 같이 파악하였다. 앞서 설명한 ntsec의 동작 내용과 크게 다르지 않고, 위 내용에 대해서는 쉽게 이해할 수 있을 거라 생각되어, 추가로 설명하지는 않겠다.

여기에 각 API별 정확한 분석을 하고자 한다면, 앞서 확인한 MSDN을 통해 확인한 API 함수에 기재된 멤버들과, 위 내용에 나타난 각 값들을 비교하면 이해하는데 큰 도움이 될 것 이다.

그럼 이제 IDA를 통해 다른 코드들에 대해서도 확인해 보도록 하자, IDA를 통한 분석은 Ollydbg 이후 꼭 진행이 필요한 것은 아니다.

여기서는 소개를 위해 이렇게 진행하는 것이고 실제는 IDA 분석만 하여도 되고, Ollydbg로만 분석하여도 충분하다는 점을 알려드린다.

그럼 IDA를 통해 ntsec.exe를 열어서, 우리가 분석하지 않은 부분을 확인해 보자

[그림] IDA로 확인한 RegDeleteValueA의 흐름도

위 내용은 RegDeleteValueA를 IDA로 확인한 내용인데, 그림과 같이 IDA의 큰 장점은 전체적인 프로그램의 내용을 흐름도 처럼 확인이 가능하다는 것이다. 이를 통해 프로그램의 처리 흐름을 쉽게 이해할 수 있을 것이다.

이제 마지막으로 ProcMon등을 통해 시스템의 변화를 감시하여 기록하면, 실제적인 환경 변화에대해 분석을 할 수 있다.

[그림] Process Monitor를 통해 확인한 Registry 모니터링 값

위 그림과 같이 ntsec.exe 프로그램이 접근하고, 변경한 Registry 내용을 모니터링 하므로써, 실제 디버깅 한 내용대로 작동됨을 검증할 수 있고, 코드상 확인하지 못한 변화되는 내용을 확인할 수 있게 된다.

이외에도 여러 도구를 통해 다각도로 해당 프로그램을 분석할 수 있지만, 여기서는 이만 줄이고

그 부분들은 각자의 몫으로 남기도록 하겠다.

마치며

앞으로 역분석에 있어 바이러스 분석은 가상화 머신 에서 네트워크를 차단하고 분석을 하기를 권한다. IDA, Ollydbg에서 분석을 진행하는 것도, 프로그램이 실행이 되므로 현재 운영중인 운영체제에 영향주기 때문에 바이러스를 분석을 한다고 실제 머신 에서 분석을 한다면, 아주 위험한 상황이 연출될 수 있다.

그리고 앞으로 어셈블리, 패킹, 후킹과 같은 언어와 기술들의 이해하고 여러 프로그램 경험하여 숙달하여야 할 것이다.

이리 저리 내용이 길어졌지만, 리버스 엔지니어링에서는 분석의 흐름을 이해하는 게 매우 중요하다. 그래서 PE Header를 먼저 분석하여, 분석의 흐름을 잡을 수 있는 큰 단서를 발견할 수 있다는 것은, 모래밭에서 바늘을 찾은 것과 같이 중요하다.

예전 읽었던 책 중에 인상 깊은 구문이 있었는데, “고전을 배워라.”였다. 요즘 나온 기술들은 기존 기술들을 흡수하면서 파생되어, 원리를 이해하는 게 더욱 힘들게 되어있다. 하지만 기초가 되는 기술들은 탄탄히 배워놓는다면, 그에 파생된 응용기술들을 쉽게 이해하고, 자신의 원하는 대로 응용 있을 것이다. 여기서 리버스 엔지니어링을 시작하고자 하시는 분이 있다면, 기본기를 잘 다지기 바란다. 그리고, 포기하지 말고, 많이 경험해 보길 권하며, 글을 여기서 마치겠다.

Facebook Comments

5 comments on “PEView – 실행 파일 구조, PE Header 분석”

  1. Eunwoo Reply

    얼마 전 “구조를 알아야 개발이 보인다” 저서를 구매한 독자입니다!
    책의 내용이 정말 깔끔하게 정리되어있고 중요한 부분이 잘 나와있어서 정말 열심히 공부중입니다!

    현재 윈도우 파트의 서브시스템을 공부하다가 PE Header에 대해서 더 알고싶어서 들어오게 되었습니다
    그런데 역시 예상대로 제 기초로서는 아직 어렵네요 ..ㅠㅠ

    꼭 5년후에 제가 만족할 만한 전문가가 되어있기를 빌면서!
    책을 잘 읽고있다고 말씀드리고 싶어 댓글을 남겼습니다 !

    • allmnet Reply

      네 감사합니다. 🙂 공부하시다 보면 충분히 넘어서실 수 있고, PE Header는 윈도우에서 기초로 사용되는 중요한 기본기이니, 꼭 이해하세요, 이미 시작하였으니 50%는 넘으신 겁니다. 화이팅!

  2. student Reply

    정말로 감사합니다. 잘 되지도 않는 영어 보면서 하고 있었는데 한글로 설명해주시니 너무나 이해하기 쉽네요 정말로 감사합니다.

Leave A Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.