PE 파일 포맷의 이해

리버싱 2017. 1. 9. 15:34

※ 주의사항

아래 공격 코드는 연구 목적으로 작성된 것이며, 허가 받지 않은 공간에서는 테스트를 절대 금지합니다.

악의 적인 목적으로 이용할 시 발생할 수 있는 법적 책임은 자신한테 있습니다. 이는 해당 글을 열람할 때 동의하였다는 것을 의미합니다.

해당 문서의 저작권은 해당 저자에게 모두 있습니다. 다른 용도로 사용할 시 법적 조치가 가해질 수 있습니다.

 

테스트 환경 (해당 될시에만)

  대상 실행파일 : bot.exe - 악성코드 샘플이지만 가볍고, 특이한 동작 없는 파일


 

상세 분석

PE (Portable Excutable) 포맷은 윈도우에서 사용되는 실행 가능한파일 형식을 말합니다. 하나의 실행파일을 다양한 운영체제에서 실행할 수 있다는 의미로 이식 가능한 실행파일(PE)’라는 이름이 붙었습니다. 일반적으로 잘 알려진 exe, dll, obj,sys 등의 확장자를 가진 파일들이 여기에 해당되구요. 유닉스의 실행파일 형식인 COFF(CommonObject File Format)을 기반으로 만들어졌습니다.

 

PE를 구성하는 요소들은 각각 구조체의 형태를 가지고 있습니다. 이런 요소들은 크게묶어 두 부분으로 볼 수 있습니다. 하나는 헤더, 하나는 섹션입니다.


PE 구조에서 
헤더에는 파일을 실행할 때 맨 처음 시작해야 할 코드의 시작부분에 대한 정보, 프로그램이 구동 될 수 있는 플랫폼에 대한 정보 등 파일을 실행하는데 있어서 필요한 전반적인 정보들을 담고있습니다.


PE 구조에서 섹션에는 실제 프로그램을 구성하는 어셈블리 코드, 그리고 소스코드 내에서 선언한 전역변수나 static 변수들 등을 담고 있습니다. 





PE라는 형식의 구조를 간단히 표현하면 위의 그림과 같습니다. 사실 PE 구조라고 해서 따로 뭔가가 있는 것이 아닙니다. 이전에 프로그램 실행의 이해에서 소스코드부터 여러 과정을 거쳐 실행 가능한 .exe 등의 파일이 생성되는 과정을 설명드린 적이 있습니다. 여기서 만들어진 .exe 파일도 하나의 PE 포맷의 파일이 되는 것이고, 따라서 PE의 구조를 갖고 있는 것입니다. 예를 들어, 우리가 간단히 만든 Hello World를 출력하는 프로그램도 PE 구조를 가지고 있고, 윈도우에 기본적으로 내장되어 있는 notepad.exe, calc.exe 같은 프로그램들도 PE 구조를 가지고 있습니다. 결론은 윈도우에서 실행가능한 실행파일이 가지는 구조라는 것입니다.


PE 구조는 위의 그림과 같은 순서로 구성요소들이 위치해 있습니다. 그리고 이 위치는 프로그램이 메인 메모리에 올라가도 거의 그 순서를 유지합니다. 하지만 각 섹션들은 메모리의 상태에 따라 다른 순서로 적재 되는 경우가 많습니다. 그럼 지금부터 PE 포맷을 구성하는 구성요소들에 대해서 하나하나 알아가 보도록 하겠습니다


1. IMAGE_DOS_HEADER


위에 보이는 것이 바로 IMAGE_DOS_HEADER 구조체의 원형입니다. winnt.h 라는 헤더파일로 부터 가지고 왔습니다. winnt.h라는 헤더에는 PE 구조에 있는 구조체들이 정의 되어있습니다. 위에 명시된 순서대로 값들이 저장되어 있습니다. 여기서 한가지 알아두어야 할 것은 PE 구조를 구성하는 대부분의 요소들이 구조체로 이루어져 있기 때문에 그것들의 크기 역시 일정하다는 것입니다. 이 구조체는 offset이 0h 부터 40h 까지로 총 크기가 64byte입니다. 


맞는지 의심이 되신다면 위에 나와있는 구조체 내의 필드들의 갯수과 각각의 자료형의 크기를 따져 계산해보시길 바랍니다. 


IMAGE_DOS_HEADER라는 구조체에서 우리가 눈여겨 봐야할 것은 'e_magic'이라는 필드와 'e_lfanew'라는 필드입니다. 그 이유에 대해서는 잠시후 설명 드리도록 하겠습니다. 그럼 다음 그림을 보도록 하겠습니다.



PE 구조 설명의 예시로 사용할 bot.exe 라는 실행파일을 PEView라는 프로그램을 이용하여 열어보았습니다.

bot.exe라는 실행파일이 가지고 있는 IMAGE_DOS_HEADER 구조체의 필드들이 보여지고 있습니다. 여기에는 이전 그림에서 봤던 어떤 필드명도 나타나 있지 않습니다. 하지만 우리는 구조체에 나와있던 필드들이 순선대로 저장이된다는 사실을 알고 있습니다. 따라서 위의 그림에서 보여지는 것들이 IMAGE_DOS_HEADER 구조체의 필드값들을 순서대로 보여주는 것을 알 수가 있습니다.

여기서 우리가 중요시 봐야할 e_magic 필드와 e_lfanew 필드 값을 찾아보도록 하겠습니다.


1.1. e_magic


먼저 e_magic 필드를 찾아보도록 하겠습니다. e_magic 필드는 구조체 내에서 가장 첫번째에 위치해 있었습니다. 해당 필드는 WORD형 변수로 크기가 2byte입니다. 따라서, 가장 첫번째 2byte에 해당 필드 값이 저장되어 있을 것이라는 것을 알 수가 있습니다.



PE View를 통해서 본 IMAGE_DOS_HEADER 구조체의 가장 첫번째 2byte값입니다. Description을 보면 맨 처음 보았던 구조체의 원형에서 e_magic 필드의 주석과 같은 것을 볼 수 있습니다. 그리고 해당 데이터의 offset이 0h으로 가장 처음인 것 또한 알 수 있습니다. 이제 이 값은 e_magic 필드의 값이라는 것이 확실해졌습니다.


e_magic 필드의 값은 '0x4D5A' 입니다. PEView에서는 값을 리틀인디언 방식으로 표현하기 때문에 하위 바이트부터 표시가 됩니다. 그래서 우리가 보기 편한 빅인디언 방식으로 상위 바이트부터 표현을 하면 0x4D5A가 되는것입니다. 


그럼 이번에는 bot.exe 파일을 헥사 에디터로 열어서 확인해보도록 하겠습니다.



처음 두 바이트가 4D, 5A 인 것을 확인할 수 있습니다. 해당 헥사코드들을 아스키코드로 바꾸면 'MZ' 라는 값이 나옵니다. 이 값은 IMAGE_DOS_HEADER 구조체에 항상 나오는 값으로 아주 중요한 값입니다. 이 값을 보셨다면 '아 내가 IMAGE_DOS_HEADER 구조체를 찾앚구나' 라고 생각하셔도 됩니다. 그런데 왜 하필 'MZ'라는 값이 들어있을까요? 'MZ'라는 값은 도스를 설계한 사람 중의 한명인 Mark Zbikowski라는 사람의 이니셜이라고 합니다.^^ 이것을 알고 'MZ'값을 찾으면 사람을 만난 것 같아 반갑겠죠?


1.2. e_lfanew


다음은 IMAGE_DOS_HEADER 구조체의 가장 마지막 필드에 해당하는 e_lfanew 필드를 찾아보도록 하겠습니다. e_lfanew 필드는 LONG형 변수로 4byte의 크기를 갖습니다. 



이것이 IMAGE_DOS_HEADER 의 가장 마지막 값인 e_lfanew 필드의 값입니다. 4byte 크기의 값으로 '0x000000D8' 이라는 값을 가지고 있습니다. 시작 offset이 0x3C 이므로 값은 offtset 0x3C, 0x3D, 0x3E, 0x3F에 걸쳐 있을 것으로 예상됩니다. 그럼 이번에도 헥사에디터를 통해 찾아보도록 하겠습니다.


 

예상한 offset에 리틀인디언 방식으로 0x000000D8 이라는 값이 들어가 있는 것을 확인할 수 있습니다.

리틀인디언 방식으로 보이기 때문에 헥사에디터에서는 0xD8000000 으로 보이는 것입니다.

e_lfanew 필드는 IMAGE_NT_HEADER의 시작 offset 값을 가지고 있습니다. IMAGE_NT_HEADER 구조체는 실질적으로 실행파일의 내용이 시작하는 부분입니다. 따라서 아주 중요한 값이라고 할 수 있습니다. 

IMAGE_NT_HEADER 구조체에 대해서는 뒤에서 알아보도록 하고 지금은 넘어 가겠습니다.


지금까지 살펴본 IMAGE_DOS_HEADER 구조체의 e_magic 필드와 e_lfanew 필드를 잘 활용해야 할 것입니다.


2. DOS Stub Program


자, 이제는 그 다음에 나오는 DOS Stub Program을 살펴보도록 하겠습니다.

DOS Stub Program은 구조체가 아닙니다. 이름에 나와있는 것처럼 하나의 프로그램입니다. 사실 이것은 일반적인 실행파일을 실행함에 있어서 별로 눈여겨 볼 필요가 없는 부분이지만 여기다가 악성코드의 동작 내용을 심는다는 것을 본적이 있습니다. 따라서 왜 있는 것인지 정도는 알아야 된다고 생각하기 때문에 간단하게 짚어보고 넘어가도록 하겠습니다.


다시 말하지만 이 부분은 구조체가 아닌 하나의 작은 프로그램이기 때문에 이전의 구조체처럼 크기가 정해져 있지 않습니다. 그럼 끝인지 어떻게 아느냐 ? 바로 앞에서 알아보았던 e_lfanew 필드의 값을 참조하는 것입니다. 

DOS Stub Program의 바로 다음에는 IMAGE_NT_HEADER 구조체가 있기 때문에 해당 구조체의 시작 offset 값을 가지고 있는 e_lfanew 필드를 참조한다면 DOS Stub Program의 끝을 알 수가 있습니다.



이 부분은 앞에서도 말했듯이 구조체 형태가 아니기 때문에 PEView에서도 헥사에디터처럼 보여줍니다.

이 실행파일의 DOS Stub Program은 총 152 byte의 크기를 가지고 있습니다.

그런데 아스키코드로 변환된 값에 'This program cannot be run in dos mode.'라는 값이 있는 것을 볼 수가 있습니다. 이것이 무엇일까요?


DOS Stub Program은 32bit 윈도우 플랫폼에서 실행되도록 만들어진 PE파일이 16bit DOS 환경에서 실행되려 하는 것을 방지하는 프로그램입니다. 따라서 위와같이 이 프로그램은 도스 모드에서 실행될 수 없다는 문구를 출력해주는 것입니다. 그리고 실행을 하지 못하도록 합니다. 이런 내용이 일반적인 PE 포맷을 가진 파일에 다 들어가 있습니다. 하지만 그 크기가 모두 같지는 않습니다.


DOS Stub Program은 링커에 의해 삽입이 됩니다. 링커의 옵션으로 /STUB:filename 이라는 것을 주면 filename에 해당하는 Stub Program을 PE 포맷의 파일에 집어 넣는 것입니다. 이 Stub Program은 개발환경 등에 따라 다르기 때문에 크기가 가변적입니다. 


그럼 이제 왜 PE 구조내에 DOS Stub Program이라는 것이 들어가있고, 그것을 구분해내는 방법을 알게 되었을 것입니다. 


3. IMAGE_NT_HEADER


다음은 IMAGE_NT_HEADER라는 구조체에 대하여 알아보도록 하겠습니다.


위의 그림은 IMAGE_NT_HEADER 구조체의 원형입니다.해당 구조체는 4byte 크기의 Signature 그리고 IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER32 로 구성되어 있습니다.


3.1. Signature


PEView에서 각각에 대하여 깔끔하게 정리를 해주었습니다. 가장 먼저 Signature입니다. 



IMAGE_NT_HEADER 구조체의 시작부분인 Signature는 실질적으로 실행파일이 시작하는 부분입니다. 이전의 IMAGE_DOS_HEADER 구조체에서 e_lfanew 필드가 이 Signature라는 필드의 offset값을 저장하고 있었습니다. 그리고 지금 우리가 보고 있는 Signature의 offset값과 비교해보면 리틀인디언 방식으로 000000D8h로 일치한다는 것을 알 수 있습니다.


이 IMAGE_NT_HEADER의 Signature는 앞서 말했듯이 실질적으로 실행파일이 시작하는 부분입니다. 여기에는 항상 'PE\0\0'라는 데이터가 고정으로 들어있습니다. 이 부분을 찾으셨다면, '아.. 이제부터 시작이구나'하고 생각하시면 될 것 같습니다. 헥사에디터로 들여다 보도록 하겠습니다.



헥사코드로는 '0x50450000'라는 값이 들어있습니다. 'PE\0\0'입니다. 기억해두시길 바랍니다.

그럼 다음으로 넘어가보도록 하겠습니다.


3.2. IMAGE_FILE_HEADER


다음은 IMAGE_FILE_HEADER입니다.

IMAGE_FILE_HEADER 역시 구조체의 형태를 가지고 있으므로 구조체의 원형을 보도록하겠습니다.


구조체의 원형입니다. 많은 필드들이 자리잡고 있는 것을 보실 수 있습니다. 해당 필드들 중에서 우리가 필요한 필들에 대해서만 알아보도록 하겠습니다. 


3.2.1. Machine


먼저 가장 처음에 자리잡은 Machine 필드입니다. 이 필드는 해당 파일이 어떤 아키텍쳐에서 동작하는 지에 대한 정보를 가지고 있습니다. 다음은 MSDN에서 발췌해온 자료입니다.



출처 : MSDN - http://msdn.microsoft.com/en-us/library/ms680313%28v=VS.85%29.aspx


위의 내용은 Machine 필드에 대한 설명입니다. Machine 필드의 값에 따른 의미를 설명하고 있습니다.

각 값에 대한 설명은 다음과 같습니다.

0x014c

- x86 계열 즉, 인텔 아키텍쳐 32비트 환경에서 동작한다는 의미입니다. 

0x0200

- x86-64 계열 즉, 인텔 아키텍쳐 64비트 환경에서 동작하다는 의미입니다.

0x8664

- 이 값은 다른데서는 잘 언급이 되어있지 않습니다만, AMD 계열 64비트를 의미하는 것 같습니다.


그럼 우리가 분석 중인 파일은 어떤 값을 가지고 있는지 알아보도록 하겠습니다.


해당 파일에서는 Machine 필드의 값이 '0x14C'인 것을 확인하였습니다. 그렇다면 위의 설명에 따라 인텔 아키텍쳐 32비트 환경에서 동작하는 실행파일이라는 것을 확인할 수 있습니다.

