Как внедрить dll через хакер процессов
Обновлено: 21.11.2024
Недавно я участвую в проекте, который требует от меня написания кода на C/C++. Поскольку мой C++ очень ржавый, я попытался его немного подточить, выполнив эти небольшие задачи по разработке. Поскольку я также занимаюсь реверсированием кода с использованием методов внедрения DLL, я подумал, что было бы неплохо лучше понять внедрение DLL, написав несколько инжекторов самостоятельно. Я начну с простейшей классической инъекции DLL через вызов LoadLibraryA через CreateRemoteThread.
Немного теории
Давайте быстро объясним, как работает традиционный метод внедрения DLL. Что такое DLL-инъекция? В общем случае внедрение кода происходит, когда один процесс запускает код в адресном пространстве другого процесса. Внедрение DLL — это особый подмножество этих методов, когда процесс вынужден загружать и выполнять внешнюю DLL. Причин для выполнения инъекции DLL может быть много, многие приложения делают это по вполне законным причинам (например, антивирусы, но не только). Это также очень распространенный метод для вредоносных программ, обычно используемый либо для сокрытия кода в другом общем процессе, либо для перехвата некоторых функций в другом процессе для кражи информации. Есть несколько методов внедрения DLL, которые также можно использовать для сохранения или повышения привилегий, но мы не будем о них здесь говорить.
Классическое внедрение DLL использует несколько вызовов Windows API для выполнения внедрения. Сначала необходимо выделить некоторую память в целевом процессе. Это можно сделать с помощью вызова VirtualAllocEx. Когда память выделена, мы хотим записать в нее наш внедренный DLL-путь. Это делается с помощью вызова WriteProcessMemory. Когда путь DLL записывается в память процесса-жертвы, мы хотим вызвать функцию LoadLibraryA в контексте нашего процесса-жертвы. Это можно сделать, создав новый поток в целевом процессе с помощью вызова CreateRemoteThread. Указатель LoadLibraryA передается CreateRemotethread вместе с указателем на место в памяти, где хранится путь к DLL. Вот и все. Теперь наша жертва загрузит в память вредоносный бинарник и выполнит его.
Короче говоря, для внедрения DLL в другой процесс необходимо выполнить следующие шаги:
- Хранить вредоносную DLL на диске
- Найти идентификатор целевого процесса
- Выделить память в целевом процессе с помощью VirtualAllocEx
- Запишите путь к DLL в память с помощью WriteProcessMemory
- Найти адрес памяти LoadLibraryA с помощью GetProcAddress
- Создайте удаленный поток в процессе-жертве, используя вызов CreateRemoteThread с указателем на LoadLibraryA и другим указателем на путь DLL, хранящийся в памяти, в качестве аргумента
Вот и все. Это самый простой способ внедрить DLL в другой процесс. Есть конечно и минусы. Самое главное — это необходимость хранить DLL на диске перед его внедрением — это может привести к срабатыванию антивирусного программного обеспечения, а также оставить дополнительные артефакты на диске. Если мы хотим избежать этого, нам нужно использовать другой метод внедрения DLL (например, рефлексивное внедрение DLL, о котором я расскажу в отдельной статье).
Давайте программировать!
Итак, мы уже знаем теорию, но теорию может быть немного сложно понять, не пытаясь реализовать ее самостоятельно.
DLL-код
Прежде всего нам понадобится некоторая DLL для внедрения в процесс-жертву. Я выбрал очень простую DLL, которая просто выводит окно сообщения с именем текущего запущенного процесса. Это даст нам четкое представление об успешной инъекции.
Это довольно простая DLL. Он состоит только из DllMain (строка 6), которая является основной функцией библиотеки DLL. Он не объявляет каких-либо экспортируемых функций (что обычно делают легитимные библиотеки DLL). Код DllMain выполняется сразу после загрузки DLL в память процесса. Это важно в контексте DllInjection, поскольку мы ищем простейший способ выполнения кода в контексте другого процесса. Вот почему большинство внедряемых вредоносных библиотек DLL содержат большую часть вредоносного кода в DllMain. Есть способы заставить процесс запускать экспортированную функцию, но написание кода в DllMain обычно является самым простым решением для выполнения кода.
Наша DllMain довольно проста. Он использует GetProcessImageFileNameW вместе с GetCurrentProcess API для получения имени текущего запущенного процесса. Затем он отображает полученное значение во всплывающем окне MessageBox. При запуске внедренного процесса он должен отображать имя процесса-жертвы, поэтому мы будем знать, что внедрение прошло успешно. Теперь мы можем скомпилировать его и поместить в каталог по нашему выбору.
Код инжектора
Теперь нам нужен только код, который внедрит эту библиотеку в выбранный нами процесс. У нас уже есть рецепт для этого несколькими абзацами выше, так что давайте подготовим код.
Строка 8: Здесь мы выполняем функцию, чтобы найти процесс-жертву.Мы также можем создать новый процесс для внедрения, но в этом случае мы просто пройдемся по списку запущенных процессов. Список функций ниже
Он создает снимок текущих запущенных процессов с помощью вызова API CreateToolhelp32Snapshot (строка 8), а затем выполняет итерацию по списку структур PROCESSENTRY32 с помощью вызова Process32Next (строка 21). Если имя исполняемого файла (szExeFile) совпадает с искомым (в данном случае «notepad.exe»), он возвращает свой идентификатор процесса.
Строка 9: Получив PID процесса-жертвы, нам нужно открыть его дескриптор. Мы делаем это, передавая только что полученный PID в вызов OpenProcess. Это вернет дескриптор (hProcess) открытого процесса, который позволит нам выполнять дальнейшие операции над этим процессом.
Строка 10: теперь у нас открыт процесс-жертва, давайте начнем внедрение. Как мы помним из теоретического раздела, первым шагом для выполнения внедрения является выделение некоторой виртуальной памяти процесса. Мы выполняем эту задачу, используя вызов VirtualAllocEx. Он принимает 5 параметров:
- Дескриптор открытого процесса: мы получили его на предыдущем шаге — это наша переменная hProcess
- Указатель, указывающий желаемый начальный адрес области страниц, которую вы хотите выделить. Лучше всего оставить NULL, чтобы Windows могла определить лучший адрес для выделения.
- Размер выделяемой области памяти в байтах. Это равно длине строки, которую мы хотим выделить.
- Тип выделения памяти. Нам нужно, чтобы бот зарезервировал и зафиксировал область памяти (чтобы иметь возможность писать в нее), поэтому MEM_COMMIT | MEM_RESERVE
- Защита памяти для выделяемой области страниц. PAGE_EXECUTE_READWRITE означает, что этот регион можно читать, записывать и выполнять.
Строка 11: После того, как мы выделили область памяти, нам нужно записать в нее путь к нашей DLL. Для этого у нас есть еще один вызов Windows API: WriteProcessMemory. Требуется 5 аргументов:
- Дескриптор открытого процесса. Это тот же дескриптор, который мы использовали на предыдущем шаге.
- Указатель на базовый адрес области памяти для записи. Он был возвращен на последнем шаге VirtualAllocEx (если выделение прошло успешно).
- Указатель на буфер, содержащий данные для записи в адресное пространство указанного процесса. Мы передаем переменную dllName, которая является указателем на строку, содержащую путь и имя нашей dll.
- Размер буфера (в данном случае длина нашей строки).
- Указатель на переменную, которая получает количество байтов, переданных указанному процессу. Это не требуется в нашем коде, поэтому мы можем оставить его равным NULL.
К этому моменту мы уже должны были записать нашу строку в память процесса-жертвы. Мы можем проверить это, установив точку останова в строке 11 и запустив нашу программу в режиме отладки (сначала откройте notepad.exe, если вы нацелились на него).
После того, как наша программа остановилась в точке останова, мы можем увидеть в переменных BaseAddress выделенную память. В данном случае это:
Если мы сейчас перейдем один раз (F10 в Visual Studio), эта память должна быть перезаписана нашим путем к DLL. Мы можем проверить это, подключив отладчик к notepad.exe или просто просмотрев эту область памяти в Process Hacker
Значит, память удаленного процесса успешно записана. Теперь мы можем перейти к следующему шагу. Внедрение нашей DLL.
Строка 12/13: прежде чем мы, наконец, внедрим и запустим наш код, нам нужен адрес памяти LoadLibraryA, так как это будет вызов API, который мы будем выполнять в контексте процесса-жертвы для загрузки нашей DLL. Чтобы получить текущий адрес LoadLibraryA, нам сначала нужно получить дескриптор библиотеки kernel32.dll, которая экспортирует LoadLibraryA. Мы делаем это с помощью вызова GetModuleHandle (строка 12). После того, как мы получим дескриптор ядра32, мы можем передать его вызову GetProcAddress для получения адреса памяти LoadLibraryA (строка 13).
Строка 14. Самая важная строка кода. Мы вызываем CreateRemoteThread для запуска удаленного потока в контексте нашего процесса-жертвы. Мы передаем 7 аргументов:
- Дескриптор нашего процесса-жертвы. То же, что и раньше.
- Дескриптор безопасности для нового потока можно оставить равным NULL для значений по умолчанию.
- Исходный размер стека. 0 по умолчанию.
- Указатель на функцию, которая будет выполняться потоком. Здесь происходит вся магия. Мы передаем указатель на LoadLibraryA, чтобы эта функция выполнялась в контексте процесса-жертвы.
- Указатель на переменную для передачи в функцию потока. Мы передаем указатель на область памяти, содержащую путь к нашей DLL. Таким образом, LoadLibraryA будет выполняться с путем к нашей библиотеке в качестве параметра.
- Флажки создания.Мы используем 0 для немедленного запуска потока. Для некоторых методов внедрения кода (Process Hollowing, о котором я надеюсь рассказать в отдельной статье в будущем) требуется, чтобы это было установлено как CREATE_SUSPENDED, что создаст поток в приостановленном состоянии. CREATE_SUSPENDED также можно использовать для отладки проблем с новым потоком. Таким образом, мы можем создать новый приостановленный поток, подключить к процессу отладчик, установить точку останова и возобновить поток. В этом случае мы просто устанавливаем 0 для немедленного запуска потока.
- Указатель на переменную, которая получает идентификатор потока. В этом случае не требуется, поэтому его можно установить как NULL.
Итак, это весь код инжектора. Довольно просто, верно? Давайте посмотрим, как (если) это работает.
Давайте внедрять!
Итак, наконец, после того, как мы поняли весь код инжектора, мы можем протестировать его. Давайте сначала запустим экземпляр notepad.exe, а затем выполним нашу программу.
Кажется, это сработало. Чтобы убедиться, что наша DLL действительно внедрена в процесс notepad.exe, мы можем использовать Process Hacker.
Кажется, наша простая инъекция сработала! Это всего лишь самый простой способ внедрить DLL в другой процесс, но во многих случаях этого достаточно и он очень полезен. Однако у него есть свои недостатки, поэтому в следующих статьях я попытаюсь объяснить более продвинутые методы внедрения кода.
Привет, девочки и небинарные приятели, сегодня мы рассмотрим программу под названием OverWolf. Overwolf — это программное обеспечение, поддерживаемое Curse Forge, Intel, Logitech, Twitch, Ubisoft и рядом других компаний, которое предназначено для добавления расширений (или модов) в игры. Некоторые примеры могут включать наложение, которое показывает ваше здоровье или количество боеприпасов в другом стиле. Но да - суть вы поняли. Это довольно интересное программное обеспечение для анализа.
Мы будем использовать довольно стандартную настройку ProcMon от SysInternals и ProcessHacker.
Это довольно стандартная установка, поэтому я не буду вдаваться в подробности процесса установки. Просто продолжайте нажимать «Далее», пока не увидите, что установка OverWolf завершена.
Анализ Overwolf
Сначала мы откроем ProcMon и добавим Process Filter — мы будем фильтровать в командной строке и убедиться, что она содержит строку Overwolf. Это, скорее всего, даст нам наилучшие результаты.
После этого нажмите «Добавить» и «Применить». Затем откройте Process Hacker и используйте функцию «Поиск», чтобы указать Overwolf. Я разделил экраны для удобства использования:
Затем мы запустим Overwolf и посмотрим, что произойдет.
При правильной настройке мы должны увидеть множество новых процессов, появляющихся в Process Hacker, а ProcMon должен увидеть, как вызывается целый ряд новых функций WindowsAPI, а также запускаются программы.
Окно Process Hacker было довольно стандартным. Следует отметить одну важную вещь: средство обновления появилось как NT AUTHORITY\SYSTEM. Это очень важно учитывать, если вы разрабатываете эксплойт для повышения локальных привилегий. Если вы захватите трафик с помощью Wireshark и сможете расшифровать протокол обновления, возможно, вы сможете разработать эксплойт LPE. Я не собираюсь сегодня копаться в этой кроличьей норе :).
Здесь мы в первую очередь ищем библиотеки DLL или программы, которые загружаются как часть запуска OverWolf. Просто просматривая запущенные процессы, особенно выделялся один из них, OverWolfHelper/OverWolfHelper64.exe, он запрашивал информацию об имени процесса «Audiodg.exe», собственном двоичном файле Windows. Я нашел его в ProcMon, однако его также можно найти в Process Hacker.
Находится в ProcMon
Расположенный в процессе хакер
Интересно, что OverwolfHelper принимает два аргумента — путь и идентификатор. Это (для меня) довольно интересные аргументы, особенно когда путь указывает на DLL.
Проведя базовый анализ, мы видим, что PID фактически указывает на родительский процесс Overwolf. Интересно, что он (OWExplorerLauncher.dll) не загружается. Однако в OverwolfHelper.exe это так.
Внедрение DLL
Давайте попробуем внедрить DLL с помощью OverWolfHelper и посмотрим, что получится. В Kali мы создадим DLL с помощью MSFVenom.
После переноса бинарного файла мы запустим измененную команду:
Проверив терминал, оказалось, что мы получили оболочку!
Чтобы выяснить, что происходит за кулисами, мы можем вернуться к ProcessHacker, перейти на вкладку «Сеть» и отфильтровать наш IP-адрес (192.168.0.198). Похоже, что он косвенно вызывает rundll32.exe
Похоже, что rundll32.exe породил дочерний процесс cmd.exe, который, вероятно, является довольно важным индикатором вредоносного ПО. Я создал каталог под названием «notmalware» в своем сеансе Netcat и записал его в эту папку
Похоже, что дочерний процесс действительно является нашей обратной оболочкой.
Заключение
Похоже, что OverwolfHelper (когда он загружает DLL) запускает rundll32 — что интересно, наша DLL не упоминается в качестве аргумента.
Но это не сработало, у меня были команды, которые работали 2 дня назад, но я их стер.
Может ли кто-нибудь помочь, поделившись кодами, которые работают?
Я думаю, что вы можете получить безопасные форсунки от gamebanana или mpgh, но я так давно не возился с теми, что не могу точно вспомнить.
Раньше они работали в играх по локальной сети, так что я развлекался, разозлив одноклассников
@Korishi
Да, я знаю, что в Интернете есть другие инжекторы dll, но большинство из них не принимают никаких команд cmd.
Я знаю, что это можно сделать с помощью process hacker, но я стер команду, которую использовал
Взлом в видеоиграх? ^___-
Что ты делаешь?
Да, я хочу внедрить хакерскую dll в Counter Strike с помощью командной строки, подобной этой
ProcessHacker.exe -c -ctype process -cobject 768 -caction injectdll -cvalue C:\path\example.dll
/>ProcessHacker.exe -c -ctype process -cobject explorer.exe -caction injectdll -cvalue C:\path\example.dll
Но эти коды не сработали
Да, я хочу внедрить хакерскую dll в Counter Strike с помощью командной строки, подобной этой
ProcessHacker.exe -c -ctype process -cobject 768 -caction injectdll -cvalue C:pathexample.dll
ProcessHacker .exe -c -ctype процесс -cobject explorer.exe -caction injectdll -cvalue C:pathexample.dll
Но эти коды не сработали
Нет, я хотел знать, взламывали ли вы его, чтобы разблокировать дерьмо, спрятанное за гриндом (респектабельным), или присоединиться к ордам скучных читеров, которые загрязняют онлайн-видеоигры своими дерьмовыми аимботами/wallhacks/autofire, чтобы почувствовать сильнее других (что постыдно)
Да, я хочу внедрить хакерскую dll в Counter Strike с помощью командной строки, подобной этой
ProcessHacker.exe -c -ctype process -cobject 768 -caction injectdll -cvalue C:pathexample.dll
ProcessHacker .exe -c -ctype процесс -cobject explorer.exe -caction injectdll -cvalue C:pathexample.dll
Но эти коды не сработали
Нет, я хотел знать, взламывали ли вы его, чтобы разблокировать дерьмо, спрятанное за гриндом (респектабельным), или присоединиться к ордам скучных читеров, которые загрязняют онлайн-видеоигры своими дерьмовыми аимботами/wallhacks/autofire, чтобы почувствовать сильнее других (что постыдно)
Я понял, как это сделать
ProcessHacker.exe -c -ctype process -cobject hl.exe -caction injectdll -cvalue "D:\CS\MYDLL.dll"
Проблема заключалась в том, что я добавлял только имя dll вместо полного пути
Я не думаю, что это был массовый взлом сайта. Я имею в виду, кто захочет взломать такой форум? Здесь есть всякие странные темы. То есть, если посмотреть на ситуацию с логической точки зрения, так называемый хакер ничего от этого не выиграет. Я тоже сомневаюсь, что это прикол модераторов. Может глюк был или что? Мы все получили одну и ту же аву в один и тот же день, верно? Или какой-то наемный хакер решил взломать этот форум просто для развлечения.
Эта статья содержит функции и возможности, которые не задокументированы первоначальным производителем. Следуя советам из этой статьи, вы делаете это на свой страх и риск. Методы, представленные в этой статье, могут зависеть от внутренней реализации и могут не работать в будущем.
Этот пост будет больше похож на видеоблог, чем на что-либо еще. Я потратил более недели на запись экрана во время кодирования драйвера Windows в Visual Studio. Видео должно показать, как можно внедрить тестовую DLL во все запущенные процессы в Windows 10. Я записал, как кодирую ее от начала до конца, так что это должна быть довольно полная демонстрация. или дремать. 😁
В процессе я также понял, что кодировать сложные алгоритмы и говорить одновременно непросто, и то, что получается, не всегда совпадает с тем, что я хотел бы сказать, если бы у меня было время подумать об этом 😂 Итак , я оговорился в нескольких местах там. Поэтому, пожалуйста, будьте ко мне снисходительны, если будете смотреть все это.
И, если вы не любите читать сообщения в блогах и хотите начать просмотр самого видеоурока, проверьте плейлист. Кроме того, вы можете просто загрузить только исходный код.
Наконец, позвольте мне сказать, что если вы испортите свою производственную ОС, неправильно применив то, что я здесь показал, это будет полностью на вашей совести. Не вините меня позже!
Прежде всего, я хочу выразить признательность Rbmm за то, что он поделился своим исходным кодом, на котором основано мое решение. Пожалуйста, дайте ему реквизиты в его репозитории GitHub.Он является первоначальным автором большинства концепций, которые я изложу здесь в своей длинной видео-презентации.
Я не буду вдаваться во все мельчайшие детали в этом сообщении в блоге, которое я освещал при записи своего руководства. Но напомню, как работает процесс внедрения во все запущенные процессы в Windows:
- Мы напишем драйвер ядра для установки нашего обратного вызова, который будет вызываться, когда модуль (или DLL) сопоставляется с процессом. Мы можем сделать это с помощью функции PsSetLoadImageNotifyRoutine.
- Зная последовательность загрузки библиотек DLL в Windows, а именно: сначала у нас есть ntdll.dll, который загружается в любой процесс пользовательского режима, а затем kernel32.dll, который загружается во все несобственные процессы. Таким образом, если мы перехватим в нашем обратном вызове момент загрузки kernel32.dll, мы сможем внедрить перед ним собственную DLL.
Для развлечения мы назовем нашу DLL, которую мы будем внедрять во все процессы, FAKE.DLL . И чтобы обозначить его разрядность, фактический файл будет называться FAKE64.DLL или FAKE32.DLL. Он мало что сделает, кроме того, что просто запишет в файл журнала дату и время и процесс, в который он был внедрен.
Способ внедрения накладывает ограничения на нашу FAKE.DLL, поскольку она не может полагаться на импорт из каких-либо DLL, кроме ntdll.dll . Сюда входит C-Runtime (или CRT) и большинство стандартных библиотек C++.
Обратите внимание, что это не обход мер безопасности в Windows, поскольку мы используем драйвер ядра для нашего решения.
- Мы откроем нашу FAKE.DLL и создадим из нее раздел KnownDll в обратном вызове функции PsSetLoadImageNotifyRoutine. Мы должны иметь в виду, что обратный вызов будет выполняться из критической секции, и поэтому мы мало что можем с ним сделать. Таким образом, мы только быстро поставим в очередь APC ядра, используя функции KeInitializeApc/KeInsertQueueApc.
- В наших обратных вызовах APC мы пропустим подпрограмму KernelRoutine, поскольку она будет выполняться с уровнем IRQL APC_LEVEL.
- Но внутри подпрограммы NormalRoutine (которая будет работать с IRQL PASSIVE_LEVEL) мы сопоставим наш специальный шелл-код, не зависящий от базы, с целевым процессом и поставим в очередь APC пользовательского режима, который будет его вызывать.
Мы напишем наш шелл-код на языке ассемблера, что позволит ему быть независимым от базы, а это означает, что он не потребует перемещений и может запускаться с любого адреса в памяти.
Это краткий обзор метода внедрения, в котором я опускаю особенности работы с процессами WOW64 (или 32-разрядными процессами, работающими в 64-разрядной операционной системе) и другие важные детали, которые я подробно рассмотрел в своем видеообзор.
В видеоруководстве также рассматриваются аспекты тестирования драйвера на виртуальной машине и создания отдельного тестового проекта C++ для отладки внедренной FAKE.DLL.
Обратите внимание, что ниже приведен плейлист из нескольких последовательных видеороликов, в которых я покажу вам процесс кодирования от начала до конца. Я бы рекомендовал просматривать их последовательно и воспроизводить их в полноэкранном режиме, чтобы убедиться, что вы видите код:
Временные коды видео
Или следующие фрагменты руководства с временным кодом, которые откроются в проигрывателе YouTube:
-
:
-
- Настройка виртуальных машин для запуска тестов драйверов. - Настройка компонентов Visual Studio, необходимых для кодирования нашего проекта. - Настройка инструментов в виртуальной машине:
-
- Process Hacker - для просмотра запущенных процессов и модулей. - DebugView - для просмотра результатов отладки нашего драйвера. - WinObj - для просмотра объектов пространства ядра. - PEInternals - для статического просмотра PE-файлов. - WERSetup — для настройки отчетов об ошибках Windows для обнаружения сбоев процессов пользовательского режима. - Поиск WinAPI - для проверки импорта/экспорта из PE-файлов и поиска кодов ошибок. - Driver Loader/Unloaded - для регистрации, запуска, остановки и отмены регистрации нашего драйвера.
-
- Кредит Rbmm. - Резюме того, как мы будем внедрять нашу FAKE.DLL во все процессы: ntdll.dll, kernel32.dll, без CRT, использовать CFG, ядро APC. - Приступаем к кодированию: Создание решения под названием "InjectAll". - Запуск проекта драйвера WDM для Windows с именем "Drv". - Добавлен DrvMain.cpp. - Добавление DrvTypes.h. - Добавление SharedDefs.h. - Добавлен класс CFunc. - Добавлена функция DriverEntry. - Установка правильного Windows SDK и WDK. - Установка (борьба с) Spectre-митигированных библиотек для Visual Studio.- Решение проблемы с отсутствующими библиотеками Spectre. - Исправление первоначальных проблем с созданием драйвера. - (Ошибочно) Удаление тестовой подписи при сборке драйвера. - Кодирование макроса DbgPrintLine. - Кодирование программы DriverUnload. - Тестирование нашей первой сборки драйвера. — Добавление тестовой подписи для сборки драйвера в Visual Studio. - Удалось запускать и останавливать нашу первую сборку драйвера!
-
- Кодирование основных объектов ввода драйвера. - Настройка обратного вызова PsSetLoadImageNotifyRoutine. - Настройка обратного вызова OnLoadImage. - Кодирование функции FreeResources(). - Кодирование оператора для обнаружения загрузки kernel32.dll. - Кодирование функции CFunc::IsSuffixedUnicodeString(). - Определение макроса STATIC_UNICODE_STRING. - Кодирование функции CFunc::IsMappedByLdrLoadDll(). - Кодирование функции CFunc::IsSpecificProcessW(). — Определение того, есть ли у нас процесс WOW64, IoIs32bitProcess. - Запуск еще одного теста драйверов того, что мы создали до сих пор.
-
- Краткий обзор того, что мы сделали до сих пор. - Настройка класса CSection. - Настройка структуры DLL_STATS. - Объявление перечисления SECTION_TYPE. - Кодирование функции CSection::Initialize(). - Кодирование одноэлементной функции CSection::GetSection() с использованием функций RtlRunOnceBeginInitialize/RtlRunOnceComplete. - Объяснение Code Integrity Guard (CIG) и того, как это может повлиять на наше внедрение DLL. - Подробности о KnownDlls. - Использование PsInitialSystemProcess для подключения к системному процессу. - Определение макроса TAG отладки для функций ядра. - Продолжаем кодировать функцию CSection::GetSection().
-
- Исправление предыдущей ошибки в функции CSection::GetSection(). - Кодирование функции CSection::FreeSection(). — Добавление директивы препроцессора DBG_VERBOSE_DRV для подробного вывода отладки. — Добавлен код для вызова функции CSection::FreeSection(). - Начинаем кодировать функцию CSection::CreateKnownDllSection(). - Настройка "украсть" дескриптор безопасности из существующей KnownDll - kernel32.dll. - Открытие существующего раздела kernel32.dll. - Тестирование текущей сборки драйвера. — Добавлен код для вызова функции CSection::GetSection(). - Повторное тестирование текущей сборки драйвера. - Возвращаясь к кодированию функции CSection::CreateKnownDllSection(). — Получение дескриптора безопасности из раздела kernel32.dll с помощью ZwQuerySecurityObject. - Описание объекта раздела OBJ_PERMANENT. - Дифференциация имен разделов Fake.dll для KnownDlls. - Выделение памяти под дескриптор безопасности из раздела kernel32.dll.
-
- Добавлен новый проект C++ - FAKE.dll. - Пересмотр ограничений внедрения нашей DLL в процесс: ntdll.dll, kernel32.dll. - Добавлен новый файл DllTypes.h. — Удаление C-Run-Time (CRT) из нашей FAKE.dll для 64-битной сборки. - Добавлен файл Exports.def. — Добавлен файл loadcfg.c для включения Control Flow Guard (CFG) для нашей FAKE.dll. - Добавление в него файла loadcfg64.asm и сборки x64 для CFG. — Удаление C-Run-Time (CRT) из нашей FAKE.dll для 32-битной сборки. - Кодирование файла loadcfg32.asm с сборкой x86 в него для CFG. — Добавлена функция LogToFile() с использованием собственных функций из ntdll.dll. - Добавлена функция LogToFileFmt(). — Добавлен код в DllMain() для запуска, когда наша DLL внедряется в процесс.
-
- Создание проекта TestConsole. - Написание тестового кода для вызова DllMain в нашей FAKE.DLL. - Способы отладки DLL с помощью проекта TestConsole. — Добавлен код для получения указателя на TEB в DllMain. - Кодирование функции Get_TEB(). - Кодирование функции Get_PEB(). - Добавление кода в наш DllMain для вывода отладки: идентификатор процесса, путь к образу процесса, текущее время только с ntdll.dll. - Тестирование нашего FAKE.DLL в TestConsole с отладочным выводом. - Объяснение, почему нам нужно настроить дескриптор безопасности для папки InjectAll для доступа из любого процесса. — Добавлена функция отладки SetDS_InjectAllFolder(). - Запуск нашей TestConsole с функцией SetDS_InjectAllFolder() для настройки дескриптора безопасности в папке InjectAll.
-
- Продолжаем кодировать функцию CSection::CreateKnownDllSection(). - Открытие нашего файла FAKE.DLL с помощью ZwOpenFile. - Создание раздела из нашего FAKE.DLL с помощью ZwCreateSection. - Заполнение нашей DLL_STATS информацией о созданном разделе. — Получение указателя на объект нашего раздела с помощью ObReferenceObjectByHandleWithTag. - Настройка функции CSection::FreeSection() для удаления нашего раздела. — Настройка функции CSection::CreateKnownDllSection() для корректного закрытия постоянной секции в случае ошибки. - Тестирование текущей сборки драйвера и двух разрядностей FAKE.DLL на тестовой ВМ. - Работа с ошибкой 0xC0000035 во время тестирования. - Исправлена ошибка с отсутствующим вызовом функции CSection::Initialize(). - Настройка вывода отладки sectionType, чтобы сделать его более читабельным после изменения, выполнив некоторый рефакторинг. - Проверка правильности настройки дескриптора безопасности в папке InjectAll.
-
- Добавление ресурса версии в наш FAKE.DLL. - Объяснение, почему нам нужно использовать асинхронные вызовы процедур (APC) из обратного вызова нашего драйвера.- Быстрый обзор ядра APC KernelRoutine, NormalRoutine, RundownRoutine. — Добавлена функция CSection::InjectDLL(). - Краткий обзор того, почему нам нужно выделять из NonPagedPool при постановке в очередь KAPC. — Кодирование организации очереди ядра APC с помощью KeInitializeApc. - Использование счетчика ссылок на наш объект драйвера и объект раздела для предотвращения проблем при постановке APC в очередь. — Вставка ядра APC с помощью KeInsertQueueApc. - Объяснение того, как правильно разыменовать объект драйвера из подпрограмм APC. Почему я пишу это, используя инструкцию JMP на языке ассемблера. — Добавление файлов asm64.asm и asm32.asm для заглушек обратного вызова APC. - Кодирование заглушки обратного вызова RundownRoutine APC в сборке x64. - Кодирование процедуры обратного вызова RundownRoutine_Proc() на C++. - Подробная информация об использовании префикса __imp_ для импортированных вызовов функций из кода сборки. - Кодирование заглушки обратного вызова KernelRoutine APC в сборке x64. - Кодирование процедуры обратного вызова KernelRoutine_Proc() на C++. - Объяснение переадресации параметров вызова функции в стеке внутри функции KernelRoutine, написанной на ассемблере x64. - Кодирование заглушки обратного вызова NormalRoutine APC в сборке x64. - Код процедуры обратного вызова NormalRoutine_Proc() на C++.
-
- Резюме того, что мы закодировали на ассемблере x64 до сих пор. - Начинаем кодировать asm32.asm x86 Сборочный файл. — Кодирование заглушки обратного вызова RundownRoutine APC в сборке x86. - Объяснение переадресации параметров вызова функции в стеке внутри функции RundownRoutine, написанной на ассемблере x86. — Кодирование заглушки обратного вызова KernelRoutine APC в сборке x86. — Объяснение переадресации параметров вызова функции в стеке внутри функции KernelRoutine, написанной на ассемблере x86. - Кодирование заглушки обратного вызова NormalRoutine APC в сборке x86.
-
- Причины использования APC для внедрения кода DLL из нашего обратного вызова ядра OnLoadImage. - Кодирование обратного вызова RundownRoutine_Proc(). - Кодирование обратного вызова KernelRoutine_Proc(). - Кодирование обратного вызова NormalRoutine_Proc(). - Объяснение двух типов кода, которые мы будем помещать в нашу FAKE.DLL: Shell-код и DllMain. - Добавление файла dll_asm64.asm с базово-независимым шелл-кодом x64 Assembly в проект FAKE.DLL. - Написание шелл-кода функции UserModeNormalRoutine в базово-независимой сборке x64. - Объяснение того, почему мы не можем использовать импорт из внешних DLL для вызова системных функций в нашем базово-независимом шелл-коде. — Кодирование функции getProcAddrForMod для разрешения экспортированного адреса функции из модуля в независимой от базы x64-сборке. — Завершение кодирования функции UserModeNormalRoutine в базово-независимой сборке x64.
-
- Добавление файла dll_asm32.asm с базово-независимым шелл-кодом x86 Assembly в проект FAKE.DLL. — Повторение функции UserModeNormalRoutine из ассемблерного кода x64. — Кодирование функции getProcAddrForMod для разрешения экспортированного адреса функции из модуля в независимой от базы сборки x86. - Кодирование функции UserModeNormalRoutine в базово-независимой сборке x86. - Кодирование функции getStr_LdrLoadDll() для получения указателя на базово-независимую статическую строку. - Кодирование функции getStr_NtUnmapViewOfSection() для получения указателя на базово-независимую статическую строку. - Настройка функции UserModeNormalRoutine для экспорта с порядковым номером 1 в Exports.def. — Объяснение, как пометить функцию UserModeNormalRoutine для обхода подавления экспорта из CFG. - Кодирование экспортированной функции-заглушки f1() для включения соответствия CFG для функции UserModeNormalRoutine.
-
- Добавление структуры SEARCH_TAG_W для сохранения статической подписи в нашей подделке.dll. - Модификация нашей фиктивной экспортируемой функции f1() для включения статической подписи в структуру SEARCH_TAG_W. - Кодирование функции CFunc::FindStringByTag(). — Настройка функции CSection::CreateKnownDllSection() для получения информации из нашего раздела FAKE.DLL: ZwMapViewOfSection, разрешение порядкового номера 1 для UserModeNormalRoutine, вызов CFunc::FindStringByTag и ZwQuerySection. - Добавление новых членов в DLL_STATS с дополнительной информацией о нашем разделе.
-
- Обзор членов структуры DLL_STATS. - Схема отображения FAKE.DLL в процесс: шелл-код и функции DllMain, PreferredAddress при отображении. - Создание функции CSection::MapSectionForShellCode(), отображающей наш шелл-код. - Написание кода для отображения раздела для шелл-кода в обратном вызове NormalRoutine_Proc(). - Кодирование CFunc::debugGetCurrentProcName() для получения текущего имени образа процесса.
-
- Повторение того, как наш шелл-код будет запускаться из функции UserModeNormalRoutine(). - Схема с объяснением вызова APC ядра для запуска нашего Shell-кода в пользовательском режиме. - Завершение написания обратных вызовов ядра APC: KernelRoutine_Proc() , NormalRoutine_Proc() . — Добавлен код для внедрения DLL в обратный вызов OnLoadImage() через нашу функцию CSection::InjectDLL(). - Сборка и тестирование нашего проекта внедрения только с помощью процесса notepad.exe. - Пример работы со сбоем в пользовательском режиме процесса (notepad.exe), сбор аварийных дампов с помощью WERSetup.— Настройка NormalRoutine_Proc() для обработки внедрения в процессы WOW64 с помощью PsWrapApcWow64Thread. - Тестирование внедрения в процесс WOW64 notepad.exe.
-
- Сборка и тестирование нашего проекта на 32-битной ОС. - Создание проекта для внедрения во все процессы. - Тестирование на 64-битной Windows 10 с инъекцией во все процессы. - Тестирование на 32-битной Windows 10 с инъекцией во все процессы.
-
- Исправление небольшой ошибки. - Обзор того, как я использовал инструмент PE Internals. - Тестирование нашего драйвера на Windows 7 Pro, 64-битной ОС. - Работа с синим экраном смерти (BSOD) или BugCheck в Windows 7. - Открытие файла аварийного дампа memory.dmp в WinDbg для анализа сбоя ОС: запустите !analyze -v . - Исправление проблемы со сбоем, чтобы сделать наш драйвер обратно совместимым с Windows 7. - Тестирование обновленного драйвера на Windows 7 для внедрения нашего FAKE.DLL во все запущенные процессы. - Заключение.
Если вас интересует исходный код того, что я написал в приведенном выше руководстве:
В нашем последнем блоге Брэндон, член нашей высококвалифицированной команды Red Team в Secarma, познакомил нас с основами и теорией внедрения в процесс. Выписав всю информацию, которую он хотел бы получить, когда впервые развивал свои хакерские способности, теперь он собирается предоставить обзор некоторых вещей, которыми он занимается сейчас, будучи гораздо более опытным тестировщиком. Читайте дальше, чтобы узнать о некоторых из его более современных методов внедрения процессов:
Введение
В части 1 этой серии мы рассмотрели теорию, лежащую в основе внедрения процессов, и то, что необходимо сделать на хосте Windows, чтобы оно было успешным. В конце этого блога были приведены два примера:
Однако ни один из них не был рассмотрен с точки зрения операционной безопасности. В этом блоге мы рассмотрим, как превратить внедрение процессов в оружие и защитить его от современной защиты конечных точек — современного внедрения процессов, если хотите.
Индикаторы компрометации
Чтобы упростить этот блог, мы сосредоточимся на одном методе: внедрении шелл-кода.
Напоминаем, вот стандартный способ сделать это:
Если это не кажется вам знакомым, обратитесь к первой части. Давайте рассмотрим некоторые точки обнаружения в PEStudio:
Импорт можно увидеть выше, все остальное будет многопоточной компиляцией Visual Studio, о которой нам не нужно беспокоиться. Однако все звонки, которые мы сделали, там:
В связи с этим возникает вопрос: "Почему это проблема?". Endpoint Protection & Response (EDR) считаются современным решением для защиты рабочих станций и серверов. За последние несколько лет было проведено множество исследований EDR, в том числе:
В большинстве случаев это работает с помощью перехвата функций, который позволяет EDR идентифицировать вызов Windows API, который требуется приложению, и перенаправлять его через собственную библиотеку DLL в качестве своего рода прокси. Это работает путем копирования первых нескольких инструкций целевой функции в исполняемую память и замены их jmp на хук. Добавлена еще одна инструкция jmp, позволяющая вызову вернуться к исходной функции. Это достаточно подробно описано в FireWalker: новый подход к общему обходу перехвата EDR в пользовательском пространстве.
Напомним, перехват функций позволяет оценивать вызовы приложений при выполнении, что является проблемой. Даже если внедряемый шелл-код чист, такая методология вызовов имеет высокую вероятность быть помеченной. Этому посвящено множество исследований, и мы вернемся к этому позже, но сначала нам нужно обсудить защитное кольцо.
Пользователи и ядро
Если учесть контекст, большая часть действий пользователей будет происходить на третьем кольце, известном как режим пользователя. И ядро работает (на удивление) в режиме ядра. Более подробную информацию можно найти в разделе Программирование Windows/режим пользователя и режим ядра. Переход между пользовательским режимом и режимом ядра может происходить и происходит.
Чтобы лучше понять режим ядра и режим пользователя, см. документацию «Обзор компонентов Windows»:
Кольца 1 и 2 обычно оставляются для драйверов устройств. Но почему это полезно? Оказывается, Windows API использует Native API, который работает в режиме ядра. Например, API Monitor можно использовать для просмотра выполняемых вызовов:
Два в одном: выше показано, как вызывается CreateThread, а затем, вскоре после этого, вызывается NtCreateThreadEx. То же самое происходит с WaitForSingleObject.
Итак, что мы узнали? Что ж, когда делается вызов Windows API, скажем, из Kernel32.dll, он затем вызывает NTDLL.DLL. Затем эта NTDLL.DLL загрузит в регистр EAX номер системной службы для вызовов эквивалентной функции ядра. Например, CreateThread вызывает NtCreateThreadEx. Наконец, NTDLL.dll выдаст инструкцию SYSENTER. Это приводит к переключению процессора в режим ядра и переходу к предопределенной функции, называемой диспетчером системных служб. Следующее изображение взято из Rootkits: Subverting the Windows Kernel, в разделе Userland Hooks:
«SysWhispers предоставляет Red Teams возможность генерировать пары заголовок/ASM для любого системного вызова в основном образе ядра (ntoskrnl.exe). Заголовки также будут включать необходимые определения типов».
Затем modexp предоставил обновление, которое исправило недостаток версии 1 и дало нам SysWhispers2:
«Конкретная реализация в SysWhispers2 — это разновидность кода @modexpblog. Одно отличие состоит в том, что хэши имен функций рандомизируются при каждом поколении. @ElephantSe4l, ранее опубликовавший эту методику, имеет другую реализацию, основанную на C++17, которую также стоит проверить».
Пример подключения
Перед обновлением функции внедрения требуется какое-либо приложение-перехватчик. Для этого можно приспособить SylanStrike. Точнее, основная DLL. Логика этого «EDR» описана в:
Все это написано CCob. Для перехвата используется библиотека MinHook, но полный список можно найти на GitHub.
Используя ту же логику, что и в части 1, DLL можно внедрить в жертвующий процесс блокнота:
В этом случае «EDR» называется Po, и DLL успешно загружена. Ловушка в настоящее время присутствует в NtProtectVirtualMemory, которая также соответствует VirtualProtect:
Чтобы эта ловушка работала, этот вызов должен присутствовать в процессе внедрения, чего в настоящее время нет. Этот современный блог внедрения процессов не будет освещать это так подробно, как хотелось бы, но еще одна общая точка подписи — это разделы чтения, записи и выполнения (RWX) в памяти. Охота на это была задокументирована, и настоятельно рекомендуется избегать этого. Эту проблему достаточно легко исправить:
- Выделить как PAGE_READWRITE
- Обновить до PAGE_EXECUTE_READ
Подробнее об этом можно найти в MSDN.
Обновите VirtualAllocEx для использования PAGE_READWRITE:
Выполнение всего этого и запуск процесса с загруженной Po.DLL позволит обойти его. Это связано со следующим блоком кода:
Если установлен параметр PAGE_EXECUTE_READWRITE, отметьте его. Это подтверждает точку зрения разделов RWX. Итак, удаление этого и установка флага при любом вызове VirtualAllocEx:
Этот код непрактичный, но он докажет свою правоту. Если вызов идентифицирован, покажите сообщение SylanStrike по умолчанию и завершите:
До сих пор мы сделали краткий обзор RWX и привели краткий пример подключения. Итак, давайте обойдем его.
Обход хуков
Часть современного внедрения процессов связана с перехватом функций, и есть несколько способов сделать это. В этой демонстрации будет обсуждаться достижение этого с помощью системных вызовов, как обсуждалось ранее. Тем не менее, некоторые другие методы будут кратко обсуждены позже. Для этого будет использоваться SysWhispers2.
Windows API | Собственный API | tr>
VirtualAllocEx | NtAllocateVirtualMemory |
WriteProcessMemory | NtWriteVirtualMemory |
VirtualProtectEx | NtProtectVirtualMemory< /td> |
CreateRemoteThread | NtCreateThreadEx |
Чтобы получить правильные данные для этих вызовов, можно использовать следующую команду:
Поскольку код пишется в Visual Studio, следует использовать инструкции из репозитория:
- Скопируйте сгенерированные файлы H/C/ASM в папку проекта.
- В Visual Studio выберите Проект → Настройки сборки… и включите MASM.
- В Обозреватель решений добавьте файлы .h и .c/.asm в проект в качестве заголовочных и исходных файлов соответственно.
- Перейдите к свойствам файла ASM и установите для параметра Тип элемента значение Microsoft Macro Assembler.
- Убедитесь, что платформа проекта установлена на x64. 32-разрядные проекты в настоящее время не поддерживаются.
При выполнении замен VirtualAllocEx заменяется на:
Тестирование кода:
Обновленный код работает, повторно внедряя DLL:
Теперь, когда он повторно внедрен, новая утилита внедрения процесса будет использоваться для проверки того, что хук был обойден.
Напоминаем, что он настроен на перехват NtProtectVirtualMemory. Для смены обстановки попробуем поймать NtWriteVirtualMemory:
Никаких конкретных обстоятельств не было установлено, поэтому это должно помечаться всякий раз, когда оно что-либо видит:
А затем в Cobalt Strike:
Введен PID 7864. Итак, подведем итоги. До сих пор мы использовали безвредную технику внедрения процессов и заимствовали некоторую логику перехвата у SylantStrike, чтобы воспроизвести один аспект EDR. Затем это было обойдено с помощью системных вызовов x64, что является одним из способов сделать это. Если требуется x86, можно использовать SysWhispers2_x86.
Вместо использования системных вызовов другим распространенным методом является отключение функций, который работает именно так, как он предлагает. Он отменяет реализованный хук. Это хорошо задокументировано в Universal Unhooking: Blinding Security Software и Beacon Object File взяли эту логику и обернули ее в unhook-bof. SpecterOps также опубликовали Adventures in Dynamic Evasion, которую настоятельно рекомендуется прочитать.
Еще один шаг
Техника, используемая для внедрения в процесс, соответствует наиболее распространенной методологии, но существует множество других, которые обеспечивают больший контроль и их труднее обнаружить с точки зрения взаимодействия между процессами. Если это что-то, что реализуется, настоятельно рекомендуется провести некоторое исследование, которое будет оставлено читателю в качестве домашнего задания. Есть несколько причин для использования внедрения процесса:
- Первоначальная установка имплантата
- Fork & Run для последующей эксплуатации
- Боковое перемещение/повышение привилегий
И некоторые другие. В зависимости от требований, возможно, стоит изучить Parent PID Spoofing, чтобы избежать неудобной архитектуры процесса, такой как WORD.EXE, работающий с MALWARE.EXE, например, это просто не «естественная» структура процессов. Кроме того, возможно, стоит еще больше защитить выполнение, используя «Блокировку не-Microsoft DLL», которая была задокументирована xpn.
Чем больше функциональности используется, тем больше импорта будет у PE; это еще одна вещь, которую следует иметь в виду, и ее можно решить с помощью динамического разрешения функций или большего количества системных вызовов. В любом случае, независимо от причины необходимости внедрения процесса; всегда есть больше операций, которые вы можете и должны взять на себя.
Заключение
Хорошо, так; этот раздел, состоящий из двух частей, представил процесс внедрения и его место в мире Windows. Как это работает, почему это работает и для чего используется. Изучив теорию, мы перешли к пониманию требований к современному внедрению процессов, которое вводит примитивный EDR, где мы знали, какие вызовы перехватывались. Почетным упоминанием здесь является репозиторий EDR от Mr.Un1k0d3r, целью которого является документирование всех подключенных функций в нескольких EDR. Если есть какие-либо неточности или вопросы общего характера, напишите мне в Twitter.
Хотите узнать больше от команды? Мы обеспечим вас: загляните в Твиттер Secarma Labs, чтобы узнать больше о наступательных размышлениях о безопасности.
Если вы заинтересованы в расширении своих знаний в области пентестинга, мы проводим серию обучающих курсов по взлому и защите безопасности, на которых вы получите практический опыт этического взлома. Если вы заинтересованы в современном технологическом впрыске, этот курс может быть для вас. Если вы хотите принять участие, посетите нашу страницу обучения или свяжитесь с нами здесь.
Последние
Пример использования vISM: тесное сотрудничество с LedgerEdge
Secarma и LedgerEdge разработали постоянное партнерство в области кибербезопасности, основанное на консультациях.
Заблуждения о кибербезопасности
В Secarma мы уделяем большое внимание безопасности. Вот почему в рамках 20-го месяца осведомленности о кибербезопасности.
События кибербезопасности в столице
За последний месяц или около того команда Secarma была очень занята мероприятиями по кибербезопасности. Из .
Читайте также: