[조립하면서 배우는 PE] 첫번째 이야기 : PE의 전체적인 구조 Reverse Engineering

리버싱 2017. 1. 10. 16:26

번째 이야기입니다. PE 파일을 수작업으로 만들어 가려면 당연히 전체적인 모습부터 알아야 겠습니다. 어떻게 생겨먹은 놈(공식적으로 PE의 성별에 대해서 확인된 바는 없습니다. ^^;) 인지를 알아야 조립을 할 수 있겠죠. PE 파일의 전체적인 구조는 [그림 1]과 같습니다.  PE 파일의 전체적인 구조를 공부할 때는 "PE 파일은 디스크 상의 모습과 메모리 상의 모습이 거의 같다"라는 것과 PE 파일의 구성 요소, 그리고 각 구성 요소의 시작점을 찾는 방법을 잘 알아두어야 합니다


사용자 삽입 이미지


[그림 1] PE File의 전체적인 모양 (클릭 후 확대해서 보세요)



PE 구성 요소
PE는 DOS header, DOS stub code, PE header, Section table 및 1개 이상의 섹션으로 구성됩니다.  각 구성 요소에 대해서는 차근차근 공부해 나갈 것입니다. 지금은 자세한 내용보다는 전체적인 구조에 대해서 파악하는 것이 중요합니다. 이에 맞춰 각 구성 요소의 명칭과 구성 형태를 [그림 1]을 참고하여 스스로 그림으로 그려낼 수 있도록 노력하면 되겠습니다. 한가지만 더 언급하자면 DOS stub 코드는 PE 파일의 필수 구성요소가 아니라서 생략이 가능합니다. 


각 구성 요소의 시작 위치 계산 방법

DOS header : PE 파일은 DOS header로 시작합니다. 따라서 디스크 상에서는 파일의 첫부분이 DOS header가 됩니다. 메모리 상에서 PE 파일은 ImageBase에서 시작하므로 DOS header는 ImageBase에서 찾을 수 있습니다.간단하게 확인해 보도록 하죠. 먼저 calc.exe를 StubPE를 이용하여 열어보겠습니다. [그림 2]는 StudPE를 통해 살펴 본 PE 헤더 일부입니다.  ImageBase 값이

사용자 삽입 이미지

01000000이군요. 메모리에 로드된 PE 파일의 시작점이 01000000 번지라는 이야기입니다. 디버거를 하면 간단하게 확인이 가능합니다. calc.exe를 ollydbg로 오픈해 보죠. 오픈 후에는 [그림 3]처럼 우측 하단의 [데이터 윈도우]를 클릭하고 Ctrl+G(Goto)를 눌러 01000000을 입력합니다 

[그림 2] calc.exe의 ImageBase 값


사용자 삽입 이미지

[그림 3]  메모리상 0x01000000 번지로 이동(확대해서보세요)

이동하여 살펴보면 [그림 4]처럼 데이터값 4D 5A(MZ)를 발견할 수 있습니다.

사용자 삽입 이미지












[그림 4] calc.exe가 로드된 후 0x01000000번지의 내용

MZ은 DOS 개발자 중 한명인 Mark Zbikowski라는 분의 이니셜로 DOS header의 시그너춰로 사용됩니다. 이로써 0x01000000번지 즉 ImageBase에 DOS header가 로드되어 있음을 확인할 수 있습니다. 

DOS Stub Code :  윈도우용 애플리케이션을 도스 모드에서 실행시킨 경우 애플리케이션이 정상적으로 동작되지 않습니다. 이러한 경우 윈도우는 애플리케이션 대신에 스텁 코드를 실행시킵니다. 스텁 코드에는 DOS 모드에서 실행 가능한 프로그램이면 어느 것이나 삽입이 가능합니다. 스텁 코드는 DOS header 바로 뒤에 위치합니다. DOS header의 사이즈는 64bytes로 고정되어 있기 때문에 디스크 상에서나 메모리 상에서 DOS 스텁 코드는 시작점으로 부터 64bytes만큼 떨어진 곳에서 찾을 수 있습니다.  

PE Header : 디스크 상이나 메모리 상에서 PE 헤더의 위치는 DOS header에 있는 e_lfanew 값을 이용하여 계산할 수 있습니다. e_lfanew는 4byte 크기의 데이터로 DOS header의 마지막에 위치합니다. e_lfanew에는 파일의 시작점에서 부터 PE 헤더까지의 오프셋 값이 저장되어 있습니다. 간단하게 확인해 보도록 하겠습니다. Hex 에디터로 calc.exe를 열어 e_lfanew 값을 확인합니다. e_lfanew 는 DOS header의 마지막 멤버로 DOS header 시작점으로 부터 60byte 만큼 떨어진 곳에 위치합니다. 
확인된 e_lfanew 값을 DOS header 시작 주소(PE 파일 시작 주소)에 더해주면 PE 헤더(PE헤더는 "50 45 00 00"으로 시작합니다)를 찾을 수 있습니다.  이는 디스크상에서나 메모리상에서 모두 동일합니다. [그림 5]를 참고하세요. 

