시작하기
이 가이드는 OZero Security 설치부터 첫 번째 보안 기능 적용까지 단계별로 안내합니다. 보안 전문 지식이 없어도 약 3분이면 기본 설정을 완료할 수 있습니다.
개요
OZero Security는 일반 해킹 툴이 접근하기 어려운 Native C++ 레이어에서 보안 로직을 실행하여 Unity 게임을 보호합니다. Unity 에디터 내 대시보드에서 모듈을 활성화하는 것만으로 핵심 보호 기능이 동작합니다. 씬 설정이나 별도 코드 작성은 필요하지 않습니다.
- 빌드 무결성 검사 (앱 변조 탐지)
- 스피드핵 및 타임핵 탐지
- 메모리 인젝션 모니터링
- 암호화된 인게임 변수 타입 (Secure Types)
- 암호화된 세이브 파일 및 PlayerPrefs
- 활성화된 탐지기는 SDK 시작 시 자동으로 준비됩니다 — 씬 배치나 반복 코드(boilerplate) 호출이 필요 없습니다.
- 보안 설정은 빌드 과정에서 보호되어 일반 플레이어 배포물에 평문 설정이 노출되지 않습니다.
- Native C++ 런타임 가드가 관리형 Unity 코드 바깥에서 추가 검증 계층을 제공합니다.
- 위협 대응은 SDK 정책과 Pro 텔레메트리로 중앙화되어, 구현 세부를 노출하지 않고도 현장 조사가 쉬워집니다.
1
패키지 임포트
Unity 에디터를 열고 OZero Security 패키지를 임포트합니다. Unity Package Manager를 통하거나 .unitypackage 파일을 더블클릭하여 임포트할 수 있습니다.
Import Unity Package 다이얼로그가 나타나면 모든 항목이 체크된 상태로 Import를 클릭하세요. 필요한 스크립트, 네이티브 플러그인, 에디터 도구가 자동으로 추가됩니다.
2
대시보드 열기
임포트가 완료되면 Unity 메뉴바에서 OZero Security Dashboard를 엽니다:
커스텀 에디터 창이 열립니다. 이곳이 OZero 보안 모듈 전체를 제어하는 중앙 패널입니다. Project 하이라키는 건드릴 필요가 없습니다.
3
모듈 활성화
대시보드 안에서 보안 모듈 목록과 토글 스위치를 확인할 수 있습니다. 사용하려는 모듈을 활성화하세요. 아래는 권장 시작 구성입니다.
보안 프리셋 선택
먼저 프리셋을 선택한 뒤, 프로젝트에 맞춰 개별 모듈만 조정하세요. 일반적인 라이브 게임은 Standard부터 시작하는 것을 권장합니다. 보호 수준, 성능, 오탐 가능성의 균형이 가장 좋습니다.
| 프리셋 | 권장 사용처 | 적용 정책 요약 |
|---|---|---|
| Low | 프로토타입, 개발 빌드, 초기 QA | 가벼운 핵심 검사만 유지합니다. 테스트 환경이 너무 일찍 차단되지 않도록 플랫폼 네이티브 검사와 강제 종료 정책을 완화합니다. |
| Standard | 대부분의 출시 게임에 권장되는 기본값 | 핵심 보호 세트, 시작 시 검증, 런타임 재검증, 에뮬레이터 검사, 권장 IL2CPP 파일 커버리지를 켭니다. 호환성과 보호 수준의 균형을 맞춘 구성입니다. |
| Strict | 고위험 라이브 서비스, PvP, 경쟁형 빌드 | 가장 넓은 커버리지를 적용하고 더 많은 실패를 치명적 위반으로 처리합니다. 플랫폼, 서명, 스토어 배포 흐름을 충분히 테스트한 뒤 적용하세요. |
| 모듈 | 기능 설명 | 권장 여부 |
|---|---|---|
| Build Integrity Validator | 앱 바이너리 변조 여부 탐지 | 권장 |
| Speed Hack Detector | 시간 조작 치트 탐지 | 권장 |
| Injection Detector | 메모리 후킹 툴 모니터링 | 권장 |
| Install Source Validator | 불법 APK 차단 (Android 전용) | 선택 |
별도의 "Auto Setup" 버튼을 누를 필요가 없습니다. 대시보드에서 사용할 모듈만 체크하고 OZeroSecurityConfig 에셋을 저장하면 됩니다. 플레이어 실행 시 SDK가 게임 시작 전에 활성 탐지기를 자동으로 준비합니다.
OZeroSecurityConfig 에셋을 저장하면, 다음 실행 시 모든 활성 모듈이 자동 생성됩니다. 씬 배치나 버튼 클릭이 필요하지 않습니다.
라이선스 모델 — Standard / Plus / Pro
OZero Security는 Standard, Plus, Pro 세 가지 티어로 제공됩니다. Standard는 공용 네이티브 모듈을 사용하는 완전 서버리스 구성입니다. Plus는 런타임 서버 없이 앱별 Native Variant 패키지와 매니페스트 / Bundle ID 바인딩을 추가합니다. Pro는 Plus 구성을 포함하고 Cloud Telemetry, Signed Time, 원격 정책, 서버 검증, 디바이스 한도 같은 운영 기능을 활성화합니다.
| 항목 | Standard | Plus | Pro |
|---|---|---|---|
| 라이선스 키 | — (없음) | OZ-PLS-XXXX ×6 |
OZ-PRO-XXXX ×6 |
| 부팅 시 네트워크 | 전혀 없음 — 완전 오프라인 | 런타임 서버 불필요 — 포털에서 Variant 다운로드만 진행 | 디바이스당 POST /v1/activate 1회 후 캐시 |
| 10개 보호 모듈 | 10개 보호 모듈 전체 활성화 | 10개 전체 (로컬 보호 모듈은 Standard와 동일) | 10개 전체 (Standard와 동일) |
| 네이티브 Variant | 공용 네이티브 모듈 | 앱별 Variant + 매니페스트 바인딩 | 포함 |
| 클라우드 텔레메트리 | 꺼짐 (조용히 동작 없음) | 꺼짐 (서버리스) | 켜짐 — 위협 이벤트를 /v1/telemetry로 전송 |
| 서명된 시간 (시계 조작 방지) | 꺼짐 — WebTime은 HTTPS HEAD만 사용 |
꺼짐 — Standard와 동일 | 켜짐 — 서명된 /v1/time 응답 사용 |
| 디바이스당 한도 | 무제한 (키 없음, 강제 없음) | 프로젝트 귀속 라이선스, 런타임 기기 한도 없음 | 기본 5대 / 조정 가능 |
| 소스 코드 접근 | 관리형 C#만 | 관리형 C#만 | 관리형 C#만 |
OZeroLicenseConfig 기반 서버 기능을 더합니다.
Plus / Pro 라이선스 키 등록
Standard 티어는 별도 설정이 필요 없습니다. Plus 또는 Pro는 ScriptableObject 에셋 1개를 추가하고 구매 후 받은 키를 붙여 넣습니다. Plus 키는 프로젝트별 Variant 검증과 포털 다운로드에 사용되고, Pro 키는 런타임 서버 활성화에도 사용됩니다.
1. 설정 에셋 생성
OZero Security Config & Dashboard (메뉴: OZero Security → Security → Config & Dashboard)를 열고 License & Server 섹션에서 Create OZeroLicenseConfig 버튼을 클릭합니다. 에셋이 자동으로 올바른 Resources/ 폴더에 생성되므로 수동으로 이동할 필요가 없습니다.
Assets → Create → OZero → License Config로 수동 생성도 가능합니다. 단, 에셋이 Resources/ 폴더 아래에 있고 이름이 정확히 OZeroLicenseConfig(대소문자 구분)인지 직접 확인해야 합니다.
2. Inspector 필드 채우기
| 필드 | 필수 여부 | 설명 |
|---|---|---|
| tier | 모든 티어 | 드롭다운에서 Standard, Plus, Pro 중 하나를 선택합니다. Standard는 공용 native 모듈을 사용합니다. Plus는 프로젝트에 바인딩된 Variant manifest가 필요합니다. Pro는 Plus를 포함하고 서버 기능을 활성화합니다. |
| licenseKey | Plus / Pro | 이메일로 받은 키입니다. Plus 키는 OZ-PLS-..., Pro 키는 OZ-PRO-... 형식을 사용합니다. 비워 두면 tier가 Plus 또는 Pro여도 SDK는 Standard/serverless처럼 동작합니다. |
| appIdentifier | 자동 전송 | SDK는 활성화 시 Unity의 Application.identifier를 appIdentifier로 전송합니다. 서버는 이 값을 라이선스에 등록된 번들 / 패키지 id와 비교하므로, Player Settings의 Identifier가 발급된 키와 일치해야 합니다. |
| serverBaseUrl | Plus / Pro | 기본 https://api.ozero.security — OZero 지원팀이 별도 엔드포인트를 안내한 경우가 아니면 그대로 두세요. 릴리스 빌드에서는 반드시 HTTPS여야 하며, trailing slash는 자동으로 제거됩니다. |
| serverPublicKeyHex | Plus / Pro | OZero License Server의 32바이트 digital signature 공개키 (64 hex 문자). SDK가 활성화 토큰의 서명을 검증하는 데 사용합니다 (Phase 1.5 / N-1 강화). 비워두면 서명 검증을 조용히 건너뜁니다 — 전환기 빌드에만 허용되며, 프로덕션은 항상 이 값을 채워야 합니다. 정확한 hex 문자열은 구매 이메일에서 확인하세요. |
| tokenTtlSeconds | 선택 | 오프라인 상태에서 캐시된 활성화 결과를 신뢰하는 시간(초). 기본 604800 (7일). 만료 후에도 SDK는 계속 동작하지만, 다음 성공 활성화까지 서버 전용 기능(텔레메트리 / signed time)이 꺼집니다. |
| offlineProPolicyMode | Plus / Pro | 디바이스가 오프라인일 때 서명된 Pro 포털 차단 정책을 어떻게 적용할지 정합니다. 권장값은 ApplyCachedBlockPolicies입니다. RequireFreshPolicy는 정책이 오래되면 거부해야 하는 온라인 전용 게임에만 사용하세요. IgnoreCachedBlockPolicies는 구버전 호환 모드이며 라이브 빌드에는 권장하지 않습니다. |
| activationTimeoutSeconds | 선택 | /v1/activate 응답을 기다리는 최대 시간(초). 이 시간을 초과하면 SDK는 캐시된 entitlement로 폴백합니다. 기본 6.0. 활성화 호출은 절대 씬 로드를 블로킹하지 않습니다 — 이 값은 백그라운드 Task가 타임아웃 로그를 찍기 전 대기 시간만 결정합니다. |
| enableLog | 선택 | true이면 라이선스 흐름 이벤트가 OZeroSecLog를 통해 기록됩니다 (캐시 hit / 활성화 성공 / 타임아웃 / digital signature 불일치). 기본 true; 출시 시 로그를 조용히 하려면 끄세요. |
3. 빌드 & 검증
코드 변경은 필요 없습니다. OZeroLicenseBootstrap이 [RuntimeInitializeOnLoadMethod(AfterAssembliesLoaded)]로 자동 연결되어 앱 시작 시 에셋을 읽습니다. 첫 실행 시 Player 로그에서 [OZeroLicense] activated; tier=pro caps=5 같은 줄을 확인하세요. Pro 설정인데 [OZeroLicense] Standard / serverless mode.가 보이면, 에셋이 Resources/ 폴더 아래에 있고 이름이 OZeroLicenseConfig (확장자/이름 변경 접미사 없음)인지 다시 확인하세요.
서버 활성화 흐름
활성화는 앱 시작 시 백그라운드에서 진행됩니다. SDK는 절대 씬 로드를 블로킹하지 않습니다 — 6초 타임아웃이 발생해도 캐시된 entitlement (또는 Standard 폴백)를 사용해 플레이어가 검은 화면에서 HTTP를 기다리는 일이 없습니다.
부팅 시퀀스
- SDK는 앱 시작 시 라이선스 런타임을 자동 초기화합니다.
- Standard와 Plus는 런타임 서버 호출 없이 계속 진행됩니다.
- Pro는 백그라운드에서 가벼운 활성화 요청을 보냅니다.
- 활성화에 성공하면 텔레메트리, signed time, attestation 같은 Pro 서버 기능이 사용 가능해집니다.
- 활성화 실패, 만료, 타임아웃이 발생해도 플레이어에게 오류를 표시하지 않고 Standard 모드로 계속 실행됩니다.
네트워크에 흐르는 데이터
활성화 요청은 작은 JSON POST입니다. 필수 필드는 licenseKey, deviceId, sdkVersion, platform입니다. appIdentifier, companyName, productName, webglOrigin은 Unity에서 비어 있지 않은 값을 제공할 때만 추가됩니다. 기기 식별자 소스는 OZeroLicenseRuntime.DeviceIdProvider로 오버라이드할 수 있습니다.
POST https://api.ozero.security/v1/activate
Content-Type: application/json
{
"licenseKey": "OZ-PRO-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX",
"deviceId": "<DeviceIdProvider result; default SystemInfo.deviceUniqueIdentifier>",
"sdkVersion": "<OZeroSdkVersion.ManagedVersion>",
"platform": "<android | ios | windows | macos | linux | webgl | ...>",
"appIdentifier": "<Application.identifier>",
"companyName": "<Application.companyName>",
"productName": "<Application.productName>",
"webglOrigin": "<WebGL origin only>"
}
서버는 JWT 형태의 signedToken (header.payload.signature, base64url, no padding), 결정된 tier, capabilities 배열, serverFeaturesEnabled 플래그, (선택) expiresAt unix-ms를 포함한 JSON으로 응답합니다. SDK는 serverPublicKeyHex로 OZerodigital signature.VerifySignature를 호출해 signedToken을 검증한 후에만 entitlement를 받아들입니다 — 그 외는 모두 fail-closed.
Graceful degradation
서버 점검, 네트워크 타임아웃, 라이선스 만료/정지/폐기, Bundle mismatch는 조작으로 보지 않습니다. 해당 상태는 Pro 전용 기능을 Standard/serverless 모드로 강등해 플레이어 세션을 유지합니다. 반대로 빌드 불일치, 차단된 빌드, 인젝션, 디버거처럼 조작이 확정된 상태는 설정된 위협 대응 정책을 따릅니다.
await OZeroLicenseRuntime.Initialize();를 호출하고 OZeroLicenseRuntime.Entitlement?.cachedAtMillis를 살펴보세요. 최근 몇 초 이내 값이면 서버가 방금 응답한 것이고, 더 오래된 값이면 캐시 기반으로 동작 중입니다.
오프라인 동작
OZero는 네트워크 상태와 무관하게 플레이어가 항상 게임을 실행할 수 있도록 설계되었습니다. 정확한 동작은 티어에 따라 다릅니다:
Standard — 항상 오프라인
서버는 절대 호출되지 않습니다. 10개 디텍터 모듈 모두 저하 없이 동작합니다. 라이선스 런타임은 IsServerless = true, HasEntitlement = false로 설정됩니다. OZeroTelemetryReporter는 attach되지만 모든 이벤트에서 no-op합니다.
Pro — TTL 내 오프라인 가능
지정 디바이스에서 /v1/activate가 처음 성공한 후, entitlement는 PlayerPrefs에 캐시됩니다 (기기 바인딩 키로 암호화 — 아래 캐시 보안 참조). 이후 실행에서는 네트워크 호출이 시작되기 전에 캐시가 동기적으로 읽히므로, 플레이어가 오프라인이어도 SDK는 어떤 티어이고 어떤 capability가 잠금 해제되었는지 알고 있습니다.
캐시된 entitlement는 활성화 시점에 기록된 cachedAtMillis로부터 tokenTtlSeconds 동안 신뢰됩니다. 기본 7일 (604800). 디바이스가 그 동안 계속 오프라인 상태로 TTL이 만료되면 캐시가 폐기되고, 다음 성공 활성화까지 SDK는 Standard 수준으로 강등됩니다. 디텍터는 계속 동작합니다 — 텔레메트리 전송과 signed-time 검증만 꺼집니다.
Pro — 서명된 오프라인 차단 정책
Graceful degradation은 라이선스, 네트워크, 서버 장애를 위한 동작입니다. 포털에서 명시적으로 차단한 빌드가 오프라인에서 허용된다는 뜻은 아닙니다. Pro 활성화가 성공하면 서버는 현재 포털 차단 정책을 활성화 토큰 안에 서명해 넣고, SDK는 이 정책을 entitlement와 별도로 캐시합니다.
권장값인 ApplyCachedBlockPolicies 모드에서는 서명된 정책이 유효한 동안 플레이어가 비행기 모드로 실행하더라도 차단된 manifest hash, SDK 버전, 앱 버전이 Build Integrity에서 계속 거부됩니다. 정책은 서명 토큰 TTL을 따르므로 포털에서 차단 규칙을 바꾼 뒤에는 한 번의 온라인 활성화가 필요합니다.
| 상태 | 디텍터 | 텔레메트리 | Signed Time |
|---|---|---|---|
| 온라인, 방금 활성화 | 10개 모두 On | On | On |
| 오프라인, 캐시 < TTL | 10개 모두 On | 큐 (다음 온라인 시 전송) | WebTime HTTPS HEAD로 폴백 |
| 오프라인, 캐시 > TTL | 10개 모두 On | Off (강등) | Off (강등) |
| 캐시 없음 (첫 실행 + 오프라인) | 10개 모두 On | 첫 온라인 실행까지 Off | 첫 온라인 실행까지 Off |
트러블슈팅
모든 라이선스 이벤트는 기본적으로 Unity의 Debug.Log로 라우팅되는 OZeroSecLog를 통해 기록됩니다 (OZeroSecurityConfig에서 설정 가능). Player 로그를 [OZeroLicense] 접두사로 필터링하여 부팅 시점의 결정을 확인하고, [OZeroTelemetry]로 리포터를 확인하세요. 일반적인 증상과 해결책:
| 증상 (로그 라인) | 예상 원인 | 확인할 사항 |
|---|---|---|
[OZeroLicense] Standard / serverless mode. |
에셋 누락 또는 tier=Standard 또는 빈 키 | OZeroLicenseConfig.asset이 Resources/ 폴더 아래에 있고 이름이 정확히 OZeroLicenseConfig이며 tier=Pro이고 비어 있지 않은 licenseKey를 가지고 있는지 확인하세요. |
/v1/activate failed: 401: invalid_key |
키 오타 또는 잘못된 티어 접두사 | 원본 구매 이메일에서 키를 다시 붙여넣으세요. 형식은 대소문자를 구분하며, 대시가 포함되어야 하고, 접두사는 OZ-PRO-여야 합니다. |
/v1/activate failed: 403: bundle_mismatch |
appIdentifier 불일치 |
서버 측 라이선스 기록은 키를 특정 번들 / 패키지 id에 바인딩합니다. Player Settings의 Application.identifier가 라이선스에 등록된 id와 일치하는지 확인하세요. |
activation token signature verification failed |
잘못된 serverPublicKeyHex 또는 MITM |
에셋의 hex 문자열을 구매 이메일의 표준 공개키와 비교하세요 — 64개의 hex 문자가 한 글자도 빠짐없이 일치해야 합니다. 정확한데도 계속 실패한다면 기업 프록시/MITM 어플라이언스를 의심하고 직접 모바일 데이터 연결을 시도해 보세요. |
/v1/activate timed out |
네트워크 지연 / 방화벽 | SDK는 자동으로 캐시로 폴백합니다 — 첫 실행 / 캐시 없음 시에만 문제입니다. 기기가 https://api.ozero.security/health에 도달할 수 있는지 확인하세요; 지연이 심한 지역에서 운영하는 경우 activationTimeoutSeconds를 늘리세요. |
cached entitlement past TTL; clearing. |
플레이어 오프라인 > TTL | 정상입니다. 디텍터는 계속 작동합니다; 다음 온라인 실행까지 텔레메트리 / signed-time은 꺼집니다. 사용자가 7일 이상 오프라인으로 플레이하는 경우가 많다면 tokenTtlSeconds를 늘리세요. |
cache hit; tier=pro on a different machine than expected |
디바이스 지문 변경 | SystemInfo.deviceUniqueIdentifier가 변경되면(일부 Android ROM / OS 초기화), v2 캐시를 읽을 수 없게 되고 SDK는 단순히 다시 활성화합니다. 사용자 조치는 필요 없습니다 — 다음 온라인 실행 시 복구됩니다. |
enableLog = true 상태로 빌드를 배포하지 마세요. 라이선스 흐름 로그는 티어/capability 상태를 노출하며 이는 괜찮지만, 디텍터 내부 로직이 조용히 유지되도록 전역 OZeroSecurityConfig.enableLog도 쌍으로 비활성화하세요.
프로젝트 설정
개별 보안 모듈을 조정하기 전에, 네이티브 플러그인과 스토어 빌드, 플랫폼 검증에 영향을 주는 Unity Player Settings를 먼저 확인하세요.
최소 빌드 타겟
| 플랫폼 | 최소 타겟 | 설명 |
|---|---|---|
| iOS | 12.0+ |
iOS 빌드에서는 Project Settings > Player > iOS > Target minimum iOS Version을 12.0 이상으로 설정하세요. OZero는 이 PlayerSettings 값을 강제로 덮어쓰지 않으므로, 앱의 지원 정책에 맞춰 유지하면 됩니다. |
| Android | API 21+ |
Android 빌드는 Project Settings > Player > Android > Minimum API Level을 Android 5.0 Lollipop (API level 21) 이상으로 설정하세요. 스토어 릴리스 빌드는 IL2CPP와 ARM64 사용을 권장합니다. |
Android ProGuard / R8 설정
OZero Security는 별도의 Java SDK 패키지를 요구하지 않습니다. 다만 Android 릴리스 빌드에서 Minify, ProGuard, R8을 활성화했다면 Unity Java 브리지와 프로젝트에서 사용하는 커스텀 Android 브리지 클래스를 보존해야 합니다. 그래야 패키지 정보, 설치 출처, APK 서명 인증서 검사, 부트 에셋 로딩에 필요한 JNI 호출이 난독화 이후에도 안정적으로 동작합니다.
proguard-user.txt에 추가하세요. Minify를 사용하지 않는 경우 별도 ProGuard 설정은 필요하지 않습니다.
# OZero Security - Unity Android ProGuard/R8 keep rules
-keep class com.unity3d.player.UnityPlayer { *; }
-keep class com.unity3d.player.UnityPlayerActivity { *; }
-keep class com.unity3d.player.UnityPlayerGameActivity { *; }
-keep class com.unity3d.player.UnityPlayerForActivityOrService { *; }
-keepattributes *Annotation*,InnerClasses,EnclosingMethod,Signature
# If your game adds custom Java/Kotlin bridge classes that OZero or your code
# calls through AndroidJavaClass / AndroidJavaObject, keep those classes too.
# Replace the package below with your own bridge package.
# -keep class com.yourcompany.yourgame.bridge.** { *; }
libOZeroSecurity.so를 이름 변경, 삭제, 재패키징하지 마세요. 플러그인은Assets/OZeroSDK/Plugins/Android/arm64-v8a아래에 유지하고, 32비트 빌드를 배포한다면armeabi-v7a도 함께 유지하세요.- Build Integrity > Check Platform Native와 Android SHA Keys를 사용하는 경우, Minify/R8 적용 후 최종 서명된 APK 또는 AAB로 반드시 테스트하세요. 디버그 키스토어 지문과 릴리스 키스토어 지문은 서로 다릅니다.
- Minify를 켠 뒤에만 Android 로그에서 JNI 조회 실패, 설치 출처 감지 실패, APK 서명 검사 결과 비어 있음이 보인다면 먼저 커스텀 브리지 keep 규칙을 확인하세요.
- 스토어 배포용 빌드는 IL2CPP, 릴리스 서명,
Android Sha Keys에 등록된 예상 Android SHA-256 지문, 그리고 최소 한 대 이상의 실제 기기 클린 실행 테스트를 기준으로 검증하세요.
5
OZeroSecurityConfig 설정
OZeroSecurityConfig ScriptableObject 에셋은 패키지 임포트 시 포함됩니다. Project 창에서 선택하여 모든 보안 모듈 설정을 확인하고 조정하세요. 런타임에서는 OZeroSecurityConfig.Instance로 접근합니다.
일반 설정
모든 모듈에 전역으로 적용되는 최상위 필드입니다.
| 필드 | Type | 기본값 | 설명 |
|---|---|---|---|
| developerSecret | string | — | 암호화 키 도출에 사용되는 고유 패스프레이즈입니다. 게임에 고유한 값으로 설정하고 비밀로 유지하세요. 출시 후 변경 시 기존 세이브 데이터를 읽을 수 없게 됩니다. |
| enableLog | bool | false | Enable Debug Logs를 켜면 SDK 초기화, 설정 로딩, 라이선스/텔레메트리 흐름, 탐지 이벤트 같은 내부 상태를 Unity Console과 Player 로그에 출력합니다. 개발/QA 단계에서 원인 파악에 유용하지만 탐지 흐름과 모듈 상태가 노출될 수 있으므로 운영 빌드에서는 끄는 것을 권장합니다. |
| enableFailureDiagnostics | bool | false | Enable Failure Diagnostics를 켜면 보안 위반이 발생했을 때 로컬 진단 파일을 Application.persistentDataPath에 저장합니다. 파일에는 모듈명, 해시, 기기 상태, 설치 패키지명, 런타임 설정 일부가 포함될 수 있으므로 QA나 고객 지원 세션에서만 켜는 것을 권장합니다.플랫폼별 기본 저장 위치:
|
Developer Secret을 설정하세요. 기본값을 사용하지 말고 출시 후에는 변경하지 마세요.
응답 설정
보안 모듈이 탐지 이벤트를 발생시켰을 때의 동작을 제어합니다.
| 필드 | Type | 기본값 | 설명 |
|---|---|---|---|
| forceQuitOnDetection | bool | true | forceQuitOnDetection은 확정 위협이 감지되었을 때 앱을 자동 종료할지 결정합니다. Standard와 Strict 프리셋에서는 기본적으로 켜져 있으며, 관리 코드 콜백이 패치되거나 누락되어도 내부 fallback receiver가 대응 정책을 강제할 수 있습니다. 탐지 상황을 종료 없이 관찰해야 하는 통제된 QA 흐름에서만 끄세요. |
빌드 무결성 검증기 설정
수정된 게임 파일, 디버거 연결, 비정상적인 실행 환경을 감지하는 빌드 무결성 검사기를 구성합니다.
| 필드 | Type | 기본값 | 설명 |
|---|---|---|---|
| Activate Build Integrity | checkbox | On | Build Integrity 모듈을 활성화합니다. |
| validateOnStartup | bool | true | 게임 실행 즉시 빌드 무결성 검사를 실행합니다. |
| validateInEditor | bool | false | Unity 에디터에서도 검증을 실행합니다(테스트용으로 유용하지만 일반 개발 중에는 끄는 것을 권장). |
| enablePeriodicValidation | bool | true | 게임 실행 중에도 무결성 검증을 반복합니다. 시작 시 1회 검증만으로 충분한 빌드가 아니라면 켜두는 것을 권장합니다. |
| periodicCheckInterval | float | 300 s | 게임 실행 중 주기적인 보안 검사가 실행되는 간격(초)입니다. |
| periodicCheckJitterPercent | float | 35% | 검증 주기에 무작위 지터를 더해 공격자가 정확한 검증 타이밍을 예측하기 어렵게 합니다. 예를 들어 300초에 35% 지터면 대략 195~405초 사이에서 실행됩니다. |
| timingAnomalyConsecutiveRequired | int | 7 | 타이밍 기반 이상 신호가 몇 번 누적되어야 디버거 타이밍 드리프트로 판단할지 정합니다. 강한 디버거 신호는 즉시 실패할 수 있습니다. |
| timingAnomalyWindowSeconds | float | 900 s | 타이밍 기반 이상 신호를 누적하는 시간 창입니다. 길수록 관대하고, Strict는 더 짧은 창을 사용합니다. |
| timingAnomalyFrameHitchSuppressionSeconds | float | 20 s | 씬 로딩, 셰이더 컴파일, GC, OS 스케줄링 지연처럼 큰 프레임 히치가 발생한 뒤 일정 시간 동안 타이밍 기반 디버거 검사를 완화합니다. |
| checkAssemblyHash | bool | true | 빌드 무결성 검사의 일환으로 컴파일된 DLL 어셈블리의 해시를 검증합니다. |
| checkDebugger | bool | true | 런타임에 디버거가 프로세스에 연결되어 있는지 탐지합니다. |
| failOnDebugBuild | bool | false | Unity 디버그 빌드를 위반으로 간주합니다(릴리스 빌드에서 권장). |
| checkPlatformNative | bool | true | 에뮬레이터나 비정상적인 환경에서 실행 중인 징후를 확인합니다. |
| failIfManifestMissing | bool | true | OZeroIntegrityManifest.ozero 파일이 없을 경우 위반으로 처리합니다. 공격자가 매니페스트를 삭제하여 무결성 검사를 우회하지 못하도록 활성화 상태를 유지하세요. |
| failIfAssemblyHashBlobMissing | bool | true | 매니페스트에 번들된 어셈블리 해시 블롭이 없을 경우 위반으로 처리합니다. |
| requireCodeSignature (Windows) | bool | false | 메인 실행 파일이 코드 서명되어 있어야 합니다. Windows 전용. |
| blockVirtualMachine (Windows) | bool | false | 가상 머신 내부에서 게임 실행을 차단합니다. Windows 전용. |
| blockHyperV (Windows) | bool | false | Hyper-V VMBus 신호를 차단합니다. WSL2, Docker Desktop, Windows Sandbox 사용자도 막힐 수 있으므로 통제된 환경에서만 신중히 사용하세요. |
| blockNetworkProxies (Windows) | bool | false | Wireshark, Fiddler, Charles, HTTPDebugger 같은 네트워크 프록시/패킷 검사 도구를 감지합니다. 경쟁형 빌드에서 사용하되 오탐 가능성을 테스트하세요. |
| blockReverseEngineeringTools (Windows) | bool | false | ILSpy, dotPeek, Ghidra, WinDbg, IDA Pro 같은 역공학 도구 실행 신호를 감지합니다. |
| blockSystemMonitorTools (Windows) | bool | false | Process Explorer, Process Monitor, Process Hacker 같은 시스템 모니터링 도구를 감지합니다. 파워 유저 오탐 가능성을 고려하세요. |
| il2cppHashGameAssembly | bool | true | Windows IL2CPP 빌드의 GameAssembly.dll을 해시 검증합니다. IL2CPP 파일 보호의 최소 권장 항목입니다. |
| il2cppHashGlobalGameManagers | bool | true | IL2CPP 매니페스트 검증 범위에 globalgamemanagers를 추가합니다. |
| il2cppHashSharedAssets | bool | true | IL2CPP 검증 범위에 sharedassets* 파일을 추가합니다. |
| il2cppHashSceneFiles | bool | true | IL2CPP 검증 범위에 level* 같은 Unity 씬 파일을 추가합니다. |
| il2cppHashResourcesAssets | bool | false | IL2CPP 검증 범위에 resources.assets를 추가합니다. Strict 모드에 유용하지만 패치 흐름을 먼저 테스트하세요. |
| il2cppAdditionalWatchedFiles | List<string> | — | 프로젝트가 별도 네이티브 페이로드를 포함하는 경우, 추가로 감시할 Windows IL2CPP 출력 파일을 지정합니다. |
| blockEmulator (Android) | bool | true | Android 전용입니다. 에뮬레이터 또는 미지원 런타임 신호를 무결성 위반으로 취급합니다. QA/에뮬레이터 테스트 중에는 완화하고, 프로덕션 빌드에서는 더 엄격한 정책을 권장합니다. |
| blockSystemRwMount (Android) | bool | true | Android 전용입니다. 시스템 파티션이 쓰기 가능하거나 root 계열 mount 상태가 보이면 무결성 위반으로 처리합니다. |
| androidShaKeys | List<string> | — | 기대되는 APK 서명 인증서의 SHA-256 지문 목록. 설치된 APK가 이 키 중 하나로 서명되지 않았다면 검증에 실패합니다(Android 전용). |
| expectedBundleIds (iOS) | List<string> | — | 허용할 iOS Bundle ID 목록입니다. 비워두면 Bundle ID 검사를 건너뜁니다. |
| excludedAssemblies | List<string> | — | 해시 검증에서 제외할 어셈블리 이름 목록(.dll 확장자 제외). 런타임에 변경되는 어셈블리(예: 생성된 코드)에 사용하세요. |
| checkIntegrityWithServer (Pro) | bool | false | Pro 서버 Attestation을 켭니다. 클라이언트가 nonce를 요청하고 무결성 증거를 제출한 뒤, 게임 서버 또는 OZero 대행 검증(Managed Verification)이 검증할 수 있는 서명된 OZA 토큰을 받습니다. 최신 서버에서는 nonce가 manifest hash, platform, SDK version, 앱 식별 정보와 함께 묶여 재사용/증거 바꿔치기를 줄입니다. |
| enableManagedVerification (Pro) | bool | false | 자체 게임 서버가 없는 팀을 위한 위임 검증 옵션입니다. 발급된 OZA 토큰을 OZero 서버가 다시 검증하고 allow/warn/block verdict와 짧은 managed session을 반환합니다. SDK는 managed session이 만료되기 전에 자동으로 재검증을 시도합니다. checkIntegrityWithServer가 켜져 있어야 합니다. |
| requireManagedVerification (Pro) | bool | false | 켜면 Managed Verification 네트워크 실패도 검증 실패로 처리합니다. 일반적으로는 꺼두고 기본(Standard) 보호로 자동 전환되게 두는 것을 권장합니다. |
| attestRefreshLeadSeconds (Pro) | float | 300 s | OZA 토큰 만료 전에 몇 초 먼저 갱신을 시도할지 정합니다. 0이면 만료 전 선갱신을 사용하지 않습니다. |
| attestRefreshCooldownSeconds (Pro) | float | 30 s | Attestation 갱신 시도 사이의 최소 대기 시간입니다. |
| manifestSigningPublicKey | string | — | requireManifestSignature가 활성화된 경우, 매니페스트 서명 검증에 사용되는 RSA 공개키(PEM). |
| requireManifestSignature | bool | false | 무결성 매니페스트 자체가 manifestSigningPublicKey로 서명되었는지 검증합니다. |
| manifestSigningPrivateKeyPath | string | OZeroSigningKeys | 빌드 시 manifest 서명에 사용할 private key PEM 경로입니다. Editor 전용이며 플레이어 빌드에는 포함되지 않습니다. |
Events
| Event | Description |
|---|---|
| OnValidationPassed | 로컬 무결성 검사가 위반 없이 완료되었을 때 발생합니다. Pro 서버 검증(attestation)은 아직 진행 중일 수 있습니다. |
| OnAttestationPassed | Pro OZA 검증(attestation) 토큰이 실제 발급된 뒤에만 발생합니다. 게임 서버 로그인, PvP, 랭킹, 재화 흐름은 이 이벤트 또는 AttestationToken.IsValid(nowMillis)로 검사해 통과한 경우에만 진행하세요. |
| OnValidationFailed | 무결성 검사가 위반을 감지했을 때 발생합니다. 글로벌 onHackDetected 이벤트도 ModulationType.BuildIntegrity와 함께 트리거됩니다. |
RSA 서명 키 생성 (빌드 무결성)
빌드 무결성을 활성화하고 Require Manifest Signature를 켠 경우, OZero는 public-key signature 비대칭 서명을 사용하여 무결성 매니페스트의 위변조 여부를 검증합니다. 릴리스 빌드를 만들기 전에 Unity 에디터에서 키 쌍을 반드시 생성해야 합니다.
빌드 시점에 OZero는 개인 키로 어셈블리 매니페스트를 서명하고 그 결과 서명을 빌드에 포함시킵니다. 런타임에는 OZeroSecurityConfig에 저장된 공개 키로 서명을 검증합니다. 서명이 일치하지 않으면 — 즉 매니페스트 또는 바이너리가 변조된 경우 — 게임은 이를 변조로 간주하고 Response Settings에 따라 대응합니다.
키 쌍 생성하기
- Unity 에디터에서 Project 창의
OZeroSecurityConfig를 선택합니다. - 인스펙터에서 Build Integrity 섹션을 펼칩니다.
- Require Manifest Signature 체크박스를 활성화합니다.
- Generate Key Pair 버튼을 클릭합니다.
- OZero가 public-key signature 키 쌍을 생성합니다. 공개 키는
OZeroSecurityConfig에 직접 기록됩니다(Assets에 저장). 개인 키는 다음 경로에 저장됩니다:[ProjectRoot]/OZeroSigningKeys/manifest_private_key.pem - 개인 키 위치를 보여주는 확인 대화상자가 나타납니다. OK를 클릭하여 닫습니다.
- 개인 키 파일은 Unity가 빌드에 포함시키지 않도록
Assets/폴더 외부에 저장됩니다. 절대로Assets/안으로 이동하지 마세요. OZeroSigningKeys/를 즉시.gitignore에 추가하세요. 개인 키를 버전 관리에 커밋하는 것은 심각한 보안 위험입니다.- 개인 키를 안전한 오프라인 장소(암호화된 USB 드라이브, 패스워드 매니저 등)에 백업하세요. 이 파일을 가진 사람은 누구든 유효한 매니페스트를 위조할 수 있습니다.
- 개인 키를 분실한 경우 새 키 쌍을 생성해야 합니다. 새 공개 키는 다음 빌드에 자동으로 포함됩니다. 기존에 출시된 빌드는 모두 새 공개 키로 매니페스트 검증에 실패하므로 업데이트를 배포해야 합니다.
사용자 지정 개인 키 경로 (CI/CD & 팀 환경)
인스펙터의 Manifest Signing — Editor Only 아래에 있는 Manifest Signing Private Key Path 필드에서 개인 키의 대체 위치를 지정할 수 있습니다. 다음 두 가지 상황에서 유용합니다:
- CI/CD 파이프라인 — 개인 키를 CI 시크릿으로 저장하고 빌드 시에 경로를 주입하면 빌드 머신이 키를 영구적으로 디스크에 보관하지 않아도 됩니다.
- 팀 환경 — 키를 공유 보안 서버나 시크릿 매니저에 보관하고 필드를 마운트 경로로 지정합니다. 릴리스 빌드를 실행하는 팀원만 접근 권한을 가지면 됩니다.
기존 키 쌍 유효성 검사
디스크의 개인 키와 OZeroSecurityConfig에 저장된 공개 키가 일치하는지 확인하려면 Validate Key Pair 버튼을 클릭하세요. OZero가 개인 키로 소규모 테스트 페이로드에 서명하고 공개 키로 검증합니다. 일치하면 성공 메시지가 표시되고, 일치하지 않으면 새 쌍을 생성해야 합니다.
- 개인 키를 분실하거나 유출된 경우.
- Validate Key Pair에서 불일치를 보고하는 경우(키가 동기화되지 않음).
- 예정된 보안 정책의 일환으로 의도적으로 키를 교체하는 경우.
재생성 후에는 새 공개 키가 다음 빌드에 자동으로 포함됩니다. 기존에 출시된 빌드는 새 공개 키로 매니페스트 검증에 실패하므로 업데이트를 배포해야 합니다.
듀얼 핑거프린트 무중단 키 회전
Generate Key Pair 버튼은 공개 키를 두 곳에 동시에 저장합니다 — OZeroSecurityConfig (Assets 안의 ScriptableObject) 와 Assets/OZeroSDK/Scripts/Security/BuildIntegirity/OZeroManifestTrustAnchor.cs 안의 SHA-256 fingerprint 상수. 런타임에 SDK 는 RSA 서명 검증 전에 먼저 에셋의 공개 키가 어셈블리에 박힌 fingerprint 와 일치하는지 확인합니다. 일치하지 않으면 매니페스트 자체를 거부합니다. 이 핀닝이 "공격자가 에셋과 .sig 를 동시에 자기 키로 갈아끼우는 리패킹 공격"을 차단하는 핵심입니다.
OZeroManifestTrustAnchor.cs 안에는 두 const 슬롯이 있습니다 — ExpectedPublicKeyFingerprintHex (현재 프로덕션 fingerprint) 와 PreviousPublicKeyFingerprintHex (회전 시에만 채우는 직전 fingerprint, 평소엔 비어 있음). Matches() 검증 함수는 실제 SPKI 해시가 둘 중 하나와 일치하면 true 를 반환하며, constant-time XOR 비교로 타이밍 공격을 차단합니다. 이 듀얼 슬롯 모델이 홈페이지에서 광고하는 "무중단 키 회전" 의 실체입니다 — 새 빌드가 새 키 (Expected) 와 옛 키 (Previous) 를 동시에 신뢰하므로 grace 기간 동안 옛 빌드는 옛 매니페스트를 그대로 검증할 수 있고, 그 사이 새 릴리스를 푸시할 수 있어요.
회전 절차 (수동이지만 5분이면 충분)
- 현재 fingerprint 메모.
Assets/OZeroSDK/Scripts/Security/BuildIntegirity/OZeroManifestTrustAnchor.cs를 열어ExpectedPublicKeyFingerprintHex의 현재 값을 클립보드 (또는 임시 메모) 에 복사합니다. - Previous 슬롯에 옮겨 붙이기. 같은 파일에서
PreviousPublicKeyFingerprintHex(보통"") 자리에 1단계에서 복사한 값을 붙여 넣고 저장합니다. - 새 키 쌍 생성.
OZeroSecurityConfig인스펙터에서 Generate Key Pair 클릭. "기존 매니페스트가 무효화됩니다" 경고에 Regenerate 선택. 새 private key 가 PEM 에 쓰이고, 새 public key 가 에셋에 갱신되고, 새 fingerprint 가ExpectedPublicKeyFingerprintHex에 자동 주입됩니다. 2단계에서 채워둔PreviousPublicKeyFingerprintHex는 그대로 유지됩니다. - 새 SDK 릴리스 빌드 + 출시. 평소 패치 / 스토어 채널로 푸시. 이 빌드의 새 매니페스트는 새 private key 로 서명됩니다.
- Fleet 롤오버 대기. Grace 기간 동안 새 빌드는 두 kid 모두 신뢰, 옛 빌드는 (옛 fingerprint 만 Expected 로 박혀 있는 채로) 자신의 옛 매니페스트를 그대로 검증. 어느 쪽도 깨지지 않음. 라이브 게임 기준 보통 1 ~ 4 주.
- Previous 슬롯 비우기. 텔레메트리에서 옛 빌드 점유율 0% 확인 후
PreviousPublicKeyFingerprintHex = ""로 되돌리고 한 번 더 SDK 릴리스. 회전 완료.
옛 빌드 바이너리는 옛 키로 서명된 옛 매니페스트와, Expected 슬롯에 박힌 옛 fingerprint 를 이미 함께 들고 있습니다. 새 키에 대해 아무것도 몰라도 자기 매니페스트를 자기 키로 검증하므로 그대로 동작합니다. 새 빌드의 Previous 슬롯이 필요한 이유는 단 하나 — 플레이어가 회전 도중 강제 재설치하거나 옛 버전으로 롤백했을 때, 캐시된 옛 매니페스트가 새 빌드의 트러스트 앵커로도 여전히 검증되어야 하기 때문입니다. Fleet 에서 옛 빌드가 사라지면 다음 릴리스에서 Previous 를 비우면 됩니다.
트러블슈팅
아래 5가지 증상은 모두 같은 방어 스택에서 비롯됩니다 — 빌드 타임 PreBuild 검증 (OZeroBuildPreflightValidator), 부트 타임 앵커 가드 (OZeroManifestTrustAnchor.ValidateConfigurationOrFail), 런타임 검증 (OZeroBuildIntegrityValidator.VerifyManifestSignature). 모든 케이스의 해결책은 세 산출물 (private key PEM, OZeroSecurityConfig 의 public key, OZeroManifestTrustAnchor 의 fingerprint) 을 다시 동기화하는 것입니다.
ExpectedPublicKeyFingerprintHex 가 빈 문자열입니다. Editor 와 Development 빌드는 경고만 띄우고 통과하지만 릴리스 빌드는 신뢰할 수 없는 매니페스트가 받아들여지기 전에 즉시 종료합니다. 해결: Editor 에서 Generate Key Pair 를 한 번 실행하면 fingerprint 가 const 에 자동 주입됩니다. const 가 채워져 있는데도 종료된다면 어셈블리 해시 blob (oz_ahash.bin) 이 오래되었을 수 있으니 클린 상태에서 재빌드하세요.
PreBuild 검증기 (callbackOrder 51) 가 OZeroSecurityConfig.Integrity.ManifestSigningPublicKey 비어 있음을 잡았습니다. 해결: 인스펙터에서 OZeroSecurityConfig 열기 → Build Integrity 펼치기 → Generate Key Pair 클릭 후 재빌드. 참고로 이 검증은 Android / iOS (OS 레벨 서명 사용) 와 IL2CPP Standalone (per-DLL 파일 자체가 없음) 에서는 자동 skip 되므로, 키 누락은 Mono Standalone 타겟만 막습니다.
OZeroSecurityConfig 의 public key 문자열을 해싱한 값이 ExpectedPublicKeyFingerprintHex 도 PreviousPublicKeyFingerprintHex 도 일치하지 않습니다. 정상 빌드라면 수동 편집으로 두 산출물이 어긋난 경우입니다. 해결: Editor 에서 Tools → OZero → Validate Manifest Signing Keys 실행 — 키 쌍이 일관되면 도구가 const 를 자동으로 다시 써 줍니다. 불일치를 보고하면 Generate Key Pair 로 새 쌍을 발급. 변조된 바이너리에서 이 메시지가 뜨는 것은 의도된 fail-closed 신호 — APK / .exe 가 리패킹됐는지 점검해야 합니다.
회전 절차의 2단계를 빠뜨렸습니다. 새 SDK 릴리스가 PreviousPublicKeyFingerprintHex = "" 인 상태로 출시되어, 옛 빌드를 캐시한 채 강제 재시작한 플레이어 (또는 강제 재설치로 옛 매니페스트가 새로 fetch 된 플레이어) 가 자기 트러스트 앵커로 매니페스트를 검증하지 못합니다. 해결: PreviousPublicKeyFingerprintHex 에 옛 fingerprint 를 채운 핫픽스 SDK 릴리스를 즉시 배포 — 이후 매니페스트 fetch 가 두 kid 중 하나로 검증됩니다.
CI 머신에는 .gitignore 로 정상 차단된 OZeroSigningKeys/manifest_private_key.pem 이 없습니다. 해결: PEM 을 CI secret (GitHub Actions Secret, GitLab Variable, Jenkins Credentials 등) 에 보관하고, Unity 빌드 단계 직전에 파이프라인이 OZeroSigningKeys/manifest_private_key.pem 으로 복원하도록 구성하세요. 또는 OZeroSecurityConfig 의 Manifest Signing Private Key Path 를 CI 러너가 mount 가능한 경로 (예: secrets manager mount) 로 지정. 모든 CI 러너가 같은 PEM 을 써야 합니다 — public key 는 같아도 PEM 이 다르면 서명이 달라집니다.
설치 출처 설정
게임이 승인된 스토어 또는 경로에서 설치된 경우에만 실행되도록 제한합니다. 사이드로드 또는 재패키징된 APK 방지에 유용합니다.
| 필드 | Type | 기본값 | 설명 |
|---|---|---|---|
| Activate Install Source | bool | true | Install Source 모듈을 활성화합니다. |
| allowGooglePlayStore | bool | true | Google Play 스토어를 통한 설치를 허용합니다. |
| allowSamsungGalaxyStore | bool | false | Samsung Galaxy Store를 통한 설치를 허용합니다. |
| allowAmazonAppstore | bool | false | Amazon Appstore를 통한 설치를 허용합니다. |
| allowHuaweiAppGallery | bool | false | Huawei AppGallery를 통한 설치를 허용합니다. |
| allowOneStore | bool | false | 원스토어(한국)를 통한 설치를 허용합니다. |
| allowXiaomiGetApps | bool | false | Xiaomi GetApps를 통한 설치를 허용합니다. |
| allowOppoAppMarket | bool | false | OPPO App Market를 통한 설치를 허용합니다. |
| allowVivoAppStore | bool | false | Vivo App Store를 통한 설치를 허용합니다. |
| allowADB | bool | false | ADB(Android Debug Bridge)를 통한 설치를 허용합니다. 내부 테스트 용도에서만 활성화하세요. |
| allowDetectionFailed | bool | false | Android installer package 조회나 JNI 호출이 실패했을 때 게임 실행을 허용합니다. 릴리스 빌드에서는 꺼두는 것을 권장합니다. |
| allowUnknownSources | bool | false | 기본 목록이나 커스텀 허용 목록에 없는 설치 출처를 허용합니다. 지역 스토어 대응이 필요할 때만 테스트 후 사용하세요. |
| enableServerSync (Pro) | bool | false | 감지된 installer package를 OZero 서버로 보내 allowlist 검증과 감사 로그를 수행합니다. Pro 전용입니다. |
| customAuthorizedPackages | List<string> | — | 추가로 허용할 설치자 패키지 이름 (예: com.yourcompany.launcher). |
| reportViolationToCallback | bool | true | 허용되지 않은 설치 출처가 감지되면 서버로 리포트를 전송합니다. |
| logRawInstallerPackage | bool | true | 원본 설치자 패키지 이름을 콘솔에 로그로 출력합니다. 커스텀 스토어의 올바른 패키지 이름을 식별할 때 개발 단계에서 유용합니다. |
Steam Anti-Piracy 설정
Steam으로 배포되는 PC 빌드에서 Steam 실행 경로, App ID, 권한 상태, 릴리스 설정을 확인합니다. Standard는 로컬 검증이며, Steam 서버를 통한 더 강한 소유권 검증은 Pro의 서버 Steam Attestation에서 제공합니다.
| 필드 | Type | 기본값 | 설명 |
|---|---|---|---|
| Activate Steam Anti-Piracy | checkbox | Off | Steam Anti-Piracy 활성화 체크박스입니다. Steam App ID와 배포 방식이 프로젝트마다 다르므로 기본값은 Off입니다. 먼저 관찰/QA 모드에서 상태를 확인한 뒤 출시 빌드 정책을 강화하세요. |
| expectedSteamAppId | int | 0 | 프로젝트의 Steam App ID입니다. 개발 확인용 AppID 480을 사용했다면 출시 전 반드시 실제 App ID로 바꾸세요. |
| requireSteamLaunch | bool | true | 게임이 실행 파일 직접 실행이 아니라 Steam을 통해 시작되었는지 확인합니다. 개발 중 직접 실행 테스트와 릴리스 정책을 구분하세요. |
| requireSteamApiInit | bool | true | Steam API 초기화 성공 여부를 확인합니다. 로컬 개발 실행에서는 Steamworks 설정에 따라 실패할 수 있으므로, 최종 판정은 실제 Steam 배포 경로에서 확인하세요. |
| requireSubscribedCurrentApp | bool | true | 현재 Steam 계정이 앱을 소유하거나 사용 권한을 가지고 있는지 확인합니다. 이 항목은 로컬 검증이며, 서버 측 소유권 검증은 Pro에서 수행합니다. |
| requiredDlcAppIds | List<int> | — | 보호할 DLC 흐름에서 소유가 필요한 DLC App ID 목록입니다. |
| blockSteamAppIdTxtInRelease | bool | true | 릴리스 빌드에 개발용 steam_appid.txt가 남아 있으면 실패 처리합니다. 이 파일은 로컬 개발 확인용이며 고객에게 배포하지 마세요. |
| validateSteamApiDllHash | bool | false | Steam API DLL 해시를 알려진 SHA-256 값과 비교해 검증합니다. |
| allowFamilySharing / allowFreeWeekend / allowTimedTrial | bool | true | Steam 가족 공유, 무료 주말, 시간 제한 체험 권한 상태를 허용할지 제어합니다. |
| enableServerSteamAttestation (Pro) | bool | false | Pro 전용입니다. Steam 권한 증거를 OZero 서버로 제출해 클라이언트 로컬 결과보다 강한 서버 측 소유권 검증을 수행합니다. |
디바이스 바인딩 설정
하드웨어 지문을 사용해 게임 계정을 최초 기기에 바인딩합니다. 다른 하드웨어로의 계정 이전을 감지합니다.
상용 운영 목적
기기 바인딩은 Pro 운영 레이어로 가장 유용합니다. 라이선스를 서버에 등록된 하드웨어 지문과 연결해 위험 기기를 격리하고, 단순 라이선스 공유 비용을 높이며, 정상적인 기기 변경을 전체 라이선스 차단 없이 지원할 수 있습니다.
반복 위험 기기 차단
SpeedHack, Injection, Install Source, Build Integrity 같은 보안 이벤트 증거를 확인한 뒤 사용하세요. 차단된 기기는 다음 활성화, 등록, 검증(verify), 정책(policy) 체크에서 DEVICE_BLOCKED를 받습니다.
정상 유저 리셋 지원
휴대폰 교체, OS 재설치, 하드웨어 교체, 정상 환경 변경 후 지문 불일치가 발생한 경우 Reset Token을 사용합니다. 토큰은 5분 유효, 1회용이며 정확한 라이선스와 기기에 묶입니다.
기기 슬롯 통제
Pro 라이선스에서는 maxDevices로 등록 가능한 활성 서버 지문 수를 제한합니다. 차단된 지문은 신뢰 가능한 재사용 슬롯이 아니므로, 리셋 또는 삭제는 고객지원 검증 후 사용하세요.
Reset Token 고객지원 흐름
- 일반 고객지원 절차에 따라 플레이어 계정과 리셋 사유를 확인합니다.
- Customer Portal → Device Binding에서 대상 기기를 찾고 Reset Token을 발급합니다.
- 토큰은 인증된 고객지원 채널로만 전달합니다. 장기간 남는 공개 채팅이나 스크린샷에 보관하지 마세요.
- 게임 또는 고객지원 UI는 토큰을
OZeroDeviceBindingDetector.Instance?.ClearStoredFingerprint(token.Trim())에 전달해야 합니다. - SDK가 토큰을 소비한 뒤 앱을 재시작하거나 보호 초기화 흐름에 다시 진입하면 현재 하드웨어 지문이 다시 등록됩니다.
| 필드 | Type | 기본값 | 설명 |
|---|---|---|---|
| Activate Device Binding | bool | true | Device Binding 모듈을 활성화합니다. |
| hardwareChangeTolerance | int (0–3) | 1 | 위반을 트리거하기 전에 허용되는 하드웨어 구성요소 변경 횟수. 0 = 엄격(변경 불가), 3 = 관대(주요 하드웨어 교체). 1은 사소한 OS 또는 펌웨어 업데이트를 허용하는 수준입니다. |
| storageKey | string | "ozero_dfp" | 암호화된 디바이스 지문이 저장되는 PlayerPrefs 키. 게임 내 기존 키와 충돌하는 경우에만 변경하세요. |
| enableServerSync | bool | false | 서버 사이드 검증을 위해 디바이스 지문을 백엔드 서버와 동기화합니다. |
| maxDevices (Pro) | int | 0 | 의도한 서버사이드 기기 한도를 문서화하는 값입니다. 실제 한도는 서버 라이선스 기록에서 강제되므로 이 로컬 값만 바꿔도 운영 한도가 올라가지는 않습니다. |
스피드핵 감지기 설정
Unity Time.realtimeSinceStartup를 네이티브 플랫폼 타이머 및 선택적으로 신뢰할 수 있는 웹 시간 소스와 비교하여 타임 스케일 조작(스피드핵)을 감지합니다.
| 필드 | Type | 기본값 | 설명 |
|---|---|---|---|
| Activate Speed & Time Hack | bool | true | Speed Hack Detector 모듈을 활성화합니다. |
| autoStart | bool | true | 게임 실행 시 자동으로 감지를 시작합니다. 비활성화할 경우 OZeroSpeedHackDetector.StartDetection()으로 수동 시작해야 합니다. |
| checkInterval | float | 1.0 s | 감지기가 타이머를 샘플링하고 비교하는 주기(초). |
| requiredDetections | int | 3 | 위반이 트리거되기 전에 필요한 연속 이상 샘플 수. 불안정한 기기에서의 오탐을 줄이려면 값을 높이세요. |
| ratioTolerance | float | 0.15 | Unity 시간과 네이티브 타이머 간의 허용 편차(±15%). 이 범위를 벗어난 샘플은 이상치로 판정됩니다. |
| maxAllowedRatio | float | 4.0 | 허용되는 최대 타임 비율. 이 값을 초과하면 requiredDetections 값과 관계없이 즉시 스피드핵으로 판정됩니다. |
| detectSlowHack | bool | true | 속도 증가뿐 아니라 시간을 느리게 만드는 도구(1.0 - 허용오차 미만의 비율)도 함께 감지합니다. |
| enableTimeScaleDetection | bool | true | 메모리 에디터 등에 의해 프로그래밍적으로 설정된 비정상 Time.timeScale 값을 감지합니다. |
| hackDetectMultiplier | float | 1.3 | maxAllowedRatio × hackDetectMultiplier를 초과한 샘플은 requiredDetections에 도달하기 전에도 감지로 집계됩니다. |
| enableThreadTimerCheck | bool | true | 백그라운드 스레드 타이머를 추가 기준 클록으로 사용하여, 메인 스레드만 영향을 주는 해킹을 더 어렵게 만듭니다. |
| useWebTimeValidation | bool | false | webTimeUrl에서 주기적으로 현재 시각을 가져와 기기 시계와 비교하여, 기기 수준의 시간 조작을 감지합니다. |
| webTimeUrl | string | — | 신뢰할 수 있는 시간 API 엔드포인트 URL (Unix 타임스탬프 또는 RFC 2616 Date 헤더 반환 필수). useWebTimeValidation 활성화 시 필수입니다. |
| webSyncInterval | float | 15 s | 웹 타임 서버와 동기화하는 주기(초). |
| webRatioTolerance | float | 0.15 | 기기 시간과 웹 서버 시간 간의 허용 편차(±15%). |
| timeOffsetTolerance | float | 60 s | 위반 트리거 전에 허용되는 기기와 웹 서버 간 절대 시계 오프셋(초). |
| focusIgnoreTime | float | 4 s | 앱이 포커스를 다시 획득한 직후 무시할 시간(초). OS가 앱을 일시 정지시킬 때 발생하는 오탐을 방지합니다. |
| loadingGraceTime | float | 6 s | 시작 시 감지 결과를 무시하는 유예 시간(초). 씬 로딩 중 발생할 수 있는 오탐을 방지합니다. |
| lagSpikeIgnore | float | 0.5 s | 프레임 델타가 이 값을 초과하는 샘플은 폐기됩니다(시간 조작이 아닌 실제 랙 스파이크로 간주). |
| buildFailIfTimeScaleTampered | bool | true | 보호 대상 코드가 정책 밖에서 Time.timeScale을 변경하는 것으로 보이면 빌드/검증 단계에서 실패하게 하는 가드입니다. |
| timeScaleTamperExemptions | List<string> | — | 일시정지나 불릿타임처럼 의도적으로 Time.timeScale을 바꾸는 클래스/메서드 패턴을 예외로 등록합니다. |
| webTimeUrls | string[] | — | 신뢰할 시간 엔드포인트를 여러 개 지정할 수 있습니다. 여러 응답 성공을 요구해 웹 시간 신뢰도를 높입니다. |
| minSuccessfulEndpoints | int | 2 | 여러 웹 시간 엔드포인트를 사용할 때 성공해야 하는 최소 응답 수입니다. |
| maxConsecutiveFailures | int | 6 | 웹 시간 조회 실패가 몇 번 연속 발생하면 unavailable 정책을 적용할지 정합니다. |
| onWebTimeUnavailable | enum | WarnOnly | 신뢰할 웹 시간에 접근하지 못할 때 적용할 정책입니다. 네트워크 상황을 테스트한 뒤 엄격한 모드로 올리세요. |
| detectTimeHack | bool | true | 속도 비율 검사 외에 기기 시계와 웹 시간 조작 검사를 켭니다. |
| webSyncJitterPercent | float | 20% | 웹 시간 동기화 주기에 무작위 지터를 더해 예측 가능한 polling을 피합니다. |
| sustainedLagThreshold | float | 0.15 | 반복되는 느린 프레임을 치트 증거가 아니라 지속 lag로 분류하기 위한 기준입니다. |
| sustainedLagGraceDuration | float | 3 s | 지속 lag로 분류되는 동안 적용되는 유예 시간입니다. |
| overloadStrictMultiplier | int | 3 | 반복적인 비정상 타이밍 조건 이후 overload 처리 기준을 더 엄격하게 적용하는 배수입니다. |
| enableRemoteSpeedHackConfig (Pro) | bool | false | Pro 서버 정책으로 Speed & Time Hack의 일부 임계값을 클라이언트 코드 변경 없이 덮어쓸 수 있습니다. |
| remoteSpeedHackConfigInterval | float | 300 s | 원격 Speed & Time Hack 설정을 새로 받아오는 주기입니다. |
| enableSignedServerTime (Pro) | bool | false | 사용 가능한 경우 서명된 서버 시간을 사용해 unsigned 응답 위조를 방지합니다. |
Physics Hack Detector
OZeroPhysicsHackDetector는 각 플레이어 오브젝트마다 독립적인 감지 임계값이 필요한 멀티플레이어/MMORPG 게임을 위해 설계됐습니다. 플레이어 프리팹에 직접 컴포넌트를 추가하고 Inspector에서 오브젝트별로 설정을 구성하세요. 스폰 로직에서 Initialize(playerId)를 호출하여 감지를 활성화하세요.| 필드 | Type | 기본값 | 설명 |
|---|---|---|---|
| maxAllowedSpeed | float | 15 u/s | 허용되는 최대 이동 속도(Unity 유닛/초). 게임에서 허용되는 최대 정당 플레이어 속도에 맞춰 튜닝하세요. |
| distanceTolerance | float | 2.0 u | 네트워크 지연 또는 물리 부정확성을 보정하기 위한 추가 거리 허용값. |
| obstacleLayer | LayerMask | — | 벽과 장애물을 나타내는 레이어 마스크. 라인캐스트로 월핵을 감지하는 데 사용됩니다. |
| checkInterval | float | 0.05 s | 물리 검증기가 플레이어 위치와 속도를 샘플링하는 주기(초). |
| maxDeltaTimeCap | float | 0.1 s | 거리 계산에 사용되는 델타 타임의 상한. 랙 스파이크에 의해 부정 텔레포트가 가려지는 현상을 방지합니다. |
Injection Detector 설정
어셈블리 인젝션, DLL 하이재킹, IL2CPP 패칭 등 무단 코드 인젝션을 감지합니다.
| 필드 | Type | 기본값 | 설명 |
|---|---|---|---|
| Activate Injection & Hooking | checkbox | On | Code Injection Detector 모듈을 활성화합니다. 감지 로직은 전적으로 Native C++ 레이어에서 동작하며 추가 설정이 필요 없습니다. |
| enableServerWhitelist (Pro) | bool | false | 승인된 오버레이나 파트너 모듈을 중앙에서 허용할 수 있도록 서버에서 신뢰 네이티브 모듈 정책을 내려받습니다. |
| serverWhitelistRefreshInterval | float | 0 s | 서버 관리 whitelist 정책 갱신 주기입니다. 0이면 시작 또는 정책 bootstrap 시점에만 갱신합니다. |
| requireSignerForNativeWhitelist | bool | false | 네이티브 whitelist 항목에 파일 해시뿐 아니라 서명자 식별 정보도 요구합니다. 더 강하지만 깨끗한 모듈 서명 데이터가 필요합니다. |
| enableRemoteInjectionConfig (Pro) | bool | true | Pro 서버 정책으로 Injection 임계값과 probe 구성을 클라이언트 코드 변경 없이 조정할 수 있습니다. |
| remoteInjectionConfigInterval | float | 300 s | 원격 Injection 정책을 새로 받아오는 주기입니다. |
| enableWindowsModuleIdentityScan | bool | true | 로드된 Windows 네이티브 모듈을 스캔하고 해시/서명자 정보를 신뢰 정책과 비교합니다. |
| scanIntervalSeconds | float | 1 s | Injection probe의 기본 런타임 스캔 주기입니다. |
| windowsModuleIdentityScanIntervalSeconds | float | 5 s | 상대적으로 무거운 Windows 모듈 식별 스캔의 별도 주기입니다. |
| scanJitterPercent | float | 20% | 스캔 타이밍에 지터를 더해 probe 시점을 예측하기 어렵게 합니다. |
| enableExecutablePrivateMemoryScan | bool | true | Injected shellcode나 런타임 patcher가 자주 사용하는 실행 가능한 private memory 영역을 감지합니다. |
| enableInlineHookScan | bool | false | 알려진 코드 진입점의 의심스러운 inline patch를 감지합니다. Strict 모드에서 호환성 테스트 후 켜는 항목입니다. |
| enableThreadStartAddressScan | bool | true | 스레드 시작 주소가 신뢰 모듈 밖의 의심스러운 위치인지 확인합니다. |
| enableExternalProcessHandleScan | bool | true | 외부 프로세스가 게임 프로세스에 의심스러운 handle을 들고 있는지 감지합니다. |
| detectionConfidenceThreshold | int | 70 | 여러 Injection 신호를 종합했을 때 위반으로 확정하기 위한 최소 confidence 점수입니다. |
| enableWebRuntimeTamperScan (WebGL) | bool | true | WebGL 전용의 가벼운 브라우저 tamper 검사입니다. Native C++ 보호가 아니므로 제한적인 클라이언트 신호 수집으로 보세요. |
| WebGL probes | bools | true | WebGL 빌드에서 DevTools, clock hook, network hook, WASM hook, storage hook, crypto hook 검사를 제어합니다. |
PlayerPrefs 암호화
Unity의 PlayerPrefs에 저장되는 데이터(설정, 사용자 환경설정 등)를 보호하려면 PlayerPrefs를 OZeroSafePlayerPrefs로 교체하세요. API는 동일합니다.
using OZeroSDK.Security;
// Save a value
OZeroSafePlayerPrefs.SetInt("score", 4200);
OZeroSafePlayerPrefs.SetFloat("volume", 0.8f);
OZeroSafePlayerPrefs.SetString("username", "Hero");
// Read a value
int score = OZeroSafePlayerPrefs.GetInt("score", 0);
float volume = OZeroSafePlayerPrefs.GetFloat("volume", 1.0f);
string username = OZeroSafePlayerPrefs.GetString("username", "");
세이브 파일 암호화
세이브 파일을 암호화하려면 File.ReadAllText / File.WriteAllText 대신 OZeroSV_File을 사용하세요. 파일은 쓸 때 자동으로 암호화되고 읽을 때 복호화됩니다. 로드 시 변조 여부도 검사합니다.
using OZeroSDK.Security;
string path = Application.persistentDataPath + "/save.json";
// Write encrypted file
OZeroSV_File.WriteAllText(path, jsonString);
// Read and decrypt file
string json = OZeroSV_File.ReadAllText(path);
developerSecret을 변경하지 마세요. developerSecret은 OZeroSafePlayerPrefs와 OZeroSV_File 데이터를 보호하는 기준값 중 하나입니다. 출시 후 이 값을 바꾸면 기존 버전에서 저장한 보호 데이터가 열리지 않을 수 있습니다.
4
인게임 변수 보호 (Secure Types)
Secure Types는 일반 C# 변수 타입을 암호화된 타입으로 대체합니다. 값이 Native C++ 힙에 저장되어 Cheat Engine 같은 도구로는 메모리를 스캔해도 찾을 수 없습니다. 타입 이름만 바꾸면 되고 나머지 코드는 그대로 동작합니다.
지원 타입
| 기존 | OZero 적용 후 |
|---|---|
| int | OZeroSV_Int |
| long | OZeroSV_Int64 |
| uint | OZeroSV_UInt |
| ulong | OZeroSV_UInt64 |
| short | OZeroSV_Short |
| ushort | OZeroSV_UShort |
| byte | OZeroSV_Byte |
| float | OZeroSV_Float |
| double | OZeroSV_Double |
| decimal | OZeroSV_Decimal |
| bool | OZeroSV_Bool |
| string | OZeroSV_String |
| Vector2 | OZeroSV_Vector2 |
| Vector3 | OZeroSV_Vector3 |
| byte[] | OZeroSV_Buffer |
예시 코드
// ── Before: plain C# variable ──────────────────────────────
using UnityEngine;
public class PlayerStats : MonoBehaviour
{
public int gold = 0;
public float speed = 5.0f;
public int hp = 100;
}
// ── After: OZero Secure Types (only the type name changes) ──
using UnityEngine;
using OZeroSDK.Security;
public class PlayerStats : MonoBehaviour
{
public OZeroSV_Int gold = 0;
public OZeroSV_Float speed = 5.0f;
public OZeroSV_Int hp = 100;
// All arithmetic works exactly as before — no code changes needed
void TakeDamage(int dmg) => hp -= dmg;
void AddGold(int amount) => gold += amount;
}
7
보안 이벤트 처리 (선택)
기본적으로 OZero는 위협을 감지하면 설정된 대응 정책에 따라 앱을 종료하거나 로그만 남깁니다. 직접 경고 화면을 표시하거나, 자체 서버 로그를 보내거나, 종료 직전 저장 처리를 해야 한다면 콜백을 등록할 수 있습니다. 콜백은 OZeroSecurityEvent를 통해 변조 타입, abort code, 메시지 키, 상세 메시지, 종료 예정 여부를 함께 전달합니다.
using UnityEngine;
using OZeroSDK.Security;
public class SecurityHandler : MonoBehaviour
{
void OnEnable()
{
// RegisterUserCallback runs on the user chain only.
// The built-in default handler runs independently and cannot be silenced.
OZeroSecurityManager.Instance.RegisterUserCallback(OnThreatDetected);
}
void OnDisable()
{
OZeroSecurityManager.Instance.UnregisterUserCallback(OnThreatDetected);
}
void OnThreatDetected(OZeroSecurityEvent evt)
{
// evt contains Type, AbortCode, AbortCodeHex, MessageKey, Message, and WillAbort.
Debug.LogWarning(
$"OZero threat: {evt.Type} {evt.AbortCodeHex} {evt.Message}");
switch (evt.Type)
{
case ModulationType.SpeedHack:
// e.g. kick the player, show warning, report to server
break;
case ModulationType.BuildIntegrity:
// evt.WillAbort is usually true for fatal build integrity violations.
break;
case ModulationType.Injection:
break;
}
if (evt.WillAbort)
{
// Last chance to flush your own analytics or save state.
}
}
}
OZeroSecurityEvent, ModulationType, OZeroAbortCode의 전체 목록은 API 레퍼런스에 문서화되어 있습니다.
evt.WillAbort가 true이면 현재 대응 정책상 콜백 이후 앱이 종료됩니다. 이때는 자체 analytics flush나 저장 처리만 짧게 수행하세요.
침해가 감지되면 (1) OZeroSecurityManager.BuiltinResponse 기본 핸들러(항상 실행), (2) 사용자 OZeroSecurityReceiver.HandleModulation 및 인스펙터 onHackDetected 이벤트(씬 레벨 선택 훅), (3) OZeroInternalFallbackReceiver(Response.ForceQuitOnDetection 정책을 리시버 부재 시에도 강제), (4) 네이티브 OZ_ReportViolation(관리 패치 영역을 완전히 우회하는 C++ 익스포트) — 네 경로가 독립적으로 발동합니다. 어느 한 경로를 패치·무력화해도 SDK 전체가 침묵하지 않습니다.
다음 단계
이제 OZero의 핵심 보호 기능이 실행 중입니다. API 레퍼런스에서 모든 클래스, 메서드, 설정 옵션을 자세히 확인해 보세요.