IOFluke

From GZProject Wiki

Jump to: navigation, search
HOWTO-Fluke
FlukeScript
Hi-level API
IOFluke
Input/Output capture library
Fluke
Code Injection library

Contents

IOFluke пространство памяти

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

Переопределение ввода\вывода

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

Замена библиотеки

Один из популярных методов перехвата, и самый простой - использование одноименной библиотеки. Если наша задача узнать какие данные проходят между исследуемым процессом и библиотечной функцией, то это очень просто реализовать. Нам понадобиться создать такую же библиотеку с тем же именем и набором функций, какие использует целевой процесс. После чего используя правило поиска библиотек в операционной системе Windows Dynamic-Link Library Search Order, мы смело можем подкладывать нашу новую библиотеку в каталог с исполняемым файлом. После запуска процесс будет использовать нашу подставную библиотеку заместо системной. Далее в зависимости от желаемого результата мы помещаем обработчик вызовов в саму библиотеку, либо программируем надстройку для передачи параметров в управляющий процесс.

Использование Hooks

В операционной системе Windows используется система ловушек, для слежения за некоторыми вызовами системных функций. Этот процесс подробно описан в документации на операционную систему Win32 Hooks. Для работы этого метода необходимо создать библиотеку с экспортируемой функцией, далее, следуя документации, вызвать из мастер приложения функцию с параметрами указывающими на конкретный процесс и на исследуемое действие. Функция SetWindowsHookEx принимает на вход процедуру обработчик, и как опциональный параметр идентификатор потока. Более подробное описание, так же, доступно в интернете в безграничном количестве.

Virtual Device Driver

Вот это уже нестандартный и мало известный способ слежение за работой системы, он использует в своей основе виртуальный драйвер устройств. Благодаря чему не только остается абсолютно не заметным, но еще и очень эффективным. Один из проектов, которые мне удалось найти в интернете с этой технологией это Regmon компании Sysinternals. Если вы обратитесь к документации на программу то найдете там три разных способа реализации такого алгоритма, базируются они на использовании Drivers Hooks.

Переопределение секции импорта

Основной смысл этой операции есть переопределение адреса функции, которую мы хотим перехватывать в специальной таблице, которая используется основным процессом для вызова библиотечных функций. Эта таблица находиться в исполняемом файле и модифицируется после загрузки, каждое поле которой указывает на адрес импортируемой функции. Поэтому перехват функции сводится к поиску этого адреса и замене на новый, адрес нашей подставной функции. Подробное описание структуры исполняемых файлов, на диске и в памяти можно найти в документации A Tour of the Win32 Portable Executable File Format. Интернет полон примерами работы и замещения функций через таблицу импорта.

Замена содержимого памяти

Из названия следует, что мы собираемся изменять содержимое функции для того, чтобы поместить безусловный переход на наш код, и вызвать соответствующий обработчик. Для этого нам необходимо найти адрес функции на памяти, запомнить старое содержимое функции и заменить ее на код команды безусловного перехода. Функция может быть двух типов, первая экспортируемая из текущего процесса или импортируемая из другого модуля. Тогда поиск такой функции сводится к вызову метода GetProcAddress. А замена содержимого есть процедура записи по этому адресу - новой команды ассемблера безусловного перехода на адрес нашей новой функции. В этом случае, как и в предыдущих я пока не стану рассматривать реализацию такого механизма подробней, она будет расскрыта при изучении пакета IOFluke.

Пример работы с библиотекой

После того как мы рассмотрели все методы перехвата управления, давайте рассмотрим как это делается с использованием библиотеки IOFluke. Реализация методов скрыта внутри кода, и не обременяет разработчика. Но для полного понимания ее функций, необходимо знать о методах перехвата функций, которые реализованы внутри библиотеки:

  • Замещение адреса в таблице импорта
  • Замещение памяти внутри процесса.

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

Определение типа вызова функций

Первым, самым важным, моментом при работе с библиотекой является механизм определения типа функции. К сожалению это необходимо диагностировать в ручную. Для этого нам необходимо хорошо представлять, как происходит вызов функции на самом низком уровне. Это базовое знание позволит правильно писать определения функций, которые мы хотим перехватывать и обеспечить корректную работу библиотеки. Тип функции определяет способ передачи параметров внутрь функции. Сюда водит в основном правила: способ очистки значений на стеке и порядок передачи параметров.

Возможные типы вызова функций:

  • this call
  • stdcall
  • cdecl
  • fastcall

Каждая функция должна быть привязана к одному из приведенных выше типу вызова, успех внедрения в чужой процесс напрямую зависит от успеха данной операции. Для того чтобы определить тип вызова, можно воспользоваться разными средствами. Самый простой способ - обратится к документации, если перехватываемая функция подробно описана, то обязательно среди всех параметров указано соглашение вызова. Именно его необходимо указать. Другой способ использовать утилиту Depends.Exe, при просмотре имен экспортируемых функций мы можем увидеть декоррированые имена. По измененному имени функции, в некоторых случаях, можно понять о количестве параметров и способе передачи их внутрь функции, что позволит определить тип вызова функции. Если выше описанные способы не применимы для определения соглашения о вызове функции, необходимо произвести анализ кода, просмотрев проект и по структуре кода, способу помещения параметров, способу возврата из функции можно однозначно определить искомый тип функции.

Замещение таблицы импорта

Мы подошли к самому интересному - использование библиотеки. В этой секции будет рассказано о использовании части библиотеки IOFluke для перехвата импортируемых функций. Механизм перехвата достаточно простой:

  • создание функции
  • создание связи между старым именем функции и адресом новой
  • поиск и перенаправление вызова.

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

Создадим класс с функциями перехвата:

 class CClientUOSecure
 {
   static void hook_closesocket(CMemMngr* mngr,va_list args)
   {
     CClientUOSecure* _this=dynamic_cast<CClientUOSecure*>(mngr);
     // пустая функция, не производит никакого действия
   }
   static void hook_send(CMemMngr* mngr,va_list args)
   {
     CClientUOSecure* current=dynamic_cast<CClientUOSecure*>(mngr);
 
     SOCKET s=va_arg(args,SOCKET);
     char* buf=va_arg(args,char*);
     int len=va_arg(args,int);
     int flags=va_arg(args,int);
 
     // производим обработку параметров и вызова дополнительных функций
   }
   static void hook_send_leave(CMemMngr* mngr,int *retval)
   {
     CClientUOSecure* current=dynamic_cast<CClientUOSecure*>(mngr);
     
     // производим изменения результата, возвращаемого функцией send
   }
 };

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

Далее в коде библиотеки необходимо сопоставить имя импортируемой функции с адресом наших процедур:

 CatchImportFunction("send","wsock32.dll",hook_send,hook_send_leave);
 CatchImportFunction("closesocket","wsock32.dll",hook_closesocket);

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

Замещение памяти

Алгоритм замещения функций идентичен алгоритму замещения функций в секции импорта. Поэтому сразу перейдем к примеру.

Класс с функциями перехватчиками:

 class CClientUOCommands
 {
   static void FromClient(CMemMngr*,va_list args)
   {
     CClientUOCommands* pclt=dynamic_cast<CClientUOCommands*>(client);
 
     unsigned char* input=va_arg(args,unsigned char*);
     int size=va_arg(args,int);
   }
   static void SetNewPlayersCoord(CMemMngr* client,int *retval)
   {
     CClientUO* pclt=dynamic_cast<CClientUO*>(client);
   }
 };

Вызов соответствия адреса функции адресу перехватчика:

 CatchRetFunction(0x004C0E30,FromClient);
 CatchRetFunction(0x00477C80,0,SetNewPlayersCoord);

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

Анализ кода

Наша задача состоит в анализе клиента, выделении функциональных частей и имплантирование наших функций внутрь рабочего процесса. Для того чтобы мы могли управлять целевым процессом необходимо выявить ключевые функции и перехватить их. Существует несколько основных способов получения адресов функций:

  • Дизассемблер
  • Отладочная информация
  • Анализ интерфейсов объектов
  • Анализаторы статических библиотек

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

Получение исходников

Для сборки библиотеки необходимо получить исходный код к следующим модулям:

Ссылки