PE 헤더의 각 내용은 WinNT.h 파일에서 구조체 형태로 찾을 수 있다.
프로그램 빌드시 각 PE 필드 값을 세팅할 수 있는 옵션이 제공되지만, 이미 생성된 PE는 EditBin 도구를 이용해 변경할 수 있다.
IMAGE_DOS_HEADER
모든 윈도우 PE(exe, dll, ocx, cpl, sys, .net 애플리케이션)파일은 MZ로 시작한다.
MZ(e_magic)를 포함한 40byte 크기이며 맨 마지막 4byte에는 PE의 시작을 알리는 오프셋(e_lfanew)값이 적혀있다. 이후 도스 스텁코드가 위치하여 도스환경에서도 실행될 수 있도록 코드가 존재한다. 지금은 문자열만 출력하고 종료한다.
- WinNT.h
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ. Mark Zbikowski
- usage (e_magic)
PIMAGE_DOS_HEADER pdh = (PIMAGE_DOS_HEADER)m_pImgView;
if (pdh->e_magic != IMAGE_DOS_SIGNATURE)
throw _T("윈도우 실행 파일 포맷이 아닙니다");
- usage2 (e_lfanew)
#define GET_NT_OFFSET(ib) (PIMAGE_DOS_HEADER(ib)->e_lfanew) // e_lfanew값(NT_HEADER 오프셋) 가져오기
#define GET_NT_HDRPTR(ib) ((PBYTE)(ib) + PIMAGE_DOS_HEADER(ib)->e_lfanew) // NT_HEADER의 VA 가져오기
IMAGE_NT_HEADERS
32bit와 64bit의 차이는 OptionalHeader 에서 나눠진다.
#define IMAGE_NT_SIGNATURE 0x00004550
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature; // PE\x0\x0
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_NT_HEADERS32 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
- IMAGE_FILE_HEADER
#define IMAGE_SIZEOF_FILE_HEADER 20
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;
Machine : CPU ID 시그니쳐. I386(32bit)과 AMD64(64bit)는 호환되지만, IA-64는 인텔에서 독자적으로 개발한 Itanium으로 IA-64 플랫폼 위에 설치된 윈도우에서만 동작한다.
* I386(0x014c), AMD64(0x8664), IA-64(0x0200)
NumberOfSections : PE 파일 내 섹션 수. IMAGE_SECTION_HEADER 구조체 배열의 엔트리 수
TimeDateStamp : obj 파일이면 컴파일러가, exe나 dll 같은 PE 파일이면 링커가 해당 파일을 만들어낸 시간 1970.01.01 09:00(GMT) 부터 파일이 만들어진 시점까지의 시간을 초단위로 표현
PointerToSymbolTable : 심볼 테이블의 파일 오프셋
NumberOfSymbolTable : 위 필드가 가리키는 심볼테이블 내 심볼 수. 컴파일러에 의해 생성되는 obj파일이나 링크시 /DEBUGTYPE:COFF 옵션을 준 PE 파일에서만 사용된다. 이제는 새로운 디버그 포맷이 있기 때문에 크게 의미가 없다.
SizeOfOptionalHeader : IMAGE_OPTIONAL_HEADER구조체의 바이트 수. obj 파일인 경우 0, PE 파일인 경우에는 32bit 시스템은 224(0xE0)바이트, 64bit 시스템은 240(0xF0) 바이트이다. 이 필드를 이용하여 IMAGE_DATA_DIRECTORY, IMAGE_SECTION_HEADER 테이블의 위치를 구할 수 있음
Characteristics : PE 파일에 대한 특정 정보를 나타내는 플래그. 프로젝트 링크 설정을 통해 세팅할 수 있음
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001
기준 재배치 섹션이 존재하지 않을때 세팅
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002
obj나 lib가 아닌 PE 파일(exe, dll, sys)이 맞는경우 세팅
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020
2G 이상의 가상주소 번지를 제어할 수 있도록 세팅 링크시 /LARGEADDRESSAWARE 옵션 세팅
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080
#define IMAGE_FILE_32BIT_MACHINE 0x0100
32비트 워드 머신을 필요로 하는 PE인 경우 세팅
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200
디버그정보가 이 파일내에 존재하지 않고 별도의 외부 파일에 존재하는 경우 세팅
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400
PE 이미지가 CD 등 이동 가능한 장치에 존재할때 고정디스크상 스왑 파일로 복사해서 실행
#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800
PE 이미지가 네트워크상에 존재할때 고정디스크상 스왑 파일로 복사해서 실행
#define IMAGE_FILE_SYSTEM 0x1000
#define IMAGE_FILE_DLL 0x2000
dll 파일인 경우 세팅. 실행가능한 PE 파일이지만 독자적으로 실행될 수 없음
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000
단일프로세서가 장착된 머신에서만 실행가능한 경우 세팅. 보통 디바이스 드라이버에서 설정
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000
- IMAGE_OPTIONAL_HEADER
이름은 OPTIONAL 이지만 실제로는 FileHeader보다 더 많은 정보를 담고있다.
ImageBase와 SizeOf... 는 주소를 담아야 하기때문에 32bit시스템은 DWORD로 충분하지만 64bit시스템에선 ULONGLONG을 사용한다.
32bit에 있는 4byte짜리 구조체가 하나 빠지고, ImageBase가 4byte 길어졌기 때문에 SizeOf... 필드 이전까지 두 구조체의 각 필드 위치는 거의 같다고 봐도 무방하다.
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
typedef struct _IMAGE_OPTIONAL_HEADER64 {
...
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
typedef struct _IMAGE_OPTIONAL_HEADER {
// 표준필드
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData; // 64bit에는 존재하지 않음
// NT 부가 필드
DWORD ImageBase; // 64bit : ULONGLONG
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; // 64bit : ULONGLONG
DWORD SizeOfStackCommit; // 64bit : ULONGLONG
DWORD SizeOfHeapReserve; // 64bit : ULONGLONG
DWORD SizeOfHeapCommit; // 64bit : ULONGLONG
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
Magic : PE가 32bit인지, 64bit인지만 확인하려면 IMAGE_NT_HEADER의 Machine 보다 이 값을 이용하는게 더 확실하다.
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b //32bit
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b //64bit
#define IMAGE_ROM_OPTIONAL_HDR_MAGIC 0x107 //ROM 이미지의 경우
MajorLinkerVersion, MajorLinkerVersion : 링커의 메이저, 마이너 버전을 각각 나타냄
SizeOfCode : 모든 코드섹션(IMAGE_SCN_CNT_CODE 속성을 가진 섹션)의 크기를 합한 값. 일반적으로는 .text라는 코드섹션이 하나지만, NTDLL.dll 과 같이 둘 이상인 경우도 있다. 각 코드섹션 SectionHeader의 VirtualSize 필드값을 FileAlignment 필드 값으로 라운드업 처리한 값을 합한 값
SizeOfInitializedData : 초기화된 데이터 섹션의 크기를 합한 값. .data, .rdata, .idata, .rsrc 등 IMAGE_SCN_CNT_INITIALIZED_DATA 속성을 가진 섹션
SizeOfUninitializedData : 초기화되지 않은 데이터 섹션(.bss, .textbss 등)의 크기를 합한 값. 일반적으로 링커는 초기화되지 않은 데이터를 일반 데이터 섹션에 병합시켜 보통은 0으로 세팅된다.
AddressOfEntryPoint : PE가 로드된 후 실행할 주소에 대한 RVA 값이 저장된 필드. 이 값은 코드섹션의 특정 번지를 가리키며 exe의 경우 프로세스의 메인스레드가 최초로 실행하는 코드라고 보면 된다.
* 윈도우 exe : WinMainCRTStartup / 콘솔 exe : minaCRTStartup / dll : _DllMainCRTStartup
inline DWORD PEPlus::GetAddressOfEntryPoint(PBYTE pImgBase){
PIMAGE_NT_HEADERS pnh = (PIMAGE_NT_HEADERS)GET_NT_HDRPTR(pImgBase);
if (Is32bitPE(pImgBase))
return ((PIMAGE_OPTIONAL_HEADER32)&pnh->OptionalHeader)->AddressOfEntryPoint;
else
return ((PIMAGE_OPTIONAL_HEADER64)&pnh->OptionalHeader)->AddressOfEntryPoint;
}
BaseOfCode : 첫번째 코드 섹션이 시작되는 RVA. exe의 경우 코드섹션은 PE헤더 이후, 데이터섹션 이전에 오는데 보통 exe의 경우 이 값은 0x1000이다. 그래서 exe를 디버거에 올리면 메인함수 가상주소는 0x00401000 값이 된다.
BaseOfData : 32bit PE에만 존재하는데, 데이터 섹션의 첫 바이트 RVA 값이며 이 값은 링커버전에 따라 다르다
ImageBase : RVA 값에 대한 기준주소가 되는 필드. 주소값이기 때문에 32bit는 DWORD, 64bit는 ULONGLONG 크기다. 특별한 설정이 없다면 디폴트 주소로 설정된다.
SectionAlignment : PE 파일이 메모리에 매핑될때 각 섹션의 시작 주소는 SectionAlignment 필드에 지정된 값의 배수가 되도록 해야된다. 이 값은 FileAlignment 값 보다 크거나 같아야 한다. 인텔기반 윈도우의 디폴트값은 4096kb. 이값이 메모리 페이지값보다 작은 경우에는 파일 오프셋과 RVA값이 동일한 값을 갖는다.
FileAlignment : PE 파일 내에서 섹션의 정렬 단위. 디폴트 값은 512kb 이다
MajorOperatingSystemVersion, MinorOperatingSystemVersion : PE를 실행하는데 필요한 운영체제의 최소버전. 높은버전의 API를 사용하지 않았다면 낮은버전의 OS도 호환된다.
MajorImageVersion, MinorImageVersion : 컴파일한 exe, dll에 대한 사용자가 생각하는 버전.
MajorSubsystemVersion, MinorSubsystemVersion : PE를 실행하는데 필요한 서브시스템의 최소버전.
Win32VersionValue : VC++6.0 까지는 예약필드였다가 7.0부터 바뀌었지만 거의 사용하지않고 0으로 설정된다.
SizeOfImage : 로더가 메모리에 PE를 로드할때 예약해야할 충분한 크기를 가리킨다. SectionAlignment 값의 배수가 되어야하여 헤더를 포함하여 메모리에 로드될 수 있는 섹션의 전체 크기를 합한 값이기 때문에 PE파일의 크기보다 크다.
SizeOfImage = 각 섹션 엔트리 VirtualSize의 SectionAlignment 라운드업의 합 + SizeOfHeaders의 S.A 라운드업 크기
SizeOfHeaders : PE파일의 전체 헤더 크기. e_lfanew(PE헤더의 시작 위치이므로 PE헤더시작 이전까지의 크기와 같다) + 4byte(PE\x0\x0) + size of IMAGE_FILE_HEADER + size of IMAGE_OPTIONAL_HEADER + size of IMAGE_SECTION_HEADER 값이며 FileAlignment 필드 값으로 라운드업 되기 때문에 실제 헤더크기보다 크거나 같다
CheckSum : 이미지 체크섬. IMAGEHELP.DLL의 CheckSum-MappedFile 함수를 통해 얻을 수 있다. 보통은 0으로 세팅되지만 SignTool.exe로 사이닝을 하게되면 이 필드의 값이 자동으로 세팅된다.
Subsystem : 윈도우 아키텍처는 커널모드와 사용자모드로 나뉘는데, 사용자모드에 서브시스템이라는 컴포넌트가 존재한다. 윈도우서브시스템(콘솔, GUI), OS/2 서브시스템, POSIX 서브시스템 등이 존재하고, 이 필드 값에 따라 메인함수의 선언이 달라지기 때문에 맞춰줘야한다.
#define IMAGE_SUBSYSTEM_UNKNOWN 0
#define IMAGE_SUBSYSTEM_NATIVE 1 // DriverEntry
#define IMAGE_SUBSYSTEM_WINDOWS_GUI 2 // WinMain
#define IMAGE_SUBSYSTEM_WINDOWS_CUI 3 // main
#define IMAGE_SUBSYSTEM_OS2_CUI 5
#define IMAGE_SUBSYSTEM_POSIX_CUI 7
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS 8
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9
DllCharacteristics : XP부터 사용되는 필드이며 2000까지는 0으로 세팅되었다.
#define IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA 0x0020
// 높은 엔트로피의 64bit ASLR을 사용. 32bit에선 의미없다.
#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040
// ImageBase 필드에 지정된 값과 다르게 재배치될 수 있다.
// 반대로 재배치 될때 이 값이 설정되지 않으면 로드에 실패하게된다.
#define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080
// PE가 로드될때 코드사이닝과 상관없이 무결성 체크를 수행하는 플래그
#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100
// 실행방지 메모리영역을 지정하는 DEP(Data Execution Prevention)기술과 호환됨을 의미하는 플래그
#define IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200
// 격리 정보를 매니페스트에 지정되지 않도록해서 격리를 인지해도 격리되지 않도록 함
#define IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400
// 해당 PE가 SEH(try~catch)를 사용하지 않는다는것을 의미
#define IMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800
// Bind.exe툴을 통한 이미지 바인딩 처리를 이 플래그가 설정된 PE에 대해서는 수행할 수 없도록함
#define IMAGE_DLLCHARACTERISTICS_APPCONTAINER 0x1000
// 윈도우 스토어에서 설치하는 메트로앱인경우 설정됨. 일반 PE가 이 플래그를 설정하면 실행이 안된다
#define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000
// WDM 모델을 사용하는 디바이스 드라이버 PE 파일임을 의미함
#define IMAGE_DLLCHARACTERISTICS_GUARD_CF 0x4000
// CFG(Control Flow Guard) 보안 기술을 사용하는지를 나타냄
#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0x8000
// 터미널 서버가 터미널 서비스를 인식못하는 앱을 로드시킨경우 호환코드를 담은 DLL을 함께 로드하도록 세팅
SizeOfStackReserve, SizeOfStackCommit, SizeOfHeapReserve, SizeOfHeapCommit
프로세스는 가상 주소 공간에 자신만의 힙과 스레드를 위한 스택을 별도로 갖기 때문에 프로세스 생성 시 메인스레드를 위한 디폴트 스택과 프로세스를 위한 디폴트 힙을 할당해주는데, 이 필드에서 힙과 스택의 예약크기와 확정크기를 지정한다. 디폴트값은 예약크기 0x100000(1M), 확정크기 0x1000(4K) 이다
LoaderFlags : 디버깅 지원과 관련이 있어보이지만 0으로 설정된다.
NumberOvRvaAndSizes : IMAGE_DATA_DIRECTORY 구조체 배열의 엔트리 개수를 의미함. 값은 0x00000010 고정이다.
IMAGE_DATA_DIRECTORY
이 구조체 배열은 16개의 엔트리로 이뤄져 있으며 각 엔트리마다 의미를 가지고있다. 마지막 16번째 엔트리(15인덱스)는 배열의 끝을 의미하기 위해 0으로 채워진다. 해당 엔트리의 의미와 관련된 섹션이 PE에 존재하지 않을경우에도 0으로 설정된다.
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
typedef struct _IMAGE_DATA_DIRECTORY{
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0
// DLL이 내보내는 함수/변수에 대한 정보(Export 섹션의 정보)를 담은 IMAGE_EXPORT_DIRECTORY 구조체의 RVA, size
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1
// EXE나 DLL이 Import한 다른 DLL,함수에 대한 정보를 담은 IMAGE_IMPORT_DESCRIPT 구조체의 RVA, size (.idata 내부)
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2
// 리소스 섹션을 설정하는 IMAGE_RESOURCE_DIRECTORY의 RVA, size 값이며 (.rsrc 내부에 위치)
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3
// 런타임 함수 테이블 IMAGE_RUNTIME_FUNCTION_ENTRY 구조체의 rva, size (.pdata)
// 64bit 아키텍쳐에서는 이 디렉터리에 PE에 정의된 모든 함수정보를 담는다
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4
// PE가 코드사이닝된 경우 공인인증서의 정보를 담고있는 디렉터리. WIN_CERTIFICATE 구조체의 RVA, size
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5
// 기준재배치 정보에대한 RVA, size. (.reloc)
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6
// IMAGE_DEBUG_DIRECTORY의 RVA, size. (.debug)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7
// 아키텍쳐 정보가 담긴 IMAGE_ARCHITECTURE_HEADER의 RVA, size. x86, IA-64, AMD64에서는 사용되지 않음
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8
// RVA는 글로벌 포인터를 가리키고 size는 0으로 고정된다. x86, AMD64에선 사용되지 않음.
#define IMAGE_DIRECTORY_ENTRY_TLS 9
// Thread Local Storage의 제어에 필요한 정보 블록의 시작 RVA, size.
// __declspec는 지시어를 통해 TLS변수가 선언되면 .tls 섹션이 생성되는데, 보통은 .rdata 섹션을 가리킴
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10
// IMAGE_LOAD_CONFIG_DIRECTORY 구조체의 RVA, size.
// 이 구조체의 정보는 윈도우 버전별로 다르며, 이미지가 로드된 후 사용할 정보들을 담는다.
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11
// DLL 바인딩과 관련된 IMAGE_BOUND_IMPORT_DESCRIPTOR 구조체의 배열 RVA, size.
#define IMAGE_DIRECTORY_ENTRY_IAT 12
// 가져온 DLL함수에 대한 포인터를 담고있는 Import Address Table 의 RVA, size
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13
// 지연로드와 관련된 DelayImp.h의 ImgDelayDescr 구조체 RVA, size
// 지연로드 : DLL에서 내보낸 함수가 코드상에서 최초로 호출되기 전까지 DLL을 로드하지 않는기술
// 지연로드는 윈도우 시스템이 해주는것이 아니라 링커나 런타임 라이브러리에 의해 구현된다.
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14
// .NET 기반 PE를 위한 엔트리이다. .NET 최상위 정보(IMAGE_COR20_HEADER)에 대한 시작번지
//15 인덱스는 0으로 세팅
IMAGE_SECTION_HEADER
이 구조체는 엔트리당 40byte이며 엔트리 수는 IMAGE_FILE_HEADER의 NumberOfSections 필드에 세팅된다.
이 헤더에서는 해당되는 섹션의 시작위치와 크기, 속성을 담고있다.
#define IMAGE_SIZEOF_SHORT_NAME 8
#define IMAGE_SIZEOF_SECTION_HEADER 40
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;
DWORD NumberOfRelocations;
DWORD NumberOfLinenumbers;
DWORD Characteristics;
}
Name : 섹션의 아스키 이름(.text, .rdata, .idata 등)을 나타내며, NULL바이트를 제외하고 최대 8byte까지이다. 링커가 8byte이후의 문자열은 자른 후 필드를 세팅한다.
VirtualSize (PhysicalAddress) : 메모리에 로드됐을때 라운드업 처리가 없는 섹션의 바이트 단위 크기를 의미한다. 하지만 섹션의 시작주소는 SectionAlignment의 배수가 되어야하기 때문에 이 섹션을 위해 메모리에 할당된 실제 크기는 라운드업 처리된다. 그렇기 때문에 Section 사이사이에 NULL 바이트의 공간이 존재한다. PhysicalAddress는 OBJ 파일에서만 사용했었는데 지금은 OBJ파일에서도 0으로 세팅되기 때문에 VirtualSize의 의미만 가지고 있다고 보면 된다.
VirtualAddress : PE가 로드될 때 해당 섹션이 매핑될 시작 번지에 대한 RVA를 담고 있다. SectionAlignment의 배수값
SizeOfRawData : 디스크상의 PE 파일에서 섹션내 초기화된 데이터가 차지하는 바이트수. FileAlignment의 배수 바이트 단위 크기이며 초기화되지 않은 데이터로 구성된 섹션(.bss, textbss)은 이 값이 0이다. 이 값은 라운드업 처리되었기 때문에 보통 Section의 실제 사이즈인 VirtualSize가 작지만, .data섹션 같은 경우엔 0으로 초기화된 전역 데이터가 많기 때문에 낭비를 줄이기 위해 디스크를 차지하지 않고있다가 메모리에 로드될땐 VirtualSize만큼 확보해둔 뒤 0으로 초기화한다.
PointerToRawData : 디스크상의 PE 파일에서 섹션이 시작되는 파일오프셋. FileAlignment 필드의 배수. SizeOfRawData와 마찬가지로 초기화되지 않은 데이터가 저장되는 섹션이라면 0으로 세팅된다.
PointerToRelocations, NumberOfRelocations : 재배치 파일 정보인 IMAGE_RELOCATION 구조체에 대한 오프셋, 원소개수를 의미한다. OBJ파일에서만 사용되고, PE 에서는 0으로 세팅된다.
PointerToLinenumbers, NumberOfLinenumbers : COFF 줄번호와 관련있는 필드. 보통 0으로 설정됨
Characteristics : 해당 섹션의 속성을 나타내는 플래그. IMAGE_SCN_... (SectionContains)으로 정의되어있다.
링커는 링크시 필요에 의해 또는 사용자의 지시에 의해 섹션을 병합시킬 수 있다. 이때 병합된 섹션내에 해당 섹션이 포함되어있는지 판단할 때에도 사용된다.
#define IMAGE_SCN_CNT_CODE 0x00000020
// 섹션이 코드를 포함함. 보통은 .text 섹션에 이 플래그가 설정된다.
#define IMAGE_SCN_CNT_INITALIZED_DATA 0x00000040
// 섹션이 초기화된 데이터를 포함하고 있다. 실행 가능 섹션과 .bss섹션을 제외한 대부분의 섹션에 설정
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080
// 초기화되지 않은 데이터를 포함하고 있는경우 설정
메모리 페이지 속성을 나타내는 플래그
#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000
// 메모리에 로드되고 난 후 필요에 따라 제거될 수 있을때 설정됨. .reloc 섹션이 대표적
#define IMAGE_SCN_MEM_NOT_CACHED 0x04000000
// CPU 캐시에 기록되지 않으며 캐시의 히트 실패에 따른 처리에 영향을 받지 않는다는 의미
#define IMAGE_SCN_MEM_NOT_PAGED 0x08000000
// 항상 RAM 상에 존재하며 페이지파일로 스왑되지 않는다는 의미
// 이 두 섹션은 보통 커널모드에서 작동하는 디바이스 드라이버같은 실행모듈에서 사용된다.
#define IMAGE_SCN_MEM_SHARED 0x10000000
// 공유 가능한 섹션임을 의미. 이 속성이 지정된 DLL을 사용하는 모든 프로세스는 이 섹션의 데이터를 공유할 수 있다.
#define IMAGE_SCN_MEM_EXECUTE 0x20000000
// 실행 가능한 섹션임을 나타내며 보통 '코드포함'플래그인 IMAGE_SCN_CNT_CODE 플래그와 함께 설정됨
#define IMAGE_SCN_MEM_READ 0x40000000
// 읽기 가능한 섹션임을 나타냄. 별도 작업을 하지 않으면 보통 PE파일의 모든 섹션에 설정됨
#define IMAGE_SCN_MEM_WRITE 0x80000000
// 쓰기 가능한 섹션임을 나타냄. .bss, .data 섹션이 해당된다.
이외에도 OBJ에서 사용되는 플래그들이 더 존재한다.
그리고 이 섹션헤더의 엔트리에 포함되지는 않지만, IMAGE_DOS_HEADER, IMAGE_NT_HEADERS, IMAGE_SECTION_HEADER 등의 모든 PE헤더들은 헤더섹션이라는 하나의 섹션을 가지며 SectionAlignment 필드에 대한 라운드업된 크기로 갖는다. 따라서 정확한 전체 섹션의 수는 NumberOfSections(나머지섹션) + 1(헤더섹션)이다.
RVA <-> 파일오프셋 주소 변환
IMAGE_DATA_DIRECTORY의 VirtualAddress는 각 엔트리가 의미하는 특정 구조체의 RVA 값이다. 이 구조체는 엔트리의 의미에 맞는 섹션에 포함되어 있으며, 이 구조체는 PE 파일상에서 오프셋을 구할땐 IMAGE_SECTION_HEADER 를 함께 참조해야한다.
* 파일오프셋 = 구조체의 RVA - 구조체가 포함된 섹션의 RVA(VirtualAddress) + 구조체가 포함된 섹션의 FileOffset(PointerToRawData)
* RVA = 파일오프셋 - PointerToRawData + VirtualAddress
#define RVA_TO_OFFSET(psh, rva) (rva - (psh)->VirtualAddress + (psh)PointerToRawData)
#define OFFSET_TO_RVA(psh, off) (off - (psh)->PointerToRawData + (psh)VirtualAddress)
해당 구조체가 소속된 SECTION_HEADER를 찾아야하는데, IMAGE_NT_HEADERS의 OptionalHeader 이전까지는 32bit 나 64bit 나 같은 크기이고 IMAGE_FILE_HEADER의 SizeOfOptionalHeader 필드값을 더해서 첫번째 섹션 헤더의 오프셋을 구할 수 있다.
그리고 IMAGE_FILE_HEADER의 NumberOfSections 필드 값만큼 섹션헤더를 돌면서 구조체의 RVA값이 해당하는 섹션이 어디인지찾는다.
#define IMAGE_FIRST_SECTION(ntheader) ((PIMAGE_SECTION_HEADER)((ULONG_PTR)(ntheader) + \
FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) + ((ntheader))->FileHeader.SizeOfOptionalHeader))
// NTHeader의 VA + NTHeader기준 OptionalHeader의 상대적 오프셋 + OptionalHeader의 크기
// = 첫번째 섹션 헤더의 VA
#define FIELD_OFFSET(type, field)((LONG)(LONG_PTR)&(((type *)0->field))
// WinNT.h에 정의되어있는 type의 field를 type기준으로 상대적 오프셋을 구하는 매크로
PIMAGE_SECTION_HEADER PEPlus::FindSectHdr(PByte pImgBase, DWORD dwRVA, short &nSectIdx){
nSectIdx=-1;
PIMAGE_NT_HEADERS pnh = (PIMAGE_NT_HEADERS)GET_NT_HDRPTR(pImgBase);
// NtHeader 포인터 가져오기
PIMAGE_SECTION_HEADER pshs = IMAGE_FIRST_SECTION(pnh);
// 첫번째 섹션 헤더의 VA (IMAGE_SECTION_HEADER 배열의 포인터)
for (WORD i = 0; i < pnh->FileHeader.NumberOfSections; i++){
// 섹션 수 만큼 섹션헤더 배열에서 VirtualAddress를 통해 섹션바디에 찾을 RVA값이 포함되는지 확인
if (dwRVA >= pshs[i].VirtualAddress &&
dwRVA < pshs[i].VirtualAddress + pshs[i].Misc.VirtuialSize){
nSectIdx = (short)i;
return &pshs[i];
// 찾았다면 섹션 헤더의 VA, index를 반환
}
}
return NULL;
}
파일 오프셋을 RVA로 변환하는 경우에도 오프셋이 소속된 섹션 헤더를 먼저 찾아야한다. 위와 전부 동일하지만, VirtualAddress가 아닌 PointerToRawData 필드로 파일 오프셋을 이용하여 찾게된다.
섹션헤더를 찾았다면 RVA_TO_OFFSET이나 OFFSET_TO_RVA 매크로에 섹션헤더를 전달하여 원하는 동작을 수행할 수 있다.
섹션은 언제든지 컴파일러의 최적화나 병합옵션을 통해 병합될 수 있기 때문에 이름을 통해서 디렉터리 엔트리를 찾으면 안된다.
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 디렉터리는 DLL 바인딩과 관련되어 있으며, 바인딩 자체가 최적화와 관련되어 있기 때문에 섹션 내부가 아닌 헤더 섹션의 여분의 공간에 위치하기 때문에 섹션헤더와 .text 섹션 사이에 위치한다
IMAGE_DIRECTORY_ENTRY_SECURITY 디렉터리는 코드 사이닝 처리된 파일들의 무결성을 체크하는 인증정보를 담고 있기 때문에 체크 대상에서 제외되어야 하며 원활한 무결성 체크를 위해 섹션 내부가 아닌 파일끝에 위치한다.
이 두 디렉터리는 다른 디렉터리와 다르게 섹션 외부에 존재하며 VirtualSize필드는 RVA가 아닌 파일오프셋이 될 수 있다.
'리버싱 > 윈도우' 카테고리의 다른 글
리버스 엔지니어링 : 섹션 (0) | 2020.09.01 |
---|---|
리버스 엔지니어링 1 : PE 공부 전 필요한 기본지식 (0) | 2020.08.23 |
안티 디버깅(Anti-Debugging) (0) | 2020.07.28 |
특정 프로세스를 보호하기 위해서 Hook을 해야 할 API 정리 (0) | 2020.07.28 |
윈도우 리버싱 도구 (0) | 2020.07.28 |