문서
/
레시피
/

VM 방어 텔레메트리 및 대응

VM 방어 텔레메트리 및 대응

Pro
v7.1.0+

vmDefenseHook으로 VM 방어 탐지 결과를 백엔드에 보고하고, vmDefenseReaction으로 탐지 범주별 대응 방식을 조정하세요. 전혀 중단되지 않는 텔레메트리 전용 빌드부터 탈취된 번들을 확실히 중단시키는 빌드까지 구성할 수 있습니다.

문제

VM 방어 기능, 즉 vmSelfDefending, vmDebugProtection, vmDomainLock은 로컬에서 작동합니다. 디버거, 자동화 도구, 변조된 환경, 승인되지 않은 도메인이 탐지되면 보호된 코드가 중단되거나 조용히 자기 결과를 오염시킵니다. 공격자는 막을 수 있지만, 기본 설정에서는 그 사실을 전혀 알 수 없습니다. 번들이 얼마나 자주 탐색당하는지, 어떤 탐지기가 작동했는지, 정상 사용자에게 방어 기능이 오작동하고 있지는 않은지 알 방법이 없습니다.

v7.1.0부터 두 가지 옵션이 이 공백을 메웁니다. 어느 쪽도 방어 기능을 활성화하지는 않으며, 이미 켜 둔 방어 기능을 관찰하고 방향을 잡아줄 뿐입니다.

  • vmDefenseHook - 방어 기능이 무언가를 탐지할 때마다 시그널 객체를 전달받는 전역 콜백입니다. 백엔드로 텔레메트리를 보내는 데 사용하세요.
  • vmDefenseReaction - 활성화된 방어 기능이 어떻게 대응할지를 범주별로 선택하는 맵입니다. 중단하거나, 결과를 오염시키거나, 로컬에서는 아무것도 하지 않도록 할 수 있습니다.

레시피 1 - 탐지 결과를 백엔드로 보고하기

1단계 - 난독화된 번들이 로드되기 전에 전역 훅 함수를 등록하세요

VM 런타임과 그 방어 기능은 보호된 프로그램보다 먼저 실행되므로 상당수의 탐지가 시작 시점에 발생합니다. 훅은 난독화된 script 태그보다 앞선 위치에서 호스트 페이지의 평범한 전역으로 정의하세요.

<script>
    // In your page, BEFORE the obfuscated script:
    window.__vmDetection = function (signal) {
        navigator.sendBeacon('/api/vm-defense', JSON.stringify(signal));
    };
</script>
<script src="/app.obfuscated.js"></script>

2단계 - vmDefenseHook이 이를 가리키게 하세요

이 옵션은 name이 호출할 전역 함수 이름인 객체입니다(aliases는 선택 사항이며 아래에서 설명합니다).

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    // the hook alone enables nothing - a defense must be on for detectors to run:
    vmSelfDefending: true,
    vmDebugProtection: true,
    vmDefenseHook: { name: '__vmDetection' }
});

문자열만 넘기는 형태(vmDefenseHook: '__vmDetection')도 { name: '__vmDetection' }의 축약형으로 여전히 허용되지만 사용이 중단될 예정이므로 객체 형태를 사용하세요.

대시보드에서는 방어 기능(vmSelfDefending, vmDebugProtection, vmDomainLock) 중 하나 이상을 켜면 VM 옵션 패널에 VM Defense Hook 필드가 나타납니다.

3단계 - 백엔드에서 시그널을 받으세요

탐지가 발생할 때마다 훅은 하나의 signal 객체와 함께 호출됩니다.

  • source - 구체적인 탐지기입니다. headless, node, agent, domain, debugger, sandbox, nativeHook, timing, integrity 중 하나입니다. v7.4.0부터 기존의 envinspector 탐지기는 source: 'debugger'로 보고됩니다.
  • category - automation, debugger, sandbox, domain, tamper, integrity 중 하나입니다. node 소스는 category: 'debugger'로 보고됩니다(v7.4.0+).
  • score, threshold - 탐지 점수와 그 점수가 넘어선 임곗값입니다

