커널 안티치트의 내부 동작 원리: 왜 게임 회사들은 내 PC에 루트킷을 설치하는가


최근 발로란트(Valorant)를 설치해본 엔지니어라면, 게임 하나 하겠다고 시스템을 재부팅해야 하는 상황에 짜증이 났을 것입니다. 뱅가드(Vanguard)가 OS 부팅 시점에 로드되어야 하기 때문이죠. 보안 엔지니어로서 내 PC에 Ring 0 권한의 상용 루트킷(Rootkit)을 설치하는 것은 영 찜찜한 일입니다. 하지만 최근 S4dbrd의 블로그 글과 해커뉴스(Hacker News)의 열띤 토론을 보면서, 왜 게임사들이 이토록 극단적인 선택을 할 수밖에 없는지 아키텍처 관점에서 다시 한번 생각해보게 되었습니다.

결론부터 말하자면, 이것은 윈도우(Windows) 커널 아키텍처가 만들어낸 필연적인 군비 경쟁(Arms Race)입니다. 오늘 포스팅에서는 현대 커널 안티치트가 어떻게 동작하는지, 그리고 왜 유저모드 보호만으로는 한계가 있는지 깊게 파헤쳐 보겠습니다.

유저모드 안티치트의 죽음

과거의 안티치트는 유저모드(Ring 3)에서 돌았습니다. 하지만 유저모드 안티치트는 본질적으로 신뢰 모델이 깨져 있습니다. 안티치트가 ReadProcessMemory를 호출해 게임 메모리의 무결성을 검사하려 해도, 커널모드(Ring 0)에서 동작하는 핵(Cheat) 드라이버가 이를 후킹하여 가짜 데이터를 반환하면 그만입니다.

유저모드 프로세스는 자기 위에 있는 커널에서 무슨 일이 일어나는지 전혀 알 수 없는 장님이나 마찬가지입니다. 치트 개발자들은 안티치트 엔지니어들보다 몇 년은 앞서 커널의 강력함을 깨달았고, 결국 방어자들 역시 커널로 내려가는 수밖에 없었습니다.

3계층 아키텍처와 부팅 시점 로드

현대 커널 안티치트(BattlEye, EAC, Vanguard 등)는 보편적으로 3계층 아키텍처를 따릅니다.

  • 커널 드라이버: Ring 0에서 실행되며 콜백 등록, 시스템 콜 인터셉트, 메모리 스캔을 담당합니다. 실질적인 권한을 가진 핵심 컴포넌트입니다.
  • 유저모드 서비스: SYSTEM 권한으로 실행되는 윈도우 서비스로, 백엔드 서버와의 네트워크 통신 및 텔레메트리 전송을 담당합니다.
  • 게임 인젝션 DLL: 게임 프로세스 내부에 로드되어 유저모드 측면의 검증을 수행합니다.

배틀아이(BattlEye)의 아키텍처를 보면 이 구조가 명확히 보입니다.

여기서 흥미로운 점은 드라이버 로드 시점입니다. 배틀아이나 EAC는 게임 실행 시점에 드라이버를 로드하지만, 라이엇 게임즈의 뱅가드는 시스템 부팅 시점(SERVICE_BOOT_START)에 드라이버(vgk.sys)를 로드합니다.

개인적으로 뱅가드의 방식이 아키텍처 관점에서는 훨씬 강력하다고 봅니다. 안티치트가 가장 먼저 로드되어 자리를 잡으면, 이후에 로드되는 모든 드라이버를 감시하고 평가할 수 있기 때문입니다. 치트 드라이버가 시스템에 숨어들 틈 자체를 주지 않는 Allowlist 모델을 강제할 수 있는 것이죠.

커널 콜백: 핸들 권한 박탈하기

안티치트가 게임 프로세스를 보호하는 가장 기본적인 방법은 윈도우 커널이 제공하는 ObRegisterCallbacks API를 사용하는 것입니다. 외부 프로세스가 게임 프로세스에 접근하려고 핸들을 열 때, 이 콜백이 개입합니다.

OB_CALLBACK_REGISTRATION callbackReg = {0};
OB_OPERATION_REGISTRATION opReg[2] = {0};

