| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- 깃
- 알고리즘
- SK쉴더스루키즈
- 루키즈 31기
- 프랑스어 #프랑스어배우기 #프랑스어독학 #델프인강 #시원스쿨프랑스어 #delf독학 #델프 #프랑스어기초 #프랑스어공부
- linux
- 다이나믹 프로그래밍
- 애플리케이션 계층
- 웹개발
- 예술의 전당
- 서울청년문화패스
- 루키즈31기
- SK쉴더스
- 우아한테크코스
- 코리안챔버오케스트라
- c
- 위상 정렬
- sk 쉴더스 루키즈
- 트랜스포트 계층
- React
- 프리코스
- 우테코
- sk쉴더스 루키즈
- Dreamhack
- 백엔드
- 악성코드 분석
- webhacking
- 진입차수
- 레나튜토리얼
- 자바
- Today
- Total
yon11b
[SK 쉴더스 루키즈] notepad로 알아보는 PE 파일 구조 (feat: PEview) 본문
사전 지식
OS는 .exe를 디스크에서 바로 실행하지 않는다.
실행하려면 먼저 프로세스를 만들고, 실행에 필요한 부분을 메모리에 매핑한 뒤 CPU가 실행하게 한다.

PE 파일 형식이란?
Windows 실행 파일 형식이다.
.exe, .dll, .sys, .scr 이런 파일들 전부 내부 구조가 PE형식이다.

PE 파일 구조

DOS Header: 옛날 윈도우 DOS에서 쓰던 것.
NT Header: DOS 이후 윈도우 버전에서 사용하는 (New Technology)
Section Header(.text): text 영역의 주소 위치를 알려주는 역할
실습: notepad.exe로 알아보는 PE 구조
환경: Windows XP
도구: PEview
분석 파일: C:\windows\system32\notepad.exe

IMAGE_DOS_HEADER
PE 파일의 첫 헤더

시그니처: MZ(0x4D5A)
*MZ: 마크 즈비코프스키


마지막에 NEW EXE header를 가리키는 것을 확인할 수 있다.
(like 연결리스트: IMAGE_DOS_HEADER -> IMAGE_NT_HEADER)
리틀 엔디안이라 뒤부터 읽는다: 00 00 00 E0 주소가 NT가 시작하는 주소이다.
Q. intel 계열에서 리틀 엔디안 쓰는 이유?
1. 하위 바이트 접근이 쉽기 때문.
-> 리틀 엔디안은 시작 주소만 읽어도 바로 LSB를 얻을 수 있다.
int x = 0x12345678;
char *p = (char*)&x;
추가 주소 계산 없이 바로 접근 가능하다.
왜 저렇게 되는지 설명
x는 4바이트 정수다.
int x = 0x12345678;
리틀 엔디안 Intel 메모리에는 이렇게 저장된다:
1000 -> 78
1001 -> 56
1002 -> 34
1003 -> 12
즉 낮은 주소에 LSB(78)가 들어간다.
*p는 char이므로 1byte임. 그래서 앞에 있는 것(78)이 먼저 출력되는 것임.
*(p+1)하면 56 출력 될거임
2. 가변 크기 정수 확장이 편함
8bit → 16bit → 32bit 확장 시, 기존 값의 메모리 시작 주소가 그대로 유지된다.
1byte: 78
2byte: 78 56
4byte: 78 56 34 12
앞부분 주소가 안 바뀌므로 하드웨어 설계가 단순해진다.
3. 산술 회로 구현에 유리
덧셈은 LSB부터 계산한다.
1234
5678
----
컴퓨터도 오른쪽(LSB)부터 carry를 전파한다.
리틀 엔디안은 메모리의 시작 주소가 LSB라서, 일부 초기 설계에서 구현이 더 자연스러웠다.
MS-DOS_Stub Program
DOS로 이 파일이 실행되었을 때는 This program cannot be run in DOS라고 하고 종료된다는 것을 알 수 있다.

즉, DOS에서 실행이 되긴하지만 종료 메시지를 출력하고 끝내버린다는 것이다.
IMAGE_NT_HEADER
PE 파일의 핵심 헤더
여기서부터 진짜 PE 헤더이다.
시그니처: PE\0\0

