2024년 WhiteHat School 2기 WhiteGang에서 제가 Palworld의 Unreal Engine 객체 구조를 분석했던 과정을 정리했습니다. 당시 개인 기록을 최종보고서와 공개 저장소에 다시 대조하며, 지금도 근거를 확인할 수 있는 내용과 당시의 시행착오를 구분했습니다.
분석은 교육 목적의 로컬 환경에서 수행했습니다. 특정 게임 버전의 주소나 우회 절차가 아니라, 무엇을 근거로 구조를 해석했는지에 초점을 맞춥니다.
출발점: 값보다 구조를 찾기
처음에는 화면에 보이는 체력이나 좌표 같은 값을 찾는 데 집중하기 쉽습니다. 하지만 동적 할당과 버전 변화가 있는 게임에서는 단일 주소가 오래 유지되지 않습니다. 그래서 분석 단위를 “현재 값이 있는 주소”에서 객체를 찾는 기준과 참조 관계로 바꾸었습니다.
Unreal Engine 계열 게임을 이해하기 위해 당시 기록에서 반복해서 확인한 축은 다음 세 가지였습니다.
- FNamePool: 엔진이 사용하는 이름 정보를 해석하기 위한 단서
- GUObjectArray: 런타임 UObject 목록과 객체 메타데이터를 추적하기 위한 단서
- GWorld / UWorld: 현재 월드에서 레벨·게임 인스턴스·플레이어 관련 객체로 이동하기 위한 시작점
이들은 “정답 주소”가 아니라 객체 그래프를 읽는 출발점입니다.
분석 흐름
1. 정적 분석으로 후보를 좁혔다
IDA에서 관련 문자열과 참조 흐름을 살피고, 전역 객체로 이어질 가능성이 있는 코드를 후보로 잡았습니다. 이 단계의 목적은 오프셋 하나를 외우는 것이 아니라 어떤 코드 경로가 엔진의 전역 상태를 참조하는지 이해하는 것이었습니다.
2. 동적 디버깅으로 실제 값을 확인했다
WinDbg에서 후보 주소가 실행 중 어떤 객체를 가리키는지 확인했습니다. 정적 분석만으로 추정한 구조가 실제 실행 상태에서도 일관되는지, 포인터가 유효한 객체와 연결되는지 검증했습니다.
3. 구조 시각화로 포인터 체인을 이해했다
ReClass를 사용해 클래스처럼 메모리 구조를 관찰했습니다. 필드의 의미를 한 번에 단정하지 않고, 값 변화·정렬·다른 객체와의 참조를 반복해서 보며 가설을 수정했습니다.
4. 생성된 SDK는 답이 아니라 대조 자료로 썼다
Dumper-7 기반 SDK 생성 결과를 참고해 클래스와 멤버 이름을 확인했습니다. 다만 자동 생성 결과 역시 게임 빌드와 엔진 버전에 영향을 받기 때문에, 디버거에서 관찰한 구조와 일치하는지 다시 확인했습니다.
이 분석이 안티치트와 연결된 이유
리버스 엔지니어링은 치트 기능을 만드는 데서 끝나는 작업이 아니었습니다. 어떤 데이터가 어디에 있고 어떤 경로로 접근되는지를 알아야 방어할 자산과 신뢰 경계를 정할 수 있습니다.
- 게임 프로세스 내부에서 코드를 실행하려는가?
- 외부 프로세스가 핸들을 열어 메모리를 읽거나 쓰는가?
- 사용자 모드 API 후킹으로 관찰 가능한가?
- 커널이 핸들 권한을 부여하는 시점에서 제어해야 하는가?
이 질문들이 이후 사용자 모드 PoC와 커널 모드 핸들 보호 설계로 이어졌습니다.
당시 접근의 한계
- 전역 객체와 구조는 게임 업데이트에 따라 달라질 수 있습니다.
- 자동 생성 SDK는 정확성을 보장하지 않으며 실행 상태 검증이 필요합니다.
- 객체 이름을 확인했다고 필드 의미와 접근 경로가 모두 증명되는 것은 아닙니다.
- 당시 메모에는 시행착오와 충분히 검증하지 못한 표현도 남아 있습니다. 그래서 이 글에서는 최종보고서와 코드로 다시 확인한 내용만 제 성과로 적었습니다.
정리
이 단계에서 얻은 핵심은 “특정 값을 찾았다”가 아닙니다. 엔진의 객체 구조를 기준으로 가설을 세우고, 정적 분석·동적 분석·구조 시각화를 서로 대조하는 방법을 익혔습니다. 이 방식이 다음 단계의 위협 모델과 방어 지점을 결정하는 기반이 되었습니다.
당시 원본 기록
아래 하위 문서는 2024년 당시 제가 작성한 개인보고서 원문입니다. 사진·코드·시행착오는 그대로 보존했고, 지금 다시 보며 바로잡은 내용은 위 본문에 따로 적었습니다.