// 프로세스 객체에 대한 핸들 오픈 감시
opReg[0].ObjectType = PsProcessType;
opReg[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
opReg[0].PreOperation = ObPreOperationCallback;
// ... 생략 ...
NTSTATUS status = ObRegisterCallbacks(&callbackReg, &gCallbackHandle);

치트 프로그램이 OpenProcess를 호출하면 어떻게 될까요? 안티치트의 콜백이 발동하여 PROCESS_VM_READPROCESS_VM_WRITE 같은 메모리 접근 권한을 싹 지워버립니다(strip). 치트 프로그램은 핸들을 얻긴 하지만, 그 핸들로 메모리를 읽으려 하면 ERROR_ACCESS_DENIED를 뱉어내는 껍데기 핸들이 됩니다.

메모리 스캔과 VAD 트리 순회

제가 가장 감탄한 부분은 메모리 스캔 기법입니다. 치트 개발자들은 일반적인 LoadLibrary 대신 Manual Mapping 기법을 사용해 악성 코드를 메모리에 욱여넣습니다. 이렇게 하면 PEB(Process Environment Block) 모듈 리스트에 나타나지 않아 유저모드 API로는 절대 찾을 수 없습니다.

이를 잡기 위해 커널 안티치트는 메모리 관리자가 사용하는 커널 내부 구조체인 VAD(Virtual Address Descriptor) 트리를 직접 순회합니다.

VOID WalkAVLTree(PMMADDRESS_NODE node)
{
    if (node == NULL) return;
    PMMVAD vad = (PMMVAD)node;
    
    // Private 메모리이면서 실행 권한이 있는지 확인
    if (vad->u.VadFlags.PrivateMemory && IsExecutableProtection(vad->u.VadFlags.Protection)) {
        // 백업하는 파일이 없다면 매우 의심스러움
        if (vad->Subsection == NULL) {
            ReportSuspiciousVAD(vad);
        }
    }
    WalkAVLTree(node->LeftChild);
    WalkAVLTree(node->RightChild);
}

파일 백업(File-backed)이 없는 순수 Private 메모리 영역인데 실행 권한(Executable)이 있다? 이는 십중팔구 수동 매핑된 치트 코드나 쉘코드입니다. VAD는 유저모드에서 조작이 불가능하므로 빼도 박도 못하는 증거가 됩니다.

BYOVD 공격과 방어자의 딜레마

윈도우 64비트는 DSE(Driver Signature Enforcement)를 통해 서명되지 않은 드라이버의 로드를 막습니다. 그래서 요즘 치트 개발자들은 BYOVD(Bring Your Own Vulnerable Driver) 기법을 씁니다. MSI나 ASUS 같은 하드웨어 벤더의 합법적으로 서명된 옛날 드라이버(임의의 메모리 읽기/쓰기 취약점이 있는)를 로드하고, 이를 익스플로잇해 커널 권한을 탈취하는 방식입니다.

이 권한을 얻은 치트 개발자들은 자신들의 흔적을 지우기 위해 커널 내부의 캐시인 PiDDBCacheTable을 덮어써버립니다. 안티치트 엔지니어들은 이를 막기 위해 취약한 드라이버의 거대한 Blocklist를 유지하고, 메모리 풀(Pool)을 스캔해 캐시가 조작되었는지 검증해야 합니다. 말 그대로 끝없는 창과 방패의 싸움입니다.

해커뉴스의 반응: 서버사이드와 머신러닝

해커뉴스 스레드를 보면 흥미로운 토론이 많습니다. 특히 시스템 설계를 조금 아는 개발자들이 자주 하는 주장이 있습니다. “그냥 모든 연산을 서버에서 처리하면(Server-side authoritative) 되는 거 아님?”

분산 시스템을 다뤄본 엔지니어로서 단언컨대, FPS 게임에서 이는 몽상에 가깝습니다. 클라이언트 측 예측(Client-side prediction) 없이 서버 응답만 기다린다면, 레이턴시(Latency) 때문에 게임이 이메일로 체스를 두는 수준으로 느려질 것입니다.

반면, 머신러닝과 통계적 허니팟(Statistical Honeypot)을 활용한 이상 탐지(Anomaly Detection)는 꽤 현실적인 대안으로 논의되고 있습니다. 실제로 밸브(Valve)는 Dota 2 클라이언트 내부에 일반 유저는 절대 읽을 일이 없는 ‘비밀’ 데이터를 심어두고, 이를 읽어간 계정들을 싹 다 밴(Ban)해버린 적이 있죠. 훌륭한 접근이지만, 이는 사후 조치(Reactive)일 뿐 실시간 에임봇의 즉각적인 피해를 막기엔 한계가 있습니다.

총평

커널 안티치트는 기술적 분류상 분명히 루트킷(Rootkit)의 특성을 공유합니다. 시스템의 가장 깊은 곳을 들여다보고, 정당한 보안 제품을 위해 설계된 콜백을 가로채니까요.

하지만 아키텍처를 이해하고 나면 이들이 왜 이런 선택을 했는지 납득할 수밖에 없습니다. 이것은 게임사가 고객의 PC를 감시하고 싶어서가 아니라, 윈도우 OS의 신뢰 모델이 강제한 필연적인 결과입니다. 사이버 보안 업계에서 가장 고도화된 공격과 방어 기술이 엔터프라이즈 서버가 아닌 10대들의 비디오 게임에서 벌어지고 있다는 사실, 참으로 아이러니하면서도 흥미롭지 않습니까?