Начинающий
- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 126
- Реакции
- 3
Народ, кто сейчас ковыряет драйвер-левелы под актуальные билды Windows 11, делюсь инфой по поводу хуков win32k.sys. Те, кто привык работать через .data ptr, наверняка уже заметили, что на «одиннадцатке» эта лавочка прикрыта — оффсеты теперь живут не в .data, а непосредственно в структурах ядра.
Суть проблемы в том, что стандартный подход с поиском через
больше не канает. Сейчас приходится идти через цепочку вызовов W32GetSessionStateForSession.
Для тех, кто хочет прокинуть хук, структура выглядит примерно так:
Код для ресолва и самого патча через InterlockedExchange64 накидал ниже. Важный момент: не пытайтесь юзать это «в лоб» на разных билдах. Оффсеты поплывут моментально. Если пилите что-то под продакшн или серьезный софт — обязательно внедряйте сигнатурный поиск для автоматического получения этих смещений, иначе будете ловить BSOD после каждого обновления винды.
В теории, это дает возможность полноценного перехвата функций уровня ядра без лишнего шума. Кто-то уже тестил стабильность этого метода под нагрузкой античитов (EAC/BattlEye)? Есть подозрение, что если просто менять указатель, можно быстро отлететь по хендл-чекам, если не делать это максимально аккуратно.
Братва, делитесь опытом — кто допиливал этот метод под конкретные билды, какие сигнатуры лучше брать за основу? Буду признателен за адекватные фидбеки по реализации.
Суть проблемы в том, что стандартный подход с поиском через
Код:
mov rax, cs:qword_...
Для тех, кто хочет прокинуть хук, структура выглядит примерно так:
Код:
__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;
}
Код для ресолва и самого патча через InterlockedExchange64 накидал ниже. Важный момент: не пытайтесь юзать это «в лоб» на разных билдах. Оффсеты поплывут моментально. Если пилите что-то под продакшн или серьезный софт — обязательно внедряйте сигнатурный поиск для автоматического получения этих смещений, иначе будете ловить BSOD после каждого обновления винды.
Код:
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)? Есть подозрение, что если просто менять указатель, можно быстро отлететь по хендл-чекам, если не делать это максимально аккуратно.
Братва, делитесь опытом — кто допиливал этот метод под конкретные билды, какие сигнатуры лучше брать за основу? Буду признателен за адекватные фидбеки по реализации.