Гайд Обнаружение Reflective Injection (с очищенным PE Header)

0Z0SK0

Участник
Автор темы
36
15
Всем привет.
В силу своей деятельности, возникла необходимость определять инжекты динамических библиотек (DLL) на уровне user-mode.
Простейшие инжекты достаточно просто обнаруживаются через перебор модулей процесса (EnumProcessModules), но, кроме простейших способов существует и различные усложнения, противодействующие детекту обычными методами.

Одним из таких способов является Reflective-Injection, это особая техника, позволяющая встраивать библиотеки непосредственно в память целевого процесса, без упоминания в списке загруженных модулей.
  • Execution is passed, either via CreateRemoteThread() or a tiny bootstrap shellcode, to the library's ReflectiveLoader function which is an exported function found in the library's export table.
  • As the library's image will currently exists in an arbitrary location in memory the ReflectiveLoader will first calculate its own image's current location in memory so as to be able to parse its own headers for use later on.
  • The ReflectiveLoader will then parse the host processes kernel32.dll export table in order to calculate the addresses of three functions required by the loader, namely LoadLibraryA, GetProcAddress and VirtualAlloc.
  • The ReflectiveLoader will now allocate a continuous region of memory into which it will proceed to load its own image. The location is not important as the loader will correctly relocate the image later on.
  • The library's headers and sections are loaded into their new locations in memory.
  • The ReflectiveLoader will then process the newly loaded copy of its image's import table, loading any additional library's and resolving their respective imported function addresses.
  • The ReflectiveLoader will then process the newly loaded copy of its image's relocation table.
  • The ReflectiveLoader will then call its newly loaded image's entry point function, DllMain with DLL_PROCESS_ATTACH. The library has now been successfully loaded into memory.
  • Finally the ReflectiveLoader will return execution to the initial bootstrap shellcode which called it, or if it was called via CreateRemoteThread, the thread will terminate.

Не смотря на заметное усложнение, данный метод так же возможно обнаружить.
Начнем с того, что каждая динамическая библиотека содерит PE Заголовки (Header), они начинаются с двух определенных символов - MZ.
Благодаря им, мы можем перебрать память процесса и найти все заголовки динамических библиотек.

1677010926722.png


Эти заголовки являются обязательными и используются в любой PE-формат программе/библиотеке.

Если превратить данный метод обнаружения в определенный набор действий, то мы получим примерно подобное:

  • Получаем все модули процесса через EnumProcessModules
  • Записываем их базовые адреса в определенный массив
  • Перебираем всю память процесса (все страницы) и ищем заголовки MZ
  • Найденные заголовки (их адреса) ищем в нашем массиве с модулями, отсекая найденные
  • Оставшиеся заголовки и будут инжектнутыми библиотеками в наш процесс

Но, дальше труднее. PE Заголовки возможно удалить из памяти после инжекта, так как они не используются в процессе работы, только лишь в начале.

С этого момента, у нас пропадает возможность как либо идентифицировать библиотеку обычными методами. Так как именно PE Header содержит информацию о библиотеке (в том числе название, физический путь, базовый адрес и т.д).
Поэтому такие части кода следует называть chunk-кодом.

Но, мы ведь встраиваем определенный код в виртуальную память процесса, и этот процесс не протекает бесследно?
Верно, мы всё еще можем обнаружить такие сегменты при помощи полного изучения памяти процесса.
Обьединив размер всех секций + заголовков известных нам библиотек и самого процесса, мы можем узнать размер оставшеегося неизвестного пространства, и получить те самые участки с chunk-кодом.
Если снова представить данный способ в виде плана, то он будет примерно следующим:
  1. Мы перебираем все модули через EnumProcessModules и при помощи способа описанного ранее.
  2. Предварительно записав размер всех модулей и нашего процесса из PE заголовков, мы перемещаемся в (ожидаемый) конец программы.
  3. При обнаружении кода вне известного конца, мы записываем его размер и можем полагать о инжекте в программу с удалением PE заголовков.

1677012829965.png




Но остаётся следующая проблема, мы не можем идентифицировать эти участки как либо.
И тут нам в помощь приходят эвристические методы.

Например, взяв известную нам последовательность байтов (byte pattern), мы можем найти ее в chunk-коде. И тем самым идентифицировать возможную библиотеку.
Откуда же взять эту последовательность? Тут необходимы ваши знания reverse-engineering`а. У нас есть возможность открыть любую библиотеку в различных дебаггерах и изьять начало любой из функций внутри библиотеки в виде набора байтов. А после найти этот набор байтов в chunk-коде.

1677012908657.png


Конечно и тут существуют проблемы, данный способ неприменим при self-morphing коде, или обернутом в какую либо VM машину (VMProtect к примеру). Так как набор байтов постоянно меняется при исполнении, и не оставляет нам возможности его найти.

Существуют и другие трудности при данном методе обнаружения, код может быть встроен непосредственно в секции игры с патчингом оригинальных PE-заголовков, но мы способны сравнить ожидаемый размер секций программы и их размер непосредственно в процессе исполнения для обнаружения различных патчингов или инжектов.
 

kin4stat

mq-team
Всефорумный модератор
2,730
4,710
  • Execution is passed, either via CreateRemoteThread() or a tiny bootstrap shellcode, to the library's ReflectiveLoader function which is an exported function found in the library's export table.
  • As the library's image will currently exists in an arbitrary location in memory the ReflectiveLoader will first calculate its own image's current location in memory so as to be able to parse its own headers for use later on.
  • The ReflectiveLoader will then parse the host processes kernel32.dll export table in order to calculate the addresses of three functions required by the loader, namely LoadLibraryA, GetProcAddress and VirtualAlloc.
  • The ReflectiveLoader will now allocate a continuous region of memory into which it will proceed to load its own image. The location is not important as the loader will correctly relocate the image later on.
  • The library's headers and sections are loaded into their new locations in memory.
  • The ReflectiveLoader will then process the newly loaded copy of its image's import table, loading any additional library's and resolving their respective imported function addresses.
  • The ReflectiveLoader will then process the newly loaded copy of its image's relocation table.
  • The ReflectiveLoader will then call its newly loaded image's entry point function, DllMain with DLL_PROCESS_ATTACH. The library has now been successfully loaded into memory.
  • Finally the ReflectiveLoader will return execution to the initial bootstrap shellcode which called it, or if it was called via CreateRemoteThread, the thread will terminate.
Я немного хлебушек, а чем это от мануал мапа отличается?
 

0Z0SK0

Участник
Автор темы
36
15
Я немного хлебушек, а чем это от мануал мапа отличается?
По моему мнению, в основной массе они ничем не различаются, но можно найти отличия в реализации. (некоторые инжекторы к примеру оставляют экспорт функции загрузчика или подобное)
 

AeSiK256

Участник
58
23
По моему мнению, в основной массе они ничем не различаются, но можно найти отличия в реализации. (некоторые инжекторы к примеру оставляют экспорт функции загрузчика или подобное)
Небезопасный метод привел