1.pe파일 분석하기
1. PE파일이란?
PE(Portable Executable)파일은 윈도우 실행파일이라고 부르며 윈도우OS에서 사용되는 실행파일형식을 의미하며 UNIX의 COFF(Common Object File Format)을 기반으로 만들어졌다
=>이게 먼말이냐 이파일을 어떻게 메모에 올려서 실행을 할지 적혀있는 설계도
프로그램이 실행되려면 하드디스크에 있던 파일이 메모리(RAM) 위로 올라가야 함
윈도우 운영체제가 .exe 파일을 더블클릭 받았을 때, 이 파일 뭉치를 메모리의 어디에, 어떻게 배치해야 할지 알아야함이때 PE 파일의 앞부분(헤더)에 적힌 설계도를 읽습니다.
2. PE파일의 구조

PE는 크게 DOS Header, NT Header, Section Header, Section으로 구성되어 있다
*참고
파일에 있을 때의 위치(Offset)와 메모리에 올라갔을 때의 위치(VA)가 똑같지 않다
3. PE Header

pe파일은 헤더와 바디로 구분이 된다
PE 헤더 (DOS header ~ Section header): 프로그램이 실행되기 위한 '설계도'이자 요약표
바디는 실제 동작하는데 필요한 내용물이들어잇다
1. DOS Header
이름 그대로 아주 옛날 운영체제인 DOS 시절의 흔적
- 역할: 윈도우 실행 파일이 맞는지 확인하는 첫 번째 관문
- 특징: 헥스 에디터(HxD)로 파일을 열었을 때 맨 앞에 나오는 글자 MZ(4D 5A)가 바로 이 DOS Header의 시작을 알리는 서명

2. DOS Stub
이것도 과거의 유산
- 역할: 만약 이 윈도우용 프로그램을 옛날 DOS 환경에서 실행하려고 하면, This program cannot be run in DOS mode라는 문구를 띄우고 프로그램을 종료시키는 역할
- 특징: 요즘 환경에서는 아예 실행될 일이 없는 부분 리버싱할 때는 그냥 무시하고 넘어가셔도 됨 (빈 공간이 많아서, 악성코드 제작자들이 이 영역에 악의적인 코드를 몰래 숨겨놓는 용도로 가끔 쓰이기도 함)
3. NT Header
여기서부터가 진짜 윈도우 운영체제를 위한 메인 헤더입니다.
- 역할: 파일의 기본적인 정보들을 묶어둔 큰 껍데기
- 구조: 앞부분에는 PE라는 글자로 시작하는 서명(Signature)이 있고, 그 뒤에 File Header와 Optional Header라는 두 가지 중요한 구조체를 품고 있습니다.
- #1 Machine: 파일의 실행 대상 플랫폼을 나타냄(winnt.h에 정의됨) Intel x86(0x014C), IA64(0x200), AMD64(0x8664)
#2 NumberOfSections: 파일에 존재하는 섹션의 개수(파일에 따따라 섹션의 개수가 달라지므로)이며 반드시 0보다 커야함, (정의된 섹션 개수 > 실제 섹션 개수 : 실행 에러), (정의된 섹션 개수 < 실제 섹션 개수 : 정의된 개수만큼 인식)
#3 SizeOfOptionalHeader: Optional Header의 크기를 알려준다.
#4 Characteristics: 파일의 속성을 나타내는 값으로 실행 파일인지 DLL파일인지 등의 정보들이 bit OR연산을 통해 최종 값으로 설정된다. (winnt.h파일에 정의됨), exe파일(0x0002), dll파일(0x2000) (word니까 2바이트) 보통 3개를 가짐( .text, .data, .rsrc)
#5 TimeDateStamp: 파일의 실행에 영향을 미치지 않는 값으로, 해당 파일의 빌드 시간을 나타냄


