PE File Format (1)

Wooki·2021년 8월 11일
2
post-thumbnail

PE 파일?

PE(Portable Excutable) 파일, 리버싱을 처음 공부할 때 이 PE 파일부분을 공부하고 부터 내용이 살짝 어려워져서 흥미가 약간 떨어졌었던 것
같다.
뭔가 알것 같으면서 정확히는 모르겠는 느낌이랄까..?

PE 파일이란, Windows OS에서 사용되는 실행 파일의 형식이다.
UNIX 에서는 COFF(Common Object File Format)을 사용하는데 PE 는 이를 기반으로다른 운영체제에 이식하기 좋게 하려고 Microsoft에서 만들었다.

실제로는 Windows 계열 OS에서만 사용되고 있다.

PE 파일의 종류는 다음과 같다.

종류주요 확장자
실행 계열 EXE , SCR
라이브러리 계열DLL, OCX, CPL, DRV
드라이버 계열 SYS , VXD
오브젝트 파일 계열OBJ

OBJ 파일을 제외한 파일들은 모두 실행 가능한 파일들이다.
(디버거 등을 이용해서)

PE파일의 구조


PE 파일의 구조를 살펴보자, 아래 그림에서 DOS header ~ Section header 부분을 PE 헤더 라고 하고, 그 밑의 Section 들을 PE 바디 라고 한다.

파일이 메모리에 로딩되면 Section의 크기나 위치 등이 달라지고
파일의 내용은 보통 코드는 .text , 데이터는 .data, 리소스는 rsrc 섹션에 나눠서 저장된다.

PE 헤더에 있는 각 Section header들은 대응되는 Section에 대한 크기, 위치, 속성 등에 대한 정보가 정의되어 있고,

각 섹션의 끝에는 NULL Padding 이 존재한다.

컴퓨터에서 파일, 메모리, 패킷 등을 처리할 때 효율성을 위해서 최소 바이트 단위라는 개념을 이용하는데, PE 파일도 마찬가지로 최소 기본 단위가 정해져있다. 그래서 파일/메모리 에서의 각 섹션의 시작 위치는 최소 기본 단위의 배수가 되어야 하고,
부족한 빈 공간을 NULL Padding으로 채워버리는 것이다.

VA & RVA

메모리 주소를 확인 할 때는 VA(Virtual Address)와 RVA(Rela-tive Virtual Address)를 잘 구분해야 한다.

VA는 가상 메모리의 절대 주소를 말한다.
RVA는 기준 위치(ImageBase) 에서부터의 상대 주소를 말한다.

쉽게 풀어서 말하자면 메모리가 00000000~10000000 가 있다고 하고, 주소가 00005000 이라고 하면

주소가 절대 주소(VA)일 경우에는 00005000이 가리키는 주소가 될 것이고,
주소가 상대 주소(RVA)일 경우에는 기준 위치에 따라서 달라진다.
예) 기준위치(ImageBase)가 00012000 이라고 하면 가리키는 주소는 00017000이 된다.

VA 구하는 공식
RVA + ImageBase = VA

파일 재배치

PE 헤더의 정보는 RVA형태로 이루어진 것들이 많다.
만약 절대 주소로만 되어 있다면, 해당 하는 메모리의 주소에 이미 다른 PE파일(DLL)이 로딩되어 있다면 파일 재배치를 통해서 사용하지 않고 있는 메모리의 위치에 원하는 PE 파일을 로딩시켜야 하는데, 만약 PE헤더의 정보들이 VA(절대주소)로 적혀있다면 정상적인 파일 동작이 이루어질수 없다.

하지만 RVA(상대주소)로 정보가 저장되어 있다고 생각해보자.
다른 위치에 파일을 로딩시켰다 하더라도, 정보는 RVA로 이뤄져있기 때문에
기준 위치에 대한 상대주소가 변하지 않고, 문제없이 메모리에 엑세스할 수 있다.


PE 헤더


PE 헤더는 여러 개의 구조체로 이루어져서 정보를 전달한다.
중요한 구조체 멤버들에 대해서 하나씩 살펴보자.

Dos 헤더

PE File Format이 사용되기 전에는 DOS 형태의 파일이 널리 사용되고 있었다.
그래서 Dos 파일에 대한 호환성을 위해 PE헤더의 처음에는 Dos exe header를 확장한 형태의 IMAGE_DOS_HEADER가 구조체가 존재한다.

IMAGE_DOS_HEADER의 구성이다. 구조체의 크기는 40Bytes로 이루어져 있다.
중요한 멤버 두개만 짚고 넘어가면,

e_magic : DOS Signature(4D5A / ASCII 'MZ')
e_Ifanew : NT Header의 Offset 표시

모든 PE파일은 시작 부분에 'MZ'가 가 존재하고, e_Ifanew 값이 가리키는 위치에 NT 헤더가 존재한다.

