우리가 흔히 아는 컴파일 과정이 있다.
전처리 > 컴파일 > 어셈블 > 링크 > 실행파일 생성
이중에서도 실행파일의 PE포맷을 짧게 알아보고 실행파일 조작을 어떻게 하는지 알아보자.
obj파일 생성과 분석
우선 .obj이 어떻게 생성되는지 부터 보도록하자.
#include <iostream>
using namespace std;
int main()
{
int a = 10;
return 0;
}
위처럼 코드를 작성해주고 PowerShell을 켜준다.
(본인은 직접 .obj파일을 생성하고 링크할 것이다)
cl /c main.cpp
위 명령어를 통해 .obj파일을 뽑아내준다.
(물론 중간에 전처리, AST, IR등 어렵고 복잡한 단계를 거쳐서 .obj파일이 생성된다. 이부분은 다음에 글을 쓰도록 하겠다)

생성한 main.obj을 그냥 열면 알수 없는 외계어들이 등장한다. (이게 해독이 가능하다면 뇌가 Intel CPU와 동기화되어 있다고한다)
무튼 보기가 어려우니 어셈블리화하여 한번 보도록하자.
dumpbin /DISASM main.obj > dis.txt
위 명령어를 power shell에 입력해준다.

그럼 위처럼 dis.txt라는 텍스트 파일이 생성될 것이다.

파일을 열어보면 위처럼 생겼다. 그나마 사람이 읽을 수 있는 어셈블리로 기계어를 변환해서 보여준 모습이다.
00000000: 55 push ebp
00000001: 8B EC mov ebp,esp
10~11줄만 짧게 분석해보도록 하자. ebp라는 영어가 있는데 이는 CPU내부의 레지스터 이름이다.
ebp레지스터는 현재 스택 프레임의 베이스 주소(최하단 주소)를 저장하고 있는 레지스터이다.
즉, 함수 호출시 현재 ebp를 스택에 저장하고 esp값을 ebp에 복사해 스택 프레임을 설정한다. 는 뜻이 된다.
스택프레임은 함수 호출시 스택에 함수의 매개변수, 호출이 끝나고 난뒤 돌아갈 반환 주소 값, 함수에 선언된 지역변수등의 데이터를 저장한 구조 == 프레임을 스택프레임이라고 하는데 main함수도 결국 함수이기 때문에 ebp레지스터를 사용하는 듯하다
(머리가 어지럽다...)
0000000D: 8B E5 mov esp,ebp
0000000F: 5D pop ebp
쨋든 스택에 ebp를 push했다가 16~17줄을 보면 ebp에서 esp로 mov(이동한다)시킨다고 대충 때려 맞출 수 있고
ebp를 pop한다고 그다음줄에 적혀져있는 것을 확인할 수 있다.
이렇게 .obj파일에는 어셈블리로 볼 수 있는 형태의 기계어(opcode)가 담겨있다.
(우리가 어셈블리 코드를 본 것은 실제 .obj에 담긴 기계어를 사람이 이해할 수 있게 역변환 한 것을 본 것이다)
실행파일에는 어셈블리 코드가 그 자체가 담기는 것이 아니라, 해당 어셈블이에 대응하는 실제 CPU가 실행이 가능한 기계어 바이트들이 포함되어 있다.
exe분석
링커가 .obj들을 링킹하여 exe파일을 생성하는데 윈도우에서는 PE라는 포맷으로 실행파일을 생성한다.
실행파일에는 기계어와 다양한 메타데이터들을 포함하게 된다. (단순 16진수 데이터들만 들어가지 않는다)
(리눅스나 MAC에서는 다른 포맷을 사용한다.)
(main.obj파일이 있다면 power shell에 link main.obj로 main.exe를 생성하자)

