0%

windows 시스템 실행파일의 구조와 원리 책 내용을 요약 및 정리 하는 포스팅이에요.

섹션들의 정보들의 위치와 크기를 나타내는 IMAGE_SECTION_HEADER에 대한 내용을 알아볼 거에요.


IMAGE_SECTION_HEADER의 시작

IMAGE_NT_HEADER에 대한 설명이 이제 다 끝났어요. 그 다음으로 이어질 구조체인 IMAGE_SECTION_HEADER에 대한 설명을 시작할게요.

여기에서도 섹션들의 정보들의 위치와 크기등 여러가지를 알려주는 IMAGE_SECTION_HEADER에 대해 알아볼 건데요. 섹션들의 정보들의 위차와 크기를 나타내는 IMAGE_DATA_DIRECTORY 구조체와 어떤 차이가 있는 걸까요?

IMAGE_DATA_DIRECTORY 구조체에 있는 섹션들은 IMAGE_SECTION_HEADER 구조체에 있는 섹션들에게 병합되어 있기 때문에 PE 파일 내에 정보가 없어요! 따라서 IMAGE_DATA_DIRECTORY 구조체에 있는 섹션들의 정보를 확인하고 싶으면, IMAGE_SECTION_HEADER 구조체에 있는 정보들과 조합해서 찾아야 합니다!

자 진짜로 IMAGE_SECTION_HEADER에 대한 설명을 시작할게요!

IMAGE_SECTION_HEADER 구조체는 40바이트로 구성되어 있고요. IMAGE_FILE_HEADER의 NumberOfSections 필드에 있는 섹션의 개수만큼 IMAGE_SECTION_HEADER 가 존재해요.
이 구조체도 마찬가지로 “WinNT.h” 헤더 파일에 정의되어 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#defome IMAGE_SECTION_HEADER 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;

첫 번째 멤버, BYTE Name[IMAGE_SIZEOF_SHORT_NAME]

섹션의 아스키 이름을 나타내요. IMAGE_SIZEOF_SHORT_NAME은 8바이트로 매크로 상수로 정의되어 있고요. 문자열임에도 불구하고 NULL을 신경쓰지 않아요.
그 이유가 섹션 이름을 8바이트 이상으로 지정했을 경우 링커가 알아서 8바이트 이후의 문자열은 잘라 버린 후 값을 채우기 때문이에요.


두 번째 멤버, DOWRD PhysicalAddress or VirtualSize

이 필드는 OBJ 파일인 경우와 PE의 경우 의미가 달라져요.

PE의 경우 공용체의 VirtualSize 필드를 사용하여 코드와 데이터의 실제 바이트수를 담고 있어요. 즉, 파일 바이트 정렬의 배수로 라운드업이 되기 전의 실제 바이트 수를 담고 있어요.
이 값에 대한 라운드업된 값은 이 구조체의 SizeOfRawData 필드에 저장되요.

이전까지는 OBJ 파일의 경우 공용체의 PhysicalAddress 필드를 통해 섹션의 물리적인 번지를 지정했지만, 현재에는 OBJ의 경우 이 필드는 0으로 세팅되요. 즉, PhysicalAddress 필드는 더 이상 의미가 없어졌어요.

이렇게 물리적인 번지를 직접 사용하지 않는 이유가 현대에는 보호모드가 보편적으로 사용되기 때문이에요.


세 번째 멤버, DWORD VirtualAddress

PE에서 해당 섹션을 매핑시켜야 할 가상 주소 공간 상의 RVA를 가지고 있어요.
즉, 메모리 상에서의 본 섹션의 시작 주소를 의미하는 거죠.
그리고 SectionAlignment 필드 값의 배수가 되어야 한다.


네 번째 멤버, DWORD SizeOfRawData

VirtualSize 필드 값에 대한 파일 정렬(Alignment) 값의 배수로 라운드업된 값이다.
만약 IMAGE_OPTIONAL_HEADER의 FileAlignment 필드의 값이 0x200 이고, VirtualSize 필드 값이 0x35A이면 SizeOfRawData 필드 값은 0x400이 된다.

OBJ 파일의 경우 이 값은 0이다.


다섯 번째 멤버, DWORD PointerToRawData

해당 섹션의 PE 파일 내에서 시작하는 실제 파일 오프셋 값이다.
이 값 역시 IMAGE_OPTIONAL_HEADER의 FileAlignmnet 필드 값의 배수가 되어야 한다.
이 필드의 값은 VirtualAddress 필드의 값과 같을 수도 있고 다를 수도 있다.

그렇게 하는 이유가 VirtualAddress 필드는 IMAGE_OPTIONAL_HEADER의 SectionAlignment 필드 값의 배수가 되기 때문에 바이트 정렬 문제로 인하여 하드디스크 상의 공간을 낭비될 수 있고, 이때 PointerToRawData 필드에 실제 섹션의 파일 상의 시작 오프셋을 지정하고 오프셋에 섹션 데이터를 기록함으로써 PE 파일의 크기를 줄일 수 있는 거라고 해요.

따라서 VirtualAddress 필드와 PointerToRawData 필드 사이의 관계를 파악하는 것이 중요하다고 해요.
VirtualAddress의 값은 RVA이기 때문에 IMAGE_OPTIONAL_HEADER의 ImageBase 필드 값을 더한 결과가 프로그램 로더가 PE 파일을 위하여 프로세스 커널 객체를 생성한 후 가상 주소 공간을 만들고 나서 이 섹션을 해당 주소 공간에 매핑시킬 실제 번지 값이 되요. 그리고 PointerToRawData의 값은 이 섹션이 위치한 PE 파일 내의 파일 선두로 부터의 오프셋이 되고요.

VirtualAddress 필드와 PointerToRawData 필드는 중요한 필드인 거죠!
아래의 사진은 책에 첨부되더 있는 VirtualAddress와 PointerToRawData 사이의 관계에 대한 그림입니다.


여섯 번째 멤버, PointerToRelocations

본 섹션을 위한 재배치 파일 오프셋이에요. 이 필드는 OBJ에서만 사용되고 실행 파일에서는 0으로 세트되요. OBJ 파일에서 이 필드가 0이 아닐 경우 IMAGE_RELOCATION 구조체 배열의 시작 주소를 가리켜요.


일곱 번째 멤버, PointerToLinenumbers

본 섹션을 위한 COFF 스타일의 라인 번호를 위한 파일 오프셋이에요. 이 필드의 값이 0이 아닐 경우 IMAGE_LINENUMBER 구조체 배열의 시작을 가리키고, COFF 라인 번호가 PE에 첨부되었을 경우에만 사용해요.


여덟 번째 멤버, NumberOfRelocations

PointerToRelocations 필드가 가리키는 IMAGE_RELOCATION 구조체 배열의 원소의 개수에요. 실행 파일에서는 항상 0이에요!


아홉 번째 멤버, NumberOfLinenumbers

PointerToLinenumbers 필드가 가리키는 IMAGE_LINENUMBER 구조체 배열의 원소의 개수에요. 마찬가지로 COFF 라인 번호가 PE 파일에 추가 되어쓸 때만 사용해요.


열 번째 멤버, Characteristics

이 필드는 해당 섹션의 속성을 나타내는 플래그의 집합이에요.
“WinNT.h” 헤더 파일에 IMAGE_SCN_XXX_XXX의 형태로 매크로로 정의되어 있어요.

1
2
3
4
5
6
7
8
// Section contains code.
#define IMAGE_SCN_CNT_CODE 0x00000020

// Section contains ubutuakuzed data
#define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040

// Section contains uninitialized data
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080

링커는 링크 시에 필요에 따라서나 혹은 사용자의 지시에 의해 여러 섹션들을 하나로 병합할수 있어요. 병합하는 옵션은 /MERGS:from=to 형식으로 지정하면 되요. 이때 병합된 섹션 내에 특정 섹션이 포함되어 있는지를 확인할 수 있는 플래그 집함을 제공한느데 위의 플래그 집함이 그 목적으로 사용된다고 해요.

IMAGE_SCN_CNT_CODE

섹션의 코드를 포함하고 있고, 보통 이 플래그는 실행 가능 플래그를 의미하는 IMAGE_SCN_MEM_EXECUTE(0x20000000) 플래그와 함께 저장된다고 해요.

IMAGE_SCN_CNT_INITIALIZED_DATA

섹션이 초기화된 데이터를 포함하고 있어요. 실행 가능 섹션과 .bss 섹션을 제외한 거의 대부분의 섹션이 이 플래그를 가져요.

IMAGE_SCN_CNT_UNINTIALIZED_DATA

섹션이 초기화되지 않은 데이터(.bss 섹션 …)들이 가져요.


1
2
3
4
5
6
7
#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 // Section can be discared.
#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 readable.
#define IMAGE_SCN_MEM_READ 0x40000000 // Section is readable.
#define IMAGE_SCN_MEM_WRITE 0x80000000 // Section is writeable.

위의 매크로 정의들은 메모리 페이지 속성을 나타내는 플래그들이에요.

IMAGE_SCN_MEM_DISCARDABLE

이 섹션은 실행 이미지가 메모리에 완전히 매핑되고 난 뒤 버려질 수 있다는 것을 의미해요.
일단 메모리에 로드(매핑)되고 난 후 프로세스에게 더 이상 의미없는 섹션이며, 가장 일반적인 버릴 수 있는 섹션은 기본 재배치(.reloc)섹션이에요.

IMAGE_SCN_MEM_NOT_CACHD

IMAGE_SCN_MEM_NOT_PAGED

해당 섹션은 페이지되지 않거나 캐쉬되지 않아요. 페이지 되지 않는다는 말은 결코 페이지 파일로 스왑되지 않는 다는 것을 의미하며 이는 항상 RAM에 존재하는 섹션임을 의미해요. 이런 종류의 섹션을 필요로 하는 경우는 커널 모드에서 작동하는 디바이스 드라이버 가능 실행 모듈이에요.

IMAGE_SCN_MEM_SHARED

해당 섹션은 공유 가능한 섹션이라는 것을 의미해요. DLL과 함께 사용될 때 이 섹션에 있는 데이터는 DLL을 사용하는 모든 프로세스들에 의해 공유될 수 있어요.
일반적으로 데이터 섹션들은 공유 불가능하다. 이 말은 해당 DLL을 사용하는 각각의 프로세스는 이 섹션의 데이터에 대한 자신만의 사본을 가진다는 의미에요.
하지만 공유 가능 섹션은 메모리 매니저에게 해당 섹션을 공유 가능하도록 지시해요. 공유 가능 섹션을 만들려면 링커 시에 SHARED 속성을 주면 되요.

LINK /SECTION:MYDATA, RWS …

IMAGE_SCN_MEM_EXECUTE

이 섹션은 실행 가능한 섹션임을 의미해요. 이 플래그는 보통 코드 포함 플래그인 IMAGE_SCN_CNT_CODE 플래그와 함께 셋되요.

IMAGE_SCN_MEM_READ

이 섹션은 읽기 가능한 섹션임을 의미해요. 이 플래그는 언제나 EXE 파일 내의 대부분의 섹션들에 셋되요.

IMAGE_SCN_MEM_WRITE

이 섹션은 쓰기 가능한 섹션임을 의미해요. 이 플래그가 EXE의 섹션에 세트되지 않으면 로더는 메모리 맵드 페이지를 읽기 전용 또는 실행 전용으로 마크한다고 해요.
쓰기 가능한 전형적인 예로 .data와 .bss 섹션이 있고, .idata 섹션 역시 이 플래그를 가져요.


1
2
3
4
5
6
7
8
// Section contains comments or some other type of information
#define IMAGE_SCN_LNK_INFO 0x00000200

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

