Palworld 내부 DLL 주입 흐름을 분석하고, Detours 기반 사용자 모드 안티치트의 DLL 화이트리스트 정책을 구현했습니다.
1차 실패 후 실제 Injector를 다시 분석해
LoadLibraryA·W·ExA·ExW 경로로 감시 범위를 넓혔고, 2차 테스트에서 핵 DLL 미주입·핵 UI 미표시와 게임 정상 동작을 확인했습니다.모든 분석과 검증은 교육 목적의 로컬 테스트 환경에서 수행했습니다. 이 페이지는 사용자 모드 기본 프로젝트만 다루며, 커널 핸들 접근 제어는 별도의 후속 프로젝트로 분리했습니다.
PROJECT OVERVIEW
항목 | 내용 |
기간 | 2024.05.05–2024.07.08 · 기본 프로젝트 기록 기준 |
수행 형태 | WhiteHat School 2기 · WhiteGang 팀 프로젝트 |
문제 | Palworld 내부 핵의 객체 접근·DLL 주입 흐름을 분석하고, 사용자 모드에서 DLL 로딩과 디버거 실행 흔적을 관찰·제한할 수 있는 지점을 찾는 문제 |
데이터·대상·환경 | Windows x64 · Steam Palworld · Unreal Engine · 팀 내부 테스트 핵 |
분석·구현 범위 | Unreal Engine 객체·SDK 구조 분석, CreateRemoteThread 기반 주입 흐름 정리, Detours 인라인 후킹과 DLL 허용 정책, LoadLibraryA·W·ExA·ExW 경로 확장, 3차례의 Anti-Debugger 구현 |
결과 | 2차 테스트에서 핵 DLL 미주입과 게임 정상 동작을 확인하고, 반복 실험과 GUI 실행 흔적 분석을 통해 WinDbg·Cheat Engine 탐지에 성공 |
후속 프로젝트 |
PalAnticheat
foxirain
핵심 질문 — Palworld 내부 핵의 객체 접근·DLL 로딩과 디버거 실행 과정에서 사용자 모드 안티치트가 실제로 관찰하고 제한할 수 있는 지점은 어디인가?
Project Flow
단계 | 판단 기준 | 산출물 |
1. 대상 분석 | 게임 상태와 내부 객체에 어떤 구조로 접근하는가? | Unreal Engine 객체·SDK와 내부 핵의 객체 접근 경로 분석 |
2. 주입 흐름 정리 | 팀 내부 핵의 DLL 주입이 어떤 Windows API와 프로세스 경계를 통과하는가? | OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread → LoadLibraryA 흐름 정리 |
3. 방어 지점 선택·구현 | 게임 내부에서 적은 오버헤드로 로딩 요청을 제한할 수 있는 지점은 어디인가? | Detours 인라인 후킹과 DLL 화이트리스트 1차 구현 |
4. 공격·방어 재검증 | 경고가 아니라 핵 DLL 미주입과 게임 정상 동작을 함께 확인했는가? | 1차 실패 기록, Injector 재분석, 4종 LoadLibrary 경로 확장과 2차 차단 검증 |
5. Anti-Debugger | 정형화된 API가 실패할 때 실제 실행 중인 도구를 어떤 흔적으로 구분할 수 있는가? | 5초 주기 검사와 세 가지 추가 신호 구현, Spy++ 기반 기준 전환, WinDbg·Cheat Engine 탐지 |
ROLE
Game Security Researcher · User-Mode Anti-Cheat Developer- 엔진·위협 분석 — Unreal Engine 객체·SDK 구조와 Palworld 내부 핵의 객체 접근·DLL 주입 경로 분석
- 사용자 모드 방어 구현 — Detours 기반 x64 DLL 화이트리스트와
LoadLibraryA·W·ExA·ExW경로 확장
- 공격·방어 검증 — 핵 개발 담당자와 동일 Injector로 1·2차 테스트를 진행하고 핵 DLL 미주입과 게임 정상 동작 확인
- Anti-Debugger 설계·구현 — 1·2·3차 탐지 로직 구현, 실패 원인 분석, Spy++ 기반 신호 전환과 WinDbg·Cheat Engine 탐지 검증
TECH STACK
- Language —
C·C++
- Platform·Security —
Windows x64·Windows API·Microsoft Detours
- Reverse Engineering —
WinDbg·IDA·ReClass.NET·Spy++
- Engine·Build —
Unreal Engine·Dumper-7·Visual Studio
MY WORK
1. 엔진 구조에서 핵의 공격 표면까지 역추적
게임에서 보이는 값부터 곧바로 수정하기보다, 그 값이 어떤 이름과 객체로 관리되고 실제 게임 월드까지 어떻게 연결되는지 먼저 복원하는 것을 출발점으로 삼았습니다. 5월 회의에서
FName → UObject → SDK Dumper를 분석 순서로 정한 뒤, 여러 주에 걸쳐 하나씩 실제 메모리 구조와 연결했습니다.처음에는 PDB가 없는 데모 게임에서
FName을 찾으려 했지만 분석 지점을 잡지 못했습니다. PDB가 포함된 Unreal Engine 샘플을 직접 빌드하기 위해 노트북과 PC방 환경까지 바꿔가며 시도했지만 긴 빌드 시간, 저장 공간 부족과 크래시를 겪었습니다. 기존 UE4 SDK Generator도 빌드 오류와 버전 차이로 적용되지 않았습니다. 이 시행착오 뒤에 도구가 답을 만들어 주길 기다리지 않고 Unreal Engine 원본 소스로 돌아갔습니다.FNamePool — 자료가 부족한 구조를 직접 복원
NameTypes.h와 UnrealNames.cpp에서 FName 생성자부터 FNamePool::Store, FNameEntry, FNameEntryHeader, FNamePoolShard, NameEntryAllocator로 이어지는 문자열 저장 흐름을 추적했습니다. 구조를 이해하기 위해 국내외 자료를 넓혀 찾았고, 당시 중국 Zhihu에 정리된 FNamePool 구조도까지 확보해 엔진 소스와 비교했습니다.
중국 Zhihu에서 찾은 FNamePool·Shard·Allocator 전체 구조도