MZ는 Microsoft에서 DOS파일을 설계한 마크 주비코브스키라는 분의 이니셜이라고 한다.

HxD(헥사 에디터)로 열어본 notepad.exe 파일이다.

빨간색 박스로 표시해놓은 40Bytes가 IMAGE_DOS_HEADER 구조체의 내용이다.
보면 처음 부분에 'MZ'(4D 5A) 가 존재하고(e_magic)
끝부분을 보면 e_IFanew가 000000E0임을 나타내고 있다.

이전 포스트에서 언급했던 것처럼 리틀 엔디언으로 표기되어 있기때문에
E0000000 이 아니라 000000E0으로 읽어야 한다.

DOS Stub

DOS Header 아래에는 DOS Stub이 존재한다. DOS Header가 없으면 파일이 정상적으로 실행되지 않지만, DOS Stub은 필수가 아니며 없어도 실행은 된다.
DOS Stub은 코드와 데이터들로 이루어져 있다.

NT Header

NT 헤더는 앞전에 DOS 헤더에서 언급한 적이 있다. DOS Header의 e_IFanew가 NT Header의 주소를 값으로 가지고 있다고 했을 것이다.

IMAGE_NT_HEADERS 라는 구조체고 3가지 멤버를 가지고 있다.
구조체의 크기는 F8으로 큰 값을 가진다.

첫 번째 멤버는 Signature 로 이는 50450000h ("PE"00)값을 가진다.
그리고 FileHeaderOptionalHeader라는 구조체 멤버를 가진다.

FileHeader

FileHeader 구조체는 파일의 대략적인 속성을 나타낸다.

중요한 4가지 멤버가 있다.

  1. Machine
    Machine은 CPU 별로 달라지는 CPU의 고유한 값이다. winnt.h파일에 Machine값들이 정의되어 있고, 주로 사용되는 Intel x86 호환 칩들의 고유 값은 14C를 가진다.
  2. NumberOfSections
    PE파일은 코드, 데이터, 리소스 등이 각각 다른 Section에 저장된다고 위의 PE파일 구조를 나타낸 그림과 함께 설명하였다.
    NumberOfSections 멤버는 그 섹션들의 개수를 나타낸다.
    이 값은 항상 0보다 커야하고, 값과 실제 섹션의 개수가 같아야만 한다.
  3. SizeOfOptionalHeader
    NT Header에 구조체 멤버로 지금 설명하고 있는 FileHeader와 OptionalHeader가 있다고 했다. SizeOfOptionalHeader멤버는
    구조체 OptionalHeader의 크기를 나타내고 있다.
  4. Characteristics
    파일의 속성을 나타내는 값이다, 이 파일이 실행이 가능한 파일인지 아닌지, 또는 DLL파일인지 등에 대한 정보들을 bit or한 값을 가진다.

    실행 가능한 파일은 0x0002를 가지고, DLL 파일은 0x2000을 가진다.

헥사 에디터로 열어본 모습이다. e_IFanew 의 값대로 0x000000E0 주소에서 NT_HEADER가 시작된다. 처음에 보이는 50450000은 Signature멤버로 PE파일임을 나타낸다. 그 이후 2바이트는 0x4C01으로 제대로 읽으면 014C 즉 Intel x86 호환 칩을 나타내는 Machine 멤버다.
이후 0x0300은 NumberOfSections로 섹션의 개수가 3개임을 알려주고 있고, 0x000000F4의 0xE000은 SizeOfOptinalHeader로 OptionalHeader의 크기가 E0이라는 것을 의미한다.
그다음의 0x0F01은 Characteristics멤버다.

OptionalHeader

PE 헤더의 구조체중 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32 구조체다.

~~멤버가 너무 많다..~~

