1. 소개
Windows 운영체제는 기본적으로 파일의 포맷을 PE File Format으로 사용
분류
32비트 형태의 실행 파일: PE 또는 PE32
64비트 형태의 실행 파일: PE+ 또는 PE32+
2. PE File Format
종류
종류 | 주요 확장자 |
실행 계열 | EXE, SCR |
라이브러리 계열 | DLL, OCX, CPL, DRV |
드라이버 계열 | SYS, VXD |
오브젝트 파일 계열 | OBJ |
32비트 형태의 실행 파일: PE 또는 PE32
64비트 형태의 실행 파일: PE+ 또는 PE32+
2.1 기본 구조
구성: "PE Header" + "PE Body"
헤더: 이 파일에 대한 대략적인 정보들을 갖고 있음
바디: 실제 데이터들은 바디에 들어가 있음
섹션 헤더: 각 섹션에 대한 파일/메모리의 크기 및 위치, 속성 등이 정의되어 있다.
NULL padding:
일반적으로 효율성을 위해 파일/메모리의 크기는 최소 기본 단위 사용
PE 파일도 이 원칙을 적용
-> 헤더와 각 섹션은 최소 기본 단위의 배수에 해당되는 위치부터 시작
-> 실제 사용하지 않는 나머지 부분은 NULL로 패딩

2.2 VA & RVA
VA (Vitual Address): 프로세스 가상 메모리의 절대 주소
RVA (Relative VA): 기준 위치(ImageBase)로부터의 상대 주소
PE 헤더 내의 정보는 RVA 형태가 많음
ImageBase + RVA = VA
3. PE 헤더
3.1 PE 헤더 - DOS Header
DOS 파일에 대한 호환성을 고려하여 만든 헤더
헤더의 앞 부분에는 기존 DOS EXE Header를 확장시킨 64바이트 크기의 IMAGE_DOS_HEADER 구조체가 존재
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; //DOS signature : 4D5A("MZ")
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew; //offset to NT header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER
주요 field
1) e_magic: DOS의 signature를 나타냄. MZ값이 와야 함.
2) e_lfanew: NT Header의 offset을 나타냄. (파일에 따라 값이 가변적)

e_magic 부분: MZ의 아스키 값인 4D5A가 나타남
e_lfanew 값: 000000E0 (little endian임에 주의)가 나타남
3.2 PE 헤더 - DOS Stub
DOS Header 다음에 위치한다.

존재 여부는 Optional이며, 크기도 가변적이다.
-> DOS Header의 e_lfanew 값을 이용
코드와 데이터가 함께 구성 됨
Windows 32비트 운영체제에서는 이 부분의 코드가 실행되지 않는다 -> PE 파일로 인식하여 이 부분을 무시한다.
DOS 환경이나 DOS용 디버거를 이용하여 실행하면 이 코드 부분이 실행된다.
-> windows XP 환경에서 컴맨드 창에 다음과 갈이 입력. debug c:₩windows₩notepad.exe
-> 커서에 'u'명령 입력 (unassemble)
-> 상단의 그림과 같은 화면이 출력된다.
-> 문자열 출력 후, 종료
3.3 PE 헤더 - NT Header
NT Header 구조체: IMAGE_NT_HEADER
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
1) 크기: 248 바이트 (F8h)
2) Field
1) Signature: 50450000h ("PE"00) 값
2) FileHeader
3) OptionaHeader

3.4 PE 헤더 - NT Header - File Header
파일의 개략적인 "속성"을 표시 (IMAGE FILE HEADER) : 20 바이트 (WORD-2바이트, DWORD-4바이트)
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;
주요 필드
1 ) Machine: CPU 별 고유 값 -> 32비트 Intel x86 호환칩: 0x014C
2 ) NumberofSections: PE파일에 저장된 세션의 수
3 ) SizeofOptonalHeader: IMAGE NT_HEADERS32 구조체의 마지막 field인 IMAGE OPTIONAL HEADER32 의 크기
-> PE2+ 형태의 파일은 IMAGE_OPTIONALHEADER62를 가지기 때문에 구조체 크기의 차이가 발생한다.
이를 해결하기 위하여 구조체의 크기를 명시한다.
4 ) Characteristics: 파일의 속성을 표시
-> 실행 파일인지 혹은 DLL 파일인지등의 정보를 bit OR 형식으로 제공
-> File is executable : 0x0002
-> link 참조: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_nt_headers32
5 ) symbol = 정적함수, 변수 -> Symbol Table은 이러한 함수/변수의 주소값을 정리한 테이블

