본 글은 졸업작품(안티치트 시스템 개발)을 위한 학술 목적의 보안 연구입니다. 치트의 동작 원리를 분석하여 탐지 및 방어 방안을 설계하는 데 활용합니다.
📌 이 글은 팰월드 안티치트 프로젝트의 학습 기록 시리즈입니다. 안티치트를 만들기 위해 먼저 "공격자가 게임을 어떻게 공격하는지" 이해하는 과정을 정리합니다.
이전 글에서는 UE5 게임의 내부 구조를 분석하는 방법을 다뤘다. IDA Pro로 GWorld, GNames, GObjects의 주소를 찾고, 참조 게임의 PDB를 활용해 함수 이름을 복원하는 과정이었다.
이번 글에서는 한 발 더 나아간다. 실제 치트가 이 구조를 어떻게 이용해서 게임을 조작하는지, 공개된 Cheat Engine 치트 테이블(.CT 파일)을 직접 사용해보고 내부 스크립트를 리버싱하여 동작 원리를 분석한다.
"적을 알아야 방어할 수 있다." 안티치트를 만들려면 먼저 치터가 어떤 기술을 쓰는지 정확히 이해해야 한다.
이 글에서 다루는 내용:
- CT 파일이란 무엇이고, 어떻게 게임 메모리를 찾아가는지
- AOB 패턴 스캔과 코드 인젝션의 동작 원리
- 실제 Damage/Health 치트 스크립트의 어셈블리 코드 분석
- IDA Pro에서 인젝션 포인트를 직접 확인하는 과정
분석 환경:
| 항목 | 내용 |
| 게임 | Palworld v0.7.2.87654 (Steam) |
| 엔진 | Unreal Engine 5.1 |
| 바이너리 | Palworld-Win64-Shipping.exe |
| 정적 분석 | IDA Pro + IDA MCP |
| 동적 분석 | Cheat Engine 7.6 |
| CT 파일 | FearLess Revolution — VoidUzumaki 제작 (v0.7.0 대응) |
CT 파일이란?
CT(Cheat Table) 파일은 Cheat Engine에서 사용하는 치트 테이블 파일이다. 쉽게 말하면, 누군가가 이미 게임의 메모리 구조를 분석해서 포인터 체인, 오프셋, 바이트 패턴을 전부 정리해둔 파일이다.
팰월드로 예를 들면, 내 캐릭터의 체력 값은 메모리 어딘가에 있지만 매번 다른 주소에 위치한다. CT 파일에는 그 값을 매번 정확히 찾아가는 공식이 들어있기 때문에, Cheat Engine에 로드하기만 하면 체력, 스태미나, 공격력 같은 값에 바로 접근하여 변조할 수 있다.
CT 파일이 고정 주소가 아닌 "찾는 공식"인 이유:
이전 글에서 다뤘듯이, UE5 게임은 ASLR(주소 공간 배치 난수화)과 동적 메모리 할당 때문에 게임을 재시작할 때마다 데이터의 실제 주소가 바뀐다. 그래서 CT 파일은 고정 주소를 저장하는 것이 아니라, "이런 바이트 패턴을 가진 코드를 찾아서, 그 위치를 기준으로 조작해라" 라는 방식으로 동작한다.
또한 CT 파일은 게임 버전에 종속된다. 업데이트로 코드가 변경되면 오프셋이 밀리거나 AOB 패턴이 깨져서 동작하지 않을 수 있다. 그래서 CT 파일 배포 사이트를 보면 버전별로 따로 올라와 있는 것을 확인할 수 있다.
CT 파일 다운로드 및 적용:
CT 파일은 FearLess Revolution(https://fearlessrevolution.com)이라는 Cheat Engine 커뮤니티에서 다운로드했다. 이 사이트는 가장 오래되고 신뢰도 높은 CE 치트 테이블 커뮤니티 중 하나다.
사이트에서 Palworld를 검색하면 여러 버전의 CT 파일이 올라와 있다. 본 분석에서는 VoidUzumaki가 제작한 Steam v0.7.0 대응 버전을 사용했다. 팰월드의 현재 버전은 v0.7.2이지만, 마이너 패치 차이라서 대부분의 기능이 정상 동작했다.

CT 파일 적용 방법:
1단계: 팰월드 실행 : 게임을 실행하고 메인 화면까지 진입한다.
2단계: 다운로드한 Palworld-Win64-Shipping.CT 파일을 더블클릭하면 Cheat Engine에 자동으로 로드된다. 이때 lua 스크립트 실행 허용 팝업이 뜨는데, "Yes"를 클릭한다. 이 스크립트가 AOB 패턴 검색과 메모리 할당 등의 초기 설정을 수행한다.


3단계: Cheat Engine 좌측 상단의 File → Open Process를 클릭한다. 프로세스 목록에서 Palworld-Win64-Shipping.exe를 선택하면 CE가 게임 프로세스에 연결된다.

4단계: 로드가 완료되면 CE 화면에 치트 목록이 표시된다. 원하는 항목의 체크박스를 켜면 해당 치트가 즉시 활성화된다.

CT 파일 동작 원리 분석
1단계: CT 스크립트에서 AOB 패턴 추출
CT 파일을 열어보면 내부에 lua 스크립트가 존재한다. 이 스크립트의 핵심은 AOB(Array of Bytes) 패턴 스캔이다.
AOB란? 말 그대로 "바이트 배열"이다. 게임의 실행 파일(.exe)은 결국 0과 1로 이루어진 바이트의 나열인데, 그 중 특정 기능을 수행하는 코드 부분의 바이트열을 미리 알아둔 것이다.
CT 파일의 Damage/Health 스크립트에는 다음과 같은 패턴이 들어있다:
48 8B 80 00 04 00 00 48 89 44 24 20 45 8B C7 48 8D 54 24 20 48 8D 4D 9F
이 바이트열은 게임 코드 중 데미지 처리와 관련된 명령어에 해당한다. CT 파일은 게임이 실행되면 이 패턴을 메모리에서 검색하여, 해당 코드의 위치를 찾아낸다.
여기서 중요한 점은, 게임은 실행될 때마다 ASLR(Address Space Layout Randomization)에 의해 메모리 주소가 달라진다. 그래서 "주소 0x12345678로 가라"는 식으로는 치트를 만들 수 없다. 대신 바이트 패턴으로 검색하면, 주소가 바뀌어도 해당 코드를 정확히 찾아갈 수 있는 것이다.
2단계: IDA에서 패턴 검색으로 위치 확인
이제 이 패턴이 실제로 게임 바이너리에 존재하는지 직접 확인해보자.
IDA Pro에서 Palworld-Win64-Shipping.exe를 열고, Search → Sequence of bytes를 눌러 위의 바이트 패턴을 검색한다.

검색 결과, 딱 1곳에서 매칭된다:
주소: 0x2A80D64
소속 함수: sub_2A80A30

전체 exe에서 단 1곳에서만 발견되었다는 것은, 이 패턴이 유니크하다는 뜻이다. CT 파일이 이 패턴을 사용하는 이유가 여기에 있다
이 주소의 어셈블리를 보면 다음과 같다:
0x2A80D64: mov rax, [rax+400h] ; 체력 홀더 포인터를 로드
0x2A80D6B: mov [rsp+20h], rax ; 스택에 저장
0x2A80D70: mov r8d, r15d ; r15d = 데미지 값
하지만 여기서 의문이 생긴다. 바이트를 검색해서 위치를 찾은 것까지는 좋은데, 이게 정말로 데미지와 관련된 코드라는 걸 어떻게 증명할 수 있을까?
단순히 "패턴을 검색했더니 나왔다"는 것만으로는 부족하다. 실제로 이 코드가 데미지 처리 시 실행되는지를 직접 확인해봐야 한다.
3단계: 브레이크포인트로 데미지 함수 증명
Cheat Engine의 Memory View 기능을 사용하여 이 코드가 실행되는 순간을 직접 잡아보자.
1) Cheat Engine에서 팰월드 프로세스에 연결한 후, Memory View를 연다 (Ctrl+M 또는 메뉴에서 Memory View 클릭).

2) 상단의 주소창에 다음 주소를 입력하여 이동한다: Palworld-Win64-Shipping.exe+2A80D64

3) 해당 줄에서 F5 또는 우클릭 → Toggle Breakpoint를 눌러 브레이크포인트를 설정한다.

4) 게임으로 돌아가서 몬스터를 때려본다. 결과: 데미지를 받는 순간 게임이 멈춘다.
게임이 멈췄다는 것은, 방금 우리가 브레이크포인트를 건 바로 그 코드(0x2A80D64)가 실행되었다는 뜻이다. 즉, 피격 시 이 코드를 거친다는 것이 확인된 것이다.
5) 이제 레지스터 값을 확인해보자. 브레이크포인트에 걸렸을 때 레지스터 창을 보면:

0x30은 10진수로 48이다. 이것이 바로 데미지 값이다.
아까 어셈블리에서 mov r8d, r15d라는 명령어가 있었다. 이는 R15 레지스터에 담긴 값을 데미지로 사용한다는 의미다. 실제로 R15에 48이라는 데미지 수치가 들어있는 것을 확인한 것이다.
이로써 0x2A80D64 주소의 코드가 데미지를 받는 순간 실행된다는 것이 확인되었다. R15 레지스터에는 실제 데미지 수치가 담겨 있었으며, 이 코드가 속한 함수 sub_2A80A30이 데미지 처리 함수라는 것이 증명된 것이다.
4단계: 코드 인젝션 — 원래 코드를 바꿔치기하는 방법
CT 파일은 데미지 처리 코드의 위치를 찾은 뒤, 그 코드를 통째로 바꿔치기한다. 이것을 **코드 인젝션(Code Injection)**이라고 한다. 원리는 간단하다. 원래 코드의 첫 몇 바이트를 jmp(점프) 명령어로 덮어씌워서, 게임이 해당 코드를 실행하려 할 때 CT가 별도로 만들어둔 커스텀 코드로 강제 이동시키는 것이다.
원래 코드 (치트 OFF 상태)
0x2A80D64: mov rax, [rax+400h] ; 체력 홀더 포인터 로드 (7바이트)
0x2A80D6B: mov [rsp+20h], rax ; 스택에 저장
이것이 정상적인 게임 코드다. 데미지를 받으면 체력 홀더를 불러와서 HP를 깎는 흐름이다.

치트 활성화 후 (치트 ON 상태)
0x2A80D64: jmp 7FF789480000 ; CT의 커스텀 코드로 점프 (5바이트)
0x2A80D69: nop ; 남은 2바이트를 nop으로 채움
원래 7바이트짜리 명령어(mov rax, [rax+400h])가 있던 자리에, 5바이트짜리 jmp 명령어가 들어갔다. 남은 2바이트는 nop(아무것도 하지 않는 명령어)으로 채워서 바이트 수를 맞춘다. 이제 게임이 데미지를 처리하려고 이 주소에 도달하면, 원래 코드 대신 jmp를 만나 CT가 만들어둔 커스텀 코드로 날아가게 된다.

5단계: 커스텀 코드 — 데미지를 0으로 만드는 로직
jmp로 이동한 곳에는 CT가 메모리에 새로 할당한 커스텀 코드가 존재한다. 이 코드가 하는 일은 다음과 같다:
; ── 원래 코드 실행 ── push rax
mov rax,[rax+00000400] ; 체력 홀더 포인터 로드 (원래 게임 코드)
mov [mobsTakeDamgBossFSaveSpace+9],rax ; 체력 홀더를 별도 공간에 저장
pop rax
; ── 아군/적 판별 ──
cmp dword ptr [rax+60],1 ; 팀 플래그 확인 (1 = 아군)
jne enemy ; 아군이 아니면 → 적 처리로 점프
player:
mov [mobsTakeDamgBossFSaveSpace+1],rax ; 플레이어 포인터 저장
bt [mobsTakeDamgBossFSaveSpace],0 ; 비트0: Max Out Health 옵션
jnc @f ; 꺼져있으면 스킵
push rax
mov eax,[rax+000005F8] ; Max HP 값을 읽어서
mov [mobsTakeDamgBossFSaveSpace+9],rax ; 현재 HP를 Max HP로 덮어씌움
pop rax
@@:
bt [mobsTakeDamgBossFSaveSpace],1 ; 비트1: Take No Damage 옵션
jnc @f ; 꺼져있으면 스킵
xor r15d,r15d ; 데미지 = 0 (무적)
@@:
jmp code0 ; 원래 코드로 복귀
enemy:
bt [mobsTakeDamgBossFSaveSpace],2 ; 비트2: One-Hit Kill 옵션
jnc @f ; 꺼져있으면 스킵
push rax
xor rax,rax ; 적 체력 = 0 (원킬)
mov [mobsTakeDamgBossFSaveSpace+9],rax
pop rax
@@:
bt [mobsTakeDamgBossFSaveSpace],3 ; 비트3: Immortal Enemy 옵션
jnc @f ; 꺼져있으면 스킵
xor r15d,r15d ; 적이 받는 데미지 = 0 (적 무적)
@@:
bt [mobsTakeDamgBossFSaveSpace],4 ; 비트4: Almost Dead 옵션
jnc @f ; 꺼져있으면 스킵
push rax
push rcx
push rdx
mov r15d,[mobsTakeDamgBossFSaveSpace+9] ; 적 현재 HP 가져와서
dec r15d ; HP - 1
mov eax,r15d
mov ecx,#1000 ; 1000으로 나눔
xor edx,edx
div ecx
mov r15d,eax ; 데미지 = (HP-1)/1000 → 천천히 죽음
pop rdx
pop rcx
pop rax
@@:
jmp code0 ; 원래 코드로 복귀
여기서 핵심은 xor r15d, r15d이다. 아까 3단계에서 R15에 데미지 값이 담겨있다는 걸 증명했다. 이 값을 xor 연산으로 0으로 만들어버리면, 게임은 데미지가 0인 것으로 처리한다. 결과적으로 플레이어는 아무리 맞아도 HP가 줄지 않는 무적 상태가 되는 것이다.
마무리
이번 글에서는 팰월드의 CT 파일을 통해 치트가 어떻게 동작하는지를 분석하였다.
AOB 패턴 스캔으로 데미지 처리 코드의 위치를 찾고, IDA Pro에서 해당 패턴이 실제로 존재하는 것을 확인하였다. 브레이크포인트를 걸어 이 코드가 피격 시 실행되며 R15 레지스터에 데미지 값이 담긴다는 것까지 증명하였다.
이후 치트를 활성화하면 원래 코드가 jmp로 바뀌어 커스텀 코드로 이동하고, 그곳에서 데미지를 0으로 만들거나 적의 체력을 0으로 만드는 등의 조작이 이루어지는 것을 CE Memory View에서 직접 확인하였다.
결과적으로 CT 파일은 게임의 코드를 런타임에 수정하여 원하는 동작을 끼워넣는 방식이며, 하나의 인젝션 포인트에서 플래그 비트를 통해 무적, 원킬, 체력 최대 유지 등 5가지 기능을 제어하는 구조였다.
'캡스톤 디자인 > 정보보호 프로젝트실습' 카테고리의 다른 글
| [팰월드 안티치트 프로젝트] #5 전용서버 메모리 분석 - 스피드핵 (0) | 2026.04.02 |
|---|---|
| [팰월드 안티치트 프로젝트] #4 전용서버 메모리 분석 - 서버 측 변조는 정말 가능한가? (0) | 2026.03.31 |
| [팰월드 안티치트 프로젝트] #3 팰월드 치트 동작 원리 분석 - Cheat Engine CT 파일 리버싱 (Inf Spher) (0) | 2026.03.26 |
| [팰월드 안티치트 프로젝트] #1 - 언리얼 엔진 5 게임은 어떻게 분석되는가? (0) | 2026.03.23 |
| picoCTF- No way out (0) | 2026.03.17 |