4. Optional Header
이름은 Optional(선택적)이라고 되어 있지만, 실행 파일(.exe)에서는 절대 없어서는 안 될 가장 핵심적인 헤더입니다. x64dbg 같은 디버거로 분석할 때 가장 많이 쳐다보게 될 구역이기도 합니다.
- 역할: 프로그램이 메모리에 어떻게 올라가야(로딩되어야) 하는지에 대한 구체적인 설정값들이 다 들어있습니다.
- 특징: 프로그램이 메모리에 올라갈 때의 기준 위치, 프로그램이 처음 시작되는 지점, 파일 상태일 때와 메모리 상태일 때의 정렬 크기 등 리버싱에서 주소 계산을 할 때 필요한 모든 핵심 힌트가 여기에 다 모여있습니다.
5. DataDirectory
이 친구는 Optional Header의 맨 마지막에 위치한 디렉터리(폴더) 주소록입니다.
- 역할: 프로그램이 실행되는 데 필요한 각종 핵심 데이터들이 어디에 있는지 위치(주소)와 크기를 표 형태로 정리해 둔 곳입니다.
- 특징: 어떤 외부 함수(API)를 가져다 쓰는지 적혀있는 Import Directory(IAT), 반대로 남에게 제공하는 함수 목록인 Export Directory, 리소스 위치 등등 리버서들이 분석을 시작할 때 가장 먼저 찾아보는 정보들의 좌표가 기록되어 있습니다.
4. PE Body (notepad.exe)
- .text (Code 섹션):
- 개발자가 짠 진짜 코드(명령어)들이 들어있는 방
- C++이나 파이썬 등으로 작성한 프로그램 로직이 어셈블리어 형태로 번역되어 이곳에 저장
- .data (Data 섹션):
- 프로그램이 실행될 때 사용하는 전역 변수나 정적 변수 같은 데이터들이 저장되는 방
- .rsrc (Resource 섹션):
- 코드나 데이터가 아닌 꾸밈 요소들이 들어갑니다. 프로그램의 아이콘 모양, 마우스 커서 이미지, 경고창이나 메뉴 화면 등의 리소스가 여기에 저장됩니다.
INT: 내가 빌려 쓰고 싶은 함수의 진짜 이름(문자열)이 적혀잇음. 예를 들면 "MessageBoxA라는 이름의 함수가 필요해"라고 명시되어 있는 곳
IAT: 실제로 그 함수들이 메모리의 어디에 있는지 진짜 주소
5. PE Body (kernel32.dll)
EAT:
- 앞서 배운 IAT가 남의 기능을 빌려오기 위한 수입(Import) 목록이었다면, EAT는 반대로 내 기능을 남에게 빌려주기 위한 수출이라 생각하면 쉬움 프로그램이 내 파일에 있는 함수를 가져다 쓸 수 있도록, 함수들의 이름과 실제 메모리 주소를 정리해 둔 표라고 생각하면 이해하기 쉽습니다.
6. 간단한 질문
IAT를 제대로 찾았다면, INT와 같은 값들이 저장되어 있을텐데, 이유는?
- INT 주소 안에 적힌 값을 따라가면: MessageBoxA 같은 '함수 이름표'가 나옴
- IAT 주소 안에 적힌 값을 따라가면: INT랑 똑같이 '함수 이름표'가 나옴
즉 똑같은 곳을 가르키고 잇음
INT는 백업용
- INT: 영원히 변하지 않는 원본 보관소. 프로그램이 실행되고 IAT가 덮어씌워진 후에도, INT는 꿋꿋하게 원래 이름표를 계속 가리키고 있음 그래서 윈도우나 디버거가 원래 이름을 참고해야 할 때 언제든 열어볼 수 있는 원본
- IAT: 진짜 메모리 주소로 덮어씌워질 실전용 빈칸. 프로그램 코드는 함수를 실행할 때 무조건 이 IAT 방에 들어있는 주소만 꺼내서 쓴다
INT와 IAT x64dbg에서 찾아보기
HxD에서 정적으로 뜯@어보는거랑 x64dbg에서 실행해 프로세스가 로드 후 구조가 어떻게 달라지는가?
프로세스에서 dll파일을 가져올 때, 어떤 과정이 일어나는지 정리. (x64dbg에서 뜯어보면 더 좋고~)
HxD로 파일 뜯@어보기~
먼저 NT_header를 찾아봅시다

이건 DosHeader가 어케 생겻는지
e_lfanew를 먼저 찾아야한다
word는 2바이트
Dword는 4바이트
18*2를 하면 36인줄 알앗는데 word배열이 4개랑 10개가 잇다 2*30을 해서
60바이트 뒤에 e_lfanew가 나와잇음
60 == 0x3C

보니까 0000000E이네요

이동해보니 PE를 확인할 수 잇다
PE는 NT Header의 시그니쳐!!
(Dosheader는 별로 중요하지 않은 정보가 있어서 뛰어넘기)
*왜 리틀레디언을 쓸까?
CPU가 덧셈 같은 수학 계산을 훨씬 빠르고 효율적으로 할 수 있기 때문
*언제 리틀레디언을 쓸까?
1. 숫자(주소, 크기)는 무조건 거꾸로 읽는다
- 이유: 컴퓨터(CPU)가 덧셈, 뺄셈 같은 수학 계산을 빨리 하려고 '숫자 덩어리'를 메모리에 쑤셔 넣을 때만 일의 자리부터 거꾸로 집어넣기 때문
- 언제?: DWORD, WORD 같은 타입으로 선언된 숫자 값이나 주소, 크기를 읽을 때
- 예시: 아까 우리가 찾은 e_lfanew 값 E0 00 00 00은 위치를 나타내는 주소(숫자)니까 00 00 00 E0으로 뒤집어서 0xE0으로 읽음

