3. PE 구조
1) PE 구조란
▣ PE 란?
- Microsoft의 운영체제 Windows 3.1 부터 지원되는 (실행)파일의 형식
▣ PE 파일의 종류
- 실행 파일 계열 : EXE, SCR(Screen Saver)
☞ 독립프로그램 형태. 혼자서 실행가능
- 라이브러리 계열 : DLL, OCX
☞ 독립프로그램 형태가 아님. 혼자서 실행불가하고 다른 프로그램에 들어가서 빌붙어 실행.
- 드라이브 계열 : SYS
- 오브젝트 파일 계열 : OBJ
==> PE 구조를 보기위해 PEview 프로그램을 사용할 것임.
▣ PE 구조
[헤더] -> like 설명서
- IMAGE_DOS_HEADER & Dos Stub : 시그니처랑 다음 헤더의 위치 및 Dos에서 실행될때..
- IMAGE_NT_HEADER
☞ Signature : PE\0\0 (4바이트)
☞ IMAGE_FILE_HEADER : 파일에 대한 전반적인 내용
☞ IMAGE_OPTIONAL_HEADER : 환경설정. 제일 크고 정보가 많음.
- Section Table : 크기 미지정 배열. 뒤에 나오는 섹션수만큼 배열이 존재. (각 센션에 대한 정보)
[섹션] -> 실제 실행에 필요한 정보들(헤더부분보다 크기가 큼)
- .text : 실행코드
- .data : 데이터영역
- .edata : export (실행파일은 대부분 x, dll 파일은 대부분 o)
- .idata : import (실행파일은 대부분 o, dll 파일은 대부분 o)
2) IMAGE_DOS_HEADER
#include <winnt.h> typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD e_cparhdr; // Size of header in paragraphs WORD e_minalloc; // Minimum extra paragraphs needed WORD e_maxalloc; // Maximum extra paragraphs needed WORD e_ss; // Initial (relative) SS value WORD e_sp; // Initial SP value WORD e_csum; // Checksum WORD e_ip; // Initial IP value WORD e_cs; // Initial (relative) CS value WORD e_lfarlc; // File address of relocation table WORD e_ovno; // Overlay number WORD e_res[4]; // Reserved words WORD e_oemid; // OEM identifier (for e_oeminfo) WORD e_oeminfo; // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; // File address of new exe header } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
- e_magic : MZ
- e_lfanew : 다음 헤더의 위치(NT 헤더)
3) IMAGE_NT_HEADER
#include <winnt.h> typedef struct _IMAGE_NT_HEADERS64 { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER64 OptionalHeader; } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
- DWORD Signature : PE\0\0 (온전히 4바이트로 존재해야함)
- IMAGE_FILE_HEADER FileHeader : 파일에 대한 전반적인 설명
- IMAGE_OPTIONAL_HEADER OptionalHeader : 환경설정
▣ IMAGE_FILE_HEADER
#include <winnt.h> 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;
- Machine : 빌드 당시 cpu 정보 (I386 : 32bit 운영체제)
- NumberOfSection : 읽어올 섹션의 수 (실제 존재하는 섹션 수보다 적을수도 있음)
- TimeDateStamp : PE 이미지가 빌드된 시간
- SizeOfOptionalHeader : 00E0으로 고정된 크기
- Characteristics : 속성 (실행가능? dll? 32비트?)
▣ IMAGE_OPTIONAL_HEADER
#include <winnt.h> typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; // // NT additional fields. // 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;
[Standard Fields]
- Magic : 비트정보. PE이미지가 몇 비트인가?
- AddressOfEntryPoint : 진입점. 첫번째로 실행될 코드의 주소(RVA)
☞ 주소표기방식
(1) VA (Virtual Addr.) : 실제 주소
(2) RVA (Relative Viture Addr.) : 상대주소 (메모리 어느위치에 들어갈지 정확히 모르니까..)
[NT Additional Fields]
- ImageBase : PE 파일이 메모리에 로딩되는 시작주소
☞ ImageBase + AddressOfEntryPoint == 처음 EIP
☞ ASAR이 적용되어 있는 곳에선 무의미함.
- SectionAlignment : 메모리상의 섹션의 배치 간격
- FileAlignment : 파일상의 섹션 배치 간격
☞ 위의 두개는 새로운 섹션이 할당될 수 있는 위치를 결정. (숫자의 배수마다)
☞ 요즘은 2 값이 같은 값을 많이 가져서 파일과 메모리상에서 크게 달라지진 않음.
- SizeOfImage : PE 이미지 전체의 크기 (가상메모리상에서..)
☞ Alignment 값에 따라 실제보다 크거나 작을수도 있음.
- Subsystem : 콘솔기반? 윈도우기반? --> 그냥 실행시키면 알 수 있음.
- NumberOfRvaAndSizes : Datadirectory 원소 수 -> 0x10 고정.
☞ 원소 하나하나가 구조체로 되어있음. (RVA & SIZE정보 담고 있음)
- IMAGE_DATA_DIRECTORY
#include <winnt.h> typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
[정적 라이브러리]
- 실행시부터 종료할때까지 내 프로그램에 포함되어 있음.
[동적 라이브러리]
- 내 실행파일 자체에는 해당 함수가 존재하지 않음. 프로그램 실행 직후에 프로그램이 사용하는 메모리 상으로 추가적으로 들어옴.
- 이 때 필요한 것이 IMPORT TABLE! -> 빌드하는 시점에는 해당 함수의 정확한 주소를 모름.
[IMPORT & EXPORT]
- Directory Table : 사용하는 dll 파일들의 목록
- Address Table : 사용하는 함수들의 주소
- Name Table : 사용하는 함수들의 이름.
---> 함수 이름을 가지고 Address Table에 가서 해당 주소를 얻어옴.
4) IMAGE_SECTION_HEADER
#include <winnt.h> 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;
- VirtualSize & VirtualAddress : 메모리상에서의 섹션 크기 및 위치 (RVA)
- SizeOfRawData & PointerToRawData : 파일 상에서의 섹션 크기 및 위치
- Characteristics : 섹션의 속성 (읽,쓰,실/8,4,2)
☞ 섹션 별로 따로 권한을 줄 수 있음.
☞ 예를 들어, 데이터 섹션은 쓰기 권한이 필요(전역변수) 그러나 실행권한은 딱히 필요없음.
5) PE 구조를 이용한 실습
▣ putty 파일을 실행전 메세지박스를 띄워보겠다.
- MessageBox(핸들 윈도우, 내용, 제목, 버튼모양)
☞ 핸들 윈도우 : 핸들은 윈도우 상에서 개체 구분시 붙임. 윈도우 핸들은 화면에 뜨는 창마다 핸들을 붙임.
☞ 0 : 실행중인 프로그램의 핸들
▣ MessageBox(0, "go to hell", "hehehe", 0); 을 삽입하려면 어셈코드는..
push 0
push "hehehehe"
push "go to hell"
push 0
call MessageBox함수 주소
JMP 원래EntryPoint
1. 우선 두 개의 문자열을 메모리상에 삽입하기 위해 PEview로 빈공간을 찾아준다.
2. MessageBox 함수 주소를 알아보도록 한다. (USER32.dll 속 존재)
☞ MessageBox의 RVA는 A03E0임을 알아냈다.
☞ VA를 알아내기 위해서는 RVA + Image base == 4A03E0
3. 정상작동을 위해 기존의 첫 EIP를 구한다.
☞ Entry Point(RVA) + Image Base == 45C478
4. 코드 삽입을 한다. 코드 삽입을 위한 위치는 1번에서 확인한 빈공간인 47B8B0(VA)으로 한다.
☞ OllyDBG를 이용하여 47B8B0로 이동한다.
push 0
push 0047B870
push 0047B890
push 0
call dword ptr [4A03E0]
JMP 45C478
☞ 이 문장들을 올리디버거로 해당 공간에 넣어준다.
☞ 수정 후 오른쪽 버튼 클릭 -> copy to exe... -> all -> all
5. 시작 Entry Point를 수정.
☞ RVA로 7B8B0에 코드를 넣었기 때문에 HxD로 Entry Point를 수정해준다.
☞ 이 때, 리틀엔디안이기때문에 B0 B8 07 로 저장해야한다.
6. 시작하면 다음과 같은 메시지 박스가 뜨며 확인을 누르면 정상적으로 putty가 작동하는것을 확인가능하다.
i2sec 대구지점 23기 수료생.
'해킹&보안 > 리버싱' 카테고리의 다른 글
[D+10] 패킹&언패킹 (2) (0) | 2017.04.12 |
---|---|
[D+9] 패킹&언패킹 (1) (0) | 2017.04.11 |
[D+7] 함수호출규약 (0) | 2017.03.30 |
[D+6] 리버싱 소개 (6) (0) | 2017.03.28 |
[D+5] 리버싱 소개 (5) (0) | 2017.03.27 |