WHS 2nd , WhiteGang — PalAnticheat: 사용자 모드 안티치트

WHS 2nd , WhiteGang — PalAnticheat: 사용자 모드 안티치트

기간
May 5, 2024 → Jul 8, 2024
분야
보안
개발
page icon
Palworld 내부 DLL 주입 흐름을 분석하고, Detours 기반 사용자 모드 안티치트의 DLL 화이트리스트 정책을 구현했습니다.
1차 실패 후 실제 Injector를 다시 분석해 LoadLibraryA·W·ExA·ExW 경로로 감시 범위를 넓혔고, 2차 테스트에서 핵 DLL 미주입·핵 UI 미표시와 게임 정상 동작을 확인했습니다.
page icon

왜 이 프로젝트를 시작했는가

WhiteGang 기본 프로젝트에서는 Palworld의 Unreal Engine 객체 구조와 팀에서 제작한 내부 핵의 동작을 분석했습니다. 기능을 나열하기보다 실제 주입 흐름이 통과하는 지점을 찾아 적은 오버헤드로 제한할 수 있는지 확인하고, 사용자 모드 안티치트가 어디까지 방어할 수 있는지를 검증하고자 했습니다.
모든 분석과 검증은 교육 목적의 로컬 테스트 환경에서 수행했습니다. 이 페이지는 사용자 모드 기본 프로젝트만 다루며, 커널 핸들 접근 제어는 별도의 후속 프로젝트로 분리했습니다.

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

  • LanguageC · C++
  • Platform·SecurityWindows x64 · Windows API · Microsoft Detours
  • Reverse EngineeringWinDbg · IDA · ReClass.NET · Spy++
  • Engine·BuildUnreal 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.hUnrealNames.cpp에서 FName 생성자부터 FNamePool::Store, FNameEntry, FNameEntryHeader, FNamePoolShard, NameEntryAllocator로 이어지는 문자열 저장 흐름을 추적했습니다. 구조를 이해하기 위해 국내외 자료를 넓혀 찾았고, 당시 중국 Zhihu에 정리된 FNamePool 구조도까지 확보해 엔진 소스와 비교했습니다.
notion image
중국 Zhihu에서 찾은 FNamePool·Shard·Allocator 전체 구조도
notion image
문자열 해시부터 Slot·Block·FNameEntry로 이어지는 참조 흐름
참고 자료를 그대로 결론으로 사용하지는 않았습니다. Singleton 접근, 문자열·바이트 패턴과 IDA 참조 분석을 각각 시도했고, PDB가 포함된 Lyra 샘플을 WinDbg에 연결해 심볼 재로딩과 생성자 브레이크포인트를 반복했습니다. x64에서 생성자의 RCXthis를 가리킨다는 점을 이용해 실제 FNamePool 주소를 잡고, 모듈 베이스와의 차이로 0x11DBB680 오프셋을 계산했습니다. 같은 위치를 IDA와 ReClass에서 다시 확인해 NameDump까지 연결했습니다.
notion image
IDA에서 FNamePool 전역 위치와 오프셋을 찾은 기록
notion image
ReClass에서 FNamePool 메모리를 펼쳐 NameDump 구조를 확인한 화면
FUObjectArray — 이름 체계를 실제 객체 그래프로 연결
FNameDump로 이름을 해석한 다음에는 GUObjectArray → FUObjectArray → FChunkedFixedUObjectArray → FUObjectItem → UObjectBase를 엔진 소스에서 역추적했습니다. WinDbg에서 GUObjectArray 위치를 구해 0x11EA83E0 오프셋을 계산하고, 이중 포인터와 FUObjectItem0x20 단위를 따라 실제 UObject에 접근했습니다. 이후 ClassPrivate, NamePrivate, OuterPrivate를 분석하고 ComparisonIndex를 앞서 만든 FNameDump와 연결해 객체의 클래스·이름·소속 관계를 복원했습니다.
notion image
GUObjectArray → FUObjectItem → UObjectBase 포인터 관계를 확인한 화면
notion image
전체 구조 분석을 마친 뒤 Dumper-7로 생성한 CppSDK와 GObjects Dump
GWorld — 현재 월드에서 ULevel과 AActor까지 확장
객체 하나를 해석하는 데서 멈추지 않고 UObjectBase → UObject → UWorld 상속 관계와 GWorldUWorldProxy 구조를 따라갔습니다. WinDbg에서 UWorld.PersistentLevel0x30 오프셋과 ULevel.Actors0xA0 위치를 확인해, 현재 월드에서 Level을 거쳐 TArray<TObjectPtr<AActor>>에 도달하는 경로를 정리했습니다. 이를 통해 게임의 캐릭터와 오브젝트가 어떤 메모리 관계로 노출되는지 설명할 수 있게 됐습니다.
notion image
WinDbg에서 확인한 UWorld와 PersistentLevel 구조
notion image
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 이름의 나열이 아니라, 각 호출이 어느 프로세스에서 실행되는지 구분하는 것이었습니다.
notion image
팀 테스트 Injector에서 확인한 CreateRemoteThread 기반 DLL 주입 코드
방어 후보는 구현 난이도보다 관찰 범위와 실행 비용을 기준으로 비교했습니다.
후보
판단
결정
외부 프로세스 상시 스캔
넓게 볼 수 있지만 지속적인 탐색 비용과 이름 기반 오탐 가능성이 큼
보류
명령·바이트 패턴 탐지
게임 빌드와 핵 구현 변화에 민감하고 유지 비용이 큼
보류
게임 내부 DLL 로딩 지점 후킹
실험 범위가 명확하고 요청 시점에만 정책을 적용할 수 있음
LoadLibraryA 1차 구현으로 채택
따라서 방어 범위를 막연하게 넓히기보다, 팀 내부 핵으로 반복 재현할 수 있는 LoadLibrary 기반 DLL 로딩 경로를 첫 번째 위협 모델로 정했습니다. 구현 이후에는 경고 발생 여부가 아니라 핵 DLL이 실제로 주입되는지를 기준으로 검증하기로 했습니다.