3.5 PE 헤더 - NT Header - Optional Header
PE헤더 구조체 중에서 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
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;
1) Magic:
IMAGE_OPTINAL_HEADER32: 010B
IMAGE_OPTINAL_HEADER64: 020B
2) AddressOfEntryPoint
EP의 RVA 값
프로그램에서 최초로 실행되는 코드의 시작 주소 값
3) ImageBase
32비트의 경우, 프로그램의 가상메모리는 주소 범위 :0~FFFFFFFF
가상메모리에서 PE 파일이 로딩되는 시작 주소
EXE, DLL 파일: 0~7FFFFFFF (User Memory 영역)
SYS 파일: 80000000~FFFFFFFF (Kernel Memory 영역)
PE로더는 PE파일을 실행하기 위해 프로세스를 생성하고,
파일을 로딩한 후 EIP = ImageBase + AddressofEntrypoint 로 VA 설정한다.
4) SectionAlignement & FileAlignment
SectinAlignment: "메모리"에 저장된 섹션의 최소 단위
FileAlignment: "파일"에서 섹션의 최소 단위
파일/메모리에 저장된 PE 파일의 Body를 구성하는 섹션의 크기는 각각 이 두 값의 배수가 되어야 한다.
5) SizeOflmage
PE 파일이 메모리에 로딩될 때 가상 메모리에서 PE lmage의 크기
6) SizeOfHeaders
PE 헤더의 전체 크기: 파일 시작에서 SizeOfHeaders 옵셋만큼 떨어진 위치에 슈 섹션이 존재
FileAlignment의 배수
7) Subsystem: *.sys 파일인지 또는 일반 실행 파일(*.exe, *.dl)인지를 구분한다
1: driver file -> 시스템 드라이버. ex: *.ntfs, *.sys
2: GUI file -> 창 기반 Application. ex: notepad.exe
3: CUI file -> 콘솔 기반 Applicatin. ex: cmd.exe
8) NumberofRvaAndsizes: DataDirectory 배열의 개수
-> 배열이 2개 이상 있을 수 있나? ( 지금까지 1만 보고 2 이상 쓰고 있는 파일이 어떤 파일인지 아직 보지를 못함)
9) DataDirectory: IMAGE_DATA_DIRECTORY 구조체의 배열로, 배열의 각 항목마다 정의된 값을 지닌다. 각 항목은 다음과 같다.
DataDirectory[0] = EXPORT Directory //중요
DataDirecotry[1] = IMPORT Directory //중요
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 Direectory
DataDirectory[E] = COM_DESCRIPTOR Directory
DataDirectory[F] = Reserved Directory

3.6 섹션 헤더 (Section Header)
각 섹션의 속성(Property)을 정의
각 섹션별로 IMAGE_SECTION_HEADER 구조체의 배열로 되어 있다.
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;
섹션으로 구분된 데이터 (code, data, resource, etc) 관리
: 프로그램의 안전성 확보 -> ex. overflow 발생으로 인한 오류 예방
메모리 속성별 접근 권한
종류 | 접근 권한 |
code | 실행, 읽기 권한 |
data | 비실행, 읽기, 쓰기 권한 |
resource | 비실행, 읽기 권한 |
섹션 헤더의 개수 = NT_Header. FileHeader. NumberOfSections 에 기록
주요 field
1) VirtualSize: 메모리에서 섹션이 차지하는 크기
2) VirtualAddress: 메모리에서 섹션의 시작주소(RVA)
3) SizeOfRawData: 파일에서 섹션이 차지하는 크기
4) PointerToRawData: 파일에서 섹션의 시작 위치
5) Characteristics: 섹션의 속성 (bit OR로 표시)
VirtualAddress와 PointerToRawData는
sectionAlignment와 FileAlignment의 조건에 맞게 결정된다.
Name: 별다른 규정이 없음. NULL로 끝나지 않음 (8바이트)
Characteristics
Define | Value | 설명 |
IMAGE_SCN_CNT_CODE | 0x00000020 | 섹션에 코드가 포함됨 |
IMAGE_SCN_CNT_INITIALIZED_DATA | 0x00000040 | 섹션에 초기화된 데이터가 포함됨 |
IMAGE_SCN_CNT_UNINITIALIZED_DATA | 0x00000080 | 섹션에 초기화되지 않은 데이터가 포함됨 |
IMAGE_SCN_MEM_EXECUTE | 0x20000000 | 섹션 실행 가능 |
IMAGE_SCN_MEM_READ | 0x40000000 | 섹션 읽기 가능 |
IMAGE_SCN_MEM_WRITE | 0x80000000 | 섹션 쓰기 가능 |

