글수 69
가입인사겸 저또한 그랬고, 많은 프로그래머들이 궁금해했던 exe 파일의 PE구조에 대해서 간략하게나마 소개하도록하겠습니다. 실제 PE구조를 보는 여러가지 툴들도 많이 공개되어있고 울트라 에딧으로 바이너리를 직접 확인해볼수도 있습니다. 원론적인 이야기는 저도 열심히 공부중이고, 이 글은 'API 정복에 있는 내용 + 잡다한 지식'으로 제 블로그에 저장했던 글입니다.
퍼포먼스를 높히기위해(?) 존칭은 생략하도록 하겠습니다.
--------------------------------------------------
대락적인 구조는 다음과 같다.
Dos Stub
도스와의 하위 호환성 유지를 위한 더미다.
프로그램을 기획할수도있겠지만 보통 도스에서 실행하면 윈도어플리케이션이라는 경고만 띄우는 정도다.
도스 설계자의 이름인 MZ로 시작되는데 이는 실행파일임을 표시하는 일종의 매직넘버다.
나머지는 별로 알아서 도움될거 없고 스텁의 0x3c에 PE헤더의 오프셋이 기록되어있다.
IMAGE_NT_HEADER
Signature <- 이 멤버는 항상 "PE\0\0"이다 PE파일임을 표시하는 매직넘버다.
여담으로 확장자 TXT를 EXE로 바꿔도 로더가 실행하지않는 이유는 이것때문이다.
IMAGE_FILE_HEADER <- PE의 주요 속성
IMAGE_FILE_HEADER는 PE파일에 대한 개략적이고 전체적인 정보를 제공한다.
실행 파일뿐만 아니라 OBJ 파일에도 존재하므로 흔히 COFF 헤더라고 부른다.
(참고루 COFF format은 이 헤더와 섹션정보만 있다.)
[code]
typedef struct _IMAGE_FILE_HEADER{
WORD Machine;
// 이 이미지가 실행될 수 있는 CPU의 타입을 지정하며 ALPHA, MIPS, POWERPC, ARM 등 여러가지의
// 값을 가질 수 있다. 이 정보가 있기 때문에 PE가 이식성이 있는 것이다.
// 하지만 2000이후에는 인텔만 지원하므로 현실적으로 i386(0x14c)와 ia64(0x200), x64(0x8664)정도다.
WORD NumberOfSections;
// 이미지를 구성하는 섹션의 개수이다. 섹션 테이블 배열의 크기, 이 크기만큼 뒷부분에 섹션이 따라온다.
DWORD TimeDateStamp;
// 이미지가 만들어진 시간이며 유닉스 타임스탬프로 구성.
DWORD PointerToSymbolTable;
// 일종의 디버깅 정보인 심볼 테이블의 오프셋이다. 없을경우는 0이고 신형 PE는 보통 0이다.
DWORD NumberOfSymbols;
// 심볼 테이블 내의 심볼개수이다. 심볼 테이블 뒤에 따라오는
// 문자열 테이블의 주소를 구하기 위해 이 정보가 사용된다.
WORD SizeOfOptionalHeader;
// 옵션 헤더의 크기이다. 옵션 헤더는 없을수도(COFF) 있고 크기도 가변적이기 때문에
// 이 정보가 있어야만 옵션 헤더 뒤의 섹션 테이블을 정확하게 찾을 수 있다.
// OBJ는 당연히(COFF) 0이고 EXE나 DLL에서만 사용되는데,
// 32비트 버전에서는 0xE0, 64비트 버전에서는 0xF0의 크기를 가진다.
WORD Characteristics
// 이미지의 여러가지 특징을 표현하는 플래그의 집합이다.
// 예를들어 디버깅 정보가 있는지, 직접 실행가능한 이미지인지, 재배치 가능한지 등에 대한 정보가 있다.
} IMAGE_FILE_HEADER;
[/code]
장황하게 길어보이지만 사실상 플랫폼 정보와 섹션 개수, 특성 플래그 정도가 유용하다.
지원 가능한 CPU 목록과 특성 플래그들의 전체 목록, 구체적인 의미에 대해서는 레퍼런스를 뒤지되
사실 이런 목록까지 알아야 할 필요는 없다.
32비트에서는 64비트 프로그램을 실행할 수 없지만 32비트 프로그램을 64에서는 당연히 실행가능하다.
IMAGE_OPTIONAL_HEADER <- PE의 세부 속성
64비트에서는 PE모양이 조금 달라지는데, BaseOfData멤버가 없어지고
ImageBase와 스택, 힙의 크기 지정 멤버가 ULONGLONG타입의 64비트로 확장된다.
여튼 32비트를 기준으로 헤더를 분석해보자.
[code]
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
// 이미지 파일의 버전과 종류를 나타낸다. 32비트 PE는 0x10B, 64는 0x20B, ROM이미지는 0x107.
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
// 링커의 버전을 표시하는 정수부와 실수부를 의미한다.
// 비쥬얼 C++ 6.0은 6과 0이고 8.0은 8, 0 Dev C++ 4989 버전은 2.38로 조사된다.
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
// 이 세 개의 멤버는 지정한 속성을 가지는 섹션들의 총 크기이다.
// 순서대로 코드, 초기화된 섹션, 초기화되지 않은 섹션의 크기값이다.
DWORD AddressOfEntryPoint;
// 최초 실행될 이미지상의 RVA이며 쉽게 말해 main이나 WinMain의 번지라고 생각하면 된다.
// RVA(Relative Virtual Address)란 이미지가 주소 공간에 전개된후 ImageBase를 기준으로
// 얼마나 떨어져 있는가를 지정하는 상대적인 주소값이다.
// 다행히도 RVA값과 오프셋을 기록하는 멤버의 크기는 어느 플랫폼에서든 32비트이다.
// 따라서 이미지의 크기나 메모리에 배치되는거나 4G가 한계다.
// 다만 메모리에 전개되었을때 그보다 훨씬 더 많은 메모리를 쓸수있을 뿐이다.
// DLL같은거에는 없을 수 있다.
DWORD BaseOfCode;
DWORD BaseOfData;
// 코드와 데이터가 시작되는 RVA이다.
DWORD ImageBase;
// 이미지가 주소공간으로 로드될때 배치될 번지이며 할당단위(통상 64K)의 배수 번지여야 한다.
// 실행 파일은 보통 0x400000로 지정되며 DLL은 0x10000000이되 링커 옵션으로 변경 가능하다.
// CE용 PE는 0x100000의 베이스를 가지며 로더는 가급적 이 번지에 이미지를 배치하지만 DLL의 경우엔
// 다른 DLL이 이미 선점하고있을 수도 있으므로 다른 곳에 배치되는 경우가 종종있다.
DWORD SectionAlignment;
// 섹션을 배치할 정렬 기준값이다. 섹션은 이 멤버의 배수 위치에 배치된다.
// 페이지 크기와 같은데 디폴트는 4K이다.
DWORD FileAlignment;
// 섹션내의 정보를 배치하는 정렬값이다. 0x200~0x10000사이의 2의 거듭승으로 지정, 보통 512
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
// 실행되기 위한 운영체제의 버전이다. 예를들어 윈도우즈 2000이상에서만 실행될수있다면
// 5.0의 값을 가질것이고 32비트 PE는 보통 95이상에서 실행가능하므로 4.0으로 설정된다.
WORD MajorImageVersion;
WORD MinorImageVersion;
// 실행 이미지의 버전이다. 링크 옵션에 실행 파일의 버전을 지정하면 여기에 버전정보가 삽입되고
// 별다른 지정이 없으면 0이다.
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
// 요구되는 서브 시스템의 버전이다. 버전 번호는 서브시스템에 따라 다른데 GUI, CUI인 경우는
// 윈도우즈 버전과 일치한다.
DWORD Win32VersionValue;
DWORD SizeOfImage;
// 헤더를 포함한 이미지의 총 크기이다. 로더는 섹션 배치전에 이만큼의 메모리를 할당한다.
DWORD SizeOfHeaders;
// 헤더의 총 크기이다.
DWORD CheckSum;
WORD SubSystem;
// 이미지가 실행될 서브 시스템을 의미한다. POSIX, OS/2, CE(9) 등의 여러가지 서브 시스템이 있지만,
// 현실적으로 GUI(2), 아니면 CUI(3) 둘 중 하나인 경우가 많다.
WORD DllChracteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
// 스택과 힙의 초기 예약 크기와 확정 크기이다. 로더는 이 멤버가 지시하는대로 스택과 힙을
// 미리 예약및 할당해 놓는다. 보통 1M을 예약하고 그중 한두페이지를 확정한다.
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
// 바로 다음 멤버인 디렉토리 배열의 개수이다. 통상 0x10이다.
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
// 요넘은 약간의 설명이 필요한 배열이다.
// 일단 IMAGE_DATA_DIRECTORY는 DWORD형의 VirtualAddess와 Size 멤버를 가지고 있다.
// 이 배열은 바로전 멤버인 NumberOfRvaAndSizes의 크기만큼의 배열크기를 가진다.
// 의미는 0번은 엑스포트 테이블, 1번은 임포트 테이블, 2번은 리소스 테이블, 9번은 TLS 테이블 등이다.
// 모든 요소가 다있는것은 아니고 존재하지 않는 디렉토리는 0의 값을 가진다.
// 섹션별로 정보를 구성하는 PE에서 왜 디렉토리 배열을 사용하는가?
// 이유는 유연성을 확보하기 위해서이다.
// 예를 들어 엑스포트 정보가 너무 작을 때는 데이터 섹션에 합쳐지며 별도의 엑스포트 섹션이
// 생성되지 않는다. 하지만 디렉토리 배열의 0번에 엑스포트 블록의 시작 위치가 있으므로
// 이 위치를 읽으면 엑스포트 정보를 구할 수 있다.
} IMAGE_OPTIONAL_HEADER;
[/code]
Section Header Array <- PE를 구성 섹션들의 속성
배열이다. 앞의 IMAGE_FILE_HEADER의 NumberOfSections 만큼의 크기를 가지고있다.
타입의 의미는 다음과 같다.(날려버려서 귀찮아 =_=)
[code]
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
// 총 8바이트까지 가능한 섹션의 이름이며 보통 .으로 시작한다.
// 8자까지 다쓸경우 널 종료 문자는 생략할 수 있다.
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
// 여기선 VirtualSize만 중요한데 이값은 메모리에 로드되었을 때 섹션의 크기를 지정한다.
// OBJ의 경우에는 0으로 설정된다.
DWORD VirtualAddress;
// 메모리에 로드되었을때 섹션의 RVA이다. 로더는 베이스에서 이값을 더한 번지에 섹션을 배치한다.
DWORD SizeOfRawData;
// VirtualSize를 정렬 기준에 맞게 올림한 값이다.
// VirtualSize가 메모리상의 크기라면 이 값은 디스크상의 크기라고 할 수 있다.
DWORD PointerToRawData;
// 섹션이 시작되는 PE파일상의 오프셋이다. 덤프에서 이 번지를 읽으면 섹션의 내용을 직접 읽을수있다.
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
// 섹션의 속성을 지정한다. 32비트 각 비트의 의미는 다음과 같다.(31번째 비트부터 출발한다)
// 쓰기가능 | 읽기가능 | 실행가능 | 공유가능 | 페이징안됨 | 캐시금지 | 버릴수있다 | |
// ........ 중간의 16비트는 생략하겠다. 자세한건 레퍼런스
// | 초기화되지 않은 데이터 | 초기화된 데이터 | 코드를 담고 있다 | | | | | |
} IMAGE_SECTION_HEADER;
[/code]
Sections <- 실제 정보를 가지는 섹션들. 코드, 데이터, 리소스, 임포트, 엑스포트 테이블 등등
머 이 부분은 기냥 실제 섹션이므로 더 설명할게 없다.(사실 슬슬 귀찮아지기 시작했다.)
섹션은 다른 섹션에 병합되기도하고 이래저래 뻘짓을 많이하므로 정확한 위치를 파악하기 위해서는
역시 IMAGE_OPTIONAL_HEADER의 IMAGE_DATA_DIRECTORY 배열에서 파악해야겠다.
머 간단하게나마 각섹션들이 가지는 의미를 파악해보도록하자.
섹션명은 컴팔러마다 틀리고 VC를 기준으로 말한다.
.text - 코드, 실행, 읽기
실행코드를 가진다. CPU에 의존적이며 소스의 컴파일 결과가 여기에 작성된다.
실행만 가능하며 쓰기는 불가능하다.
물론 엔트리포인트는 옵션헤더에 있다.
.data - 초기화, 읽기, 쓰기
초기화된 전역 변수를 가지며 읽기, 쓰기가 모두 가능하다. 컴파일러가 여기에 변수의 영역을 마련하고
초기값을 미리 대입해 놓는다.
.rdata - 초기화, 읽기
읽기전용의 변수를 가지며 문자열 상수나 const로 선언된 변수들이 이 섹션에 저장된다.
당연히 읽기전용이며 쓰기는 불가능하다.
.bss - 비초기화, 읽기, 쓰기
초기화되지 않는 전역변수를 위한 섹션이다.
.edata - 초기화, 읽기
export할 함수에 대한 정보를 가진다. DLL의 경우에만 이 섹션이 존재한다.
.idata - 초기화, 읽기, 쓰기
import할 함수에 대한 정보를 가진다.
어떤 DLL에 있는 어떤 함수가 필요한지에 대한 정보가 이 섹션에 작성된다.
.rsrc - 초기화, 읽기
리소스가 저장된다.
.tls - 초기화, 읽기, 쓰기
스레드를 위한 지역 저장소이다.
.relec - 초기화, 읽기, 쓰기
DLL이 지정한 기준 번지에 배치되지 못할 때를 대비하여 재배치 정보를 가진다.
너무 간략하다 생각하지 말것,,
자세히 들이다보면 머리깨질거고 필요부분만 레퍼런스로 뒤지는 알흠다운 IT 레퍼런스룰을 지키도록 하자.
아!! 병합되는 정보는 디렉터리에 있다는 사실을 절대 잊지말자.
--------------------------------------------------
이만 글을 마치도록 하겠습니다. ^^
혹여 글에 문제가 있다거나 수정사항이 필요하다면 귀뜸해주시구요,
반응이 좋으면 리버싱 위주로 계속 글을 올리겠습니다. ^^
날씨 선선한데 책 많이 읽으시길 바라고 항상 건강하세요.
퍼포먼스를 높히기위해(?) 존칭은 생략하도록 하겠습니다.
--------------------------------------------------
대락적인 구조는 다음과 같다.
Dos Stub
도스와의 하위 호환성 유지를 위한 더미다.
프로그램을 기획할수도있겠지만 보통 도스에서 실행하면 윈도어플리케이션이라는 경고만 띄우는 정도다.
도스 설계자의 이름인 MZ로 시작되는데 이는 실행파일임을 표시하는 일종의 매직넘버다.
나머지는 별로 알아서 도움될거 없고 스텁의 0x3c에 PE헤더의 오프셋이 기록되어있다.
IMAGE_NT_HEADER
Signature <- 이 멤버는 항상 "PE\0\0"이다 PE파일임을 표시하는 매직넘버다.
여담으로 확장자 TXT를 EXE로 바꿔도 로더가 실행하지않는 이유는 이것때문이다.
IMAGE_FILE_HEADER <- PE의 주요 속성
IMAGE_FILE_HEADER는 PE파일에 대한 개략적이고 전체적인 정보를 제공한다.
실행 파일뿐만 아니라 OBJ 파일에도 존재하므로 흔히 COFF 헤더라고 부른다.
(참고루 COFF format은 이 헤더와 섹션정보만 있다.)
[code]
typedef struct _IMAGE_FILE_HEADER{
WORD Machine;
// 이 이미지가 실행될 수 있는 CPU의 타입을 지정하며 ALPHA, MIPS, POWERPC, ARM 등 여러가지의
// 값을 가질 수 있다. 이 정보가 있기 때문에 PE가 이식성이 있는 것이다.
// 하지만 2000이후에는 인텔만 지원하므로 현실적으로 i386(0x14c)와 ia64(0x200), x64(0x8664)정도다.
WORD NumberOfSections;
// 이미지를 구성하는 섹션의 개수이다. 섹션 테이블 배열의 크기, 이 크기만큼 뒷부분에 섹션이 따라온다.
DWORD TimeDateStamp;
// 이미지가 만들어진 시간이며 유닉스 타임스탬프로 구성.
DWORD PointerToSymbolTable;
// 일종의 디버깅 정보인 심볼 테이블의 오프셋이다. 없을경우는 0이고 신형 PE는 보통 0이다.
DWORD NumberOfSymbols;
// 심볼 테이블 내의 심볼개수이다. 심볼 테이블 뒤에 따라오는
// 문자열 테이블의 주소를 구하기 위해 이 정보가 사용된다.
WORD SizeOfOptionalHeader;
// 옵션 헤더의 크기이다. 옵션 헤더는 없을수도(COFF) 있고 크기도 가변적이기 때문에
// 이 정보가 있어야만 옵션 헤더 뒤의 섹션 테이블을 정확하게 찾을 수 있다.
// OBJ는 당연히(COFF) 0이고 EXE나 DLL에서만 사용되는데,
// 32비트 버전에서는 0xE0, 64비트 버전에서는 0xF0의 크기를 가진다.
WORD Characteristics
// 이미지의 여러가지 특징을 표현하는 플래그의 집합이다.
// 예를들어 디버깅 정보가 있는지, 직접 실행가능한 이미지인지, 재배치 가능한지 등에 대한 정보가 있다.
} IMAGE_FILE_HEADER;
[/code]
장황하게 길어보이지만 사실상 플랫폼 정보와 섹션 개수, 특성 플래그 정도가 유용하다.
지원 가능한 CPU 목록과 특성 플래그들의 전체 목록, 구체적인 의미에 대해서는 레퍼런스를 뒤지되
사실 이런 목록까지 알아야 할 필요는 없다.
32비트에서는 64비트 프로그램을 실행할 수 없지만 32비트 프로그램을 64에서는 당연히 실행가능하다.
IMAGE_OPTIONAL_HEADER <- PE의 세부 속성
64비트에서는 PE모양이 조금 달라지는데, BaseOfData멤버가 없어지고
ImageBase와 스택, 힙의 크기 지정 멤버가 ULONGLONG타입의 64비트로 확장된다.
여튼 32비트를 기준으로 헤더를 분석해보자.
[code]
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
// 이미지 파일의 버전과 종류를 나타낸다. 32비트 PE는 0x10B, 64는 0x20B, ROM이미지는 0x107.
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
// 링커의 버전을 표시하는 정수부와 실수부를 의미한다.
// 비쥬얼 C++ 6.0은 6과 0이고 8.0은 8, 0 Dev C++ 4989 버전은 2.38로 조사된다.
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
// 이 세 개의 멤버는 지정한 속성을 가지는 섹션들의 총 크기이다.
// 순서대로 코드, 초기화된 섹션, 초기화되지 않은 섹션의 크기값이다.
DWORD AddressOfEntryPoint;
// 최초 실행될 이미지상의 RVA이며 쉽게 말해 main이나 WinMain의 번지라고 생각하면 된다.
// RVA(Relative Virtual Address)란 이미지가 주소 공간에 전개된후 ImageBase를 기준으로
// 얼마나 떨어져 있는가를 지정하는 상대적인 주소값이다.
// 다행히도 RVA값과 오프셋을 기록하는 멤버의 크기는 어느 플랫폼에서든 32비트이다.
// 따라서 이미지의 크기나 메모리에 배치되는거나 4G가 한계다.
// 다만 메모리에 전개되었을때 그보다 훨씬 더 많은 메모리를 쓸수있을 뿐이다.
// DLL같은거에는 없을 수 있다.
DWORD BaseOfCode;
DWORD BaseOfData;
// 코드와 데이터가 시작되는 RVA이다.
DWORD ImageBase;
// 이미지가 주소공간으로 로드될때 배치될 번지이며 할당단위(통상 64K)의 배수 번지여야 한다.
// 실행 파일은 보통 0x400000로 지정되며 DLL은 0x10000000이되 링커 옵션으로 변경 가능하다.
// CE용 PE는 0x100000의 베이스를 가지며 로더는 가급적 이 번지에 이미지를 배치하지만 DLL의 경우엔
// 다른 DLL이 이미 선점하고있을 수도 있으므로 다른 곳에 배치되는 경우가 종종있다.
DWORD SectionAlignment;
// 섹션을 배치할 정렬 기준값이다. 섹션은 이 멤버의 배수 위치에 배치된다.
// 페이지 크기와 같은데 디폴트는 4K이다.
DWORD FileAlignment;
// 섹션내의 정보를 배치하는 정렬값이다. 0x200~0x10000사이의 2의 거듭승으로 지정, 보통 512
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
// 실행되기 위한 운영체제의 버전이다. 예를들어 윈도우즈 2000이상에서만 실행될수있다면
// 5.0의 값을 가질것이고 32비트 PE는 보통 95이상에서 실행가능하므로 4.0으로 설정된다.
WORD MajorImageVersion;
WORD MinorImageVersion;
// 실행 이미지의 버전이다. 링크 옵션에 실행 파일의 버전을 지정하면 여기에 버전정보가 삽입되고
// 별다른 지정이 없으면 0이다.
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
// 요구되는 서브 시스템의 버전이다. 버전 번호는 서브시스템에 따라 다른데 GUI, CUI인 경우는
// 윈도우즈 버전과 일치한다.
DWORD Win32VersionValue;
DWORD SizeOfImage;
// 헤더를 포함한 이미지의 총 크기이다. 로더는 섹션 배치전에 이만큼의 메모리를 할당한다.
DWORD SizeOfHeaders;
// 헤더의 총 크기이다.
DWORD CheckSum;
WORD SubSystem;
// 이미지가 실행될 서브 시스템을 의미한다. POSIX, OS/2, CE(9) 등의 여러가지 서브 시스템이 있지만,
// 현실적으로 GUI(2), 아니면 CUI(3) 둘 중 하나인 경우가 많다.
WORD DllChracteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
// 스택과 힙의 초기 예약 크기와 확정 크기이다. 로더는 이 멤버가 지시하는대로 스택과 힙을
// 미리 예약및 할당해 놓는다. 보통 1M을 예약하고 그중 한두페이지를 확정한다.
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
// 바로 다음 멤버인 디렉토리 배열의 개수이다. 통상 0x10이다.
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
// 요넘은 약간의 설명이 필요한 배열이다.
// 일단 IMAGE_DATA_DIRECTORY는 DWORD형의 VirtualAddess와 Size 멤버를 가지고 있다.
// 이 배열은 바로전 멤버인 NumberOfRvaAndSizes의 크기만큼의 배열크기를 가진다.
// 의미는 0번은 엑스포트 테이블, 1번은 임포트 테이블, 2번은 리소스 테이블, 9번은 TLS 테이블 등이다.
// 모든 요소가 다있는것은 아니고 존재하지 않는 디렉토리는 0의 값을 가진다.
// 섹션별로 정보를 구성하는 PE에서 왜 디렉토리 배열을 사용하는가?
// 이유는 유연성을 확보하기 위해서이다.
// 예를 들어 엑스포트 정보가 너무 작을 때는 데이터 섹션에 합쳐지며 별도의 엑스포트 섹션이
// 생성되지 않는다. 하지만 디렉토리 배열의 0번에 엑스포트 블록의 시작 위치가 있으므로
// 이 위치를 읽으면 엑스포트 정보를 구할 수 있다.
} IMAGE_OPTIONAL_HEADER;
[/code]
Section Header Array <- PE를 구성 섹션들의 속성
배열이다. 앞의 IMAGE_FILE_HEADER의 NumberOfSections 만큼의 크기를 가지고있다.
타입의 의미는 다음과 같다.(날려버려서 귀찮아 =_=)
[code]
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
// 총 8바이트까지 가능한 섹션의 이름이며 보통 .으로 시작한다.
// 8자까지 다쓸경우 널 종료 문자는 생략할 수 있다.
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
// 여기선 VirtualSize만 중요한데 이값은 메모리에 로드되었을 때 섹션의 크기를 지정한다.
// OBJ의 경우에는 0으로 설정된다.
DWORD VirtualAddress;
// 메모리에 로드되었을때 섹션의 RVA이다. 로더는 베이스에서 이값을 더한 번지에 섹션을 배치한다.
DWORD SizeOfRawData;
// VirtualSize를 정렬 기준에 맞게 올림한 값이다.
// VirtualSize가 메모리상의 크기라면 이 값은 디스크상의 크기라고 할 수 있다.
DWORD PointerToRawData;
// 섹션이 시작되는 PE파일상의 오프셋이다. 덤프에서 이 번지를 읽으면 섹션의 내용을 직접 읽을수있다.
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
// 섹션의 속성을 지정한다. 32비트 각 비트의 의미는 다음과 같다.(31번째 비트부터 출발한다)
// 쓰기가능 | 읽기가능 | 실행가능 | 공유가능 | 페이징안됨 | 캐시금지 | 버릴수있다 | |
// ........ 중간의 16비트는 생략하겠다. 자세한건 레퍼런스
// | 초기화되지 않은 데이터 | 초기화된 데이터 | 코드를 담고 있다 | | | | | |
} IMAGE_SECTION_HEADER;
[/code]
Sections <- 실제 정보를 가지는 섹션들. 코드, 데이터, 리소스, 임포트, 엑스포트 테이블 등등
머 이 부분은 기냥 실제 섹션이므로 더 설명할게 없다.(사실 슬슬 귀찮아지기 시작했다.)
섹션은 다른 섹션에 병합되기도하고 이래저래 뻘짓을 많이하므로 정확한 위치를 파악하기 위해서는
역시 IMAGE_OPTIONAL_HEADER의 IMAGE_DATA_DIRECTORY 배열에서 파악해야겠다.
머 간단하게나마 각섹션들이 가지는 의미를 파악해보도록하자.
섹션명은 컴팔러마다 틀리고 VC를 기준으로 말한다.
.text - 코드, 실행, 읽기
실행코드를 가진다. CPU에 의존적이며 소스의 컴파일 결과가 여기에 작성된다.
실행만 가능하며 쓰기는 불가능하다.
물론 엔트리포인트는 옵션헤더에 있다.
.data - 초기화, 읽기, 쓰기
초기화된 전역 변수를 가지며 읽기, 쓰기가 모두 가능하다. 컴파일러가 여기에 변수의 영역을 마련하고
초기값을 미리 대입해 놓는다.
.rdata - 초기화, 읽기
읽기전용의 변수를 가지며 문자열 상수나 const로 선언된 변수들이 이 섹션에 저장된다.
당연히 읽기전용이며 쓰기는 불가능하다.
.bss - 비초기화, 읽기, 쓰기
초기화되지 않는 전역변수를 위한 섹션이다.
.edata - 초기화, 읽기
export할 함수에 대한 정보를 가진다. DLL의 경우에만 이 섹션이 존재한다.
.idata - 초기화, 읽기, 쓰기
import할 함수에 대한 정보를 가진다.
어떤 DLL에 있는 어떤 함수가 필요한지에 대한 정보가 이 섹션에 작성된다.
.rsrc - 초기화, 읽기
리소스가 저장된다.
.tls - 초기화, 읽기, 쓰기
스레드를 위한 지역 저장소이다.
.relec - 초기화, 읽기, 쓰기
DLL이 지정한 기준 번지에 배치되지 못할 때를 대비하여 재배치 정보를 가진다.
너무 간략하다 생각하지 말것,,
자세히 들이다보면 머리깨질거고 필요부분만 레퍼런스로 뒤지는 알흠다운 IT 레퍼런스룰을 지키도록 하자.
아!! 병합되는 정보는 디렉터리에 있다는 사실을 절대 잊지말자.
--------------------------------------------------
이만 글을 마치도록 하겠습니다. ^^
혹여 글에 문제가 있다거나 수정사항이 필요하다면 귀뜸해주시구요,
반응이 좋으면 리버싱 위주로 계속 글을 올리겠습니다. ^^
날씨 선선한데 책 많이 읽으시길 바라고 항상 건강하세요.
