logo

포터블 실행 파일 (PE) 형식

첫 번째로 알아야 할 것은 PE 포맷입니다. 이 포맷에 대한 지식과 이해는 Windows 플랫폼에 대한 안티바이러스 엔진을 개발하는 데 필수적입니다 (역사적으로 세계의 대부분의 바이러스가 Windows를 타깃으로 합니다).

포터블 실행 파일(PE) 형식은 Windows 운영 체제에서 실행 파일, 예를 들어 .EXE 및 .DLL 파일을 저장하는 데 사용되는 파일 형식입니다. 이 형식은 1993년 Windows NT 출시와 함께 도입되었으며, 이후 Windows 시스템에서 실행 파일의 표준 형식이 되었습니다.

PE 포맷이 도입되기 전에, Windows는 16비트 프로그램용 New Executable (NE) 포맷과 32비트 프로그램용 Compact Executable (CE) 포맷을 포함하여 실행 파일들을 위한 여러 가지 다른 포맷을 사용했습니다. 이러한 포맷들은 각기 독특한 규칙과 관례를 가지고 있었기 때문에 운영 체제가 프로그램을 신뢰성 있게 로드하고 실행하는 것이 어려웠습니다.

실행 파일의 레이아웃과 구조를 표준화하기 위해 Microsoft는 Windows NT 출시와 함께 PE 포맷을 도입했습니다. PE 포맷은 32비트 및 64비트 프로그램 모두에 대한 공통 포맷으로 설계되었습니다.

PE 형식의 주요 특징 중 하나는 표준화된 헤더를 사용하는 것으로, 이는 파일의 시작 부분에 위치하며 실행 파일에 대한 중요 정보를 운영 체제에 제공하는 여러 필드를 포함하고 있습니다. 이 헤더에는 IMAGE_DOS_HEADERIMAGE_NT_HEADER 구조가 포함되어 있으며, 이는 IMAGE_FILE_HEADERIMAGE_OPTIONAL_HEADER의 두 주요 섹션으로 나뉩니다.

PE-format 헤더의 대부분은 헤더 파일 WinNT.h에 선언되어 있습니다.

 

IMAGE_DOS_HEADER

IMAGE_DOS_HEADER 구조는 MS-DOS와의 역호환성을 지원하기 위해 사용되는 레거시 헤더입니다. 이는 MS-DOS에 필요한 파일에 대한 정보, 예를 들어 파일 내에서 프로그램의 코드와 데이터 위치, 프로그램의 진입점 등을 저장하는 데 사용됩니다. 이는 MS-DOS용으로 작성된 프로그램들이 PE 파일로 컴파일되었다면 Windows NT에서 실행될 수 있게 해주었습니다.

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];
    DWORD e_lfanew; // offset of IMAGE_NT_HEADER

} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

우리에게 다음과 같은 흥미로운 분야가 있습니다:

  • e_magic 필드는 파일을 유효한 PE 파일로 식별하는 데 사용됩니다. 보시다시피 e_magic 필드는 파일의 "매직 넘버"를 지정하는 16비트 부호 없는 정수입니다. 매직 넘버는 파일을 유효한 PE 파일로 식별하는 특수 값입니다. 이는 0x5A4D(16진수)로 설정되며, 이는 문자 "MZ"의 ASCII 표현입니다(IMAGE_DOS_SIGNATURE).

  • e_lfanew 필드는 PE 파일의 레이아웃과 특성에 대한 정보를 포함하는 IMAGE_NT_HEADERS 구조의 위치를 지정하는 데 사용됩니다. 보시다시피 e_lfanew 필드는 파일 내에서 IMAGE_NT_HEADERS 구조의 위치를 지정하는 32비트 부호 있는 정수입니다. 일반적으로 파일 시작 부분에 대한 구조의 오프셋으로 설정됩니다.

 

역사

1980년대 초, 마이크로소프트는 개인용 컴퓨터를 위해 간단하고 가벼운 운영 체제인 MS-DOS를 개발하고 있었습니다. MS-DOS의 주요 특징 중 하나는 실행 파일을 실행할 수 있는 능력이었는데, 실행 파일이란 컴퓨터에서 실행될 수 있는 프로그램들입니다.

실행 파일을 쉽게 식별할 수 있도록 MS-DOS 개발자들은 모든 실행 파일의 시작 부분에 특별한 "매직 넘버"를 사용하기로 결정했습니다. 이 매직 넘버는 데이터 파일이나 구성 파일과 같은 다른 유형의 파일과 실행 파일을 구분하는 데 사용됩니다.

Mark Zbikowski는 MS-DOS 팀의 개발자였으며, "MZ" 문자를 매직 넘버로 사용하는 아이디어를 생각해냈습니다. ASCII 코드에서, 문자 "M"은 16진수 값 0x4D로 표현되고, 문자 "Z"는 16진수 값 0x5A로 표현됩니다. 이 값들이 결합되면, 매직 넘버 0x5A4D가 되는데, 이는 문자 "MZ"의 ASCII 표현입니다.

오늘날 "MZ" 서명은 여전히 Windows 운영체제에서 사용되는 주요 실행 파일 형식인 PE 파일을 식별하는 데 사용됩니다. 이것은 PE 파일의 첫 번째 구조인 IMAGE_DOS_HEADER 구조의 e_magic 필드에 저장됩니다.

 

IMAGE_NT_HEADER

IMAGE_NT_HEADER 는 1993년에 출시된 Windows NT 운영 체제와 함께 도입된 데이터 구조입니다. 이 구조는 운영 체제가 실행 파일(PE 파일)의 내용을 읽고 해석하는 표준 방법을 제공하도록 설계되었습니다.

