본 글은 졸업작품(안티치트 시스템 개발)을 위한 학술 목적의 보안 연구입니다. 치트의 동작 원리를 분석하여 탐지 및 방어 방안을 설계하는 데 활용합니다.
📌이전 글(#9)에서는 ESP의 핵심인 Actor 열거와 클래스 분류를 완성했다. SDK에서 Actor 배열을 찾고, IDA에서 FNamePool을 발견하여 클래스명을 디코딩하고, CE에서 검증하여 esp_scan.lua 텍스트 ESP를 완성했다.
이번 글에서는 온도 시스템을 분석한다. 팰월드에서는 환경 온도에 따라 캐릭터가 추위/더위 상태에 빠지고 지속 데미지를 받는다. 화면 하단의 온도 게이지가 왼쪽(추움)이나 오른쪽(더움)으로 치우치면 체력이 깎이기 시작한다. 이 온도 게이지를항상 정중앙(중립)에 고정하여 온도 데미지를 완전히 무력화하는 치트를 구현한다.
이 글에서 다루는 내용
- SDK 덤프에서 온도 시스템 구조 분석 (UPalBodyTemperatureComponent, FPalTemperatureInfo)
- 플레이어 캐릭터에서 온도 컴포넌트까지의 포인터 체인 확인
- CE에서 포인터 체인 등록 및 온도값 고정 검증
- 안티치트 관점 탐지 방안 정리
분석 환경
| 항목 | 내용 |
| 서버 | PalServer-Win64-Shipping-Cmd.exe (Steam Dedicated Server) |
| 클라이언트 | Palworld v0.7.2.87654 (Steam) |
| SDK 덤프 | Dumper-7 |
| 동적 분석 | Cheat Engine 7.6 |
| 정적 분석 | IDA Pro 9.0 |
| 접속 환경 | 로컬 전용서버 (127.0.0.1:8211) |
1단계: 게임 관찰 — 온도 시스템은 어떻게 작동하는가?
분석에 앞서 게임 내 온도 시스템이 어떻게 동작하는지 관찰한다.
[1] 온도 게이지 UI
화면 하단에 HP 바 옆에 온도 게이지가 있다.

게이지가 왼쪽으로 치우치면 캐릭터가 추위 상태에 빠져 지속적으로 체력이 줄어들고 반대로 오른쪽으로 치우치면 더위 상태가 되어 마찬가지로 지속 데미지를 입게된다. 게이지가 중앙에 있을 때만 정상 상태로, 온도에 의한 페널티가 전혀 없다.
[2] 온도에 영향을 주는 요소
게임 내에서 온도가 변하는 상황
- 시간대: 밤이 되면 추워짐, 낮에는 따뜻함
- 지역: 설산은 항상 추움, 화산은 항상 더움
- 건축물: 난로, 에어컨 근처에 있으면 온도가 중립으로 회복
- 장비: 방한복, 방열복으로 저항력 증가
즉, 서버에 매 게임 시간마다 환경 + 열원 + 장비를 종합해서 온도를 계산한다. 이 계산 결과를 0으로 덮어쓰면 영원히 중립 상태를 유지할 수 있다.
2단계: SDK에서 온도 시스템 구조 분석
[1] 온도 관련 클래스 검색
Pal_classes.hpp에서 Temperature를 검색한다. 두 가지 클래스가 나온다.
첫 번째: UPalTemperatureComponent — 환경 온도 컴포넌트

이것은 환경 자체의 온도를 관리하는 컴포넌트다. 건물이나 지역에 붙어서 해당 영역의 온도를 정의한다.
두 번째: UPalBodyTemperatureComponent — 캐릭터 체온 컴포넌트

이것은 플레이어 캐릭터에 직접 붙어있는 체온 컴포넌트다. TemperatureInfo(+0x0130)에 현재 온도 상태가 저장되고, 이 값이 네트워크로 동기화(Net, RepNotify)된다.
[2] FPalTemperatureInfo 구조체 분석
앞에서 UPalBodyTemperatureComponent의 구조를 보면 실제 온도 데이터가 저장되는 곳은 TemperatureInfo 필드다:
FPalTemperatureInfo TemperatureInfo; // 0x0130 (Net, Transient, RepNotify)
이 필드의 타입이 FPalTemperatureInfo이므로, 온도값을 변조하려면 이 구조체의 내부 레이아웃을 알아야 한다. 어떤 오프셋에 현재 온도가 저장되어 있고, 어떤 타입(int32인지 float인지)이며, 상태값은 어디에 있는지를 파악해야 정확한 주소에 값을 쓸 수 있기 때문이다.
Pal_structs.hpp에서 FPalTemperatureInfo를 찾는다

FPalTemperatureInfo를 찾으면 4개의 필드가 있는데, 이름을 보면 역할을 파악할 수 있다. CurrentResistRank_Heat/Cold는 이름에 "Resist(저항)"가 붙어있으므로 장비 저항 관련이고, 온도 자체와는 무관하다. 변조에 필요한 것은 나머지 두 가지다. CurrentTemperature는 이름 그대로 현재 온도, 즉 게이지의 실제 수치다. CurrentBodyState는 현재 체온 상태를 나타내는 값인데, 구체적으로 어떤 값이 어떤 상태인지는 이 필드의 타입인 EPalBodyTemperatureState를 확인해봐야 한다.
[3] EPalBodyTemperatureState 확인
CurrentBodyState의 타입인 EPalBodyTemperatureState를 SDK에서 검색한다.

Default(0)은 정상 상태로, 이때는 온도에 의한 데미지가 발생하지 않는다. Cold(1)은 추위 상태로 지속 데미지가 발생하고, Heat(2)는 더위 상태로 마찬가지로 지속 데미지가 발생한다. 즉 값이 0이 아닌 이상 캐릭터는 항상 페널티를 받게 되므로, 이 값을 0(Default)으로 고정해야 한다.
[4] 플레이어 캐릭터 → BodyTemperatureComponent 오프셋 경로
이전 글(#4)에서 이미 GWorld부터 플레이어 캐릭터(Pawn)까지의 포인터 체인은 확보되어 있다
GWorld (base + 0x882DA60) → UWorld + 0x0158 → GameState (AGameStateBase) + 0x02A8 → PlayerArray[0] (APlayerState) + 0x0308 → PawnPrivate (APalCharacter)
이제 남은 문제는 이 Pawn에서 온도 컴포넌트까지 어떻게 가느냐다. 온도 컴포넌트(UPalBodyTemperatureComponent)는 C++ 코드가 아니라 Blueprint에서 추가된 컴포넌트이기 때문에, Pal_classes.hpp가 아닌 플레이어 Blueprint 클래스 파일을 봐야 한다.
BP_Player_Female_classes.hpp를 열면 플레이어 캐릭터가 가지고 있는 컴포넌트 목록이 나온다

class UPalBodyTemperatureComponent* PalBodyTemperature; // 0x0FC8
PalBodyTemperature가 +0x0FC8에 있다. 따라서 Pawn + 0x0FC8을 읽으면 UPalBodyTemperatureComponent 인스턴스의 주소를 얻을 수 있다
UPalBodyTemperatureComponent 안에 FPalTemperatureInfo(TemperatureInfo)는 +0x0130 위치에 있다.
struct FPalTemperatureInfo TemperatureInfo; // 0x0130
CurrentTemperature는 그 FPalTemperatureInfo 안의 +0x0008 위치에 있다.
int32 CurrentTemperature; // 0x0008
따라서 컴포넌트 기준으로는 +0x0130 + 0x0008 = +0x0138이다. CurrentTemperature의 오프셋이 된다. 마찬가지로 CurrentBodyState는 FPalTemperatureInfo 안의 +0x000C에 있으므로 컴포넌트 기준 +0x013C가 된다
[5] 전체 포인터 체인 정리
아래는 GWorld에서 온도값까지의 완전한 포인터 체인이다.
GWorld (base + 0x882DA60) → UWorld + 0x0158 → GameState (AGameStateBase) + 0x02A8 → PlayerArray[0] (APlayerState) + 0x0308 → PawnPrivate (APalCharacter) + 0x0FC8 → UPalBodyTemperatureComponent + 0x0130 → FPalTemperatureInfo +0x08 → CurrentTemperature (int32) ★ 0으로 고정
GWorld (base + 0x882DA60) → UWorld + 0x0158 → GameState (AGameStateBase) + 0x02A8 → PlayerArray[0] (APlayerState) + 0x0308 → PawnPrivate (APalCharacter) + 0x0FC8 → UPalBodyTemperatureComponent + 0x0130 → FPalTemperatureInfo +0x0C → CurrentBodyState (uint8) ★ 0으로 고정
2단계: Cheat Engine에서 실제 메모리 검증
이론적으로 구성한 포인터 체인을 Cheat Engine에서 실제로 따라가며 검증한다.
[1] 포인터 체인으로 CurrentTemperature, CurrentBodyState까지 접근
CE에서 포인터 체인을 등록하면 게임을 재시작해도 자동으로 주소를 다시 찾아준다. 고정된 주소가 아니라 "시작점(PalServer 모듈 베이스)부터 오프셋을 따라가는 경로"를 저장하기 때문이다. Add Address 창에서 Pointer 체크박스를 활성화하면 오프셋을 하나씩 추가할 수 있다. 맨 아래에 시작점(PalServer-Win64-Shipping-Cmd.exe + 0x882DA60)을 넣고, 그 위로 각 단계의 오프셋을 순서대로 쌓아 올리면 CE가 자동으로 포인터를 따라가서 최종 주소를 계산한다.


오른쪽에 파란색으로 표시되는 부분이 각 단계에서 실제로 읽힌 주소값이다. 예를 들어 [1A41BD51120+FC8] -> 1A41D997180은 Pawn 주소에서 +0xFC8을 읽었더니 온도 컴포넌트 주소 0x1A41D997180이 나왔다는 뜻이다.
맨 위에 표시되는 값이 최종 결과로, 이 체인의 끝에 있는 CurrentTemperature의 현재 값을 보여준다. Type을 4 Bytes로 설정한 이유는 CurrentTemperature가 int32(4바이트 정수) 타입이기 때문이다.
FPalTemperatureInfo는 포인터가 아니라 인라인 구조체이므로, CE에서 포인터 점프로 따라갈 수 없다. 따라서 0x130 + 0x08을 합쳐서 0x138 하나로 넣어야 한다.
CE 주소 목록에 포인터 체인으로 등록된 두 항목이 보인다. 첫 번째(0x1A41D9972B8)는 CurrentTemperature, 두 번째(0x1A41D9972BC)는 CurrentBodyState이며, 현재 둘 다 값이 0이다. 이 두 항목을 선택하고 우클릭하여 Freeze를 걸면 CE가 값을 0으로 계속 유지해주므로, 온도 게이지가 항상 정중앙에 고정된다.

방한복 없이 설산 지역에 진입한 상태다. 캐릭터가 얇은 기본 옷만 입고 있음에도 화면 하단의 온도 게이지가 정중앙에 고정되어 있고, 추위 상태 아이콘이나 지속 데미지가 발생하지 않는 것을 확인할 수 있다.

CurrentTemperature만 0으로 고정해도 온도 게이지는 중앙에 머물지만, 게임이 OnChangeHour에서 온도를 재계산한 직후
CurrentBodyState가 업데이트되기까지 찰나의 시간이 있다. 그 사이에 CurrentBodyState가 Cold(1)이나 Heat(2)로 남아있으면 지속 데미지가 들어올 수 있다. 따라서 CurrentTemperature와 CurrentBodyState를 동시에 0으로 고정하여 빈틈 없이 온도 시스템을 무력화한다.
분석 결과 정리
서버 측에서 발견된 취약점
1. 안티치트 미적용: 서버 프로세스에 EAC 등의 보호가 전혀 없어 CE 어태치가 자유롭게 가능하다.
2. 메모리 무결성 검증 없음: 서버 메모리의 값을 외부에서 변조해도 감지/차단하는 메커니즘이 존재하지 않는다.
3. Net Replicated 값 무조건 신뢰: TemperatureInfo의 Net/RepNotify 속성으로 인해 서버에서 변경한 값이 클라이언트에 그대로 전파된다. 변조된 값인지 검증하는 로직은 확인되지 않았다.
안티치트 설계 시 고려사항
이번 분석을 통해 서버 측 보호의 부재가 확인되었다. 안티치트 시스템 설계 시 다음 사항을 고려해야 한다:
1. 서버 프로세스 보호: 서버에도 메모리 무결성 검증을 적용하여, 외부 프로세스에 의한 읽기/쓰기를 감지한다.
2. 환경-체온 불일치 검증: 플레이어가 위치한 지역의 환경 온도와 실제 체온을 비교하여, 설산에서 방한복 없이 체온이 0인 경우 등 비정상적인 상태를 탐지한다.
3. Net Replicated 값 검증: 서버에서 변경된 값이 게임 로직(OnChangeHour)을 통해 변경된 것인지 확인하는 무결성 검사를 추가한다.
'캡스톤 디자인 > 정보보호 프로젝트실습' 카테고리의 다른 글
| [팰월드 안티치트 프로젝트] #11 클라이언트 Internal DLL — 스켈레톤 위에 SpeedHack 모듈 통합하기 (0) | 2026.05.08 |
|---|---|
| [팰월드 안티치트 프로젝트] #9-1 전용서버 메모리 분석 - ESP (0) | 2026.04.16 |
| [팰월드 안티치트 프로젝트] #8 전용서버 메모리 분석 - 재료 무소비, 아이템 복제, 무한 획득" (0) | 2026.04.16 |
| [팰월드 안티치트 프로젝트] #7 전용서버 메모리 분석 - 텔레포트 (0) | 2026.04.10 |
| [팰월드 안티치트 프로젝트] #6 전용서버 메모리 분석 - 포획률 (0) | 2026.04.07 |