PE FILE 구조와 언패킹의원리 지선호 kissmefox@gmail.com - 1 -
< PE FILE 이란 > -win32 운영체제에서이용되는파일형식 ( 현재사용되는대부분의 OS) -Portable executable, : exe, dll, ocx 이식가능한실행파일형식 - 윈도우의바이너리를분석하기위한가장기본이되는지식 : unpacking, API Hooking, DLL injection, 악성코드분석.. <PE FORMAT 이해를위한기본지식> RVA - 이미지가해당프로세스의가상주소공간내에로드돼었을때에그시작 주소에대한상대적번지개념. 메모리상에서의 PE의시작주소에대한 offset 으로생각하면됨 Section - PE 가가상주소공간에로드된뒤의실제내용( 코드와데이터,Import API,Export API, 리소스, 재배치정보 TLS 등) 을담고있는블록들 ** 파일로존재하는 PE 구조와가상번지에로드되었을때의 PE 구조의관계 - 프로세스에할당되는 4기가바이트의가상주소공간을유지하기위해가상메모리 관리자(VMM) 는페이지파일(pagefile.sys) 이라는스와핑영역을하드디스크에유지하며이 페이지파일과의적절한스와핑, 매핑을통해프로세스에게 4기가의가상공간을실제로제공해 주는것처럼실행환경을만들어주게됩니다. 하지만 PE 파일이로드될때 VMM 은페이지파일을사용하지않고, PE 파일자체를마치 페이지파일처럼가상주소공간에그대로매핑하게됩니다. 이렇게파일자체가페이지파일의 역할을대신하는경우를메모리에매핑된파일(MMF) 라고하며즉 PE 의경우 MMF 로해당 파일을연다고볼수있습니다. WinNT.H 에정의된 PE Header 의구조체를보면 IMAGE 라는 단어를사용하고있습니다. 여기서 "IMAGE" 의의미는메모리에매핑된하드디스크상의 PE 파일자체를말하고있습니다. 정리하자면, 메모리상의로드된 PE 파일포맷이나, 하드디스크 상에서의파일로존재하는 PE 포맷의모습이같다고말할수있습니다. - 2 -
< PE FILE HEADER 구조분석 > - Win32 Platform SDK 의 WinNT.H" 헤더파일참조 <Block 으로간단히표현> - 3 -
1. DOS MZ header (IMAGE_DOS_HEADER) :PE 파일의시작지점 ( PE header 의 offset 을가짐) win32 기반 OS에서 PE 파일형식을실행하면 PE loader는 PE header의 offset을읽고바로 DOS 부분을건너뛰고 PE header 에접근 -DOS stub :OS 가 PE 파일형식을알지못할때 This program cannot be run in DOS mode" 메시지출력, real dos mode 실행목적으로사용 주요필드 e_magic DOS 헤더를구별하는식별자. MZ" 모든실행파일은파일의가장첫부분에이값을가지게됨. PE 로더가이값을체크하여맞다면실행파일을메모리에로드하게됨 e_lfanew PE 헤더(IMAGE_NT_HEADER) 가있는곳의 offset - 실행파일의시작부분정도로만생각하면됩니다. 2. PE HEADER (IMAGE_NT_HEADER) : PE 로더가사용하는핵심정보를담고있음 - 4 -
signature : PE 파일임을나타내는매직넘버. 4byte 로구성되며항상 PE\x0\x0" 이다. IMAGE_DOS_HEADER 구조체의 e_lfanew 필 드가가리키는오프셋으로부터의 4byte 값. FileHeader : IMAGE_FILE_HEADER 구조체멤버 주요필드 Machine 파일이실행될 CPU 플랫폼 NumberOfSections TimeDateStamp SizeOfOptionalHeader 파일에존재하는섹션의수 파일이생성된시간과날짜 IMAGE_OPTIONAL_HEADER 구조체의크기 Characteristics 해당 PE 파일에대한특정정보를나타내는 flag **OptionalHeader : IMAGE_OPTIONAL_HEADER 의구조체멤버 Logical Layout 에대한정보 - 5 -
<IMAGE_OPTIONAL_HEADER 의구조> 주요필드 SizeOfCode 코드영역에 CPU가실행하는기계어코드의전체크기 SizeOfInitializedData SizeOfUninitializedData AddressOfEntryPoint Data 가초기화된섹션의총합 SizeOfInitializedData 의반대 프로그램의시작위치(main) 의 RVA ** 프로세스가가장먼저시작하는위치라고생각하면안됨! BaseOfCode 코드영역( 주로.text 섹션) 의시작주소 RVA BaseOfData 데이터영역( 주로.data 섹션) 의시작주소 RVA - 6 -
ImageBase SectionAlignment FileAlgnment SizeOfImage SizeOfHeaders PE file 이메모리에매핑될 RVA의기준이되는시작주소 ( 주로 exe : 0x00400000 dll : 0x10000000) 메모리에매핑된후의섹션의배치간격. 섹션이 PE로더에의해메 모리에올려질때항상이멤버의배수값으로위치. 섹션헤더의 VirtualAddress 멤버에영향을주게됨파일상에서의섹션의배치간격. SectionAlignment 와같은개념. 섹션헤더의 PointerToRawData 멤버에영향을주게됨로더가해당 PE 를메모리상에로드할때확보해야할충분한 Size PE 파일상에서의섹션의배치가메모리에매핑되면서달라질수 있기때문에보통 PE 파일의크기보다크다. 값은반드시 SectionAlignment 필드값이배수가되어야함 PE 포맷의모든헤더를더한값 FileAlignment 필드값의배수가되어야함 IMAGE_DATA_DIRECTORY 구조체의멤버 DataDirectory VirtualAddress : 데이터구조체의 RVA Size : VirtualAddress 에서참조된데이터구조체의크기 총 16 개의배열을가지고있음. 00 Export table 01 Import table 02 Resource 03 Exception 04 Security 05 Base relocation 06 Debug 07 Copyright string 08 Unknown 09 Thread local storage(tls) 10 Load configuration 11 Bound Import 12 Import Address Table 13 Delay Import 14 COM descriptor - 7 -
3. Section Table (IMAGE_SECTION_HEADER) :PE 헤더바로뒤에구조체배열형식으로위치해있음. 배열의개수는 FILE HEADER 의 NumberOfSection 멤버값에의해결정됨 주요필드 Name PhysicalAddress / VirtualSize VirtualAddress SizeOfRawData PointerToRawData Characteristics 섹션의네임을나타내는멤버 * 섹션의속성을파악하기위해선 Characteristics 멤버를참조해야함 PE로더에의해이미지가메모리에올려진후에해당섹션이얼마만큼의 크기를가지는지의정보. * 물리적주소정보아님 PE로더에의해이미지가메모리에올려진후에해당섹션이어느주소에 위치하는지의 RVA 주소값 *IMAGE_OPTIONAL_HEADER 의멤버인 SectionAlignment 의배수값을가짐 Raw Data 상에서해당섹션에실제사용된 Size * 섹션의영역은 FileAlignment 의영향을받음 정보 Raw Data가파일상의어느주소에위치해있는지나타내는변수 해당섹션에대한속성정보 flag 값을상수매크로로정의 IMAGE_SCN_CNT_CODE 0x00000020 : 코드로채워진섹션 IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 : 데이터가초기화된코드 IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 : 데이터가비초기화된섹션 IMAGE_SCN_MEM_EXECUTE 0x20000000 : 코드로서실행될수있는섹션 IMAGE_SCN_MEM_READ 0x40000000 : 읽기가능역역섹션 IMAGE_SCN_MEM_WRITE 0x80000000 : 쓰기가능영역섹션 - 8 -
4. IMPORT TABLE ( IMAGE_IMPORT_DESCRIPTOR ) : Import Section - 사용하고자하는익스포트함수들과그 DLL 에대한정보 를보관하고있는곳. 일반적으로섹션테이블에는.idata 라는이름으로지정됨 <import 정보를가진구조체의관계> 1) OptionalHeader 구조체의 DataDirectory 의주소를구한다. 2) DataDirectory 의두번째인덱스(import table) 에서 VirtualAddress 멤버값을 참조한다.(IMAGE_IMPORT_DESCRIPTOR 의위치) * 임포트섹션헤더의 VirtualAddress 필드와동일한값을가진다 3) IMAGE_IMPORT_DESCRIPTOR 의구조 - 9 -
주요필드 OriginalFirstThunk IMAGE_THUNK_DATA IMAGE_THUNK_DATA 는구조체를가리키는 구조체의배열을가리키는 배열의원소는 RVA 값을가짐 RVA 값 IMAGE_IMPORT_BY_NAME TimeDateStamp 바인딩되지않았을경우 0, 바인딩이후엔 -1 값을가짐 ForwarerChain DLL 포워딩과 TimeDateStamp 와관련있음 Name FirstThunk 이라 임포트된 DLL의이름을담고있는 NULL로끝나는 ASCII 문자열에대 한 RVA 값 IMAGE_THUNK_DATA 구조체의배열을가리키는 RVA *PE File 이가상주소공간에매핑되면 값 IMAGE_THUNK_DATA 는실 제해당주소의함수포인터를담게된다. 이배열을 Import Address Table(IAT) 이라고한다. 실제해당주소가 IAT 에설정되면이것을바 인딩되었다고한다. -*OriginalFirstThunk, FirstThunk? <OriginalFirstThunk, FirstThunk 가가리키는 IMAGE_THUNK_DATA 의구조> ForwarderString Function Ordinal AddressOfData *OriginalFirstThunk 는정확히이값을가리킨다. DLL 에서임포트할함수가포워딩된함수일경우를위한필드. * 포워딩된함수의경우익스포트섹션의익스포트함수포인터 내의엔트리값이실제함수진입점주소가아니라포워딩된대 상 DLL과실제함수를나타내는문자열에대한주소가됨 PE file이메모리에매핑된후에 IAT 에저장된함수포인터를직 접참조할때 Function 필드를통해서참조한다. 모듈정의파일을통해서구체적으로서수파일을지정하게되면 링커는 DLL 링크시함수명(symbol) 을통한링크가아니라서수 를통해서사용된함수를링크하게된다. 이때사용되는필드 IMAGE_THUNK_DATA 의값이 체의시작번지를가리킬때사용돼는필드 IMAGE_IMPORT_BY_NAME 구조 - 10 -
<IMAGE_IMPORT_BY_NAME 의구조> Hint : 서수를지정하지않은경우에임의로지정하는임포트된 함수에대한 0 으로시작하는인덱스. 차례대로 1 씩증가. 이값은필수요소가아니며어떤링커는이값을 만든다. Name[1] : NULL 0으로 로끝나는아스키문자열로이루어진해당 DLL 에사용된익스포트함수의이름을나타냄 OriginalFirstThunk -> INT (Import Name Table) & FirstThunk -> IAT (Import Address Table) - 메모리에매핑되기전 INT 와 IAT 는동일한위치를가리킴 - memory 에매핑이되면로더는 OriginalFirstThunk 가가리키는 INT 배열을 참조해서임포트할함수에대한정보를얻어오게되고, 포인터를획득하게됨 가상주소공간에서함수 - 이함수포인터는 IAT 에기록이되고이제 FirstThunk 는 IMAGE_THUNK_DATA 의 Function 필드를통해실제함수의함수포인터를 이용하여해당함수를호출하게됨 ( 이시점에서 INT 와 IAT 의값이달라짐) 5. Export Table (IMAGE_EXPORT_DIRECTORY) :PE File 이메모리에매핑될때필요한 DLL들을해당프로세스주소공간에 로드하고메인프로그램에서필요한함수를구하기위해로드된 하는곳 DLL에서참조 - 11 -
-PE 파일이 DLL에서 Export 함수를참조하는방식을정의할때, 보통 IMAGE_EXPORT_DIRECTORY 구조체의 AddressOfName 필드를참조하거 나, 함수의 Ordinal 을이미알고 AddressOfFunction 필드를참조하는두가지 방식을가짐 <IMAGE_EXPORT_DIREXTORY 구조체> 주요필드 Name 해당 DLL 의이름을나타내는아스키코드문자열의위치 Base 익스포트된함수들에대한서수의시작번호 RVA AddressOfFunction 배열에대한 index를얻기위해 ordinal 을 보정하는데사용되는값 *ordinal - base NumberOfFunctions AddressOfFunction 필드가가리키는 RVA의배열의원소개수 NumberOfNames AddressOfFunctions AddressOfNames AddressOfNameOridinals AddressOfNames 개수와 필드가가리키는 AddressOfNameOridinals 의원소의개수를나타냄. RVA의배열의원소의 * 실제익스포트된함수의정확한개수를나타냄 필드가가리키는서수배열 익스포트된함수들의함수포인트를가진배열을가리킴. RVA 익스포트된함수의심볼을나타내는문자열포인터배열을가 리키는 RVA 값 AddressOfName 배열에있는함수심볼과연관된 WORD 타입 의배열에대한포인터. 이 WORD 타입의값들은익스포트된 모든함수들의서수를담고있음. -Export 함수의주소를찾아가는과정 - 12 -
<Export Table 의구조> 1) 이름으로참조할때 Export Table에 NumberOfNames에서 Name 원소의개수를확인 AddressOfName 과 AddressOfNameOrdinals 을병렬적으로수행한후 AddressOfName에서 Name 이발견되면 AddressOfNameOrdinals에서이와 연결된값을구함. AddressOfNameOridinals 배열에서얻은값을 AddressOfFunction배열의 index 로사용. 그값이그함수의 RVA 2) Ordinal 로참조 Export Table에서 Base 값을구함. Base 값이서수의시작번호이므로 Oridinals에서 Base 값을빼면 AddressOfFunction 배열에대한 index를얻게 됨 AddressOfFunction 배열에서 index 값을이용해함수의 RVA 를구함 - 13 -
< 실행압축 (Packing) 이란> - 일반압축과같이여러파일을하나로묶어압축을수행하는것이아니라, 하나의실행파일을파일의형태를그대로유지하면서 Size 를줄여주는압축방식 - 확장자를그대로유지하면서파일의실행도전과같이이루어짐 - 패킹툴의종류, 버전과패킹방식이다양함 ASPack -> Alexey Solodovnikov Pack, UPX, ASprotect, NeoLite, Armadillo, Exeshield, Pecompect, PEncrypt, CryptFF, DBPE, telock, Stxe, PE_Patch.AvSpoof, Bat2Exe.BDTmp, Batlite, ExeStealth, JDPack, PECRC, PE_Patch.Elka, Pex, Pingvin, Mmpo, Embedded CAB, Morphine, Eagle, PE-Crypt.Negn, Bat2Exe, PCPEC, FlySFX, Exe2Dll, Teso, PE-Crypt.UC, Polyene, PE-Crypt.UC, PEBundle, CryptFF, DBPE, BitArts.Fusion, PE_Patch.Aklay, TapTrap, CryptZ, PE-Crypt.Moo, PE-Pack, RarSFX, XCR, ZipSFX, DoomPack, NDrop, PECrc32, DebugScript, PE_Patch.Ardurik, PE-Crypt.Wonk, PE_Patch.Upolyx, MEW, PE_Patch.ZiPack, ZiPack, CryptFF.b, MEW 등등... -최근발견되는악성코드들은대부분실행압축과파일보호기법으로악의적인코드를숨기고있음 *PE file 구조를이해해야악성코드분석을위한실행압축해제를수행할수있음 < 실행압축의기본적인원리> -packing 을수행하는프로그램이압축을수행할프로그램의실제 Code Data 를다른영역에압축, 저장한후프로그램의 Entry Point 를실행압축해제 루틴을먼저가리키게한후, 실행압축해제(Unpacking) 가먼저이루어진후에 프로그램이동작하는방식 및 - 14 -
Target File : putty-2938-rm2rob.exe <PEid 로본실행압축(ASPack 2.12->Alexey Solodovnikov) 된파일과원본파일의모습> * Entry Point 가다른위치의섹션을가리키고있습니다. -PE explorer 를사용하여두파일의차이점을분석 < 패킹된 putty-2938-rm2rob.exe 파일의헤더정보> - 15 -
< 원본 putty-2938-rm2rob.exe 파일의헤더정보> 변경된필드값 필드네임패킹된 putty 원본 putty NumberOfSection 0006h 0004h AddressOfEntryPoint 0046D001h 0044265Fh FileAlignment 00000200h 00001000h SizeOfImage 00071000h 0006D000h SizeOfHeaders 00000600h 00001000h : 변경되 Field 값으로패킹된이후새로운섹션이 2 개추가되었고, EntryPoint 의위치가변경되었으며, 있습니다. FileAlignment 수있습니다. 파일의사이즈와헤더의크기가변경된것을알수 값으로파일상에서의섹션의배치간격이줄어든걸알 ( 패킹된 putty) ( 원본 putty) - 16 -
변경된 DataDirectory 정보 Directory Name 패킹된 putty (VA / Size) 원본 putty (VA / Size) Import Table 0046DFACh / 00000214h 00460DD8h / 00002F28h Relocation Table 0046DF54h / 00000008h Load Configuration Table 00460D78h / 00000008h Import Address Table 0044B000h / 00000045h :IMAGE_OPTIONAL_HEADER에서참조하는 DataDirectory 의정보 패킹된 putty에서 Import Table 의위치와크기가변경되었고, Configuration Table 니다 과 IAT 정보가없어지고재배치정보가새로추가된것을확인할수있습 : IMAGE_SECTION_HEADER 구조체의 PointerToRawData 변수값이 600으로 변경되었고 ( FileAlignment 200 의배수값) 새로추가된.aspack 섹션에임포 트정보와재배치정보가병합되어있는것을알수있습니다. 변경사항이없습니다. 리소스테이블은 - 17 -
< 실행압축해제의기본원리> OEP(Original Entry Point) - PE file 찾기 의내부구조가바뀌어도결과적으로실행되는프로그램의기본구조 자체는변하지않습니다. 즉실행압축된프로그램이실행되어패킹루틴에 의해처리가된후에는압축되기전상태로돌아가게되며이때메모리 Dump 를수행하고 Entry Point 를이지점으로수정해주는것입니다. 1. PEiD plugiin 이용 : PEiD 의 generic OEP Finder 와같은플러그인을이용하여 OEP 주소를쉽 게찾을수있습니다. 그러나모든패킹프로그램의 OEP를찾아내지는못합니 다. <PEiD를이용하여 OEP 를찾아내는화면> 2. Ollydbg 로 MUP (Manual Unpacking) 수행 :Ollydbg 를사용하여 OEP 를찾아내는과정은패킹프로그램마다각자다른방식의패킹알고리즘이있고, 언패킹을방지하기위한트릭이나안티디버거가존재하기때문에다양한방법으로과정을수행할수있습니다. *SEH 예외처리이용 -대부분의패킹툴은디버깅을방지하기위해고의로예외처리를발생시키는코드를삽입합니다. Ollydbg 에서는예외처리옵션에서이와같은예외가발생하였 - 18 -
을때무시하도록설정을변경할수있습니다. Ollydbg 의 Shift + F9 키를이용하여 exception 이발생한코드를무시하고디 버깅을진행할수있습니다. 디버깅이진행되다프로그램이실행이되면 SEH 창 에서마지막 SE handler 를확인한예외처리루틴이위치한주소로이동합니다. 그리고예외처리루틴을 Reference 한지점을검색하여 OEP 에근접한곳을찾 아낼수있습니다. *Code Section 접근확인하기 Ollydbg에서 Alt + M 키를눌러 Memory map 구조를확인할수있습니다. 여기 서코드섹션에브레이크포인트를설정한후실행하면해당섹션의메모리에접 근하는순간을잡을수가있습니다. - 19 -
* BreakPoint Hardware.on access Dword PEtite 2.2 나 ASpack 2.12 이용하기 등으로패킹된프로그램을언패킹하면패킹루틴의 시작지점에 pushad 어셈블리명령어를볼수있습니다. 이명령어는모든레지 스터값을스택에저장하는명령어입니다. 주로압축이모두풀리면원래의레지 스터들을복원하기위해사용됩니다. 이런과정을이용해서 OEP 에접근할수가있습니다. pushad 명령어에서 F8을 한번눌러명령을실행하면레지스터값들이스택에저장이되고 ESP 스택포인 터는마지막으로삽입된그값들을가리키고있습니다. ESP 를 Follow in Dump 를실행하여 Hex dump 창에나타난값에 4 byte 를지정해준후에메뉴에서 BreakPoint Hardware.on access Dword 를선택한후실행을하게되면지정된 메모리에접근할때 break 가되고 popad 와같은레지스터를복원하는명령어 를볼수있습니다. 그후에 OEP 값을찾아낼수있습니다. - 20 -
IAT(Import Address Table) 복구하기 - 패킹프로그램마다경우가틀리지만대부분의패킹프로그램은 IAT 정보를변 경하거나사용할 Import function 들을에뮬레이션해주고 IAT엔쓰레기값을 넣는경우가발생하기때문에 지금은좋은툴이많아서자동으로 IAT 정보를재구성해주어야합니다. IAT 복구를수행해주고있습니다. 1. OllyDump 이용 Ollydbg에서 OEP 값을찾아낸후에 Ollydump 플러그인을이용하여바로메모리 덤프를수행할수있습니다. Rebuild Import 를체크하면자동으로 IAT 정보를수정하여덤프를수행합니다. 2. ImportREC ImportREC 이용 를실행하여디버깅을수행중인패킹된프로그램을 attach 한후찾 아낸 OEP 값을입력해주면자동으로 RVA 값과 Import Function 들을찾아줍니 다. - 21 -
Fix Dump를선택하여언패킹한프로그램에 IAT 를복구해주면됩니다. 참고문서, 사이트 Windows 시스템실행파일의구조와원리 ( 이호동저) OPEN REVERSE FORUMS https://ampm.ddns.co.kr/~reverse/phpbb/index.php BSW-Powered by vbulletion http://codediver.kaist.ac.kr/vbulletin/index.php PE Format 완전분석 ( 김경곤 (A.K.A. Anasra)) - 22 -