문자열 해시부터 Slot·Block·FNameEntry로 이어지는 참조 흐름
참고 자료를 그대로 결론으로 사용하지는 않았습니다. Singleton 접근, 문자열·바이트 패턴과 IDA 참조 분석을 각각 시도했고, PDB가 포함된 Lyra 샘플을 WinDbg에 연결해 심볼 재로딩과 생성자 브레이크포인트를 반복했습니다. x64에서 생성자의
RCX가 this를 가리킨다는 점을 이용해 실제 FNamePool 주소를 잡고, 모듈 베이스와의 차이로 0x11DBB680 오프셋을 계산했습니다. 같은 위치를 IDA와 ReClass에서 다시 확인해 NameDump까지 연결했습니다.
IDA에서 FNamePool 전역 위치와 오프셋을 찾은 기록

ReClass에서 FNamePool 메모리를 펼쳐 NameDump 구조를 확인한 화면
FUObjectArray — 이름 체계를 실제 객체 그래프로 연결
FNameDump로 이름을 해석한 다음에는
GUObjectArray → FUObjectArray → FChunkedFixedUObjectArray → FUObjectItem → UObjectBase를 엔진 소스에서 역추적했습니다. WinDbg에서 GUObjectArray 위치를 구해 0x11EA83E0 오프셋을 계산하고, 이중 포인터와 FUObjectItem의 0x20 단위를 따라 실제 UObject에 접근했습니다. 이후 ClassPrivate, NamePrivate, OuterPrivate를 분석하고 ComparisonIndex를 앞서 만든 FNameDump와 연결해 객체의 클래스·이름·소속 관계를 복원했습니다.
GUObjectArray → FUObjectItem → UObjectBase 포인터 관계를 확인한 화면

전체 구조 분석을 마친 뒤 Dumper-7로 생성한 CppSDK와 GObjects Dump
GWorld — 현재 월드에서 ULevel과 AActor까지 확장
객체 하나를 해석하는 데서 멈추지 않고
UObjectBase → UObject → UWorld 상속 관계와 GWorld의 UWorldProxy 구조를 따라갔습니다. WinDbg에서 UWorld.PersistentLevel의 0x30 오프셋과 ULevel.Actors의 0xA0 위치를 확인해, 현재 월드에서 Level을 거쳐 TArray<TObjectPtr<AActor>>에 도달하는 경로를 정리했습니다. 이를 통해 게임의 캐릭터와 오브젝트가 어떤 메모리 관계로 노출되는지 설명할 수 있게 됐습니다.
WinDbg에서 확인한 UWorld와 PersistentLevel 구조