헥사 에디터에서도 역시 같은 값을 확인할 수 가 있습니다. 그런데 주의해야 할 것은 해당 필드는 리틀인디언 방식으로 데이터가 쓰인다는 것입니다. 그럼 다음으로 우리가 정보를 얻을 필드에 대해서 알아보도록 하겠습니다. 


3.2.2. NumberOfSections


다음은 NumberOfSections 필드입니다. 해당 필드는 해당 파일이 가지고 있는 섹션의 수를 나타냅니다.



실제로 섹션의 갯수가 SECTION .rdata, SECTION .data, SECTION .rsrc로 3개 인것을 확인할 수 있습니다. 



해당 필드 역시 리틀 인디언 방식으로 저장되는 값으로 헥사 에디터에서 값이 0x0300 으로 기록된 것을 확인할 수가 있습니다. 


3.2.3. SizeOptionalHeader





NumberOfSections 필드의 다음에 나오는 필드들인 TimeDateStamp, PointerToSymbolTable, NumberOfSymbols 필드들은 건너 뛰고 SizeofOptionalHeader 필드를 살펴보도록 하겠습니다.

해당 필드는 IMAGE_NT_HEADER 구조체 내에서 IMAGE_FILE_HEADER 구조체 다음에 나오는 필드로, 역시 구조체의 형태를 가지고 있는 IMAGE_OPTIONAL_HEADER 구조체의 크기에 대한 정보를 가지고 있습니다.



우리가 분석중인 파일의 IMAGE_OPTIONAL_HEADER 구조체의 크기는 '0x00E0' = 224byte의 크기를 갖고 있다는 것을 알 수 있습니다. 해당 구조체의 크기에 대한 정보를 기록하는 필드가 있다는 것은 IMAGE_OPTIONAL_HEADER 구조체는 크기가 가변적이라는 것을 알 수가 있습니다.



해당 필드 역시 리틀인디언 방식으로 기록이 되어있는 것을 볼 수 있습니다.


3.2.4. Characteristics


그 다음으로 설명할 Characteristics 필드는 해당 실행파일의 특성에 대한 정보를 담고 있습니다.

각 특성별로 부여된 헥사코드가 있습니다. 실행파일에 해당하는 헥사코드 값들을 모두 더해 표시합니다.

특성별 헥사코드 값들의 의미는 다음과 같습니다.

0x0001

- 파일에 대한 재배치 정보가 없으므로, 기본 주소에 로드 되어야한다는 의미입니다.

0x0002
- 확인되지 않은 외부참조가 없고, 파일이 실행가능하다는 의미입니다.
0x0004
- COFF 라인 번호가 파일에서 삭제되었다는 의미입니다.
0x0008
- COFF 심볼 테이블 항목이 파일에서 삭제되었다는 의미입니다.
0x0010
- 적극적으로 워킹 셋을 정리해야한다는 의미입니다.
0x0020
- 응용 프로그램이 2GB보다 큰 주소를 처리할 수 있다는 의미입니다.

0x0080

- 단어의 바이트가 반대로 되어있다는 의미입니다.

0x0100
- 32비트 단어를 지원한다는 의미입니다.
0x0200
- 디버깅 정보가 해당 파일에 없고 다른 파일에 저장되어있다는 의미입니다.
0x0400
- 파일이 이동식 미디어에 있는경우, 그것을 복사하여 스왑파일에서 실행한다는 의미입니다.
0x0800
- 파일이 네트워크상에 있는 경우, 그것을 복사하여 스왑파일에서 실행한다는 의미입니다.
0x1000
- 파일이 시스템 파일임을 의미합니다.
0x2000
- 파일이 DLL 파일이며, 이것이 실행파일이지만 직접 실행할 수는 없다는 의미입니다.
0x4000
- 파일이 단일 프로세서 컴퓨터에서만 실행되어야 한다는 의미입니다.
0x8000
- 0x0080과 같은 의미 입니다.

파일의 특성에 따라 해당하는 헥사코드들을 더한 값을 Characteristics 필드에 저장합니다.

그럼 이제 우리가 분석중인 bot.exe 파일은 어떤 특성값을 가지고 있는지 확인해보도록 하겠습니다.



이 파일은 0x0001, 0x0002, 0x0004, 0x0008, 0x0100 네 가지 값을 더해 합이 '0x010F'가 되었습니다. 
그럼 위에서 설명한 각 헥사코드별 의미를 더해 해석해보면 다음과 같은 정보를 얻을 수 있습니다.

파일에 대한 재배치 정보가 없어서 기본 주소에 로드가 될 것이고, 확인되지 않은 외부참조가 없어서 실행이 가능합니다. 또 COFF 라인 번호와 COFF 심볼 테이블 항목이 파일에서 삭제 되어있고, 32비트 단어를 지원하는 파일입니다.

이렇게 Characteristics 필드를 통해 위와 같은 정보를 얻어낼 수가 있습니다.



이 값 역시 리틀인디언 방식으로 저장이 되어있는 것을 확인할 수 있습니다.

추가적인 정보를 알려드리자면, 일반적인 응용프로그램은 대체로 Characteristics 필드의 값으로 '0x010F' 값을 가진다고 합니다.


3.3. IMAGE_OPTIONAL_HEADER


다음은 PE 헤더부분에서 가장 중요한 요소인 IMAGE_OPTIONAL_HEADER 에 대하여 알아보도록 하겠습니다.

계속 그래왔듯이 이번에도 해당 구조체의 원형부터 보도록 하겠습니다.



수많은 필드들이 보이십니까? 하지만 다행이도 저 많은 필드들을 모두 살펴볼 필요는 없습니다. 그럼 바로 이어서 중요한 필드들에 대해서 하나하나 알아보도록 하겠습니다.


3.3.1. Magic


Magic이라는 단어가 익숙하지 않으십니까? 이전의 IMAGE_DOS_HEADER 구조체에서 e_magic이라는 필드에 대해서 알아본적이 있습니다. e_magic이라는 필드는 하나의 Signature로 해당 구조체의 시작부분임을 알리는 역할을 하여 'MZ'라는 특정 단어가 들어가 있었음을 확인했었습니다. 제가 왜 이것을 다시 설명했을까요?


눈치채신 분들도 있을 것입니다. IMAGE_OPTIONAL_HEADER의 Magic 필드 역시 해당 구조체의 시작임을 나타내는 Signature로 사용되는 것입니다. 그렇다면 어떤 특정 값으로 고정이 되어있을 것임을 알 수가 있습니다.

어떠한 값이 들어가 있는지 찾아보도록 하겠습니다.



먼저 PEView를 통해 값을 확인해보았습니다. '0x10B'라는 값이 들어있음을 볼 수가 있습니다. 지금은 우리가 32비트에서 동작하는 PE를 가지고 확인을 해보고 있지만 64비트환경에서 동작하는 PE에서는 이 값이 '0x20B'로 고정되어있습니다.



헥사에디터에서는 리틀인디언 방식으로 0x0B01 이라고 값이 들어가 있음을 확인할 수 있습니다.


3.3.2. AddressOfEntrypoint


다음은 AddressOfEntrypoint라는 필드입니다. 해당 필드에 대해서 알아보기 전에 알아두어야 할 개념을 먼저 배우고 해당 필드에 대해서 알아보도록 하겠습니다.


제가 이전에 다루었던 가상 메모리의 이해라는 페이지에서 32비트환경에서는 프로그램당 4GB의 메모리 공간을 할당한다고 하였습니다. 여기서 할당되는 메모리 공간은 가상 메모리로 0x00000000 부터 0xFFFFFFFF 까지의주소를 가집니다. 이 4GB 크기의 가상 메모리 주소를 VA(Virtual Address) 즉, 가상 주소라고 합니다.