3. Detours 기반 x64 DLL 로딩 정책 구현

설계 기준 — DLL 로딩을 전부 막지 않고, 게임의 정상 동작을 유지하면서 허용되지 않은 런타임 로딩만 제한했습니다.
팀 내부 Injector의 동작을 따라가며 원격 스레드가 최종적으로 게임 프로세스 내부의 LoadLibraryA를 실행해 핵 DLL을 올린다는 점에 주목했습니다. 초기에는 API 후킹, 핵에서 반복되는 명령어 탐지, 외부 프로세스 탐지를 후보로 검토했고, 실제 주입 흐름과 직접 연결되는 LoadLibrary 호출을 첫 방어 지점으로 선택했습니다.
IAT·EAT·인라인 후킹을 비교한 뒤 Microsoft Detours를 이용한 인라인 후킹을 채택했습니다. 대상 함수의 진입점을 검사 함수로 연결하면서도 trampoline을 통해 원래 함수를 다시 호출할 수 있어, 정상 요청과 비인가 요청을 분기하는 정책을 구현할 수 있었습니다.
notion image
Detours가 대상 함수의 첫 명령어를 detour 함수로 연결하고 원래 함수는 trampoline으로 보존하는 구조를 확인한 당시 자료
그러나 LoadLibrary 자체를 전부 차단하면 게임이 실행 중 필요에 따라 불러오는 정상 DLL까지 막힐 수 있었습니다. 프로세스 시작 시 로드되는 주요 모듈과 런타임의 명시적 DLL 로딩을 구분한 뒤, 알려진 게임 DLL은 허용하고 그 밖의 요청만 거부하는 화이트리스트 정책으로 설계를 구체화했습니다.
notion image
프로세스 시작 → 주요 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에 구성하고, GetModuleHandleGetProcAddresskernel32.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가 정상적으로 실행됐습니다. 경고가 발생했다는 사실만으로 성공으로 판단하지 않고, 목표였던 핵 실행 제한에 실패한 것으로 기록했습니다.
notion image
1차 테스트: DLL 로딩 경고 발생
notion image
경고 이후에도 실행된 핵 UI
notion image
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 문서에서 IsDebuggerPresentCheckRemoteDebuggerPresent의 동작을 조사한 뒤, SetTimerTimerProc을 연결해 CheckForDebugger()가 5초마다 실행되도록 설계했습니다. Anti-Cheat DLL이 로드된 뒤에 디버거가 붙는 상황까지 계속 확인하기 위한 구조였습니다.
Victim 프로세스에 WinDbg를 직접 연결해 테스트했지만 탐지하지 못했습니다. 이 결과를 단순한 구현 오류로 넘기지 않고, PEB의 BeingDebugged처럼 사용자 모드에서 확인하는 상태값에 의존하는 방식은 실제 디버거 실행 여부와 다른 값을 반환할 수 있다고 판단했습니다.
notion image
1차 구현: WinDbg를 Victim 프로세스에 연결했지만 탐지하지 못한 화면
notion image
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;; 접두사는 반복됐습니다. 전체 문자열을 고정값으로 가정하지 않고, 실행마다 달라지는 부분과 계속 유지되는 부분을 분리해 탐지 기준을 만들었습니다.
notion image
첫 번째 실행에서 확인한 WinDbg Window Class
notion image
재실행 후 달라진 값과 반복되는 접두사
GetTopWindowGetNextWindow로 최상위 창을 순회하고, GetClassName으로 얻은 문자열에 공통 접두사가 포함되는지 검사하는 IsWinDbgRunning()을 구현했습니다. 그 결과 앞선 두 구현에서 찾지 못했던 WinDbg 탐지에 성공했습니다.
notion image
공통 Window Class 접두사를 기준으로 WinDbg 탐지에 성공한 화면
Cheat Engine — 같은 기준을 강제하지 않고 Window Title로 전환
Cheat Engine에도 Window Class 방식을 적용해 보았지만 Window, Button처럼 다른 프로그램도 사용하는 일반적인 값만 노출되어 식별 기준으로 사용할 수 없었습니다. 도구마다 같은 신호를 강제하지 않고, 버전 문자열이 달라져도 공통으로 남는 Window Title의 Cheat Engine 문자열을 기준으로 바꿨습니다.
EnumWindows로 시스템의 창을 열거하고 GetWindowText로 제목을 읽는 IsCheatEngineRunning()을 구현했습니다. 제목에 Cheat Engine이 포함된 창을 찾으면 열거를 중단하고 탐지 결과를 반환하도록 구성해 실제 탐지까지 확인했습니다.
notion image
Cheat Engine의 Window Title에서 버전과 무관한 공통 문자열을 찾은 화면
notion image
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 계열 경로에 한정했으며, 그 밖의 주입 방식은 실험하지 않았습니다.
 