NTheader는 이렇게생겼다
sizeOfOptionalHeader찾기!!
2+2+4+4+4=16
한줄에 16개가 이씀
PE시그니쳐가 4바이트를 차지하고 잇다

0B01이라고 되어잇는곳이 OptionalHeader의 Magic 값
이걸로 32비트임을 확인할 수 있
SectionHeader시작 위치 찾기
아래 사진은 다시 나온 NTHeader구요
다더하면 2 + 2 + 4 + 4 + 4 + 2 + 2 = 20바이트 = 0x14
NTheader 시작 위치는 0xE0이엇다
그리고 pe시그니쳐 크기는 4
0xE0 + 0x04 + 0x14 = 0xF8
이 된다
0xF8은 optionalheader시작

0xF8 + 0xE0 = 0x1D8
그 주소로 이동을 해보면

.text가 있는것을 확인 할 수 있다.
잘 찾아왔네요 NT Header
- RVA(Relative Virtual Address) = 프로그램이 메모리에 올라갔을 때, 시작점(ImageBase)로부터 얼마나 떨어져 있냐(오프셋)
- ImageBase = 프로그램이 메모리에 로딩되는 기준 주소
- VA(Virtual Address) = 진짜 실행 중인 절대주소
<공식>
VA = ImageBase + RVA
위에 RVA랑 ImageBase랑 이해하면 당연함(기준+ 얼마나 떨어져있냐)
RAW(오프셋) = RVA(구하려는 RVA값) – 해당 섹션 VirtualAddress(RVA값) + 해당 섹션 PointerToRawData

IAT랑 INT가 어디있는지 알려줌
1) Import Table RVA 확인
OptionalHeader의 DataDirectory[1] (Import Table) 값을 확인하면
Import Table의 RVA가 0x7604인 것을 알 수 있음
- 0x7604 = Import Table 위치(메모리, RVA)
2) 이 RVA가 어느 섹션에 속하는지 확인
Section Header를 확인해보면, 0x7604는 .text 섹션 범위에 포함됨
따라서 변환에 사용할 섹션 정보는 .text의 값을 사용함
- 0x1000 = .text 시작(메모리, VirtualAddress)
- 0x0400 = .text 시작(파일, PointerToRawData)
3) Import Table RVA → 파일 오프셋(RAW) 변환
공식:
- RAW = RVA − 해당 섹션 VirtualAddress + 해당 섹션 PointerToRawData
대입:
- 0x7604 − 0x1000 + 0x0400 = 0x6A04
- 0x6A04 = Import Table 위치(파일, RAW)
이동 해보자

INT는 00 00 79 90
IAT는 00 00 12 C4
x32dbg로 실행을 해보겟습니다
x32dbg에서 ImageBase가 0x01000000으로 로드된 것을 확인함
0x01000000 + 0x12C4 = 0x010012C4

IAT값을 찾아 가보앗슴
0x010012C4 → 메모리에서 실제 IAT 위치(VA)
INT확인해보기
0x01000000 + 0x7990 = 0x01007990

INT와 IAT의 값이 서로 다르게 나타나는 것을 확인할 수 있다
INT는 함수 이름을 가리키는 RVA 형태로 유지되지만 IAT는 프로세스 로드 후 실제 DLL 함수 주소로 패치된 값을 가진다
HxD로 확인한 PE는 디스크에 저장된 파일 구조이므로 주소가 오프셋 기준
Import 관련 테이블은 아직 해결되지 않은 상태로 존재
반면 x32dbg에서 실행 후에는 모듈이 메모리에 매핑되며 주소가 VA(ImageBase+RVA) 기준으로 해석된다
이때 Windows Loader가 Import table을 따라가 DLL을 로드하고
INT(OriginalFirstThunk)에 있는 함수 이름 목록을 기반으로 각 함수의 실제 주소를 찾아 IAT(FirstThunk)에 기록한다
따라서 로드 전에는 IAT가 실제 함수 주소가 아니지만, 로드 후에는 IAT 엔트리가 0x74xxxxxx 형태의 실제 DLL 함수 주소로 패치된 것을 확인할 수 있다.
'리버싱' 카테고리의 다른 글
| 리버싱-6주차: 지뢰찾기 분석과 핵(OllyDbg) (0) | 2026.02.26 |
|---|---|
| 리버싱 4주차 -IDA (0) | 2026.02.10 |
| 리버싱 3주차 과제 (0) | 2026.01.31 |
| 리버싱 2주차 과제 (0) | 2026.01.28 |
| 리버싱 1주차 과제 (0) | 2026.01.20 |