// Section contents comdat.
#define IMAGE_SCN_LNK_COMDAT 0x00010000

위의 매크로들은 컴파일 후 생성되는 OBJ 파일 내에서만 설정되는 플래그들로서 후에 링크 시에 링크로 하여금 최종 실행 모듈을 생성하는 데 필요한 정보를 참조할 수 있게 되요.

IMAGE_SCN_LNK_INFO

해당 섹션이 링커에 의해 사용될 주석이나 다른 어떤 종류의 정보를 가져요. 이 섹션의 전형적인 예는 컴파일러에 의해 생성되며 링커를 위한 명령을 담고 있는 .drectve 섹션이에요.

IMAGE_SCN_LNK_REMOVE

이 플래그는 링크 시에 최종 실행 파일의 일부가 되지 말아야 할 섹션의 내용들을 지시해요. 이 섹션들은 링커에게 정보를 넘겨주기 위한 컴파일러나 어셈블러에 의해 사용되요.

IMAGE_CSCN_LNK_COMDAT

해당 섹션의 내용은 공용 데이터라는 것을 의미해요. 이것은 COMDATA 라고 하는데, 공용 데이터나 코드 플래그는 여러 OBJ 파일에 걸쳐서 정의될 수 있어요. 링커는 실행 파일로 포함시키기 위해 하나의 복사본을 선택할 수 있어요.
COMDATA는 C++ 템플릿 함수와 함수 레벨 링킹을 지원하는데 매우 중요한 녀석이에요.


1
2
3
4
5
6
7
8
9
// Default alignemnt if no others are sepcifed.

#define IMAGE_SCN_ALIGN_1BYTES 0x00100000
#define IMAGE_SCN_ASLIGN_2BYTES 0x00200000
#define IMAGE_SCN_ALIGN_4BYTES 0x00300000
#define IMAGE_SCN_ALIGN_8BYTES 0x00400000

#define IMAGE_SCN_ALIGN_8192BYTES 0x00E00000
#define IMAGE_SCN_ALIGN_MASK 0x00F00000

위의 매크로들은 생성된 실행 파일 내의 해당 섹셔의 데이터 정렬 단위를 나타내는 플래그이며, OBJ 파일 내에서만 셋팅되요.

IMAGE_SCN_ALIGN_XBYTES

_XBYTES의 값으로 1BYT부터 8192BYTE 까지의 정렬 단위를 사타내요. 특별히 지정되지 않으면 디폴트로 16BYTE에 해당하는 IMAGE_SCN_ALIGN_16BYTES가 되고 이 값은 0x00500000 이에요.


마지막으로…

IMAGE_SECTION_HEADER에 있는 멤버들 중, VirtualSize, VirtualAddress, SizeOfRawData, PointerToRawData, Characteristics 멤버에 대해서는 확실하게 알아야 해요.

이 멤버들을 이용해서 메모리의 속성이 어떤지 파악할 수 있고, 어떤 부분이 데이터들이 저장되어 있는지 알 수 있거든요. 이 부분을 알아야지 우리가 필요한 데이터들을 찾을 수 있어요.


PE 파일에서 각 섹션의 실제 내용을 확인하고자 한다면 PointerToRawData가 가리키는 위치로 파일 오프셋을 이동시키고, 그곳에서부터 SizeOfRawData의 바이트 수만큼이 해당 섹션의 실제 내용이 되요.
이 섹션이 실제로 가상 주소 공간에 매핑되었을 때의 RVA가 VirtualAddress가 되며 매핑된 후의 섹션의 실제 시작 포인터를 얻고자 한다면 VirtualAddress 값에다가 IMAGE_OPTIONAL_HEADER의 ImageBase 필드 값을 더하면 되요. 그리고 매핑된 후에 이 섹션이 차지하고 있는 메모리 상의 크기는 VirtualSize에 명시되어 있어요.

일반적으로 포함되어 있는 섹션의 Charateristics 값을 한번 살펴봐요.

.textbss의 속성

  • .textbss -> 0xE0000000
    • IMAGE_SCN_MEM__EXECUTE -> 0x20000000
    • IMAGE_SCN_MEM_READ -> 0x40000000
    • IMAGE_SCN_MEM_WRITE -> 0x80000000
    • IMAGE_SCN_CNT_CODE -> 0x00000020
    • IMAGE_SCN_CNT_UNINITALIZED_DATA -> 0x00000080

.textbss 섹션은 코드 섹션과 초기화되지 않은 데이터가 있는 .bss 섹션이 혼합되어 있다는 것을 알 수 있어요. 그래서 메모리 속성으로는 실행, 읽기, 쓰기 속성이 지정되어 있고, 컨테이터 속성 중에서는 코드 속성과 초기화되지 않는 데이터 속성이 지정되어 있죠.

.text의 속성

  • .text -> 0x60000020
    • IMAGE_SCN_MEM_EXECUTE -> 0x20000000
    • IMAGE_SCN_MEM_READ -> 0x40000000
    • IMAGE_SCN_CNT_CODE -> 0x00000020

이 섹션은 코드 섹션으로서 전형적인 콛그 섹션의 속성만을 가지고 있어요. 메모리 속성으로는 실행과 읽기 속성을 가지며 컨테이너 속성으로는 코드 속성을 가져요.

.rdata의 속성

  • .rdata -> 0x40000040
    • IMAGE_SCN_MEM_READ -> 0x40000000
    • IMAGE_SCN_CNT_INITIALIZED_DATA -> 0x00000040

이 섹션은 읽기 전용 데이터 섹션이에요. 따라서 메모리 속성은 읽기 속성만 가지며 컨테이너 속성으로는 초기화된 데이터 속성을 가져요.

.data, .idata의 속성

  • .data, .idata -> 0xC0000040
    • IMAGE_SCN_MEM_READ -> 0x40000000
    • IMAGE_SCN_MEM_WRITE -> 0x80000000
    • IMAGE_SCN_CNT_INITIALIZED_DATA -> 0x00000040

.data 섹션은 전형적인 데이터 섹션이에요. 그래서 읽기 쓰기가 가능하며, 초기화된 전역 변수들은 모두 이 섹션에 자리를 잡아요. 따라서 메모리 속성은 읽기, 쓰기 속성이 모두 지정되어 있으며 컨테이너 속성은 초기화된 데이터 속성이에요.

.idata 섹션은 임포트된 DLL과 그 함수들에 대한 정보를 담고 있는 섹션이에요. 따라서 프로그램 로드 시에 로더는 이 섹션을 참조하여 해당 DLL을 메모리에 매핑을 시켜요. 그렇기 때문에 당연히 메모리 속성은 읽기 속성을 가져야 해요. 그리고 이 정보는 링크 시에 정해지기 때문에 초기화된 데이터 컨테이너 속성을 가질 수밖에 없어요.

하지만 임포트 섹션은 왜 쓰기 속성을 가질까요? 그 이유는 로더가 DLL을 로드한 후에 이 섹션에 존재하는 DLL에 속하는 임포트 함수에 대한 함수 포인터를 임포트 주소 테이블(IAT)라고 하는 곳에 저장하기 떄문이에요.

임포트 주소 테이블(IAT)이 임포트 섹션 내에 존재하기 때문에 임포트 섹션은 쓰기 속성을 가져야만 하고, 임포트 함수를 사용할 때 임포트 주소 테이블(IAT)를 통해서 함수의 포인터를 얻어서 사용해요.






참고자료


IMAGE_SECTION_HEADER

windows 시스템 실행파일의 구조와 원리 책 내용을 요약 및 정리 하는 포스팅이에요.

주요 섹션들과 정보들의 위치와 크기를 나타내는 IMAGE_DATA_DIRECTORAY에 대한 내용을 알아볼 거에요.


IMAGE_DATA_DIRECTORY의 시작

이 구조체에는 주요 섹션들과 정보들의 위치와 크기를 나타내는 값들이 저장되어 있어요.
구조체 배열의 크기에 대한 값이 매크로 상수 값(IMAGE_UNMBEROF_DIRECTROY_ENTRIES)으로 지정이 되어 있지만, 공식 문서에서는 항상 16개로 고정되어 있지 않다고해요.
따라서 IMAGE_DATA_DIRECTORY 구조체의 배열의 개수를 알기 위해서는 IMAGE_OPTIONAL_HEADER 구조체의 NumberOfRvaAndSizes 멤버를 확인해야 되요.


지금은 16개로 고정되있다고 생각을 할게요!
16개가 있다면 이 구조체는 128바이트로 이루어진 구조체 배열이에요.
그리고 이 배열의 마지막 엔트리, 인덱스 15에 해당하는 엔트리는 언제나 0으로 세팅되야 한다고 해요.

즉, 배열의 인덱스 0부터 NumberOfRvaSizes-1까지의 인덱스가 지정하는 필드가 모두 사용되는 것이 아니라 마지막에 해당하는 인덱스를 제외하고 사용하는 거죠.

각 엔트리들은 나름대로의 의미를 가지고 있고요. 특히 병합된 섹션과 관련된 정보가 엔트리에 들어가기 때문이에요. 그래서 이 필드는 반드시 참조해야 하는 영역입니다.

각 배열의 엔트리의 의미는 이것 또한 “WinNT.h” 헤더 파일에 정의되어 있어요.

1
2
3
4
5
6
7
#define IMAGE_UNMBEROF_DIRECTORAY_ENTRIES 16

