Пример использования Dataflow. Часть 2

This post is also available in: Английский

Страница проекта доступна здесь.

Кое-что полезное так же можно найти в Пример использования Dataflow.

0.     Установка

  1. Скачайте архив dataflow-0.3.0.zip
  2. Распакуйте в любую директорию ( например c:/temp )

1.     Настройка

  1. Переименуйте dataflow.cfg.example в dataflow.cfg
  2. Если вы распаковали в отличную от c:/temp директорию, то измените путь на соответствующий

3.     Запуск

  1. Запустите программу DataflowManager.exe. В трее появится иконка со смайликом.
  2. Откройте консоль cmd.exe. Перейдите в c:/temp

cd c:/temp
c:

Начните анализ программы-примера FunctionsTest.exe

Dataflow.exe FunctionsTest2.exe

Программа FunctionsTest2.exe представляет собой скомпилированный cl код:

#include <stdio.h>
#include "stdafx.h"
int foo5( void )
{
   printf( "foo5\n" );
   return 0;
}

int foo6( void )
{
   printf( "foo6\n" );
   return 0;
}

int foo4( void )
{
   printf( "foo4\n" );
   return 0;
}

int foo3( void )
{
   printf( "foo3\n" );
   foo6();
   return 0;
}

int __stdcall foo2( int a, int b, int c, int d )
{
   printf( "foo2\n" );
   printf( "a: %i, b: %i, c: %i, d: %i\n", a, b, c, d );

   foo4();

   foo5();

   return 0;
}

int __fastcall foo1( int a, int b, int c, int d )
{

   printf( "foo1\n" );

   printf( "a: %i, b: %i, c: %i, d: %i\n", a, b, c, d );

   foo2( 13, 24, 35, 46 );

   foo3();

   return 0;
}

int main(int argc, char* argv[])
{
   goto start;

   getchar();

start:
   __asm
   {
      mul ebx;
      jmp nogetch;
   }
   getchar();

nogetch:

   printf( "main\n" );

   foo1( 12, 23, 34, 45 );

   do
   {
   } while( 1 );
   return 0;
}

Программа запустится, выведет сообщения и застынет в бесконечном цикле

Т.е. фактически программа еще не завершена.

С помощью управляющей программы запрашиваем получение статистики

На диск в папку FunctionsTest.exe [время дата] (PID) размещается данные, полученные в ходе статического и динамического анализа FunctionsTest.exe.

Данные можно запрашивать в любой момент. Каждый раз они будут размещаться в отдельную папку в номером запроса.

В каждой из папок находится несколько файлов.

Файл functionstest.exe_bundle.gdl содержит информацию об исследуемой программе: загружаемые модули, графы вызова функций, CFG функций, информацию о покрытии кода.

Файл functionstest.exe_fuzzing.gdl содержит рейтинг функций для fuzzing

Далее, на каждый подгруженный модуль из файла конфигурации генерируется подключаемый файл имя_модуля.h. В нем содержатся восстановленные прототипы фунций модуля и их адреса.  Вот фрагмент файла functionstest2.h:

int ( __stdcall *functionstest2_sub_10C0__)( int c, int d )
   = ( int ( __stdcall * ) ( int c, int d) ) 0x4010c0;

inline int __stdcall functionstest2_sub_10C0( int a,
                                                               int b,int c,int d )
{
   __asm{
      mov ECX, a
      mov EDX, b
   }
   return functionstest2_sub_10C0__( c, d );
}

int ( __stdcall *functionstest2_sub_1080__)( int a, int b, int c, int d )
      = ( int ( __stdcall * ) ( int a, int b, int c, int d) ) 0x401080;

inline int __stdcall functionstest2_sub_1080( int a, int b,int c,int d )
{
   __asm{
   }
   return functionstest2_sub_1080__( a, b, c, d );
}

Важным является то, что данный подключаемый файл является SDK для фаззинга в памяти произвольной функции модуля.  В вышеописанном фрагменте описаны функции functionstest2_sub_10C0 и functionstest2_sub_1080. В выборе точки внедрения данных помогут данные из файла functionstest.exe_fuzzing.gdl. Файл содержит описание на языке GDL. Открыть его можно в программе aiSee (www.aiSee.com). Открываем с помощью aiSee файл functionstest.exe_fuzzing.gdl (удобно ассоциировать программу с расширением .gdl).

Отобразится самый верхний уровень  данных о программе.  Выделив блок и нажав кнопку “I” получим информацию о модуле.

Module code size – количество байт кода программы (подсчитываются только те модули, которые были указаны в dataflow.cfg). Значение получается как сумма размеров исполняемых секций модулей.

