1. Этот сайт использует файлы cookie. Продолжая пользоваться данным сайтом, Вы соглашаетесь на использование нами Ваших файлов cookie. Узнать больше.
  2. Вы находитесь в сообществе Rubukkit. Мы - администраторы серверов Minecraft, разрабатываем собственные плагины и переводим на различные языки плагины наших коллег из других стран.
    Скрыть объявление
  3. Данный раздел создан исключительно для релизов! Вопросы по лаунчеру или обвязке задавайте ТОЛЬКО в соответсвующей теме автора. Любые другие темы будут удалены, а авторы понесут наказание.

Скрыть объявление
В преддверии глобального обновления, мы проводим исследования, которые помогут нам сделать опыт пользования форумом ещё удобнее. Помогите нам, примите участие!

Лаунчер [C++17|Avn2]: Пишем защиту вместе! #7 Виртуализация на службе у читера

Тема в разделе "Веб-обвязки и лаунчеры", создана пользователем HoShiMin, 16 мар 2019.

  1. gelion

    gelion Активный участник Пользователь

    Баллы:
    78
    [​IMG]
     
  2. Aganus

    Aganus Активный участник Пользователь

    Баллы:
    68
    Имя в Minecraft:
    TheXaver
    Хотелось бы спросить, будет ли возможность сделать защиту в виде драйвера, сертификат для подписывания exe имеется.
     
  3. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    Да, без проблем. Какой функционал требуется именно от драйвера?
    Сертификат твой личный и EV? Потому что тебе потребуется дополнительно подписывать драйвер на портале Microsoft, чтобы была возможность загружаться на машинах с включенным SecureBoot или HVCI/DeviceGuard (для некоторых настроек последнего дополнительно придётся проходить сертификацию WHQL). Для этого нужно регистрировать на портале свою компанию, на которую оформлен сертификат, и сертификат, разумеется, должен быть EV. Если сертификат обычный (не-EV) - сможешь загружаться только на машинах без SecureBoot'a.
     
  4. KsuKsu

    KsuKsu Активный участник Пользователь

    Баллы:
    66
    Хошимяут вернулся на Рубакит. Ура! :cute:
     
  5. SaturnZero228

    SaturnZero228 Активный участник Пользователь

    Баллы:
    66
    Имя в Minecraft:
    SaturnPvP
  6. Aganus

    Aganus Активный участник Пользователь

    Баллы:
    68
    Имя в Minecraft:
    TheXaver
    Будет EV, пока не ev
     
  7. Aganus

    Aganus Активный участник Пользователь

    Баллы:
    68
    Имя в Minecraft:
    TheXaver
    ну чтобы драйвер уже чекал инжекты
     
  8. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    А смысл? Почему именно в драйвере? Драйвер стоит использовать для задач, которые нельзя сделать в юзермоде. Проверять инжекты в драйвере и вполовину не так удобно, как в юзермоде (и возможностей меньше), но в драйвере можно блокировать доступ к процессу со стороны - например, для защиты от читэнджина и других редакторов памяти. Этот функционал уже готов в не связанном с антиинжектами проекте - Kernel-Bridge (посмотри у меня на гитхабе).
     
  9. niki96

    niki96 Старожил Пользователь

    Баллы:
    123
    Хошимин (вносит импакт в развитие ведра и античит индустрии)
    p.s. Как давно я тут ничего не писал
     
  10. SergK35

    SergK35 Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    Sergk35
    От читэнжина можно нормально защититься только драйвером или все же можно как-то нативно?
     
  11. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    Без драйвера нельзя, с драйвером можно, но тоже не на 100%. Если пересоберут драйвер читэнджина, сменят имена девайсов - обойдут защиту, потому что в ядре нет простых способов задетектить, что кто-то читает память. Например, в моём драйвере можно поставить фильтр на функцию DeviceIoControl, через которую читэнджин общается со своим драйвером, но как отличить драйвер читэнджина от любого другого системного драйвера - вопрос открытый. Разве что сигнатурами, которые легко обходятся, стоит читеру накрыть драйвер криптором.
     
  12. fereter

    fereter Активный участник Пользователь

    Баллы:
    66
    Имя в Minecraft:
    Fereter
    Проще бороться с самими последствиями работы CheatEngine, проверяя корректность тех участков памяти, которые читеры меняют чаще всего, чем пытаться выявить модифицированный драйвер. Таким способом можно защититься не только от CheatEngine, но и от других читов, меняющих память.
     
  13. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    Допустим, игрок меняет координаты. Как проверить их корректность в условиях сервера с модами?
     
  14. SergK35

    SergK35 Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    Sergk35
    СталКрафт, хкс вроде же защитились от ЧитЭнжина. В чем секрет?)
     
  15. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    Именно от читэнджина не защитились, но защитились от инжекта классов. На сталкрафте пересобрана джава, все классы (в том числе, классы джавы) зашифрованы, а внутри джавы вшит декриптор. Плюс, вырезано очень много того, что облегчает читерам внедрение классов, и перемешаны поля во внутренних структурах JVM, которые необходимы для работы с JNI (разработчик читов, разумеется, новое расположение полей не знает, а итоговая джава накрыта сверху протектором - например, вмп или темидой). Но именно от редактирования памяти они не защищены, как и не защищены от воллхака (на сталкрафте работают хуки OpenGL). Другое дело, что у них есть проверки и на сервере. Например, на хкс отправляются не координаты игрока, а движение (типа, "сделать шаг вперёд"), а сервер уже сам решает, на сколько сдвинуть игрока - поэтому спидхаки не работают.
    Это очень сложная работа, требует модификации джавы, клиента и сервера, поэтому такое себе могут позволить только топовые сервера со штатом разработчиков.
     
    Последнее редактирование: 24 мар 2019
  16. alexandrage

    alexandrage Старожил Пользователь

    Баллы:
    173
    Вот это правильная защита да. Все остальное изи сдуть на стороне клиента.
     
  17. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    В паблик такую защиту выкладывать бессмысленно, т.к. требует доработки конкретного клиента и сервера, а мало кто будет с этим заморачиваться, особенно с учётом паблик-модов, которые тоже, вероятно, потребуется допиливать руками. Но да, если продавать конкретному серверу за крупную сумму - только такую защиту и есть смысл писать
     
  18. SergK35

    SergK35 Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    Sergk35
    Не хочешь заняться таким ?:В

    Коммерческий раздел здесь присутствует, спрос думаю будет.
     
  19. SergK35

    SergK35 Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    Sergk35
    Пример такого имеется ?
     
  20. Автор темы
    HoShiMin

    HoShiMin Старожил Пользователь

    Баллы:
    173
    #3: Защищаемся от инжектов через оконные хуки

    Третий по популярности способ инжекта библиотек.
    Основан на работе функции SetWindowsHookEx, предназначенной для перехвата оконных сообщений, отправляемых оконной подсистемой из ядра соответствующим окнам в пределах экземпляра рабочего стола.

    Посмотрим на прототип функции:
    Код:
    HHOOK SetWindowsHookExA(
        int idHook,
        HOOKPROC lpfn,
        HINSTANCE hmod,
        DWORD dwThreadId
    );
    
    Нас интересуют два последних параметра:
    hmod - инстанс (базовый адрес) модуля, в котором расположена функция фильтра оконных сообщений, адрес которой передаём в параметре lpfn.
    dwThreadId - идентификатор потока, с которым ассоциирован наш оконный фильтр. Если этот параметр выставить равным нулю, оконный фильтр будет ловить все оконные сообщения для всех оконных потоков в пределах экземпляра рабочего стола. В этом случае система должна доставить код фильтра в каждый целевой процесс. Это возможно только в случае, если код фильтра расположен в dll - в этом случае система подгружает эту библиотеку в каждый оконный процесс. Так и происходит инжект.

    Напишем простейшую дллку, загружающую себя во все оконные процессы:
    Код:
    #include <Windows.h>
    
    static HMODULE hInstance = NULL;
    static HHOOK hHook = NULL;
    
    static LRESULT CALLBACK HookProc(int code, WPARAM wParam, LPARAM lParam)
    {
        return CallNextHookEx(hHook, code, wParam, lParam);
    }
    
    extern "C"
    __declspec(dllexport)
    BOOL WINAPI InjectEmAll()
    {
        hHook = SetWindowsHookEx(WH_DEBUG, HookProc, hInstance, 0);
        return hHook != NULL;
    }
    
    extern "C"
    __declspec(dllexport)
    BOOL WINAPI UnInjectEmAll()
    {
        return UnhookWindowsHookEx(hHook);
    }
    
    
    BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, PCONTEXT lpContext)
    {
        if (dwReason == DLL_PROCESS_DETACH) {
            hInstance = hModule; // Сохраняем инстанс нашей библиотеки
        }
        return TRUE;
    }
    
    И, соответственно, приложение, которое загрузит эту библиотеку и внедрит её во все процессы:
    Код:
    #include <Windows.h>
    
    int main()
    {
        // Загружаем библиотеку и импортируем нашу функцию:
        HMODULE hLib = LoadLibrary(L"OurAwesomeLib.dll");
        auto InjectEmAll = reinterpret_cast<BOOL(WINAPI*)()>(GetProcAddress(hLib, "InjectEmAll"));
    
        // Инжектим нашу дллку во все процессы:
        InjectEmAll();
    
        return 0;
    }
    
    Как видим, способ простой, и потому - достаточно широко распространённый, однако, защита от него, в отличие от двух предыдущих видов инжекта, далеко не так тривиальна. Но обо всём по-порядку.

    Рассмотрим загрузку такой библиотеки в процесс. Так как магии не бывает, вероятнее всего, процесс сам каким-то образом подгружает библиотеку по сигналу из ядра. Поставим брейкпоинт на LoadLibrary в библиотеке kernelbase.dll и посмотрим на трейс потока в случае, когда в процесс загружается библиотека через оконные хуки:
    upload_2019-3-25_20-57-57.png

    Мы видим, что процесс уходит в ядро на функции NtUserCallNoParam, после чего ядро выполняет юзермодный каллбэк __ClientLoadLibrary, вызвав его из диспетчера юзермодных каллбэков KiUserCallbackDispatcher:
    upload_2019-3-25_21-5-44.png

    Диспетчер каллбэков ищет нужный каллбэк по индексу в таблице юзермодных каллбэков KernelCallbackTable (обведена рамкой), адрес которой можно найти в PEB - Process Environment Block. Эта таблица нужна для хранения адресов юзермодных каллбэков, использующихся в оконной подсистеме, которые ядро может при необходимости вызывать в любом оконном процессе напрямую, используя сервис KeUserModeCallback.

    Подытожим вышесказанное:
    1. Процесс-инжектор устанавливает глобальные оконные хуки функцией SetWindowsHookEx с обработчиком в дллке, которую хотим подгрузить во все оконные процессы
    2. Ядро вызывает KeUserModeCallback для всех оконных процессов, передавая в неё индекс функции __ClientLoadLibrary в таблице PEB->KernelCallbackTable
    3. Происходит переключение в юзермод на точку входа KiUserCallbackDispatcher, которая по переданному индексу находит и вызывает __ClientLoadLibrary
    4. Внутри __ClientLoadLibrary происходит вызов LoadLibrary из KernelBase
    5. После подгрузки библиотеки каллбэк возвращается в ядро через сервис ZwCallbackReturn

    Для полноты картины приведём внутреннее устройство __ClientLoadLibrary:
    upload_2019-3-25_22-39-19.png
    Итак, чтобы защититься от таких инжектов, можно пойти двумя путями:
    1. Перехватить __ClientLoadLibrary (например, подменить её адрес в KernelCallbackTable)
    2. Проверять, откуда вызвана LdrLoadDll, и, если вызвана из __ClientLoadLibrary, запрещать загрузку

    Но для начала нужно найти сам адрес __ClientLoadLibrary. Эта функция не экспортируется, но её адрес лежит в KernelCallbackTable. Так как это единственная функция из таблицы, которая вызывает LoadLibrary, мы можем поставить хук на LdrLoadDll и в обработчике проверять каждый фрейм стактрейса, лежит ли он в диапазоне одной из функций в KernelCallbackTable.

    Так как размер KernelCallbackTable не определён (как и порядок функций), мы можем прибегнуть к хитрости: перебирать элементы в таблице до тех пор, пока адреса функций лежат внутри user32.dll. Первый не попавший в user32 элемент считаем концом таблицы.

    Теперь в обработчике хука LdrLoadDll снимаем трейс функцией CaptureStackBackTrace. Нам хватит 5-6 фреймов. Для каждого фрейма выполняем проверку, псевдокод которой представлен ниже:
    Код:
    // Проверяем, лежит ли адрес в первых 256 байтах функции:
    static inline BOOL IsInFunction(PVOID Ptr, PVOID FuncBase)
    {
        return reinterpret_cast<PBYTE>(Ptr) >= reinterpret_cast<PBYTE>(FuncBase)
            && reinterpret_cast<PBYTE>(Ptr) < (reinterpret_cast<PBYTE>(FuncBase) + 256);
    }
    
    BOOL IsCalledFromWinHook(PVOID StacktraceFrame)
    {
        for (PVOID KernelCallback : KernelCallbackTable) {
            if (IsInFunction(StacktraceFrame, KernelCallback)) {
                return TRUE; // Мы внутри __ClientLoadLibrary
            }
        }
        return FALSE;
    }
    
    Соответственно, если мы видим, что нас вызвали из __ClientLoadLibrary, можно просто отменить загрузку библиотеки, вернув из хука LdrLoadDll любой код ошибки - например, STATUS_NOT_FOUND.

    Подводные камни:
    Через оконные хуки подгружаются, в том числе, и системные библиотеки (uxtheme.dll), необходимые для корректного рендеринга UI, а также, библиотеки драйверов видеокарт, стримилки, библиотеки от мессенджеров (например, Discord), поэтому бездумное блокирование может повлечь дополнительные проблемы со сторонним софтом. Как вариант, можно разрешать загрузку только подписанных и системных библиотек (на помощь идут функции WinVerifyTrust и SfcIsFileProtected соответственно).
     
    Последнее редактирование: 26 мар 2019

Поделиться этой страницей