typedef struct _IMAGE_DATA_DIRECTORY
{
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTROY, *PIMAGE_DATA_DIRECTROY:

VirtualAddress 와 Size는 각각 해당 IMAGE_DATA_DIRECTORY_ENTRY의 인덱스에 해당하는 미리 지정된 섹션 또는 블록의 정보에 대한 시작 주소와 그 크기를 가리키는 RVA 이에요.

예를 들면, 인덱스 0번은 Export Directory(내보내기 함수 테이블)에 대한 RVA와 크기이고, Import Directory(가져오기 함수 테이블)에 대한 RVA와 크기에요.


IMAGE_DATA_DIRECTORY_ENTRY의 종류

이것 또한 각 인덱스의 의미는 “WinNT.h” 헤더 파일에 정의되어 있죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Directory Entries

#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

0번 인덱스, IMAGE_DIRECTORY_ENTRY_EXPORT

익스포트 테이블(Export Directory Table), IMAGE_EXPORT_DIRECTORY 구조체의 시작 번지를 가리켜요. 이 시작 번지가 익스포트 섹션의 시작 주소에요.


1번 인덱스, IMAGE_DIRECTORY_ENTRY_IMPORT

임포트 테이블(Import Directory Table), IMAGE_IMPORT_DESCRIPTOR 구조체 배열의 번지를 가리켜요. 이 시작 번지가 임포트 섹션의 시작 주소에요.


2번 인덱스, IMAGE_DIRECTORY_ENTRY_RESOURCE

리소스, IMAGE_RESOURCE_DIRECTORY 구조체의 시작 번지를 가리켜요. 이 시작 번지가 리소스 섹션의 시작 주소에요.


3번 인덱스, IMAGE_DIRECTORY_ENTRY_EXCEPTION

예외 핸들러 테이블(Exception Directory Table), IMAGE_RUNTIME_FUNCTION_ENTRY 구조체 배열의 시작 번지를 가리켜요. CPU에 의존적이고, 테이블 기반 예외 핸들링을 위한 것이죠. x86 계열을 제외한 모든 CPU상에서 사용되는데, X86 기반의 윈도우 PE 파일에서는 의미가 없는 영역이에요.

IA-64 CPU, 64비트 CPU에서는 사용하는 영역인거죠!!


4번 인덱스, IMAGE_DIRECTORY_ENTRY_SECURITY

“WinTrust.h” 헤더 파일에 정의되어 있는 WIN_CRETIFICATEW 구조체들의 리스트 시작 번지를 가리켜요.
이 리스트는 메모리 상에 매핑되지 않기 때문에 VirtualAddress 필드 값은 RVA가 아니라 파일 오프셋에 해당되요.


5번 인덱스, IMAGE_DIRECTORY_ENTRY_BASERELOC

기본 재배치(Base Relocation) 정보를 가리켜요.

재배치는 무엇일까요?
로더가 실행 모듈을 원하는 위치, 즉 IMAGE_OPTIONAL_HEADER의 ImageBase 필드에 지정된 가상 주소 공간의 주소에 위치시키지 못했을 때 코드 상의 포인터 연산과 관련된 주소를 다시 갱신해야 하는 경우를 재배치 라고 불러요!

저번에 말했듯이 EXE의 경우에는 이런 상황이 거의 없어요. 그 이유는 EXE 파일보다 DLL이 먼저 메모리에 로딩되기 때문이에요. 그에 반면 DLL의 경우에는 빈번히 발생하죠.
이를 위해 재배치 섹셔이 존재하는데, 이 필드가 재배치 섹션의 시작 주소를 가리켜요.


6번 인덱스, IMAGE_DIRECTORY_ENTRY_DEBUG

IMAGE_DEBUG_DIRECTORY 구조체의 배열을 가리키는 번지에요.
각각 해당 이미지의 디버그 정보를 가지고 있죠.

초기 볼랜드 링커의 경우 이 인덱스에 해당하는 IMAGE_DATA_DIRECTORY 엔트리의 Size 필드를 이 섹션의 바이트 수가 아니라 구조체의 수로 세트했다고 해요.

물론 지금은 섹션의 크기를 가지고 있어요!
따라서 IMAGE_DEBUG_DIRECTORY 구조체의 수를 얻기 위해서는 Size 필드의 값을 IMAGE_DEBUG_DIRECTORY 구조체의 바이트 수(28 바이트)로 나누면 되요.


7번 인덱스, IMAGE_DIRECTORY_ENTRY_ARCHITECTURE

아키텍처에 대한 구체적인 데이터, IMAGE_ARCHITECTURE_HEADER 구조체의 배열에 대한 시작 번지를 가리켜요.

X86 또는 IA-64 계열에서는 사용하지 않아요.
애초에 인텔 계열 CPU를 위해 만들어진 운영체제 이기 때문에 아키텍처에 대한 구체적인 데이터가 필요가 없는거죠!


8번 인덱스, IMAGE_DIRECTORY_ENTRY_GLOBALPTR

글로벌 포인터 레지스터(GP, Global Pointer)로 사용되는 시작 번지를 가리켜요.
x86에서는 사용되지 않지만, IA-64 에서는 사용되요.
여기서 Size 필드는 필요가 없어요. 사용되지 않아요.


9번 인덱스, IMAGE_DIRECTORY_ENTRY_TLS

스레드 지역 저장소 초기화 섹션에 대한 시작 번지를 가리켜요.
별도의 TLS 함수를 사용하지 않고 __declspec(thread)라는 지시어를 통해 변수가 선언되면 이 변수는 TLS에 들어가게 되고 이를 위해 링커는 별도의 TLS 섹션을 만들게 되죠.


10번 인덱스, IMAGE_DIRECTROY_ENTRY_LOAD_CONFIG

IMAGE_LOAD_CONFIG_DIRECTORY 구조체애 대한 시작 번지를 가리켜요.
Windows NT, Windows 2000, Windows XP의 구체적인 정보가 들어가 있다고해요.

이 구조체를 실행 파일에 집어 넣으러면 IMAGE_LOAD_CONFIG_DIRECTORY 구조체를 _load_config_used라는 이름으로 전역적으로 정의 해야 한다고 해요

1
2
3
extern "C"

IMAGE_LOAD_CONFIG_DIRECTORY __load_config_used = {...};

11번 인덱스, IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT

IMAGE_BOUND_IMPORT_DESCRIPTOR 구조체 배열에 대한 시작 번지를 가리켜요.
DLL 바인딩과 관련된 정보를 가리켜요.

이 필드도 중요한 녀석이죠! 나중에 더 상세하게 다룰 예정입니다!


12번 인덱스, IMAGE_DIRECTORY_ENTRY_IAT

첫 번째 임포트 주소 테이블(IAT, Import Address Table)의 시작 번지를 가리켜요.
임포트된 각각의 DLL에 대한 IAT는 메모리 상에서 연속적으로 나타나게 되요.
Size 필드는 모든 IAT의 전체 크기를 가리켜요.

로더는 임포트 섹션을 해석할 동안 이 엔트리의 주소와 크기를 이용해서 임시적으로 IAT들을 읽기/쓰기 모드로 마킹한다고 해요.


13번 인덱스, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT

Visual C++ 이 제공하는 DELAYTMP.H 헤더 파일에 정의되어 있는 ImgDelayDescr 구조체의 배열을 가리키는 시작 번지에요.

지연 로딩 DLL은 해당 API가 처음으로 호출되기 전까지 로드되지 않아요.
윈도우는 지연 로딩 DLL에 대한 어떠한 암시적인 정보고 가지고 있지 않아요.
전적으로 지연 로딩의 정보는 링커나 런타임 라이브러리에 구현되어 있죠.


14번 인덱스, IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR

업데이트된 시스템 헤더 파일에 IMAGE_DIRECTORY_ENTRY_COMHEADER 라는 이름으로 변경되었다고 해요.

이 필드의 정보는 .NET 응용 애플리케이션이나 DLL용 PE를 위한 거라고 해요.
PE 내의 .NET 정보에 대한 최상위 정보의 시작 번지를 가리킨다고 해요.

이 정보 같은 경우에는 IMAGE_COR20_HEADER 구조체의 형태로 구성되었다고 합니다.


마지막으로…

모두 다 알면 좋겠지만, Export Directory, Import Directory, Exception Directory, Base Relocation Table, Bound Import Directory, Import Address Table(IAT), Delay Load Import Descriptors에 해당 하는 섹션들은 확실하게 공부를 해야한다고 생각을 합니다!






참고자료


IMAGE_DATA_DIRECTORY

windows 시스템 실행파일의 구조와 원리 책 내용을 요약 및 정리 하는 포스팅이에요.

PE파일의 PE 시그니처 다음에 이어지는 IMAGE_OPTIONAL_HEADER에 대한 내용을 알아볼 거에요.


IMAGE_OPTIONAL_HEADER의 시작

IMAGE_FILE_HEADER에 이어서 나오는 구조체는 224바이트로 구성된 IMAGE_OPTIONAL_HEADER 이에요.

IMAGE_OPTIONAL_HEADER 구조체는 96바이트를 차지하는 30개의 기본 필드8바이트 크기의 IMAGE_DATA_DIRECTORY 구조체에 대한 엔트리 개수가 16개인 배열 128(=8*16)바이트로 구성되어 있어요.

-> IMAGE_OPTIONAL_HEADER32 구조체를 가지고 설명.

-> 64비트 같은 경우에는 BaseOfData 부분이 없으며, 메모리 영역 부분이 DWORD 형이 아닌 ULONGLONG 형으로 선언이 되어있고, 데이터 디렉토리 구조체의 크기가 고정되어 있지 않음.

구조체는 다음과 같이 “WinNT.h” 헤더 파일에 정의되어 있음.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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;

첫 번째 멤버, WORD Magic

IMAGE_OPTIONAL_HEADER를 나타내는 시그니처에요. 이 값은 32비트 PE의 경우 0x010B이고, 64비트 PE의 경우 0x020B에요.
닷넴 프레임워크 .NET PE의 경우 항상 0x010B 라고 해요.

1
2
#define IMAGE_NT_OPTIONAL_HDR32 0x10B
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20B

두 번째 멤버, BYTE MajorLinkerVersion

세 번째 멤버, BYTE MinorLinkerVersion

PE 파일을 만들어낸 링커의 버전을 나타내요.
예를 드러 메이저 0x09, 마이너 0x0A라면 링커 버전 9.10을 나타내는 거죠.

“Visual Studio.NET Command Prompt”를 실행시켜 Link.exe를 실행시키면 링커의 버전을 확인할 수 있다고 해요.


네 번째 멤버, DWORD SizeOfCode

모든 코드 섹션들의 사이즈를 합한 라운드업된 크기에요.
일반적으로 실행 파일은 하나의 코드 섹션을 가지기 때문에 이 필드는 .text 섹션의 바이트 수와 동일해요.

정확하게 말하면, 섹션 중 “IMAGE_SCN_CNT_CODE” 속성을 가진 섹션들의 전체 크기라고 해요.
이러한 속성들은 IMAGE_SECTION_HEADER의 Characteristics에서 찾아볼 수 있어요.


다섯 번째 멤버, DWORD SizeOfInitializeData

코드 섹션을 제외한 초기화된 데이터 섹션의 전체 크기를 나타내요.
초기화된 데이터 섹션은 “IMAGE_SCN_CNT_INITALIZED_DATA” 속성을 가져요.


여섯 번째 멤버, DWORD SizeOfUninitializedData

초기화되지 않은 데이터 섹션(일반적으로 .bss 섹션 또는 .textbss 섹션)의 바이트수를 나타내요.

초기화되지 않은 데이터 섹션은 “IMAGE_SCN_CNT_UNINITALIZED_DATA” 속성을 가져요.

일반적으로 초기화되지 않은 데이터를 일반 데이터 섹션에 병합시킬 수 있기 때문에 보통 이 필드의 값은 0이 된다.


일곱 번째 멤버, DWORD AddressOfEntryPoint

로더가 실행을 시작할 주소를 나타내요. RVA 형태로 값이 저장되고, 일반적으로 .text 섹션 내의 특정 번지가 되요.
즉, 이 필드의 값은 프로그램이 처음으로 실행될 코드를 담고 있는 주소인 거죠.

좀 더 자세히보면, 프로그램이 로드된 후 이 프로세스의 메인 스레드 문맥(Context)의 EIP/RIP 레지스터가 가질 수 있는 최초의 값이에요.
일반적으로 이 번지가 가리키는 값은 소위 말하는 런타임 시작 루틴(EXE의 경우 WinMainCRTStartup 또는 mainCRTStartup, DLL의 경우 DllMainCRTStartup)의 번지 값이에요.

AddressOfEntryPoint 멤버는 중요한 필드죠!


여덟 번째 멤버, DWORD BaseOfCode

첫 번째 코드 섹션이 시작 되는 RVA값을 가지고 있어요.
코드 섹션은 전형적으로 PE 헤더 다음, 데이터 섹션 바로 직전에 있어요.

MS 링커가 만들어내는 EXE의 RVA는 보통 0x1000인데, 이 값은 설정에 따라 바뀔 수 있어요.


아홉 번째 멤버, DWORD BaseOfData

이론적으로 메모리에 로드될 때 데이터의 첫 번째 바이트의 RVA를 가리켜요.
그리고 이 필드의 값은 MS의 링커 버전에 따라 다르고, 64비트 PE에서는 없는 멤버에요.


열 번째 멤버, ImageBase

해당 PE가 가상 주소 공간에 매핑될 때, 매핑시키고자 하는 메모리 상의 시작 주소에요.

그 주소가 사용되고 있지 않으면 로더는 이 멤버가 가리키는 값으로 매핑시키기를 원해요.

만약에 PE 파일이 ImageBase가 가리키는 주소에 로드되면 로더는 기본 재배치를 수행하는 과정을 건너뛸 수 있어요.

EXE에 대해서 이 값은 기본 이미지 베이스의 값은 0x00400000 이고, DLL의 경우 이 값은 0x10000000 이에요. 이 값들은 링크 시 옵션 /BASE를 지정함으로써 변결될 수 있어요.

추가적으로…

일반적으로 PE 파일이 가상 메모리 공간에 매핑될 때, DLL 파일보다 EXE 파일이 먼저 올라가게 되요. 그래서 EXE 파일이 메모리에 올라갈 때 재배치 과정을 거치지 않아요. 하지만 DLL의 경우에는 그 해당하는 위치가 다른 녀석들이 이미 올라가 있는 경우가 많아서 재배치 과정을 거쳐요.

요즘에는 윈도우의 보호기법 중 하나인 ASLR(Address space layout randomization) 때문에 ImageBase에 있는 주소에 PE 파일들이 매핑되지 않고, 랜덤으로 가상 메모리 공간에 매핑이 되요!


열한 번째 멤버, SectionAlignment

PE 파일이 메모리에 매핑될 때 각 섹션의 시작 주소는 언제나 이 SectionAlignment 필드에서 지정된 값의 배수가 되는 가상 주소가 되도록 보장되요.

이러한 제한이 바로 PE 파일 자체가 메모리 맵 파일임을 말하고 있는 거에요.

일반적인 파일들은 시스템 페이징 파일을 통해서 메모리에 매핑이 되지만, PE 파일 경우에 PE 파일 자체가 페이징 파일이 되기 떄문에 섹션의 시작 주소는 항상 메모리 페이지의 배수이어야 되요.

PE 파일 자체가 메모리 맵 파일이라고 하더라도 메모리에서 PE 파일에 대한 변조가 일어나더라도 실제 파일에는 영향을 끼치지 않아요.

메모리 속성 중 “실행 가능“ 속성을 가지게 되면 이 페이지에 대한 “쓰기“ 동작에 대한 파일로의 반영은 “COW“ 라는 매커니즘을 통해서 페이지에 데이터를 갱신하게 되면 매핑된 파일로의 반영이 아니라 해당 데이터를 그 페이지에 갱신하기 직전에 페이지 파일(PageFile.sys)로 복사되어서 백업된 뒤 해당 페이지가 갱신되요.

따라서 메모리에 매핑된 PE는 “실행 가능”속성을 지니기 때문에 디스크 상의 해당 PE 자체로의 즉각적인 반영이 이루어지지 않는거죠.


열두 번째 멤버, FileAlignment

PE 파일 내에서 섹션들의 정렬 단위를 나타내요.
하드 디스크에 저장된 PE 파일 내의 각각의 섹션을 구성하는 바이너리 데이터들은 FileAlignemnt 필드의 값의 배수로 시작하도록 보장해요.

이 값이 실질적으로 디스크의 섹터 단위가 되는거죠.
NTFS의 경우 한 섹터는 디폴트로 4K이고, 보통 0x200 아니면 0x100의 값을 가지고 있어요.
MS 링커 버전에 따라 기본 값은 변경이 되고, 무조건 이 값은 2의 멱승이 되어야 해요.

만약 SectionAlignment 필드의 값이 CPU 페이지 사이즈보다 작을 경우 이 필드는 SectionAlignment 필드의 값과 같아야 해요.


열세 번째 멤버, MajorOperatingSystemVersion

열네 번째 멤버, MinorOperatingSystemVersion

PE 파일을 실행하는 데 필요한 운영체제의 최소 버전을 말해요.
Windows 10 기반에서 컴파일과 링킹 과정을 거쳤다고 하더라도, 특정 버전 이상에서만 지원되는 기능 같은 것들을 사용하지 않으면 그 이전 버전에서도 돌아갈 수 있기 때문에 운영체제의 최소 버전 값이 정해져요.


열다섯 번째 멤버, MajorImageVersion

열여섯 번째 멤버, MinorImageVersion

유저가 정의 가능한 필드이며, 사용자가 만드는 EXE나 DLL에 유저 나름대로의 버전을 넣을 수 있어요.
링킹 시에 /VERSION 옵션을 사용해서 링킹을 해주면 되요.


열일곱 번째 멤버, MajorSubsystemVersion

열여덟 번째 멤버, MinorSubsystemVersion

PE 파일을 실행하는 데 필요한 서브시스템의 최소 버전을 말해요.
링킹 시에 /SUBSYSTEM 스위치로 변경할 수 있어요.


열아홉 번째 멤버, Win32VersionValue

이 필드는 VC++ 6.0 SDK 까지는 예약 필드였는데, 7.0에 와서 Win32VersionValue 라는 이름을 가진 필드로 바뀌었고, 보통 0으로 결정되요.


스물 번째 멤버, DWORD SizeOfImage

이 멤버는 로더가 해당 PE 파일을 메모리 상에 로드할 때 확보/예약해야 할 해당 PE를 위한 충분한 크기 값을 가져요.
PE 파일의 크기와 같을 수도 있지만 PE 파일 상에서의 섹션의 배치가 메모리에 매핑되면서 달라질 수 있기 때문에 보통은 PE 파일의 크기보다 커요.

이 필드의 값은 반드시 SectionAlignment 필드의 값의 배수가 되어야 해요.


스물 첫 번째 멤버, DWORD SizeOfHeaders

이 필드는 MS-DOS 헤더, PE 헤더, 섹션 테이블들의 크기를 모두 합친 바이트의 수에요.
모든 헤더나 테이블들은 PE 파일 상에서 반드시 코드 또는 데이터 섹션의 앞쪽에 위치에 있어야 해요.

이 필드의 값은 FilaAlignment 필드 값의 배수가 되어야 해요.


스물 두 번째 멤버, DWORD CheckSum

이름 그대로 이미지의 체크섬 값이다. PE 파일의 체크섬 값은 IMAGEHELP.DLL의 CheckSumMAppedFile API를 통해서 얻을 수 있으며, 체크섬 값은 커널 모드 드라이버나 시스템 DLL의 경우 요구 된다.
그 외의 경우라면 보통 0으로 설정이 된다.
/RELEASE 링커 스위치를 통해 이 필드를 설정을 할 수 있다.


스물 세 번째 멤버, WORD Subsystem

Win32 아키텍처는 크게 유저 모드와 커널 모드로 나눌 수 있으며, 유저 모드에는 서브시스템이라는 컴포넌트가 존재해요.

Win32에서 지원하는 기본 서브 시스템은 Win32 서브시스템, 이전 OS/2와의 호환을 위한 OS/2 서브시스템, 이전 UNIX와의 호환을 위해 최소 표준으로 지원되는 POSIX/CUI 서브시스템 이렇게 3가지 정도가 있다고 해요.

어떤 서브시스템을 지원하는지 알고 싶으면, “WinNT.h” 헤더 파일에 정의되어 있으니, 그것을 참고하면 되요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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
#define IMAGE_SUBSYSTEM_XBOX_CODE_CATALOG 17

대부분의 경우 IMAGE_SUBSYSTEM_WINDOS_GUI 또는 IMAGE_SUBSYSTEM_WINDOWS_CUI 이에요.

WinMain()으로 시작하는 경우 IMAGE_SUBSYSTEM_WINDOWS_GUI 인 0x0002가 될 것이고, main()으로 시작하는 콘솔 응용 애플리케이션의 경우 IMAGE_SUBSYSTEM_WINDOWS_CUI인 0x0003이 될거에요.

그 이외의 다바이스 드라이버 같이 별도로 서브시스템을 사용하지 않는 경우 IMAGE_SUBSYSTEM_NATIVE인 0x00001의 값을 가지게 되요.


스물 네 번째 멤버, WORD DllCharacteristics

이 멤버는 PE가 DLL 이라는 전제 하에 어떤 상황에서 DLL 초기화 함수(DLLMain())을 호출 되어야 하는지를 지시하는 플래그 값이 저장되어 있어요.

“WinNT.h” 헤더 파일에 이 필드에 집어 넣을 수 있는 매크로 정의가 되어 있어요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// DllCharacteristics Entries

// IMAGE_LIBRARY_PROCESS_INIT 0x0001 // Reserved.
// IMAGE_LIBRARY_PROCESS_TERM 0x0002 // Reserved.
// IMAGE_LIBRARY_THREAD_INIT 0x0004 // Reserved.
// IMAGE_LIBRARY_THREAD_TERM 0x0008 // Reserved.


#define IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA 0x0020 // Image can handle a high entropy 64-bit virtual address space.
#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040 // DLL can move.
#define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080 // Code Integrity Image

#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100 // Image is NX compatible
#define IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200 // Image understands isolation and doesn't want it
#define IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400 // Image does not use SEH. No SE handler may reside in this image
#define IMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800 // Do not bind this image.

#define IMAGE_DLLCHARACTERISTICS_APPCONTAINER 0x1000 // Image should execute in an AppContainer
#define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000 // Driver uses WDM model
#define IMAGE_DLLCHARACTERISTICS_GUARD_CF 0x4000 // Image supports Control Flow Guard.
#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0x8000

이 중 DllMain() 정의시 아래의 대응 관계처럼 이 진입점 함수의 파라미터로 넘어오는 fdwReason의 가능한 네가지 값과 동일한 위상이라고 볼 수 있어요.

  • IMAGE_LIBRARY_PROCESS_INIT < - > DLL_PROCESS_ATTACH
  • IMAGE_LIBRARY_PROCESS_TERM < - > DLL_PROCESS_DEATCH
  • IMAGE_LIBRARY_THREAD_INIT < - > DLL_THREAD_ATTACH
  • IMAGE_LIBRARY_THREAD_TERM < - > DLL_PROCESS_DETACH

하지만 이 필드의 값은 EXE의 경우에도 0이며, DLL의 경우에도 0이에요.
MS는 위의 예약어에 해당하는 네 가지 플래그를 더이상 사용하지 않고 IMAGE_DLLCHARACTERISTIC_XXX 라는 플래그들을 추가했어요.

  • IMAGE_DLLCHARACTERISTICS_NO_SEH ( 0x0400)

    구조적 예외 핸들링(SEH, Structured Exception Handling)을 사용하지 않는다. 구조적 예외 핸들링은 Win32 시스템에서 제공하는 기능(C++ 에서의 try{…}catch(…){…{ 예외 제어 구문과 비슷한 기능)이다.

    Visual C++ 컴파일러의 경우 이 SEH를 지원하기 위해 try{…}except(…){…} 라는 키워드를 제공한다. 이 플래그의 경우 VC++ 6.0에서는 지원하지 않음.

  • IMAGE_DLLCHARACTERISTICS_NO_BIND ( 0x0800)

    이 플래그는 이 이미지를 바인딩하지 않음을 의미함. VC++ 6.0 에서는 없는 정의

  • IMAGE_DLLCHARACTERISTICS_WDM_DRIVER ( 0x2000)

    드라이버가 WDM 모델을 사용한다는 의미. WDM은 Windows Driver Model의 약자로서 모든 Microsoft Windows 운영체제에 걸쳐서 호환 가능한 소스코드로 구성되는 디바이스 드라이버를 작성할 수 있도록 소개된 모델임.

    WDM 룰을 따르는 커널 모드 드라이버를 WDM 드라이버라고 부름. DDK의 서브셋의 일종임.

  • IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE ( 0x8000)

    이 플래그는 터미널 서버가 터미널 서비스가 인식하지 못하는 애플리케이션을 로드시켰을 경우 실행 가능하도록 하기 위해 호환 가능한 코드를 담고 있는 DLL도 같이 로드시킬 수 있음을 의미함.


스물 다섯 번째 멤버, DWORD SizeOfStackReserver

스물 여섯 번째 멤버, DWORD SizeOfStackCommit

스물 일곱 번째 멤버, DWORD SizeOfHeapReserve

스물 여덟 번째 멤버, DWORD SizeOfHeapCommit

프로세스는 가상 주소 공간에 자신만의 스택과 힙을 별도로 가져요.
프로세스 생성 시 시스템은 언제나 메인 스레드를 위한 디폴트 스택과 프로세스를 위한 디폴트 힙을 해당 프로세스 내에 생성 시켜 주는데, 이 스택과 힘의 크기와 속성에 관계된 설정을 이 필드에서 지정해요.

PE 파일의 경우 로드되면서 하나의 프로세스가 되든지, DLL 이라면 특정 프로세스의 주소 공간 내부에 잠입해 들어가게 되는데요.
이때 스택과 힙의 예약 크기와 커밋 크기를 지정해줄 수 있어요.

PE 파일이 메모리에 로드될 때 시스템은 이 필드들의 값을 참조하여 해당 프로세스에 디폴트 스택과 디폴트 힙을 만들어 줘요.

링커가 만들어낸 디폴트 값은 스택과 힙 모두 예약 크기 1M인 0x00100000 이며 커밋 크기는 1페이지에 해당하는 4K인 0x00001000이에요.


스물 아홉 번째 멤버, DWORD LoaderFlags

이 필드는 0으로 설정이되요. 원래 목적은 디버깅 지원에 관계 되었다고 해요.


서른 번째 멤버, DWORD NumberOfRvaAndSizes

이 필드는 바로 다음에 나올 주요 섹션들과 정보들의 위치와 크기를 나타내는 IMAGE_DATA_DIRECTORY 구조체 배열의 원소 개수를 의미해요.
책 에서는 이 구조체의 개수는 항상 16라서, 이 멤버의 값은 언제나 0x00000010 이라고 해요.

하지만 공식 문서에서 항상 그렇지 않다고 해요. 따라서 IMAGE_DATA_DIRECTORY 구조체 배열의 개수를 알기 위해서는 이 멤버의 값을 확인 해야 되요.


마지막으로…

IMAGE_OPTIONAL_HEADER에서 기억하고 가야할 멤버들은 AddressOfEntryPoint, ImageBase, SectionAlignment, FileAlignment, SizeOfImage, NumberOfRvaAndSizes 라고 생각해요.

AddressOfEntryPoint는 OP의 주소 값이 들어가고,
ImageBase는 가상 주소 공간에 매핑될 때, 매핑시키고자 하는 메모리 상의 주소 값이고,
SectionAlignment는 메모리에 매핑될 때, 섹션의 크기를 확인할 때 사용하는 녀석이고,
FileAlignemt는 파일 내에서, 섹션의 크기를 확인할 때 사용하는 녀석이고,
SizeOfImage는 PE 파일을 메모리 상에 로드할 때 확보해야할 크기 값을 가지고 있는 녀석이고,
NumberOfRvaAndSizes는 PE 파일 안에 있는 섹션들의 위치와 크기를 나타내는 녀석이므로 기억해야 한다고 생각해요!






참고자료


IMAGE_OPTIONAL_HEADER

windows 시스템 실행파일의 구조와 원리 책 내용을 요약 및 정리 하는 포스팅이에요.

PE파일의 PE 시그니처 다음에 이어지는 IMAGE_FILE_HEADER에 대한 내용을 알아볼 거에요.


IMAGE_FILE_HEADER 구조체

IMAGE_FILE_HEADER 구조체는 20바이트로 구성이 되어 있어요.
이것도 당연히 “WinNT.h” 헤더 파일에 정의되어 있어요.

1
2
3
4
5
6
7
8
9
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;

구조체 멤버에 대해 하나씩 설명을 해드릴게요.


첫 번째 멤버, WORD Machine

Machine 필드는 PE 파일의 CPU ID를 나타내요. 즉, 사용자가 어떤 CPU를 통해서 PE 파일을 생성했는지 알 수 있어요.

C언어와 C++로 짜여진 코드 같은 경우에는 CPU 의존적이라서 이런 정보가 들어가 있나봐요.

이것도 CPU ID에 해당하는 매크로 상수가 정의되어 있는데, 자주 사용되는 매크로 상수는 아래와 같아요

1
2
#define IMAGE_FILE_MACHINE_IA64              0x0200  // Intel 64
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)

두 번째 멤버, WORD NumberOfSections

PE 파일에서의 섹션의 수를 나타내요. IMAGE_SECTION_HEADER 구조체 배열의 원소의 개수. 즉, 해당 PE 파일의 섹션의 개수를 의미해요.


세 번째 멤버, DWORD TimeDateStamp

OBj 파일이라면 컴파일러가, EXE 또는 DLL과 같은 PE 파일이라면 링커가 해당 파일을 만들어낸 시간을 의미해요.

이 값은 1970년 1월 1일 09시(GMT 시간 기준)로부터 해당 파일을 만들어낸 시점까지의 초 단위로 표현해요.


네 번째 멤버, DWOARD PointerToSymbolTable

PointerToSymbolTable 필드는 말 그대로 심볼 테이블의 데이터를 가리키는 주소 값이 들어가는 영역이에요. 즉, COFF 심벌의 파일 오프셋 값을 가지고 있는 거에요.

이 필드 같은 경우에는 컴파일러에 의해 생성되는 OBJ 파일 또는 디버그 모드로 만들어져서 COFF 디버그 정보를 가진 PE 파일에서만 사용이 되요.

PE 파일은 복수의 디버그 포맷을 지원하기 떄문에 IMAGE_DIRECTORY_ENTRY_DEBUG 엔트리를 참조해야 되고요.

요즘에는 COFF 심벌 테이블은 새로운 디버그 포맷에 의해 대체되어서 PE 파일 내에서는 거의 사용하지 않아요.
물론 링커 스위치를 /DEBUGTYPE:COFF로 지정할 경우 생성할 수 있어요.


다섯 번째 멤버, DOWRD NumberOfSymbols

PointerToSymbolTable 필드가 가리키는 COFF 심벌 테이블 내에서의 심벌의 수를 나타냄.

이것도 마찬가지로 새로운 디버그 포맷에 의해 대체되어서 사용되지 않음.


여섯 번째 멤버, WORD SizeOfOptionalHeader

IMAGE_FILE_HEADER 구조체 바로 다음에 이어서 나오는 IMAGE_OPTIONAL_HEADER 구조체의 바이트 수를 나타내요.

이 필드는 OBJ 파일의 경우 0이고, 실행 파일의 경우는 “sizeof(IMAGE_OPTIONAL_HEADER)”의 값을 가져요.

32비트 PE 파일의 경우 0xE0(224)바이트이고, 64비트 PE 파일의 경우 0xF0(240)바이트에요.


일곱 번째 멤버, WORD Characteristics

PE 파일에 대한 특정 정보를 나타내는 플래그에요. 이 필드도 “WinNT.h” 헤더 파일에 정의되어 있어요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved external 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 // Aggressively 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_RELOCS_STRIPPED 0x0001 현재 파일에 재배치 정보가 없음
IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 본 파일은 실행 파일 이미지이다.
IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 본 파일에 라인 정보가 없다.
IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 본 파일에 로컬 심벌이 없다.
IMAGE_FILE_AGGRESIVE 0x0010 OS로 하여금 적극적으로 워킹셋을 정리할 수 있도록 한다
IMAGE_FILE_LARGE_ADD_RESS_AWARE 0x0020 애플리케이션이 2G 이상의 가상 주소 번지를 제어할 수 있도록한다.
IMAGE_FILE_32BIT_MACHINE 0x0100 본 PE는 32비트 워드 머신을 필요로 한다.
IMAGE_FILE_DEBUG_STRIPPED 0x0200 디버그 정보가 본 파일에는 없고 .DBG 파일에 존재한다.
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 PE 이미지가 이동 가능한 장치 위에 존재할 때 고정 디스크 상의 스왑 파일로 카피해서 실행한다.
IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 PE 이미지가 네트워크 상에 존재할 때 고정 디스크 상의 스왑 파일로 카피해서 실행한다.
IMAGE_FILE_DLL 0x2000 본 파일은 동적 링크 라이브러리 파일이다.
IMAGE_FILE_UP_SYSTEM_ONLYT 0x4000 본 파일은 하나의 프로세서만을 장착한 머신에서 실행된다.

마지막으로…

IMAGE_FILE_HEADER에 대해서 살펴봤는데요.
여기서 중요한 멤버들은 섹션의 수를 조절할 수 있는 NumberOfSections 하고, PE 파일에 대한 특정 정보는 나타내는 플래그인 Characteristics 이에요.

NumberOfSections을 조절하면, 우리가 원하는대로 임의의 섹션을 추가할 수 있어요. 이러한 방법을 이용해서 악성코드를 삽입도 가능하다고 해요!

PE 파일에 특정 정보를 나타내는 플래그인 Characteristics 값의 조합을 알고 있으면, PE 파일을 분석할 필요 없이 이 값만을 가지고 PE 파일이 어떤 특징을 가지고 있는지 알 수 있을 거에요.






참고자료


IMAGE_FILE_HEADER

windows 시스템 실행파일의 구조와 원리 책 내용을 요약 및 정리 하는 포스팅이에요.

PE파일의 맨 앞부분에 해당하는 IMAGE_NT_HEADER에 대한 알아볼 거에요.

이 부분은 내용이 길어서 여기서는 간단한 구조체 설명만 할거에요.


PE 파일의 시작

IMAGE_NT_HEADER은 진정한 PE파일의 시작을 알리는 신호이면서, PE에 관계된 많은 부분을 담고 있어요.

이것도 마찬가지로 IMAGE_NT_HEADER도 “WinNT.h” 헤더 파일에 정의되어 있어요.

1
2
3
4
5
6
typedef struct _IMAGE_NT_HEADERS
{
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADER32, *PIMAGE_NT_HEADER32;

여기서 DWORD 형인 Signature 필드는 PE 파일을 나타내는 매직넘버에요. 이 값도 마찬가지로 “WinNT.h” 헤더 파일에 정의되어 있어요.

1
2
3
4
5
#define IMAGE_DOS_SIGNATURE                 0x5A4D      // MZ
#define IMAGE_OS2_SIGNATURE 0x454E // NE
#define IMAGE_OS2_SIGNATURE_LE 0x454C // LE
#define IMAGE_VXD_SIGNATURE 0x454C // LE
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00

PE 파일임을 확인하는 방법?

PE 파일을 확인하는 방법은 아주 간단해요.
IMAGE_DOS_HEADER 구조체의 마지막 필드인 e_lfanew가 가리키는 값만큼 파일 포인터를 이동시켜 그 오프셋부터 DWORD형으로 값을 읽고, 그 값을 IMAGE_NT_SIGNAUTRE 매크로 상수와 비교하기만 하면 되요.


마지막으로…

IMAGE_NT_HEADER 구조체는 매직넘버 필드뿐만 아니라, PE에 관계된 부분을 설명해주는 다른 구조체들도 포함되어 있어요. IMAGE_FILE_HEADERIMAGE_OPTIONAL_HEADER 에요.

여기서는 IMAGE_NT_HEADERS 중에서 32비트용 IMAGE_NT_HEADER32를 설명을 했어요. 64비트용 IMAGE_NT_HEADER64도 있다는 것을 잊지 마세요!
그리고 이 구조체에 필드로 들어있는 IMAGE_OPTIONAL_HEADERS 도 32비트용인 IMAGE_OPTIONAL_HEADER32, 64비트용인 IMAGE_OPTIONAL_HEADER64가 있어요.

32비트용과 64비트용은 별다른 차이가 없어요.
대부분의 구조가 거의 같기 때문에 32비트용을 완벽하게 이해하고 차후에 64비트용에서 쓰이는 부분과 바뀐 부분만 체크하면 되요






참고자료


IMAGE_NT_HEADER32
IMAGE_NT_HEADER64

windows 시스템 실행파일의 구조와 원리 책 내용을 요약 및 정리 하는 포스팅이에요.

PE파일의 맨 앞부분에 해당하는 DOS 스텁에 대한 내용을 알아볼 거에요.


DOS 스텁의 시작

PE 파일은 몇십 바이트의 MS-DOS 스텁으로 시작 하고, MS-DOS 스텁은 예전의 MS-DOS 시절때의 MZ 포맷으로 PE 포맷에서는 큰 의미가 없다고 해요.

MS-DOS 스텁은 PE 파일을 도스 환경에서 실행 시킬 때 “This program cannot be run in DOS mode” 라는 문장을 출력하기 위한 조그마한 16비트 도스용 응용 프로그램에 불과해요.
그리고 DOS 스텁은 40바이트의 IMAGE_DOS_HEADER 구조체와 해당 프로그램 코드로 이루어져 있어요.


IMAGE_DOS_HEADER 구조체

IMAGE_DOS_HEADER 구조체는 “WinNT.h” 헤더 파일에 정의되어 있다고 해요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _IMAGE_DOS_HEADER // DOS .EXE header
{
WORD e_magic; // Magic number
WORD e_cblp; // Byte 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; // Checksum
WORD e_ip; // Initital 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 구조체는 64바이트로 구성되어 있어요.
PE 파일에서 0x0000003F 까지가 IMAGE_DOS_HEADER 구조체에요.
이 구조체에서 실제로 쓸모가 있는 것은 첫 번쨰 필드인 e_magic과 마지막 필드인 e_lfanew 이에요. 그 이외에 나머지는 사용되지 않아요.


첫 번째 필드인 e_magic

모든 PE 파일은 IMAGE_DOS_HEADER로 시작해요.
이때 e_magic은 ASCII “MZ” 로 고정되어 있어요. 위 구조체에서는 e_magic WORD 형으로 되어 있는데, “MZ”을 워드로 읽게 되면 0x5A4D가 되요.

여기서 0x5A=’Z’, 0x4D=’M’ 에 해당해요.
인텔 cpu 같은 경우에는 리틀 엔디번 방식으로 메모리를 정렬하기 때문에 0x5A4D로 값을 저장하게 되요.

이러한 “MZ”도 “WinNT.h” 헤더 파일에 매크로로 정의되어 있어요.

1
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ

마지막 필드인 e_lfanew

IMAGE_DOS_HEADER 구조체에서 e_flanew 필드에는 실제 PE 파일의 시작이라고 할 수 있는 IMAGE_NT_HEADER 시작 오프셋 값을 가진다고 해요.

예를 들어서 DWORD 형으로 정의된 e_flanew의 값이 0x000000F0 이라면, 실제 PE 파일의 시작은 0x000000F0 이고, 여기서 PE 파일의 시그니쳐인 “PE”를 볼 수 있어요.


마지막으로…

IMAGE_DOS_HEADER 와 DOS 스텁은 MS-DOS 시절의 유산물이에요. 우리는 여기서 PE 파일내에서 사용하지 않는 부분이 있다는 것을 알았어요. 앞으로도 PE 파일내에서 사용하지 않는 부분이 존재할거에요. 그리고 이러한 부분들을 이용해서 패커 같은 것을 구현해서 PE 파일의 크기를 줄이고, 리버싱을 하기 어렵게 만들었다고 해요!!

우리가 IMAGE_DOS_HEADER에서 e_magic의 값이 0x5A4D 이어야지 PE 파일이라는 것을 알 수 있다는 사실과 e_lfanew의 값에 따라서 IMAGE_NT_HEADER의 시작 오프셋 값을 알 수 있다는 것만 알면 되요.

어젯밤에 이마트에서 햄스터를 봤는데, 너무 귀여운거 있죠
그 중에서 가장 유독 눈이 띄는 아이가 있었어요.
다른 아이들은 친구들과 놀고 있는데, 펄 햄스터라고 불리는 아이 한명이 계속 잠만 자고 있는 거에요.
이 친구가 내 맘을 흔들더라고요.

지금 당장 데리고 가고 싶었는데, 지금은 분양 받을 수 없다고 하더라고요.
그 떄 시간이 오후 10시 30분이었으니까… ㅎㅎㅎ
데려갈 수 없었던게 당연한 걸지도 몰라요.
덕분에 햄스터를 키우는 데 필요한 용품들을 더 자세하게 찾아보게 되었어요.
천만다행이었죠!!
그때 햄스터를 데리고 왔다면, 햄스터는 좋은 환경에서 살지도 못하고, 스트레스를 받았을 거에요

아 그리고 다음날 데리고 왔는데, 아라찌(햄스터)가 은신처에서 잠을 자고 있는데, 관리 하시는 분이 오셔서 은신처에서 쉬고 있는 얘를 탁탁 털어서 꺼내더라고요. 순간 가슴이 철렁했답니다.


햄스터에게 필요한 것들

너무 데리고 데리고 가고 싶어서, 필요한 것들을 이것저것 찾아 봤어요.
햄스터를 기르는데 필수적으로 있어야 하는 것들은
햄스터 케이지, 햄스터 집, 쳇바퀴, 급수기, 급식기, 베딩, 이라고 해요. 일단은 이렇게 7가지가 필수적으로 있어야 된다고 해요.

그 중 가장 중요한 것은 햄스터 케이지에요!


햄스터 케이지

햄스터한테 케이지는 가장 중요한 물품에 속해요.
케이지의 크기에 따라서 햄스터의 활동반경이 정해지는 지거든요.

케이지의 종류는 여러가지가 있어요.
철장 케이지, 채집통 케이지, 수족관 케이지, 터널형 케이지, 리빙박스 케이지이 있어요.

옛날에는 햄스터를 유통될 때, 가장 손쉽게 구할 수 있는 철장 케이지를 많이 사용했다고 해요.
요즘은 철장 비스무리한 플라스틱 케이지도 많이 유통이 되는 것 같아요. 이마트에서 이런 비스무리한
플라스틱 철장 케이지를 판매를 하고 있어요.

채집통 케이지는 소,중,대 사이즈가 있다고해요.
보통 일반적으로 채팁통의 크기는 햄스터를 기르기에는 작다고 해요. 일반적으로 햄스터를 다른 곳으로 이동시킬 떄, 이동장 역할로 많이 사용한다고 해요.

수족관 케이지는 공간이 협소한 우리나라에서는 볼 수 없다고 해요. 보통 일반적으로 서양권에서 볼 수 있다고 해요. 기다란 수족관에다가 햄스터가 살 수 있는 환경을 구성한다고 해요.
햄스터들이 완전 기부니가 좋아서 방방 뛸 것 같아요 ㅎㅎ

터널형 케이지는 햄스터들이 굴속에서 삶을 사는 그 환경을 그대로 재현할 수 있다고 해요. 하지만 가격이 비싸고 청소하기도 힘들어서 터널형 케이지는 자주 볼수 없는 케이스라고 합니다.

리빙박스 케이지는 우리나라에서 요즘에 흔히 볼 수 있는 햄스커 케이지라고 해요. 철장 케이지는 햄스터들이 다칠 수 있을 뿐만 아니라 크기가 커질 수록 가격이 상승하거든요. 수족관 케이스도 마찬가지죠.

그래서 저렴한 리빙박스를 이용해서 햄스터 케이지를 개조하는 경우가 많다고 해요.
드워프 햄스터는 못해도 리빙박스 55L 짜리를 구매를 해야 한다고 하고요. 골든 햄스터는 못해도 68L 짜리를 구매를 해야 한다고 해요.

그래서 아라찌(햄스터)를 데리고 올 때, 리빙박스 55L 짜리로 구매를 했어요. 빠르게 데려오고 싶은 마음 때문에 120L 짜리는 구매를 하지 못한게 아쉬워요.
대신 차후에 리빙박스에 구멍을 뚫어서 터널을 연결해서 더 큰 공간을 만들어줄 생각이에요!


햄스터 집

햄스터 집하고 햄스터 케이지는 다른 거에요. 햄스터를 키울 때, 햄스터의 넓은 활동반경을 조성을 할수가 없어요. 웬만한 부자가 아닌 이상은… 그래서 우리는 타협을 본거에요. 햄스터 케이지를 이만큼의 크기로 햄스터를 기르자. 즉, 햄스터 케이지의 크기는 햄스터의 활동반경의 크기에요.

햄스터 집은 말그대로 자신만의 영역인거에요! 우리도 엄청나게 넓은 지구에서 살아가고 있잖아요. 그리고 자기 자신만의 집이 존재하죠? 햄스터도 마찬가지에요. 햄스터도 집이 필요해요. 즉, 햄스터 집, 은신처를 만들어주는 거에요.

은신처는 2~3개 있으면 좋다고해요. 햄스터들이 번갈아가면서 사용한다고 해요. 은신처에다가 먹이를 보관하기도하고 그런다고 하네요 ㅎㅎ


쳇바퀴

다른 햄스터를 키우는 분들이 말씀하시길, 햄스터에게 쳇바퀴를 제공하지 않으면 동물 학대라고 합니다. 무조건 쳇바퀴를 구매를 해야 한다고 하더라고요.

그래서 쳇바퀴에 대해서 찾아봤어요. 싼 가격부터 비싼 가격까지 다양하게 있어요. 당연히 비싼게 가장 좋겠죠? 그렇지만 언제나 비싸고 좋은 제품을 살 수가 없용. 우리도 비싼게 좋다는 것을 알지만 항상 비싼것만 사면서 살지는 않잖아요. 그래서 가성비를 본다고 해요.

쳇바퀴는 아크릴 쳇바퀴가 최고봉이라고 해요. 소리도 안나고, 햄스터들이 다칠 이유도 없다고 해요. 하지만 가격이 3만원 이상을 한다고 해요.

저의 개인적인 생각으로는 발빠짐 쳇바퀴류만 아니면 괜찮다고 생각해요. 저는 소음에 민감한 편이 아니라서 햄스터들이 쳇바퀴 돌리는 시간이 짜증이 나지 않거든요. 그리고 돈이 넉넉한 편은 아니라서 가성비를 생각해서 저같은 경우에는 사일런트 휠을 구매를 했어요. 이거 같은 경우에는 만원정도에 인터넷에서 구매를 할 수 있어요.

아직 쳇바퀴를 케이지 안에 배치를 못해서… 저는 햄스터를 학대하고 있답니다. ㅠㅠㅠ 쳇바퀴가 배송되자마자 바로 케이지에 넣어줄 거에요


급수기와 급식기

급식기 같은 경우에는 다이소에서 파는 자그마한 그릇으로 충분해요. 거기에다가 햄스터의 몸무게의 12% 해당하는 양만 배식하면 된다고 해요. 햄스터도 인간처럼 편식을 하는 동물이라고 해요. 그래서 원하는 것만 먹다보면, 쉽게 우리처럼 비만이 될 수 있다고 하니 음식 조절을 잘 해줘야 된다고 해요 ㅎㅎㅎ

그리고 편식을 하지 않는 햄스터 같은 경우에는 큰 그릇에 하루 분량이 아니라 며칠치 분량을 놓아도 된다고 해요. 햄스터의 습성중 하나가 먹을 것들을 은식처에 숨기는 거라고 해요. 급식기에 먹을 것이 있다면, 햄스터들이 그 자리에서 먹지 않고 볼주머니에 음식을 가득 담고 은신처로 파파팍 가는 모습을 볼 수 있을 거에요.

급수기는 그릇으로 해도 되고, 햄스터용으로 나온 전용 급수기를 사용해도 된다고 해요.
급수기는 종류가 여러가지라고 해요. 볼 급수기, 진공 급수기 하나 더 있었는데 기억이 안나네요. ㅎㅎ

볼 급수기는 물이 누수가 발생하기도 하는데, 그런 것을 방지하기 위해서 만든게 진공 급수기라고 해요. 그래서 진공 급수기가 볼 급수기보다 비싸죠. 그렇다고 하더라도 그렇게 비싼 금액은 아니에요 ㅎㅎ

그릇 같은 경우는 잘 사용하지 않는다고 해요. 그릇에 베딩이 들어갈 수도 있고, 햄스터들이 움직이면서 그릇에 이물질이 들어갈 수 있다고 해서 그릇은 깨끗하지 않다고 해요.그래서 그릇은 비추를 한다고 합니다.

저 같은 경우에는 아라찌(햄스터)에게 거치대형 볼 급수기를 줄거에요. 리빙박스를 벽면을 뚫어서 거는 것보다는 바닥에 배치할 생각이에요. 아직 급수기가 안와서 그릇을 사용하고 있지만, 살짝 미안해지네요 ㅠㅠㅠ


베딩

햄스터에게 중요한 것이라고 해요. 햄스터의 잠자리가 될 수도 있고, 햄스터의 놀이터가 될 수도 있거든요. 햄스터들이 파묻힐 수 있을 정도로 베딩을 깔아줘야 된다고 해요.

베딩의 종류도 여러가지가 있어요. 톱밥도 있고, 종이 베딩도 있고, 천 베딩도 있어요. 톱밥 하고 종이 베딩은 햄스터의 면역력에 따라서 사용하면 된다고해요. 그 중에서도 끝판왕이 당연히 존재하겠죠? 그게 바로 아스펜 베딩이라고 해요. 다른 것들에 비해 가격이 비싸지만, 햄스터들에게 안전성이 보장된 베딩이라고 해요. 그리고 천 베딩은 웬만하면 사용하지 말라고 하네요. 천 베딩 같은 경우에는 꾸준히 갈아줘야 되서 여러 개를 구매할 뿐만 아니라 세탁을 하는 것도 귀찮고 햄스터들의 본능인 디깅을 하지 못한다는게 단점이에요. 물론 따로 디깅박스를 만들어준다고 하더라도 전 개인적으로 천 베딩을 하지 않을 것 같아요.

하지만 햄스터가 아프다면, 천 베딩을 할거나, 베딩을 아에 깔지 않을지도 몰라요. 햄스터가 아플 경우에는 의료목적으로 베딩을 깔지 않는다고 해요.

그리고 베딩에는 압축 베딩 비압축 베딩이 있다고해요. 압축 베딩은 말그대로 압축해서 베딩을 포장한 것이고요, 비압축 베딩은 압축하지 않은 상태로 베딩을 포장한 것이에요.
압축 베딩이 비압축 베딩에 비해 먼지가 많다고해요. 어찌됬든 압축 베딩과 비압축 베딩 둘다 먼지를 털어내고 사용하는 것이 좋다고 해요. 저도 알았다면, 먼지를 털어주고 깔아줬을 텐데… 먼저 데려오고 준비를 하다보니까… 미숙한 저의 모습이 보이는 것 같아요.

저 같은 경우네느 펫 리터의 종이 베딩을 사용하고 있어요. 이마트에서 9000원 주고 구입했어요 ㅎㅎ. 55L 짜리 리빙박스에 이것을 다 부으니까 햄스터가 굴도파고 푹 파고 들어갈 수 있더라고요


사람에게 있어서 밥이 중요하듯이 햄스터한테도 밥이 중요해요. 사람들도 건강을 지키는 사람들은 영분을 따져가면서 밥을 먹듯이 햄스터도 건강을 지킬러면 영양분을 따져가면서 먹어야 해요.
마트에서 파는 일반적인 사료들은 햄스터의 영양분을 충족시키지 못하는 저급사료라고 해요.
여기서 저급 사료는 사료의 질이 좋지 않다는게 아니라, 햄스터에게 영양분을 골고루 주지 못한다는 의미에요.

그래서 좀더 찾아보면서 우리 햄스터에게 좋은게 뭐가 있을지 찾아볼 계획이에요 ㅎㅎ


기타 등등

햄스터들은 일반적으로 대소변 훈련을 시킬수가 없다고 해요.
하지만 시킬수 있다고해요!!! 햄스터들은 소변을 볼 때, 가장자리에 소변을 보는 경향이 있는데, 그 부분을 잘 관찰해서 거기다가 화장실을 만들면, 거기서 소변을 보게 만들 수 있다고 해요.
똥 같은 경우에는 아무곳에서나 싸기 때문에 가릴수가 없다고 해요.

똥 같은 경우에는 햄스터들이 자기가 어느 한 곳에다가 모아두는 경향이 있기도 하지만, 웬만해서는 그냥 흩뿌리고 다닌다고 하네요 ㅎㅎ 우리 햄스터들 귀엽죵 ㅎㅎㅎ

저는 마트에서 파는 목욕용 모래를 가지고 왔어요. 근데 이게 알고보니까 엄청 햄스터들에게 안 좋다고 하더라고요. 그래서 햄스터들한테 좋은 사막모래로 바꿔줄 예정입니다. 안 좋은 이유는 목욕용 모래가 입자가 고와서 햄스터에 기관지에 들어가면 좋지 않다고 합니다. 사람도 마찬가지죠?

우리 아라찌(햄스터)가 목욕용 모래에서 소변을 봤어요!!! 그것도 처음 데려온 날 말이죠. 저는 이 목욕용 모래가 마법의 가루라고 생각해요. 어떤 사람들은 이런 모래를 마약이라고 해요. 대소변 못가리는 햄스터들이 마법처럼 대소변을 가리게 된다고 해서요 ㅎㅎ


먹이를 집으로 feat. 아라찌

이제 포스팅을 끝났어요 ㅎㅎ 햄스터를 처음 데려왔을 때 일주일 동안은 햄스터에게 관심을 가지면 안된다고 해요. 근데 너무 귀여워서… 어쩔수 없이 동영상을 찍어버렸어요.
마지막으로 아라찌가 먹이를 집으로 가져가는 모습을 볼까요? ㅎㅎ

ㅎㅎ 귀엽죠? 원래 베딩위에 아라찌 집이 있었는데, 제가 잠든 사이에 베딩을 열심히 옮겨서 은식처를 베딩안으로 파묻었어요. 우리 아라찌가 집이 엄청 맘에 드나봐요. 근데 이렇게 아라찌가 굴속에서만 살면, 저는 언제 아라찌를 볼 수 있을까요? ㅠㅠㅠㅠ

pwnable.kr unlink 문제를 풀어보기 위해서, unlink 취약점에 대해서 찾아 봤어요.
처음에 봤을 때는, 하나도 이해가 안 갔지만 ㅠㅠㅠ
그래도 계속 읽다보고 생각하다보니까 아주 조금 이해가 됬어요!!
조금이라도 더 기억하기 위해서 한번 끄적여 볼게요


malloc의 동적할당?

malloc을 통해서 32바이트만큼 동적할당을 한다고 생각해봐요.
실제로 heap 영역에는 32바이트만큼 메모리가 할당이 되어야할 것 같지만, 실제로는 40바이트가 할당이 됩니다.
왜 그럴까요? 바로, 할당된 메모리를 관리하기 위한 정보들이 포함되어 있기 때문이에요!
그러한 정보는 chunk라고 불리는 구조체를 통해 관리됩니다.


chunk의 구조

아까 말했듯이 동적할당된 메모리는 chunk라고 불리는 구조체로 관리하고 있어요.
이 구조체는 다음과 같이 구성되어 있죠

1
2
3
4
5
6
7
8
9
10
11
12
struct malloc_chunk{
INTERNAL_SIZE_T mchnk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchnk_size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only sued for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize;
sutrct malloc_chunk* bk_nextsize;
};
typedef struct malloc_chunk* mchunkptr;

mchunk_prev_size: 해제가 되면, 이전 chunk의 크기를 가지게 되요.

mchunk_size: overhead를 포함해서, 현재 chunk의 크기를 가지게 되요. 여기서 overhead는 하위 3비트에 set되는 플래그를 말해요.

  • P(PREV_INUSE): 이전의 chunk가 없으면 0으로 셋되는 데, 맨 처음 chunk에서는 free 됬을 때 합병되지 않도록 하기위해서 1로 셋이 되어 있어요.
  • M(IS_MMAPPED): mmap함수를 통해서 만들어진 chunk일 경우 1로 셋이 되요.
  • A(NON_MAIN_ARENA): main 쓰레드에서 만들어졌을 경우 0으로 셋이 되지만 그외의 쓰레드에서 만들어질 경우 1로 셋이 되요.

fd, bk: 각각 forward pointer, back pointer 라고해서 다음의 chunk를 가리키고, 이전의 chunk를 가리켜요.


할당된 메모리와 해제된 메모리의 차이?

이제 할당된 메모리와 해제된 메모리가 어떤 차이가 있는지 알아봅시다.

Allocated chunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |A|M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
. .
. (malloc_usable_size() bytes) .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (size of chunk, but used for application data) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Free chunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' | Size of chunk, in bytes |A|0|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Forward pointer to next chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Back pointer to previous chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Unused space (may be 0 bytes long) .
. .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:' | Size of chunk, in bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

할당된 chunk와 해제된 chunk를 비교하시면 사용하고 있는 데이터 영역이 다르다는 것을 알수가 있어요.
할당된 chunk 같은 경우에는 저희가 입력한 데이터들이 들어가 있지만, 해제된 chunk에는 아까 말했던 FD와 BK가 들어가 있고, 나머지 영역은 사용하지 않는다는 것을 알 수가 있어요.

동적메모리가 어떻게 할당되는지 알았으니, 이러한 동적메모리가 여러 개가 존재를 한다고 생각해봐요.
그러면 이러한 동적메모리들을 한 곳에 모아서 보관하는 것이 편하겠죠?
그래서 bin이라는 자료구조가 존재해요!!


bin 자료구조

bin 자료구조는 총 4가지가 있다고해요.
chunk의 크기에 따라서 사용되는 bin이 달라져요.

  • Fast bin
  • Unsorted bin
  • Small bin
  • Large bin

Fast bin은 10개가 존재하고, 다른 bin들은 double linked list로 구성되어 있지만, Fast bin은 single linked list로 구성되어 있어요.
데이터를 삭제하거나 추가할 때 LIFO 방식으로 동작한다고 합니다.
그리고 각각의 bin들은 크기가 달라요. 16, 24, 32, 48, 56, 64, 72, 80, 88 순으로 구성되어 있죠.
당연히 여기에도 메타 데이터가 포함되요.

Unsorted bin은 오직 한개만 존재하는데, 이 놈은 다른놈들과 달라요. 이 bin의 목적은 cache layer처럼 빠르게 할당과 해제를 할 수 있게 도와주는 녀석이에요. 즉, 같은 크기의 chunk가 있다면 해당 chunk를 재사용하는 거에요.
메모리를 동적할당하고 사용하고 나서 어떤 값을 입력하고 해제한 후, 똑같은 크기의 메모리를 재사용할 때, 메모리를 초기화 해주는 작업을 해주지 않으면 그 메모리 안에 쓰레기 값이 존재할 수도 있어요. 그리고 이를 이용해서 exploit도 가능하다고 합니다.!!

Small bin은 62개가 존재하고, 16byte에서 시작해서 8byte씩 늘어낙서 504byte까지 존재한다고 해요. 추가하고 제거하는 방식은 FIFO 방식으로 관리 된다고 합니다.
그리고 small bin 같은 경우에는 해제된 후에, Unsorted bin에 의해 관리되기전에 서로서로가 합쳐질수도 있다고 합니다.

Large bin은 63개가 존재하고, 처음 32개의 bin들은 64byte만큼 떨어져 있지만, 특정 large bin들은 크기가 다른 chunk들을 가질 수 있다고 합니다. 그리고 추가하고 제거하는 방식은 어떤 자리에서도 일어날 수 있다고 합니다.!


동적할당 해제

이렇든 chunk들은 4가지의 bin을 통해서 관리가 되고, list를 이동해야할 일도 생긴다고 해요. 이때, free() 함수가 실행되는 과정중에 unlink라는 매크로 함수가 불릴 때 발생한다고 해요.

free된 chunk가 다시 malloc 되는 경우,
chunk의 크기가 증가해서 다른 bin으로 이동할 경우,
어떤 chunk가 free 되었을 때,
연속된 위치의 다른 chunk가 free 되었을 떄,
unlink 매크로 함수를 통해서 2개의 chunk를 합병을 해준다고 해요.

이렇게 chunk의 사이즈가 증가할 때는 원래 속해있던 bin 리스트에서 해당 chunk를 제거하고, 다시 적절한 bin에 넣어줘야 한다고 합니다.


이때 unlink corruption -> Double Free Bug가 발생한다고 합니다.
맨 처음에 봤을 때는 무슨 이상한 줏자들과 함께 써져 있어서, 헷갈리고 이게 무슨 소리인지 이해되지 않았어요.
fd와 bk를 조작해서 우리가 원하는 값을 쓸수 있다는 것만 알고 있으면 되요!!






참고자료


Heap Exploitation
Heap 영역에서 발생하는 취약점을 알아보자.

어떤 기기에서든 hexo 블로그를 업데이트 하기 위해서는 hexo 프로젝트와 hexo 테마를 백업을 해야 한다.

그 이유는 hexo 프로젝트를 통해서 배포를 할 때, 올라가는 데이터들은 웹사이트을 구동시킬 때 필요한 데이터들이 USERNAME.github.io 에 올라 가는 것이기 때문이다.

즉, hexo 블로그를 만들면서 포스팅 했던 데이터들은 github에 올라가지 않는다.
따라서 별도로 hexo 프로젝트를 백업할 필요가 있다.


어디서 백업을 해야할까?

다시 한번 말하자면, hexo 프로젝트 안에 있는 데이터를 보관하기 위한 새로운 저장소를 만들어야 한다.

github 을 이용하든 gitlab을 이용하든 상관은 없다.
하지만 배포를 할 떄 github을 이용했고, github에서 현재 private도 무료로 서비스를 제공하기 때문에 github을 사용하는 것이 좋다.

저장소를 만들 때,
private를 선택하든, public를 선택하든 상관은 없다.
자신의 데이터를 공개하고 싶지 않다면, private를 선택하는 것이고, 자신의 데이터를 공개해도 좋다고 하면, public을 선택할 뿐이다.


새로운 저장소 생성

저장소는 총 2개가 필요하다. 하나는 테마를 보관할 곳, 또 다른 하나는 hexo 프로젝트를 보관할 곳이다.

저장소의 이름은 사용자가 구분하기 쉽고, 쉽게 이용할 수 있는 이름으로 정하면 된다.

나 같은 경우에는 설명하기 쉽게 hexo 프로젝트를 저장할 공간인 blog 테마를 저장할 공간인 theme 라는 이름으로 만들었다.


hexo 프로젝트 백업하기

hexo 프로젝트를 백업하는 방법은 간단하다.
다음처럼 git을 만들어 주기만 하면된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# initialize git
git init

# add contents
git add .

# commit contents
git commit -m "Initial commit"

# change remote repository
git remote add origin https://github.com/777bareman777/blog.git

# push contents
git push -u origin master

테마 백업하기

직접 만든 경우에는 상관은 없지만, 다른 사람들이 만든 테마 같은 경우에는 백업하기가 힘들다.

fork를 하든, duplicate를 하든 상관이 없다.
어차피 원래의 테마 개발자가 업데이트를 하고, 우리가 그것을 따르고 싶다면, 우리가 설정했던 것들은 날라가기 때문이다.

-> 그럴 경우에는 자신이 설정했던 파일들을 따로 백업을 하는 것이 좋음.

그렇지 않고, 그 테마를 가지고 스스로 수정하고 싶다면 fork를 하는 것이 좋을지도 모른다.

일단은 duplicate를 했을 때를 생각해보자.

원하는 테마를 고르고 duplicate를 하고, 저장소 위치를 우리가 theme를 만들고자 했던 곳으로 바꾸고,
안에 있는 데이터들을 올리기만 하면된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# clone theme
git clone https://github.com/aircloud/hexo-theme-aircloud

# check remote repository
git remote -v

# change retmoe repository
git remote set-url origin https://github.com/777bareman777/theme

# add contents
git add .

# commit contents
git commit -m "Initial commit"

# push contents
git push -u origin master

서브모듈 추가하기

테마 폴더를 hexo 프로젝트에 push 하기 전에 고려해야할 점이 있다. git안에 git이 있는 경우 서로의 독립성을 유지하면서 서로의 버전을 따로 관리 하기 위해서 서브모듈로 추가해서 관리하는 것이 필수이다.

서브모듈에 대한 자세한 설명은 아래의 블로그를 참고하기 바란다.

그럼 서브모듈을 hexo 프로젝트에 추가해보자.

먼저 hexo 프로젝트로 이동을하고, hexo 프로젝트 안에 있는 themes 폴더에 테마를 서브모듈로 추가한다. 그 후에 제대로 변경이 되었는지 확인을 하고 변경된 내용을 push 하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# add theme repository in hexo theme folder as submodule
git submodule add https://github.com/777bareman777/theme themes/aircloud

# check status
git status
git submodule status

# add contents
git add .

# commit contents
git commit -m "Initial commit"

# push contents
git push -u origin master

마지막으로…

이러한 작업을 거치면 github에서 hexo 프로젝트와 테마를 관리해주기 때문에 데이터가 날라가도 다시 복구를 할 수가 있다.

데이터가 날라갔을 떄, 복구하는 방법은 다음과 같다.

먼저 hexo 프로젝트를 불러온다. 그 후에 서브모듈을 초기화 시키고 업데이트 시켜서 서브모듈의 데이터들을 가져온다. 서브모듈은 또 다른 git으로 이루어져 있기 때문에 그 독립성과 버전 관리를 위해서 데이터를 포함하고 있지 않기 때문이다. 마지막으로 local hexo를 다시 설치해줘야 한다.

1
2
3
4
5
6
7
git clone https://github.com/777bareman777/blog

git submodule init

git submodule update

npm install hexo --save

진짜 마지막으로…

변경된 사항을 github에 push를 하기 전에 Slave(submodule) 먼저 push를 한후 Master(iriginal git)을 push를 해야 한다.

Master -> Slave의 순서로 commit을 하게 되면, 다시 한번 더 Master에서 변경사항이 있다는 것을 알게 된다.

이렇게 작동 하는 이유는 Slave가 Master를 바라볼 때 Abstraction되는 방식으로 관리하기 때문이다.

Slave에서 변경된 사항은 Master에서 commit 할 때, 파일들을 Tracking 하는 것이 아니라 Slave의 HEAD가 무엇으로 변경되었는지에 대한 정보만 기록하기 때문이다.






이전 포스팅


Hexo 블로그 만들기 삽질 일기 - 1

아는 형한테 github page와 hexo를 통해서 블로그를 만들 수 있다고 이야기를 들었다.
티스토리 보다는 간지나게 hexo로 블로그를 관리해보자라는 욕심으로 정보를 찾아봤다.
하지만 그 이야기를 들었으면 안됬다.

이제부터 시작이다 나의 hexo 삽질이 ….


Git Page로 정적 페이지 Hosting하기

Git Page를 통해 USERNAME.github.io 도메인을 통해 정적 페이지를 호스팅할 수 있고, 자기 자신이 도메인을 가지고 있으면 커스텀 도메인할 수 있다. 이런거 필요 없다. 몰라도 좋다.

그냥 github에 들어가서 USERNAME.github.io를 생성하면 된다.
public 이든 private든 무엇이든 선택해도 좋다.


Hexo 시작

자 이제 우리는 고통의 시간이 조금씩 다가왔다.
hexo를 사용하기 전에 필수적으로 설치해야 하는 것들이 있다.

  • Node.js
  • Git

위의 두가지만 설치를 하면 된다. 설치 되어 있으면 생략해도 씹가능이다.


hexo cli 설치 + 블로그 생성

1
2
3
4
npm install hexo-cli -g
hexo init blog
cd blog
npm install

설정파일 바꾸기

위와 같은 방식으로 Hexo 프로젝트를 만들 수 있는데, Hexo 프로젝트 안에 _config.yml 이라는 설정파일이 존재한다. 이 설정 파일을 가지고 블로그에 대한 설정을 할 수 있는데, 나중에 테마를 설정하게 되면 테마 폴더에 있는 _config.yml 도 건드려야 한다.
설정파일안에 주석도 달려있고, 공식 문서도 존재하고, 다른 블로그에서도 설명이 되있으니 생략한다.

-> 이 부분이 좀 많이 짜증난다.

하지만 그 설정 파일 중에서 필수적으로 바꿔야 하는 것이 있다.
블로그 URL을 바꾸는 부분과 Github 정보를 바꾸는 부분이다.

1
2
3
4
5
# blod url
url: https://USERNAME.github.io
root: /
permalink: :year/:month/:day/:title/
permalink_defaults:
1
2
3
4
5
# Deployment
deploy:
type: git
repo: https://USERNAME@github.com/USERNAME/USERNAME.github.io
branch: master

위와 같이 자신의 github 아이디를 USERNAME에 넣어주기만 하면 배포를 할 수 있게된다.


로컬에서 테스트하기

설정을 완료하면 아래의 명령어를 통해 로컬에서 서버를 구동할 수 있다.

1
hexo server

서버가 구동이 되면 브라우저에서 https://localhost:4000 을 통해 hexo로 만든 블로그에 접속할 수 있다.


Github에 배포하기

로컬에서 정상적으로 블로그가 동작한다는 것을 확인 한후에 github을 통해 배포가 가능하다.

먼저 hexo 설정을 가지고 리소스를 생성하고 배포하면된다.

1
2
3
hexo clean
hexo generate
hexo deploy

생성과 배포를 동시에 할 수도 있다.

1
2
hexo clean
hexo generate --deploy

하지만 배포를 하기 전에 hexo에서 github에 push 할 수 있도록 플러그인을 다운로드 받아야한다.

1
npm instgall hexo-deployer-git --save

플러그인을 받은 후에 _config.yml에 플러그인 설정을 해주기만 하면된다.

-> 설정을 하지 않아도 동작하기는 한다.

1
2
plugins:
- hexo-deployer-git

기타 등등

포스팅을 하고, 이미지를 넣고, 하는 방법은 다른 블로그에도 많으니 다른 곳을 참고하길 바란다.
다음 글에서는 hexo 프로젝트를 백업하고, 테마를 설정하고, 테마를 백업하는 방법에 대해 설명하고자 한다.

hexo blog를 백업하는 부분에서 많은 시간이 소모가 된다. github을 자주 사용하지 않아서 이 부분에서 깊은 빡침을 느꼈다.

다음 포스팅에서 보자.






다음 포스팅


Hexo 블로그 만들기 삽질 일기 - 2