DetectHypervisor

Тема в разделе "WASM.SOURCES & 2LZ", создана пользователем Ahimov, 3 май 2025.

  1. Ahimov

    Ahimov Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2024
    Сообщения:
    646
    q2e74,

    Прот, который на всех играх был взломан, еще и задним числом; не зири, но всеже.. через паблик гипердебаг.
     
    q2e74 нравится это.
  2. galenkane

    galenkane Active Member

    Публикаций:
    1
    Регистрация:
    13 янв 2017
    Сообщения:
    469
    Код (Text):
    1. Вот что нашёл в таргете (Windows 11 ARM64, Build 26100). Полная картина детекции
    2.   гипервизора:
    3.  
    4.   Детекция на ARM64 — совсем другой мир            
    5.  
    6.   На x86 (из треда) Windows 8 использовала rdtsc + idiv timing heuristics. На ARM64
    7.   Windows 11 — ни одного timing-замера. Всё через регистры.
    8.  
    9.   nt!HalpDetectHypervisor — первичная детекция
    10.  
    11.   mrs  x8, ID_AA64PFR0_EL1      ; читать Feature Register 0
    12.   tst  x8, #0xF00                ; проверить bits[11:8] = EL2 support
    13.   beq  no_hypervisor             ; EL2 не реализован → нет гипервизора
    14.  
    15.   ldr  x10, [global_vendor_id]   ; 0x534c5250 = "PRLS" (Parallels!)
    16.   ldr  x11, =0x4d4f4351          ; "QCOM" (Qualcomm) — референсное значение
    17.   cmp  x10, x11
    18.   csel x9, xzr, x10, eq          ; если QCOM → x9=0, иначе x9=vendor
    19.  
    20.   Логика: Если vendor == "QCOM" — это нативный ARM платформенный гипервизор
    21.   Qualcomm, игнорируем. Если любой другой — гипервизор обнаружен.
    22.  
    23.   Текущее состояние на таргете
    24.  
    25.   ┌────────────────────────────┬────────────────────┬───────────────────────────┐
    26.   │         Переменная         │      Значение      │        Расшифровка        │
    27.   ├────────────────────────────┼────────────────────┼───────────────────────────┤
    28.   │ Vendor ID                  │ 0x534c5250         │ "PRLS" = Parallels        │
    29.   ├────────────────────────────┼────────────────────┼───────────────────────────┤
    30.   │ Сравнение 1                │ 0x4d4f4351         │ "QCOM" — нативный         │
    31.   │                            │                    │ Qualcomm                  │
    32.   ├────────────────────────────┼────────────────────┼───────────────────────────┤
    33.   │ Сравнение 2                │ 0x567265707948734d │ "MsHypreV" — Microsoft    │
    34.   │                            │                    │ Hyper-V                   │
    35.   ├────────────────────────────┼────────────────────┼───────────────────────────┤
    36.   │ HalpDetectHypervisor       │ 0x00               │ Не обнаружен (EL2 скрыт   │
    37.   │ result                     │                    │ от guest)                 │
    38.   ├────────────────────────────┼────────────────────┼───────────────────────────┤
    39.   │ HvlHypervisorPresent flag  │ 0x00               │ Не подключён              │
    40.   ├────────────────────────────┼────────────────────┼───────────────────────────┤
    41.   │ HviIsAnyHypervisorPresent  │ PRLS ≠ QCOM → TRUE │ Обнаружен через Vendor ID │
    42.   └────────────────────────────┴────────────────────┴───────────────────────────┘
    43.  
    44.   Парадокс
    45.  
    46.   HalpDetectHypervisor говорит НЕТ — потому что Parallels скрывает EL2 (bits[11:8] в
    47.    ID_AA64PFR0_EL1 = 0 для guest). Но vendor ID глобально = "PRLS", и
    48.   HviIsAnyHypervisorPresent видит, что PRLS ≠ QCOM → ДА.
    49.  
    50.   Механизм на ARM64 (вместо CPUID timing)
    51.  
    52.   x86 (Windows 8):                    ARM64 (Windows 11):
    53.   cpuid leaf 0x40000000               mrs ID_AA64PFR0_EL1 (check EL2)
    54.   rdtsc timing variance              Vendor ID comparison (PRLS vs QCOM)
    55.   idiv jitter heuristics             HvcallGetVpRegister (HVC calls)
    56.   CPUID bit 31 (hypervisor bit)      System register reads
    57.  
    58.   HviGetHypervisorInterface и HviGetHypervisorFeatures используют
    59.   HvcallGetVpRegister — это HVC инструкция (ARM64 Hypervisor Call), аналог VMCALL на
    60.    x86. Регистры:
    61.   - Interface: 0x00090006 (ARM64 VP register)
    62.   - Features: 0x000200 (feature flags register)
    63.  
    64.   Ответ на тред
    65.  
    66.   Тайминг-код из Windows 8 (INIT:00910EDC) с rdtsc+idiv+variance — вырезан из
    67.   современных билдов. На ARM64 его никогда не было. Вместо него — чисто
    68.   архитектурная детекция через system registers. Timing-based подход不稳定 (как
    69.   Ahimov и обнаружил — "на онлайн виртах анстаб и рандом"), и Microsoft от него
    70.   отказалась в пользу deterministic register checks.
    Код (Text):
    1. ⏺ Конкретные команды:
    2.  
    3.   x nt!*Hypervisor*                          — найти все символы с Hypervisor
    4.   uf nt!HalpDetectHypervisor                 — полная дизассемблика первичной
    5.   детекции
    6.   uf nt!HviIsAnyHypervisorPresent            — проверка "любой гипервизор"
    7.   uf nt!HviGetHypervisorInterface            — получение интерфейса
    8.   uf nt!HviGetHypervisorFeatures             — получение фич
    9.   uf nt!HvlIsHypervisorPresent               — кэшированный флаг
    10.  
    11.   dq fffff801e06880f0 L2                     — embedded константы ("QCOM",
    12.   "MsHypreV")
    13.   dq fffff801e0e107d8 L1                     — глобальный Vendor ID → "PRLS"
    14.   dq fffff801e05807e8 L1                     — константа в HviGetHypervisorInterface
    15.   dq fffff801e05807f0 L1                     — номер VP регистра (0x00090006)
    16.   dq fffff801e07c6ef0 L1                     — константа "QCOM" в
    17.   HviIsAnyHypervisorPresent
    18.   dq fffff801e07c6ec8 L1                     — константа "MsHypreV" в
    19.   HviGetHypervisorFeatures
    20.  
    21.   db fffff801e1009A87 L1                     — HvlIsHypervisorPresent флаг (0x00)
    22.   db fffff801e0e10500 L8                     — структура HalpDetectHypervisor:
    23.   [+0]=result, [+1]=done, [+2]=detected
    24.  
    25.   Vendor ID "PRLS" раскодирован из 0x534c5250 побайтово: 0x50='P', 0x52='R',
    26.   0x4c='L', 0x53='S'.
    27.  
    либо кто хочет

    Код (Text):
    1.  
    2. ⏺ Вот практические методы через WinDbg для тайминг-детекции:
    3.   Метод 1: Прочитать ARM64 счётчики напрямую
    4.   На ARM64 аналог RDTSC — CNTVCT_EL0 (virtual) и CNTPCT_EL0 (physical). Гипервизор
    5.   может перехватывать CNTPCT_EL0 — разница во времени чтения выдаст его.
    6.   ; Прочитать виртуальный счётчик (быстро, не трапает)
    7.   mrs x0, CNTVCT_EL0                                      
    8.   ; Прочитать физический счётчик (может вызывать trap → VM-exit → медленно)
    9.   mrs x0, CNTPCT_EL0
    10.   Через WinDbg нельзя напрямую выполнить mrs, но можно собрать stub:
    11.   ; Выделить память и записать ARM64 код
    12.   .pool
    13.   ; mrs x0, CNTPCT_EL0    → d53be080
    14.   ; mrs x1, CNTVCT_EL0    → d53be020
    15.   ; str x0, [x2]           → f9000040
    16.   ; str x1, [x2,#8]        → f9000441
    17.   ; ret                     → d65f03c0
    18.   Метод 2: Вызвать KeQueryPerformanceCounter дважды
    19.   ; Первый замер
    20.   .call nt!KeQueryPerformanceCounter(0)
    21.   r @$t0 = rax
    22.   ; <подождать секунду>
    23.   ; Второй замер
    24.   .call nt!KeQueryPerformanceCounter(0)
    25.   r @$t1 = rax
    26.   ; Разница
    27.   ? @$t1 - @$t0
    28.   Частота = 24 MHz (0x16e3600). За 1 секунду должно быть ~24,000,000 тиков. Если
    29.   дельта другая — время искажается.
    30.   Метод 3: Проверить TSC offset через гипервизор
    31.   uf nt!HvlQueryHypervisorTscAdjustment
    32.   В нашем таргете = mov x0, #0; ret — возвращает 0, значит offset не применяется
    33.   (или гипервизор не подключён на уровне Hvl).
    34.   Метод 4: Прочитать KUSER_SHARED_DATA и сравнить
    35.   ; Прочитать InterruptTime и TickCount
    36.   dq SharedUserData+0x8 L4       ; InterruptTime
    37.   dq SharedUserData+0x318 L2      ; TickCount (32-bit)
    38.   ; Подождать
    39.   g                              ; запустить на секунду
    40.   <Ctrl+Break>                   ; снова остановить
    41.   dq SharedUserData+0x8 L4       ; новое InterruptTime
    42.   ; Сравнить дельту с ожидаемой
    43.   Метод 5: Сравнить частоту с реальным железом
    44.   ; Частота generic timer
    45.   ? nt!KdPerformanceCounterRate
    46.   ; = 0x16e3600 = 24,000,000 = 24 MHz
    47.   ; На реальном Apple Silicon M1/M2 частота = 24 MHz
    48.   ; Если Parallels не подделывает → совпадает
    49.   ; Если подделывает → будет другая
    50.   Метод 6: Ключевой — CNTPCT vs CNTVCT timing
    51.   Это ARM64 аналог rdtsc vs cpuid из треда:
    52.   ; Написать тестовый stub в пул
    53.   .pool
    54.   ; Код:
    55.   ;   isb                    ; serialization barrier
    56.   ;   mrs x0, CNTPCT_EL0    ; physical — МОЖЕТ трапать в EL2
    57.   ;   isb
    58.   ;   mrs x1, CNTVCT_EL0    ; virtual — обычно НЕ трапает
    59.   ;   sub x0, x0, x1        ; разница = offset
    60.   ;   ret
    61.   Если CNTPCT_EL0 трапает в EL2 (Parallels перехватывает) — чтение займёт на порядки
    62.   больше тиков, чем CNTVCT_EL0. Замеряешь это через KeQueryPerformanceCounter до и
    63.   после каждого mrs.
    64.   Суть (как на x86 из треда)
    65.   ┌────────────────────────┬──────────────────────────────┬─────────────────────┐
    66.   │          x86           │            ARM64             │    Что детектит     │
    67.   ├────────────────────────┼──────────────────────────────┼─────────────────────┤
    68.   │ rdtsc                  │ CNTVCT_EL0                   │ Не трапает, быстрый │
    69.   ├────────────────────────┼──────────────────────────────┼─────────────────────┤
    70.   │ cpuid (serializing)    │ CNTPCT_EL0 + ISB             │ Трапает в EL2 =     │
    71.   │                        │                              │ медленно            │
    72.   ├────────────────────────┼──────────────────────────────┼─────────────────────┤
    73.   │ rdtsc:cpuid ratio ~2:1 │ CNTVCT:CNTVCT ratio 1:1 host │ Норма               │
    74.   │  host                  │                              │                     │
    75.   ├────────────────────────┼──────────────────────────────┼─────────────────────┤
    76.   │ rdtsc:cpuid ratio      │ Чтение CNTPCT в 50-100x      │ Гипервизор          │
    77.   │ ~95:1 VM               │ медленнее VM                 │                     │
    78.   └────────────────────────┴──────────────────────────────┴─────────────────────┘
    79.   Идея та же: сравнить тайминг инструкции которая не вызывает VM-exit с инструкцией
    80.   которая вызывает. На железе разница минимальна, в VM — огромная.
    81.  
    Код (Text):
    1.  Результат чека: обнаружение гипервизора на ARM64
    2.  
    3.   Уровень 1: HAL — HalpDetectHypervisor
    4.  
    5.   Результат: НЕ обнаружен (ложноотрицательный)
    6.  
    7.   Логика функции (/tmp/halp_detect.json):
    8.   mrs  x8, ID_AA64PFR0_EL1    ; читаем CPU features
    9.   tst  x8, #0xF00              ; проверяем биты [11:8] — EL2 support
    10.   beq  no_hypervisor           ; если 0 → нет EL2 → нет гипервизора
    11.  
    12.   Значение в памяти структуры (/tmp/hv_flags.json):
    13.   fffff801`e0e10500: 00 01 00 01 00 02 00 00
    14.   [+0]=0x00 → result=FALSE
    15.   [+1]=0x01 → done=TRUE
    16.   [+2]=0x00 → detected=FALSE
    17.  
    18.   Почему: Parallels скрывает EL2 от гостя. Поле ID_AA64PFR0_EL1[11:8] = 0. HAL
    19.   думает, что голое железо.
    20.  
    21.   ---
    22.   Уровень 2: HVL — HvlIsHypervisorPresent
    23.  
    24.   Результат: НЕ обнаружен
    25.  
    26.   ldrb w8, [x8, #0xA87]    ; читает флаг
    27.   cmp  w8, #0
    28.   csetne w0                 ; флаг = 0x00 → FALSE
    29.  
    30.   Флаг по адресу fffff801e1009a87 = 0x00. Слой HVL не подключён — гипервизор
    31.   Parallels не использует Windows Hypervisor Platform (не Hyper-V).
    32.  
    33.   ---
    34.   Уровень 3: HVI — HviIsAnyHypervisorPresent
    35.  
    36.   Результат: ОБНАРУЖЕН (единственный рабочий метод)
    37.  
    38.   ldr  x8, [x8, #0x7D8]     ; vendor ID = 0x534c5250 = "PRLS"
    39.   ldr  x9, =0x4d4f4351       ; "QCOM" (native)
    40.   cmp  x8, x9
    41.   cseleq x8, xzr, x8        ; если QCOM → 0, иначе → vendor value
    42.   cmp  x8, #0
    43.   csetne w0                  ; PRLS ≠ QCOM → TRUE
    44.  
    45.   Глобальный Vendor ID по адресу fffff801e0e107d8 = 0x534c5250 = "PRLS" (Parallels).
    46.  
    47.   ---
    48.   Сводная таблица
    49.  
    50.   ┌───────────────────────────┬───────────┬──────────────────────────────────────┐
    51.   │           Метод           │ Результат │                Почему                │
    52.   ├───────────────────────────┼───────────┼──────────────────────────────────────┤
    53.   │ HalpDetectHypervisor      │ FALSE     │ EL2 скрыт, ID_AA64PFR0_EL1[11:8]=0   │
    54.   ├───────────────────────────┼───────────┼──────────────────────────────────────┤
    55.   │ HvlIsHypervisorPresent    │ FALSE     │ Флаг HVL=0, Parallels ≠ Hyper-V      │
    56.   ├───────────────────────────┼───────────┼──────────────────────────────────────┤
    57.   │ HviIsAnyHypervisorPresent │ TRUE      │ Vendor "PRLS" ≠ "QCOM"               │
    58.   ├───────────────────────────┼───────────┼──────────────────────────────────────┤
    59.   │ HviGetHypervisorInterface │ Работает  │ Вызывает                             │
    60.   │                           │           │ HvcallGetVpRegister(0x00090006)      │
    61.   ├───────────────────────────┼───────────┼──────────────────────────────────────┤
    62.   │ HviGetHypervisorFeatures  │ Работает  │ Вызывает HvcallGetVpRegister(0x200)  │
    63.   ├───────────────────────────┼───────────┼──────────────────────────────────────┤
    64.   │ HvlQueryTscAdjustment     │ 0 (stub)  │ mov x0, #0; ret — нет TSC-сдвига     │
    65.   └───────────────────────────┴───────────┴──────────────────────────────────────┘
    66.  
    67.   ---
    68.   Вывод по таймингу
    69.  
    70.   На x86 классический метод — rdtsc vs cpuid timing ratio. На ARM64 аналог:
    71.  
    72.   1. CNTVCT_EL0 (виртуальный счётчик) — читается без трапа в EL2, быстрый
    73.   2. CNTPCT_EL0 (физический счётчик) — может трапить в EL2, если гипервизор это
    74.   настроил
    75.  
    76.   На таргете KeQueryPerformanceCounter работает через generic timer на 24 МГц
    77.   (KdPerformanceCounterRate = 0x16e3600). Есть отдельные shift-значения для
    78.   гипервизорного случая:
    79.   - PpmPerformanceCounterShift = 0x0a00000005
    80.   - PpmHvPerformanceCounterShift = 0x0900000004
    81.  
    82.   Это значит, что Windows ожидает разную скорость счётчика для голого железа vs
    83.   гипервизора. Parallels настроил проход счётчика без трапов (иначе таймеры ядра
    84.   плыли бы), поэтому чистый тайминг между CNTVCT и CNTPCT не даст заметной разницы.
    85.  
    86.   Рабочий вектор обнаружения для anti-VM на этом таргете: не тайминг, а прямая
    87.   проверка vendor ID через HviIsAnyHypervisorPresent — она однозначно возвращает
    88.   TRUE для PRLS.
    89.  
     
    Последнее редактирование: 29 май 2026 в 00:27