ULevel 내부 Actors 배열과 AActor 포인터 구조
Dump·SDK — 분석을 실제 핵의 객체 접근 경로로 연결
분석한 구조를 NameDump와 ObjectDump 코드로 옮기고, Dumper-7을 x64 Release로 빌드해
CppSDK, GObjects-Dump.txt, DumpSpace와 IDA Mapping을 생성했습니다. 생성 결과를 정답처럼 받아들이지 않고 앞서 확인한 FName·UObject 구조와 대조했습니다. 이후 팀 내부 핵의 PalLauncher → NetCrack-Palworld → PalworldSDK 흐름을 읽으며, SDK가 GWorld와 플레이어 객체를 거쳐 실제 게임 기능에 접근하는 방식까지 연결했습니다.이 과정의 핵심 성과는 오프셋 몇 개를 찾은 것이 아니라, 해외 참고 자료와 엔진 소스에서 세운 가설을 IDA·WinDbg·ReClass로 교차 검증하고, 그 결과를 Dump·SDK와 실제 핵 분석까지 이어 간 것입니다. 이 선행 연구를 통해 “핵이 어떤 값을 바꾸는가”뿐 아니라 “게임 객체에 접근하기 위해 어떤 구조와 진입 경로를 사용하는가”를 안티치트의 방어 질문으로 전환했습니다.
2. 실제 Injector의 프로세스 경계를 기준으로 위협 모델 정의
팀에서 테스트한 Palworld Internal Hack과 Injector 코드를 직접 읽어
OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread → LoadLibraryA로 이어지는 주입 경로를 정리했습니다. 여기서 중요한 것은 API 이름의 나열이 아니라, 각 호출이 어느 프로세스에서 실행되는지 구분하는 것이었습니다.
팀 테스트 Injector에서 확인한 CreateRemoteThread 기반 DLL 주입 코드
방어 후보는 구현 난이도보다 관찰 범위와 실행 비용을 기준으로 비교했습니다.
후보 | 판단 | 결정 |
외부 프로세스 상시 스캔 | 넓게 볼 수 있지만 지속적인 탐색 비용과 이름 기반 오탐 가능성이 큼 | 보류 |
명령·바이트 패턴 탐지 | 게임 빌드와 핵 구현 변화에 민감하고 유지 비용이 큼 | 보류 |
게임 내부 DLL 로딩 지점 후킹 | 실험 범위가 명확하고 요청 시점에만 정책을 적용할 수 있음 | LoadLibraryA 1차 구현으로 채택 |
따라서 방어 범위를 막연하게 넓히기보다, 팀 내부 핵으로 반복 재현할 수 있는 LoadLibrary 기반 DLL 로딩 경로를 첫 번째 위협 모델로 정했습니다. 구현 이후에는 경고 발생 여부가 아니라 핵 DLL이 실제로 주입되는지를 기준으로 검증하기로 했습니다.
3. Detours 기반 x64 DLL 로딩 정책 구현
설계 기준 — DLL 로딩을 전부 막지 않고, 게임의 정상 동작을 유지하면서 허용되지 않은 런타임 로딩만 제한했습니다.
팀 내부 Injector의 동작을 따라가며 원격 스레드가 최종적으로 게임 프로세스 내부의
LoadLibraryA를 실행해 핵 DLL을 올린다는 점에 주목했습니다. 초기에는 API 후킹, 핵에서 반복되는 명령어 탐지, 외부 프로세스 탐지를 후보로 검토했고, 실제 주입 흐름과 직접 연결되는 LoadLibrary 호출을 첫 방어 지점으로 선택했습니다.IAT·EAT·인라인 후킹을 비교한 뒤 Microsoft Detours를 이용한 인라인 후킹을 채택했습니다. 대상 함수의 진입점을 검사 함수로 연결하면서도 trampoline을 통해 원래 함수를 다시 호출할 수 있어, 정상 요청과 비인가 요청을 분기하는 정책을 구현할 수 있었습니다.