가상 주소와 더불어 앞으로 나올 개념인 RVA에 대하여 꼭 알고 다음으로 넘어가셔야 합니다.

RVA라는 것은 Relative Virtual Address의 약자로 상대적인 가상 주소라는 개념입니다. 이해가 안되시죠?

저는 그림을 그려 설명하는 것을 좋아하기 때문에 그림을 보도록 하겠습니다.



위의 그림은 왼쪽부터 오른쪽으로 보시면 되겠습니다. 먼저 메인 메모리와 하드 디스크로부터 띄엄띄엄 떨어져있는 사용가능한 공간들을 연속된 주소를 가진 공간으로 사용하기 위해 4GB 크기의 가상 메모리를 구성합니다. 여기서 사용되는 연속된 주소를 가상주소 VA(Virtual Address)라고 합니다. 실제로는 불연속적인 메모리 공간들로 구성되어 있지만 프로그램이 사용하기 편하도록 가상의 연속된 주소로 볼 수 있게 재구성 한 가상 메모리 입니다. 이부분에 대해서는 설명해둔 것이 있기 때문에 더이상 설명하지 않겠습니다.


이렇게 구성된 가상메모리에 PE파일이 로드되어 동작을 합니다. 가상 메모리의 일부분에 로드된 PE파일은 그 안에서 또 상대적인 가상 주소를 사용하여 0x00000000 부터의 주소를 사용합니다. 이것이 바로 상대적인 가상 주소 RVA(Relative Virtual Address)입니다.


즉, RVA(Relative Virtual Address)는 가상 주소 공간(가상 메모리)내에 로드된 PE파일의 시작주소로부터 상대적인 번지의 위치를 나타내는 Offset인 것입니다. 예를 들어, PE파일을 분석하다가 'IMAGE_NT_HEADER의 시작 offset 값이 0x000000D8이다.'라고 한다면 0x000000D8 이라는 것은 가상 메모리의 주소가 아니라 'IMAGE_NT_HEADER의 시작 위치는 해당 PE파일이 로드된 시작주소로부터 0x000000D8 만큼 떨어진 위치에 있다.'라고 하는 것이 맞다는 말입니다.


따라서, RVA를 가지고 가상 메모리상에서의 실제 위치를 구하려고 한다면 다음과 같이 계산합니다.


실제 위치(가상 메모리의 주소, VA) = PE파일이 로드된 시작번지 + RVA


이제 VA와 RVA에 대한 개념이 잡히셨을 거라고 생각을 하고, AddressOfEntrypoint 필드에 대하여 알아보도록 하겠습니다.


AddressOfEntrypoint 라는 이름에서 해당 필드가 어떤 값을 저장하는지 감이 오시는 분들도 있을 것입니다.

해당 필드는 EP 즉, EntryPoint라고 하는 주소값을 가지고 있습니다. 그런데 그냥 주소 값이면 위에서 설명한 것들이 의미가 없어지겠죠? 


해당 필드에는 EntryPoint의 RVA 값이 저장되어 있습니다. 이것이 의미하는 것은 EntryPoint라는 지점의 위치를 PE파일이 시작하는 위치로부터 얼마나 떨어져있는지를 나타내는 offset 값이 저장되어 있다는 것입니다. 

그런데 EntryPoint라는 것이 무엇일까요?


여기서 말하는 EntryPoint라는 것은 로더에 의해 PE파일이 로딩되고나서 가장 먼저 시작되는 소스코드의 시작점이라고 할 수 있습니다. 이 EntryPoint라는 것이 중요한 의미를 가지고 있다는 것을 아셔야합니다.

왜냐하면 두말 할것도 없이 제일 먼저 시작되는 소스코드의 시작점이니까요.



PEView를 이용해서 AddressOfEntrypoint의 값을 확인해 보았습니다. '0x0002CB61' 이라는 값이 있음을 확인할 수가 있습니다. 


다음은 헥사에디터에서 찾아보도록 하겠습니다.



offset 100h에 리틀인디언 방식으로 0x0002CB61 값이 들어가있음을 확인할 수가 있습니다.


그런데 이 EntryPoint라는 값은 앞에서도 말했듯이 중요한 의미를 가지고 있습니다.

그래서 PE파일을 분석해주는 다른 툴들에서는 EntryPoint를 따로 찾아내어 표시해주는 것이 일반적입니다.

PEiD와 Stud_PE라는 툴을 통해 그 예를 보도록 하겠습니다. 먼저 PEiD입니다.



위의 그림은 PEiD를 이용하여 bot.exe를 열어본 결과입니다. 첫번째 항목으로 Entrypoint가 있으며, 우리가 PEView를 통해 알아보았던 AddressOfEntrypoint 필드에 저장되어 있던 값과 동일한 값이 표시되는 것을 확인할 수 있습니다.


다음은 Stud_PE를 이용해 bot.exe를 열어본 결과입니다.



역시 첫번째 항목으로 EntryPoint가 있으며 심지어는 빨간색으로 강조되어 있습니다. 그리고 RVA 값임을 알 수 있도록 표시를 해둔 것이 참 친절하다는 생각이듭니다. 


이처럼 EntryPoint는 PE파일이 가지고 있는 소스코드의 실질적인 시작점으로 중요한 의미를 갖는다는 것을 알아두셨으면 좋겠습니다. 또한 그 값을 가지고 있는 필드가 IMAGE_NT_HEADER 구조체 내의 IMAGE_OPTIONAL_HEADER가 가진 필드인 AddressOfEntrypoint에 저장이 되어있다는 것도 알아두시면 좋겠습니다.


3.3.3. ImageBase


다음으로 살펴볼 필드는 ImageBase라는 필드입니다.


PEView를 통해 ImageBase 필드의 값을 확인해보았습니다. offset 10Ch에 '0x400000'이라는 값이 있습니다.
헥사에디터에서 offset 10Ch에 해당하는 위치를 찾아가 보았습니다.

우리가 찾았던 0x00400000 값이 리틀인디언 방식으로 자리잡고 있는 것을 확인할 수 있습니다. 

이 ImageBase 필드에 저장되어 있는 값은 PE파일이 로드되는 위치를 나타냅니다. 즉 VA(Virtual Address), 가상주소 값을 가지고 있습니다. RVA값이 아닌, RVA의 기준이되는 위치 값을 가지고 있는 것입니다.


이 값은 링커에 의해서 설정이 되며, 일반적으로 우리가 분석하고 있는 bot.exe와 같은 EXE파일의 경우에는 해당 필드 값이 0x00400000으로 설정되어 있습니다. 그러면 PE파일의 시작지점은 가상 메모리의 주소범위인 0x00000000 ~ 0xFFFFFFFF 중에서 0x00400000라는 주소를 가지는 번지에 위치하게 되는 것입니다.

이 위치는 곧 PE 프로그램 내에서 사용하는 RVA값의 기준 즉, RVA값으로 0h의 위치를 갖게되는 것입니다.

지만, DLL 파일의 경우에는 일반적으로 해당 값이 0x10000000으로 주어집니다. 왜냐하면 DLL 파일은 보통 다른 모듈에 의해서 불려오는 경우가 많기때문에 일반적인 EXE파일이 로드되는 가상주소 0x00400000 번지에 로드를 했다가는 충돌이 일어나는 상황이 발생할 수 있기 때문입니다. 따라서 DLL파일은 가상주소 0x10000000 번지에 로드가 되어 다른 곳에 재배치 되는 과정을 거치게 됩니다.