사용자 삽입 이미지


  [그림 5] PE 헤더 찾기 (확대해서 보세요)

섹션 테이블 : 섹션 테이블은 PE Header 바로 뒤에 위치합니다. 따라서 섹션 테이블을 찾으려면 PE 헤더 시작 주소에서 PE header 사이즈를 더해 주면 될 것입니다. 이는 메모리 상 PE나 디스크 상의 PE가 모두 동일합니다. 나중에 좀 더 자세히 알아보겠지만 PE 헤더는 PE signature(4 bytes 고정)와 File Header(20bytes 고정) 그리고 Optional Header(기본 : 224bytes, 변할 수 있음) 로 구성되어 있습니다. 따라서 PE 헤더의 사이즈는 24bytes + Optional Hedaer 사이즈가 됩니다. Optional Header의 사이즈는 File Header에 저장되어 있으므로 어렵지 않게 알아낼 수 있습니다.
 
섹션의 위치 : 각 섹션의 위치는 섹션 테이블에 저장된 섹션 헤더을 통해서 확인 가능합니다. 섹션 헤더에는 해당 섹션의 위치와  관련하여 VirtualAddress라는 값과 PointerToRawData라는 값이 저장되어 있습니다. 둘 다 offset 값인데 PointerToRawData는 디스크 상의 섹션의 위치를 가르키는 offset 값이고 VirtualAddress는 메모리 상에서의 section의 위치를 가르키는 offset 값입니다. 이러한 사실로 부터 우리는 [그림 1]에 나타난 것처럼 디스크 상에서의 섹션의 위치와 메모리 상에서의 섹션의 위치가 같지 않을 수도 있다는 사실을 유추해 볼 수 있겠습니다. 이 부분은 섹션에 대해서 좀 더 자세히 다룰 때 함께 알아보도록 하겠습니다. 여기서는 간단히 확인만 하도록 하겠습니다. 먼저 디스크 상에서의 섹션의 위치입니다. calc.exe를 계속 사용하도록 하죠. [그림 6]에서 처럼 StudPE로 calc.exe를 열어 section header를 살펴보겠습니다. 

사용자 삽입 이미지

    [그림 6] calc.exe의 섹션 헤더

.text 섹션의 RawOffset 값이 0x400임을 알 수 있습니다. 이는 디스크 상의 .text 섹션이 파일의 시작점으로 부터 0x400 떨어진 곳에서 시작한다는 것을 의미합니다. 이를 확인하기 위해 [그림 7]에서 처럼 마우스 오른쪽 버튼을 클릭한 후 "GoTo Section Start"를 선택하여 .text 섹션의 시작 위치로 이동해보겠습니다.

사용자 삽입 이미지


















[그림 7] calc.exe의 .text 시작점으로 이동

시작 위치로 이동하였으면 [그림 8]을 봐주세요. 

사용자 삽입 이미지


[그림 8] calc.exe의 .text 섹션(on disk, 클릭해서 확대 후 보세요)

.text 섹션으로 이동 후 StudPE의 HexViewer의 하단 정보를 확인하면 파일 상의 위치(in File)가 0x400임을 확인할 수 있습니다. 길을 잃어버리시면 안되죠~ 지금까지의 실험은 섹션 테이블에 위치한 섹션 헤더의 PointerToRawData 값은 해당 섹션의 파일 내 시작 위치를 알아내는 데 사용한다는 사실을 확인해 보기 위한 것이었습니다. 이제는 메모리에서 찾아볼까요? Ollydbg를 이용하여 calc.exe를 오픈해 보도록 하죠. 그 다음 ImageBase로 이동해 보겠습니다.(앞에서 해보셨죠? 헛갈리는 분들은 그림 2,3,4를 참고하세요) 이동 후에서는 [그림 9]에서와 같이 마우스 오른쪽 버튼을 눌러 메뉴를 띄운 후 Special->PE header를 선택합니다. 이 메뉴는 데이터를 PE 헤더로 파싱할 때 사용합니다. 

사용자 삽입 이미지






















[그림 9] 메모리에 위치한 calc.exe의 PE 헤더 분석

