[리버싱] : PE Header - ②
#REVERSING너무 중요한 IMAGE_OPTIONAL_HEADER 구조체 알아보기
IMAGE_OPTIONAL_HEADER
PE 구조체 중 가장 중요하다고 판단되는 IMAGE_OPTIONAL_HEADER
구조체이다. 코드는 아래와 같다.
너무 많은 멤버 중에서 중요한 멤버만 추려보자.
Magic
이미지 파일의 상태이다. 일종의 표식이라고 생각하면 되고 32비트
의 경우 0x10b
가 64비트
의 경우 0x20b
가 들어온다.
SizeOfCode
코드 양의 전체 크기를 가리킨다. 실제 개발자가 코딩한 양이 어느 정도인지 여기서 확인 가능하다.
MajorLinkerVersion / MinorLinkerVersion
어떤 버전의 컴파일러로 빌드했는지 알려주는 멤버이다.
ImageBase
해당 파일이 실행될 때 가상메모리에 로드될 번지를 가리킨다. 32비트
의 경우 DLL
의 ImageBase
는 0x10000000
이고 EXE
는 0x400000
이고
64비트
의 경우 DLL
과 EXE
의 ImageBase는 각각 0x180000000
, 0x140000000
이다.
AddressOfEntryPoint
실제 파일이 메모리에서 실행되는 시작 지점. 즉, Entry Point
를 말한다.
IDA
등 디버거를 통해 파일을 열었을 때 AddressOfEntryPoint
의 값과 ImageBase
의 값을 더한 주소에 처음 위치 시켜준다.
GifCam이라는 프로그램을 PEview로 열었을 때 사진이 위에 있다. ImageBase
는 0x400000
, AddressOfEntryPoint
의 값이 0x155538
이라고 적혀있다.
IDA로 GifCam 프로그램을 열면 ImageBase
와 AddressOfEntryPoint
를 더한 값인 0x555538
에 처음 위치로 잡아주는 것을 아래와 같이 확인 가능하다.
BaseOfCode
실제 코드가 실행되는 번지이다. ImageBase
는 PE 파일 전체에 대한 시작 주소이고 코드 영역이 시작되는 부분은 ImageBase
와 BaseOfCode
값을 더한 부분부터 시작된다.
웬만해선 BaseOfCode
의 값은 0x1000
으로 설정되어 있지만 컴파일로 변경할 수도 있다.
SectionAlignment / FileAlignment
각 섹션을 정렬하기 위한 저장 단위라고 생각할 수 있다. 보통은 0x1000
으로 값이 채워져 있다. 이는 0x1000 단위로 섹션을 구분한다는 의미이다.
예를 들어 .text 섹션이 존재하고 그 다음에는 .rdata 섹션이 있다고 하자. .text 섹션의 크기가 0x800 바이트 정도라면 800바이트 이후에 바로 .rdata 섹션이 등장하는 것이 아닌 0x1000에서 0x800을 뺀 0x200은 0으로 채우고 0x1000 이후부터 .rdata 섹션이 등장하는 방식이다.
FileAlignment
는 파일상의 간격, SectionAlignment
는 메모리에 올라갔을 때의 간격이라고 생각하면 된다.
SizeOfImage
EXE나 DLL이 메모리에 올라갔을 때의 전체 크기라고 생각하면 된다. 로더가 PE를 메모리에 올릴 때 이 SizeOfImage
의 값을 보고 공간을 확보한다.
보통 파일로 존재할 때와 실제 메모리에 올라갔을 때의 크기가 같은 경우도 있겠지만 대부분 다를 때가 더 많으므로, 특히 섹션의 위치가 각각 다른 위치에 매핑되면서 메모리에 올라간 PE의 크기가 더 큰 경우가 많다.
따라서 SizeOfImage
의 값은 항상 SectionAlignment
값의 배수가 된다.
SizeOfHeaders
PE 헤더의 크기를 알려주는 필드이다. 보통 0x1000
의 값을 가지며 이럴 경우 ImageBase
의 값만 더하면 돼서 간편하지만
0x400
등의 경우(위 GifCam의 SizeOfHeaders의 값이 0x400이다.)
메모리 위치와 파일 위치가 단순 더해서만 되는 것이 아니라서 이 경우 직관적으로 번지를 계산할 수 없고 계산기등의 도움이 필요하다.
Subsystem
이 프로그램이 콘솔인지 GUI인지 확인할 때 이용할 수 있다.
- 0x2 : Windows GUI 하위 시스템
- 0x3 : Windows CUI 하위 시스템
DataDirectory
데이터 디렉터리의 첫 번째 IMAGE_DATA_DIRECTORY
구조체에 대한 포인터이다.
VirtualAddress
와 Size
라는 필드가 포함되어 있다. Export 디렉터리나 Import 디렉터리, Resource 디렉터리, IAT 등 각각의 가상 주소와 크기를 이 필드를 통해 알 수 있다.
총 배열의 크기는 16개로 원소는 아래와 같다.
붉은색 글씨의 원소는 다른 원소에 비해 중요한 원소이다.
- IMAGE_DIRECTORY_ENTRY_ARCHITECTURE(7) : 아키텍처별 데이터
- IMAGE_DIRECTORY_ENTRY_BASERELOC(5) : 기본 재배치 테이블
- IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT(11) : 바인딩된 가져오기 디렉터리
- IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR(14) : COM 설명자 테이블
- IMAGE_DIRECTORY_ENTRY_DEBUG(6) : 디버그 디렉터리
- IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT(13) : 테이블 가져오기 지연
- IMAGE_DIRECTORY_ENTRY_EXCEPTION(3) : 예외 디렉터리
IMAGE_DIRECTORY_ENTRY_EXPORT(0)
: 디렉터리 내보내기- IMAGE_DIRECTORY_ENTRY_GLOBALPTR(8) : 전역 포인터의 상대 가상 주소
IMAGE_DIRECTORY_ENTRY_IAT(12)
: 주소 테이블 가져오기IMAGE_DIRECTORY_ENTRY_IMPORT(1)
: 디렉터리 가져오기- IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG(10) : 구성 디렉터리 로드
IMAGE_DIRECTORY_ENTRY_RESOURCE(2)
: 리소스 디렉터리- IMAGE_DIRECTORY_ENTRY_SECURITY(4) : 보안 디렉터리
IMAGE_DIRECTORY_ENTRY_TLS(9)
: 스레드 로컬 스토리지 디렉터리
위 사진을 보면 RVA(Relative Virtual Address)
가 나오는데 상대 주소라고 생각하면 된다.
즉, 실제 주소가 0x403000이고 베이스 주소가 0x400000이라면, RVA는 0x3000이다.
실제로 계산 해보자.
위 사진에서 IAT 테이블
의 RVA
값은 0x324A84이다.
베이스 주소(0x400000) + RVA(0x324A84) = 0x724A84이다.
IDA에서 0x724A84 주소게 가보자. Import oleaut32.dll로 시작하는 것으로 보아 IAT가 시작되는 부분임을 알 수 있다.
RVA는 이처럼 베이스 주소를 기준으로 얼마만큼 떨어져 있는지 알려준다.