1. Export Directroy의 Raw값 구하기
1.1 PE Header 위치
- e_lfanew 값(3C에 위치) : 0xF0
- PE Header는 파일의 0xF0 오프셋에서 시작합니다.
1.2 PE Header 구조
PE Header는 다음과 같은 구조를 가지고 있습니다:
- PE Signature (4 bytes)
- 값: 50 45 00 00 (ASCII로 'PE')
- COFF File Header (IMAGE_FILE_HEADER) (20 bytes)
- Optional Header (IMAGE_OPTIONAL_HEADER32)
- 이 부분에서 DataDirectory를 찾을 수 있습니다.
1.3 Optional Header 위치
- Optional Header는 PE Header의 시작 + 24바이트 지점에서 시작됩니다.
- PE Header가 0xF0에 있으므로, Optional Header의 시작 오프셋은: 0xF0 + 0x18 = 0x108
1.4 Data Directory 위치
- DataDirectory는 Optional Header 내에서 96바이트 오프셋부터 시작됩니다.
- 따라서, DataDirectory[0]의 시작 위치는: 0x108 + 0x60 = 0x168
1.5 IMAGE_DATA_DIRECTORY 구조(0x168)
PE Header는 다음과 같은 구조를 가지고 있습니다:
- VirtualAddress (4 bytes): 0x262C
- Size (4 bytes): 0x6CFD
1.6 다음 단계: 파일 오프셋 계산
RVA는 메모리 상의 주소를 기준으로 합니다. 파일에서 해당 데이터를 읽으려면 RVA를 파일 오프셋으로 변환해야 합니다.
파일 오프셋을 계산하려면 Section Header 정보를 참조해야 합니다:
- PE 헤더의 NumberOfSections를 통해 섹션 수를 확인.
- 각 섹션의 VirtualAddress와 PointerToRawData를 이용해 매핑
- VirtualAddress ≤ RVA < VirtualAddress + VirtualSize인 섹션을 찾습니다.
- 해당 섹션의 파일 오프셋
FileOffset = PointerToRawData + (RVA − Section.VirtualAddress)
1.7 Section Header 확인
Section Header는 Optional Header의 끝에 있습니다:
- Optional Header 크기는 PE Header의 SizeOfOptionalHeader 필드에서 확인 가능.
- SizeOfOptionalHeader 필드는 PE Header + 20바이트(0x14) 위치에 있습니다.
따라서 F0 + 0x14 = 0x104이며 그 값은 "E0 00"입니다. - 이를 리틀엔디언 방식으로 계산하면 "0x00E0"이며 이는 10진수로 224입니다.
- SizeOfOptionalHeader 필드는 PE Header + 20바이트(0x14) 위치에 있습니다.
- PE Header 위치 (0xF0) + 4 (Signature) + 20 (COFF Header) + SizeOfOptionalHeader로 Section Header 시작 위치를 찾습니다.
- IMAGE_SECTION_HEADER 구조를 읽어 Section 정보를 얻습니다.
1.8 Section Header 위치 계산
- Optional Header 끝 위치
- Optional Header의 시작 위치는 PE Header 바로 뒤이므로
- Optional Header 시작 = 0xF0 + 24 = 0x108.
- Optional Header의 끝은 Optional Header 시작 + SizeOfOptionalHeader이므로
- Optional Header 끝 = 0x108 + 0xE0 = 0x1E8.
- Section Header 위치
- Section Header는 Optional Header 끝 위치에서 시작합니다.
- 따라서 Section Header의 시작 위치는 0x1E8입니다.
1.9 Section Header 데이터 분석
PE 파일의 Section Header는 각 섹션당 40바이트로 구성됩니다. 따라서 주어진 데이터에서 각각 40바이트씩 나눠서 분석합니다.
2E 74 65 78 74 00 00 00 ; Name: .text
C9 30 08 00 ; Virtual Size: 0x000830C9
00 10 00 00 ; Virtual Address (RVA): 0x00001000
00 32 08 00 ; Size of Raw Data: 0x00083200
00 04 00 00 ; Pointer to Raw Data: 0x00000400
00 00 00 00 ; Pointer to Relocations: 0x00000000
00 00 00 00 ; Pointer to Line Numbers: 0x00000000
00 00 ; Number of Relocations: 0
00 00 ; Number of Line Numbers: 0
20 00 00 60 ; Characteristics: 0x60000020
- Name: .text (코드 섹션)
- Virtual Size: 0x000830C9 (메모리에서 차지하는 크기)
- RVA: 0x00001000 (메모리 기준 시작 주소)
- Size of Raw Data: 0x00083200 (파일에서 차지하는 크기)
- Pointer to Raw Data: 0x00000400 (파일에서의 시작 위치)
- Characteristics: 0x60000020 (섹션의 속성 - 실행 가능, 읽기 가능)
2E 64 61 74 61 00 00 00 ; Name: .data
00 44 00 00 ; Virtual Size: 0x00004400
00 50 08 00 ; Virtual Address (RVA): 0x00085000
26 00 00 00 ; Size of Raw Data: 0x00000026
36 08 00 00 ; Pointer to Raw Data: 0x00000836
00 00 00 00 ; Pointer to Relocations: 0x00000000
00 00 00 00 ; Pointer to Line Numbers: 0x00000000
00 00 ; Number of Relocations: 0
00 00 ; Number of Line Numbers: 0
40 00 00 C0 ; Characteristics: 0xC0000040
- Name: .data (데이터 섹션)
- Virtual Size: 0x00004400
- RVA: 0x00085000
- Size of Raw Data: 0x00000026
- Pointer to Raw Data: 0x00000836
- Characteristics: 0xC0000040 (섹션의 속성 - 읽기/쓰기 가능)
2E 72 73 72 63 00 00 00 ; Name: .rsrc
94 FE 09 00 ; Virtual Size: 0x0009FE94
00 A0 08 00 ; Virtual Address (RVA): 0x0008A000
00 0A 00 00 ; Size of Raw Data: 0x00000A00
5C 08 00 00 ; Pointer to Raw Data: 0x0000085C
00 00 00 00 ; Pointer to Relocations: 0x00000000
00 00 00 00 ; Pointer to Line Numbers: 0x00000000
00 00 ; Number of Relocations: 0
00 00 ; Number of Line Numbers: 0
40 00 00 40 ; Characteristics: 0x40000040
- Name: .rsrc (리소스 섹션)
- Virtual Size: 0x0009FE94
- RVA: 0x0008A000
- Size of Raw Data: 0x00000A00
- Pointer to Raw Data: 0x0000085C
- Characteristics: 0x40000040 (섹션의 속성 - 읽기 가능)
2E 72 65 6C 6F 63 00 00 ; Name: .reloc
80 5C 00 00 ; Virtual Size: 0x00005C80
00 A0 12 00 ; Virtual Address (RVA): 0x00012A000
5E 00 00 00 ; Size of Raw Data: 0x0000005E
5C 12 00 00 ; Pointer to Raw Data: 0x0000125C
00 00 00 00 ; Pointer to Relocations: 0x00000000
00 00 00 00 ; Pointer to Line Numbers: 0x00000000
00 00 ; Number of Relocations: 0
00 00 ; Number of Line Numbers: 0
40 00 00 42 ; Characteristics: 0x40000042
- Name: .reloc (재배치 정보 섹션)
- Virtual Size: 0x00005C80
- RVA: 0x00012A000
- Size of Raw Data: 0x0000005E
- Pointer to Raw Data: 0x0000125C
- Characteristics: 0x40000042 (섹션의 속성 - 읽기 가능)
1.10 Export Directory의 RVA를 RAW로 변환
- 필요한 정보
- Export Directory의 RVA : 0x262C
- Export Directory가 속한 섹션의 시작 RVA와 Pointer to Raw Data
- 섹션 .text
- Virtual Address (RVA): 0x1000
- Pointer to Raw Data: 0x400
- 변환 공식
- RAW = Pointer to Raw Data + (RVA - Section's Virtual Address)
- 계산
- RVA : 0x262C
- Section's Virtual Address : 0x1000
- Pointer to Raw Data : 0x400
- RAW = 0x400 + (0x262C − 0x1000) = 0x1A2C
- 결과
- Export Directory의 RAW 값은 "0x1A2C"입니다.
2. kernel32.dll 파일의 EAT에서 AddAtomW 함수 주소 찾기
2.1 각 멤버 해석
구조체 멤버 | Offset | 값 (리틀 엔디언) | 해석된 값 |
Characteristics | 0x1A2C | 00 00 00 00 | 예약된 값 (사용되지 않음) |
TimeDateStamp | 0x1A30 | 48 02 5B E1 | 타임스탬프 : 0x48025BE1 |
MajorVersion | 0x1A34 | 00 00 | 주요 버전 : 0 |
MinorVersion | 0x1A36 | 00 00 | 부 버전 : 0 |
Name | 0x1A38 | 00 00 4B 8E | DLL 이름의 RVA : 0x00004B8E |
Base | 0x1A3C | 00 00 00 01 | Ordinal 시작 번호 : 1 |
NumberOfFunctions | 0x1A40 | 00 00 03 B9 | 함수 개수(EAT) : 0x000003B9 (953개) |
NumberOfNames | 0x1A44 | 00 00 03 B9 | 함수 이름 테이블의 항목 개수 : 0x000003B9 (953개) |
AddressOfFunctions | 0x1A48 | 00 00 26 54 | EAT의 RVA : 0x00002654 |
AddressOfNames | 0x1A4C | 00 00 35 38 | Export Name Table의 RVA : 0x00003538 |
AddressOfNameOrdinals | 0x1A50 | 00 00 44 1C | Export Ordinal Table의 RVA : 0x0000441C |
2.2 필요한 정보의 RAW 값 계산
- AddressOfFunctions의 RVA(0x2654) → RAW 변환
- .text 섹션: RVA = 0x1000, RAW = 0x400
- 변환 결과: 0x2654 - 0x1000 + 0x400 = 0x1A54
→ 함수 주소 배열의 RAW 값: 0x1A54
- AddressOfNames의 RVA(0x3538) → RAW 변환
- .text 섹션: RVA = 0x1000, RAW = 0x400
- 변환 결과: 0x3538 - 0x1000 + 0x400 = 0x2938
→ 이름 배열의 RAW 값: 0x2938
- AddressOfNameOrdinals의 RVA(0x441C) → RAW 변환
- .text 섹션: RVA = 0x1000, RAW = 0x400
- 변환 결과: 0x441C - 0x1000 + 0x400 = 0x381C
→ 이름 Ordinals 배열의 RAW 값: 0x381C
2.3 AddAtomW 이름 찾기
2.3.1 AddressOfNames(0x2938)에서 AddAtomW 문자열 위치 확인

- 배열에서 AddAtomW 문자열의 RVA를 찾습니다.
- 해당 RVA로 이동하여 문자열 매칭을 수행합니다.
1. AddressOfNames에서 시작
AddressOfNames 필드에 있는 값들은 함수 이름의 RVA 값들입니다. 각 RVA는 함수 이름의 문자열이 저장된 위치를 나타냅니다. 우리가 해야 할 일은 이 중에서 AddAtomW가 저장된 위치를 찾아야 합니다.
2. 각 RVA를 .text의 RAW 주소로 변환
AddressOfNames의 각 엔트리는 RVA 값이므로 이를 RAW 주소로 변환해야 합니다. 변환 공식은 다음과 같습니다:
RAW = RVA − Section RVA + Raw Data Start
- .text 섹션의 시작 RVA : 0x1000
- .text 섹션의 시작 RAW : 0x400
예를 들어, 첫 번째 RVA (0x4B9B)를 변환하면
RAW = 0x4B9B − 0x1000 + 0x400 = 0x3F9B

3. 이름 데이터에서 AddAtomW 찾기
- 변환된 RAW 주소로 HxD에서 데이터를 확인합니다.
- 각 문자열을 확인하며 AddAtomW 이름이 포함된 위치를 찾습니다.(0x3FB3)
- AddAtomW의 인덱스는 3번째입니다.
4. AddressOfNameOrdinals에서 Ordinal 값 확인
- AddressOfNameOrdinals의 RAW값인 0x381C로 갑니다.
- Ordinal 값은 2바이트 값(WORD)으로 저장되어 있습니다.
- 3번째 index 값(2)을 찾습니다.

5. 함수 주소 배열에서 AddAtomW 찾기
- 함수 주소 배열의 RAW 값은 "0x1A54" 입니다.
- 여기서 3번째 인덱스에 해당하는 값을 찾습니다.
- 이 값은 AddAtomW의 RVA 값입니다.(0326D9)

6. AddAtomW 함수 주소 확인하기
- kernel32.dll의 ImageBase값과 AddAtomW의 RVA 값을 더합니다.
- ImageBase = 7C800000
- AddAtomW의 RVA = 326D9
- AddAtomW 함수 주소 = 7C8326D9

※ ImageBase 구하는 법
1. PE Header 위치 찾기
PE Header의 위치는 0x3C 위치에 있습니다. 그 값은 F0입니다. 즉, PE 헤더 파일은 F0부터 시작합니다.(그림 1 참조)
2. PE 헤더의 Optional Header에서 ImageBase 확인
- PE 헤더의 시작 위치:
- e_lfanew 값이 0xF0입니다.
- 따라서, PE 헤더는 0xF0에서 시작합니다.
- Optional Header의 시작 위치:
- PE 헤더에서 Optional Header는 0x18 오프셋 이후에 시작합니다.
- Optional Header 시작 위치 = 0xF0 + 0x18 = 0x108입니다.
- ImageBase 필드 위치:
- Optional Header에서 ImageBase는 0x1C 오프셋에 있습니다.
- 따라서, ImageBase 위치 = 0x108 + 0x1C = 0x124입니다.
- ImageBase 값은 "7C800000" 입니다.
