С развитием технологий безопасности и защиты от киберугроз, появляются различные новые методы атак. Одна из сфер которая постоянно развивается в своих возможностях, называется Code Injection. В системах под управлением Windows, это стало весьма распространённым явлением. Поэтому сегодня мы обсудим один из таких способов для инъекции кода, - Reflective Injection.

Ранее на Хабре уже обсуждался и данный способ, и различные другие, поэтому конкретно в данной статье мы затронем способ защиты от подобного метода атак.

PE Header is the head of everything

Несмотря на то, что, DLL-библиотеки инжектированные данным способом не отображаются при помощи перебора EnumProccessModules и подобными способами, их всё равно возможно обнаружить.

Начнём с того, что каждая каждая DLL-библиотека содержит т.н. PE Header, они начинаются с двух определенных символов, иначе называемых Magic Number, - символов MZ (либо 4D 5A). Думаю, уже каждый понял к чему я веду, поэтому нет смысла таить, предлагается перебор всей динамически выделенной памяти процесса на наличие этих двух байт.

Полученные данные позволят нам найти все точки вхождения библиотек в памяти процесса и провести их дальнейшее анализирование.



Пробуем изложить наши мысли в виде алгоритма действий, и получаем примерно следующее:

  1. Получаем все дескрипторы модулей процесса через EnumProcessModules

  2. Записываем их базовые адреса в определенный массив

  3. Перебираем всю память процесса (все страницы) и ищем магические числа MZ

  4. Из найденного вытаскиваем базовые адреса и ищем в нашем массиве, отсекая имеющиеся

  5. Оставшиеся и будут разыскиваемыми инъекциями!

Это действительно так просто?

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

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

Объединив размер всех секций и PE заголовков известных нам (из метода описанного выше) библиотек и самого процесса, мы можем узнать размер оставшегося неизвестного пространства, и получить те самые участки с chunk-кодом. Если снова представить данный способ в виде алгоритма действий, то он будет примерно следующим:

  1. Мы перебираем все модули через EnumProcessModules и при помощи способа описанного ранее.

  2. Предварительно записав размер всех модулей и нашего процесса из PE заголовков, мы итеративно перемещаемся в (ожидаемый) конец библиотеки.

  3. При обнаружении байт-кода вне известного конца, мы записываем его размер и можем полагать об инъекции в программу с удалением PE заголовков

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

Тот самый byte-pattern выглядит примерно так:
Тот самый byte-pattern выглядит примерно так:

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

P.S.

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

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

Комментарии (1)


  1. qw1
    03.01.2024 16:29

    TLDR. Если мы знаем, какой нужно найти инжектированный вредоносный код, сканируем всю память процесса и ищем известную подстроку.