그래서 ImageBase 필드 값을 통해서 우리는 PE파일이 가상 메모리 상의 어느위치에 로드가 되었는지를 알 수 있게 되는 것입니다. 즉, RVA의 기준이 되는 값을 알아냄으로써 PE를 구성하는 데이터들이 가상메모리 상의 어느 번지에 저장이 되어있는지 알 수가 있는 것입니다.


추가적으로, 앞에서 말했듯이 이 값들은 일반적인 값이지 절대적인 값들이 아닙니다. 이 값들은 링커의 옵션을 통해 다른 값으로 바꿀 수도 있습니다.


3.3.4. SectionAlignment


다음으로 살펴볼 필드는 SectionAlignment 입니다. 


섹션이라는 것은 실제 프로그램을 구성하는 어셈블리 코드, 그리고 소스코드 내에서 선언한 전역변수나 static 변수들 등을 담고 있다고 처음에 말씀드린 적이 있습니다.


그렇다면 Alignment는 무엇일까요? 자동차에 관심이 많은 분들이라면 휠 얼라이먼트라는 것을 들어보신 적이 있을 것입니다. 대개 정비소에 커다란 현수막으로 붙어져있죠. '휠 얼라이먼트 합니다'라구요.


휠 얼라이먼트라는 정비작업은 쉽게 말하면 자동차의 핵심인 바퀴들을 정렬하는 작업입니다. 자동차를 타고 다니다보면 여러가지 충격들에 의해 출고 당시 일정한 각도, 위치로 정렬되어 있던 바퀴들이 조금씩 틀어지게 됩니다. 그런 충격들이 많이 쌓이다보면 차 바퀴의 각도가 운전 중에 느껴질 정도로 틀어지게 됩니다. 그럼 아주 위험하겠죠. 왜 갑자기 자동차 정비에 대한 이야기가 나왔을까요? 사실 Alignment라는 단어가 생소한 분들을 위해 뜻을 설명해 드리고 싶은데, 사전적 의미만 말하면 너무 딱딱하고 재미없지 않습니까 :-)


잠시 다른 이야기로 샜었는데, 여기서 제가 초점을 두고 싶은 것은 각도가 틀어진다는 것이 아니라 얼라이먼트라는 작업이 '일정하게 정렬한다'는 것을 의미한다는 것입니다. 

그럼 본 내용으로 돌아와서 SectionAlignment는 무엇을 의미할까요? 

결론부터 말하면, SectionAlignment는 메모리 상에서 섹션을 정렬하는 단위입니다.


먼저 PEView를 통해서 SectionAlignment 필드의 값을 확인해보도록 하겠습니다. 



위의 그림에서 해당 필드에 저장된 값은 '0x00001000' 입니다. 이 값을 기준으로 섹션들이 정렬될 것입니다. 

사실 이렇게 말하면 이해가 잘 되지 않을 것입니다. 따라서 그림을 그려 설명하도록 하겠습니다.



위의 그림처럼 SectionAlignment 필드의 값이 0x00001000 이면 섹션의 절대 단위는 4096 바이트가 됩니다.

그럼 이제 예를 들어보도록 하겠습니다. .text 섹션과 .data 섹션이 연속되어 나열된 섹션이라고 하겠습니다.

그리고 .text 섹션의 데이터는 총 512 바이트이고 .data 섹션의 데이터는 총 5632 바이트라고 하겠습니다.

그럼 각 섹션의 데이터는 어떤 형태로 배치가 될까요?



위의 그림을 보십시오. .text 섹션이 가지는 데이터의 크기는 섹션의 단위에 훨씬 못미치는 512 바이트였습니다. 하지만 .text 섹션은 4096 바이트의 공간을 할당 받았습니다. 섹션의 단위가 4096 바이트이기 때문에 .text 섹션의 데이터의 크기가 매우 작아 빈공간이 생기더라도 섹션의 절대 단위인 4096 바이트의 공간을 할당할 수 밖에 없습니다. .text 섹션의 데이터가 끝나고 3584 바이트의 빈공간이 지나고 나서야 다음 섹션인 .data 섹션이 시작됩니다.


그럼 .data 섹션도 살펴보도록 하겠습니다. 해당 섹션이 가지는 데이터의 크기는 섹션의 절대 단위인 4096 바이트를 훨씬 웃도는 5632 바이트입니다. 그럼 해당 섹션이 할당 받는 공간은 첫 4096 바이트에 대한 공간으로 절대단위인 4096 바이트의 공간과 나머지 1536 바이트에 대한 공간으로 절대 단위인 4096 바이트의 공간을 할당 받아 총 8192 바이트의 공간을 할당 받게 되는 것입니다. 


섹션의 최소단위라는 개념을 이제 이해하셨으리라 생각됩니다. 위에서 열심히 설명한 섹션의 단위를 설정하는 필드가 SectionAlignment 필드입니다.


이쯤에서 안보고 넘어가면 섭섭할 헥사에디터에서 해당 필드를 찾은 값을 확인해보도록 하겠습니다.



예상대로 0x001000 이라는 값이 리틀인디언 방식으로 저장되어 있는 것을 확인할 수 있습니다.


3.3.5. FileAlignment


다음은 FileAlignment 필드입니다. 


FileAlignment 필드도 SectionAlignment 필드와 같이 섹션의 절대 단위를 설정하는 값을 저장하는 필드입니다. 다만, SectionAlignment 필드가 메모리상에서 섹션의 단위를 설정하였다면 FileAlignment 필드는 디스크 상에서 섹션의 절대 단위를 설정합니다.


예를 들어, SectionAlignment 필드와 FileAlignment 필드에서 설정하는 섹션의 단위가 같다면 디스크 상에서 파일 형태로 있을 때의 PE 파일의 모습이나 메모리 상에서의 PE 파일의 모습은 같은 모습을 하고 있을 것입니다.(물론 예외사항이 없다고 가정했을 때입니다.)


그럼 우리가 분석중인 PE 파일의 FileAlignment 필드 값은 어떨까요? PEView를 통해 확인해보도록 하겠습니다.



해당 필드의 값이 '0x00001000' 으로 SectionAlignment 필드의 값과 일치합니다. 그러므로 우리가 분석중인 bot.exe 파일은 별다른 예외사항이 없다면 디스크 상에서와 메모리 상에서의 PE 파일의 모습이 같다고 볼 수 있습니다.


다음은 헥사에디터에서 offset 114h를 찾아가 값을 확인 해보도록 하겠습니다.



예상한대로 0x00001000 값이 리틀인디언 방식으로 저장되어 있는 것을 확인할 수 있습니다.


3.3.6. SizeOfImage


다음으로 알아볼 필드는 SizeOfImage 필드입니다. 


해당 필드는 PE 파일이 메모리 상에 로드되기 위해 충분히 확보해야 할 크기를 설정합니다. 즉, 메모리 상에 로드된 PE 파일의 전체 크기라고도 할 수 있습니다. 해당 필드에 설정되는 값은 SectionAlignment 에 설정된 섹션의 절대 단위의 배수가 되야합니다.


실제로 섹션 절대 단위의 배수가 설정되어 있는지 확인해보도록 하겠습니다.



PEView에 나타난 해당 필드의 값을 확인한 결과 저장된 값은 '0x00034000' 으로 SectionAlignment 필드에 설정된 값인 0x00001000의 배수임을 확인할 수 있습니다.


표시된 offset 값인 128h 위치를 헥사에디터로 찾아보도록 하겠습니다.