그리고 main.exe를 16진수로 보게 되면 위와같은 알 수 없는 글자들이 적혀져있다..
너무 어지러우니 PE 포맷으로 위의 실행파일 구조를 보도록 하자.
아래 명령어를 통해 PE 헤더, 섹션 구조, 주소 정보를 텍스트로 확인할 수 있다.
dumpbin /headers main.exe > ExeOut.txt
위 명령어를 통해 아래와 같은 .txt파일을 열어서 확인할 수 있다.
Microsoft (R) COFF/PE Dumper Version 14.38.33145.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file main.exe
PE signature found
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
14C machine (x86)
5 number of sections
688A0EAB time date stamp Wed Jul 30 21:23:07 2025
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
102 characteristics
Executable
32 bit word machine
OPTIONAL HEADER VALUES
10B magic # (PE32)
14.38 linker version
DC00 size of code
8800 size of initialized data
0 size of uninitialized data
1258 entry point (00401258)
1000 base of code
F000 base of data
400000 image base (00400000 to 00419FFF)
1000 section alignment
200 file alignment
6.00 operating system version
0.00 image version
6.00 subsystem version
0 Win32 version
1A000 size of image
400 size of headers
0 checksum
3 subsystem (Windows CUI)
8140 DLL characteristics
Dynamic base
NX compatible
Terminal Server Aware
100000 size of stack reserve
1000 size of stack commit
100000 size of heap reserve
1000 size of heap commit
0 loader flags
10 number of directories
0 [ 0] RVA [size] of Export Directory
14A5C [ 28] RVA [size] of Import Directory
0 [ 0] RVA [size] of Resource Directory
0 [ 0] RVA [size] of Exception Directory
0 [ 0] RVA [size] of Certificates Directory
19000 [ F78] RVA [size] of Base Relocation Directory
13F18 [ 1C] RVA [size] of Debug Directory
0 [ 0] RVA [size] of Architecture Directory
0 [ 0] RVA [size] of Global Pointer Directory
0 [ 0] RVA [size] of Thread Storage Directory
13E58 [ 40] RVA [size] of Load Configuration Directory
0 [ 0] RVA [size] of Bound Import Directory
F000 [ 124] RVA [size] of Import Address Table Directory
0 [ 0] RVA [size] of Delay Import Directory
0 [ 0] RVA [size] of COM Descriptor Directory
0 [ 0] RVA [size] of Reserved Directory
SECTION HEADER #1
.text name
DA3D virtual size
1000 virtual address (00401000 to 0040EA3C)
DC00 size of raw data
400 file pointer to raw data (00000400 to 0000DFFF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
60000020 flags
Code
Execute Read
SECTION HEADER #2
.rdata name
60E2 virtual size
F000 virtual address (0040F000 to 004150E1)
6200 size of raw data
E000 file pointer to raw data (0000E000 to 000141FF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
40000040 flags
Initialized Data
Read Only
Debug Directories
Time Type Size RVA Pointer
-------- ------- -------- -------- --------
688A0EAB coffgrp 2DC 000141AC 131AC
SECTION HEADER #3
.data name
1318 virtual size
16000 virtual address (00416000 to 00417317)
A00 size of raw data
14200 file pointer to raw data (00014200 to 00014BFF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
C0000040 flags
Initialized Data
Read Write
SECTION HEADER #4
.fptable name
80 virtual size
18000 virtual address (00418000 to 0041807F)
200 size of raw data
14C00 file pointer to raw data (00014C00 to 00014DFF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
C0000040 flags
Initialized Data
Read Write
SECTION HEADER #5
.reloc name
F78 virtual size
19000 virtual address (00419000 to 00419F77)
1000 size of raw data
14E00 file pointer to raw data (00014E00 to 00015DFF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
42000040 flags
Initialized Data
Discardable
Read Only
Summary
2000 .data
1000 .fptable
7000 .rdata
1000 .reloc
E000 .text
위 구조는 PE 포맷의 핵심정보들이다. 실행파일은 아래와같은 구조로 표현할 수 있다.
[ DOS Header (MZ) ]
[ PE Signature ('PE\0\0') ]
[ COFF File Header ]
[ Optional Header ]
[ Section Headers (.text, .data, .rdata, .reloc 등) ]
[ 각 섹션 데이터 (코드, 상수, 전역변수 등) ]
이중에서도 눈여겨볼 부분은 아래 부분이다.
SECTION HEADER #1
.text name
DA3D virtual size
1000 virtual address (00401000 to 0040EA3C)
DC00 size of raw data
400 file pointer to raw data (00000400 to 0000DFFF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
60000020 flags
Code
Execute Read
.text라는 영역인데 이 영역은 실제 실행될 소스코드가 담기는 메모리 영역이다.
실행파일의 해당 주소를 찾아서 int a = 10;부분을 찾고 10이라는 데이터 담긴 값을 수정할 수 있다!
그리고 이것을 해보려고 한다.
SECTION HEADER #1
.text name → 코드 영역
DA3D virtual size → 0xDA3D = 55869 bytes (메모리에 올라가는 크기)
1000 virtual address → RVA: 0x1000, 메모리상 위치: 0x00401000
DC00 size of raw data → 0xDC00 = 56320 bytes (파일에 저장된 크기)
400 file pointer to raw data → 0x400 (파일 내 오프셋 위치)
60000020 flags
Code
Execute Read
1000 virtual address -> RVA : 0x1000이라고 되어있고 메모리상 위치는 0x00401000이고 .text파일 offset은 0x400이다.
즉 0x00401000에 실행파일의 위치가 적힌 것이다.
근데 HxD라는 프로그램을 이용하면 보다 쉽게 찾을 수 있다.

어셈블리 코드를 보면 mov dword ptr[ebp-4], 0Ah라고 되어 있고 이것을 16진수로 나타내면
C7 45 FC 0A 00 00 00 이 된다. 이게 실행파일 어딘가에 분명히 있을 것이라는 기대하에 HxD로 (텍스트 편집기) 찾아보자

위는 main.exe를 16진수로 열어 봤을때이다.
0x00000400에 C7 45 FC 0A 00 00 00이라는게 분명히 존재한다.
(10이라는 값을 저장했는데 0A 00 00 00 이 나오는 이유는 리틀에디안 방식을 따르기 때문이다.)

위 부분을 FF 로 변경하면 위처럼 된다. (컨트롤 + S 로저장)

실제로 실행하면 255가 찍히는 것을 볼 수 있다. (중간에 printf('%d", a)로 코드를 넣고 실행파일을 생성했다.
정리
윈도우 실행파일 포맷인 PE가 뭔지 정말 겉할기 식으로 알아보았고 실행파일이 조작이 가능하다는 것도 알았다.
때문에 옛날 게임들의 경우 리버싱을 통해 무적치트같은 버그를 사용할 수 있었다고 한다.
최근에는 당연히 통하지 않는다. 요즘은 보안 변수등을 통해 직접적으로 변수에 대입하여 치트키그를 쓸 수 있는 부분들은 원천 봉쇄하고있고 데디케이티드 서버를 두고 값을 검증하기 때문이다.
그래도 실행파일을 조작하여 데이터를 변경할 수 있다는 것을 직접 알아보는데 의미가 있고 리버싱이 이런 원리를 기초로한다고 하니 CS공부와 컴파일 과정등 밑바닥 원리에 대한 관심을 꾸준히 가지고 공부를 해야하는듯 하다고 생각한다.
다들 화이팅
'CS' 카테고리의 다른 글
| PCB와 TCB (1) | 2025.08.30 |
|---|---|
| Floating Point 란? (2) | 2025.07.21 |
| 쓰레드와 컨텍스트 스위칭 (1) | 2024.09.18 |
| Stack Frame과 함수 호출 규약(__stdcall) (1) | 2024.04.28 |
| [CS] 가상 메모리와 페이징 (1) | 2023.08.15 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!