page icon

배운 점과 다음 단계

후킹할 API의 개수보다 공격 코드가 어느 프로세스와 권한 경계에서 실행되는지 먼저 구분해야 한다는 점을 배웠습니다. 또한 실패 결과를 다시 관찰해 탐지 기준을 바꾸는 과정이 방어 범위를 넓혔고, 사용자 모드에서 보이지 않는 접근을 다루기 위해 후속 프로젝트에서는 방어 지점을 Windows 커널의 프로세스 핸들 생성 단계로 옮겼습니다.
또 하나 크게 배운 것은 방어는 공격보다 훨씬 어렵다는 점이었습니다. 공격자는 여러 경로 중 하나만 성공시키면 되지만, 방어자는 게임의 정상 동작을 해치지 않으면서 실제로 사용될 수 있는 공격 경로를 찾아 막아야 했습니다. 우리도 핵을 차단하기 전에 핵이 게임 객체에 어떻게 접근하고 DLL이 어떤 흐름으로 주입되는지부터 분석해야 했습니다. 그러나 1차 구현에서는 LoadLibraryA만 후킹했고, 경고창이 나타났음에도 핵 UI가 실행됐습니다. 실제 Injector의 코드를 다시 확인한 뒤에야 우리가 예상하지 못한 LoadLibraryW·ExA·ExW 경로가 있다는 것을 알았고, 이를 방어 로직에 반영해야 했습니다.
이 경험을 통해 방어는 한 번의 구현으로 끝나는 작업이 아니라, 공격자의 구현을 계속 분석하고, 놓친 경로를 찾아 위협 모델과 방어 정책을 반복해서 수정하는 과정이라는 점을 배웠습니다. 창은 여러 빈틈 중 하나만 뚫으면 되지만, 방패는 정상적인 흐름을 지키면서 공격이 들어올 수 있는 방향을 계속 예상하고 검증해야 했습니다. 그래서 모든 공격 기법을 하나씩 따라 막기보다, 공격이 반드시 통과하는 지점과 더 높은 권한의 경계를 찾는 것이 중요하다고 판단했고, 이는 이후 방어 지점을 사용자 모드에서 커널의 프로세스 핸들 생성 단계로 옮기는 계기가 됐습니다.
 

전체 기록과 원본