학사 나부랭이
Reversing - Portable Executable - PE header - NT header 본문
Reversing - Portable Executable - PE header - NT header
태양왕 해킹 (14세) 2021. 6. 1. 07:57
PE header - NT header
아래는 NT header 구조체 _IMAGE_NT_HEADERS이에요. 3개의 멤버가 있으며, 위는 64비트, 아래는 32비트용이죠. F8h(=16진수 F8 = 10진수 248)바이트로 상당히 큰 구조체예요.
PE header - NT header - Signature
4바이트의 0x4550h("PE"00)이라는 값을 가지며, 변경할 수 없어요. PE 구조를 가진 파일이라는 것을 명시해주죠.
PE header - NT header - FileHeader
파일의 대략적인 속성을 나타내요. 여기에는 다시 몇 가지의 멤버들이 있어요.
이 멤버 중 중요한 4가지 멤버들은 볼드처리 할게요. 이 값들이 정확히 설정되지 않으면 정상적인 실행이 불가능하죠.
1. Machine
Machine 넘버는 CPU 별로 고유한 값이에요. 예를 들어 32비트 인텔 호환 칩(인텔 386)은 14Ch(=332)라는 값을 가져요.
여기서는 8664h로, AMD64 호환 칩이네요.
2. NumberOfSections
PE 파일은 코드, 데이터, 리소스 등이 각각의 섹션에 나뉘어 저장되는데 이 멤버는 그 섹션의 개수를 나타내요. 당연히 0보다 커야겠죠? 정의된 개수보다 실제 섹션이 적다면 실행 에러가 발생하고 많으면 정의된 개수만큼만 인식되죠.
여기서는 17개가 있네요.
3. TimeDataStamp
파일 실행에는 영향을 미치지 않는 값인데, 빌드 시간을 나타낸 값이죠. 개발 도구에 따라, 개발 도구의 옵션에 따라 이 값을 세팅하기도 하고 하지 않기도 해요.
60B267CFh = 1622304719초 ~= 51년 44일
이 파일은 1970/01/01 + 51/00/44 = 2021/02/14 쯤에 빌드되었네요.
4. PointerToSymbolTable, NumberOfSymbols
PointerToSymbolTalbe> COFF* Symbol Table이 존재하면 심벌 테이블로 향하는 포인터 역할을 해요.
NumberOfSymbols> 위의 PTST에 값이 설정되어 있으면(심벌 테이블을 참조한다면), 해당 테이블의 심벌의 개수가 설정되어요. 요즘은 심벌의 크기가 커져 따로 파일로 저장되어 사용되기에 PTST와 NOS는 잘 사용되지 않죠.
*Common Object File Format, COFF는 수많은 프로그램들을 하나로 합쳐 메모리에 적재하는 과정에서 매우 복잡하고 많은 시간을 요구하는 문제점을 개선하기 위해 AT&T가 고안한 규칙이에요. 같은 특성을 가지는 것을 하나로 합치고, 합쳐진 것을 섹션이라 하며, 섹션은 언어에 따라 다르게 정해져 있죠. 이 규칙은 프로그램 개수와 상관없이 프로그램을 몇 개의 섹션으로 구분하기에, 프로그램 구성적인 측면에서 보면 간단해지는 장점이 있어요.
5. SizeOfOptionalHeader
NT header의 구조체, IMAGE_NT_HEADERS의 마지막 멤버는 IMAGE_OPTIONAL_HEADER32/64 구조체인데, SizeOfOptionalHeader 멤버는 이 IMAGE_OPTIONAL_HEADER32/64 구조체의 크기를 나타내요. 32비트 PE 파일은 0xE0h(224), 64비트는 0xF0h(240), obj는 0이 기록되어 있어요.
이 파일은 당연 64비트에서 구동되니까 0xF0h, IMAGE_OPTIONAL_HEADER64은 240바이트인가 보네요.
6. Characteristics
파일의 속성을 나타내요. 실행 가능한 형태인지(executable), DLL 파일인지 등의 정보가 bit OR 형식으로 조합되어요. 아래는 winnt.h에 정의된 Characteristics 값들인데, 0x02h와 2000h가 중요해요.
PE 파일 중, Characteristics 값에 0002h가 없는 경우, File is not executable, 실행할 수 없는 파일이며, 예를 들자면 obj나 dll 파일이 있어요.
0x27의 경우 0020과 0001, 0002, 0004를 OR 연산한 결과예요.
현재까지 프로그램 요약
이름 | 용도 | 현재 프로그램에서 값 |
DOS header | PE 구조 사용 여부 | 5A4Dh (MZ) |
DOS stub | DOS 시스템에서 실행할 때 동작 | WriteString() ("This program cannot...") Exit() |
NT header | ||
Machine | 프로그램이 동작될 CPU | 8664h (AMD64) |
NumberOfSections | 섹션의 수 | 0x11h (17개) |
TimeDataStamp | 만들어진 시간 | 약 2021/02/14 |
PointerToSymbolTable, NumberOfSymbols |
COFF 심벌 테이블이 있을 때, 이를 향한 포인터 해당 테이블에 있는 심벌의 개수 |
|
SizeOfOptionalHeader | OptionalHeader의 크기 | 0xF0h (240바이트) |
Characteristics | 파일의 속성 | 0x27 (64비트 실행파일) |
PE header - NT header - OptionalHeader
파일 실행에 관한 중요한 정보를 담고 있고 PE header 구조체 중 가장 크기가 커요.
이 값들 역시 파일 실행에 필수적인 값이라 잘 못 세팅되면 파일이 정상 실행되지 않아요.
1. Magic
32비트의 구조체는 010Bh, 64비트는 020Bh를 가져요. 여기서는 020Bh, 64비트네요.
2. SizeOfCode
코드 섹션(.text Section)의 크기를 나타내요. 여기서의 크기는 0x071200h네요.
3. AddressOfEntryPoint
프로그램이 시작되는 코드의 주소가 RVA(여기서는 0x1500h + ImageBase) 값으로 저장되어 있어요. 올리디버거로 실행시킬 때, 처음 만나는 위치예요.
4. BaseOfCode
코드 섹션이 시작되는 상대 주소(RVA, 여기서는 0x1000h + ImageBase)예요.
5. ImageBase
32비트에서 프로세스의 가상 메모리는 0~FFFFFFFFh 범위인데, ImageBase는 이 광활한 메모리 안에서 PE 파일이 매핑되는 시작 주소를 나타내줘요. 여기서는 0이네요.
exe, dll 파일은 유저 메모리 영역인 0~7FFFFFFFh에 위치하고 sys 파일은 커널 메모리 영역인 80000000h ~ FFFFFFFFh에 위치해요.
일반적으로 개발 도구가 만드는 exe파일의 ImageBase 값은 00400000h, dll 파일은 01000000h에 위치하지만 설정에 따라 다른 값도 가능해요.
PE loader는 PE 파일을 실행시키기 위해 프로세스를 생성하고, PE 파일을 해석, 메모리에 매핑시킨 후, IP 레지스터 값을 AddressOfEntryPoint + ImageBase로 세팅하죠.
6. SectionAlignment, FileAlignment
PE 파일은 섹션으로 나뉘어져 있는데 메모리에서 섹션의 최소 단위가 SectionAlignment(여기서는 0x1000h), 파일에서 섹션의 최소 단위가 FileAlignment(여기서는 0x2000h)이에요. 메모리/파일의 섹션 크기와 시작 주소는 반드시 SectionAlignment/FileAlignment의 배수가 되어야 하죠.
7. SizeOfImage
PE 파일이 메모리에 매핑되었을 때, 메모리에서 PE Image*가 차지하는 크기를 나타내요. 파일에서의 크기와 메모리에서의 크기가 다르기 때문이죠? 여기서의 크기는 0x100000h이네요.
*PE Image: PE 파일이 메모리에 매핑된 상태에서 PE 메모리 영역 전체라고 생각하면 돼요.
8. SizeOfHeader
PE header(DOS header + DOS stub + NT header + Section header)의 전체 크기를 나타내요. 당연히 FileAlignment의 배수이죠. 여기서의 크기는 0x0600h이네요.
파일 시작에서 SizeOfHeader 오프셋만큼 떨어진 위치에 첫 번째 섹션이 있어요.
9. Subsystem
파일을 시스템 드라이버, GUI, CUI(CLI)로 구분해요. 값이 1인 경우 시스템 드라이버 파일, 2인 경우 GUI 파일, 3인 경우 CUI(CLI, cmd.exe)이에요. 제가 짠 C++코드니까 0x03h, CLI모드 맞네요.
10. NumberOfRvaAndSizes
DataDirectory 배열의 개수를 나타내요. 위 구조체의 정의에서는
이렇게 명시되어 있지만 PE loader는 NumberOfRvaAndSizes 값으로 배열의 크기를 인식해요. 여기서는 0x10h아리는데, 10진법으로 16 맞네요.
11. DataDirectory
IMAGE_DATA_DIRECTORY 구조체의 배열로, 배열의 항목마다 정의된 값을 가져요.
DataDirectory[0]: EXPORT Directory
DLL 등 파일에서 외부에 함수를 공개하기 위한 정보들을 가지고 있어요.
DataDirectory[1]: IMPORT Directory
프로그램 실행을 위해 import 하는 DLL 이름과 사용할 함수의 정보가 담긴 INT(Import Name Table)의 주소, IAT(Import Address Table) 주소 등의 정보가 있어요. 이 정보들은 PE 로더가 실행될 때 주소 값을 IAT에 동적으로 입력하기 위해 필요한 부분으로, 내부 동작 방식이 복잡하죠. 쉽게 말해 외부 DLL 파일의 함수 주소를 가져올 때, Export table을 참고하고, 여기서 찾은 주소를 Import table을 활용해 IAT에 저장&사용하죠.
DataDirectory[2]: RESOURCE Directory
DataDirectory[3]: EXCEPTION Directory
DataDirectory[4]: SECURITY Directory
DataDirectory[5]: BASERELOC Directory
DataDirectory[6]: DEBUG Directory
DataDirectory[7]: COPYRIGHT Directory
DataDirectory[8]: GLOBALPTR Directory
DataDirectory[9]: TLS Directory
DataDirectory[A]: LOAD_CONFIG Directory
DataDirectory[B]: BOUND_IMPORT Directory
DataDirectory[C]: IAT Directory
DataDirectory[D]: DELAY_IMPORT Directory
DataDirectory[E]: COM_DESCRIPTOR Directory
DataDirectory[F]: Reserved Directory
볼드 처리한 거는 PE 헤더에서 매우 중요하기에 나중에 따로 설명할게요. 나머지는 크게 중요하지 않아요.
현재까지 프로그램 요약
이름 | 용도 | 현재 프로그램에서 값 |
DOS header | PE 구조 사용 여부 | 5A4Dh (MZ) |
DOS stub | DOS 시스템에서 실행할 때 동작 | WriteString() ("This program cannot...") Exit() |
NT header | ||
Machine | 프로그램이 동작될 CPU | 8664h (AMD64) |
NumberOfSections | 섹션의 수 | 0x11h (17개) |
TimeDataStamp | 만들어진 시간 | 약 2021/02/14 |
PointerToSymbolTable, NumberOfSymbols |
COFF 심벌 테이블이 있을 때, 이를 향한 포인터 해당 테이블에 있는 심벌의 개수 |
|
SizeOfOptionalHeader | OptionalHeader의 크기 | 0xF0h (240바이트) |
Characteristics | 파일의 속성 | 0x27 (64비트 실행파일) |
OptionalHeader | ||
Magic | OptionalHeader의 종류 | 020Bh (64비트) |
SizeOfCode | 코드 섹션의 크기 | 0x071200h |
AddressOfEntryPoint | 처음 실행되는 코드의 주소 | 0x1500h + ImageBase |
BaseOfCode | 코드 섹션의 주소 | 0x1000h + ImageBase |
ImageBase | RVA의 기준점 | 0 |
SectionAlignment, FileAlignment |
섹션의 메모리/파일에서 단위 | 0x1000h 0x2000h |
SizeOfImage | 메모리에 매핑되었을 때 크기 | 0x100000h |
SizeOfHeader | 파일에서 PE 헤더의 총 크기 | 0x0600h |
SubSystem | 파일의 종류 | 0x03h (CLI) |
NumberOfRvaAndSizes | DataDirectory 배열의 개수 | 0x10h (16개) |
DataDirectory | IMAGE_DATA_DIRECTORY 구조체의 배열 |
'Dot-Gabi > Reversing' 카테고리의 다른 글
The basic of Assembly language & Debugger (0) | 2021.07.08 |
---|---|
Reversing - Portable Executable - PE header - Section header (0) | 2021.06.04 |
Reversing - Portable Executable - PE header - DOS header, stub (0) | 2021.06.01 |
Reversing - Portable Executable - Opening (0) | 2021.06.01 |
Reversing - Register, Stack Frame (0) | 2021.05.30 |