본문으로 바로가기

[D+8] PE 구조

category 해킹&보안/리버싱 2017. 4. 10. 22:23
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

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로 빈공간을 찾아준다.

☞ .text 섹션의 마지막 부분을 보면 널인 공간이 많다. 
☞ 이 공간에 삽입하기 위해 HxD로 해당 주소에 문자열들을 넣어준다.
0007AC70(File Offset) : "HeHeHe"    -> 47B870(VA)
0007AC90(File Offset) : "Go to Hell!"    -> 47B890(VA)




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