ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • PE 구조 (3) - 계산기의 PE 구조 / PE offset 확인하는 법
    Knowledge/Reversing 2019. 9. 18. 17:31

    안녕하세요. 오랜만에 PE 구조 관련글을 다시 적게 되었습니다.

     

    요즘 자격증 공부로 바빠서 하지 못했던 개인 공부들을 다시 시작하며 밀렸던 글들을 적어나가려고 한다.

    인터넷에는 대부분 32bit notepad로 PE구조를 분석한 글들이 많이 있다.

    그 이유는 아마 리버싱 핵심원리 책에서 예로 든 프로그램이 바로 이 메모장이여서 그런게 아닐까하는 생각이 든다.

    그래서 지난번 글에선 필자도 메모장으로 글을 포스팅했었지만 이번엔 필자의 실습을 위해서 계산기를 직접 offset을 알아보려고 한다.

     

    오늘 볼 파일은 calc.exe(x86)이다. feat.windows xp

    오늘 필자가 분석할 큰 구조는 이렇게 나뉘어진다.

     

    Header

    DOS Header

    DOS Stub

    NT Header

    Section Header

     

    Body

    Section

     

     

    공부한지 너무 오래된 내용이라서 다시 분석하려니까 처음 공부하는 기분이네;

     

     

    나도 모르게 마음의 소리가 적힌것 같지만 넘어가도록 하자.

     

    각각의 오프셋 위치를 알기 위해선 무엇을 먼저 봐야 할까?

     

    가장 먼저 DOS Header를 찍어주자!

    이유는 간단하다. 가장 처음이기 때문이다.

     

    clac.exe의 헥스값을 확인해보자.

     

     

     

    DOS Header는 offset 0부터 시작된다.

    그럼 DOS header 구조체의 크기는 몇바이트일까?

    IMAGE_DOS_HEADER 구조체 정의를 보도록 하자.


    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;

     

     

    지난 글에서 DOS header와 stub의 기능을 설명해 두었으니 이번 글에선 설명은 넘어가도록 하자.

    여기서 우리가 봐야할 것은 WORD e_magic 뿐이다.

     

    e_magic의 위치를 보면 가장 처음 2bytes다.

    계산기 헥스값 가장 처음 2bytes는 4D, 5A이다.

    DOS모드로 실행시 MZ 파일 형식으로 실행된다는 걸 의미한다.

     

    구조체를 분석해보면 DOS header의 크기를 알 수 있다.

    일반 WORD 변수가 16개,

    WORD 배열이 2개.

    LONG 타입 변수가 1개이다.

     

    IMAGE_DOS_HEADER

    16 * 2 + 4 + 2 * 4 + 2 * 10 = 64 Bytes

     

    16진수 값으로 보면 40이다.

    즉 0~ 3F offset까지가 DOS header라는 뜻이다.

     

     

    DOS Header

    바로 이 부분으로 우리는 첫 2bytes e_magic만 보고 넘어가면 된다.

     

     

    우린 DOS header의 크기를 알아봤다.

    그럼 다음엔 dos stub을 봐야할까?

    그렇지 않다.

     

    왜냐하면 NT header의 시작값이 보통은 육안으로 확인이 쉽기 때문이다.

    그 이유는 NT Header 구조체를 보면 알 수 있다.

     

    typedef struct _IMAGE_NT_HEADERS {
        DWORD Signature;
        IMAGE_FILE_HEADER FileHeader;
        IMAGE_OPTIONAL_HEADER32 OptionalHeader;
    } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

     

    구조체의 처음 시작이 Signature라는 변수로 PE 파일은 대부분 PE(50, 45)라는 값이 저장되어 있다.

     

     

    그렇기에 저 문구를 찾았다는 것은 NT header의 시작위치를 찾았다는 뜻이다.

    NT header의 시작 위치가 F0임으로 40~ EF까지는 자동적으로 DOS Stub이라는 것을 알 수가 있다.

     

    DOS Stub

     

    바로 이 부분이 DOS Stub이다.

     

    그럼 벌써 DOS Header, DOS Stub의 위치는 완벽히 구했다.

     

     

    그럼 이제 NT header를 보자.

    NT header의 구조체는 아까 본거와 같이 signature, FileHeader, OptionalHeader가 존재한다.

    MAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER32 라는 구조체를 사용하고 있다.

     

    어차피 크기를 확인하긴 할 것이지만 사실 이것도 육안으로 크기를 짐작할 수가 있다.

    section header의 시작값을 육안으로 쉽게 확인 할 수 있기 때문이다.

     

     

     

    section header 구조체를 이따가 더 자세히 알아보겠지만 첫번째 변수가 name으로 8byte char형이다.

    .text가 section header의 시작 위치로 봐도 무방하다는 뜻이다.

    그뜻은 NT header의 크기는 F0 ~ 1E7까지이고

    Section header의 시작 위치는 1E8이 된다.

     

     

    NT Header

     

    파란색 표시 부분이 NT Header이며 사실상 PE 구조를 분석할 때 가장 중요한 부분이 아닐까 싶다.

    NT header에 안에 있는 내용도 따로 분석을 할 예정인데 글이 길어질게 분명함으로 오늘은 그냥 넘어가도록 하겠다.

     

    아마 NT header에 대한 글 1개와 section에 대한 글 1개를 추가로 작성하게 될 것 같다.

     

    이 글에서는 짧게 한가지만 알아보고 갈 것이다.

    IMAGE FILE HEADER를 보면 섹션의 개수를 확인 할 수가 있다.

    섹션의 개수를 파악하면 섹션 헤더와 바디를 분석할 때 편하다.

     

    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;

     

    2번째 변수 선언을 보면 NumberOfSections라고 적혀있는 것을 확인 할 수가 있다.

    이것은 이 파일의 섹션이 몇개 인지를 알려주는 녀석이다.

    calc.exe에서 저 부분을 확인해보면 03이라고 적혀있는 것을 확인할 수가 있다.

     

    즉 3개의 섹션으로 이루어 져있다는 것을 알 수 있다.

    그럼 이제 섹션 헤더의 크기를 알면 모든게 끝난다.

     

     


    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;

     

    IMAGE_SECTION_HEADER의 자세한 구조는 다다음 글에 작성할 예정이다.

    지금은 크기만 알아보도록 하자.

    사실 헥스 값 문구로도 지레짐작이 가능하다.

     

    그래도 계산을 해보면 byte 배열 1개, word 변수 2개, dword 변수7개 (union으로 묶여 있는 것은 2개 중 1개만 사용된다는 뜻이다.)

    Name이란 배열의 사이즈를 보면 IMAGE_SIZEOF_SHORT_NAME라고 정의되어 있는데

    define을 확인해 보면 8이란 값을 확인 할 수가 있다.

     

    즉, 8 + 4 * 7 + 2 * 2 = 40 bytes

    16진수로 28의 크기이다.

    맞는지 한번 확인해보도록 하자.

     

    40 bytes 크기만큼 드래그를 해보았다.

    놀랍게도 이 다음에 그 다음 섹션의 시작을 알리는 name변수 값이 저장되어 있었다.

    Section Header

     

    이렇게 Section Header의 위치까지 다 구하였다.

    Section Header는 1E8 ~ 25F까지이다.

     

    .text section의 시작 위치를 확인함으로써 body의 시작을 알아보자.

    각 섹션의 시작위치는 각각의 섹션 헤더를 통해서 구할 수 있다.

     

    파일 상에서의 section의 시작 위치를 알려주는 PointerToRawData를 확인하면 .text섹션의 시작위치를 알 수가 있다.

    확인해 보면 00 04 00 00이라고 적혀있다.

    리틀 엔디안 방식으로 표기되어 있음으로 400에서 시작하고 있음을 알수가 있다.

    그리고 body의 끝을 알기 위해서는 .rsrc의 크기와 시작 위치를 파악하면 알 수가 있다.

     

    우선 시작 위치값이 00 36 01 00으로

    13600부터 시작이라는 것을 알 수 있다.

    section의 파일 상의 크기는 SizeOfRawData를 보면 알 수가 있다.

    00 8A라고 적혀있는 것을 확인 할 수가 있다.

    즉 8A00만큼 크기를 갖고 있다는 뜻이다.

    13600 + 8A00을 하면 body의 끝을 알 수가 있다.

    13600 + 8A00 = 1C000

    즉 Body의 offset은 400 ~ 1BFFF이다

     

     

     

    실제 hex값 offset의 마지막이 1BFFF인 것을 확인 할 수가 있다.

     

     

    요약

     

    Calc.exe(x86)

     

    Header

    DOS Header : 0~ 3F

    DOS Stub : 40~ EF

    NT Header : F0 ~ 1E7

    Section Header : 1E8 ~ 25F

     

    Body

    400 ~ 1BFFF

     

     

    정확한 section 별 body위치는 다다음 글에서 확인하도록 하겠다.

     

    이렇게 해서 계산기의 대략적인 PE 구조의 offset값 분석이 끝났다.

     

    다음에는 NT header에 대해서 집중해서 알아보도록 하자.

    댓글

Designed by Tistory.