콘텐츠로 이동

리버싱 핵심원리 2부 1장 정리 1

Note

본 게시글의 내용은 리버싱 핵심원리를 보며 복습 겸 정리하였습니다.

책의 내용과 일부 상이할 수 있고, 이해를 돕기 위해 강좌 형식의 말투와 적절한(?) 예시를 추가하였습니다.

본 게시글에서 사용되는 소스 코드와 파일, 프로그램은 리버싱 핵심원리에서 제공하는 파일과 언급되는 것들을 기반으로 하며, 일부 상이할 수 있습니다.

PE 파일

PE 파일(Portable Executable File)은 Windows 운영체제에서 사용되는 실행 파일 형식을 말합니다. Windows 프로그램, 라이브러리(DLL), 드라이버 등이 PE 형식을 따르고 있습니다.

PE 파일이라 하면 32비트 형태를 의미하고 PE32라 부르기도 합니다. PE+ 또는 PE32+이라 하면 64비트 형태를 의미합니다. PE64는 아닙니다.

PE 파일에는 실행 가능한 형식인 EXE와 동적 링크 라이브러리(DLL), Windows 드라이버 파일(SYS) 등이 있습니다.

PE 파일 구조

PE HEADER
DOS Header
DOS Stub
NT Header
Section Header (.text)
Section Header (.data)
Section Header (.rsrc)
PE BODY
NULL Padding
Section (.text)
NULL Padding
Section (.data)
NULL Padding
Section (.rsrc)
NULL Padding

PE 파일의 구조는 대략 위와 같습니다.

PE 헤더의 끝 부분과 각 섹션의 끝 부분에는 널 패딩(NULL Padding)이라 불리는 공간이 있습니다. 이 공간은 아래에서 설명하겠지만 정렬(Alignment) 때문에 그렇습니다.

VA와 RVA

VA와 RVA는 실행 파일의 메모리 주소를 표현할 때 사용하는 개념입니다.

VA(Virtual Address)는 메모리 내에서 사용되는 전체 주소를 의미합니다. VA는 ImageBase + RVA로, 절대 주소라는 특징을 갖고 있습니다.

RVA(Relative Virtual Address)는 기준 주소(ImageBase)로부터의 상대 주소를 나타냅니다. RVA는 VA - ImageBase입니다.

RVA를 사용하는 이유

PE 파일은 기본적으로 ImageBase에 로드될 수 있도록 노력을 합니다. 하지만 상황에 따라 불가한 경우가 생기기도 합니다. 만약, RVA가 아닌 VA로 했다면 절대주소라는 특징을 갖기 때문에 모든 주소의 값을 일일이 변경해야 한다는 단점이 있습니다. 반면 RVA는 상대주소이기 때문에 어느 위치에 불러와지든 기반이 되는 주소에 RVA를 더하면 되기 때문에 RVA로 주소를 저장하고 관리합니다.

PE 헤더

DOS Header

