참고 도서 : [리버싱 핵심원리 : 악성 코드 분석가의 리버싱 이야기]
저자 : 이승원
출판사 : 인사이트
참고 사이트 :
- [Microsoft] IMAGE_NT_HEADERS structure
- [Microsoft] IMAGE_FILE_HEADER structure
- [Microsoft] IMAGE_OPTIONAL_HEADER structure
- [Microsoft] IMAGE_DATA_DIRECTORY structure
- [Microsoft] IMAGE_SECTION_HEADER structure
PE Structure (1) PE 포맷 에서 확인한 바와 같이 PE의 기본 구조는 아래와 같다. 여기서 PE 헤더에 대해 알아보도록 합니다. 실제 구조의 확인은 PE Structure (1)에서 참조했던 notepad.exe (10.0버전)를 이용할 것이다.
DOS Header
PE 헤더의 제일 앞부분에는 기존 DOS EXE Header를 확장시킨 IMAGE_DOS_HEADER 구조체가 존재한다. DOS 헤더는 DOS와의 호환을 위해 유지되고 있는 헤더이다. IMAGE_DOS_HEADER 구조체의 크기는 40이며 아래와 같은 구조체를 지닌다.
typedef struct _IMAGE_DOS_HEADER { WORD e_magic; // DOS signature : 4D5A (ASCII 값, "MZ") WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; LONG e_lfanew; // offset to NT header, NT header의 옵셋을 표시 (파일에 따라 가변적인 값을 가짐) } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
|
모든 PE 파일의 시작 부분 (e_magic)에 DOS signature ("MZ")가 존재하고, e_lfanew 값이 가리키는 위치에 NT header 구조체가 존재해야 한다.
실제 IMAGE_DOS_HEADER 구조체를 확인해보자. (참고로, WORD는 32 bits이며 아래는 숫자 하나가 16진수를 나타낸다. 따라서 4개의 숫자가 하나의 WORD를 나타낸다. LONG의 경우 64 bits이므로 8개의 숫자가 LONG을 나타낸다.)
시작 값이 4D5A ("MZ")이며, e_lfanew 값은 000000F0임을 확인할 수 있다. (Intel 계열 CPU는 자료를 역순으로 저장하는 Little Endian 표기법을 사용한다.)
DOS Stub
DOS Stub의 존재 여부는 옵션이며 크기도 일정하지 않다. DOS Stub이 없어도 파일 실행에는 문제가 없다. DOS Stub은 코드와 데이터의 혼합으로 이루어져 있다. 아래는 DOS Stub에 해당한다.
파일 옵셋 40~4D까지의 영역 (붉은색 상자로 체크해 놓은 부분)은 16비트 어셈블리 명령어이다. Windows OS에서는 이 명령어가 실행되지 않는다. 만약 이 파일을 DOS 환경에서 실행하거나, DOS용 디버거를 이용해 실행하면 이 코드가 실행된다.
현재 notepad.exe는 Windows10의 실행파일을 이용하였다. 앞서 살펴본 DOS Stub을 WindowsXP에서는 cmd 명령어를 통해 쉽게 디버깅이 가능하다. 직접 디버깅을 해보기 위해 WindowsXP의 notepad.exe (5.1버전)를 살펴보고자 한다. 아래는 WindowsXP notepad.exe (5.1버전)의 DOS Stub 구조이다. 현재 살펴보고 있는 실행 파일과 어셈블리 명령어 부분은 동일한 값을 가지고 있는 것을 확인할 수 있다.
이를 명령어 "debug C:\WINDOWS\system32\notepad.exe"를 이용해 16비트 어셈블리 명령어를 확인해보도록 한다.
각각의 코드는 아래와 같은 의미를 지닌다.
1A37:0000 |
0E |
PUSH |
CS |
||
1A37:0001 |
1F |
POP |
DS |
|
|
1A37:0002 |
BA0E00 |
MOV |
DX, 000E |
; DX = 0E : "This program cannot be run in DOS mode" |
|
1A37:0005 |
B409 |
MOV |
AH, 09 |
|
|
1A37:0007 |
CD21 |
INT |
21 |
; AH = 09 : WriteString() |
|
1A37:0009 |
B8014C |
MOV |
AX, 4C01 |
|
|
1A37:000C |
CD21 |
INT |
21 |
; AX = 4C01 : Exit() |
코드는 화면에 문자열 "This program cannot be run in DOS mode"를 출력하고 종료해 버린다. 즉 notepad.exe 파일은 MS-DOS 호환 모드를 가지고 있음을 확인할 수 있다.
NT Header
먼저, notepad.exe (10.0버전)의 NT header 영역은 아래와 같다. "PE" 문구를 통해 NT header의 시작임을 알 수 있다. 또한 각각 파란색과 초록색으로 나뉜 영역은 IMAGE_NT_HEADERS와 IMAGE_OPTIONAL_HEADER를 의미한다.
NT header 구조체 IMAGE_NT_HEADERS는 아래와 같다.
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; // PE Signature : 50450000 ("PE"00) IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
|
앞서 살펴봤던 첫 멤버는 Signature로 50450000h ("PE"00) 값을 가진다. IMAGE_NT_HEADERS 구조체의 크기는 F8이다.
IMAGE_NT_HEADERS의 멤버인 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 : Machine 넘버는 CPU 별로 고유한 값이다.
- NumberOfSections : PE 파일은 코드, 데이터, 리소스 등이 각각의 섹션에 나뉘어서 저장되는데 이 섹션의 개수를 나타낸다. 반드시 0보다 커야 하며 정의된 섹션의 개수와 실제 섹션의 개수가 동일해야 한다.
- SizeOfOptionalHeader : IMAGE_NT_HEADERS 구조체의 멤버인 IMAGE_OPTIONAL_HEADER 구조체의 크기를 나타낸다.
- Characteristics : 파일의 속성을 나타내는 값이다. 실행이 가능한 형태인지 (executable or not) 혹은 DLL 파일인지 등의 정보들이 bit OR 형식으로 조합된다.
TimeDateStamp 멤버는 파일의 실행에 영향을 미치지 않는다. 해당 파일의 빌드 시간을 나타내는 값이다.
IMAGE_NT_HEADERS의 멤버인 IMAGE_OPTIONAL_HEADER는 PE 헤더 구조체 중에서 가장 크기가 크다. 구조체는 아래와 같다.
typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; 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_HEADER, *PIMAGE_OPTIONAL_HEADER; typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
|
IMAGE_OPTIONAL_HEADER의 멤버 중 중요한 것은 아래와 같다. 이 값들 역시 파일 실행에 필수적이다.
- Magic
- AddressOfEntryPoint : EP (Entry Point)의 RVA (Relative Virtual Address) 값을 가지고 있다. 프로그램에서 최초로 실행되는 코드의 시작 주소로, 매우 중요한 값이다.
- ImageBase : PE 파일이 로딩되는 시작 주소를 나타낸다. PE 로더는 PE 파일을 실행시키기 위해 프로세스를 생성하고 파일을 메모리에 로딩한 후 EIP 레지스터 값을 ImageBase+AddressOfEntryPoint 값으로 세팅한다.
EXE, DLL 파일 : user memory 영역인 0~7FFFFFFF 범위에 로딩, 일반적으로 EXE는 00400000이고 DLL은 10000000
SYS 파일 : kernel memory 영역인 80000000~FFFFFFFF 범위에 로딩
- SectionAlignment : 메모리에서 섹션의 최소단위를 나타낸다.
- FileAlignment : 파일에서 섹션의 최소단위를 나타낸다.
파일/메모리의 섹션 크기는 반드시 각각 FileAlignment/SectionAlignment의 배수가 되어야 한다.
- SizeOfImage : PE 파일이 메모리에 로딩되었을 때 가상 메모리에서 PE Image가 차지하는 크기이다. 일반적으로 파일의 크기와 메모리에 로딩된 크기는 다르다.
- SizeOfHeader : PE 헤더의 전체 크기이다. FileAlignment의 배수여야 한다. 파일 시작에서 SizeOfHeader 옵셋만큼 떨어진 위치에 첫 번째 섹션이 위치한다.
- Subsystem : 시스템 드라이버 파일인지 실행 파일인지 구분할 수 있다.
- NumberOfRvaAndSizes : IMAGE_OPTIONAL_HEADER 구조체의 메버인 DataDirectory 배열의 개수를 나타낸다.
- DataDirectory : IMAGE_DATA_DIRECTORY 구조체의 배열이다. 배열의 각 항목마다 정의된 값을 가진다.
Section Header
섹션 헤더는 섹션 테이블이라고도 한다. 섹션은 비슷한 성격의 자료들을 모아둔 곳으로 각각의 섹션의 속성을 기술한 것을 섹션 헤더라고 한다. 즉, IMAGE_SECTION_HEADER 구조체는 섹션에 대한 정보를 관리하는 구조체이다.
code/data/resource 마다 각각의 특성, 접근 권한 등을 다르게 설정해야 한다.
종류 | 액세스 권한 |
code |
실행, 읽기 권한 |
data |
비실행, 읽기, 쓰기 권한 |
resource |
비실행, 읽기 |
IMAGE_SECTION_HEADER 구조체는 아래와 같다.
#define IMAGE_SIZEOF_SHORT_NAME typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; // 메모리에서 섹션이 차지하는 크기 } Misc; DWORD VirtualAddress; // 메모리에서 섹션의 시작 주소 (RVA) DWORD SizeOfRawData; // 파일에서 섹션이 차지하는 크기 DWORD PointerToRawData; // 파일에서 섹션의 시작 위치 DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; // 섹션의 속성 (bit OR) } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; |
'Study > 리버싱' 카테고리의 다른 글
Registry (0) | 2016.07.06 |
---|---|
PE Structure (3) DLL (0) | 2016.06.29 |
PE Structure (1) PE 포맷 (0) | 2016.06.19 |
Stack (0) | 2016.06.12 |
Register (1) | 2016.06.08 |
댓글