- Black Ops Cold War의 TAC(Treyarch Anti-Cheat) 는 Ricochet 커널 드라이버가 없는 사용자 모드 안티치트지만, 최신 Call of Duty 계열과 상당 부분 유사한 코드 구조를 가짐
- 보호 계층은 Arxan의 실행 파일 암호화·체크섬·jmp 난독화·엔트리포인트 난독화와 Treyarch/IW 계열 포인터 암호화를 함께 사용함
- TAC는 런타임 API 해시 조회, API 훅 패턴 검사, 디버그 레지스터 확인, Windows 테스트 서명 감지, 콘솔 할당 감지, DirectX/오버레이 탐지 등 여러 사용자 모드 탐지를 수행함
- 외부 오버레이는 창 스타일·위치·표시 친화도·프로세스 모듈 목록을 수집해 서버로 업로드하며, Cheat Engine류 메모리 스캐너는 가상 메모리 허니팟으로 감지될 수 있음
- 가장 독특한 기법은 암호화된 커스텀 syscall 스텁으로, ntdll 훅을 우회하고 syscall 출처를 다른 ntdll 함수처럼 보이게 만들어 모니터링을 어렵게 함
분석 대상과 범위
- 분석 대상은 Black Ops Cold War 내부의 사용자 모드 안티치트이며, 명칭은 TAC(Treyarch Anti-Cheat) 로 사용됨
- Black Ops Cold War에는 Modern Warfare 2019 및 이후 타이틀에 있는 Ricochet의 커널 모드 구성요소가 없음
- 최신 Call of Duty와의 큰 차이는 커널 모드 드라이버이며, 안티치트 코드의 대부분은 사용자 모드이고 TAC와 매우 유사함
- 함수 의사코드는 실제 디컴파일 결과가 난독화·해결 코드로 복잡해 재구성된 형태임
- 일부 내용은 치팅 또는 우회 홍보를 피하기 위해 삭제됨
Arxan과 실행 파일 보호
- Arxan은 Black Ops 3 이후 많은 Call of Duty 게임에서 사용되는 난독화·보호 도구임
- 런타임 실행 파일 복호화
- 게임 실행 파일은 패킹되고 암호화되어 있음
- Arxan은 시작 과정에 코드를 삽입해 실제 게임 실행 파일을 언팩·복호화함
- 실행 파일 체크섬
- Arxan은 게임 실행 파일 패치를 지속적으로 감시함
- 디버거 또는 체크섬 불일치가 감지되면 프로세스를 종료함
- jmp 난독화
- 함수의 명령어 사이에 다수의
jmp를 삽입해 정적 분석을 어렵게 만듦 - 큰 함수에 수백 개의 점프가 삽입되면 IDA 분석이 깨지고 외부 도구가 필요해짐
- 함수의 명령어 사이에 다수의
- 엔트리포인트 난독화
- 보호된 Arxan 코드가 실제 엔트리포인트를 언팩하고 실행함
- 이 구간에도 jmp 난독화가 들어갈 수 있어 흐름 추적이 어려움
포인터 암호화
- 중요한 포인터는 사용 직전에 암호화·복호화됨
- 현재 게임 전역 객체
- 엔티티 배열
- 오브젝트 포인터 등
- 같은 암호화 방식에는 16개 변형이 있으며, 현재 PEB 주소가 어떤 방식을 쓸지 결정함
- 이 방식은 Cheat Engine의 포인터 스캔을 방해함
- 전역 값에는 암호화된 값만 저장됨
- 복호화된 값은 스택에만 존재함
- 복호화 포인터를 얻으려면 복호화 명령어 추적 도구를 쓰거나, 게임이 이미 복호화한 지점에 훅을 거는 방식이 필요함
런타임 API 조회와 훅 탐지
- TAC는 인라인된 런타임 API 조회 함수를 사용함
- 모듈 해시와 API 이름 해시를 받음
- 로드된 모듈 목록을 순회하며 이름을 해시함
- 모듈의 export 함수를 순회하며 컴파일 타임 해시와 비교함
- 해시 식별은 게임 프로세스의 로드된 모듈 목록과 게임의 해싱 함수를 이용해 진행됨
- 모듈명과 export 이름의 해시를 계산함
- 디컴파일 결과에서 base hash와 function hash를 수작업으로 가져와 어떤 API를 호출하는지 매칭함
- 해시는 게임 버전마다 같지 않음
- 함수 포인터가 전역 변수에 저장되므로, 가상 주소를 로드된 DLL의 export 함수와 비교하는 방식으로도 식별 가능함
- TAC의 API 훅 탐지는 현재 7개 패턴만 확인함
push/movabs/xchg/ret계열 스텁push imm후retcalljmp [rip+x]
- 모든 중요한 API를 검사하지는 않고, 자신이 사용하는 API에 대한 훅을 검사함
디버그 레지스터와 드라이버 테스트 서명 감지
- 디버그 레지스터는 Arxan의
.text패치 감시를 우회하는 비코드 패치 훅 방식으로 사용될 수 있음 - TAC는 스레드 컨텍스트에서 DR0~DR3 값을 확인함
- 값이 있으면 현재 프로세스 내부인지 여부에 따라 다른 메시지로 콜백을 호출함
- 이후 종료 함수로 흐름이 이동함
- DR0~DR3는 privileged register라서 일반 어셈블리로 직접 읽을 수 없고, Windows 커널 또는 예외 전달을 통해 가져와야 함
- Windows 테스트 모드는 정상 서명이 없는 커널 모드 드라이버 실행을 허용함
- TAC는
NtQuerySystemInformation으로 테스트 서명 활성화 여부를 확인함- 이 감지만으로 직접 ban되지는 않지만 계정이 flag됨
프로세스 종료 방식
- TAC는 두 가지 방식으로 프로세스를 종료함
- 첫 번째 방식은 레지스터를 지운 뒤
NtTerminateProcess를 호출함RCX를-1로 설정함NtTerminateProcess가 훅된 것으로 감지되면 이 방식을 쓰지 않음
- 두 번째 방식은 레지스터를 지운 뒤
0x0으로 점프해 프로세스를 크래시시킴 - 두 방식 모두 중요한 레지스터를 지우기 때문에 복구가 어려움
콘솔·시각화·오버레이 탐지
- 내부 치트는 로그 출력이나 메뉴 구현을 위해
AllocConsole을 사용할 수 있음 - TAC는 콘솔 창 또는 PEB의
ConsoleHandle을 확인해 콘솔 할당을 감지함 - 내부 시각화는 주로 그래픽 API 훅으로 화면에 그려짐
- 최신 Call of Duty는 DirectX 12를 사용함
- 흔한 훅 대상은
IDXGISwapChain::Present - DirectX 12에서는 command queue가 필요하며,
ID3D12CommandQueue::ExecuteCommandLists가 흔한 확보 지점임
- OBS Studio, Streamlabs OBS, Discord 게임 오버레이, Steam 게임 오버레이도 유사한 위치에서 동작할 수 있음
- Steam과 Discord는 그리기를 수행함
- OBS 계열은 게임 캡처 사용 시 렌더링 이미지를 캡처함
- TAC는 현재 DXGI present 함수 자체를 스캔하지 않고, vtable의 present 포인터를 확인함
외부 치트와 창 기반 탐지
- 외부 치트는 게임 창 위를 덮는 overlapped window를 만들 가능성이 큼
- TAC는 모든 창을 순회하며
GetWindowLongA로 WS_EX_LAYERED 스타일을 확인함 - 이후
GetWindowRect로 게임 창과 겹치는지 비교함- 겹침 비율이
0.5이상이고 캐시 개수가 8개 미만이면 해당hwnd를 저장함 - 예시 화면 크기로
1920x1080에 해당하는 값이 나타남
- 겹침 비율이
- 캐시된 창은 별도 함수에서 추가 조사됨
GetWindowTextW로 창 텍스트 확인GetClassNameA로 클래스 이름 확인GetWindowDisplayAffinity로 표시 친화도 확인
SetWindowDisplayAffinity와WDA_EXCLUDEFROMCAPTURE를 사용해 녹화·스크린샷 도구에서 숨기는 경우도 TAC가 확인함- 창 관련 정보는 암호화 버퍼에 저장되고 서버로 업로드됨
- 창 텍스트
- 클래스 이름
- 창 위치와 스타일
- display affinity
- 겹친 창 프로세스의 모듈 목록과 exe 이름
- 겹친 창 프로세스는
OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION)로 열고,K32EnumProcessModules와K32GetModuleFileNameExW로 모듈명을 수집함
Cheat Engine류 메모리 스캐너 감지
- Cheat Engine은 Windows 가상 메모리 동작 때문에 감지하기 쉬움
- 프로그램이
VirtualAlloc으로 가상 메모리를 할당해도, 접근 전에는 물리 메모리로 backing되지 않음 - 게임은 메모리를 할당한 뒤 사용하지 않을 수 있음
- Cheat Engine 또는 Process Hacker의 메모리 탭이 해당 영역을 스캔하면 접근이 발생하고 메모리가 valid 상태가 됨
- TAC식 허니팟은
K32QueryWorkingSetEx로 해당 가상 주소가 실제 접근되었는지 확인해 메모리 스캐너를 감지함
시그니처 스캔 방해
- 게임 해커는 업데이트 후에도 치트가 자동으로 동작하도록 signature scanning을 자주 사용함
- Treyarch의 아이디어는 다시 호출되지 않을 함수에서 return address 주변을 PAGE_NOACCESS로 보호하는 방식임
- signature scanner는 실행 파일 바이트를 처음부터 끝까지 읽으며 패턴을 찾음
- 각 바이트마다 접근 가능 여부를 조회하면 매우 느리기 때문에,
PAGE_NOACCESS영역에 도달하면 프로세스가 크래시할 수 있음 - 이 방식은 완전한 차단책은 아니지만 많은 분석자를 어렵게 만들 수 있음
안티디버깅
- TAC 자체의 안티디버그 검사는 단순하지만, Arxan도 별도의 안티디버그 기법을 제공함
- TAC는 현재 프로세스의 모든 스레드를 순회함
CreateToolhelp32Snapshot과 thread snapshot을 사용함- 각 스레드의 TEB에서
DbgSsReserved를 확인해 DebugObject 존재를 감지함
- 비정상 메모리에 쓰기를 수행해 access violation을 유발하는 기법도 있음
- 코드가 예외 이후 지점까지 도달하면 디버거가 예외를 처리했거나 우회했다고 판단함
CheckRemoteDebuggerPresent도 사용함ThreadHideFromDebugger는 예외를 디버거가 아니라 프로세스로 보내게 함- 디버거가 프로세스를 멈추려 할 때
STATUS_BREAKPOINT예외가 발생해 프로세스가 종료될 수 있음 - 사용자 모드에서는 이 플래그를 unset할 수 없음
- 이 전술은 실행 파일 엔트리포인트 이전의 TLS callback에서 실행됨
- 디버거가 프로세스를 멈추려 할 때
네트워크 트래픽 모니터링
- TAC는 모든 활성 연결을 저장하지 않고, 특정 조건만 찾음
- 감지 대상은 게임 프로세스 내부에 로컬 네트워크 서버를 만드는 방식임
- 치터가 shellcode를 게임 프로세스에 작성함
- 게임 프로세스 안에서 네트워크 서버를 시작함
- 외부 애플리케이션이 이 로컬 서버와 정보를 주고받음
- TAC는
GetTcpTable2로 TCP 테이블을 가져오고, 현재 프로세스가 만든 연결과 다른 프로세스의 포트 관계를 비교해 조건을 감지함
암호화된 커스텀 syscall 스텁
- ntdll의 export API 다수는 내부에서 syscall을 수행함
- 커스텀 syscall 스텁을 쓰면 사용자 모드 치트가 ntdll 함수를 훅해도 우회할 수 있음
- syscall은 instrumentation callback에 노출될 수 있음
- instrumentation callback은 syscall 이후 호출됨
- 반환 주소는 syscall instruction 바로 뒤가 됨
- TAC는 암호화된 syscall 스텁으로 정적 분석을 어렵게 함
- 스텁은
.text섹션에 할당된 큰 영역을 write/execute로 보호한 뒤 구성됨 NtReadFile에서 syscall instruction을 찾고, CPU 시간 값을 난수 요소로 사용해 위치를 바꿈- 모니터링하는 쪽에서는 syscall이 임의의 ntdll 함수에서 발생한 것처럼 보일 수 있음
- 실제 syscall은
NtReadFile이 아닐 수 있음 eax의 syscall index를 확인하지 못하면 어떤 syscall인지 알기 어려움
- 실제 syscall은
- 재실행할 때 syscall instruction 위치가 바뀌는 예시가 있음
안티디버거 숨김 우회 탐지
ThreadHideFromDebugger를 설정하려면NtSetInformationThread가 호출되어야 함- 치터는 이 API를 훅해 성공을 반환하게 만들 수 있음
- 그러면 안티치트는 숨김 설정이 성공했다고 믿지만 실제로는 아무 일도 일어나지 않을 수 있음
- TAC는 잘못 만든 훅을 잡기 위해 잘못된 인자를 넣어 호출 결과를 확인함
- 길이 인자가 맞지 않아 실패해야 하는 호출이 성공하면 감지함
- 디버거와 ScyllaHide 환경에서 다른 반환값이 나온 예시가 있음
- 가짜 핸들을 넣어
ThreadHideFromDebugger요청에 무조건 성공을 반환하는 훅도 확인함
원격 스레드 생성 차단
- TAC는
STATUS_PRIVILEGED_INSTRUCTION예외에서 현재 스레드에TerminateThread를 호출하는 예외 핸들러를 설치함 - 수동 매핑 DLL은 원격 프로세스에서 shellcode를 실행할 방법이 필요하며, 흔한 방식이
CreateRemoteThread임 - Windows PE의 TLS callback은 스레드가 생성될 때 스레드 엔트리포인트보다 먼저 호출될 수 있음
- TAC는 새 스레드 컨텍스트에서 시작 주소를 확인함
NtQueryInformationThread로 Win32 start address를 가져옴- 로드된 모듈 목록을 순회해 시작 주소가 정상 모듈 범위 안에 있는지 확인함
- 시작 주소가 어떤 로드된 모듈 범위에도 없으면 탐지를 저장하고, privileged instruction 예외를 발생시켜 해당 스레드를 종료함
기타 검사와 결론
- 알 수 없는 검사로
NtQuerySystemInformation의AllocationGranularity가0x10000인지 확인하는 코드가 있음- 가상 머신이나 커스텀 Windows 버전을 flag하는 것처럼 보임
- TAC는 연결된 모듈 목록에 크게 의존하므로, PEB의
InMemoryOrderModuleList가 빈 목록인지 확인함- 빈 목록으로 만들면 프로세스 자체가 깨질 가능성이 있음
- 최종적으로 TAC는 다음 기능을 가진 사용자 모드 안티치트임
- 런타임 API 조회
- 잘못 만든 훅 탐지
- 외부 오버레이 탐지
- 내부 DirectX 훅 탐지
- 사용 API 훅 검사
- 디버거와 디버깅 흔적 확인
AllocConsole감지CreateRemoteThread감지- spoofed·encrypted syscall 스텁
- Arxan은 강한 난독화, 정적 분석 방해, IDA Pro를 깨는 기법,
.text수정 감시, 자체 안티디버그 기능으로 TAC를 보조함 - TAC와 유사한 코드가 현대 Call of Duty 게임에서도 사용됨