← 목록으로

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

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

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 분석하기 →