이것 역시 예상한대로 0x00034000 값이 리틀인디언 방식으로 저장되어 있는 것을 확인할 수 있습니다.


3.3.7. SizeOfHeader


다음으로 알아볼 필드는 SizeOfHeader 입니다.


해당 필드는 이름에서 부터 어떤 값을 저장하고 있는지 냄새를 풀풀 풍기고 있습니다.

SizeOfHeader 필드는 바로 PE 헤더의 크기값을 저장하고 있습니다. PE 헤더는 PE 파일의 시작지점부터 첫번째 섹션의 시작지점까지를 말합니다. 해당 글의 맨 처음 그림을 보면 바로 이해하실 수가 있습니다.


즉, 다르게 말하면 PE 파일의 시작지점부터 SizeOfHeader 필드에 저장된 값만큼 떨어진 위치를 찾으면, 첫번째 섹션을 찾을 수 있다는 말입니다. 하나 더 알아두어야 할 것은 해당 필드의 값은 FileAlignment에 저장된 값의 배수가 되어야 한다는 것입니다. 


바로 PEView를 이용해서 값을 확인해 보도록 하겠습니다.



해당 필드에 0x00001000 값이 들어있는 것을 확인할 수 있습니다. 이것이 의미하는 것은 offset 값 1000h 부터 첫번째 섹션이 시작한다는 것입니다. 또한 FileAlignment의 값 0x00001000의 배수인 것도 확인할 수 있습니다.


그럼 이번에는 헥사에디터를 이용해서 SizeOfHeader값이 아닌, SizeOfHeader에 저장된 값인 offset 1000h

위치를 찾아가 보도록 하겠습니다.



위의 그림을 보면 offset 1000h 의 바로 앞까지는 빈공간이고, 해당 offset부터 데이터들이 저장되어있는 것을 확인할 수 있습니다. 이것은 이전에 설명한 Alignment에서의 절대 단위와 같은 개념으로 보면 됩니다.


SizeOfHeader의 값은 PE 헤더와 섹션의 경계를 나타내는 값입니다. 또한 FileAlignment 값의 배수가 되어야 된다고 했습니다. 그러므로 PE 헤더의 크기가 0x00001000 단위로 끝나지 않는다면 FileAlignment 값의 배수가 되는 공간을 맞추기 위해 남은 공간을 빈값으로 채워야 한다는 것입니다.


따라서 위와 같이 실제 PE 헤더에 해당하는 데이터들을 먼저 저장하고, PE 헤더에 할당된 공간을 FileAlignment 필드의 값인 0x00001000의 배수로 맞추기 위해 빈값들을 채워 넣은 것입니다. 그렇게 PE 헤더의 전체 크기를 0x00001000의 배수로 맞추고 나서야 섹션의 데이터가 저장되는 것입니다.


3.3.8. MajorSubsystemVersion/MinorSubsystemVersion


다음으로 알아볼 필드는 MajorSubsystemVersion과 MinorSubsystemVersion 입니다.  


해당 필드들은 PE파일의 실행방법을 설정합니다. 링커의 /SUBSYSTEM 옵션으로 값을 설정해줄 수 있는데요.

먼저 링커의 /SUBSYSTEM 옵션으로 어떤 값들을 줄 수 있는지 살펴보겠습니다.



출처 : http://msdn.microsoft.com/ko-kr/library/fcc1zstk(v=vs.80).aspx


위와 같은 옵션을 줄 수가 있습니다. 이 옵션으로 어떤 값이 주어지냐에 따라서 PE파일을 실행한 결과 다릅니다.


예를 들면 CONSOLE 이라는 값을 주었을 경우에는 PE파일이 윈도우 콘솔 창에서 실행이 됩니다. 흔히 말하는 CMD 창이죠. 그리고 WINDOWS 라는 값을 주었을 경우에는 PE파일이 윈도우 자체 창을 통해서 실행됩니다.

MajorSubsystemVersion과 MinorSubsystemVersion 필드의 값은 위의 옵션에 따라 달라집니다.



위와 같이 옵션으로 지정되는 Sub System의 종류에 따라 버전 값이 정해져 있습니다. 우리가 일반적으로 사용하는 Win32 응용 프로그램의 경우에는 MajorSubsystem 값이 4로 주어지고, ( WINDOWS 옵션을 사용하고 x86환경이기 때문에 4.00 이겠죠? 여기서 소수점 앞부분인 4 입니다.) MinorSubsystem 값은 0으로 주어집니다.

( 마찬가지로 4.00에서 소수점 아랫부분인 00 입니다. )


( 소수점 앞부분 뒷부분으로 나뉘어 지는 것이 맞는지 확신을 못하겠습니다. 아시는 분은 덧글 남겨주시면 감사하겠습니다 ^^ )


그럼 이제 우리가 분석 중인 bot.exe 파일을 살펴보도록 하겠습니다. 먼저 PEView를 보겠습니다.



MajorSubsystemVersion 값이 '0x0004', MinorSunsystemVersion값이 '0x0000'으로 각각 4 와 0 인것을 확인 할 수 있습니다. 다음으로 헥사 에디터에서 해당 offset을 찾아 값을 살펴 보도록 하겠습니다.



앞에서 부터 offset 120h ,121h, 122h,123h 입니다. 120h~121h 에는 MajorSubsystemVersion 값인 0x0004 가 저장되어 있고, 122h~123h 에는 MinorSubsystemVersion 값인 0x0000 이 저장되어 있는 것을 확인할 수 있습니다. 각각의 값들이 리틀인디언으로 저장되어 있습니다.

따라서 해당 PE파일은 CONSOLE이 되었든 WINDOWS가 되었든 Win32 응용 프로그램인 것을 알 수 있습니다.


3.3.9. Subsystem


다음으로 알아볼 필드는 Subsystem 입니다.


해당 필드는 PE파일이 드라이버 파일인지, GUI 기반의 파일인지, CUI 기반의 파일인지를 알아볼 수 있는 설정값을 가지고 있습니다. 물론 다른 기반의 파일일 수도 있으므로 그에 해당하는 설정 값도 있지만, 일반적으로 우리가 볼 수 있는 값은 0x1, 0x2, 0x3입니다. 0x01은 해당 파일이 .sys 확장자를 갖는 드라이버 파일임을 나타냅니다. 0x02는 메모장 같은 GUI 기반 응용 프로그램임을 나타내고, 0x03은 CMD와 같은 CUI 기반 응용 프로그램임을 나타냅니다.


먼저 PEView를 통해 살펴보겠습니다.



해당 파일에는 '0x0002'라는 값이 들어있습니다. 옆의 설명에는 WIDOWS_GUI라고 나와있습니다. 사실 해당 파일은 실행하면 아무것도 뜨지 않습니다. 해당 파일 제작자는 그냥 WINDOWS CUI와 GUI 중 마음에 드는 것을 고른 것 같습니다. ( 이부분에 대한 의견이 있으신 분 덧글 부탁드립니다.^^ )

다음은 헥사에디터에서 해당 offset을 찾아가 값을 찾아보도록 하겠습니다.

역시나 0x0002 값이 리틀인디언 방식으로 저장되어 있는 것을 확인할 수 있습니다.


3.3.10. SizeOfStackReserve/SizeOfStackCommit


다음으로 알아볼 필드는 스택에 관련된 값입니다.


스택이라는 것은 프로그램에서 사용하는 일종의 메모리 공간입니다. 스택에 대한 자세한 개념은 따로 정리를 하도록 하겠습니다. 해당 필드들은 이 스택이라는 공간에 대한 값을 설정합니다. 


