Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Вопрос Windows — Хук win32k.sys через .data ptr на Windows 11 (C++)

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
126
Реакции
3
Народ, кто сейчас ковыряет драйвер-левелы под актуальные билды Windows 11, делюсь инфой по поводу хуков win32k.sys. Те, кто привык работать через .data ptr, наверняка уже заметили, что на «одиннадцатке» эта лавочка прикрыта — оффсеты теперь живут не в .data, а непосредственно в структурах ядра.

Суть проблемы в том, что стандартный подход с поиском через
Код:
Expand Collapse Copy
mov rax, cs:qword_...
больше не канает. Сейчас приходится идти через цепочку вызовов W32GetSessionStateForSession.

1774436920483.png

Для тех, кто хочет прокинуть хук, структура выглядит примерно так:
Код:
Expand Collapse Copy
__int64 __fastcall NtUserQueryDisplayConfig(unsigned int a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5)
{
  unsigned int CurrentProcessSessionId; // eax
  __int64 (__fastcall *v10)(_QWORD, __int64, __int64, __int64, __int64); // rax
 
  CurrentProcessSessionId = GetCurrentProcessSessionId();
  v10 = *(__int64 (__fastcall **)(_QWORD, __int64, __int64, __int64, __int64))(*(_QWORD *)(*(_QWORD *)(W32GetSessionStateForSession(CurrentProcessSessionId) + 136) + 648LL) + 72LL);
 
  if ( v10 )
    return v10(a1, a2, a3, a4, a5);
  else
    return 3221225500LL;
}

1774436930749.png

Код для ресолва и самого патча через InterlockedExchange64 накидал ниже. Важный момент: не пытайтесь юзать это «в лоб» на разных билдах. Оффсеты поплывут моментально. Если пилите что-то под продакшн или серьезный софт — обязательно внедряйте сигнатурный поиск для автоматического получения этих смещений, иначе будете ловить BSOD после каждого обновления винды.

Код:
Expand Collapse Copy
static void* resolve_w32_get_session_state()
{
    void* mod = tools::get_kmodule(L"win32k.sys");
    if (!mod || !MmIsAddressValid(mod))
        return nullptr;
 
    void* exported = tools::get_exported_func(mod, "W32GetSessionState");
    if (exported && MmIsAddressValid(exported))
        return exported;
 
    return nullptr;
}
 
 
NTSTATUS ReplaceGuardPointer(
    PUCHAR guardAddr,
    UINT64 NewValue
)
{
    NTSTATUS status = STATUS_UNSUCCESSFUL;
 
    __try {
        if (guardAddr == nullptr) {
            status = STATUS_INVALID_PARAMETER;
            __leave;
        }
 
        // Atomically replace 64-bit value at guardAddr
        InterlockedExchange64(reinterpret_cast<volatile LONG64*>(guardAddr),
            static_cast<LONG64>(NewValue));
 
        status = STATUS_SUCCESS;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        status = STATUS_ACCESS_VIOLATION;
    }
 
    return status;
}
 
 
 
bool patch_ptr () {
    typedef PVOID(*W32GetSessionState_t)();
    auto pW32GetSessionState = reinterpret_cast<W32GetSessionState_t>(resolve_w32_get_session_state());
    if (!pW32GetSessionState) {
            debug_print("[driver] failed to resolve W32GetSessionState (export + call-target fallback)\n");
        return false;
    }
 
    debug_print("[driver] W32GetSessionState=0x%p\n", pW32GetSessionState);
 
    debug_print("[driver] calling W32GetSessionState()\n");
    auto patch_current_session = [&](bool set_original) -> bool {
        PVOID SessionPtr = pW32GetSessionState();
        if (!SessionPtr)
            return false;
 
        volatile UINT64* guard_slot = nullptr;
        __try {
            const auto guard_chain_1 = *reinterpret_cast<PUCHAR*>(reinterpret_cast<PUCHAR>(SessionPtr) + 0x88);
            if (!guard_chain_1)
                return false;
 
            const auto guard_chain_2 = *reinterpret_cast<PUCHAR*>(guard_chain_1 + 0x288);
            if (!guard_chain_2)
                return false;
 
            guard_slot = reinterpret_cast<volatile UINT64*>(guard_chain_2 + 0x48);
            if (!guard_slot)
                return false;
 
            if (set_original && *original == nullptr)
                *original = reinterpret_cast<void*>(*guard_slot);
        }
        __except (EXCEPTION_EXECUTE_HANDLER) {
            return false;
        }
 
        NTSTATUS repl = ReplaceGuardPointer(reinterpret_cast<PUCHAR>(guard_slot), reinterpret_cast<UINT64>(hook_handler));
        if (!NT_SUCCESS(repl))
            return false;
 
        auto swapped = *guard_slot;
        return swapped == reinterpret_cast<UINT64>(hook_handler);
    };
 
    if (!patch_current_session(true))
    {
    debug_print("[driver] Win11: failed to patch current session guard slot\n");
        return false;
    }
 
    debug_print("[driver] Win11: guard slot patched successfully\n");
    return true;
 
}

В теории, это дает возможность полноценного перехвата функций уровня ядра без лишнего шума. Кто-то уже тестил стабильность этого метода под нагрузкой античитов (EAC/BattlEye)? Есть подозрение, что если просто менять указатель, можно быстро отлететь по хендл-чекам, если не делать это максимально аккуратно.

Братва, делитесь опытом — кто допиливал этот метод под конкретные билды, какие сигнатуры лучше брать за основу? Буду признателен за адекватные фидбеки по реализации.
 
Назад
Сверху Снизу