Detours가 대상 함수의 첫 명령어를 detour 함수로 연결하고 원래 함수는 trampoline으로 보존하는 구조를 확인한 당시 자료
그러나
LoadLibrary 자체를 전부 차단하면 게임이 실행 중 필요에 따라 불러오는 정상 DLL까지 막힐 수 있었습니다. 프로세스 시작 시 로드되는 주요 모듈과 런타임의 명시적 DLL 로딩을 구분한 뒤, 알려진 게임 DLL은 허용하고 그 밖의 요청만 거부하는 화이트리스트 정책으로 설계를 구체화했습니다.
프로세스 시작 → 주요 DLL 로드 → 안티치트 로드 → 이후 DLL Injection으로 이어지는 순서를 당시 직접 정리한 흐름
구현에 앞서 Detours를 Palworld와 같은 x64 환경에서 사용할 수 있도록 빌드 환경부터 정리했습니다. Visual Studio Developer Command Prompt에서
nmake를 실행하고 DETOURS_TARGET_PROCESSOR=X64 설정으로 x86 라이브러리와 x64 타깃의 머신 타입 충돌을 해결했습니다. 이어 누락된 .NET Framework 개발 도구와 Detours include·library 링크 경로를 구성했습니다.안티치트 DLL이 로드되면
Known_dll_init()으로 허용 DLL 이름을 std::set에 구성하고, GetModuleHandle과 GetProcAddress로 kernel32.dll의 실제 함수 주소를 구했습니다. 이후 DetourTransactionBegin → DetourUpdateThread → DetourAttach → DetourTransactionCommit 순서로 후킹을 적용했습니다.HMODULE WINAPI Hooked_LoadLibraryA(LPCSTR lpLibFileName) { std::string dllname(lpLibFileName); if (DLLset.find(dllname) == DLLset.end()) return NULL; return Real_LoadLibraryA(lpLibFileName); }
허용 목록에 포함된 요청은 원래
LoadLibraryA로 전달하고, 포함되지 않은 요청은 NULL을 반환하도록 분기했습니다. DLL이 해제될 때는 DetourDetach로 적용한 후킹을 정리했습니다. 이를 통해 단순히 DLL 로딩을 탐지하는 데서 끝내지 않고, 게임의 정상 실행을 유지하면서 비인가 DLL 로딩만 제한하는 실행 정책을 구현했습니다.4. 공격·방어 재현으로 1차 실패를 확인하고 가설 수정
핵 개발 담당자와 같은 Injector를 이용해 공격·방어 테스트를 진행했습니다. 1차 실험에서는 경고창이 여러 번 나타났지만 이후 핵 UI가 정상적으로 실행됐습니다. 경고가 발생했다는 사실만으로 성공으로 판단하지 않고, 목표였던 핵 실행 제한에 실패한 것으로 기록했습니다.

1차 테스트: DLL 로딩 경고 발생

경고 이후에도 실행된 핵 UI

2차 구현 적용 후 핵 UI 없이 정상 동작한 게임 화면
경고창이 뜬 것만으로는 핵을 막았다고 할 수 없었습니다. 첫 번째 테스트에서 핵 UI가 그대로 실행되는 것을 보고 Injector 코드를 다시 확인했고,
LoadLibraryA·W·ExA·ExW로 후킹 범위를 넓혔습니다. 두 번째 테스트에서는 핵 UI가 나타나지 않았고 게임도 정상적으로 실행됐습니다.단계 | 관찰 | 판단과 다음 행동 |
1차 PoC | 경고가 반복됐지만 핵 UI 실행 | 호출 관찰과 차단 성공을 분리하고 Injector 재분석 |
Injector 재분석 | 실제 사용 도구의 코드에서 LoadLibraryA·W·ExA·ExW 경로 확인 | 1차 구현이 LoadLibraryA 한 경로만 가정했음을 확인 |
2차 구현 | LoadLibraryA·W·ExA·ExW로 감시 범위 확장 | 분석에서 확인한 우회 경로를 방어 로직에 반영 |
2차 재검증 | 핵 DLL 미주입·핵 UI 미표시, 게임 정상 동작 | 화이트리스트 기반 안티치트의 차단 성공을 당시 결과로 기록 |
5. Anti-Debugger를 세 차례 구현하며 탐지 기준을 다시 설계
LoadLibrary 기반 DLL 주입을 차단한 뒤에는, 디버거를 이용한 동적 분석까지 탐지하기 위해 Anti-Debugger를 별도 기능으로 확장했습니다. 목표는 탐지 API를 많이 붙이는 것이 아니라 실제 WinDbg와 Cheat Engine을 실행했을 때 끝까지 구분할 수 있는 신호를 찾는 것이었습니다.
1차 — 공식 API를 단발성 검사가 아닌 주기 검사로 구현
Microsoft 문서에서
IsDebuggerPresent와 CheckRemoteDebuggerPresent의 동작을 조사한 뒤, SetTimer와 TimerProc을 연결해 CheckForDebugger()가 5초마다 실행되도록 설계했습니다. Anti-Cheat DLL이 로드된 뒤에 디버거가 붙는 상황까지 계속 확인하기 위한 구조였습니다.Victim 프로세스에 WinDbg를 직접 연결해 테스트했지만 탐지하지 못했습니다. 이 결과를 단순한 구현 오류로 넘기지 않고, PEB의
BeingDebugged처럼 사용자 모드에서 확인하는 상태값에 의존하는 방식은 실제 디버거 실행 여부와 다른 값을 반환할 수 있다고 판단했습니다.
1차 구현: WinDbg를 Victim 프로세스에 연결했지만 탐지하지 못한 화면

