- (13.1~13.6)
13.1~13.2 PE File Format
PE 파일 소개
- PE(portable executable): windows 운영체제에서 사용되는 실행 파일 형식
- 32비트의 실행 파일을 의미한다.
- 64비트의 경우 PE+, PE32+ 라고 부름
PE File Format
PE 파일의 종류
- 실행 계열: EXE, SCR
- 라이브러리 계열: DLL, OCX, CPL, DRV
- 드라이버 계열: SYS, VXD
- 오브젝트 파일 계열: OBJ(이걸 제외하고 위 모든 것들은 실행 가능함)
헤더
- PE 헤더: 어떻게 메모리에 적재되고, 어디서부터 실행되어야 하는지, 실행에 필요한 DLL들은 어떤 것이 있는지 등등 exe파일이 실행되기 위해 필요한 모든 정보가 적혀있다고 할 수 있다.
PE파일의 기본 구조
실습 내용에서 확인한 바와 같이 notepad.exe는 일반적인 PE파일의 구조를 따르는데
DOS header ~Section header: PE헤더
그 밑의 sections들은 합쳐 PE바디 라고 한다.
- 파일과 메모리: 파일에서는 offset, 메모리에서는 VA(Virtual Address, 절대주소)로 표현되기 때문에 파일이 메모리에 로딩되면 모양이 달라진다.
주소(VA&RVA)
- VA(Virtual Address)
프로세스 가상 메모리의 절대 주소
-RVA(Relative Virtual Address)
어느 기준 위치(Image Base)에서부터 상대주소
따라서 관계식을 아래와 같이 쓸 수 있다.
- RVA + Image Base = VA
PE헤더 내의 정보는 RVA로 된 것이 많다.
PE파일이 프로세스 가상 메모리의 특정 위치에 로딩되는 순간 그 위치에 이미 다른 PE파일이 로딩되어 있을 수 있다. 이런 경우에 재배치(relocation)를 통해 비어 있는 다른 위치로 파일을 로딩시켜야하는데, VA( 절대 주소) 로 헤더 정보들이 표현되어있으면 정상적인 액세스를 진행할 수 없다.
RVA를 쓰면 relocation을 해도 ‘기준위치에 대한 상대주소’는 변하지 않기 때문에 정상적으로 작동된다.
13.3 PE 헤더
PE헤더는 많은 구조체로 이루어져있다. 여기서 다룰 것은
- DOS Header
- DOS Stub
- NT Header
- File Header
- Optional Header
1. DOS Header
- 마이크로소프트가 PE file format을 만들 때 당시 널리 사용되던 DOS하위 호환성 고려해서 만듦-> PE헤더의 맨 앞에 IMAGE_DOS_HEADER구조체가 있다.(DOS EXE HEADER의 확장)
- IMAGE_DOS_HEADER의 구조체 크기는 고정되어있고 40이다.
- 중요 멤버로는 e_magic, e_1fanew가 있는데
- e_magic: 모든 PE파일은 시작 부분인 e_magic에 DOS signature “MZ”(마크 주비코브스키의 이니셜이다)(4D5A)가 있음
- e_1fanew: 끝부분인 e_1fanew가 가리키는 위치에 NT Header(IMAGE_NT_HEADERS)구조체가 존재해야한다.
2. DOS Stub
- DOS Header밑에 위치한다.
- 없어도 실행에는 문제가 없기 때문에 존재 여부는 옵션이고, 크기도 일정하지 않다.
- 존재 여부가 옵션이라 개발 도구에서 지원해줘야 한다.(VB, VC++등은 기본 지원한다고 한다.)
- 코드와 데이터의 혼합으로 이루어져 있다.
3. NT Header
- DOS Header를 설명하면서 간략히 언급하였는데, e_1fanew가 가리키는 위치에 이게 있다.
- IMAGE_NT_HEADERS는 3개의 멤버로 되어있는데
-
- Signature: 50450000h(“PE”00)값을 가진다.
-
- File Header
-
- Optional Header
-
3-1. NT Header -File Header
- 파일의 개략적인 속성을 나타낸다.
- 아래 멤버가 정확히 세팅되어있지 않으면 파일은 정상적으로 실행되지 않는데
- Machine, NumberOfSections, SizeOfOptionalHeader, Characteristics
- Machine
CPU별로 고유한 값을 가진다. 인텔 x86은 14C의 값을 가진다.
- NumberOfSections
PE파일은 코드, 데이터 리소스 등이 각각의 섹션에 나뉘어서 저장되는데, 이떄 그 섹션의 수를 의미한다. 이 값은 0보다 커야하고, 정의된 섹션과 실제 섹션이 다르면 실행 에러가 발생한다.
- SizeOfOptionalHeader
IMAGE_NT_HEADER32의 크기를 나타내는 멤버인데, 이건 C언어의 구조체이기 때문에 이미 크기가 결정되어있다. 그럼에도 이게 따로 필요한 이유는 Windows의 PE로더가 SizeOfOptionalHeader값을 보고 IMAGE_OPTIONAL_HEADER32 구조체의 크기를 인식하기 때문이다.
64비트에서(PE32+)는 IMAGE_OPTIONAL_HEADER64구조체를 사용하는데, IMAGE_OPTIONAL_HEADER32와 크기가 다르기 때문에 SizeOfOptionalHeader멤버에 구조체 크기를 명시한다.
-Characteristics
파일의 속성을 나타내는 값이다.
실행 가능 여부(executable or not), DLL or not등의 정보들이 bit OR형식으로 조합된다.
3-2. NT Header -Optional Header
- PE 헤더 구조체 중에 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32
- 아래 멤버들이 잘못 세팅되면 파일이 정상 실행되지 않는다
- Magic, AddressOfEntryPoint, ImageBase, SectionAlignment, FileAlignment, SizeOfImage, SizeOfHeader, Subysystem, NumberOfRvaAndSizes, DataDirectory
1. Magic
IMAGE_OPTIONAL_HEADER32구조체의 경우 10B, 64의 경우 20B값을 가진다.
2. AddressOfEntryPoint
EP(Entry Point)의 RVA값을 가지고 있다. 이 값은 프로그램에서 최초로 실행되는 코드의 시작 주소이므로 매우 중요하다.
3. ImageBase
프로세스의 가상 메모리는 0~FFFFFFFF범위라서 매우 광활한데, ImageBase는 이 범위에서 PE파일이 로딩되는 시작 주소를 나타낸다.
- EXE, DLL -> 0~7FFFFFFF(user memory)
- SYS -> 80000000~FFFFFFFF(kernel memory)
일반적으로 개발 도구가 만들어내는 EXE파일의 ImageBase는 00400000, DLL 파일의 ImageBase 값은 10000000이라고 한다. PE로더는 PE파일을 실행시키기 위해 프로세스를 생성하고 파일을 메모리에 로딩 후 EIP 레지스터 값을 ImageBase + AddressOfEntryPoint 값으로 세팅한다.
4. SectionAlignment, FileAlignment
PE파일의 Body는 섹션으로 나눠지는데
- 파일에서 섹션의 최소단위 -> FileAlignment
- 메모리에서 섹션의 최소단위 -> SectionAlignment
한 파일에서 위 두 값은 같을 수도, 다를 수도 있다.
그러나 파일과 메모리의 섹션 크기는 반드시 각각 FileAlignment, SectionAlignment의 배수가 되어야 한다.(최소 단위의 기준이므로)
5. SizeOfImage
PE파일이 메모리에 로딩되었을 때 가상메모리에서 PE Image가 차지하는 크기를 나타낸다. 일반적으로 파일의 크기와 메모리에 로딩된 크기는 같지 않다고 한다.
6. SizeOfHeader
PE 헤더의 전체 크기를 나타낸다. 섹션 크기에서와 마찬가지로 FileAlignment의 배수 크기여야 한다. 파일 시작에서 SizeofHeader 옵셋만큼 떨어진 위치에 첫 번째 섹션이 위치한다.
7. Subysystem
Subysystem 값을 보고 시스템 드라이버인지, 일반 실행 파일인지 구분 가능하다.
8. NumberOfRvaAndSizes
IMAGE_OPTIONAL_HEADER32 구조체의 마지막 멤버 DataDirectory 배열의 개수를 나타낸다.
구조체 정의에 배열 개수가 IMAGE_NUMBEROF_DIRECTORY_ENTRIES(16)라고 명시되어 있으나 PE 로더는 이 값을 보고 배열의 크기를 인식한다.
9. DataDirectory
위에서 잠깐 언급했지만 IMAGE_DATA_DIRECTORY구조체의 배열이다. 배열의 각 항목마다 정의된 값을 가진다.
Directory란 어떤 구조체의 배열로 보면 되는데, 이 중 IMPORT, EXPORT Direcotry구조는 PE헤더에서 매우 중요하다.
DataDirectory[0] = EXPORT Directory
DataDirectory[1] = IMPORT Directory
DataDirectory[2] = RESOURCE Direcotry
DataDirectory[3] = EXCEPTION Directory
...
DataDirectory[9] = TLS Directory
...
DataDirectory[F] = Reserved Directory
섹션 헤더
각 섹션의 속성을 정의한 것이 섹션 헤더이다.
PE파일은 코드, 데이터, 리소스 등을 각각 섹션으로 나눠서 저장한다고 했는데, 책에서 설명하기로 이렇게 PE파일을 여러 개의 섹션 구조로 만드는 이유는 프로그램의 안정성에 있다고 한다.
코드와 데이터가 하나의 섹션에 있다면 복잡하기도 하지만 안정성에 문제가 생길 수 있다. 데이터에 오버플로우와 같은 문제가 생겼을 때 코드와 리소스도 하나의 섹션에 모두 모여있다면 오버플로우가 코드를 그대로 덮어써버리게 될 것이다.
이러한 이유로 PE File Format 설계자들은 비슷한 성격의 자료를 섹션으로 나눠두었다. 따라서 섹션의 속성을 나타내기 위해 각각의 섹션에 섹션 헤더가 필요한 것이다.
코드, 데이터, 리소스 각각의 섹션마다 특성이나 접근 권한을 다르게 설정할 필요가 있다.
섹션 헤더는 각 섹션별 IMAGE_SECTION_HEADER 구조체의 배열로 되어있는데,
이 중 알아야 할 중요 멤버는
- VirtualAddress, PointerToRawData는 각각 SectionAlignment와 FileAlignment에 맞게 결정된다.(아무 값이나 들어가선 안된다.)
- VirtualSize와 SizeOfRawData는 일반적으로 다른 값을 가지는데, 위에서 언급했듯 파일에서의 섹션 크기와 메모리에 로딩된 섹션의 크기가 다르다는 말이다.
- Characteristics는 아래 표시된 값들의 조합(bit OR)로 이루어진다.
Name멤버는 NULL로 끝나야 한다거나 ASCII값만 와야한다는 제한도 없는데, PE 스펙에는 섹션 네임에 대해 명시적인 규칙이 없어서 어떠한 값을 넣어도 상관없다. 심지어 NULL값도 가능하다!
13.4 RVA2RAW
PE파일이 메모리에 로딩되었을 때 각 섹션에서 메모리의 주소(RVA)와 파일 옵셋을 매핑하는 것을 RVA to RAW라고 부른다.방법은
- RVA가 속해 있는 섹션을 찾고
- 비례식을 통해 파일 옵셋(RAW)을 계산
IMAGE_SECTION_HEADER구조체에 의하면 비례식은
RAW - PointerToRawData = RVA - VirtualAddress
RAW = RVA - VirtualAddress + PointerToRawData
13.5 IAT
IAT란 프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 테이블이다.
DLL
IAT를 설명하기 위해 DLL의 개념을 먼저 짚고 넘어가자.
DLL(Dynamic Linked Library), 동적 연결 라이브러리는 메모리 절약을 위해 고안되었다고 볼 수 있는데, 이게 있기 전까지는 C라이브러리의 함수의 바이너리 코드를 그대로 가지고 와서 썼다. 그런데 멀티 태스킹 시 여러 프로그램이 동시에 실행되는데 도일한 라이브러리가 매번 포함되는 경우 심각한 메모리 낭비가 발생한다.
그래서 프로그램에 라이브러리를 포함시키지 말고 별도의 파일로 구성하여 필요할 떄마다 불러오자는 전략을 취했는데, 이 파일이 DLL이다.
DLL 로딩 방식
- Explicit Linking: 프로그램에서 사용되는 순간에 로딩하고 사용이 끝나면 메모리에서 해제되는 방법
- Implicit Linking: 프로그램 시작 시 로딩되어 프로그램 종료 시 해제됨
DLL은 위 두 로딩 방식이 있는데 IAT는 Implicit Linking에 대한 매커니즘을 제공한다.
notepad에서 살펴보면 01001104주소에 있는 값을 가져와서 호출하게 된다. 위 주소의 값은 7C8107F0인데 이걸 바로 CALL 7C8107F0으로 호출하지 않는 이유는
- notepad.exe 프로그램을 컴파일하는 순간에 어떤 윈도우즈 버전, 어떤 언어, 어떤 서비스팩 등 어떤 환경에서 실행될지 알 수 없는데, 위와 같은 환경의 변화에 따라 kernel32.dll버전과 CreateFileW함수의 주소가 달라지기 때문이다. 따라서 주소가 저장될 위치만 준비해야하고, 실제 값은 하드코딩할 수 없다.
- DLL Relocation문제가 있다. 로딩하려는 위치가 이미 사용중인 메모리인 경우 비어있는 메모리 공간에 로딩시켜주게 되므로(relocation) 하드코딩할 수 없다.
IMAGE_IMPORT_DESCRIPTER
PE파일은 자신이 어떤 라이브러리를 임포트하고 있는지 IMAGE_IMPORT_DESCRIPTER에 명시하고 있다.
'보안 > 리버싱' 카테고리의 다른 글
reversing.kr - easy unpack (0) | 2024.09.01 |
---|---|
easy keygen (0) | 2024.08.18 |
rev-basic2 (0) | 2024.08.18 |
Easy Crack Me (0) | 2024.08.11 |
ELF x86 - Basic (0) | 2024.08.10 |