핵을 막는 코드부터 작성하지 않았다. UE5의 객체 구조와 실제 핵의 실행 경로를 먼저 역분석하고, 사용자 모드 방어가 어디서 깨지는지 확인한 뒤, 방어 지점을 Windows 커널의 프로세스 핸들 경계로 옮겼다.
Figure 1. 공격 재현에서 커널 경계 보호까지 이어진 프로젝트의 전체 설계 흐름.
프로젝트 한눈에 보기
기간 | 2024.05.01 — 2024.09.21 |
맥락 | 화이트햇 스쿨 2기 WhiteGang 팀 프로젝트 + 후속 고도화 |
내 역할 | UE5 객체 구조·핵 실행 경로 분석, 사용자 모드 Anti-Cheat 구현, 커널 드라이버 설계·개발·디버깅, PalGuard·IOCTL 연동 |
기술 | C/C++, WinAPI, Microsoft Detours, IDA, WinDbg, ReClass.NET, WDK, ObRegisterCallbacks, Process Notify, IOCTL |
산출물 | Anticheat2.dll, ExternalAnticheatStart.sys, ExternalAnticheat.sys, PalGuard.exe |
검증 환경 | Windows 가상 머신·테스트 서명 환경·통제된 로컬/비공개 게임 환경 |
핵심 성과
FNamePool,GUObjectArray,UWorld를 정적·동적 분석으로 추적해 Name/Object dump와 Palworld SDK 생성까지 연결했다.
- 표준 DLL 주입 경로를 Detours로 후킹하는 사용자 모드 Anti-Cheat를 만들고, 첫 실패에서 누락된 로더 변형을 찾아 방어 범위를 확장했다.
- 사용자 모드만으로는 별도 프로세스의
Read/WriteProcessMemory를 막기 어렵다는 한계를 확인하고,ObRegisterCallbacks기반 커널 핸들 보호로 방어 지점을 이동했다.
- 게임이 실행될 때만 핵심 보안 모듈을 활성화하는 2-드라이버 생명주기와 사용자 인터페이스
PalGuard.exe를 설계했다.
- 전·후 비교 실험에서 외부 핵의 메모리 변조와 표준 DLL 주입이 실패하고, 게임은 계속 실행되는 것을 확인했다.
문제 정의와 위협 모델
Palworld 핵을 동작 방식에 따라 두 종류로 나눴다.
- Internal cheat — DLL을 게임 프로세스에 주입한 뒤 UE5 객체와 함수를 직접 조작한다.
- External cheat — 별도 프로세스에서 Palworld 핸들을 열고 메모리를 읽거나 써서 상태를 바꾼다.
초기에는
LoadLibrary 기반 DLL 주입만 막으면 된다고 보았다. 그러나 External cheat를 분석하면서 두 공격 모두 결국 게임 프로세스에 대한 권한 있는 핸들을 필요로 한다는 공통점을 확인했다. 이에 사용자 모드 API 단위 방어에서 커널 Object Manager의 핸들 생성 경계로 관점을 전환했다.방어 범위: 표준 DLL 주입과 사용자 모드 프로세스의 RPM/WPM 기반 메모리 접근. 악성 커널 드라이버, DMA·하드웨어 공격, 서버 측 조작은 이 프로토타입의 범위에서 제외했다.
담당 범위
영역 | 내 기여 | 팀 기여 |
UE5 역분석 | FName·UObject 메모리 구조 추적, 런타임 오프셋 검증, dumper 분석·적용, Dumper-7 SDK 생성 | Unreal 구조 학습, 오프셋·핵 기능 조사와 교차 검증 |
공격 표본 | PalLauncher 주입 흐름과 Tick 기반 Speed Hack을 별도로 추적해 방어 테스트에 활용 | Internal/External cheat 기능 분석·구현 및 시연 환경 구성 |
방어 구현 | Anticheat2, 커널 Anti-Cheat 개발·디버깅, 드라이버 생명주기, IOCTL·PalGuard 설계 | 공격·방어 통합 테스트와 결과 검증 |
1. UE5 내부 구조에서 공격 표면 찾기
FNamePool — 문자열에서 런타임 구조로
IDA에서
FName 생성 경로를 MakeDetectNumber → MakeWithNumber → MakeInternal → FindOrStoreString 순으로 따라가고, 저장 경로는 FNamePool::Store → ComparisonShards.Insert → CreateAndInsertEntry → FNameEntry::StoreName까지 추적했다.WinDbg에서
FNamePool 생성자에 브레이크포인트를 걸어 x64 호출 규약의 RCX(this)를 확인하고, 모듈 베이스와의 차이로 런타임 오프셋 0x11DBB680을 계산했다. IDA의 정적 주소와 교차 검증한 뒤 ReClass.NET으로 allocator·block·shard 구조를 재구성했고, 실제 Name dump에 성공했다.GUObjectArray — 이름을 객체와 함수로 연결
FUObjectArray → FChunkedFixedUObjectArray → FUObjectItem → UObjectBase를 따라 GUObjectArray 오프셋 0x11EA83E0을 찾고 ReClass.NET에서 객체 항목을 확인했다. 이어 UWorld(0x120AD660)와 Actor 순회를 dumper에 연결해, 이름·객체·함수를 하나의 분석 파이프라인으로 묶었다.결과: 버전별 오프셋을 단순 복사하지 않고 구조를 직접 검증했으며, Dumper-7로 Palworld C++ SDK를 생성해 실제 핵 코드의 함수 호출과 멤버 조작을 읽을 수 있게 됐다. 이 단계가 이후 공격 경로와 방어 지점을 고르는 근거가 됐다.
2. 사용자 모드 Anti-Cheat — 실패에서 후킹 범위를 확장
Internal cheat의 표준 주입 흐름을 다음과 같이 정리했다.
OpenProcess → VirtualAllocEx → WriteProcessMemory → LoadLibrary 주소 확인 → CreateRemoteThreadMicrosoft Detours로 게임 프로세스의 로더 경로를 인라인 후킹하고, 정상 런타임 DLL은 allowlist로 통과시키는
Anticheat2.dll을 구현했다.1차 실험
CreateRemoteThread와LoadLibraryA를 우선 후킹했다.
- 경고는 발생했지만 핵이 계속 로드됐다.
- 인젝터를 다시 분석해
LoadLibraryEx계열 등 다른 로더 변형이 사용되는 것을 확인했다.
2차 개선
- 범위를
LoadLibraryA/W/ExA/ExW로 확장했다.
- 정상 DLL allowlist를 유지해 게임의 동적 로딩과 충돌을 줄였다.
- 재실험에서 게임은 동작한 채 핵 UI가 나타나지 않는 것을 확인했다.
Anti-debugging도
IsDebuggerPresent, CheckRemoteDebuggerPresent, NtQueryInformationProcess, 예외·디버그 레지스터 검사를 차례로 시험했다. WinDbg 우회에 취약한 것을 확인한 뒤 Spy++로 관찰한 WinDbg 창 클래스와 Cheat Engine 창 제목을 보조 신호로 사용했다. 완전한 방어라기보다 실패 원인을 기록하고 관찰 가능한 신호를 조합한 실험이었다.3. 커널 Anti-Cheat — API가 아니라 권한 경계를 막기
사용자 모드 후킹은 같은 권한의 공격자가 우회·복구할 수 있고, External cheat의 메모리 접근은 게임 내부 로더 후킹을 지나지 않는다. 그래서 공격 코드가 아니라 Palworld 프로세스 핸들이 만들어지는 순간을 통제하기로 했다.
ObRegisterCallbacks의 pre-operation callback에서 다음을 수행했다.- 대상 프로세스가
Palworld-Win64-Shipping.exe인지 확인한다.
- 요청 프로세스의 이미지 경로를 얻는다.
- 게임 자체·Steam·Windows 시스템 경로·allowlist는 통과시킨다.
- 그 외 요청에서 메모리 쓰기·메모리 조작·핸들 복제 관련 권한을 제거한다.
처음 사용한 프로세스 탐색 API가 빌드 환경에서 노출되지 않아
ZwQuerySystemInformation(SystemProcessInformation)으로 전환했다. ObRegisterCallbacks가 STATUS_ACCESS_DENIED로 실패한 문제는 링크 옵션 /INTEGRITYCHECK와 드라이버 설정을 추적해 해결했다.생명주기 기반 2-드라이버 구조
첫 커널 버전은 Palworld가 꺼져 있어도 콜백이 계속 실행되고, 정상 프로세스의 핸들 요청까지 대량으로 기록했다. 이를 줄이기 위해 역할을 분리했다.
Figure 5. PalGuard, 감시 드라이버, 집중 보호 드라이버, Object Manager callback의 제어·데이터 흐름.
ExternalAnticheatStart.sys—PsSetCreateProcessNotifyRoutineEx로 게임 시작·종료를 감지하고 핵심 드라이버의 생명주기를 관리한다.
ExternalAnticheat.sys— 게임이 실행되는 동안에만 PID를 찾고 Object callback을 등록해 핸들 권한을 검사한다.
PalGuard.exe— IOCTL로 드라이버 상태와 시스템 볼륨 정보를 주고받고, 사용자가 보호 상태를 확인할 수 있게 한다.
4. 검증 결과
Figure 8. 동일 공격 시나리오를 방어 전·후에 반복한 정성적 결과 요약.
실험 | 방어 전 | 방어 후 |
Internal DLL injection | 인젝터로 핵 DLL이 로드되고 핵 UI·기능이 동작 | 표준 주입 경로에서 DLL 로드가 발생하지 않고 게임은 계속 실행 |
External memory write | 테스트 값 17을 999로 변경 | 권한 있는 핸들을 얻지 못해 동일 쓰기 동작이 실패 |
External cheat 실행 | 프로세스 베이스 주소를 찾고 기능 실행 | 필요한 핸들 권한을 얻지 못해 베이스 주소 탐색·기능 실행 실패 |
정상 동작 | 게임 실행 | 검증 시나리오에서 게임 실행 유지 |
이 결과는 특정 버전·테스트 환경에서 표준 공격 경로가 차단됐다는 정성적 검증이다. 모든 우회 기법을 포괄하거나 상용 Anti-Cheat 수준의 보안을 증명하는 수치로 해석하지 않았다.
디버깅으로 바꾼 설계
관찰한 문제 | 분석 | 반영한 변화 |
첫 Detours 버전에서 핵 로드 지속 | 인젝터가 다른 LoadLibrary 변형 사용 | A/W/ExA/ExW 계열로 관찰 범위 확장 |
PEB·API 기반 anti-debug 우회 | 단일 API 신뢰가 취약 | 창 클래스·제목 등 별도 관찰 신호 실험 |
프로세스 탐색 API 링크 실패 | 현재 WDK에서 사용할 수 없는 심볼 | ZwQuerySystemInformation 기반 열거로 전환 |
ObRegisterCallbacks 접근 거부 | 무결성 요구 조건 누락 | /INTEGRITYCHECK와 드라이버 빌드 설정 적용 |
상시 콜백으로 로그 폭증·정상 프로세스 간섭 | 보호 범위와 생명주기가 과도함 | 감시 드라이버와 집중 보호 드라이버 분리 |
HarddiskVolume 번호가 환경마다 달라 실패 | 시스템 경로를 magic value로 가정 | QueryDosDevice + IOCTL 기반 동적 전달을 설계·시험 |
RtlPrefixUnicodeString 인근 BSOD | 경로 데이터의 수명·유효성 문제 | WinDbg !analyze로 원인을 좁히고 전달·검증 흐름 수정 |
현재 한계와 다음 단계
이 프로젝트는 상용 제품이 아니라 공격 경로를 관찰하고 방어 위치를 검증한 PoC다. 공개 저장소와 최종 실험본을 함께 대조했을 때 다음 항목은 남아 있다.
- 사용자 모드 훅은 manual mapping, 직접 syscall, 훅 복구 같은 우회에 취약하며, 공개 코드의 Wide/Ex 로더 callback 타입과 detach 처리를 정리해야 한다.
- 커널 공개 코드는 handle create 중심으로 검사한다. duplicate 처리,
PROCESS_VM_READ·PROCESS_CREATE_THREAD등 권한 행렬, 이미 열린 핸들까지 포함한 회귀 테스트가 필요하다.
- 경로·파일명 allowlist는 위장 가능성이 있으므로 서명·해시·토큰과 결합해야 한다.
SeLocateProcessImageName결과 해제, 동시성, IRQL, 입력 길이 검증도 강화해야 한다.
- 문서의 동적 볼륨 전달 실험과 달리 공개 소스에는 하드코딩 fallback과 비활성화된 handoff가 남아 있고, Start 드라이버 프로젝트 파일도 누락돼 재현 가능한 빌드 정리가 필요하다.
- 테스트 서명 VM에서 검증했으며, 운영 배포용 서명·업데이트·복구·성능 측정·오탐 회귀 체계는 구현하지 않았다.
- 악성 커널 드라이버·rootkit·DMA 공격은 별도의 신뢰 체계와 하드웨어 기반 방어가 필요한 범위다.
다음 단계라면 권한별 공격 테스트 매트릭스를 먼저 만들고, callback fast path의 지연과 오탐을 계측한 뒤, 서명 기반 allowlist와 재현 가능한 빌드·설치 패키지를 완성하겠다.
배운 점
- 구조를 이해해야 방어 지점이 보인다. UE5의 이름·객체·함수 연결을 이해한 뒤에야 핵이 무엇을 조작하는지 설명할 수 있었다.
- 기능이 아니라 경계를 막아야 한다. API를 하나씩 후킹하는 접근에서 벗어나, 공격들이 공유하는 프로세스 핸들 권한으로 방어 지점을 일반화했다.
- 실패 로그도 결과다. 우회, 링크 오류, 콜백 폭증, BSOD를 남기고 원인과 다음 설계 변경을 연결했다.
- portable은 마지막 옵션이 아니다. 환경마다 다른 볼륨 번호 하나가 전체 흐름을 깨뜨렸다. 외부 입력·수명·배포 구조까지 설계의 일부로 봐야 한다.
윤리 및 테스트 원칙
공격 표본은 Anti-Cheat 방어 연구를 위한 통제된 VM·로컬/비공개 환경에서만 사용했다. 제3자 서버나 실제 사용자에게 피해를 주는 테스트는 수행하지 않았으며, 공개 자료도 방어 구조와 검증 결과를 중심으로 정리했다.
결과물
정리 근거와 검증 범위
프로젝트 원본 538개 파일(약 1.34GB)을 전수 해시·읽기 검증했다. Notion HTML 80개 페이지와 모든 가시 텍스트, 이미지 421개와 GIF 전체 프레임, DOCX 2개 8쪽, HWP 1개 10쪽, MP4 8개 약 15분 13초의 전 구간, 로그·CSV·소스·드라이버·중첩 ZIP 6개와 내부 항목, GitHub 2개 저장소의 전체 파일·commit history를 확인했다. 자동 생성 SDK·외부 라이브러리와 본인 작성·수정 코드를 구분하고, 개인 보고서·회의록·주간 역할표·실행 로그·전후 영상·최종 공개 소스를 서로 대조해 기여와 결과를 확정했다.