2차 구현: 세 가지 신호를 추가한 뒤에도 WinDbg 탐지에 실패한 화면
2차 — 서로 다른 세 가지 신호를 추가하고 다시 검증
한 API의 결과에만 기대지 않기 위해 탐지 방식을 세 방향으로 확장했습니다.
ntdll.dll에서 NtQueryInformationProcess를 동적으로 찾아 디버그 상태를 조회하고, RaiseException(EXCEPTION_BREAKPOINT)의 처리 흐름과 GetThreadContext로 가져온 Debug Register를 함께 검사했습니다. 세 로직을 실제 CheckForDebugger() 흐름에 추가해 다시 테스트했지만 WinDbg는 여전히 탐지되지 않았습니다.여기서 API를 계속 덧붙이는 대신 무엇을 검사해야 실제 실행 중인 도구를 구분할 수 있는가를 다시 생각했습니다. 프로세스 내부 플래그에서 벗어나, WinDbg와 Cheat Engine이 실행되면서 운영체제에 남기는 GUI 정보를 새로운 관찰 대상으로 정했습니다.
3차 — Spy++로 WinDbg의 실행 흔적을 비교해 고정 패턴 추출
Spy++로 WinDbg의 Window Class를 확인한 뒤 프로그램을 다시 실행해 같은 위치를 한 번 더 비교했습니다. 두 실행에서 뒤쪽 식별자는 달랐지만
HwndWrapper[DbgX.Shell;; 접두사는 반복됐습니다. 전체 문자열을 고정값으로 가정하지 않고, 실행마다 달라지는 부분과 계속 유지되는 부분을 분리해 탐지 기준을 만들었습니다.
첫 번째 실행에서 확인한 WinDbg Window Class

재실행 후 달라진 값과 반복되는 접두사
GetTopWindow와 GetNextWindow로 최상위 창을 순회하고, GetClassName으로 얻은 문자열에 공통 접두사가 포함되는지 검사하는 IsWinDbgRunning()을 구현했습니다. 그 결과 앞선 두 구현에서 찾지 못했던 WinDbg 탐지에 성공했습니다.
공통 Window Class 접두사를 기준으로 WinDbg 탐지에 성공한 화면
Cheat Engine — 같은 기준을 강제하지 않고 Window Title로 전환
Cheat Engine에도 Window Class 방식을 적용해 보았지만
Window, Button처럼 다른 프로그램도 사용하는 일반적인 값만 노출되어 식별 기준으로 사용할 수 없었습니다. 도구마다 같은 신호를 강제하지 않고, 버전 문자열이 달라져도 공통으로 남는 Window Title의 Cheat Engine 문자열을 기준으로 바꿨습니다.EnumWindows로 시스템의 창을 열거하고 GetWindowText로 제목을 읽는 IsCheatEngineRunning()을 구현했습니다. 제목에 Cheat Engine이 포함된 창을 찾으면 열거를 중단하고 탐지 결과를 반환하도록 구성해 실제 탐지까지 확인했습니다.
Cheat Engine의 Window Title에서 버전과 무관한 공통 문자열을 찾은 화면

Window Title 열거 방식으로 Cheat Engine 탐지에 성공한 화면
Anti-Cheat DLL 실행 흐름에 최종 통합
완성한 탐지 함수를 별도 PoC로 남기지 않고 Anti-Cheat DLL의
DllMain에 연결했습니다. DLL_PROCESS_ATTACH 시점에 WinDbg와 Cheat Engine을 먼저 확인하고, 발견되지 않으면 기존의 5초 주기 검사를 시작하도록 구성했습니다.if (IsWinDbgRunning()) { MessageBox(NULL, TEXT("WinDbg detected!"), TEXT("Anticheat_WG"), MB_OK); } else if (IsCheatEngineRunning()) { MessageBox(NULL, TEXT("CheatEngine detected!"), TEXT("Anticheat_WG"), MB_OK); } else { SetDebuggerCheckTimer(); }
RESULTS
성과 | 결과 | 검증 근거 |
엔진·핵 분석 | FNamePool → FUObjectArray → UObject → GWorld → ULevel → AActor 구조를 복원하고 NameDump·ObjectDump·CppSDK 생성까지 연결 | Unreal Engine 소스와 IDA·WinDbg·ReClass 결과를 교차 확인 |
위협 모델 | 팀 내부 Injector의 OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread → LoadLibraryA 흐름과 프로세스 경계를 정리 | 실제 Injector 소스를 기준으로 LoadLibrary를 첫 방어 지점으로 선정 |
DLL 로딩 방어 | Detours와 DLL 화이트리스트를 결합하고 LoadLibraryA·W·ExA·ExW 네 경로로 확장 | 1차 실패 후 재분석했으며, 2차 테스트에서 핵 DLL 미주입·핵 UI 미표시와 게임 정상 동작을 함께 확인 |
Anti-Debugger | 1·2차 탐지 실패 뒤 GUI 실행 흔적으로 기준을 전환해 WinDbg의 Window Class 접두사와 Cheat Engine의 Window Title 탐지 구현 | Spy++ 비교 기록과 WinDbg·Cheat Engine 탐지 성공 확인 |
한계
CreateRemoteThread후킹은 게임 프로세스 내부 호출에 적용된 구조였으며, 최종 차단은 게임 내부의LoadLibraryA·W·ExA·ExW경로에서 검증했습니다.
- DLL 주입 검증은 팀 내부 Injector에서 확인한 네 가지
LoadLibrary계열 경로에 한정했으며, 그 밖의 주입 방식은 실험하지 않았습니다.
배운 점과 다음 단계
후킹할 API의 개수보다 공격 코드가 어느 프로세스와 권한 경계에서 실행되는지 먼저 구분해야 한다는 점을 배웠습니다. 또한 실패 결과를 다시 관찰해 탐지 기준을 바꾸는 과정이 방어 범위를 넓혔고, 사용자 모드에서 보이지 않는 접근을 다루기 위해 후속 프로젝트에서는 방어 지점을 Windows 커널의 프로세스 핸들 생성 단계로 옮겼습니다.
또 하나 크게 배운 것은 방어는 공격보다 훨씬 어렵다는 점이었습니다. 공격자는 여러 경로 중 하나만 성공시키면 되지만, 방어자는 게임의 정상 동작을 해치지 않으면서 실제로 사용될 수 있는 공격 경로를 찾아 막아야 했습니다. 우리도 핵을 차단하기 전에 핵이 게임 객체에 어떻게 접근하고 DLL이 어떤 흐름으로 주입되는지부터 분석해야 했습니다. 그러나 1차 구현에서는
LoadLibraryA만 후킹했고, 경고창이 나타났음에도 핵 UI가 실행됐습니다. 실제 Injector의 코드를 다시 확인한 뒤에야 우리가 예상하지 못한 LoadLibraryW·ExA·ExW 경로가 있다는 것을 알았고, 이를 방어 로직에 반영해야 했습니다.이 경험을 통해 방어는 한 번의 구현으로 끝나는 작업이 아니라, 공격자의 구현을 계속 분석하고, 놓친 경로를 찾아 위협 모델과 방어 정책을 반복해서 수정하는 과정이라는 점을 배웠습니다. 창은 여러 빈틈 중 하나만 뚫으면 되지만, 방패는 정상적인 흐름을 지키면서 공격이 들어올 수 있는 방향을 계속 예상하고 검증해야 했습니다. 그래서 모든 공격 기법을 하나씩 따라 막기보다, 공격이 반드시 통과하는 지점과 더 높은 권한의 경계를 찾는 것이 중요하다고 판단했고, 이는 이후 방어 지점을 사용자 모드에서 커널의 프로세스 핸들 생성 단계로 옮기는 계기가 됐습니다.