PE 헤더로 파싱 후 [그림 10]에 나와있는 위치를 살펴보면 .text 섹션 헤더를 찾을 수 있습니다. 우리는 메모리 상의 .text 섹션의 위치를 찾으려고 하는 것이므로 이번에는 VirtualAddress 항목을 봐야 겠습니다. 예에서는 VirtualAddress 항목의 값이 0x1000인 것을 확인할 수 있는데 이는 ImageBase로 부터의 offset 값(이를 상대 가상 주소, Relative Virtual Address 줄여서 RVA라고 부릅니다.)입니다. 따라서 .text 섹션의 메모리 상 위치는 ImageBase값인 0x01000000에 오프셋 값 0x1000을 더하면 됩니다. 그럼 0x01001000 번지로 이동해 볼까요? 

사용자 삽입 이미지


 [그림 10] 메모리 상의 .text 섹션 헤더 (클릭 후 확대해서 보세요)

아래 [그림 11]은 .text 섹션으로 이동하여 살펴본 결과입니다. 

사용자 삽입 이미지








[그림 11] calc.exe의 .text 섹션의 메모리 상 시작점

주의 깊게 살펴보면 0x01001000번지에 있는 데이터는 9A22D877인데 [그림 8]에서 살펴본 .text 섹션의 시작 번지에 있는 데이터는 EA22D877로 서로 다른 값을 가지고 있음을 알 수 있습니다.  ^^; 이는 calc.exe의 경우 .text 섹션의 시작점에 실제 코드가 아닌 IAT이 위치해 있기 때문에 발생하는 상황으로 로드시 DLL 바인딩되면서 IAT에 있는 각 항목의 값들이 변경된 결과입니다. 지금은 이 부분에 신경쓰지 말고 메모리 상 섹션의 위치는 섹션 헤더의 VirtualAddress를 통해서 알 수 있다는 사실만 기억하세요. 


PE 파일은 디스크 상의 모습과 메모리 상의 모습이 거의 같다
지금까지 살펴본 결과로 DOS header에서 Section table까지의 구성은 디스크 상의 PE 파일이나 메모리 상의 PE파일 모두 동일하다는 사실을 알 수 있습니다. 이는 디스크 상의 PE 파일이 메모리로 로드 될 때 DOS header 부터 Section table까지는 그대로 로드됨을 의미합니다. 차이가 발생하는 부분은 각 섹션인데요(섹션 헤더에 디스크 상의 위치와 메모리 상의 위치를 구분하는 데이터가 있는 것만 봐도 각 경우의 위치가 다를 것이라는 것을 짐작할 수 있겠죠) 이는 alignment와 관련있습니다. Alignment와 관련하여 기억해 둘 사실은 아래와 같습니다. 
 - 섹션들의 디스크 상의 정렬 단위와 메모리 상의 정렬 단위가 다를 수 있다. 
 - 디스크 상의 정렬 단위와 메모리 상의 정렬 단위는 각각 Optional Header의 FileAlignment와 
   SectionAlignment에 저장되어 있다. 
 - 디스크 상의 섹션은 FileAlignment의 배수가 되는 주소에서 시작한다.
 - 메모리 상의 섹션은 SectionAlignment의 배수가 되는 주소에서 시작한다. 
 - 이러한 이유로 FileAlignment와 SectionAlignment 값이 다른 경우 디스크 상의 PE 파일의 모습
    과 메모리 상의 PE 파일의 모습은 DOS header부터 Section table까지는 일치하나 섹션이 배치되는 모습은 약간씩 차이가 발생한다.

Ollydbg에서 분석 한 PE 헤더의 내용을 뒤져보면 [그림 12]에서 처럼 SectionAlignment와 FileAlignment 값을 확인할 수 있습니다. 

사용자 삽입 이미지


[그림 12] SectionAlignment와 FileAlignment 

[그림 6]에서 확인할 수 있었던 것처럼 calc.exe의 .text 섹션은 디스크 상에서 0x400번지에서 시작하였으며 [그림 10]에서 확인한 .text 섹션의 시작 주소는 0x01001000 이었습니다. 0x400은 0x200의 배수이며 0x1001000은 0x1000의 배수가 맞군요. ^^; 

정리해 보겠습니다. PE 파일은 메모리에 로드될 때 DOS header부터 Section table까지는 그대로 로드되며 각 section의 경우 지정된 alignment 값에 따라 약간의 차이를 가지고 로드된다는 사실을 기억하시면 됩니다. 덧붙이자면 음... 사실 로드되지 않는 섹션도 더러 있습니다. ^^ 그냥 그럴 수도 있다고 알아두시면 될 것 같습니다. 

맺음말
지금까지 PE 파일의 전체 구조를 살펴보았습니다. 지금까지의 모든 설명을 종합 한 것이 [그림 1]입니다.  [그림 1]을 혼자서 그려 내실 수 있을 때까지 반복하시면 도움이 될 것입니다.  두번째 글부터는 각 구성 요소에 대해서 하나씩 알아보도록 하겠습니다. 


출처 - http://zesrever.tistory.com/55

: