악성코드 분석/파일 포맷

[악성 코드 분석] PE (Portable Executable) 기초

IT공부 2019. 12. 2. 23:38
반응형

PE포맷 이란?

 

윈도 운영체제에서 사용되는 실행파일(exe), DLL Object코드, FON 폰트 파일 등을 위한 파일 형식입니다. 

OE파일은 윈도우 로더가 실행 가능한 코드를 관리하는데 필요한 정보를 캡슐화한 데이터 구조체입니다. 

 

PE(Portable Executable) 말 그대로 옮겨다니면서 실행 가능한 파일

MS에서 다른 운영체제와 이식성을 좋게 하게위해 만든 파일 포맷 방식

 

PE 파일의 종류

실행, 드라이버, 라이브러리, 오브젝트 총 4개의 계열이 있습니다. 

  • 실행 계열          : EXE, SCR
  • 드라이버 계열    : SYS, VXD
  • 라이브러리 계열 : DLL, OCX, CPL, DRV
  • 오브젝트 계열    : OBJ

 

PE 파일의 기본 구조 

메모리에 적재(loading 또는 mapping) 될 때의 모습 PE File Format 

PE(Portable Excutable) 파일  - Windows 운영체제에서 사용되는 실행 파일 형식 

종류 

   - 실행 계열 : exe, scr
   - 라이브러리 계열 : dll, ocx, cpl, drv
   - 드라이버 계열 : sys, vxd
   - 오브젝트 파일 계열 : obj 

구조   
  PE Header : DOS Header ~ Section Header
  PE Body : PE헤더 이후 Section들 

파일을 실행하기 위한 모든 정보는 구조체 형식으로 PE헤더에 저장되어 있으며, 
섹션 헤더에 각 섹션에 대한 크기/위치/속성이 정의되어 있고, 
파일의 내용은 코드(. text), 데이터(. data), 리소스(. rsrc) 섹션에 나누어 저장한다. 

파일에서는 offset으로 메모리에서 VA로 위치 표현 
VA(Virtual Address)는 프로세스 가상 메모리의 절대 주소를 말하고 
RVA(Relatve Virtual Address)는 어느 기준 위치(Imagebase)에서부터의 상대 주소를 말한다. 
(DLL Relocation이 일어날 경우를 대비해, 절대 주소 대신 상대 주소를 사용한다.)

RAV + ImageBase = VA


1.PE Header 

1) DOS Header - DOS 파일에 대한 하위 호환성을 고려해서 만듦 
IMAGE_DOS_HEADER 고조체의 크기는 40 
구조체 멤버 중..
    e_magic : DOS Signature ( 4 D5 A -> ASCII값 'MZ')
    e_lfanew : NT 헤더의 오프셋을 표시 



2) DOS Stub - DOS 환경에서 실행되는 코드를 가진 영역, 일종의 옵션임(없어도 실행에 문제가 없으므로)

 코드와 데이터의 혼합으로 이루어짐


3) NT Header

IMAGE_NT_HEADERS 구조체 - 3개의 멤버로 이루어짐 
   Signature : 표식, 일반적으로 50450000 h("PE" 00)을 가짐
   FileHeader 구조체 
   Optional Header 구조체 

FileHaeder 구조체 (IMAGE_FILE_HEADER)

구조체 멤버 중..
    Machine : CPU별로 고유한 값
  NumberOfSection : 섹션의 개수 
  SizeOfOptionalHeader : IMAGE_OPTIONAL_HEADER32 구조체의 크기 
  Characteristics: 파일의 속성(실행 가능?, 시스템 파일? dll파일? 등)

 



Optional Header 구조체( IMAGE_OPTIONAL_HEADER32 )

  구조체 멤버 중..
  Magic : 32bit 형태는 10B, 64bit 형태는 20B 값을 가짐
  AddressOfEntryPoint : EntryPoint의 RVA 값을 가짐
  ImageBase : PE 파일이 로딩되는 시작 주소
  SectionAlignment : 섹션의 최소 단위
  FileAlignment : 파일의 최소 단위
  SizeOfImage : PE파일이 메모리에 로딩되었을 때 가상 메모리에서 PE 이미지 크기
  SizeOfHeader : PE헤더의 전체 크기
  Subsystem : 파일의 종류 구분(드라이버 파일? or CUI 파일 or GUI 파일)
  NumberOfRvaAndSizes : DataDirectory 배열의 개수
  DataDirectory : 구조체의 배열로 배열의 각 항목마다 정의된 값을 가짐



4) Section Header - 각 섹션의 속성을 정의함 

파일/메모리 에서의 시작 위치, 크기, 접근 권한 등 

code 섹션 - 실행, 읽기 권한 
data 섹션 - 비실행, 읽기, 쓰기 권한
resource 섹션 - 비실행, 읽기 권한 

IMAGE_SECTION_HEADER 구조체

   구조체 멤버 중..
  VirtualSize : 메모리에서 섹션 크기
  VirtualAddress : 메모리에서 섹션의 시작 주소(RVA)
          SizeOfRawData : 파일에서 섹션 크기
          PointerToRawData : 파일에서 섹션 시작 위치
          Characteristics : 섹션의 속성
          Name : 섹션 이름

 





RVA to RAW 계산

PE 파일이 메모리에 로딩되었을 때 각 섹션에서 메모리의 주소(RVA)와 파일 옵셋을 매핑하는 방법

 
1) RVA가 속한 섹션을 찾는다.
2) 간단한 비례식을 통해 파일 옵셋(RAW) 계산

 

RAW-PointerToRawData = RVA - VirtualAddress 이므로

RAW = RVA - VirtualAddress + PointerToRawData 가 됩니다.

파일의 오프셋 = 메모리 상대 주소 - 메모리의 해당 섹션 시작 위치 + 파일의 해당 섹션 시작 위치


 

DLL

메모리 낭비를 피하기 위하여,
프로그램에 라이브러리를 포함시키지 말고, 별도의 파일(DLL)을 구성하여 필요할 때마다 불러 쓴다.
한 번 로딩된 DDLL의 코드, 리소스는 Memory Mapping 기술로 여러 프로세스에서 공유해 사용

로딩 방식

- Explicit Linking : 프로그램에서 사용되는 순간에 로딩, 사용 후 해제
- Implicit Linking : 프로그램 시작 시 로딩, 종료 시 해제

 


 

IAT ( Import Address Table )

프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지 나타낸 테이블
DLL의 Implicit Linking 방식에 대한 메커니즘을 제공한다.

 


1) IMAGE_IMPORT_DESCRIPTOR (IID)

  구조체 배열 중..
  OriginalFirstThunk : INT(Import Name Table)의 주소(RVA)
  Name : 라이브러리 이름 문자열의 주소(RVA)
  FirstThunk : IAT의 주소(RVA)
  INT와 IAT는 long 타입의 4바이트 자료형 배열
  INT의 각 원소 값은 IMAGE_IMPORT_BY_NAME 구조체 포인터

 

2) IAT 입력 순서

 1. IID의 Name 멤버를 읽어 라이브러리 이름 문자열을 찾는다.
 2. 해당 라이브러리 로딩 -> LoadLibrary(“이름”)
 3. IID의 OriginalFirstThunk 멤버를 읽어 INT의 주소를 찾는다.
 4. INT의 배열에서 하나씩 값을 읽어, IMAGE_IMPORT_BY_NAME의 주소를 찾고
 5. IMAGE_IMPORT_BY_NAME의 ordinal, Name 항목을 통해 해당 함수의 주소를 찾는다.
 6. IID의 FirstThunk를 통해 IAT 주소를 찾고
 7. 해당 IAT 배열에 찾은 함수의 주소 입력
 8. 4~7 과정 반복

  Ordinal 이란, 함수의 고유 번호를 나타내는 2바이트 값

 

 



 

 

EAT( Export Address Table )

라이브러리 파일에서 제공하는 함수를 다른 프로그램에서 가져다 사용할 수 있도록 하는 메커니즘

 

1) IMAGE_EXPORT_DESCRIPTOR

  구조체 배열 중..   
  NumberOfFunctions : 실제 Export 함수 개수
  NumberOfNames : Export 함수 중 이름을 가진 함수 개수
  AddressOfFunctions Export : 함수 주소 배열
  AddressOfNames : 함수 이름 주소 배열
  AddressOfNameOrdinals : Ordinal 주소 배열

 

2) GetProcAddress()

라이브러리에서 함수 주소를 얻는 API

 동작 원리

 1. AddressOfNames 멤버를 이용해 ‘함수 이름 배열’로 감
 2. ‘함수 이름 배열’에 저장된 문자열 주소들을 문자열 비교하여 원하는 함수를 찾는다. -> index
 3. AddressOfNameOrdinals 멤버를 이용해 ‘Ordinal 배열’로 감
 4. ‘Ordinal 배열’에서 index를 이용해 해당 ordinal 값을 찾음
 5. AddressOfFunctions 멤버를 이용해 ‘함수 주소 배열’ (EAT)로 감
 6. EAT에서 아까 구한 ordinal을 배열 인덱스로 원하는 함수의 시작 주소를 얻는다.

 이름 없이 ordinal만으로 함수의 주소를 찾을 땐
 
 (Ordinal - IMAGE_EXPORT_DIRECTOR.BASE 멤버 )를 EAT의 인덱스로 함