최소한의 수신 엔드포인트 예시입니다(Express를 예로 들었지만 POST를 받을 수 있는 백엔드라면 무엇이든 됩니다). 본문을 배열로 정규화하므로 아래 버퍼 패턴이 보내는 일괄 형식도 함께 처리합니다.

app.post('/api/vm-defense', express.text({ type: '*/*' }), (req, res) => {
    // a signal: { source: 'headless', category: 'automation', score: 7, threshold: 4 }
    const signals = [].concat(JSON.parse(req.body));
    for (const signal of signals) {
        console.warn('vm-defense', { ...signal, ip: req.ip, ua: req.get('user-agent') });
    }
    res.sendStatus(204);
});

시그널 필드 이름 바꾸기 (aliases) v7.4.0+

기본 source / category 값은 뜻이 그대로 드러나는 이름이라, 콜백을 들여다보는 사람(또는 결과물을 읽는 사람)이 어떤 보호 기능인지, 어떤 탐지기가 작동했는지 알아볼 수 있습니다. aliases는 시그널 필드를 원하는 알아볼 수 없는 토큰으로 바꿔줍니다. 이 변환은 시그널이 발생하기 전에 VM 내부에서 적용되므로, 원래 이름은 결과물에 나타나지도 않고 콜백에 전달되지도 않습니다. 매핑은 여러분의 앱만 알고 있으며, 앱이 토큰을 백엔드로 전달합니다.

별칭은 필드 단위로 지정합니다. 각 항목은 key(콜백이 받게 될 프로퍼티 이름)를 받고, 문자열 이름 필드인 sourcecategoryvalues 맵도 받습니다. 반면 score / threshold는 숫자이므로 key만 받습니다. 지정하지 않은 항목은 기본 이름을 유지합니다.

vmDefenseHook: {
    name: '__vmDetection',
    aliases: {
        source:    { key: 'a8Qm', values: { headless: 'xP4m9Q' } },
        category:  { key: 'p3Tx', values: { automation: 'bQ7s1M' } },
        score:     { key: 's1' },
        threshold: { key: 't1' }
    }
    // the callback now receives e.g. { a8Qm: 'xP4m9Q', p3Tx: 'bQ7s1M', s1: <score>, t1: <threshold> }
}

대시보드에서는 VM Defense Hook 필드 아래에 Signal aliases 섹션이 있습니다.

레시피 2 - 기본 대응 방식 조정하기

vmDefenseReaction은 각 탐지 범주가 어떻게 대응할지를 설정합니다. 무언가를 활성화하지는 않습니다. 방어 기능 자체는 vmSelfDefending, vmDebugProtection, vmDomainLock으로 켜며, 이 옵션은 활성화된 방어 기능의 대응 방식만 선택합니다. 제어의 단위는 범주입니다. 한 범주에 속한 모든 탐지기는 그 범주의 대응 방식을 따르며, 해당 옵션이 꺼져 있는 범주에 대응을 설정해 봐야 아무 효과가 없습니다.

범주활성화 옵션대응 시점
automationvmSelfDefending 또는 vmDebugProtection사람이 아니라 소프트웨어가 코드를 구동하고 있을 때입니다. 헤드리스 브라우저나 자동화된 브라우저, 스크래핑 / 테스트 프레임워크, 페이지를 단계별로 실행하는 AI 코딩 에이전트 등이 해당합니다.
debuggervmDebugProtection 또는 vmSelfDefending누군가 디버거나 브라우저 개발자 도구 인스펙터를 열어 두고, 실행 중인 코드를 단계별로 따라가며 분석하고 있을 때입니다.
sandboxvmDebugProtection코드가 실제 브라우저에서 실행되고 있지 않을 때입니다. 오프라인에서 실행하고 분석하기 위해 에뮬레이션되거나 스크립트로 구성된 JavaScript 환경으로 옮겨진 경우입니다.
domainvmDomainLock승인하지 않은 사이트에서 코드가 실행되고 있을 때입니다. vmDomainLock 허용 목록에 없는 호스트가 해당하며, 예를 들어 번들이 다른 사람의 도메인으로 복사된 경우입니다.
tampervmSelfDefendingVM을 감시하거나 가로채기 위해 그 주변의 JavaScript 환경이 변경되었을 때입니다. 네이티브 브라우저 내장 객체가 계측된 버전으로 바꿔치기된 경우 등이 해당합니다.
integrityvmSelfDefending보호된 번들의 코드 자체가 생성 이후에 편집되거나 패치되었을 때입니다.