이 구조체도 중요한 몇 멤버들만 알아두고 넘어가자.

  1. Magic
    이 값은 IMAGE_OPTIONAL_HEADER32와 HEADER64에 따라 달라지는 값으로 32일때는 10B, 64일 떄는 20B를 가진다고 한다.

  2. AddressOfEntryPoint
    EP(EntryPoint) 즉, 최초로 실행되는 코드의 시작 주소를 가지는 멤버다EP의 RVA(상대주소)값을 가지고 있다.

  3. ImageBase
    32비트를 기준으로 프로세스의 가상 메모리는 00000000~FFFFFFFF범위다.
    ImageBase 값은 PE 파일이 로딩되는 시작 주소를 나타낸다.
    EXE, DLL과 같은 파일은 보통 00000000~7FFFFFFF 사이에 로딩되고,
    SYS파일은 80000000~FFFFFFFF(커널 메모리) 영역에 로딩된다.
    일반적인 VC++/VB등의 도구로 만들어진 EXE 파일의 ImageBase는 10000000을 가진다.

    로더가 PE파일을 실행할 때 파일을 메모리에 로딩한후 EIP 레지스터를
    ImageBase + AddressOfEntryPoint 값으로 세팅한다.

  4. SectionAlignment, FileAlignment
    위에서 NULL PADDING에 대해서 설명할 때 메모리의 최소 기본 단위가 있다고 설명했는데, 이 두 멤버가 그 최소 단위를 나타낸다.
    PE 파일에서의 최소 단위를 나타내는 멤버가 FileAlignment
    메모리 로딩 이후 최소 단위를 나타내는 멤버가 SectionAlignment다.

    두 값을 같을 수도 있고 다를 수도 있다. 하지만 중요한 것은
    파일/메모리의 섹션 크기는 항상 이 멤버의 배수가 되어야만 한다.

  5. SizeOfImage
    이 멤버는 PE파일이 메모리에 로딩되었을 때 가상 메모리에서 PE Image가 차지하는 크기를 나타낸다.

  6. SizeOfHeader
    이 멤버는 PE헤더의 전체 크기를 나타낸다.
    PE 헤더 다음에 섹션이 위치하므로 파일의 시작 주소에서 SizeOfHeader값 만큼 차이나는 위치에 첫 번째 섹션이 위치한다는 얘기다.

    이 값도 역시 FileAlignment의 배수가 된다.

  7. SubSystem
    이 파일이 SYS파일인지, 아니면 그냥 실행 파일인지 구분할 수 있다.

  8. NumberOfRvaAndSizes
    IMAGE_OPTIONAL_HEADER 구조체의 마지막 멤버는 DataDirectory 배열이 있다. (9번) 이 배열의 개수를 나타내는 멤버다

    구조체의 정의에 배열의 개수가 명시되어 있지만, PE로더는
    이 멤버를보고 배열의 크기를 인식한다.

  9. DataDirectory (추후 수정)
    IMAGE_DATA_DIRECTORY 구조체 배열이다.

Section Header

각 섹션들의 속성(Property)가 정의되어 있는 헤더다.
PE 파일은 Code, Data, Resource 등을 섹션별로 구분해서 저장한다.
섹션별로 구분을 하면 안정성 측면에서의 이점이 있다.
예를 들어 문자열과 같은 경우 값을 집어넣다가 그 배열의 크기를 넘어서 오버플로우가 발생했다고 하자.

섹션별로 코드와 데이터를 분리해뒀다고 하면 데이터에서 오버플로우가 발생했다 하더라도 데이터 정도만 깨진다는 특징이 있지만
만약 섹션별로 구분하지 않고 코드와 데이터가 뒤죽박죽 섞여 있다면,
데이터의 오버플로로 인해서 코드 부분이 덮어쓰여져 프로그램이 그대로 종료될 수 있다.

그래서 PE File Format 개발자들은 비슷한 자료들끼리를 해당 섹션으로 구분하여 모아두기로 했다.
섹션마다 그 용도와 크기가 다르기에 속성들을 설정하기 위해 Section Header가 존재한다.

종류액세스 권한
Code실행, 읽기
Data읽기, 쓰기
Resource읽기

이처럼 자료들의 섹션을 구분하여 섹션별로 액세스를 구분해 주는 것이 목표다.

섹션 헤더는 각 섹션별로 IMAGE_SECTION_HEADER 구조체의 배열 형태로 되어있다.

항목의미
VirtualSize메모리에서 섹션이 차지하는 크기
VirtualAddress메모리에서 섹션의 시작 주소(RVA)
SizeOfRawData파일에서 섹션이 차지하는 크기
PointerToRawData파일에서 섹션의 시작 위치
Characterisics섹션의 속성(bit Or)

상단의 표가 이 구조체에서 중요한 멤버고 나머지는 사용되지 않는다고 한다.
VirtualAddress와 PointerToRawData는 아무 값이나 가질 수 없다.
이 글에서 계속 설명했듯이 각각 SectionAlignment와 FileAlignment의 배수와 같게 값을 가져야만 한다.
VirtualSize와 SizeOfRawData는 어떨까?
섹션의 크기는 파일에서 메모리로 로딩되면서 보통 다른 값을 가지게 된다.
즉 이 두값도 일반적으로 서로 다른 값을 가지게 된다.
Characteristics는 File Header에서도 나온 것처럼 섹션의 속성을 나타내며 정의되어 있는 속성들의 Bit or 값으로 이루어진다.

섹션 헤더를 보면 Byte 배열인 Name 배열이 있다.
Name은 섹션에 붙이는 이름 같은 것인데 어떤 규칙이나 제약이 있는 것도 아니고, 유의미한 참조가 되지도 않는다 심지어 NULL로 채워도 된다.
단지 PE File을 사용하기 위해 참고할 정도의 정보? 라고 생각하면 된다.

헥사 에디터로 열어본 notepad.exe의 섹션 헤더 부분이다.


profile
웹 개발자

0개의 댓글