13장 PE File Format
PE 포맷이란?
PE(Portable Executable) 파일은 Windows 운영체제에서 사용되는 실행파일 형식입니다. 애초에는 (Portable 단어가 의미하는 대로) 다른 운영체제에 이식성을 좋게 하려는 의도였으나 실제로는 Windows 계열의 운영체제에서만 사용되고 있습니다.
기본구조
DOS header부터 Section header까지를 PE 헤더, 그 밑의 Section들을 합쳐서 PE 바디(Body)라고 합니다. 파일에서는 offset으로, 메모레에서는 VA(Virtual Address, 절대주소)로 위치를 표현합니다. 파일이 메모리에 로딩되면 모양이 달라집니다. 파읠의 내용은 보통 코드(.text), 데이터(.data), 리소스(.rsc) 섹션에 나뉘어서 저장됩니다.
섹션 헤더에 각 Section에 대한 파일/메모리에서의 크기, 위치, 속성등이 정의되어 있습니다. PE 헤더의 끝부분과 각 섹션의 끝에는 Null padding이라고 불리우는 영역이 존재합니다. 컴퓨터에서 파일, 메모리, 네트워크 패킷 등을 처리할 때 효율을 높이기 위해 최소 기본 단위 개념을 시용하는데, PE 파일에도 같은 개념이 적용된것입니다.
VA & RVA
VA(Virtual Address)는 프로세스 가상 메모리의 절대주소를 말하며, RVA(Relative Virtual Address)는 어느 기준 위치(ImageBase)에서부터의 상대주소를 말합니다. VA와 RVA의 관계는 다음 식과 같습니다.
RVA + ImageBase = VA
PE 헤더
DOS Header
Microsoft는 PE File Format을 만들 때 당시에 널리 사용되던 DOS 파일에 대한 하위호환성을 고려해서 만들었습니다. 그 결과로 PE 헤더의 제일 앞부분에는 기존 DOS EXE Header를 확장시킨 IMAGE_DOS_HEADER 구조체가 존재합니다.
MAGE_DOS_HEADER 구조체의 크기는 40 입니다. 이 구조체에서 꼭 알아둬야 할 중요한 멤버는 e_magic과 e_lfanew입니다.
e_magic : DOS signature(4D5A -> Ascill 값 MZ)
e_lfanew: NT header의 offset을 표시(파일에 따라 가변적인 값을 가짐)
DOS Stub
DOS Header 밑에는 DOS Stub이 존재합니다. DOS Stub의 존재 여부는 옵션이며 크기도 일정하지 않습니다. DOS Stub의 존재 여부는 옵션이며 크기도 일정하지 않습니다.(DOS Stub이 없어도 파일 실행에는 문제가 없습니다.)
NT header
파일의 개략적인 속성을 나타내는 IMAGE_FILE_HEADER 구조체입니다.
IMAGE_FILE_HEADER 구조체에서 아래 4가지 멤버가 중요합니다. ( 이 값이 정확히 세팅되어 있지 않으면 파일은 정상적으로 실행되지 않습니다.)
#1 Machine : Machine 넘버는 CPU별로 고유한 값이며 32비트 Intel x86 호환 칩은 14C의 값을 가집니다.
#2 NumberOfSections : PE 파일은 코드, 데이터, 리소스 등이 각각의 섹션에 나뉘어서 저장된다고 설명했습니다. 이 값은 그 섹션의 개수를 나타냅니다.
#3 SizeOfOptionHeader : IMAGE_OPTIONAL_HEADER32 구조체의 크기를 나타냅니다.
#4 Characteristics : 파일의 속성을 나타내는 값으로, 실행이 가능한 형태인지(executable or not) 혹은 DLL 파일인지 등의 정보들이 bit OR 형식으로 조합됩니다.
NT Header - Optional Header
PE 헤더 구조체 중에서 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32입니다.
#1 Magic
Magic 넘버는 IMAGE_OPTIONAL_HEADER32 구조체의 경우 10B, IMAGE_OPTIONAL_HEADER64 구조체인 경우 20B
#2 AddressOfEntryPoint
EP(EntryPoint)의 RVA 값을 가지고 있습니다. 이 값이야말로 프로그램에서 최초로 실행되는 코드의 시작 주소로, 매우 중요한 값입니다.
#3 ImageBase
프로세스의 가상 메모리는 0 ~ FFFFFFFF (4GB) 범위입니다. (32비트의 경우) ImageBase는 이렇게 광활한 메모리에서 PE 파일이 로딩되는 시작 주소를 나타냅니다.
EXE, DLL 파일은 user memory 영역인 0~7FFFFFFF 범위에 로딩되고, SYS 파일은 kernel memory 영역인 80000000~FFFFFFFF 범위에 로딩됩니다. PE 로더는 PE 파일을 실행시키기 위해 프로세스를 생성하고 파일을 메모리에 로딩한 후 EIP 레지스터 값을 ImageBase+AddressOfEntryPoint 값으로 세팅합니다.
#4 SectionAlignment, FileAilgnment
PE 파일의 Body 부분은 섹션(Section)으로 나뉘어져 있습니다. 파일에서 섹션의 최소단위를 나타내는 것이 바로 FileAlignment이고 메모리에서 섹션의 최소단위를 나타내는 것이 SectionAlignment 입니다. (하나의 파일에서 양 쪽의 값이 같을 수도 있고 다를 수도 있습니다.) 파일/메모리의 섹션 크기는 반드시 각각 FileAlignment/SectionAlignment의 배수가 되어야 합니다.
#5 SizeOfImage
SizeofImage는 PE파일이 메모리에 로딩되었을 때 가상 메모리에서 PE Image가 차지하는 크기를 나타냅니다. 일반적으로 파일의 크기와 메모리에 로딩된 크기는 다릅니다.
#6 SizeOfHeader
SizeOfHeader는 PE 헤더의 전체 크기를 나타냅니다. 이 값 역시 FileAlignment의 배수여야 합니다. 파일 시작에서 SizeOfHeader Offset만큼 떨어진 위치에 첫 번째 섹션이 위치합니다.
#7 Subsystem
이 Subsystem의 값을 보고 시스템 드라이버 파일(sys)인지, 일반 실행 파일인지(exe,dll) 구분 할 수 있습니다.
#8 NumberOfRvaAndSizes
IMAGE_OPTIONAL_HEADER32 구조체의 마지막 멤버인 DataDirectory 배열의 개수를 나타냅니다.
#9 DataDirectory
DataDirectory는 IMAGE_DATA_DIRECTORY 구조체의 배열로, 배열의 각 항목마다 정의된 값을 가집니다.
SECTION HEADER
각 섹션의 속성을 정의한 것이 섹션 헤더입니다. 섹션 헤더는 각 섹션별 IMAGE_SECTION_HEADER 구조체의 배열로 되어 있습니다.
VirtualAddress와 PointerToRawData는 아무 값이나 가질 수 없고, 각각 (IMAGE_OPTIONAL_HEADER32에 정의된) SectionAlignment,와 FileAilgnment에 맞게 결정됩니다.
VirtualSize와 SizeOfRawData는 일반적으로 서로 다른 값을 가집니다. 즉, 파일에서의 섹션 크기와 메모리에 로딩된 섹션의 크기는 다르다는 것입니다.
IAT
IAT란 쉽게 말해서 프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 테이블입니다.
DLL(Dynamic Linked Libary)
Windows OS에서는 멀티 태스킹을 지원하기 때문에 프로그램 실행 파일에 라이브러리 함수들을 포함시켜서 사용하는 것은 심각한 메모리 낭비가 되었습니다. 그래서 Windows OS 설계자들은 아래와 같은 DLL 개념을 고안해냈습니다.
- 프로그램에 라이브러리를 포함시키지 말고 별도의 파일(DLL)로 구성하여 필요할 때마다 불러 쓰자.
- 일단 한 번 로딩된 DLL의 코드, 리소스는 Memory Mapping 기술로 여로 Process에서 공유해 쓰자.
- 라이브러리가 업데이트 되었을 때 해당 DLL 파일만 교체하면 되니 쉽고 편해서 좋다.
실제 DLL 로딩 방식은 2가지 입니다. 프로그램이 사용되는 순간에 로딩하고 사용이 끝나면 메모리에서 해제되는 방법(Explicit Linking)과 프로그램 시작할 때 같이 로딩되어 프로그램 종료할 때 메모리에서 해제되는 방법(Implicit Linking)이 있습니다. IAT는 바로 Implicit Linking에 대한 메커니즘을 제공하는 역할을 합니다.
아래 그림은 notepad.exe의 kernel32.dll의 CreateFileW를 호출하는 코드입니다.
CreateFileW를 호출할 때 직접 호출하지 않고 01001104 주소에 있는 값을 가져와서 호출합니다.(모든 API 호출은 이런 방식으로 되어 있습니다.) 01001104 주소는 notepad.exe에서 '.text' 섹션의 IAT 메모리 영역입니다. 01001104 주소의 값은 7C8107F0이며, 7C8107F0 주소가 바로 notepad.exe 프로세스 메모리에 로딩된 kernel32.dll의 CreateFileW 함수 주소입니다.
그럼 왜 바로 call 7C8107F0의 주소를 하지 않는냐면 프로그램이 실행되는 순간 이 프로그램이 어떤 환경에서 동작할 지 알 수가 없기 떄문입니다. 프로그램의 동작 환경에 따라 kernel32.dll의 버전이 달라지고, CreateFileW 함수의 위치(주소)가 달라지기 때문에 그걸 가리키는 주소만 마련해두고 프로그램이 실행되는 순간 PE 로더가 01001104의 위치에 CreateFileW의 주소를 입력해줍니다.
또 다른 이유는 DLL Relocation 때문인데 보통 DLL 파일의 ImageBase 값은 0x10000000 입니다. 어떤 DLL을 로드할려고 할 때 처음에는 ImageBase에 로드할려고 합니다. 하지만 그 공간에 다른 dll의 점유하고 있다면 PE 로더는 다른 비어있는 메모리 공간을 찾아서 DLL을 로드시켜주게 됩니다. 이것이 실제 주소를 하드코딩할 수 없는 이유입니다.
IMAGE_IMPORT_DESCRIPTOR
PE 파일은 자신이 어떤 라이브러리르 임포트(Import)하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체에 명시하고 있습니다.
Import : library에게 서비스(함수)를 제공받는 일
Export : library 입장에서 다른 PE 파일에게 서비스(함수)를 제공하는 일
일반적인 프로그램에서는 보통 여러개의 라이브러리르 임포트하기 때문에 라이브러리의 개수만큼 위 구조체의 배열형식으로 존재하게 되며, 구조체 배열의 마지막은 NULL 구조체로 끝나게 됩니다.
아래 그림은 notepad.exe의 kernel32.dll에 대한 프로그램이 실행되기 전의 IMAGE_IMPORT_DESCRIPTOR 구조입니다.
INT(Import Name Table)와 IAT(Import Address Table)는 long type(4byte 자료형) 배열이고 null로 끝납니다.
INT에서 각 원소의 값은 iMAGE_IMPORT_BY_NAME 구조체 포인터입니다. 프로그램이 실행전에는 IAT도 IMAGE_IMPORT_BY_NAME 테이블을 같이 가리키고 있습니다.
하지만 프로그램이 실행되게 되면 아래의 순서에 따라 FirstThunk가 가리키는 IAT 배열에 실질적인 라이브러리 함수의 주소를 구해 저장하게 됩니다.
Disk에 존재할 때 Import Section의 구조
가상 주소 공간에 매핑된 후의 Import section
EAT
EAT(Export Address Table)은 라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 해주는 핵심 메커니즘입니다. 즉, EAT를 통해서만 해당 라이브러리에서 익스포트하는 함수의 시작 주소를 정확히 구할 수 있습니다. 앞서 설명한 IAT와 마찬가지로 PE 파일 내의 특정 구조체(IMAGE_EXPORT_DIRECTORY)에 익스포트 정보를 저장하고 있습니다. 라이브러리의 EAT를 설명하는 IMAGE_EXPORT_DIRECTORY 구조체는 PE파일에 하나만 존재합니다.
IMAGE_EXPORT_DIRECTORY
라이브러리에서 함수 주소를 얻는 API는 GetProcAddress()입니다. 이 API가 바로 EAT를 참조해서 원하는 API의 주소를 구하는 것입니다. GetProcAddress() API가 함수 이름을 가지고 함수 주소를 얻어내는 과정을 설명하겠습니다.
14장 실행압축
데이터 압축
패커
PE 패커(Packer)란 실행 파일 압축기를 말합니다.
#1 사용목적
- PE 파일의 크기를 줄이고자 하는 목적
- PE 파일의 내부 코드와 리소를 감추기 위한 목적
#2 패커종류
- 순수한 의도의 패커(UPX, ASPack 등)
- 불순한 의도의 패커(UPack, PESpin, NSAnti 등) : 악성 프로그램 등에서 사용
프로텍터
PE 프로텍터(Protector)란 PE 파일을 'Reverse Code Engineering'으로부터 보호하기 위한 유틸리티입 입니다.
#1 사용목적
- 크래킹 방지
- 코드 및 리소스 보호
#2 프로텍터 종류
- 상용 프로텍터 : ASProtect, Themida, SVKP 등
- 공개용 프로텍터 : UltraProtect, Morphine 등
15장 UPX 실행 압축된 notepad 디버깅
보통 UPX 압축된 프로그램의 경우 EP 부분에서 PUSHAD를 OEP를 복원하는 부분에서 POPAD를 수행합니다. OEP를 복원할 때 POPAD 명령을 호출해 레지스터들을 복원하므로 PUSHAD를 할 때 이 레지스터들에 하드웨어 BP를 걸어주게 되면 POPAD시에 BP가 걸려 멈추게 됩니다. 하드웨어 BP의 특성상 BP가 걸린 부분의 명령어가 실행된 이후에 제어가 멈추게 되므로 POPAD가 실행된 후에 제어가 멈추고 바로 밑에 부분에 JUMP OEP를 볼 수 있습니다. 이 정보를 이용해서 OEP를 찾아낸 다음 DUMP를 뜨고 (Import REConstructor) 같은 툴로 IAT정보를 복원해주게 되면 정상적으로 리패킹된 프로그램을 얻어낼 수 있습니다.
'Talks > milan's Talks' 카테고리의 다른 글
[리버싱 핵심원리] 2주차 3-21장 (Windows 메시지 후킹) (0) | 2015.03.06 |
---|---|
구글 SSL PacketCapture 시도 (0) | 2015.02.06 |
Charles Proxy를 이용한 SSL Traffic Capture (0) | 2015.01.30 |
웹 어플리케이션 동작 방식의 이해 (0) | 2015.01.24 |
IP와 Subnet, 그리고 TCP/IP 패킷간의 상관관계 (0) | 2015.01.17 |