typedef struct _IMAGE_NT_HEADERS
{
ULONG Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
https://www.nirsoft.net/kernel_struct/vista/IMAGE_NT_HEADERS.html
IMAGE_NT_HEADERS 는 구조체로서 시그니처, IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER 를 멤버로 가진다.
IMAGE_FILE_HEADER
PE 파일의 기본 정보를 담는 헤더

Number of Sections: Sectoin의 개수 -> 3
Time Date Stamp: 컴파일(링킹)된 시간 (근데 1992로 표시되면 델파이로 만들어진 파일이라는 뜻)
size of optional header: 다음에 올 image optional header의 크기 명시
-> image optional header 는 가변크기를 가지기 때문에 크기를 명시해줘야 한다.
IMAGE_OPTIONAL_HEADER
PE 파일이 메모리에 로드되어 실행되기 위한 핵심 정보를 담고 있는 헤더
optional이라는 이름과 달리 필수 헤더임
헤더 크기 가변
실행 파일이 메모리에 로드될 기본 주소가 32bit에서는 4바이트 주소이고, 64bit에서는 8바이트 주소라서 크기가 달라진다.
예시
DWORD ImageBase; // 4바이트
ULONGLONG ImageBase; // 8바이트
변하는 필드들 전부 정리
| 필드 | PE32 (32bit) | PE32+ (64bit) | 변화 |
| BaseOfData | DWORD (4B) | 없음 | 제거됨 |
| ImageBase | DWORD (4B) | ULONGLONG (8B) | 4B → 8B |
| SizeOfStackReserve | DWORD (4B) | ULONGLONG (8B) | 4B → 8B |
| SizeOfStackCommit | DWORD (4B) | ULONGLONG (8B) | 4B → 8B |
| SizeOfHeapReserve | DWORD (4B) | ULONGLONG (8B) | 4B → 8B |
| SizeOfHeapCommit | DWORD (4B) | ULONGLONG (8B) | 4B → 8B |

Magic
32bit일 땐 0x10B 값을 가짐
64bit일 땐 0x20B 값을 가짐
이 값을 보고 32bit인지, 64bit인지 알 수 있음
MajorLinkerVersion
사용한 컴파일러 버전을 알 수 있다.
ImageBase
기본 0x400000으로 설정이 되어있다. (ASLR이 걸려 있지 않은 XP에서)

왜 40만 번지 아님???? → os가 만든 애플리케이션(notepad)은 다르다.
좀 더 자세히 보자

ImageBase
이 주소가 시작주소이다. 여기부터 notepad 프로그램이 올라간다.
Base of Code
코드 섹션(.text)의 시작 RVA(Relative Virtual Address, 상대 가상 주소)
-> Image Base + 0x1000에서 시작
*RVA; PE 파일이 메모리에 로드된 기준 주소(ImageBase)로부터 얼마나 떨어져 있는가
Address of Entry Point
쉽게 말해 main 함수 시작 위치
실제로 코드를 실행할 때는 .text 영역의 시작에서 바로 실행을 하는게 아니라 중간 어디쯤에서 시작한다.
그래서 위 그림에서도 보면 EP가 0x1000(.text)과 0x9000(.data) 사이인 0x739D에 위치하고 있음을 알 수 있다.
Size of Code
.text 영역의 코드 사이즈
Base of Data
데이터 섹션(.data)의 시작 RVA 주소
-> Image Base + 0x9000에서 시작

PE view에서 직접 확인해보자
noteapd의 시작 주소: 0x1000000

.text 영역의 시작주소: 0x1001000

.data 영역의 시작주소: 0x1009000

.text 영역의 마지막 주소: 0x10087F0 + 0x0000010 = 0x1008800

File Alignment

파일에서 간격: 0x200 바이트씩 나누어 자름
section .text -> section .data 넘어가는 예제를 보면서 확인해보자.

0x7B40~~ 줄에서 데이터는 끝났다. 하지만 0x200씩 묶어서 영역을 구분하기 때문에 이에 맞춰서 0x7BF0(+0x10)까지 NULL을 채워준다.
그리고 다음 섹션의 시작은 0x7BF0 + 0x10(16비트) = 0x7C00일 것이다.

Section Alignment
가상 메모리에서는 0x1000 바이트씩 나누어 자름
header -> section .text 넘어가는 예제를 보면서 확인해보자.

헤더가 0x1000318에서 끝났다. 0x1000씩 잘라야 하니까 header 다음에 오는 section은 0x1001000에서 시작할 것이다.

.data -> .rsrc 넘어가는 예제도 확인해보자.

0x10097f0로 끝난다. 그럼 .rsrc는 0x100A000에서 시작할 것으로 예상된다.
그런데 확인해보면 0x100B000에서 시작한다.

그 이유는 .rsrc SECTION HEADER .rsrc에서 +0xB000에서 시작하라고 명시해놨기 때문이다.

Data Directory

DataDirectory는 IMAGE_DATA_DIRECTORY 구조체로, VitualAddress, size, export, import, rsrc 디렉토리, IAT 등의 가상 주소와 크기 정보가 담겨있는 중요한 정보이다.
PEview 로 보면 이렇게 구조체 리스트 값들을 볼 수 있다.

IMPORT Table
프로그램이 빌려쓰는 외부 함수 목록
예를 들어 .exe가 kernel32.dll, user32.dll 같은 DLL 함수를 쓰고 싶을 때 기록된다.
그럼 뭐가 기록되냐?
- 가져올 DLL 이름
실행 흐름
exe 실행
↓
Loader가 Import Table 확인
↓
필요 DLL 로드
↓
함수 실제 주소 연결(IAT)
↓
프로그램 실행 가능
주로 exe, DLL에 있다.
EXPORT Table
프로그램이 외부에 제공하는 함수 목록
예를 들어 kernel32.dll 에 있는 export table에
- CreateFileW
- ReadFile
- WriteFile
- ExitProcess
를 명시해놓으면, 다른 프로그램이 kernel32.dll의 CreateFileW를 쓰는 것이 가능해진다.
주로 DLL에 있다.
IAT
가져올 주소 명시
PEview 를 통해 직접 확인해보자

import table이 0x7604 위치에 있다고 한다.
section .text에 import table이 3개가 있다. 그리고 0x7604 위치에 import table이 있음을 확인할 수 있다.

이제 import name table에 명시된 함수 하나를 잡고 따라가 보면서 구조를 파악해보자.
전체 흐름 미리보기

IMPORT Name Table
불러올 함수의 이름 목록만 명시한 테이블.
RegQueryValueExW 함수가 필요해!!
함수 이름 문자열의 위치는 0x7CA2 야.

IMPORT Hint/Name Table
0x00007CA2 로 가보면 RegQueryValueExW 문자열이 아스키로 저장되어 있음을 확인할 수 있다.

Import Address Table(IAT)
얘는 .dll 파일에서 RegQueryValueExW 함수 찾고 그 위치를 기록한 테이블이다.

실행 흐름
Windows Loader
↓
ADVAPI32.dll 파일 찾음
↓
메모리에 로드
↓
Export Table 확인
↓
RegQueryValueExW 주소 찾음
↓
IAT 연결
* ADVAPI32.dll은 Windows 시스템에 존재하는 외부 DLL이다.

Q. INT에 함수 목록 이름이 이미 있는데 hints/names이 필요한 이유가 뭔가요?
A. name table은 주소만 가지고 있고 이름은 없다. PEview에 이름이 보이는 건 보기 쉽게 만들어준것이다. 원래 실제로는 이름 없음. 주소만 있음.
그래서 INT에 있는 내가 쓸 함수의 주소를 저장하고 그 주소를 따라가면 hints/names에서 `함수의 문자열 이름`을 찾을 수가 있다.
그러면 IAT 단계에서 그 함수 문자열 이름을 보고 .dll파일에서 그 이름을 찾는다. 그리고 그 위치를 기록한다.
IMAGE_SECTION_HEADER
.text 영역에서 정상 권한은 exec, read이다. write있으면 이상한 거.

.data영역에는 exec가 있으면 안 된다.

'보안 > SK 쉴더스 루키즈' 카테고리의 다른 글
| [SK 쉴더스 루키즈] PE 구조 실습- 손상된 notepad 파일 복구하기 (0) | 2026.05.27 |
|---|---|
| [SK 쉴더스 루키즈] 윈도우 API 개념 & ADS 실습 (0) | 2026.05.27 |
| [SK 쉴더스 루키즈] AWS 모니터링 (0) | 2026.05.13 |
| [SK 쉴더스 루키즈] 딥러닝 모델 CNN, RNN (0) | 2026.05.11 |
| [SK 쉴더스 루키즈] 리눅스/윈도우 서버에서 자동 진단 수행하기 (0) | 2026.05.09 |