Windows NT가 출시되면서 Microsoft는 실행 파일의 레이아웃과 구조를 표준화할 방법으로 IMAGE_NT_HEADER 를 도입했습니다. 이는 운영 체제가 단일 형식만 지원하면 되도록 하여 프로그램을 불러오고 실행하기가 더 쉬워졌습니다.

https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_nt_headers32

https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_nt_headers64

typedef struct _IMAGE_NT_HEADERS32
{

    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;

} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;


typedef struct _IMAGE_NT_HEADERS64
{

    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;

} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

The IMAGE_NT_HEADER 는 Windows 운영 체제의 모든 휴대용 실행 파일(PE)의 시작 부분에 나타나는 구조입니다. 이 구조는 실행 파일의 크기, 구조, 및 의도된 목적과 같은 중요한 정보를 운영 체제에 제공하는 여러 필드를 포함하고 있습니다.

IMAGE_NT_HEADER 구조는 두 주요 섹션으로 나뉩니다: IMAGE_FILE_HEADERIMAGE_OPTIONAL_HEADER.

 

IMAGE_FILE_HEADER

IMAGE_FILE_HEADER 는 실행 파일 전체에 대한 정보를 포함하고 있으며, 그 중에는 기계 유형(예: x86, x64), 파일의 섹션 수, 파일 생성 날짜 및 시간이 포함됩니다.

https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_file_header

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: 이 필드는 파일이 빌드된 대상 아키텍처를 지정합니다. 이 필드의 값은 파일이 빌드될 때 컴파일러에 의해 결정됩니다. 몇 가지 일반적인 값은 다음과 같습니다:

    • IMAGE_FILE_MACHINE_I386: 이 파일은 x86 아키텍처에서 실행되도록 의도되었습니다. 이는 32비트로도 알려져 있습니다.

    • IMAGE_FILE_MACHINE_AMD64: 이 파일은 x64 아키텍처에서 실행되도록 의도되었습니다. 이는 64비트로도 알려져 있습니다.

    • IMAGE_FILE_MACHINE_ARM: 이 파일은 ARM 아키텍처에서 실행되도록 의도되었습니다.

  • NumberOfSections: 이 필드는 PE 파일의 섹션 수를 지정합니다. PE 파일은 코드, 데이터 및 리소스와 같은 다양한 유형의 정보를 포함하는 여러 섹션으로 나뉩니다. 이 필드는 운영 체제가 파일에 섹션이 얼마나 있는지 결정하는 데 사용됩니다.

  • TimeDateStamp: 이 필드에는 파일이 빌드된 시간의 타임스탬프가 포함되어 있습니다. 타임스탬프는 1970년 1월 1일 00:00:00 UTC 이후 초 단위로 표현된 4바이트 값으로 저장됩니다. 이 필드는 파일이 마지막으로 언제 빌드되었는지를 결정하는 데 유용하게 사용할 수 있으며, 디버깅이나 버전 관리에도 유용합니다.

  • PointerToSymbolTable: 이 필드는 COFF(공통 객체 파일 형식) 심볼 테이블의 파일 오프셋을 지정합니다(해당되는 경우). COFF 심볼 테이블은 함수 이름, 변수 이름, 라인 번호와 같은 파일에서 사용된 심볼에 대한 정보를 포함합니다. 이 필드는 디버깅 용도로만 사용되며 일반적으로 릴리스 빌드에는 포함되지 않습니다.

  • NumberOfSymbols: 이 필드는 COFF 심볼 테이블에 있는 심볼의 수를 지정합니다(해당되는 경우). 이 필드는 PointerToSymbolTable과 함께 사용되어 파일에서 COFF 심볼 테이블을 위치시킵니다.

  • SizeOfOptionalHeader: 이 필드는 파일에 대한 추가 정보를 포함하는 선택적 헤더의 크기를 지정합니다. 선택적 헤더는 일반적으로 파일의 진입점, 가져온 라이브러리 및 스택 및 힙의 크기에 대한 정보를 포함합니다.

  • Characteristics: 이 필드는 파일의 다양한 속성을 지정합니다. 일반적인 값 중 몇 가지는 다음과 같습니다:

    • IMAGE_FILE_EXECUTABLE_IMAGE: 파일은 실행 파일입니다.

    • IMAGE_FILE_DLL: 파일은 동적 링크 라이브러리(DLL)입니다.

    • IMAGE_FILE_32BIT_MACHINE: 파일은 32비트 파일입니다.

    • IMAGE_FILE_DEBUG_STRIPPED: 파일에서 디버그 정보가 제거되었습니다.

이 필드들은 파일을 메모리에 로드하고 실행할 때 운영 시스템이 사용하는 파일에 대한 중요한 정보를 제공합니다. IMAGE_FILE_HEADER 구조에서 필드를 이해함으로써, PE 파일의 구조와 운영 시스템이 이를 어떻게 사용하는지에 대해 더 깊이 이해할 수 있습니다.

각 필드에 대한 가능한 값의 대부분은 헤더 파일 WinNT.h에 선언되어 있습니다.

 

IMAGE_OPTIONAL_HEADER

IMAGE_FILE_HEADER 구조는 IMAGE_OPTIONAL_HEADER 구조에 의해 설명되는 선택적 헤더를 따릅니다. 선택적 헤더에는 진입점의 주소, 이미지의 크기, 임포트 디렉토리의 주소와 같은 이미지에 대한 추가 정보가 포함되어 있습니다.

https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32

https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64

