← 목록으로

PE 포맷 내부 구조: Windows 실행 파일 뜯어보기

[Rev] 리버싱 · 작성: 2026-07-20 03:10:34 · 수정: 2026-07-21 23:32:17 · 조회 77

#reversing
목차

PE(Portable Executable) 포맷을 "Windows 실행 파일 규격"이라고만 알면, 왜 이 포맷이 굳이 DOS 헤더로 시작하는지, 그리고 이 구조가 정적 분석 글에서 다룬 ELF와 개념적으로 어떻게 대응되는지를 놓치게 된다.

구조 레벨: PE가 겹겹이 쌓인 이유#

PE 파일을 헥스 에디터로 열면 맨 앞에 MZ(매직 바이트)가 보인다 — 이건 1980년대 MS-DOS 실행 파일 헤더다. Windows는 하위 호환성을 위해, DOS에서 이 파일을 실행하면 "This program cannot be run in DOS mode" 메시지를 출력하는 작은 DOS 스텁 프로그램을 그대로 유지하면서, 그 안에 e_lfanew라는 필드로 "진짜 PE 헤더는 이 오프셋에 있다"는 포인터를 심어두는 방식으로 포맷을 확장했다.

  1. DOS 헤더 + DOS 스텁: 위에서 설명한 하위 호환 부분
  2. NT 헤더 (IMAGE_NT_HEADERS): PE\0\0 시그니처로 시작, 파일 헤더(아키텍처, 섹션 개수)와 옵셔널 헤더(엔트리 포인트 RVA, 이미지 베이스, 서브시스템)를 담는다 — ELF의 ELF 헤더에 해당
  3. 섹션 헤더 테이블: .text(코드), .data, .rdata(읽기전용 데이터, import 정보 포함), .rsrc(아이콘·문자열 등 리소스), .reloc(재배치 정보) — ELF의 섹션 헤더 테이블과 개념은 동일하고 이름 관례만 다르다
  4. Import Table (.idata, 실제로는 .rdata에 병합되는 경우가 많음): 이 실행 파일이 어떤 DLL의 어떤 함수를 가져다 쓰는지 — 여기 나열된 함수 이름이 정적 분석 글에서 강조한 "취약점 후보를 빠르게 추정하는" 단서(예: CreateProcessA, WinExec가 보이면 이 프로그램이 외부 프로세스를 실행한다는 걸 코드를 안 봐도 알 수 있다)
  5. Export Table: 반대로 이 파일(주로 DLL)이 다른 프로그램에게 제공하는 함수 목록

RVA(Relative Virtual Address)가 존재하는 이유#

PE 안의 주소들은 대부분 파일 오프셋이 아니라 RVA(이미지가 메모리에 로드된 베이스 주소로부터의 상대 오프셋)로 기록되어 있다. 이건 ASLR과 정보 유출 글에서 다룬 "베이스 주소는 매번 랜덤화되지만, 그 안의 상대적인 오프셋은 항상 고정"이라는 원리와 정확히 같은 이유다 — 로더가 파일을 메모리 어디에 매핑하든, RVA에 실제 로드 베이스 주소를 더하기만 하면 항상 올바른 절대 주소가 나오도록 설계되었다.

PE 로더가 실행 시점에 하는 일#

  1. 파일을 섹션 헤더의 지시대로 메모리에 매핑(각 섹션의 읽기/쓰기/실행 권한이 여기서 결정 — NX/DEP 글에서 다룬 실행 권한 제어가 여기서 이뤄진다)
  2. 재배치(Relocation): 이미지 베이스가 선호 주소(옵셔널 헤더에 기록된 값)와 다르게 로드됐다면(ASLR로 인해 거의 항상 다르다), .reloc 섹션의 정보를 이용해 코드 안의 절대주소 참조들을 실제 로드 주소에 맞게 보정
  3. Import 해결: Import Table에 나열된 DLL들을 로드하고, 각 함수의 실제 주소를 IAT(Import Address Table)에 채워 넣음 — 패킹과 언패킹 글에서 언패킹 후 "IAT 재구성"이 가장 까다롭다고 한 지점이 바로 이 테이블이다
  4. 옵셔널 헤더의 엔트리 포인트(RVA)에 로드 베이스를 더한 주소로 실행 시작

여기서 끝나지 않는다#

PE를 "Windows만의 독자적인 포맷"이 아니라 **"ELF와 동일한 문제(헤더·섹션·심볼·재배치)를 DOS 호환성이라는 역사적 제약 위에서 풀어낸 구조"**로 이해하면, 왜 리버싱의 기본 개념(헤더, 섹션, import/export, 재배치)이 플랫폼을 가리지 않고 그대로 적용되는지가 같은 논리로 설명된다.

관련 글

리버싱 카테고리의 글 (10/11)

  1. 리버싱이란 무엇인가: 정적 분석 기초
  2. 어셈블리어 읽기 기초 (x86-64)
  3. 동적 분석: 디버거로 실행 흐름 추적하기
  4. 안티 디버깅 / 안티 리버싱 기법과 우회
  5. 패킹과 언패킹: 실행 파일을 압축/암호화해서 숨기기
  6. Frida로 동적 계측/후킹하기
  7. 안드로이드 앱 리버싱: APK 분석 기초
  8. iOS 앱 리버싱 기초
  9. 크랙미(Crackme) 실습 방법론
  10. PE 포맷 내부 구조: Windows 실행 파일 뜯어보기
  11. 코드 난독화 해제: Control Flow Flattening 분석하기
← 크랙미(Crackme) 실습 방법론 코드 난독화 해제: Control Flow Flattening 분석하기 →