SizeOfStackReserve 필드는 스택이라는 공간을 위해 예약된 메모리의 크기 값을 가집니다. 그리고 SizeOfStackCommit 필드는 현재 스택이라는 공간을 위해 할당된 메모리의 크기 값을 가집니다. 그런데 일반적으로 SizeOfStackReserve 필드의 값은 0x100000, SizeOfStackCommit 필드의 값은 0x1000으로 링커에 의해 기본 값이 설정됩니다.


먼저 해당 값들을 PEView를 통해 보도록 하겠습니다.



역시 예상한대로 각각 순서대로 0x100000, 0x1000 이라는 값이 들어있는 것을 확인할 수 있습니다.

어라, 그런데 밑에 SizeOfHeapReserve라는 필드와 SizeOfHeapCommit라는 필드도 있습니다. 심지어 가지고 있는 값들도 똑같습니다. 예, 그렇습니다. Heap이라는 메모리 공간에 대한 크기 값을 나타내는 것입니다.

사실 스택, 힙 이름만 다르지 각 필드에 대한 설명이 같아서 은근슬쩍 끼워 넣었습니다.


그럼 쭉 연달아 있는 4개의 필드들을 헥사에디터로 한번에 찾아보도록 하겠습니다.



offset 138h 부터 147h 까지 각 4바이트씩 0x00100000(스택 예약), 0x00001000(스택 현재), 0x00100000(힙 예약), 0x00001000(힙 예약) 이라는 값들이 들어가 있는 것을 확인할 수 있습니다. 


4. IMAGE_DATA_DIRECTORY


다음은 IMAGE_DATA_DIRECTORY에 대하여 알아보도록 하겠습니다.


IMAGE_DATA_DIRECTORY라는 구조체는 사실 IMAGE_OPTIONAL_HEADER 구조체의 구성요소입니다.



위의 그림은 IMAGE_OPTIONAL_HEADER 구조체 원형의 일부분 입니다. IMAGE_OPTIONAL_HEADER 구조체의 구성요소로써 IMAGE_DATA_DIRECTORY 구조체가 배열의 형태로 있는 것을 확인할 수 있습니다.


배열의 길이를 나타내는 IMAGE_NUMBEROF_DIRECTORY_ENTRIES 값을 찾아보도록 하겠습니다.





아예 16으로 정의가 되어 고정값으로 쓰이고 있는 것을 확인할 수 있었습니다. 이를 통해서 IMAGE_DATA_DIRECTORY 구조체는 16개의 엔트리를 가지는 것을 알 수가 있습니다.


다음은 IMAGE_DATA_DIRECTORY 구조체의 원형을 보도록 하겠습니다.





Virtual Address와 Size라는 두 개의 필드를 가지고 있습니다. 이로써 IMAGE_DATA_DIRECTORY 배열의 각 요소들은 어떤 것에 대한 주소값과 크기에 대한 정보를 가지고 있다는 것을 알 수가 있습니다. 그런데, 여기서 VirtualAddress라는 필드의 이름만 보고 가상주소라고 생각하면 안됩니다. MSDN을 참고해보면 해당 필드의 값은 RVA 값으로 저장이된다고 명시되어 있습니다.


그렇다면 어디에 대한 RVA 주소값을 가지고 있을까요? PEView를 통해 IMAGE_DATA_DIRECTORY 구조체 배열을 구성하는 요소들을 살펴보고 설명을 이어가도록 하겠습니다.



위에 보이는 그림이 PEView를 통해 IMAGE_DATA_DIRECTORY 구조체 배열을 확인한 것입니다.

총 16개의 요소로 이루어져 있습니다. 위에서부터 IMAGE_DATA_DIRECTORY[0], IMAGE_DATA_DIRECTORY[1], ... , IMAGE_DATA_DIRECTORY[15] 가 되는 것입니다.

가장 마지막에 해당하는 요소에는 모두 0x0 값으로 채워져있어 사실상 아무런 의미가 없는 요소입니다. 


그럼 값이 들어가 있는 나머지 요소들은 도대체 어떤 의미를 담고 있을까요? 사실 위의 그림에서 오른쪽에 나와있는 각 요소에 대한 설명을 살펴보면 대략적으로 알 수 있습니다. 여러 테이블들이 눈에 띄네요.

이것들을 하나하나 설명을 하고는 싶지만, 사실 많기도 하지만 지금 수준에서는 어려운 개념들이 많아 이해하기가 어렵습니다. 그래서 꼭 필요한 몇개의 요소에 대해서 먼저 간단히 알아보도록 하겠습니다.


4.1. EXPORT TABLE

DLL에 프로그램에 제공하는 함수에 대한 정보가 존재하는 EXPORT 테이블이 메모리 상에서 가지는 시작주소와 크기값에 대한 정보를 가지고 있습니다. 이 부분에 대해서는 뒤에서 다룰 EAT에서 설명하도록 하겠습니다.

4.2. IMPORT TABLE
PE파일이 사용하는 외부함수들에 대한 정보가 존재하는 IMPORT 테이블이 메모리 상에서 가지는 시작주소와 크기값에 대한 정보를 가지고 있습니다. 이 부분 역시 뒤에서 다룰 IAT에서 설명하도록 하겠습니다.

4.3. RESOURCE TABLE

사용자 인터페이스 요소가 정의된 리소스 디렉터리가 메모리 상에서 가지는 시작주소와 크기값에 대한 정보를 가지고 있습니다.

4.4. TLS TABLE
Thread Local Storage의 약자로 TLS의 Callback 함수를 이용한 안티 리버싱 기술을 알아야 하기에 필요한 요소입니다.
<br />
( 리소스 테이블과 TLS 테이블에 대해서는 더 자세히 알아본 뒤 업데이트 하도록 하겠습니다. )


여기서 중요한 것은 위와 같이 IMAGE_DATA_DIRECTORY 구조체 배열의 각 요소들은 PE파일에서 특정 역할을 가지고 있는 개체들의 위치를 나타내는 주소값과 해당 개체의 크기값에 대한 정보를 가진다는 것입니다. 


이번에는 헥사에디터로 살펴 보도록 하겠습니다.



크기가 깔끔하게 128 바이트로 떨어지는 것을 확인할 수 있습니다. 분석중인 PE파일은 많은 구성요소들이 0x0h 값으로 채워져 있다는 것도 알 수가 있습니다.


5. IMAGE_SECTION_HEADER


드디어 PE 헤더의 마지막 영역인 섹션 테이블까지 왔습니다.


딱 감이 오시겠지만, 섹션 헤더에는 각 섹션에 대한 정보가 저장되어 있습니다. 

먼저 섹션 헤더 구조체의 원형을 보도록 하겠습니다.



여러가지 필드들로 이루어져 있는 것을 확인할 수 있습니다.



여기서는 두번째 섹션인 .data 섹션을 이용하여 각 필드들을 알아보도록 하겠습니다. 


5.1. Name


가장 먼저 알아볼 필드는 Name 필드입니다.

섹션이 이름을 나타내는 필드로 .text, .data, .rdata 와 같은 이름이 들어가며 이름의 길이는 8자로 제한되어 있습니다. 위에 나왔던 IMAGE_SECTION_HEADER 구조체의 원형 위에 보이는IMAGE_SIZEOF_SHORT_NAME 라는 값이 Name이라는 배열의 길이를 제한합니다. 만약, 이보다 긴 이름이 저장되면 앞에서부터 8자 밖에 표시되지 않습니다.
 