메모장의 경우 3개의 섹션이 존재하므로 3 부분이 있다. Name 멤버를 기준으로 3개(.text / .data / .rsrc)
4. RVA to RAW
Image: PE 파일의 정보가 메모리에 로딩된 상태를 지칭
섹션에서 메모리 시작 주소 값인 VirtualAddress와
파일의 시작 주소/옵셋 값인 PointerToRawData은 다를 수 있다.
그러므로 메모리와 파일 사이에 데이터 매핑이 필요하다 -> RVA to RAW ; RAW to RVA
메모리 주소 값(RVA)을 이용하여 File에서 대응되는 RAW 값 찾기
(방법)
RAW - PointerToRawData = RVA - VirtualAddress
RAW = RVA - Virtualaddress + PointerToRawData
1) RVA 값이 속한 섹션을 찾는다
2) 해당 섹션의 VirtualAddress와 PointerToRawData 값을 섹션 헤더에서 읽는다
3) 수식을 이용하여 RAW (=File Offset)를 계산한다.
5. IAT (lmport Address Table)
process, memory, dll 구조등에 대한 내용이 기록됨
프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 테이블
5.1 DLL
DLL (Dynamic Linked Library)
멀티 태스킹을 수행할 때 정적라이브러리의 비효율성을 개선하기 위해 개발됨
1) 개별 프로그램에 라이브러리를 각각 포함시키지 말고 별도로 DLL로 구성하여 필요할 때마다 불러 쓰자
2) 한번 로딩된 DLL의 코드와 리소스는 Memory Mapping 기술로 여러 프로세서에서 공유하여 쓰자
3) 라이브러리의 업데이트가 필요하면, 해당 DLL 파일만 교체하자.
종류
1) 명시적 링크 (Explicit Linking): 프로그램에서 사용되는 순간에 로딩되고, 사용이 끝나면 메모리에서 해제 된
2) 암시적 링크 (lmplicit Linking); 프로그램 시작할 때 같이 로딩되고, 프로그램이 종료될 때 메모리에서 해제 됨
IAT는 Implicit Linking에 대한 메커니즘을 제공한다.
5.2 IDT - IMAGE_IMPORT_DESCRIPTOR
PE 파일은 프로그램이 import 하여 사용할 라이브러리를
IDT에 IMAGE_IMPORT_DESCRIPTOR (IID)구조체로 명시한다.
-> 프로그램 로딩 시 IAT 갱신을 위해 사용
프로그램은 여러 개의 라이브러리를 사용하기 때문에,
라이브러리 개수만큼 구조체 배열 형식으로 존재
-> 구조체 배열의 마지막 원소는 NULL구조체
IMAGE_OPTIONAL_HEADER32.DataDirector[1].VirtualAddress 값이 IID 구조체의 시작 주소 (RVA) 값이다
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //INT(Import Name Table) address (RVA)
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name; //library name string address (RVA)
DWORD FirstThunk; //IAT(Import Address Table) address (RVA)
} IMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //ordinal
BYTE Name[1]; //function name string
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
IID 구조체 주요 field
1) OriginalFirstThunk: INT(Import Name Table)의 주소 (RVA) -> 함수정보
2) Name: Library 이름 문자열 주소 (RVA)
3) FirstThunk: IAT (Import Address Table)의 주소 (RVA)
임포트 함수 주소를 IAT에 기록하는 순서
1) IID의 Name 필드에서 라이브러리 이름을 읽는다: kernel32.dll
2) 해당 라이브러리를 로딩한다.
-> LoadLibrary("kernel32.dll") 함수 호출
3) IID의 OriginalFirstThunk 필드에서 INT 주소를 읽는다.
4) INT에서 배열의 값을 하나씩 확인하여 해당 IMAGE_IMPORT_BY_NAME의 주소(RVA)를 읽는다.
5) IMAGE_IMPORT_BY_NAME의 Hint 또는 Name 필드를 이용하여, 해당 함수의 시작 주소값을 얻는다
-> GetProcAddress("GetCurrentThreadld") 함수 호출
6) IID의 FirstThunk필드에서 IAT 주소를 읽는다.
7) IAT 배열에 step-5에서 확보한 주소를 입력한다
8) INT가 끝날 때까지 4~7을 반복한다. (NULL 배열)
'공부 > 리버싱 핵심원리' 카테고리의 다른 글
UPX 언패킹 (0) | 2024.05.12 |
---|---|
IAT 따라가기 (0) | 2024.05.12 |
Lena's Reversing for Newbies (0) | 2024.04.17 |
abex' crackme #2 분석 (0) | 2024.04.17 |
abex' crackme #1 분석 (0) | 2024.04.15 |