winnt.h
typedef struct _IMAGE_DOS_HEADER {
  WORD e_magic;
  WORD e_cblp;
  WORD e_cp;
  WORD e_crlc;
  WORD e_cparhdr;
  WORD e_minalloc;
  WORD e_maxalloc;
  WORD e_ss;
  WORD e_sp;
  WORD e_csum;
  WORD e_ip;
  WORD e_cs;
  WORD e_lfarlc;
  WORD e_ovno;
  WORD e_res[4];
  WORD e_oemid;
  WORD e_oeminfo;
  WORD e_res2[10];
  WORD e_lfanew;
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

DOS Header의 구조는 위와 같습니다. DOS Header에서 가장 중요하게 봐야할 것은 e_magice_lfanew입니다.

e_magic은 DOS의 시그니쳐로 무조건 MZ(1)라는 값을 가집니다. e_lfanew는 NT Header의 오프셋을 나타냅니다. 이는 파일에 따라 가변적인 값을 가지며, 이 오프셋이 가르키는 곳에 NT Header가 있어야 합니다. NT Header의 구조체 이름은 IMAGE_NT_HEADERS입니다.

  1. Microsoft에서 DOS 실행 파일을 설계한 마크 주비코브스키(Mark Zbikowski)라는 사람의 이니셜

DOS Stub

Stub라는 단어를 보면 알 수 있듯이 DOS Stub는 MS-DOS 환경과의 호환성을 위해 남아있습니다.

기본적으로 PE 파일은 Windows 환경에서 작동하기 때문에 MS-DOS 환경에서 실행되지 않습니다. 만약을 위해(?) MS-DOS 환경에서 실행할 경우 DOS Stub에 작성된 코드가 실행됩니다. 대부분 PE 파일이 MS-DOS 환경에서 실행할 수 없다는 문구(This program cannot be run in DOS mode.)를 출력합니다.

현재와선 딱히 필요없는 영역이지만 레거시 호환을 위해 남아있습니다.

NT Header

winnt.h
1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS {
  DWORD Signature;
  IMAGE_FILE_HEADER FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Signature0x50450000("PE\0\0")이라는 값을 가지며, 헥스 에디터로 확인 시 PE라는 문자열 값이 나타납니다.

FileHeader는 PE 파일에 대한 기본적인 정보를 가지고 있습니다.

OptionalHeader는 PE 파일 실행을 위한 부가적인 정보를 가지고 있습니다.

FileHeader

winnt.h
1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_FILE_HEADER {
  WORD Machine;
  WORD NumberOfSections;
  DWORD TimeDateStamp;
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols;
  WORD SizeOfOptionalHeader;
  WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine
winnt.h
#define IMAGE_FILE_MACHINE_UNKNOWN      0
#define IMAGE_FILE_MACHINE_TARGET_HOST  0x0001
#define IMAGE_FILE_MACHINE_I386         0x014c
#define IMAGE_FILE_MACHINE_R3000        0x0162
#define IMAGE_FILE_MACHINE_R4000        0x0166
#define IMAGE_FILE_MACHINE_R10000       0x0168
#define IMAGE_FILE_MACHINE_WCEMIPSV2    0x0169
#define IMAGE_FILE_MACHINE_ALPHA        0x0184
#define IMAGE_FILE_MACHINE_SH3          0x01a2
#define IMAGE_FILE_MACHINE_SH3DSP       0x01a3
#define IMAGE_FILE_MACHINE_SH3E         0x01a4
#define IMAGE_FILE_MACHINE_SH4          0x01a6
#define IMAGE_FILE_MACHINE_SH5          0x01a8
#define IMAGE_FILE_MACHINE_ARM          0x01c0
#define IMAGE_FILE_MACHINE_THUMB        0x01c2
#define IMAGE_FILE_MACHINE_ARMNT        0x01c4
#define IMAGE_FILE_MACHINE_AM33         0x01d3
#define IMAGE_FILE_MACHINE_POWERPC      0x01f0
#define IMAGE_FILE_MACHINE_POWERPCFP    0x01f1
#define IMAGE_FILE_MACHINE_IA64         0x0200
#define IMAGE_FILE_MACHINE_MIPS16       0x0266
#define IMAGE_FILE_MACHINE_ALPHA64      0x0284
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU      0x0366
#define IMAGE_FILE_MACHINE_MIPSFPU16    0x0466
#define IMAGE_FILE_MACHINE_TRICORE      0x0520
#define IMAGE_FILE_MACHINE_CEF          0x0cef
#define IMAGE_FILE_MACHINE_EBC          0x0ebc
#define IMAGE_FILE_MACHINE_CHPE_X86     0x3a64
#define IMAGE_FILE_MACHINE_AMD64        0x8664
#define IMAGE_FILE_MACHINE_M32R         0x9041
#define IMAGE_FILE_MACHINE_ARM64EC      0xa641
#define IMAGE_FILE_MACHINE_ARM64X       0xa64e
#define IMAGE_FILE_MACHINE_ARM64        0xaa64
#define IMAGE_FILE_MACHINE_RISCV32      0x5032
#define IMAGE_FILE_MACHINE_RISCV64      0x5064
#define IMAGE_FILE_MACHINE_RISCV128     0x5128
#define IMAGE_FILE_MACHINE_CEE          0xc0ee

winnt.h 파일에 정의된 Machine 변수가 가질 수 있는 고유 CPU 아키텍쳐 값입니다. 32비트 아키텍쳐는 IMAGE_FILE_MACHINE_I386를, 64비트 아키텍쳐는 IMAGE_FILE_MACHINE_AMD64의 값을 가집니다.

NumberOfSections

NumberOfSections는 PE 파일에 포함된 섹션의 수입니다. 이 섹션의 수는 반드시 값이 1 이상이어야 합니다.

TimeDateStamp

TimeDateStamp는 파일이 생성된 날짜와 시간을 나타내는 타임스탬프입니다.

PointerToSymbolTable

PointerToSymbolTable는 심볼 테이블의 시작 위치를 나타냅니다.

NumberOfSymbols

NumberOfSymbols는 심볼의 수입니다.

SizeOfOptionalHeader

SizeOfOptionalHeader는 OptionalHeader 구조체의 크기를 나타냅니다. 32비트 형태의 파일의 경우 IMAGE_OPTIONAL_HEADER32 구조체를, 64비트 형태의 파일의 경우 IMAGE_OPTIONAL_HEADER64 구조체의 크기를 명시합니다.

Characteristics

Characteristics는 PE 파일의 속성과 특성을 나타냅니다. 운영체제가 이 파일을 어떻게 처리할 지 결정하는 데 중요한 역할을 하죠.

winnt.h
#define IMAGE_FILE_RELOCS_STRIPPED  0x0001 /* No relocation info */
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002
#define IMAGE_FILE_LINE_NUMS_STRIPPED   0x0004
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED  0x0008
#define IMAGE_FILE_AGGRESIVE_WS_TRIM    0x0010
#define IMAGE_FILE_LARGE_ADDRESS_AWARE  0x0020
#define IMAGE_FILE_16BIT_MACHINE    0x0040
#define IMAGE_FILE_BYTES_REVERSED_LO    0x0080
#define IMAGE_FILE_32BIT_MACHINE    0x0100
#define IMAGE_FILE_DEBUG_STRIPPED   0x0200
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP  0x0400
#define IMAGE_FILE_NET_RUN_FROM_SWAP    0x0800
#define IMAGE_FILE_SYSTEM       0x1000
#define IMAGE_FILE_DLL          0x2000
#define IMAGE_FILE_UP_SYSTEM_ONLY   0x4000
#define IMAGE_FILE_BYTES_REVERSED_HI    0x8000

Characteristics는 위와 같은 값을 비트 마스크를 통해 특성을 여러 개 설정합니다.

IMAGE_FILE_DLL 플래그가 설정되면 PE 파일은 DLL로 취급되고, IMAGE_FILE_32BIT_MACHINE 플래그가 설정되면 32비트 시스템에서 실행할 수 있는 것으로 취급됩니다.

OptionalHeader

winnt.h
typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;
  DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16

// 32Bit
typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

// 64Bit
typedef struct _IMAGE_OPTIONAL_HEADER64 {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  ULONGLONG            ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  ULONGLONG            SizeOfStackReserve;
  ULONGLONG            SizeOfStackCommit;
  ULONGLONG            SizeOfHeapReserve;
  ULONGLONG            SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
Magic

PE 파일의 형식을 나타내는 값으로, 32비트는 0x10B의 값을 가지고 64비트는 0x20B의 값을 가집니다. 0x107이라는 ROM 이미지 값을 갖는 경우도 있습니다.

MajorLinkerVersion와 MinorLinkerVersion

메이저와 마이너 링커 버전을 나타냅니다.

SizeOfCode

.text 섹션에 작성된 실행 코드의 총 크기입니다.

SizeOfInitializedData

초기화된 데이터가 작성된 섹션(.data, .rdata 등)의 총 크기입니다.

SizeOfUninitializedData

미초기화된 데이터가 작성된 섹션(.bss)의 총 크기입니다.

AddressOfEntryPoint

실행 파일의 진입점(EntryPoint) 주소로, 주소는 RVA입니다.

BaseOfCode

.text 섹션의 시작 주소(RVA)입니다.

BaseOfData

.data 섹션의 시작 주소(RVA)입니다. PE32+에선 이 필드가 존재하지 않습니다.

ImageBase

파일이 메모리에 로드될 때의 기본 주소입니다. 실행 파일은 이 주소를 기준으로 불러오는데, 만약 ASLR 기법이 적용된 경우 이 주소가 아닐 수도 있습니다.

SectionAlignment

메모리 내 섹션의 정렬 단위입니다. 기본적으로 4096 크기를 가집니다.

FileAlignment

파일 내 섹션의 정렬 단위입니다. 기본적으로 5121024의 값을 가집니다.

MajorOperatingSystemVersion와 MinorOperatingSystemVersion

실행 파일을 실행하기 위해 요구되는 Windows 운영체제의 버전을 나타냅니다.

SizeOfImage

PE 파일이 메모리에 로드되었을 때 차지하는 크기를 의미합니다. 모든 섹션의 크기를 포함합니다.

SizeOfHeaders

모든 헤더(DOS Header, PE Header 등)의 총 크기입니다.

Subsystem

실행 파일이 어떤 환경에서 동작할 수 있는 지를 나타냅니다. 어떤 유형의 파일인지 구분하는 용도로 사용할 수 있습니다.

1은 드라이버, 2는 GUI, 3은 콘솔 프로그램을 의미합니다.

DllCharacteristics

DLL 동작 특성을 나타내는 플래그입니다.

SizeOfStackReserve와 SizeOfStackCommit

스택을 위해 예약된 메모리의 크기와 실제로 할당된 메모리의 크기를 나타냅니다.

SizeOfHeapReserve와 SizeOfHeapCommit

힙을 위해 예약된 메모리의 크기와 실제로 할당된 메모리의 크기를 나타냅니다.

LoaderFlags

사실상 사용되지 않는 필드라 0의 값을 가집니다.

NumberOfRvaAndSizes

DataDirectory의 수를 나타냅니다. 보통 16입니다.

DataDirectory

DataDirectory는 실행 파일이나 DLL이 메모리에 로드될 때 필요한 추가 정보를 제공합니다. DataDirectory 배열은 각각 아래의 특정 테이블을 의미합니다.

인덱스 이름 설명
0 Export Table 함수 및 변수의 내보내기 테이블
1 Import Table DLL 및 함수의 가져오기 테이블
2 Resource Table 리소스 테이블
3 Exception Table 예외 처리 정보 테이블
4 Certificate Table 코드 서명 인증서 데이터 테이블
5 Base Relocation Table 재배치 테이블
6 Debug Directory 디버그 정보 테이블
7 Architecture Specific Data CPU 아키텍쳐 데이터 테이블
8 Global Pointer Table(TLS) TLS 테이블
9 Load Configuration Table 로드 설정 테이블
10 Bound Import Table 바인드된 가져오기 테이블
11 Import Address Table(IAT) 가져온 함수의 주소 테이블
12 Delay Import Descriptor Table 지연 로드된 가져오기 테이블
13 CLR Runtime Table 닷넷 실행 파일의 CLR 런타임 헤더 테이블
14 Reserved 예약 또는 사용되지 않음
15 Reserved 예약 또는 사용되지 않음

Section Header

PE 파일에서 프로그램의 실제 데이터를 저장하는 섹션(Section)에 대한 정보가 담겨있는 구조체입니다. 각 섹션은 코드(code), 데이터(data), 리소스(resource) 등 다양한 목적으로 구분되어 사용됩니다.

PE 파일에서 흔히 볼 수 있는 섹션은 아래 표와 같습니다:

이름 설명
.text 실행 코드 섹션
.data 읽기 및 쓰기 가능한 전역 데이터 및 변수 섹션
.rdata 읽기 전용 데이터 섹션 (상수, 문자열 등)
.bss 초기화되지 않은 데이터 섹션 (런타임 시 초기화 등)
.rsrc 리소스 섹션(아이콘, 이미지 등)
.reloc 재배치 정보 섹션
.debug 디버깅 정보 섹션
winnt.h
typedef struct _IMAGE_SECTION_HEADER {
  BYTE  Name[8];
  union {
      DWORD PhysicalAddress;
      DWORD VirtualSize;
  } Misc;
  DWORD VirtualAddress;
  DWORD SizeOfRawData;
  DWORD PointerToRawData;
  DWORD PointerToRelocations;
  DWORD PointerToLinenumbers;
  WORD  NumberOfRelocations;
  WORD  NumberOfLinenumbers;
  DWORD Characteristics;
} IMAGE_SECTION_HEADER;
필드 이름 설명
Name 섹션의 이름
VirtualSize 섹션이 메모리에 로드되었을 때 차지하는 크기
VirtualAddress 섹션의 상대 가상 주소(RVA), 메모리에서 섹션이 로드될 때의 위치
SizeOfRawData 파일에서 섹션이 실제로 차지하는 크기
PointerToRawData 파일에서 섹션이 시작되는 오프셋
Characteristics 섹션의 속성 플래그

위 표에서 언급되지 않은 필드는 현재는 잘 사용되지 않는 것들입니다.

VirtualSizeSizeOfRawData의 크기는 서로 다른 값을 가질 수 있기 때문에 주의하시기 바랍니다.

Characteristics의 속성 플래그

winnt.h
/* #define IMAGE_SCN_TYPE_REG           0x00000000 - Reserved */
/* #define IMAGE_SCN_TYPE_DSECT         0x00000001 - Reserved */
/* #define IMAGE_SCN_TYPE_NOLOAD        0x00000002 - Reserved */
/* #define IMAGE_SCN_TYPE_GROUP         0x00000004 - Reserved */
#define IMAGE_SCN_TYPE_NO_PAD           0x00000008 /* Reserved */
/* #define IMAGE_SCN_TYPE_COPY          0x00000010 - Reserved */

#define IMAGE_SCN_CNT_CODE          0x00000020
#define IMAGE_SCN_CNT_INITIALIZED_DATA      0x00000040
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA    0x00000080

#define IMAGE_SCN_LNK_OTHER         0x00000100
#define IMAGE_SCN_LNK_INFO          0x00000200
/* #define  IMAGE_SCN_TYPE_OVER     0x00000400 - Reserved */
#define IMAGE_SCN_LNK_REMOVE            0x00000800
#define IMAGE_SCN_LNK_COMDAT            0x00001000

/*                      0x00002000 - Reserved */
/* #define IMAGE_SCN_MEM_PROTECTED      0x00004000 - Obsolete */
#define IMAGE_SCN_MEM_FARDATA           0x00008000

/* #define IMAGE_SCN_MEM_SYSHEAP        0x00010000 - Obsolete */
#define IMAGE_SCN_MEM_PURGEABLE         0x00020000
#define IMAGE_SCN_MEM_16BIT         0x00020000
#define IMAGE_SCN_MEM_LOCKED            0x00040000
#define IMAGE_SCN_MEM_PRELOAD           0x00080000

#define IMAGE_SCN_ALIGN_1BYTES          0x00100000
#define IMAGE_SCN_ALIGN_2BYTES          0x00200000
#define IMAGE_SCN_ALIGN_4BYTES          0x00300000
#define IMAGE_SCN_ALIGN_8BYTES          0x00400000
#define IMAGE_SCN_ALIGN_16BYTES         0x00500000  /* Default */
#define IMAGE_SCN_ALIGN_32BYTES         0x00600000
#define IMAGE_SCN_ALIGN_64BYTES         0x00700000
#define IMAGE_SCN_ALIGN_128BYTES        0x00800000
#define IMAGE_SCN_ALIGN_256BYTES        0x00900000
#define IMAGE_SCN_ALIGN_512BYTES        0x00A00000
#define IMAGE_SCN_ALIGN_1024BYTES       0x00B00000
#define IMAGE_SCN_ALIGN_2048BYTES       0x00C00000
#define IMAGE_SCN_ALIGN_4096BYTES       0x00D00000
#define IMAGE_SCN_ALIGN_8192BYTES       0x00E00000
/*                      0x00F00000 - Unused */
#define IMAGE_SCN_ALIGN_MASK            0x00F00000

#define IMAGE_SCN_LNK_NRELOC_OVFL       0x01000000


#define IMAGE_SCN_MEM_DISCARDABLE       0x02000000
#define IMAGE_SCN_MEM_NOT_CACHED        0x04000000
#define IMAGE_SCN_MEM_NOT_PAGED         0x08000000
#define IMAGE_SCN_MEM_SHARED            0x10000000
#define IMAGE_SCN_MEM_EXECUTE           0x20000000
#define IMAGE_SCN_MEM_READ          0x40000000
#define IMAGE_SCN_MEM_WRITE         0x80000000

IMAGE_SCN_CNT_CODE, IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_CNT_UNINITIALIZED_DATA, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE 속성이 주로 이용됩니다.

RVA to RAW

RVA를 Raw Offset(파일 오프셋)으로 변환하는 것을 RVA to RAW라 합니다. 헷갈릴 수 있으니 용어의 정의를 다시 짚고 넘어갑시다.

  • RVA(Relative Virtual Address): PE 파일이 메모리에 로드되었을 때의 상대 가상 주소. 기준은 ImageBase.
  • Raw Offset: PE 파일이 저장 장치에 저장된 상태에서의 파일 오프셋. 파일의 시작 부분을 기준으로 하는 절대 위치.

RVA를 RAW로 변환하는 방법

  1. RVA가 속해있는 섹션을 찾는다. 섹션 헤더의 VirtualAddressVirtualSize를 이용해 어느 섹션 범위에 속하는 지 확인하면 된다.
  2. RAW 계산식을 통해 산출한다.

Raw Offset = PointerToRawData + (RVA - VirtualAddress)

위의 식을 통해 RVA를 RAW로 변환할 수 있습니다.

예시 1

RVA는 0x1200이고 각 섹션 헤더의 정보는 아래와 같을 때 RAW를 구하시오.

섹션 VirtualAddress VirtualSize PointerToRawData
섹션 1 0x1000 0x200 0x400
섹션 2 0x2000 0x300 0x800

섹션 1과 섹션 2의 범위는 VirtualAddress + VirtualSize - 1로 알아낼 수 있습니다. 섹션 1은 0x1000 ~ 0x11FF, 섹션 2는 0x2000 ~ 0x22FF 범위를 가집니다. 주어진 RVA는 0x1200으로 섹션 1의 범위에 속합니다.

Raw Offset = PointerToRawData + (RVA - VirtualAddress)

RAW는 위 식을 통해 구할 수 있고, 0x400 + (0x1200 - 0x1000)으로 0x600이라는 값이 산출됩니다. 즉, RAW는 0x600입니다.

RAW는 SizeOfRawData를 넘길 수 없다

VirtualSizeSizeOfRawData의 값이 서로 달라 발생할 수 있는 문제입니다.

예를 들어 아래 표와 같은 경우라 생각해봅시다.

섹션명 VirtualAddress VirtualSize PointerToRawData SizeOfRawData
섹션 1 0x1000 0x500 0x400 0x200

주어진 RVA가 0x1400일 때 RAW를 구하면 0x600이 산출됩니다. RVA는 메모리 내 섹션 범위에 해당하기 때문에 문제 없지만 이를 RAW로 변환한 값은 SizeOfRawData의 값보다 큽니다. 즉, 파일 내 유효하지 않은(존재하지 않는) 주소란거죠. 이런 경우 RAW를 정의할 수 없습니다.