PE 구조 keybreak4855@tistory.com http://keybreak.tistory.com
목차 ⓵ VA and RVA 4p. ⓶ RVA to RAW 5p. ⓷ PE 7p. PE의개념. PE Header가생기는과정. PE의필요성. ⓷ DOS Header 8p. e_magic e_lfanew ⓸ DOS Stub 9p. 1
⓹ NT Header 10p. FileHeader / IMAGE_FILE_HEADER 11p. Machine NumberOfSection TimeDataStamp SizeOfOptionalHeader Characteristics OptionalHeader / IMAGE_OPTIONAL_HEADER 13p. Magic MajorLinkerVersion / MinorLinkerVersion SizeOfCode AddressOfEntryPoint BaseOfCode BaseOfData ImageBase SectionAlignment FileAlignmnet SizeOfImage SizeOfHeaders Subsystem SizeOfStackReserve SizeOfStackCommit SizeOfHeapReserve SizeOfHeapCommit NumberOfRvaAndSizes DataDirectory ⓺ IMAGE_SECTION_HEADER 19p. Name PhysicalAddress / VirtualSize VirtualAddress SizeOfRawData PointertoRawData Characteristics 2
해당문서는 Windows 7 Enterprise Edition K 32bit notepad.exe 로작성되었습니다. [File Version : 6.1.7600.16385] Hex Workshop Hex editor V6.7 을사용했습니다. Visual Studio 2012 Premium 을사용했습니다. < 참고 > 리버스엔지니어링역분석구조와원리 박병익 / 이각성저. 리버싱핵심원리 이승원저. 리버스엔지니어링바이블 강벽탁저. 뇌를자극하는윈도우즈시스템프로그래밍 윤성우저. 오타, 기타수정사항, 잘못된개념들을발견하시면언제든지 keybreak4855@tistory.com 으로메일주시면감사하겠습니다. Hex 값을보실때, 빨간박스는이문서에서설명하는부분이고, 파란박스는설명하지 않습니다. 다른색박스는해당구간이특별한경우입니다. 3
VA 와 RVA VA [Virtual Address] 가상메모리의절대주소입니다. 실제의물리적인주소를가상주소로사용됩니다. RVA [Relative Virtual Address] ImageBase 부터의상대주소입니다. Offset 과같은개념이지만대상이가상메모리입니다. RVA + ImageBase = VA 예를들어, 데이터를 4Byte 만큼사용하는프로그램이있다고합시다. 밑의그림을보면현재물리주소는 [0x1~0x4], [0x7~0xA] 는사용이되고있습니다. 4Byte 만큼사용하는프로그램은 [0x5~0x6] 을사용하고, [0xB~0xC] 를사용하면됩니다. 하지만프로그램은연속적으로메모리를사용하기때문에이처럼메모리간격이떨어지면안됩니다. 그래서가상주소라는개념이있습니다. 이렇게가상주소를사용해서연속적으로사용하도록하게하는것입니다. 4Byte 만큼필요한프로그램은 [0x0~0x3] 까지가상주소를이용해서물리주소인 [0x5~0x6] 과 [0xB~0xC] 에접근을하게됩니다. 좀더자세히말하자면, 프로그램은가상메모리인 [0x~0x3] 가지 4Byte 를접근하면되고, 운영체제가알아서물리적메모리 [0x5~0x6]~[0xB~0xC] 를찾아가서값을읽어옵니다. 4
RVA to RAW RVA to RAW 는 RVA 값이있을때, 해당 Section 이어디인지찾아내는방법입니다. [ 위그림중 Memory 에서 ImageBase 값을제외하고 RVA 값만작성했습니다.] RAW = RVA VirtualAddress + PointerToRawData RAW = RVA VA + PTRD 위식이 RAW 를찾는방법입니다. 풀어서쓰면, 파일의임의위치 = 메모리의임의위치 메모리의해당섹션의시작위치 + 파일의해당섹 션의시작위치입니다. 5
============================================================================= [ 예제 1] RVA = 5000 일때, File Offset (RAW) =? (1) RVA 가속해있는 Section 을찾습니다. RVA ( 메모리의임의위치 ) 가 5000 이므로오른쪽메모리그림에서속하는구간은.text Section 입니다. (2) RAW = RVA VA + PTRD 이므로, RAW = 5000 1000 400 = 4000 입니다. VA ( 메모리에서해당 Section 의시작위치 ) 는.text Section 의시작 1000 입니다. PTRD ( 파일에서해당 Section 의시작위치 ) 는.text Section 의시작 400 입니다. ============================================================================= [ 예제 2] RVA = 13314 일때, File Offset (RAW) =? (1) RVA 가속해있는 Section 을찾습니다. RVA ( 메모리의임의위치 ) 가 13314 이므로오른쪽메모리그림에서속하는구간은.rsrc Section 입니다. (2) RAW = RVA VA + PTRD 이므로, RAW = 13314 B000 8400 = 10714 이다. VA ( 메모리에서해당 Section 의시작위치 ) 는.rsrc Section 의시작 B000 입니다. PTRD ( 파일에서해당 Section 의시작위치 ) 는.rsrc Section 의시작 8400 입니다. ============================================================================= [ 예제 3] RVA = ABA8 일때, File Offset (RAW) =? (1) RVA 가속해있는 Section 을찾습니다. RVA ( 메모리의임의위치 ) 가 ABA8 이므로오른쪽메모리그림에서속하는구간은.data Section 입니다. (2) RAW = RVA VA + PTRD 이므로, RAW = ABA8 9000 7C00 = 97A8 입니다. VA ( 메모리에서해당 Section 의시작위치 ) 는.data Section 의시작 9000 입니다. PTRD ( 파일에서해당 Section 의시작위치 ) 는.data Section 의시작 7C00 입니다. 하지만 RAW의값이 97A8은.rsrc Section 에속해있습니다. RVA 는두번째 Section 이고 RAW 는세번째 Section 이라면이건말이되지않습니다. 따라서이와같은경우는 "RVA 에대한 RAW 값은정의할수없다 " 라고합니다. ============================================================================= 6
PE PE 는 Portable Executable File Format 의약자로써, 사람들이만든파일이실행할수있 는, 이식가능한다른곳에옮겨져도실행이가능하도록만들어놓은포맷입니다. PE Header 가생기는과정 프로그래머가 example.cpp 라는 Source 작성하고, 이를 Build 하면 Compiler 는 example.cpp 와관련된모든 Header 파일과 Source 파일을합쳐서하나의기계어코드로만들어냅니다. 그러면 Obj 파일이만들어지게됩니다. 여기까지는컴퓨터가이해하는언어일뿐파일의실행까지는할수없습니다. 그다음으로 Linking 작업을통해운영체제에서이파일을실행하기위해 Linker 가동적라이브러리 (DLL) 이나각종 Resource 데이터, Import Table, Export Table 을처리할수있는정보를어딘가에적어두게되는데, 여기서윈도우는약속된규약에맞춰정보를입력하고, EXE 파일을만들때 Header 에이같은정보를기입합니다. 이것이 PE Format 으로생성되는파일입니다. PE 의필요성 PE Header 에는실행파일을실행하기위한각종정보가기록되어있습니다. 파일을실행하게되면개발자가만든코드가실행되기전에 PE 의정보로부터읽어와서바이너리를메모리에올리기위한각종데이터를설정하는작업을하게됩니다. 그래서 PE 에이상한정보가있거나, PE 자체가깨져있다면그파일은실행하는데필요한정보를불러오지못하게됩니다. 7
DOS Header DOS 파일에대한하위호환성을고려해서만들어졌습니다. 구조체의크기는 40 이며, 중요한멤버는 e_magic 과 e_lfanew 입니다. e_magic : Magic Number 라고불립니다. 값은 AD 5A 로 ASCII 코드로 MZ 입니다. (MZ 는마크주비코브스키 (Mark Zbikowski) 의약자입니다.) e_lfanew : NT Header 의 Offset 값 (RVA 값 ) 입니다. 파일에따라서가변적인값을가 집니다. e_lfanew 의값이가리키는위치에 NT Header 가존재해야합니다. 8
DOS Stub DOS Stub 은크기는일정하지않으며, 없어도실행이가능합니다. [0x40~0x4D] 영역은 16Bit 어셈블리명령어이므로, 32Bit Windows OS 에서는실행이되지 않습니다. Windows OS 에서는 [0x40~0x4D] 구간을 PE 파일로인식하기때문입니다. 다만, DOS 환경에서실행하거나, DOS 용디버거를이용해서실행하면실행은가능합니다. 9
NT Header Signature : 해당파일이올바른 PE 포맷으로구성되어있는지확인하기위한것입니다. 올바른 PE 포맷파일이라면항상값은 4Byte 인 PE\0\0 을갖습니다. 바이러스제작자들이자신의값을표시하는데자주이용이됩니다. 이유는파일에어떤값을써넣거나표식을할때주로 PE 를이용하기도하지만아무필드에나값을넣으면 EXE 파일자체가실행되지않을가능성이있으므로확인용외에메모리를 Loding 할때는거의쓸일이없는빈공간이나다름없는필드를찾아서표식을하는것이일반적이기때문입니다. 하지만이러한행동이일반적이기때문에마이크로소프트에서이필드가변조되었을때, 파 일을실행시키지않도록보안하고있습니다. 10
NT Header FileHeader Machine : 현재파일이어느 CPU 에서실행되는지에대한정보를가지고있습니다. CPU 별로고유한값이있으며 32Bit Intel Chip 은 14C 의값을갖습니다. 11
NumberOfSections : Section을분석하기위해서사용되는값입니다. Section 을개수를나타내며, 이값은항상 0보다커야하고, 정의된 Section 의개수와실제 Section 이다르면실행 Error 가발생합니다. TimeDataStamp : 파일을 Build 한날짜가표시됩니다. Obj 파일이 Compiler 를통해 EXE 로생성한시간입니다. 이구간은변조가가능하므로 100% 신뢰를해서는안되며, 델파이의경우는정상적으로이값을기록하지않고항상 1992년으로표시합니다. [ 요일 / 월 / 일 / 시 / 분 / 초 / 년 ] 순으로표시합니다. SizeOfOptionalHeader : IMAGE_OPTIONAL_HEADER32 구조체의크기를나타냅니다. 이미크기는정해져있습니다. Windows PE Loader 는 IMAGE_FILE_HEADER의 SizeOfOptionalHeader 값을보고 IMAGE_OPTIONAL_HEADER32 의구조체크기를인식합니다. 운영체제마다크기가다를수있기때문입니다. PE32+ 형태 (64Bit) 는 IMAGE_OPTIONAL_HEADER64 구조체를사용합니다. Characteristics : 현재파일이 EXE 인지 DLL 인지, 실행이가능한지안하는지에대한정 보들이 bit OR 형식으로조합됩니다. 12
NT Header OptionalHeader 13
Magic : IMAGE_OPTIONAL_HEADER32 구조체인경우 [0x1B] IMAGE_OPTIONAL_HEADER64 구조체인경우 [0x2B] 의값을갖습니다. MajorLinkerVersion / MinorLinkerVersion : 어떤 Version 의 Compiler 로 Build 했는지 알려줍니다. SizeOfCode : 코드양의전체크기를나타냅니다. 실제개발자가만든코드의양이얼마나되는지이필드에서나타납니다. 바이러스나악성코드는이필드를읽어서자신의코드를복제할위치를기준잡기도하며, 보안솔루션에서는코드섹션의무결성검사를수행할때역시이섹션의값을얻어와서검사크기를잡기도합니다. AddressOfEntryPoint : EP (Entry Point) 의 RVA (Relative Virtual Address) 값을가지고있습니다. 프로그램에서최초로실행되는코드의시작주소로써매우중요합니다. Unpacking 할때 OEP (Original Entry Point) 를찾아야한다는말이바로이말입니다. 주의프로세스가가장먼저시작하는위치라고생각하면안됩니다. 모든프로그램은메인함수가실행되기전에프로그램실행을준비하기위해 Start Up 코드를실행하게되므로가장먼저시작되는코드의위치가 RVA가아니라는점을주의해야합니다. BaseOfCode : 코드영역의시작주소입니다. AddressOfEntryPoint와혼동하지않도록해야하고, AddressOfEntryPoint 보다 BaseOfCode 의주소가앞에있습니다. RVA 값이라는것에주의를해야합니다. BaseOfData :.data Section 의시작주소입니다. 읽고쓰기가가능한경우가많고, RVA 값입니다. 14
ImageBase : 메모리에서 PE 파일이 Loding 되는시작주소입니다. 이는 RVA가아니라 RVA의기준이되는주소입니다. EXE, DLL 파일은 User Memory 영역은 [0x0~0x7FFFFFFF] 범위에 Loding 되고 (EXE는 0x40000000의값을가지고, DLL은 0x10000000의값을가진다. 하지만항상그런것은아니다.), SYS 파일은 Kernel Memory 영역인 [0x80000000~0xFFFFFFFF] 범위에 Loding 됩니다. PE 파일을실행시키기위해프로세스를생성하고파일을메모리에 Loding 한후 EIP 레지스터값을 ImageBase + AddressOfEntryPoint 값으로코드의시작주소로합니다. 주의 RVA는 PE Loader 에의해메모리에올려진후에계산되는것으로, Hex Workshop과같은 Tool 로분석할때 RVA 계산을해서는안됩니다. Hex Workshop 으로바로수정할때에는순수한 BaseOfCode 값이가지고있는주소그대로이동해야합니다. 15
SectionAlignment : 메모리상에올려진후의 Section 의배치간격입니다. PE 파일은 Section 으로나뉘어져있습니다. 파일에서최소단위를나타내는것입니다. 예를들어서어떤한 Section 에서그 Section 에대한 Header 가있다고할때, 이 Section Header 에는 2 가지주소의정보를가지고있습니다. 첫번째주소정보는 PE Loader 가참조해서올려야할가상주소 (VirtualAddress) 를위한 주소이고, 두번째주소정보는메모리에올려지지않은파일 (RawAddress) 을위한주소입니 다. 한 Section에서 VritualAddress 멤버가 [0x2000] 의값을가지고있고, PointerToRawData(RawAddress) 멤버는 [0x1000] 의값을가지고있다고하면, 그 PE 파일을실행하면 PE Loader는 Section Header 에담겨있는정보들을참고해해당 Section 을메모리주소어딘가에올려야합니다. ImageBase 가 [0x00400000] 이므로 DOS Header 를 [0x00400000] 에올립니다. 그리고 VirtualAddress 값을조사하면 [0x2000] 이라는값이나옵니다. 그러면 PE Loader 는 [0x00400000] 에 [0x2000] 을더해서 [0x00402000] 메모리주소에해당 Section 을올립니다. 파일상에서 [0x1000] 번지에저장을시키기위해 PE 포맷을생성하고 PointerToRawData(RawAddress) 값을 [0x1000] 으로세팅한다음해당 Section 영역을파일상의 [0x1000] 에저장시킵니다. 그래서파일상태로분석할때해당영역의위치를알아내기위해서 PointerToRawData 값만참조해그주소로이동하면되는것입니다. 좀더쉽게말하자면.text Section 과.rdata Section 이있다고하자..text 의실제크기가 [0x800] Byte 정도라면 800Byte 이후에바로.rdata Section 이등장하는것이아니고 SectionAlignment 가 [0x1000] 이므로나머지 [0x200] 은 0 [NULL] 으로채워집니다. SectionAlignment 는 Section 이 PE Loader 에의해메모리에올려질때항상이멤버의배수값으로위치한다. 이값이 [0x1000] 이라고한다면항상 Section Header의 VirtualAddress 값은이값의배수가됩니다. [0x1000] [0x2000] [0x3000] 은가능하나 [0x1500] [0x2004] [0x3600] 등과같은값은불가능합니다. ([0x1000] 단위로지정된다고생각하면됩니다.) FileAlignment : SectionAlignment 와기본개념은똑같지만그기준이 PointerToRawData 에영향을준다는것만다릅니다. 파일상의 Section 위치간격입니다. SizeOfImage : PE 파일이메모리에 Loding 됐을때가상메모리에서 PE Image 가차지하는크기를나타낸것입니다. ( 이미지파일전체크기. 즉, 파일의전체크기이다.) 일반적으로파일의크기와메모리에 Loding 된크기는다릅니다. SectionAlignment 에서패딩한값까지포함되기때문에 SectionAlignment 의배수가됩니다. 16
SizeOfHeaders : PE 전체크기 (DOS Header, DOS Stub, PE Header, Section Header 를모두더한값.) 를나타냅니다. 이값또한 FileAlignment 의배수여야합니다. 파일시작에서 SizeOfHeaders Offset 만큼떨어진위치에첫번째 Section 이위치합니다. Subsystem : Subsystem 의값을보고 SYS 인지, EXE 인지, DLL 인지구분할수있습니다. 값 의미 비고 1 Driver file 시스템드라이버 (ntfs.sys) 2 GUI (Graphic Interface) 파일 창기반애플리케이션 (notepad.exe) 3 CUI (Console User Interface) 파일 콘솔기반애플리케이션 (cmd.exe) SizeOfStackReserve : 프로그램에서사용될 Stack 을얼마만큼예약할지의바이트기준 입니다. SizeOfStackCommit : 프로그램에서사용될 Stack 을얼마만큼결정할지의바이트기준 값입니다. SizeOfHeapReserve : 프로그램에서사용될 Heap 을얼마만큼예약할지의바이트기준값 입니다. SizeOfHeapCommit : 프로그램에서사용될 Heap 을얼마만큼결정할지의바이트기준 값입니다. NumberOfRvaAndSizes : IMAGE_OPTIONAL_HEADER32 구조체의 마지막 멤버인 DataDirectory 배열의 개수를 나타냅니다. 구조체 정의에 분명히 배열 개수가 IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 이라고명시되어있지만, PE Loader는 NumberOfRvaAndSizes 의값을보고배열의크기를인식합니다. 즉, 16이아닐수도있습 니다. 17
DataDirectory : DataDirectory는 IMAGE_DATA_DIRECTORY 구조체의배열로, 배열의각항목마다정의된값을가집니다. 이멤버는 Export Table, Import Table, Resource 영역, Exception 영역, 보안영역, Debug 영역등을접근할수있는주소를가지고있는배열입니다. Import 함수들을조사할때 DataDirectory 의두번째배열을참조하게됩니다. 이배열에는 각각해당영역으로갈수있는 RVA 주소와그영역의크기정보를담고있습니다. Import Table 로이동하면 IMAGE_IMPORT_DESCRIPTOR 구조체를얻을수있는데이구 조체는해당파일이 Import 하고있는 DLL 과함수정보들을가지고있습니다. API Hooking 에사용되는중요한 Table 입니다. 18
SectionHeader IMAGE_SECTION_HEADER Name : Section의이름을나타내는멤버로써 Section 이름을나타내는문자열을저장하게됩니다. IMAGE_SIZEOF_SHORT_NAME은 8Byte 크기를나타내는상수입니다..text나.data 같은이름을갖지만 Section의이름으로섹션의성격을파악해서는안됩니다. 멤버의값이 NULL 값일수도있고섹션이름이같다고해서항상같은속성을지니지않습니다. 섹션의속성을파악하기위해서는 Characteristics 멤버를참조하여야합니다..text.data.rdata.bss.idata.edata 실행되는코드초기화된전역변수를담고있는읽고쓰기가능한 Section 읽기전용데이터 Section, 문자열표현이나 c++/com 가상함수 Table. 초기화되지않은전역변수들을위한 Section 다른 DLL로부터가져다쓰는함수들의정보, IMAGE_IMPORT_DESCRIPTOR의배열로이루어져있으며하나당 DLL 한개의정보를담고있다. 다른모듈이이파일을사용할때사용하도록해놓은함수리스트 19
PhysicalAddress / VirtualSize : PE Loader에의해이미지가메모리에올려진후에해당 Section이얼마만큼의크기를가지고있게되는지의정보입니다. Misc UNION 구조체의멤버이며이는물리적주소를의미하는것이아니라가상주소상에서해당 Section의크기를나타내는멤버라는점을주의해야합니다. VirtualAddress : PE Loader에의해이미지가메모리에올려진후에해당 Section이어느주소에위치하는지 RVA 주소를값으로가지고있습니다. 이멤버는항상 IMAGE_OPTIONAL_HEADER 의멤버인 SectionAlignmnet 의배수값을가집니다. PE Loader 가메모리에 Section 을올릴때이멤버의값을참조하게된다. VirtualAddress 의값이 [0x1000] 이고 IMAGE_OPTIONAL_HEADER 의 ImageBase 값이 [0x00400000] 라면 PE Loader 는해당 Section 을 [0x00401000] 번지에올립니다. 항상이값은 ImageBase 를기준으로하는 RVA 값입니다. SizeOfRawData : Raw data 상에서해당 Section 에대한실제사용된크기정보를담고 있다. 해당 Section 의빈공간이얼마나있는가를알아내기위해서반드시필요합니다. Section 중에는 CPU 가실행할수있는기계어코드가담겨있는코드 Section 부분이있습 니다. 코드 Section 위치 : [0x00001000] 코드 Section 에서사용되는크기 : [0xF00] FileAlignment 의값 : [0x1000] 이라고합시다. 그렇다면코드 Section 대한사이즈는 [0xF00] 만할당하면됩니다. 하지만윈도우즈는이런식으로 Section 공간을할당하지않고, Raw data상에서반드시 FileAlignment 의배수로 Section 공간을할당하기때문에 [0x00001000] 에서 [0x00002000] 까지인 [0x1000] 의크기로코드 Section 을할당하게됩니다. 이렇게되면각 Section 들은사용하지않는빈공간을가지고있게되고, 이공간에우리의코드를작성함으로써남이만드는프로그램을내뜻대로조작할수있는것입니다. Section 을직접추가하거나 Section Table 의값을조정해서현재존재하는 Section 의크기를늘릴수도있습니다. Section 을분석해서다른프로그램의영역에내가원하는영역을만들수도있습니다. 20
PointerToRawData : Raw data 가파일상의어느주소에위치해있는지나타내는변수 입니다. 파일에서 Section 의시작위치라고보면됩니다. PointerToRawData 가 [0x1000], FileAlignment 가 [0x1000], SizeOfRawData 가 [0x500] 라면이섹션은 [0x00001000~0x00000fff] 까지이며, 실제사용되고있는영역은 [0x0000100~0x000004ff] 이고, 빈영역은 [0x00000500~x00000fff] 입니다. Characteristics : Section 에대한속성정보를플래그로가지고있습니다. 윈도우에서는 여러가지속성을상수매크로로정의해놓았습니다. 해당 Section 의속성값을알아내기위해서 AND 연산을이용합니다. 간단히해당 Section 이읽기속성을가지고있는지검사하기위해서는 또는코드가참이면읽기속성을가지고있는것이고읽기, 쓰기둘다가지고있는지체크하기위해서또는코드가참인지확인하면됩니다. 이속성값을변경함으로써쓰기가금지된 Section 에 Write 할수도있고쓰기가능한 Section 에 Write 를금지시킬수도있습니다. PE 파일이메모리에 Loading 될때파일이그대로올라가는것이아니라, Section Header 에정의된대로 Section 시작주소, Section 크기등에맞춰서올라갑니다. 따라서파일에서의 PE 와메모리에서의 PE 는서로다른모양을가집니다. 이를구별하기위해서메모리에 Loading 된상태를이미지라는용어를사용해서구별합니다. 21