키로는 위 여섯 개 범주 이름이나 default(지정하지 않은 범주에 적용되는 대체값)를 사용합니다. 값은 다음과 같습니다.

  • break - 즉시 중단합니다
  • decoy - 오염된 상태로 계속 실행하며 조용히 잘못된 결과를 만들어 냅니다
  • none - 로컬에서는 아무것도 하지 않습니다 (텔레메트리 전용)

설정하지 않은 범주는 기본값을 따릅니다.

// built-in defaults
vmDefenseReaction: {
    automation: 'break',
    debugger: 'decoy',
    sandbox: 'decoy',
    domain: 'break',
    tamper: 'break',
    integrity: 'break'
}

default는 구조적으로 오탐이 없는 범주(integrity, tamper)를 포함해 모든 범주에 적용됩니다. 따라서 { default: 'none' }은 실제로 아무것도 중단시키지 않는 텔레메트리 전용 빌드가 됩니다.

vmDefenseReaction: { default: 'none' } // never break - pair with vmDefenseHook
vmDefenseReaction: { automation: 'none' } // tolerate automation FPs; the rest keep their defaults (a bad domain still breaks)

대시보드에서는 방어 기능을 하나라도 켜면 VM 옵션 패널에 VM Defense Reactions 선택 항목이 나타납니다. 각 범주는 해당 탐지기를 내보내는 방어 기능이 켜져 있을 때만 편집할 수 있습니다.

텔레메트리에서 강제 적용으로

가시성과 강제 적용 중 하나를 처음부터 고를 필요는 없습니다. 방어 기능을 두 번의 빌드로 나눠 적용하세요. 먼저 보고만 하는 빌드를 내보내고, 텔레메트리가 깨끗해 보이면 실제로 대응하는 빌드를 내보내면 됩니다.

1단계 - 관찰 전용 빌드를 배포하세요

사용할 방어 기능을 모두 켜고, vmDefenseHook이 여러분의 엔드포인트를 가리키게 한 뒤, 모든 대응을 끄세요. 모든 탐지기가 그대로 동작하며 탐지 결과를 백엔드로 보고하지만, 아무것도 중단시키지는 않습니다.

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    vmSelfDefending: true,
    vmDebugProtection: true,
    vmDomainLock: ['example.com'],
    vmDefenseHook: '__vmDetection',
    vmDefenseReaction: { default: 'none' } // observe only
});

2단계 - 수집된 시그널을 검토하세요

이 빌드가 실제 트래픽을 겪고 나면, 정상적인 사용이 유발한 탐지가 있는지 살펴보세요. 가장 흔한 두 가지는 다음과 같습니다.

  • 직접 운영하는 E2E 테스트나 가동 상태 모니터링에서 발생한 automation 탐지. 프로덕션에서 이 범주를 눈감아 주기보다는, 해당 산출물을 방어 기능 없이 빌드하세요.
  • vmDomainLock 허용 목록에 넣는 것을 잊은 스테이징이나 프리뷰 호스트에서 발생한 domain 탐지. 해당 호스트를 추가하세요.

대응을 약화하기보다 원인을 고치는 편이 낫습니다. none으로 남겨 둔 범주 하나하나가 공격자가 더 이상 신경 쓰지 않아도 되는 탐지기이기 때문입니다.

3단계 - 대응을 켜세요

default: 'none' 재정의를 지우면 범주별 기본 대응이 적용됩니다. 전환에 필요한 것은 그 한 줄뿐입니다. 없앨 수 없는 오탐이 계속 발생하는 범주가 있다면 그 범주만 none으로 두고(예: vmDefenseReaction: { automation: 'none' }) 나머지는 강제 적용하세요.