여기서는 '0x2E64617461000000' 값을 가집니다. 이것을 아스키 코드로 변환하면 '.data'. 즉, 데이터 섹션을 나타내는 이름이 저장되어 있습니다. 하지만, 이 값은 아무런 값이나 들어갈 수 있기 때문에 주의해야할 필요도 있습니다.

이번에는 헥사에디터를 통해 해당 offset을 찾아보도록 하겠습니다.


해당 offset에 0x2E64617461000000 값이 들어가 있는 것을 확인할 수 있습니다.


5.2. Misc 공용체

다음으로 알아볼 필드는 Misc 공용체를 이루는 PhysicalAddress와 VirtualSize 필드입니다.

그런데 우리가 분석중인 bot.exe 파일의 PE헤더에는 VirtualSize 필드만 존재합니다. 왜냐하면 지금 분석 중인 파일이 PE 형태의 파일이기 때문입니다. 무슨말인지 이해가 안되시죠? 다음 설명을 보시면 됩니다.

5.2.1. PhysicalAddress
해당 필드는 파일이 obj 파일인 경우에만 사용되는 필드로 0x0 값이 세팅 됩니다.

5.2.2. VirtualSize
PE파일인 경우 사용되는 필드로, 섹션이 메모리 상에서 갖는 크기값을 저장합니다. 여기서는 '0x0002F458' 값을 가지고 있습니다. 즉, 193624 바이트만큼의 공간을 차지한다는 말입니다.

헥사에디터에서 찾아본 결과 리틀인디언 방식으로 0x0002F458 값이 저장된 것을 확인할 수 있습니다.

5.3. VirtualAddress ( RVA )

이어지는 필드는 VirtualAddress 필드입니다.


이 필드는 해당 섹션이 메모리 상에 올라갈때, 섹션이 시작하는 주소를 RVA 값으로 가지고 있습니다. 여기서는 '0x00003000' 값을 가지고 있습니다.


해당 필드의 값 또한 리틀인디언 방식으로 저장되어 있는 것을 확인할 수 있습니다.


5.4. PointerToRawData

다음은 PointerToRawData 필드입니다.



해당 필드는 파일에서의 해당 섹션이 시작하는 위치를 나타냅니다. 즉, 파일에서의 offset값을 가지고 있는 것입니다. 여기서는 위의 메모리 상에서의 시작 주소와 같은 '0x00003000' 값을 가지고 있습니다.

해당 필드 역시 0x00003000 값이 리틀인디언 방식으로 저장되어있는 것을 확인할 수 있습니다.

5.5. SizeOfRawData

다음은 SizeOfRawData 필드입니다.

앞의 필드에서는 메모리 상에서의 섹션에 대한 내용을 담았다면, 해당 필드는 파일에서 섹션에 대한 내용을 담고 있습니다. 파일에서 섹션이 갖는 크기값을 저장합니다. 여기서는 '0x0002F000' 값을 가지고 있습니다. 

실제 이 크기값이 맞는지 확인을 해보도록 하겠습니다. 먼저 해당 섹션인 .data 섹션의 시작 offset이 '0x00003000' 값이었습니다. 해당 섹션의 크기를 구하려면 다음 섹션의 시작 offset을 알아내면 됩니다. 
그럼 다음 섹션의 시작 offset을 알아보도록 하겠습니다.

.data 섹션의 바로 다음에 위치한 .rsrc 섹션의 헤더에서 PointerToRawData 필드를 확인하여 시작 offset을 확인해본 결과 '0x00032000' 값이 저장되어 있음을 알 수가 있었습니다.


그러면 다음과 같은 계산이 이루어 집니다.
 .rsrc 섹션의 시작 offset - .data 섹션의 시작 offset
        0x00032000 - 0x00003000 = 0x0002F000


계산결과, .data 섹션의 SizeOfRawData 필드에 저장된 값과 일치하는 것을 확인할 수 있습니다.
PEView에서 나타난 값이 의심스럽다면, 이처럼 직접 계산해보는 것도 재미있습니다.

이번에도 빠지면 섭섭할 헥사에디터로 해당 offset을 찾아보도록 하겠습니다.

0x0002F000 값이 리틀인디언 방식으로 저장된 것을 확인할 수 있습니다.

이어서 나오는 PointerToRelocations, PointerToLineNumbers, NumberOfRelocations, NumberOfLineNumbers 필드들은 실행파일에서는 별다른 의미를 갖지못하고 모두 0x0 값을 가지므로 생략하도록 하겠습니다.

5.6. Characteristics

이번에는 IMAGE_SECTION_HEADER 구조체의 마지막 필드인 Characteristics 를 알아보겠습니다.

Characteristics 필드에는 해당 PE파일의 특성에 해당하는 값들이 저장되어 있습니다.
현재 우리가 분석중인 bot.exe 파일에는 다음과 같은 특성들이 적용되어 있습니다.

0x00000040 : IMAGE_SCN_CNT_INITIALIZED_DATA
                     섹션에 초기화된 데이터가 포함되어있다는 의미입니다.
0x40000000 : IMAGE_SCN_MEM_READ
                     섹션을 읽을 수 있다는 의미입니다.
0x80000000 : IMAGE_SCN_MEM_WRITE
             섹션이 쓰기 가능하다는 의미입니다.

위와 같은 특성을 확인하면 섹션에 대한 자세한 정보를 얻을 수 있습니다.



각각의 특성들은 위와 같이 대응 하는 값들이 미리 정해져 있습니다. 해당 값들에 대한 정보는 MSDN에서 'section header'라는 키워드로 검색하면 찾을 수 있습니다.

위에서처럼 PEView를 통해 PE파일을 분석한다면 어떤 특성들이 적용되어있는지를 보기 쉽게 나타내줍니다.
그렇다면 헥사에디터에서는 어떻게 표시될까요?


벌써 알아차리신 분들도 계실거라 생각합니다. Characteristics 필드에 해당하는 offset에는 '0xC0000040' 이라는 값이 저장되어 있습니다. 이 값은 해당 PE파일이 가지는 특성들을 모두 더한 값입니다. 이렇게 해당 필드에는 특성들을 모두 더한 값으로 특성에 대한 정보를 저장합니다.

그 이후에는 섹션들이 위치하게 됩니다. 

앞에서 알아보았던 IMAGE_OPTIONAL_HEADER 구조체의 필드 중 하나인 SizeOfHeader 필드에 저장된 offset 값부터 첫번째 섹션에 해당하는 데이터가 시작됩니다. 해당 필드의 값이 0x00001000 이었으니 1000h offset 부터 .rdata 섹션이 시작해서 .data 섹션, .rsrc 섹션의 값들이 차례로 나타나고 PE파일의 내용이 끝납니다.

7. 끝

이렇게 해서 bot.exe 파일을 분석하며 PE파일 포멧의 구조에 대하여 알아보았습니다. 이것을 알고 PE파일을 분석하는 것과 모르고 달려드는 것은 분명 엄청난 차이가 있습니다. 저도 이 내용을 공부하면서 정리하기 전에는 PEView를 열어놓고도 도대체 이게 무슨 글자들이지? 했습니다. 이제는 PE구조 속에 담겨진 많은 정보들을 잘 활용할 수 있을 거라 믿습니다.

다음에는 앞의 IMAGE_DATA_DIRECTORY 부분에서 다루지 않고 넘어갔던 IAT와 EAT에 대하여 알아보도록 하겠습니다. 도움이 되셨길 바랍니다. 감사합니다.

-----------------------------------------------------------------------------------------------

부족한 부분 지적해주시면 열심히 공부해서 수정하도록 하겠습니다 ^^




출처 - http://haerakai.tistory.com/18

: