퍼블리셔 회사에 요청 중 스피드핵 검증해야 한다고 요청이 있었다.

서버 처리 없이 클라에서 스핵 어떻게 막아야 할까?

스피드핵 사용은 해봤지만 원리를 찾아본 적은 없었다.

대략적으로 알고만 있었는데, 이참에 스피드핵에 대해 공부해 봤다.

 

스피드핵 방식

1. 하드웨어 가속

2. 시간 관련 함수 후킹

하드웨어 가속은 요즘은 잘 사용하지 않는다고 함.

 

윈도우 시간 관련 함수

BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount);

시간 간격 측정에 사용할 수 있는 고해상도(<1us) 타임스탬프인 성능 카운터의 현재 값을 검색합니다.

 

BOOL QueryPerformanceFrequency( LARGE_INTEGER* lpFrequency);

성능 카운터의 빈도를 검색합니다.

성능 카운터의 빈도는 시스템 부팅 시 고정되며 모든 프로세서에서 일관됩니다.

따라서 응용 프로그램 초기화 시에만 빈도를 쿼리하면 결과를 캐시할 수 있습니다.

 

CPU 틱 카운트를 받아 오는 함수를 이용해 이전 틱과 현재 틱에 차이로 deltaTime을 만들어 사용하는데, 후킹을 통해 리턴 값을 조작하면 그게 스피드핵이 된다.

 

치트 엔진 이용해 Speedhack 사용 시 Unity에 Time.deltaTime 이 빨라지는지 테스트

TargetFrameRate 지정한 프레임 동안 DateTime이 얼마나 누적됐는지를 확인하는 식으로 검증하려 함.

deltaTime = 1 / frame rate;

 

15 프레임으로 지정한 상태 스피드핵 2배를 적용 시 DateTime.UtcNow, Time.realtimeSinceStartup가 2배 차이가 났다. 

타겟 프레임에 해당하는 시간이 지난 후 DateTime.UtcNow, Time.realtimeSinceStartup에 흐른시간에 차이를 검증.

DateTime 값도 동일하게 2배속을 적용한다 치면 두 값에 비교로 잡을 수 없겠지만, 윈도우 쪽 해킹 툴(Cheat Engine), 안드로이드 쪽 모바일 해킹 툴(GameGuardian)로 테스트 해봤을 때 Time.deltaTime에만 적용되고 DateTime에는 적용되지 않음.

 

샘플 코드

using System;
using UnityEngine;

public class SpeedHackChecker : MonoBehaviour {
    public int TargetFrameRate = 0;
    private DateTime LastUtcTime;
    public float LimitTime = 15f;
    [SerializeField]
    private float LastTimesamp;

    // 테스트용 코드
#if UNITY_EDITOR
    public float TestDateTime;
    public float TestUnityTime;
#endif

    private void Awake()
    {
        Init();
    }

    public void Init()
    {
        TargetFrameRate = 0;
        Application.targetFrameRate = 30;
    }

//#if !UNITY_EDITOR
    private void Update()
    {
        var utcNow = DateTime.UtcNow;
        float realtimeSinceStartup = Time.realtimeSinceStartup;
        if (Application.targetFrameRate != TargetFrameRate) {
            TargetFrameRate = Application.targetFrameRate;

            LimitTime = TargetFrameRate > -1 ? TargetFrameRate * 0.5f : 15f;
            SetTempstaemp(utcNow, realtimeSinceStartup);
            return;
        }

        var elapsedSpan = utcNow - LastUtcTime;
        float elapsedTime = realtimeSinceStartup - LastTimesamp;
        if (elapsedTime > TargetFrameRate) {
            SetTempstaemp(utcNow, realtimeSinceStartup);
            float intervalTime = Mathf.Abs(elapsedTime - (float)elapsedSpan.TotalSeconds);
            if (intervalTime > LimitTime) {
                Debug.LogError($"Client Speed Hack Detected({elapsedSpan.TotalSeconds:F2} : {realtimeSinceStartup:F2})");
                NoticePopupMsg();
            }
        }
#if UNITY_EDITOR
        TestDateTime = (float)elapsedSpan.TotalSeconds;
        TestUnityTime = elapsedTime;
#endif
    }

    private void NoticePopupMsg()
    {
        Init();
        Debug.LogError("시간 조작이 감지 됨");
    }

    private void Quit()
    {
#if UNITY_EDITOR
        // 초기화
#else
        Application.Quit();
#endif
    }

    private void SetTempstaemp(DateTime utcNow, float realtimeSinceStartup)
    {
        LastUtcTime = utcNow;
        LastTimesamp = realtimeSinceStartup;
    }
//#endif
}
반응형

'Programming > Blah Blah' 카테고리의 다른 글

Kinematica 캐릭터 모션 매칭(Motion Matching)  (0) 2021.06.29
Voxelization  (0) 2021.02.21
Clipmaps  (0) 2021.01.27
의존성 주입(Dependency Injection)  (0) 2020.04.10
애니메이션 자료 메모  (0) 2019.12.30
행동 트리(Behavior Tree)  (0) 2019.12.07
C# 프로젝트 관리 툴?  (0) 2019.11.16
소프트웨어 테스트 방식  (0) 2019.11.05