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

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

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

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

  1. Автор темы
    HoShiMin

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

    Баллы:
    173
    Avanguard 2
    The Win32 Anti-Intrusion Library


    Итак, погнали!

    Коротко, в чём суть: пишем библиотеку для защиты от инжектов.
    Писать будем с нуля, основываясь на опыте прошлой версии. Постараемся сделать проект максимально простым, легковесным и быстрым, а главное - надёжным.

    В рамках проекта, кроме базового функционала, постараемся решить инжект валидных библиотек (стримилки, драйвера) и сделать удобную систему информирования об инжектах, чтобы решение о блокировке принимала не библиотека, а человек, что позволит минимизировать последствия ложных срабатываний.

    Делать будем эталонную реализацию, пользуясь идеями которой, каждый сможет написать свой собственный вариант защиты от распространённых видов инжектов для своих собственных защит и лаунчеров. В комментариях также буду описывать принципы работы основных компонентов (защита от создания потоков, защита от модификации памяти, защита от макросов и т.д.), подводные камни и возможные проблемы, а также, возможные пути их решения.


    Список возможностей (будет пополняться по мере разработки):
    - [✔️] Защита от создания сторонних потоков
    - [✔️] Защита от инжекта через оконные хуки
    - [✔️] Фильтр загружаемых модулей
    - [✔️] Защита от инжекта через AppInit_DLLs
    - [✔️] Защита от мануалмаппинга библиотек
    - [✔️] Защита от угона контекста потоков
    - [✔️] Фильтр APC
    - [❌] Проверка исполняемых секций
    - [❌] Поддержка пользовательских хуков
    - [✔️] Проверка памяти на наличие неизвестных регионов
    - [✔️] Проверка стактрейсов
    - [❌] Система репортов
    - [❌] Привязки к Java, C# и Delphi
    - [❌] Связка с драйвером для запрета на получение хэндлов со стороны

    Для проекта нам понадобятся:
    - Библиотека для перехвата функций - берём свою (HookLib), чтобы иметь полный контроль над установкой перехватов и не тащить лишних зависимостей.
    - Дизассемблер (необходим для хуков и проверок) - возьмём Zydis, как самый быстрый и легковесный.
    - Некий хэш для проверки секций - возьмём t1ha, как самый быстрый из всех существующих.
    - Библиотека для шифрования строк, чтобы в бинарнике нельзя было найти нужную строку и по ней выйти на место её использования - берём xorstr.

    Основной репозиторий проекта: https://github.com/HoShiMin/Avanguard
    WinXP не поддерживается. Возможно, в будущем поддержка появится, но не в приоритете.

    Заметки о принципах работы защиты:
    #1: Защищаемся от создания потоков со стороны
    #2: Защищаемся от AppInit_DLLs
    #3: Защищаемся от инжектов через оконные хуки
    #4: Защищаемся от инжектов через APC
    #5: Защита от угона контекста
    #6: Защита от скана памяти
    #7: Виртуализация на службе у читера

    Все идеи по предотвращению ложных срабатываний, по защите, предложения по интерфейсу API, по нужным функциям или по оптимизации алгоритмов пишем в комментариях.
     
    Последнее редактирование: 31 май 2020
  2. Дмитрий keka_VAC

    Дмитрий keka_VAC Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    Cake_progress
  3. SergK35

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

    Баллы:
    76
    Имя в Minecraft:
    Sergk35
    Очень годно. Но написано, что пишем защиту вместе. Неплохо бы вставлять сюда какие-либо комментарии, а не просто выкладывать исходник на гит =)
     
  4. Автор темы
    HoShiMin

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

    Баллы:
    173
    Буду писать мини-статьи в комментах по мере времени, чтобы было понятно, как работает внутри. Потом, возможно, скомпоную их и выложу на хабр
     
  5. lim254

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

    Баллы:
    68
    Это будет на сокетах или в виде api чтобы можно было отправлять на php обработчик например?
     
  6. Автор темы
    HoShiMin

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

    Баллы:
    173
    В виде API. Разработчик подписывается на рассылку уведомлений, и ему прилетает полная информация об инжекте: тип и сопутствующая информация (имена дллок, адреса в памяти, некие дампы (возможно, даже полный дамп всего процесса), списки перехваченных функций) - всё, что поможет понять, чит это или нет. И в рантайме уже на стороне процесса или проанализировать на месте, или отправить куда-то на сервер всё необходимое. В первой версии хотел сделать подобное, но остановился на простых уведомлялках о самом факте инжекта, без подробностей - в таких репортах было мало смысла.
     
    Последнее редактирование: 16 мар 2019
  7. lim254

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

    Баллы:
    68
    Ахахах, а ведь всё так хорошо началось :(
     
  8. Автор темы
    HoShiMin

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

    Баллы:
    173
    А ты как хотел? Чтобы дллка сама отправляла репорты на сервер?
     
  9. Gravit

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

    Баллы:
    66
    А это точно C++17?
    Из C++ толком ничего не используется, а код как был в голых функциях, так и остался.

    Это вообще читаемо? Почему используется LONG вместо long long int, VOID вместо void, почему все имена функций начинаются с большой буквы, почему нет мало мальских классов? Да даже глобальные супер объекты выглядят и то лучше этой простыни из нечитаемого текста.
    Если используем стандартную библиотеку, то почему не пользуемся всеми возможностями? Почему в логгере нативные вызовы WriteFile/ReadFile, когда есть fstream? Почему мы используем printf для вывода самых обычных строк из стандартной библиотеки?
    Если мы берем для работы последний стандарт C++17, то почему 95% кода написано как 20 лет назад?
     
  10. Автор темы
    HoShiMin

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

    Баллы:
    173
    Стиль, принятый в WinAPI: CamelCase для названий функций и венгерская нотация для переменных. Аналогично для названий типов, принятых в разработке под Win32 и использующихся во всех Win-заголовочниках (ULONG/VOID и т.д.).
    Использовать стиль, принятый в Linux-сообществе, для разработки под Win - моветон. Будем придерживаться стандартов платформы.
    В обработчиках хуков нельзя использовать то, во внутреннем устройстве чего ты не уверен. Так как практически любой обработчик может быть вызван в контексте занятого лоадер-лока или лока кучи, есть вероятность получить сложноотлаживаемые взаимные блокировки, как было в прошлой версии. Кроме того, все операции внутри обработчиков должны быть максимально быстрыми, поэтому есть смысл в отказе от fstream/iostream в пользу WriteFile (или сразу NtWriteFile) и printf (но даже его использовать небезопасно).
    Поэтому, коль скоро мы работаем в специфичном окружении, следует минимизировать количество сущностей, которые могут выделять память, захватывать свои блокировки или вызывать функции из библиотек уровнем выше, чем текущий контекст. Логгирование само по себе - небезопасная операция в данном случае.
    Хороший вопрос: если фильтры предполагают существование исключительно в одном экземпляре, какой практический смысл выносить их в отдельный класс и усложнять архитектуру при том, что у такого класса не будет специфичных для него методов, и нужен он будет лишь как структура для хранения служебной информации, а вся работа будет происходить внутри внеклассовых обработчиков хуков (как вариант - внутри статических методов)?
     
    Последнее редактирование: 16 мар 2019
  11. Gravit

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

    Баллы:
    66
    То, что такой стиль принят в заголовочных файлах Windows еще не делает его хорошим. Эти файлы были написаны ОЧЕНЬ много лет назад. Ничто не мешает использовать стиль, принятый в стандартной библиотеке C++ как минимум для повышения читабельности и поддерживаемости кода.
    Следуя этой логике даже в printf нельзя быть уверенным. Используя WriteFile/ReadFile мы максимально усложняем чтение и развитие этой части кода. А это значит меньше людей станут развивать этот проект. Меньше людей развивают проект -> повторяем судьбу Avanguard 1.
    P.S. Никаких проблем с fstream и iostream в самых разных локов не возниает. Проверено на собственном опыте и опыте многих людей, что пользуются моей защитой.
    То, что обработчики хуков внеклассовые - исключительно ограничение вашей хук либы. По хорошему они так же должны находиться внутри класса. С этим проблем нет, подтверждено опытом моей защиты.
    Разбиение кода на классы УПРОЩАЕТ архитектуру как для написания кода, так и для чего разработки.

    Проблемы, заложенные в Avanguard 1 перенесены сюда без каких либо изменений. Жаль, очень жаль
     
  12. Автор темы
    HoShiMin

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

    Баллы:
    173
    Это холивар, т.к. субъективное мнение и вкусовщина. Ты можешь сказать: "Мне не нравится стиль Win32" - это нормально. Но чтобы утверждать "стиль Win32 плох, и вам всем следует использовать %стиль_нейм%", требуется компетенция и авторитет. Мне одинаково просто читать и линуксовый стиль, и стиль Win32, поэтому, аналогично, могу сказать, что оба стиля одинаково хороши, что тоже будет субъективным мнением. Но так как, де-факто, при разработке под Win именно это - стандарт, то и придерживаться следует его, т.к., по большей части, проект состоит из WinAPI/NativeAPI, а не из кода стандартной библиотеки STL. И этот код никак не портируется на другие платформы.
    Странное заявление, учитывая, что низкоуровневые библиотеки под Win практически всегда пишутся с широким использованием WinAPI, т.к. весь код находится на одном уровне абстракций и не нужно прыгать между высоким (STL) и низким (WinAPI) уровнями - STL служит ВСПОМОГАТЕЛЬНОЙ библиотекой, а не основной. Что конкретно в данном случае усложняет поддержку кода?
    Это здорово, но, чтобы судить и сравнивать объективно, хотелось бы увидеть исходники твоей защиты, потому что сейчас это голословные заявления без конкретики и больше похоже на попытку протолкнуть свои предпочтения в коде в другие проекты. Возможно, в функционале твоей защиты просто нет накладывающих свои ограничения частей, а возможно, найдутся места, до которых уже я смогу докопаться, поэтому говорить о том, что это "подтверждено" твоей защитой - некорректно.
     
    Последнее редактирование: 16 мар 2019
  13. Gravit

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

    Баллы:
    66
    Это не столько холивар, сколько использование стиля 20 летней давности. Никаких вопросов бы не возникало, если бы не это:
    Если вы спускаетесь до уровня объяснений "как это работает", раз уж вы заявляете эталонную реализацию, где КАЖДЫЙ, повторюсь, КАЖДЫЙ, не программист времен 2000-x, не профессор престарелых лет, а "мамкины кодеры", которые и знать не знали "как было раньше" смогут написать собственную защиту.
    Вы заявляете эталонную реализацию, но делаете её максимально нечитабельной для этой аудитории. Более того, я уверен, что аудитория Хабра, куда более подкованная в техническом плане чем аудитория RuBukkit, не "схавает" ваш код. Сейчас разобраться в этом коде смогут единицы, а развивать его - только Вы сами. Это дорога в никуда.

    Смотрим на Ваш код. Смотрим на "C++17" в названии. Еще раз смотрим на Ваш код.
    Так где обещанный C++17? Честно - я ждал эту защиту что бы присоединится к разработке. Но видя этот дикий ужас пришел к выводу - поддержка такого кода силами сторонних разработчиков почти невозможна.

    Подход к защите ровно такой же как и в Avanguard 1. Ничего не было изменено. Сейчас это тот же Avanguard 1, с теми же векторами атаки что и раньше, с теми же проблемами что и раньше.
    Мир изменился, HoShiMin
     
  14. Автор темы
    HoShiMin

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

    Баллы:
    173
    @Gravit, и всё же, где конкретика, что то, что ты предлагаешь, лучше? Пока это просто странный хейт и два непонятных реверанса в сторону твоей защиты, где всё идеально. Пусть так! Мне непонятно само обоснование нечитабельности. Давай откроем, например, фильтр памяти (MemoryFilter.cpp). Внутри у нас класс-хранилище и глобальная структурка со служебной инфой (в том числе, с единственным инстансом хранилища).
    Скроллим дальше: 3 обработчика и 3 интерфейсных функции для включения, выключения и проверки. Всё это обёрнуто в соответствующий неймспейс, так?
    Наружу у нас смотрит только это:
    Код:
    namespace MemoryFilter {
        BOOL EnableMemoryFilter();
        VOID DisableMemoryFilter();
        BOOL IsMemoryKnown(LPCVOID Address);
    }
    
    Пока всё понятно.

    Давай то же самое перепишем в виде класса. Получится нечто такое:
    Код:
    class MemoryFilter {
    private:
        // ... Здесь содержимое той глобальной структурки ...
        static NTSTATUS NtAllocHandler(...);
        static NTSTATUS NtProtectHandler(...);
        static NTSTATUS NtFreeHandler(...);
    public:
        bool enable();
        void disable();
        bool is_memory_known(const void* address) const;
    }
    
    А теперь сравним использование:
    Код:
    MemoryFilter::IsMemoryKnown(0x1234);
    vs
    mem_filter_instance.is_memory_known(0x1234);
    
    Мне кажется, или получилось абсолютно одно и то же? В чём конкретно заявленное тобой упрощение?

    А теперь что нас ждёт, если будем использовать твой вариант, и какие возникают вопросы:
    1. Если класс объявлен в хедере, везде, где мы будем его подключать, придётся тащить хедеры <winternl> и <ntstatus> (и, скорей всего, Windows.h, что не так страшно, т.к. он и так используется почти везде).
    2. Как обеспечить единственность экземпляра фильтра? Синглтон?
    3. Кто будет ответственен за создание и уничтожение объекта?
    4. Если создать глобальный объект в *.cpp, и extern'ить при необходимости (или даже в самом хедере), вместо лаконичного интерфейса наружу будет торчать немалых размеров класс.

    Не кажется ли тебе, что всё это капельку усложнит понимание связей в коде и твой бугурт совсем неуместен?
    Возможно, в твоей защите ты сделал невероятно удобную архитектуру, но мы не видим исходников, чтобы это оценить. И не знаем, какие задачи ты решал, чтобы сравнивать, где уместен твой подход, а где - нет.

    И наконец, про возможности C++17. В первую очередь, код должен оставаться валидным на самых новых стандартах (поэтому, формально, судя по /std:c++latest, у меня стоит поддержка черновика C++20). Все возможности стандартов используются там, где они уместны и применимы. Из мелочей - пару раз понадобилось поставить std::data. Но это не значит, что я должен искусственно изобретать ситуации, чтобы хоть где-то поставить какой-нибудь if constexpr, чтобы выпендриться умением в C++17. То, что ты напишешь простыню из шаблонов в идеально выверенном стиле, не сделает твой код понятнее, если то же самое можно заменить парой простых функций. Так что, про нечитабельность и неподдерживаемость - очень спорное утверждение.

    Так с какими "теми же"? Как замена WriteFile на fstream и кусочков процедурности на совершенно чистый ООП их исправит?
    В 5 раз меньше кода для тех же задач, а "всё тот же". Ну блин!
     
    Последнее редактирование: 17 мар 2019
  15. Plasticable

    Plasticable Старожил Девелопер Пользователь

    Баллы:
    173
    Skype:
    plasticable
    Имя в Minecraft:
    Plasticable
    Аффтар жжот пеши ишо.

    Жду использование авн на практике.
     
  16. Автор темы
    HoShiMin

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

    Баллы:
    173
    #1: Защищаемся от создания потоков со стороны

    Для начала, разберёмся, что это такое и зачем от него защищаться.

    Создание потоков с точкой входа на нужном коде - самый распространённый и самый простой способ заинжектить библиотеку или выполнить нужный код в контексте нужного процесса. Этот метод реализован во всех инжекторах, про этот метод пишут во всех статьях о внедрении кода (например, очень хорошие статьи Ms-Rem'a: Часть 1 и Часть 2) - и от него надо защищаться в первую очередь.

    Как происходит внедрение кода:
    Код:
    #include <Windows.h>
    
    int main() {
        CHAR LibPath[] = "C:\\Folder\\Library.dll"; // Путь к дллке, которую хотим подгрузить
        DWORD ProcessId = 1234; // ID целевого процесса
    
        // Открываем целевой процесс:
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
    
        // Получаем адрес функции LoadLibrary, которую выполним в чужом процессе:
        PVOID pLoadLibrary = GetProcAddress(GetModuleHandle(L"kernel32"), "LoadLibraryA");
    
        // Выделяем в целевом процессе память под путь к дллке:
        PVOID pRemoteLibPath = VirtualAllocEx(
            hProcess,
            NULL,
            sizeof(LibPath),
            MEM_RESERVE | MEM_COMMIT,
            PAGE_READWRITE
        );
    
        // Записываем путь в память процесса:
        DWORD Written = 0;
        WriteProcessMemory(hProcess, pRemoteLibPath, LibPath, sizeof(LibPath), &Written);
    
        // Создаём в процессе поток, который подгрузит нашу DLL:
        HANDLE hThread = CreateRemoteThread(
            hProcess,
            NULL,
            0,
            (LPTHREAD_START_ROUTINE)pLoadLibrary,
            pRemoteLibPath,
            NULL
        );
    
        // Ждём завершения потока:
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
    
        // Освобождаем выделенную под путь память:
        VirtualFreeEx(hProcess, pRemoteLibPath, 0, MEM_RELEASE);
    
        CloseHandle(hProcess);
    }
    
    Что здесь происходит: мы выделяем в процессе память, записываем туда путь к дллке и создаём поток с точкой входа на LoadLibrary, передав ему, как параметр, выделенную память с путём.
    Этот трюк работает, потому что основные системные библиотеки (в том числе, нужная нам kernel32.dll) загружаются во все процессы по одинаковым адресам. Соответственно, адреса функций в них совпадают во всех процессах. Поэтому, получив адрес LoadLibrary в нашем процессе, мы можем создать поток по этому же адресу в другом процессе, и тоже попадём на LoadLibrary. А так как сигнатуры LPTHREAD_START_ROUTINE и LoadLibrary совпадают, мы получаем успешный инжект.

    Чтобы понять, как от этого защищаться, подумаем, как происходит создание потоков и что происходит с потоком с момента создания его ядром.

    Поток начинает исполнение в юзермоде с точки входа LdrInitializeThunk, расположенной в ntdll.dll, и, после базовой инициализации, прыгает на RtlUserThreadStart с помощью функции NtContinue, откуда потом прыгает на заданную, уже пользовательскую, точку входа. Любые функции создания потоков сводятся к двум системным - NtCreateThread и NtCreateThreadEx. Таким образом, перехватив их две и LdrInitializeThunk, мы узнаем, запущен ли поток нашим процессом, или создан со стороны: осталось лишь в хуках NtCreateThread[Ex] как-то запомнить новый поток, а в хуке LdrInitializeThunk проверять, есть ли текущий поток в списке ранее созданных.

    Подводные камни: в Windows 7 появился системный пул потоков и API для работы с ним. Система создаёт потоки в пуле в обход NtCreateThread[Ex]. При инициализации процесса система создаёт дефолтный пул с неэкспортирующейся точкой входа TppWorkerThread в ntdll.dll.

    upload_2019-3-17_2-14-18.png

    Создание этого пула происходит на ранних этапах инициализации процесса функцией NtCreateWorkerFactory, куда передаётся адрес TppWorkerThread. Чтобы предотвратить ложные срабатывания, необходимо перехватить NtCreateWorkerFactory и запоминать все передаваемые ей точки входа, чтобы в проверке в обработчике хука LdrInitializeThunk считать их безусловно валидными, а также, безусловно добавить TppWorkerThread в список валидных точек входа.

    upload_2019-3-17_2-16-19.png

    Т.к. TppWorkerThread не экспортируется, сделаем небольшой трюк. Мы видим, что NtCreateWorkerThread вызывается из функции TpAllocPool -> TpAllocPoolInternal и в качестве параметра ему всегда передаётся адрес TppWorkerThread. TpAllocPool экспортируется из ntdll. Итак, всё, что нам нужно - самим вызвать TpAllocPool, в обработчике NtCreateWorkerThread запомнить адрес TppWorkerThread и вернуть любой статус ошибки (например, STATUS_UNSUCCESSFUL), не вызывая оригинальную функцию. В этом случае TpAllocPool не станет создавать новый пул и корректно закроет все открытые хэндлы.
     
    Последнее редактирование: 17 мар 2019
  17. Gravit

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

    Баллы:
    66
    В этом нет ничего страшного, ничего того, что стоит избегать.
    Именно это и нужно. Наружу должен торчать не "лаконичный интерфейс" как Вы его назвали, а полноценный класс. Класс, решающий возложенные на него обязанности, а не горстка функций, пусть и объеденная пространством имен.
    Использование extern необходима не для всех "модулей" этой библиотеки. Т.к. например для Logger extern нужен, а для MemoryFilter - нет.
    Все классы защит, вроде MemoryFilter, ThreadsFilter, HWID и всех всех новых функций защиты должны быть доступны из главного класса вашей библиотеки - Avanguard. А вот уже класс Avanguard инициализировать extern'ом. Так получается понятная, выверенная система, где всегда видно какие виды защит вообще существуют, сразу видны все их методы, а так же все общие методы библиотеки. Класс Avanguard - единственное место, где должны собраться все модули одновременно. Это не иделаьная архитектура библиотеки, и быть может, есть более корректная. Но даже она куда логичнее вашей.
    В Вашем подходе каждый элемент существует сам по себе, нет никакой структурированности.

    Если вы не используете в должной мере возможности C++17 - не стоит писать, что проект на C++17. Этим вы вводите в заблуждение, и, прочитав вашу рекламную брошурку, люди поверят, что этот код - вершина современного C++. Что конечно же не является правдой. Хуже того, по вашим гайдам(которые вы планируете писать) начнут учиться, и, дальше лучше не продолжать. Ничего хорошего не выйдет.
    Назвались эталонной реализацией - используйте возможности C++17 по максимуму. Назвались писать гайды и объяснять как что устроено для хотя бы средних обитателей этого форума - лучше использовать более привычные средства, такие как iostream/fstream. Иначе не поймут.
    Назвались OpenSource проектом - позаботьтесь о других разработчиках: сделайте код более понятным и легким для чтения.
    Не хотите использовать iostream/fstream по какой то причине - ну сделайте понятную обертку. Ничего из этого сейчас нет.

    Архитектура проекта не поменялась. Тот же прием с CFFExplorer и APC, тот же самый AvnAPI, тот же самый ужас из голых функций, то же самое отсутствие связанности между компонентами. Поменялись лишь детали реализации.

    Не верите мне - опубликуйте гайды на таком ресурсе как Хабр. Толковых статей нет - конкуренции с вашей никакой. С простынями вашего кода, разумеется, и "C++17" в заголовке. Подробно объясняя почему вы юзаете printf/WriteFile в C++17 в 2019 году. И тогда Вы может быть поймете, что голые функции и C++17 это несовместимые вещи.
     
  18. Автор темы
    HoShiMin

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

    Баллы:
    173
    #2: Защищаемся от AppInit_DLLs

    Второй по популярности способ инжекта - не требует написания кода и осуществляется штатными средствами Windows.

    Смотрим официальную документацию и видим, что система подгружает в каждый процесс список библиотек, указанных в реестре в ключе HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs:
    upload_2019-3-17_16-8-13.png

    Судя по документации, подгрузка происходит в момент загрузки библиотеки user32.dll, в обработчике DLL_PROCESS_ATTACH. Откроем user32.dll в IDA Pro и попытаемся узнать, как же происходит подгрузка библиотек.

    Нас встречает функция инициализации UserClientDllInitialize. Она большая и сложная, поэтому попробуем наудачу найти в библиотеке что-то со словами "AppInit". В списке функций такого сочетания нет, в экспортах тоже, а вот в импортах...
    upload_2019-3-17_16-30-51.png
    Похоже, именно то, что нам нужно. Жмём на X и убеждаемся, что она вызывается из функции ClientThreadSetup, на которую есть ссылка в InitializeCsrUserPfn, а она, в свою очередь, вызывается из UserClientDllInitialize - из той самой инициализации user32.dll, описанной в документации.

    Осталось её найти и перехватить. Однако, библиотеки "api-ms-win-core-appinit-***" нет на диске. Это виртуальная библиотека (а точнее, ApiSet), которая ресолвится в одну или несколько физических библиотек. Можно найти готовые ресолверы этих имён в сети, но, скорей всего, в данном случае, этот аписет ссылается на kernel32 или kernelbase.

    Откроем kernel32.dll в IDA Pro, сразу же идём в экспорты, вбиваем "LoadAppInitDlls", и находим:
    upload_2019-3-17_16-39-53.png

    Осталось лишь её перехватить и отменить выполнение. Сделать это на основе HookLib чрезвычайно просто:
    upload_2019-3-17_16-44-28.png

    Небольшое замечание: так как LoadAppInitDlls выполняется при загрузке user32, перехватывать нужно до этого момента. Чтобы наша библиотека загрузилась раньше, чем user32, она должна стоять в таблице импортов выше, чем user32, и этот перехват должен выполняться в DllMain. Данный макрос обеспечивает выполнение в DllMain, а убедиться в том, что ваша библиотека загружается раньше, чем user32, вы должны сами. Например, с помощью CFF Explorer. И, разумеется, ваша библиотека не должна сама зависеть от user32.
     
  19. zibranki

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

    Баллы:
    61
    BTW не слушай гравита, он не программист, скорее среднего уровня джуниор-говнокодер. Открывать исходники его лаунчера тоже не рекомендую)
     
  20. Nikolai_Faint

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

    Баллы:
    96
    Додумалась*
     

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