
                         О НЕДЕТЕКТИРУЕМЫХ ВИРУСАХ
                         -------------------------

     От чего зависит время детектирования вируса в файле?

     Во-первых,  от  числа  возможных  вариантов  тела  вируса.  Чем  таких
 вариантов больше, тем дольше при проверке их все перебирать.

     Этот  путь самый простой, и именно по нему пошли вирусы, превратившись
 в крипты, полиморфные, пермутирующие и метаморфные.

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

     Во-вторых,   общее   время  детектирования  вируса  зависит  от  числа
 возможных местоположений вируса в файле.

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

     Примером этого пути развития является технология UEP (EPO).

     Пусть

 C  - сложность проверки всего файла.

 Ci - сложность проверки вируса по некоторому данному адресу,
      включая сюда эмуляцию и проверку всех вариантов тела вируса.

 I  - число возможных адресов в файле, по которым может находиться
      тело вируса или его часть.

     Тогда
                                 C = Ci * I

     Эта  формула  отражает алгоритм проверки файла: для каждого возможного
 адреса  I,  по  которому  может  находиться  вирус,  выполнить проверку на
 наличие вируса (Ci).

     Алгоритм проверки файла на вирус в этом случае такой:

     for(i = 0; i < I; i++)
     {
       init_emul();       \
       emul(block[i]);      Ci
       check_virus();     /
     }

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

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

                                     I!
                          C = Ci * ------
                                   (I-N)!

             I!
     Здесь ------ это так называемое число размещений из I по N.
           (I-N)!

     Так,  например, если число подозреваемых на вирусные блоков равно 4, а
 всего  должно быть 2 последовательно выполненных блока, то будут проверены
 12 вариантов:

       {0,1}  {0,2}  {0,3}
       {1,0}  {1,2}  {1,3}
       {2,0}  {2,1}  {2,3}
       {3,0}  {3,1}  {3,2}

     В результате, проверка файла на вирус будет выглядеть так:

     for(i1 = 0; i1 < I; i1++)   \
     for(i2 = 0; i2 < I; i2++)    \
     if (i2 != i1)                 \
     for(i3 = 0; i3 < I; i3++)      \
     if (i3 != i1)                   \    I!
     if (i3 != i2)                      ------
     ...                                (I-N)!
     for(iN = 0; iN < I; iN++)       /
     if (iN != i1)                  /
     if (iN != i2)                 /
     if (iN != i3)                /
     ...                         /
     {
       init_emul();              \
       emul(block[i1]);           \
       ...                          Ci
       emul(block[iN]);           /
       check_virus();            /
     }

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

     Это   достигается   трассировкой   программы.   Трассировка  же  легко
 производится с использованием debug api.
     Трассировать  нужно  выбранную программу в течение небольшого времени,
 скажем,  нескольких  секунд.  Важно,  чтобы это время выбиралось случайным
 образом.
     Во время такой трассировки составляется список выполненных инструкций,
 свой для каждой нити процесса.
     Интересным  моментом  является  то,  что трассируем мы не программу, а
 процесс,  то  есть  EXE-шник  и  все  его DLL-ки. Таким образом появляется
 возможность  раскидать  вирусные  блоки  не только по разным местам одного
 файла,  но и по разным местам нескольких разных файлов, так, что выполнены
 эти блоки будут последовательно.

     Конечно,  на  небольшом  локальном  участке  кода можно обойтись и без
 трассировки,  одним  лишь  статическим  анализом.  Но когда речь заходит о
 большой  мультитреадной  программе,  состоящей  из  нескольких  файлов  --
 EXE'шника  и  всех  его  DLL'ек,  а также о достаточно больших расстояниях
 между вирусными блоками, то дизассемблирования недостаточно, и трассировка
 становится необходимой.

     Таким образом мы:

 1. Выбираем программу.
 2. Производим статический анализ.
    (парсинг на инструкции, см. движок Mistfall и вирус ZMist)
 3. Производим динамический анализ (трассировку, см. Tracer32),
    с учетом данных парсинга.
    В частности, перед трассировкой в начало всех подпрограмм вставляются
    точки останова (brrakpoint, INT3), для того, чтобы не потерять
    управление на callback'ах, вызываемых из не трассируемых библиотек.
 4. Снова производим статический анализ, уже более качественный,
    с учетом данных трассировки.
 5. Для улучшения качества анализа возможно повторение начиная с шага 3.
 6. Комбинируем полученные данные.
 7. Выбираем последовательно выполняемые места программы,
    в которые следует поместить вирусные блоки.
 8. Вносим в программу изменения.
 9. Заново собираем исполняемые файлы.

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

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

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

                                   * * *