Reached code size – количество байт кода программы, которые удалось достигнуть в ходе статического анализа (по вызовам функций ). Так же учитываются только модули из dataflow.cfg.

Covered code size – коqличество байт кода программы, которые были исполнены. Учитываются только модули из dataflow.cfg.

Для того что бы получить более детальную информацию раскроем блок Program: functionstest.exe. Для этого выделим блок и нажмем кнопку “b” ( развернуть в блок ).

Желтым подсвечены те модули, анализ которых был выполнен (они присутствовали в dataflow.cfg). Из картинки видно, что нас интересует только модуль functionstest.exe. Остальные модули не анализировались и поэтому подсвечены серым цветом.

Слудующий уровень детализации дает важную информацию о модуле – рейтинг функций в применении к фаззингу. Нажатием на кнопку ‘b’ перемещаемся на этот уровень.  Самые рейтинговые функции находятся справа.

Видно, что покрытие из точки входа в модуль самое большое( параметр Fuzzing potential reached code size ), это объясняется тем, что из точки входа потенциально доступны все функции модуля. На рисунке ниже досягаемые функции обведены красным цветом.

Однако входная функция не принимает параметров (они не используются). Двигаемся к менее рейтинговой функции.  Такой функцией является функция с прототипом  int __cdecl functionstest2_sub_1120( int a ). Ее охват более скромный, но покрываются только функции из исходного кода (без стандартного пролога).

В отношении покрытия функция является перспективной, однако являются ли удобными для фаззинга параметры? Для исследования параметров существуют информационная вкладка  2 доступная при нажатии ‘I’.

Здесь представлен прототип функции  и записанное значение параметров при вызове функций. По одной записи не каждый вызов. Функция вызывалась всего раз, поэтому и запись одна. Всего один параметр и тот вызывался с нулевым значением.  Рассмотрим дизассемблированный листинг функции, что бы понять  как используется параметр. Для этого откроем файл functionstest.exe_boundle.gdl и найдем интересующую функцию.

Так же полезно знать, через какие средства передается параметр. Для этого заглянем в заголовочный файл functionstest2.h.

int ( __cdecl *functionstest2_sub_1120__)( void )
    = ( int ( __cdecl * ) ( void) ) 0x401120;

inline int __cdecl functionstest2_sub_1120( int a )
{
   __asm{
      mov EBX, a
   }
   return functionstest2_sub_1120__( );
}

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

int main(int argc, char* argv[])
{
   goto start;

   getchar();

start:
   __asm
   {
      mul ebx;
      jmp nogetch;
   }
   getchar();

   nogetch:

   printf( "main\n" );
   foo1( 12, 23, 34, 45 );
   do
   {
   } while( 1 );

   return 0;
}

А функция соответственно main(). Перемещаемся к следующей по рейтингу функции.

Охват функции следующий:

Охват кода меньше ровно на одну функцию functiontest2_24!sub_1120. Информация о параметрах следующая:

Функция принимает четыре параметра и все они не указатели. Значения параметров похожи на числа. На те самые числа, которые выводятся на консоль во время исполнения. Выберем данную функцию в качестве точки фаззинга. Используя подключаемый файл подготовим седующий testsuite:

#include "functionstest.h"
void StartTest( void )
{
   functionstest_sub_10C0( 88, 77, 66, 55 );
   functionstest_sub_1080( 33, 44, 55, 66 );
}

BOOL APIENTRY DllMain( HMODULE hModule,
                                 DWORD  ul_reason_for_call,
                                 LPVOID lpReserved
)

void StartTest( void )
{
   switch (ul_reason_for_call)
   {
      case DLL_PROCESS_ATTACH:
         StartTest();

      case DLL_THREAD_ATTACH:
      case DLL_THREAD_DETACH:
      case DLL_PROCESS_DETACH:
      break;
  }
  return TRUE;
}

Для демонстрации того, что в тесте может использоваться не только одна функция включим так же вызов functionstest_sub_1080, которая к тому же имеет иной call convention. Ее прототип следующий inline int __stdcall functionstest2_sub_1080( int a,int b,int c,int d ). Скомпилируем и соберем в динамическую библиотеку FuzzFunctionsTest.dll. Теперь все готово для запуска теста. Выбираем соответствующий пункт меню.

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

Было выполнено два дополнительных вызова, начиная с функции foo1 и foo2. Все нижележащие функции так же были вызваны.

После этого информация об исполнении может быть снова получена. Процесс может быть повторен нужное количество раз.