typedef struct _IMAGE_OPTIONAL_HEADER32
{

    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;


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;

다음은 IMAGE_OPTIONAL_HEADER 구조에서 각 필드의 자세한 설명입니다:

  • Magic: 이 필드는 PE 파일에 존재하는 선택적 헤더의 유형을 지정합니다. 가장 흔한 값은 32비트 파일의 경우 IMAGE_NT_OPTIONAL_HDR32_MAGIC, 64비트 파일의 경우 IMAGE_NT_OPTIONAL_HDR64_MAGIC 입니다.

  • MajorLinkerVersionMinorLinkerVersion: 이 필드들은 파일을 구축하는 데 사용된 링커의 버전을 지정합니다. 링커는 객체 파일과 라이브러리를 단일 실행 파일로 결합하는 데 사용되는 도구입니다.

  • SizeOfCode: 이 필드는 파일의 코드 섹션 크기를 지정합니다. 코드 섹션에는 실행 파일의 머신 코드가 포함되어 있습니다.

  • SizeOfInitializedData: 이 필드는 파일의 초기화된 데이터 섹션 크기를 지정합니다. 초기화된 데이터 섹션에는 런타임에 초기화되는 데이터, 예를 들어 전역 변수들이 포함됩니다.

  • SizeOfUninitializedData: 이 필드는 파일의 초기화되지 않은 데이터 섹션의 크기를 지정합니다. 초기화되지 않은 데이터 섹션에는 런타임에 초기화되지 않은 데이터, 예를 들어 bss 섹션이 포함됩니다.

  • AddressOfEntryPoint: 이 필드는 파일의 엔트리 포인트의 가상 주소를 지정합니다. 엔트리 포인트는 프로그램의 시작 주소이며 파일이 메모리에 로드될 때 처음 실행되는 명령입니다.

  • BaseOfCode: 이 필드는 코드 섹션 시작의 가상 주소를 지정합니다.

  • ImageBase: 이 필드는 파일이 메모리에 로드되어야 하는 선호되는 가상 주소를 지정합니다. 이 주소는 파일 내의 모든 가상 주소의 기본 주소로 사용됩니다.

  • SectionAlignment: 이 필드는 파일 내의 섹션 정렬을 지정합니다. 파일의 섹션은 일반적으로 이 값의 배수로 정렬되어 성능을 향상시킵니다.

  • FileAlignment: 이 필드는 디스크 내의 파일 섹션의 정렬을 지정합니다. 파일의 섹션은 일반적으로 이 값의 배수로 정렬되어 디스크 성능을 향상시킵니다.

  • MajorOperatingSystemVersionMinorOperatingSystemVersion: 이 필드들은 파일을 실행하는 데 필요한 최소 운영 체제 버전을 지정합니다.

  • MajorImageVersionMinorImageVersion: 이 필드들은 이미지의 버전을 지정합니다. 이미지 버전은 버전 관리 목적으로 파일의 버전을 식별하는 데 사용됩니다.

  • MajorSubsystemVersionMinorSubsystemVersion: 이 필드들은 파일을 실행하는 데 필요한 서브시스템의 버전을 지정합니다. 서브시스템은 파일이 실행되는 환경이며, 예를 들어 Windows Console이나 Windows GUI 등이 있습니다.

  • Win32VersionValue: 이 필드는 예약되어 있으며 일반적으로 0으로 설정됩니다.

  • SizeOfImage: 이 필드는 메모리에 로드될 때 이미지의 크기를 바이트 단위로 지정합니다.

  • SizeOfHeaders: 이 필드는 헤더의 크기를 바이트 단위로 지정합니다. 헤더에는 IMAGE_FILE_HEADERIMAGE_OPTIONAL_HEADER가 포함됩니다.

  • CheckSum: 이 필드는 파일의 무결성을 확인하는 데 사용됩니다. 체크섬은 파일의 내용을 합산하여 이 필드에 결과를 저장함으로써 계산됩니다. 체크섬은 변조 또는 손상으로 인해 파일에 발생할 수 있는 변경 사항을 감지하는 데 사용됩니다.

  • Subsystem: 이 필드는 파일을 실행하는 데 필요한 서브시스템을 지정합니다. 가능한 값에는 IMAGE_SUBSYSTEM_NATIVE, IMAGE_SUBSYSTEM_WINDOWS_GUI, IMAGE_SUBSYSTEM_WINDOWS_CUI, IMAGE_SUBSYSTEM_OS2_CUI 등이 있습니다.

  • DllCharacteristics: 이 필드는 파일의 특성을 지정합니다. 예를 들어 파일이 동적 링크 라이브러리(DLL)인지 또는 로드 시 재배치가 가능한지 등의 가능한 값이 포함됩니다. 가능한 값에는 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE, IMAGE_DLLCHARACTERISTICS_NX_COMPAT 등이 있습니다.

  • SizeOfStackReserve: 이 필드는 프로그램에 예약된 스택의 크기를 바이트 단위로 지정합니다. 스택은 함수 호출 정보와 같은 일시적 데이터를 저장하는 데 사용됩니다.

  • SizeOfStackCommit: 이 필드는 프로그램에 커밋된 스택의 크기를 바이트 단위로 지정합니다. 커밋된 스택은 메모리에 실제로 예약된 스택의 부분입니다.

  • SizeOfHeapReserve: 이 필드는 프로그램에 예약된 힙의 크기를 바이트 단위로 지정합니다. 힙은 런타임에 동적으로 메모리를 할당하는 데 사용됩니다.

  • SizeOfHeapCommit: 이 필드는 프로그램에 커밋된 힙의 크기를 바이트 단위로 지정합니다. 커밋된 힙은 메모리에 실제로 예약된 힙의 부분입니다.

  • LoaderFlags: 이 필드는 예약되어 있으며 일반적으로 0으로 설정됩니다.

  • NumberOfRvaAndSizes: 이 필드는 IMAGE_OPTIONAL_HEADER에서 데이터 디렉토리 항목의 수를 지정합니다. 데이터 디렉토리에는 파일의 가져오기, 내보내기, 리소스 등에 대한 정보가 포함됩니다.

  • DataDirectory: 이 필드는 파일 내의 데이터 디렉토리의 위치와 크기를 지정하는 IMAGE_DATA_DIRECTORY 구조체의 배열입니다.

 

IMAGE_SECTION_HEADER

PE (Portable Executable) 파일의 맥락에서 섹션은 특정 유형의 데이터나 코드를 지닌 파일 내의 연속적인 메모리 블록입니다. PE 파일에서 섹션은 코드, 데이터, 리소스 등 파일의 다양한 부분을 구성하고 저장하는 데 사용됩니다.

PE 파일의 각 섹션은 고유한 이름을 가지며 IMAGE_SECTION_HEADER 구조로 설명됩니다. 이 구조는 섹션의 크기, 위치, 특성 등에 대한 정보를 포함합니다. 다음은 IMAGE_SECTION_HEADER의 필드입니다:

IMAGE_SECTION_HEADER는 Windows 운영 체제에서 파일의 메모리 내 레이아웃을 정의하는 데 사용되는 Portable Executable (PE) 파일 형식에서 사용되는 데이터 구조입니다. PE 파일 형식은 실행 파일, DLL, 그리고 Windows 운영 체제에 의해 메모리로 로드되는 기타 유형의 파일에 사용됩니다. 각 섹션 헤더는 파일 내의 연속된 데이터 블록을 설명하며, 섹션의 이름, 섹션이 로드되어야 하는 가상 메모리 주소, 그리고 섹션의 크기와 같은 정보를 포함합니다. 섹션 헤더는 파일의 특정 부분, 예를 들어 코드나 데이터 섹션을 찾아 접근하는 데 사용할 수 있습니다.

IMAGE_SECTION_HEADER 구조체는 Windows Platform SDK에서 정의되어 있으며, winnt.h 헤더 파일에서 찾을 수 있습니다. 다음은 C++에서 구조체가 어떻게 정의되어 있는지의 예입니다:

#pragma pack(push, 1)

typedef struct _IMAGE_SECTION_HEADER
{

    BYTE Name[IMAGE_SIZEOF_SHORT_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, *PIMAGE_SECTION_HEADER;

#pragma pack(pop)

보시다시피, 구조체는 C++ struct로 정의되어 있으며 섹션 이름, 가상 크기, 가상 주소, 원시 데이터 크기, 원시 데이터, 재배치, 라인 번호 및 재배치 및 라인 번호의 수에 대한 필드를 포함하고 있습니다. 추가적으로, Characteristics 필드에는 섹션이 실행 가능한지, 읽을 수 있는지, 쓸 수 있는지 등 섹션의 특성을 설명하는 플래그들이 포함되어 있습니다.

  • Name: 이 8바이트 배열은 섹션의 이름을 지정하는 데 사용됩니다. 이름은 널로 종료된 문자열이 될 수 있지만, 실행 가능 코드인 ".text", 초기화된 데이터를 위한 ".data", 읽기 전용 데이터를 위한 ".rdata", 초기화되지 않은 데이터를 위한 ".bss"와 같이 파일의 다양한 부분에 의미 있는 이름을 부여하는 데 일반적으로 사용됩니다. 섹션의 이름은 운영 체제가 파일 내에서 섹션을 찾는 데 사용되며, 디버거 및 기타 도구가 섹션과 내용을 식별하는 데에도 사용됩니다.

  • VirtualSize: 이 필드는 메모리에서 섹션의 크기를 바이트 단위로 지정합니다. 이 값은 파일이 메모리에 로드될 때 섹션이 차지할 메모리의 양을 나타냅니다. 섹션의 가상 크기는 운영 체제가 파일이 메모리에 로드될 때 섹션에 할당해야 할 메모리 양을 결정하는 데 사용됩니다.

  • VirtualAddress: 이 필드는 메모리에서 섹션의 시작 주소를 바이트 단위로 지정합니다. 이 값은 섹션이 메모리에 로드될 때의 시작 주소이며, 섹션이 메모리의 어느 위치에 로드될지를 운영 체제가 결정하는 데 사용됩니다. 섹션의 가상 주소는 또한 운영 체제가 섹션 내의 주소를 해결하고, 파일이 메모리에 로드될 때 주소가 메모리 주소로 올바르게 변환되도록 하는 데 사용됩니다.

  • SizeOfRawData: 이 필드는 파일에서 섹션의 크기를 바이트 단위로 지정합니다. 이 값은 섹션이 파일에서 차지할 공간의 양을 나타내며, 운영 체제가 파일에서 섹션의 크기를 결정하는 데 사용됩니다. 섹션의 원시 데이터 크기는 운영 체제가 파일 내에서 섹션을 찾고, 메모리에 로드될 때 섹션의 크기를 결정하는 데 사용됩니다.

  • PointerToRawData: 이 필드는 파일에서 섹션의 오프셋을 바이트 단위로 지정합니다. 이 값은 파일 내의 섹션 위치를 나타내며, 섹션의 데이터를 찾을 수 있는 위치를 결정하는 데 사용됩니다. 섹션의 원시 데이터 포인터는 운영 체제가 파일 내의 섹션을 찾고, 메모리에 로드될 때 섹션의 위치를 결정하는 데 사용됩니다.

  • PointerToRelocations: 이 필드는 섹션의 재배치 정보 오프셋을 바이트 단위로 지정합니다. 재배치 정보는 섹션 내의 주소를 수정하여 파일이 메모리에 로드될 때 주소가 올바로 해결될 수 있도록 하는 데 사용됩니다. 섹션의 재배치 포인터는 운영 체제가 섹션의 재배치 정보를 찾고, 파일이 메모리에 로드될 때 섹션 내의 주소를 수정하는 방법을 결정하는 데 사용됩니다.

  • PointerToLinenumbers: 이 필드는 섹션의 행 번호 정보 오프셋을 바이트 단위로 지정합니다. 행 번호 정보는 디버깅 목적으로 사용되며, 섹션을 생성한 소스 코드에 대한 정보를 제공합니다. 섹션의 행 번호 포인터는 디버거 및 기타 도구가 섹션을 생성한 소스 코드를 식별하고, 섹션의 내용에 대한 보다 자세한 정보를 제공하는 데 사용됩니다.

  • NumberOfRelocations: 이 필드는 섹션에 대한 재배치 항목 수를 지정합니다. 재배치 항목은 섹션 내의 주소를 수정하는 방법을 설명하는 레코드로서, 파일이 메모리에 로드될 때 주소가 올바로 해결될 수 있도록 합니다. 섹션의 재배치 수는 운영 체제가 섹션의 재배치 정보 크기를 결정하고, 파일이 메모리에 로드될 때 처리해야 할 재배치 항목 수를 알아내는 데 사용됩니다.

  • NumberOfLinenumbers: 이 필드는 섹션에 대한 행 번호 항목 수를 지정합니다. 행 번호 항목은 섹션을 생성한 소스 코드에 대한 정보를 제공하는 레코드로서, 디버깅 목적으로 사용됩니다. 섹션의 행 번호 수는 디버거 및 기타 도구가 섹션의 행 번호 정보 크기를 결정하고, 섹션을 생성한 소스 코드에 대한 정보를 얻기 위해 처리해야 할 행 번호 항목 수를 알아내는 데 사용됩니다.

  • Characteristics: 이 필드는 섹션의 속성을 지정하는 플래그 집합입니다. 섹션에 사용되는 일반적인 플래그로는 실행 가능한 코드를 포함한다는 것을 나타내는 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가 있습니다. 이러한 플래그는 운영 체제가 섹션의 속성을 결정하고, 파일이 메모리에 로드될 때 섹션을 처리하는 방법을 알아내는 데 사용됩니다.

이 필드들은 운영 체제와 다른 프로그램들이 파일의 메모리 레이아웃을 관리하고, 코드나 데이터 섹션과 같은 파일의 특정 부분을 찾아 접근하는 데 사용됩니다.

중요: Portable Executable (PE) 파일 형식에서 사용되는 IMAGE_NT_HEADER 구조체의 맥락에서 VirtualAddress와 PhysicalAddress 필드는 서로 다른 것을 참조합니다.

VirtualAddress 필드는 IMAGE_NT_HEADER 구조체가 포함된 섹션이 런타임에 메모리에 로드되는 가상 주소를 지정하는 데 사용됩니다. 이 주소는 프로세스의 기본 주소에 상대적이며 프로그램이 섹션의 데이터에 접근하는 데 사용됩니다.

PhysicalAddress 필드는 PE 파일에서 IMAGE_NT_HEADER 구조가 포함된 섹션의 파일 오프셋을 지정하는 데 사용됩니다. 이는 운영 시스템이 메모리에 로드될 때 파일에서 섹션의 데이터를 찾는 데 사용됩니다.

모든 헤더 필드와 IMAGE_NT_HEADER 의 오프셋은 메모리에 정의되어 있으며 가상 주소에서 작동합니다. 디스크 상의 어떤 필드든 오프셋할 필요가 있다면, 아래 코드의 rva2offset 함수를 사용하여 가상 주소를 물리적 주소로 변환해야 합니다.

요약하자면, VirtualAddress는 프로그램이 메모리 내의 섹션에 접근하는 데 사용되며 PhysicalAddress는 운영 시스템이 파일 내의 섹션을 찾는 데 사용됩니다.


 

수입

프로그램이 컴파일될 때, 컴파일러는 프로그램의 함수들에 대한 기계 코드를 포함하는 오브젝트 파일들을 생성합니다. 그러나 오브젝트 파일들은 프로그램이 실행되는 데 필요한 모든 정보를 가지고 있지 않을 수 있습니다. 예를 들어, 오브젝트 파일들은 프로그램에 정의되어 있지 않지만 외부 라이브러리에 의해 제공되는 함수들에 대한 호출을 포함할 수 있습니다.

여기에서 가져오기 테이블이 사용됩니다. 가져오기 테이블은 프로그램의 외부 종속성과 이러한 종속성에서 가져와야 하는 함수를 나열합니다. 동적 링커는 런타임에 이 정보를 사용하여 가져온 함수의 주소를 확인하고 프로그램에 연결합니다.

예를 들어, Windows 운영 체제의 함수를 사용하는 프로그램을 고려해 보세요. 이 프로그램은 화면에 메시지 박스를 표시하는 user32.dll 라이브러리의 MessageBox 함수에 대한 호출을 포함할 수 있습니다. MessageBox 함수의 주소를 해결하기 위해서는 프로그램이 가져오기 테이블에 user32.dll에 대한 가져오기를 포함해야 합니다.

마찬가지로, 프로그램이 타사 라이브러리의 함수를 사용해야 하는 경우, 해당 라이브러리에 대한 import를 import 테이블에 포함시켜야 합니다. 예를 들어, OpenSSL 라이브러리의 함수를 사용하는 프로그램은 import 테이블에 libssl.dll 라이브러리에 대한 import를 포함할 것입니다.

 

IMAGE_IMPORT_DIRECTORY

IMAGE_IMPORT_DIRECTORY 는 Windows 운영 체제가 동적 링크 라이브러리(DLLs)에서 함수와 데이터를 포터블 실행 파일(PE)로 가져오기 위해 사용하는 데이터 구조입니다. 이는 PE 파일의 IMAGE_OPTIONAL_HEADER에 저장된 데이터 구조의 테이블인 IMAGE_DATA_DIRECTORY의 일부입니다.

IMAGE_IMPORT_DIRECTORY 는 PE 파일이 사용하는 가져온 함수와 데이터를 해결하기 위해 Windows 로더에 의해 사용됩니다. 이는 가져온 함수와 데이터의 주소를 해당 DLL의 함수와 데이터 주소에 매핑함으로써 이루어집니다. 이를 통해 PE 파일은 마치 자체 파일의 일부인 것처럼 DLL의 함수와 데이터를 사용할 수 있습니다.

IMAGE_IMPORT_DIRECTORY는 PE 파일에서 가져온 각각의 DLL을 설명하는 일련의 IMAGE_IMPORT_DESCRIPTOR 구조들로 구성됩니다. 각 IMAGE_IMPORT_DESCRIPTOR 구조는 다음과 같은 필드를 포함하고 있습니다:

  • OriginalFirstThunk: 가져온 함수의 테이블을 가리키는 포인터입니다.

  • TimeDateStamp: DLL이 마지막으로 업데이트된 날짜와 시간입니다.

  • ForwarderChain: 전달된 가져온 함수의 체인입니다.

  • Name: 널로 끝나는 문자열로 DLL의 이름입니다.

  • FirstThunk: DLL에 연결된 가져온 함수의 테이블을 가리키는 포인터입니다.

typedef struct _IMAGE_IMPORT_DESCRIPTOR
{

    union {
        DWORD Characteristics; // 0 for terminating null import descriptor
        DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    } DUMMYUNIONNAME;

    DWORD TimeDateStamp; // 0 if not bound,
                         // -1 if bound, and real date	ime stamp
                         // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                         // O.W. date/time stamp of DLL bound to (Old BIND)
    
    DWORD ForwarderChain; // -1 if no forwarders
    DWORD Name;
    DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)

} IMAGE_IMPORT_DESCRIPTOR;

OriginalFirstThunk 테이블(또는 OriginalFirstThunk 가 0인 경우 FirstThunk )

오프셋 테이블에 의해 가리키는 문자열(OriginalFirstThunk 테이블 또는 FirstThunkOriginalFirstThunk이 0인 경우)

 

어떻게 작동하나요?

Microsoft에서 구현한 가져오기 메커니즘은 간결하고 아름답습니다!

응용 프로그램이 사용하는 제3자 라이브러리(Windows 시스템 포함)의 모든 함수 주소는 특수 테이블인 the import table에 저장됩니다. 이 테이블은 모듈이 로드될 때 채워집니다(imports를 채우는 다른 메커니즘에 대해서는 나중에 이야기하겠습니다).

더욱이, 함수가 서드파티 라이브러리에서 호출될 때마다, 컴파일러는 보통 다음과 같은 코드를 생성합니다:

call dword ptr [__cell_with_address_of_function] // for x86 architecture
call qword ptr [__cell_with_address_of_function] // for x64 architecture

따라서 라이브러리에서 함수를 호출할 수 있으려면 시스템 로더가 이미지의 한 곳에 이 함수의 주소를 한 번만 작성하면 됩니다.

 

C++ 파서

그리고 이제 실행 파일 가져오기 테이블과 호환되는 가장 간단한 파서(x86 및 x64)를 작성해 보겠습니다!

#include "stdafx.h"

/*
 *
 *  Copyright (C) 2022, SToFU Systems S.L.
 *  All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 */

namespace ntpe
{
    static constexpr uint64_t g_kRvaError = -1;

    // These types is defined in NTPEParser.h
    // typedef std::map< std::string, std::set< std::string >> IMPORT_LIST;
    // typedef std::vector< IMAGE_SECTION_HEADER > SECTIONS_LIST;

    //**********************************************************************************
    // FUNCTION: alignUp(DWORD value, DWORD align)
    // 
    // ARGS:
    // DWORD value - value to align.
    // DWORD align - alignment.
    // 
    // DESCRIPTION: 
    // Aligns argument value with the given alignment.
    // 
    // Documentation links:
    // Alignment: https://learn.microsoft.com/en-us/cpp/cpp/alignment-cpp-declarations?view=msvc-170
    // 
    // RETURN VALUE: 
    // DWORD aligned value.
    // 
    //**********************************************************************************
    DWORD alignUp(DWORD value, DWORD align)
    {
        DWORD mod = value % align;
        return value + (mod ? (align - mod) : 0);
    };

    //**********************************************************************************
    // FUNCTION: rva2offset(IMAGE_NTPE_DATA& ntpe, DWORD rva)
    // 
    // ARGS:
    // IMAGE_NTPE_DATA& ntpe - data from PE file.
    // DWORD rva - relative virtual address.
    // 
    // DESCRIPTION: 
    // Parse RVA (relative virtual address) to offset.
    // 
    // RETURN VALUE: 
    // int64_t offset. 
    // g_kRvaError (-1) in case of error.
    // 
    //**********************************************************************************
    int64_t rva2offset(IMAGE_NTPE_DATA& ntpe, DWORD rva)
    {
        /* retrieve first section */
        try
        {
            /* if rva is inside MZ header */
            PIMAGE_SECTION_HEADER sec = ntpe.sectionDirectories;
            if (!ntpe.fileHeader->NumberOfSections || rva < sec->VirtualAddress)
                return rva;

            /* walk on sections */
            for (uint32_t sectionIndex = 0; sectionIndex < ntpe.fileHeader->NumberOfSections; sectionIndex++, sec++)
            {
                /* count section end and allign it after each iteration */
                DWORD secEnd = ntpe::alignUp(sec->Misc.VirtualSize, ntpe.SecAlign) + sec->VirtualAddress;
                if (sec->VirtualAddress <= rva && secEnd > rva)
                    return rva - sec->VirtualAddress + sec->PointerToRawData;
            };
        }
        catch (std::exception&)
        {
        }

        return g_kRvaError;
    };


    //**********************************************************************************
    // FUNCTION: getNTPEData(char* fileMapBase)
    // 
    // ARGS:
    // char* fileMapBase - the starting address of the mapped file.
    // 
    // DESCRIPTION: 
    // Parses following data from mapped PE file.
    //  
    // Documentation links:
    // PE format structure: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
    //
    // RETURN VALUE: 
    // std::optional< IMAGE_NTPE_DATA >. 
    // std::nullopt in case of error.
    // 
    //**********************************************************************************
    #define initNTPE(HeaderType, cellSize) \
    { \
    char* ntstdHeader       = (char*)fileHeader + sizeof(IMAGE_FILE_HEADER); \
    HeaderType* optHeader   = (HeaderType*)ntstdHeader; \
    data.sectionDirectories = (PIMAGE_SECTION_HEADER)(ntstdHeader + sizeof(HeaderType)); \
    data.SecAlign           = optHeader->SectionAlignment; \
    data.dataDirectories    = optHeader->DataDirectory; \
    data.CellSize           = cellSize;	\
    }
    std::optional< IMAGE_NTPE_DATA > getNTPEData(char* fileMapBase, uint64_t fileSize)
    {
        try
        {
            /* PIMAGE_DOS_HEADER from starting address of the mapped view*/
            PIMAGE_DOS_HEADER dosHeader = (IMAGE_DOS_HEADER*)fileMapBase;

            /* return std::nullopt in case of no IMAGE_DOS_SIGNATUR signature */
            if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE)
                return std::nullopt;

            /* PE signature adress from base address + offset of the PE header relative to the beginning of the file */
            PDWORD peSignature = (PDWORD)(fileMapBase + dosHeader->e_lfanew);
            if ((char*)peSignature <= fileMapBase || (char*)peSignature - fileMapBase >= fileSize)
                return std::nullopt;

            /* return std::nullopt in case of no PE signature */
            if (*peSignature != IMAGE_NT_SIGNATURE)
                return std::nullopt;

            /* file header address from PE signature address */
            PIMAGE_FILE_HEADER fileHeader = (PIMAGE_FILE_HEADER)(peSignature + 1);
            if (fileHeader->Machine != IMAGE_FILE_MACHINE_I386 &&
                fileHeader->Machine != IMAGE_FILE_MACHINE_AMD64)
                return std::nullopt;

            /* result IMAGE_NTPE_DATA structure with info from PE file */
            IMAGE_NTPE_DATA data = {};

            /* base address and File header address assignment */
            data.fileBase = fileMapBase;
            data.fileHeader = fileHeader;

            /* addresses of PIMAGE_SECTION_HEADER, PIMAGE_DATA_DIRECTORIES, SectionAlignment, CellSize depending on processor architecture */
            switch (fileHeader->Machine)
            {
            case IMAGE_FILE_MACHINE_I386:
                initNTPE(IMAGE_OPTIONAL_HEADER32, 4);
                return data;

            case IMAGE_FILE_MACHINE_AMD64:
                initNTPE(IMAGE_OPTIONAL_HEADER64, 8);
                return data;
            }
        }
        catch (std::exception&)
        {

        }
        return std::nullopt;
    }


    //**********************************************************************************
    // FUNCTION: getImportList(IMAGE_NTPE_DATA& ntpe)
    // 
    // ARGS:
    // IMAGE_NTPE_DATA& ntpe - data from PE file.
    // 
    // DESCRIPTION: 
    // Retrieves IMPORT_LIST(std::map< std::string, std::set< std::string >>) with all loaded into PE libraries names and imported functions.
    // Map key: loaded dll's names. 
    // Map value: set of imported functions names.
    //
    // Documentation links:
    // Import Directory Table: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#import-directory-table
    //
    // RETURN VALUE: 
    // std::optional< IMPORT_LIST >. 
    // std::nullopt in case of error.
    // 
    //**********************************************************************************
    std::optional< IMPORT_LIST > getImportList(IMAGE_NTPE_DATA& ntpe)
    {
        try
        {
            /* if no imaage import directory in file returns std::nullopt */
            if (ntpe.dataDirectories[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0)
                return std::nullopt;

            IMPORT_LIST result;

            /* import table offset */
            DWORD impOffset = rva2offset(ntpe, ntpe.dataDirectories[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

            /* imoprt table descriptor from import table offset + file base adress */
            PIMAGE_IMPORT_DESCRIPTOR impTable = (PIMAGE_IMPORT_DESCRIPTOR)(impOffset + ntpe.fileBase);

            /* while names in import table */
            while (impTable->Name != 0)
            {
                /* pointer to DLL name from offset of current section name + file base adress */
                std::string modname = rva2offset(ntpe, impTable->Name) + ntpe.fileBase;
                std::transform(modname.begin(), modname.end(), modname.begin(), ::toupper);

                /* start adress of names in look up table from import table name RVA */
                char* cell = ntpe.fileBase + ((impTable->OriginalFirstThunk) ? rva2offset(ntpe, impTable->OriginalFirstThunk) : rva2offset(ntpe, impTable->FirstThunk));

                /* while names in look up table */
                for (;; cell += ntpe.CellSize)
                {
                    int64_t rva = 0;

                    /* break if rva = 0 */
                    memcpy(&rva, cell, ntpe.CellSize);
                    if (!rva)
                        break;

                    /* if rva > 0 function was imported by name. if rva < 0 function was imported by ordinall */
                    if (rva > 0)
                        result[modname].emplace(ntpe.fileBase + rva2offset(ntpe, rva) + 2);
                    else
                        result[modname].emplace(std::string("#ord: ") + std::to_string(rva & 0xFFFF));
                };
                impTable++;
            };
            return result;
        }
        catch (std::exception&)
        {
            return std::nullopt;
        }
    };

    //**********************************************************************************
    // FUNCTION: getImportList(IMAGE_NTPE_DATA& ntpe)
    // 
    // ARGS:
    // std::wstring_view filePath - path to file.
    // 
    // DESCRIPTION: 
    // Retrieves IMPORT_LIST(std::map< std::string, std::set< std::string >>) with all loaded into PE libraries names and imported functions bu path.
    // Map key: loaded dll's names. 
    // Map value: set of imported functions names.
    //
    // Documentation links:
    // Import Directory Table: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#import-directory-table
    //
    // RETURN VALUE: 
    // std::optional< IMPORT_LIST >. 
    // std::nullopt in case of error.
    // 
    //**********************************************************************************
    std::optional< IMPORT_LIST > getImportList(std::wstring_view filePath)
    {
        std::vector< char > buffer;
        /* obtain base address of mapped file from tools::readFile function */
        bool result = tools::readFile(filePath, buffer);
        /* return nullopt if readFile failes or obtained buffer is empty */
        if (!result || buffer.empty())
            return std::nullopt;
        /* get IMAGE_NTPE_DATA from base address of mapped file */
        std::optional< IMAGE_NTPE_DATA > ntpe = getNTPEData(buffer.data(), buffer.size());
        if (!ntpe)
            return std::nullopt;
        /* return result of overloaded getImportList function with IMAGE_NTPE_DATA as argument */
        return getImportList(*ntpe);
    }


    //**********************************************************************************
    // FUNCTION: getSectionsList(IMAGE_NTPE_DATA& ntpe)
    // 
    // ARGS:
    // IMAGE_NTPE_DATA& ntpe - data from PE file.
    // 
    // DESCRIPTION: 
    // Retrieves SECTIONS_LIST from IMAGE_NTPE_DATA.
    // SECTIONS_LIST - vector of sections headers from portable executable file.
    // Sections names exmaple: .data, .code, .src
    //  
    // Documentation links:
    // IMAGE_SECTION_HEADER: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header
    // Section Table (Section Headers): https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#section-table-section-headers
    // 
    // RETURN VALUE: 
    // std::optional< SECTIONS_LIST >. 
    // std::nullopt in case of error.
    // 
    //**********************************************************************************
    std::optional< SECTIONS_LIST > getSectionsList(IMAGE_NTPE_DATA& ntpe)
    {
        try
        {
            /* result vector of section directories */
            SECTIONS_LIST result;

            /* iterations through all image section headers poiners in IMAGE_NTPE_DATA structure */
            for (uint64_t sectionIndex = 0; sectionIndex < ntpe.fileHeader->NumberOfSections; sectionIndex++)
            {
                /* pushing IMAGE_SECTION_HEADER from iamge section headers */
                result.push_back(ntpe.sectionDirectories[sectionIndex]);
            }
            return result;
        }
        catch (std::exception&)
        {
        }
        /* returns nullopt in case of error */
        return std::nullopt;
    }
}

전체 프로젝트의 코드는 저희의 github에서 확인할 수 있습니다:

https://github.com/SToFU-Systems/DSAVE

 

사용된 도구 목록

  1. PE Tools: https://github.com/petoolse/petools 이것은 PE 헤더 필드를 조작하기 위한 오픈 소스 도구입니다. x86 및 x64 파일을 지원합니다.
  2. WinDbg: https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools 마이크로소프트의 시스템 디버거입니다. Windows OS를 위한 시스템 프로그래머의 작업에 필수적입니다.
  3. x64Dbg: https://x64dbg.com 간단하고 경량의 오픈 소스 x64/x86 디버거로 윈도우용입니다.
  4. WinHex: http://www.winhex.com/winhex/hex-editor.html WinHex는 범용 헥스 편집기로, 컴퓨터 포렌식, 데이터 복구, 저수준 데이터 편집 분야에서 특히 도움이 됩니다.

 

다음은 무엇인가요?

우리는 여러분의 지원에 감사드리며 커뮤니티에서 지속적으로 활동해주시길 기대합니다

다음 글에서는 여러분과 함께 fuzzy hashing 모듈을 작성하고, 검은색 및 하얀색 목록에 관한 문제를 다룰 것입니다. 가장 간단한 import table 분석기에 대해서도 언급할 것입니다.

이 기사의 저자에게 질문이 있는 경우 다음 이메일로 보내주세요: articles@stofu.io

 

관심을 가져주셔서 감사합니다. 좋은 하루 되세요!