안티치트

유니티 스피드핵(SpeedHack)의 동작 원리와 Native 기반 탐지 전략

스피드핵 하나가 게임 경제를 무너뜨린다

방치형 RPG에서 배속을 돌린 유저가 하루 만에 정상 유저 몇 달 치 재화를 쓸어 담습니다. 리듬·액션 게임의 랭킹 상위권이 비정상적인 플레이 타임과 점수로 도배됩니다. 대전 게임에서 한 명만 1.2배 빠르게 움직여도 공정한 매치메이킹은 그 자리에서 무너집니다.

스피드핵(SpeedHack)은 가장 흔하면서도 가장 과소평가되는 치트입니다. 메모리 변조처럼 골드·아이템 값을 직접 건드리는 대신, 게임이 인식하는 "시간"의 흐름 자체를 통째로 비틀기 때문입니다. "재화 획득량 검증"이나 "이동 좌표 검증" 같은 사후 방어선으로는 이 시간 왜곡을 근본적으로 잡기 어렵고, 피해는 매출 감소·유저 이탈·랭킹 신뢰도 하락으로 번집니다.

이 글에서는 스피드핵이 유니티 엔진 뒤에서 실제로 어떻게 시간을 조작하는지, 흔히 시도하는 C# 레벨 방어가 왜 쉽게 뚫리는지, 그리고 Native(C++) 계층에서 이를 어떻게 탐지해야 하는지 실전 관점에서 살펴봅니다.

스피드핵은 무엇을 조작하나 — 동작 원리

유니티 게임의 핵심 루프는 "이전 프레임으로부터 시간이 얼마나 흘렀는가"를 끊임없이 계산합니다. Time.deltaTime이 바로 그 값이며, 이동 속도·쿨다운·물리·애니메이션·재화 누적 등 거의 모든 시스템이 여기에 의존합니다.

스피드핵의 원리는 단순합니다. 이 시간 측정값을 인위적으로 부풀리거나 줄여서, 엔진이 "실제보다 더 많은 시간이 흘렀다"고 착각하게 만드는 것입니다. 핵심은 유니티가 시간을 스스로 만들지 않고 OS 타이머 API를 호출해 얻는다는 점이며, 공격자는 그 호출을 가로챕니다.

[ 일반적인 시간 흐름 ]
OS 하드웨어 타이머 ──> OS 타이머 API ──> 유니티(Time.deltaTime) ──> 게임 로직

[ 스피드핵 적용 시 ]
OS 하드웨어 타이머 ──> OS 타이머 API ──> [스피드핵 후킹: ×10] ──> 유니티 ──> 게임 로직 10배 가속

1) PC(Windows) Cheat Engine 류의 스피드핵은 게임 프로세스에 DLL을 인젝션한 뒤, 시간 측정 API를 후킹합니다. 대표적으로 QueryPerformanceCounter, timeGetTime, GetTickCount / GetTickCount64 입니다. 후킹된 함수는 원래 틱 값에 배율(예: ×10)을 곱해 돌려주고, 게임은 정직하게 API를 불렀을 뿐인데 한 프레임 사이에 10배의 시간이 흐른 것처럼 인식합니다.

2) 모바일(Android) GameGuardian 류도 메커니즘은 같습니다. libcgettimeofday·clock_gettime 같은 함수를 가로채거나(PLT/GOT 후킹), 앱을 가상 공간(Virtual Space) 안에서 실행해 시간 기준선 자체를 조작합니다. 루팅 환경에서는 더 낮은 계층까지 개입하기도 합니다.

어느 플랫폼이든 패턴은 같습니다. 신뢰의 사슬 맨 아래(OS 타이머)를 비틀어, 그 위에 쌓인 엔진과 게임 로직 전체를 한 번에 속이는 것입니다.

흔히 시도하는 C# 레벨 방어의 한계

스피드핵을 만난 팀은 대개 C# 레벨에서 자체 방어를 붙입니다. 방향은 옳지만 실전에서는 다음 한계에 부딪힙니다.

1) Time.deltaTime vs Time.unscaledDeltaTime 비교 — 무의미 둘 다 같은 OS 타이머에서 파생됩니다. 타이머가 후킹되면 두 값이 같은 비율로 함께 왜곡되어 차이가 나지 않습니다. "조작된 두 개의 시계"를 맞대어 정상인지 확인하는 셈입니다.

2) C# 자체 시계와 대조(DateTime.UtcNow, Stopwatch) — 역분석으로 제거됨 별도 기준으로 대조하는 발상은 옳습니다. 문제는 검사 코드가 관리 코드(C#)에 노출된다는 점입니다. IL2CPP로 빌드해도 global-metadata.dat + Il2CppDumper로 클래스·메서드 구조가 상당 부분 복원되어, 공격자는 탐지 메서드를 찾아 분기를 NOP/JMP 패치로 무력화합니다.

// 공격자는 IL2CPP 역분석으로 이 메서드를 찾아,
// 분기를 실행하지 않도록 바이너리를 패치한다.
bool CheckSpeedHack() {
    if (MySystemTime != UnityTime) {
        return true; // <-- 이 분기를 건너뛰도록 JMP 패치
    }
    return false;
}

3) 서버 타임스탬프 검증 — 강력하나 전 구간 적용 불가 클라이언트 액션 간격을 서버 시간과 대조하는 건 확실한 최종 방어선입니다. 그러나 싱글·방치형 진행, 잦은 세부 움직임까지 전부 서버로 검증하기엔 대역폭·연산 비용이 크고, 오프라인 지원 게임에는 적용이 어렵습니다.

결론적으로, 공격은 OS 타이머가 동작하는 Native 단에서 들어오는데 감시하는 눈이 그보다 위인 C# 관리 코드에 머물면, 공격자에게 먼저 칼자루를 쥐여주는 셈입니다.

Native(C++) 계층에서 스피드핵을 탐지하는 법

안정적인 탐지를 위해서는 공격자가 왜곡하는 계층과 같거나 더 낮은 영역에서 독립적인 감시 체계를 확보해야 합니다. 핵심 원리는 세 축입니다.

(1) 후킹에 강한 저수준 독립 시계 확보 치트가 주로 노리는 고수준 API(QueryPerformanceCounter 등) 대신, 더 낮은 시간원을 직접 읽어 기준선을 만듭니다. Windows에서는 CPU 사이클 카운터(rdtsc)나 사용자 영역에서 읽을 수 있는 시스템 시간 필드, Android에서는 라이브러리 인터페이스를 우회한 syscall 경로의 단조 시계(CLOCK_MONOTONIC/CLOCK_BOOTTIME)가 그 예입니다. 이 기준선과 게임이 쓰는 시간의 흐름 비율을 교차 검증해, 임계치 이상 어긋나면 시간축 조작으로 판정합니다.

다만 rdtsc도 만능은 아닙니다. 하이퍼바이저 기반 도구는 RDTSC 자체를 트랩·가상화할 수 있고, 앱이 가상 공간 안에서 실행되면 직접 syscall도 컨테이너가 가로챌 수 있습니다. 그래서 저수준 시계는 "고수준 API 후킹에 강한 한 축"으로 보고, 아래 (2)·(3) 및 서버 신뢰 시간과 함께 묶어야 견고해집니다.

(2) 시간 API의 메모리 후킹 흔적 직접 탐지 배율을 곱하려면 시간 함수의 진입부(프롤로그)를 변조해 점프(JMP, 예: 0xE9)하게 만들거나 함수 주소 테이블(IAT/EAT)을 바꿔야 합니다. Native 탐지는 주요 시간 함수의 첫 바이트가 변조됐는지, 주소가 비인가 모듈을 가리키는지 점검해 조작 도구의 흔적을 직접 잡습니다.

(3) 보안 로직의 C# 디커플링 가장 중요한 설계 원칙입니다. 탐지 루프·시계 비교·대응 로직이 C# 메모리와 IL2CPP 산출물에 노출되면 안 됩니다. 핵심 로직을 난독화된 Native C++ 바이너리(.dll/.so)의 독립 스레드에서 실행하면, 공격자가 C# 전체를 분석해도 감시자의 위치를 알기 어려워 역분석·패치 난이도가 크게 올라갑니다.

OZero Security의 스피드핵 대응

Native 안티치트를 직접 개발하려면 OS 내부 이해, 멀티플랫폼 대응, OS 업데이트 추적 유지보수까지 필요해 중소 스튜디오에는 부담이 큽니다. OZero Security는 유니티에 바로 적용하는 Native C++ 기반 안티치트를 제공합니다.

  • 신뢰 시간 기반 검증(OZero의 핵심 차별점): 로컬 시계에만 의존하지 않고, 다수의 webTime 엔드포인트를 라운드로빈 + 쿼럼(quorum)으로 대조해 신뢰할 수 있는 기준 시간을 세웁니다. 자체 운영 webTime 엔드포인트를 등록할 수도 있어, 로컬 타이머가 조작돼도 외부 신뢰 시간과의 불일치로 시간축 조작을 판별합니다.
  • Native C++ 탐지: 저수준 시계 교차 검증과 시간 API 후킹 흔적 탐지를 관리 코드 밖에서 수행합니다.
  • 성능 설계: 보안 로직을 C#이 아닌 Native C++ 계층에서 다루며, 보호 값에는 GC 할당이 발생하지 않도록 설계됐습니다.
  • 유연한 대응: 탐지 시 동작은 정책으로 설정합니다 — 로그 기록, 기능 제한, 프로세스 종료, 또는 암호화된 탐지 로그를 서버/백오피스로 전송해 실시간 제재(Pro Add-on 텔레메트리).
  • 도입: 코드 작성 없이 .unitypackage를 임포트하고 대시보드에서 모듈을 토글한 뒤 Config를 저장하는 방식으로 적용합니다.

실시간 탐지 데모

OZero Security가 스피드핵을 실시간으로 탐지하는 실제 구동 모습입니다.

Windows(PC) 스피드핵 실시간 탐지
Android(모바일) 스피드핵 실시간 탐지

정리

  • 시간을 훔치는 치트: 스피드핵은 개별 값이 아니라 게임 시스템의 시간축을 통째로 속여 경제와 공정성을 마비시킵니다.
  • C# 보안의 한계: IL2CPP를 거쳐도 C#에 노출된 검사 로직은 역분석 도구 앞에서 무력화됩니다.
  • 정석적 해법: 저수준 독립 시계, 시간 API 후킹 흔적 탐지, 검사 로직의 Native 분리를 결합하고, 로컬 시계의 한계는 서버 신뢰 시간(webTime 쿼럼) 으로 보완해야 합니다.

스피드핵 하나를 막는 데도 OS 아키텍처부터 어셈블리 레이어, 역분석 우회까지 방대한 지식이 필요합니다. 끝없는 치트 패턴 추격에 지쳤다면, 검증된 솔루션으로 콘텐츠 개발 본연에 집중하는 선택을 고려해 보세요.

OZero Security가 스피드핵을 어떻게 탐지·대응하는지 직접 확인해 보세요.

함께 읽으면 좋은 글