Virus Patrol
Volume Number: | | 5
|
Issue Number: | | 2
|
Column Tag: | | Advanced Mac'ing
|
Security Patrol for Viruses
By Steven Seaquist, Washington, DC
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
Politics of Virus Protection
SecurityPatrol is a program I wrote to detect and optionally remove the nVIR and Scores viruses (all Macintosh viruses known at the time I wrote it), plus several other conditions I would find suspicious and would want to be warned about. This is version 1.1. Its been tested against live strains of nVIR and Scores. It contains a code database of fingerprints to identify known viruses and detect alterations of a systems good (trusted) resources by future viruses. [The source code disk for this month contains the complete library of system resource fingerprints for Security Patrol. In the interest of space, only a few example fingerprints are printed in the magazine. -Ed]
It wasnt coded so much to be user-friendly; my main goal was to make it security-stubborn: Its deliberately not easy to alter its code database of resources. You have to code them in manually and recompile. To put it another way, the easier it is for a user to alter the database, the easier it is for a virus to alter the database and masquerade as something innocuous.
On the other hand, there are advantages to the fact that its being published in source code format. It allows you to re-code it to deal with future viruses and/or to make it deliberately inconsistent with the published version. (A predictable defense is generally a weakness to attack.) Another significant advantage is that it inherently discloses its behavior. Sunlight is a strong disinfectant.
[While Apple Computer cannot and does not endorse anything in this article, it's publication in MacTutor was of sufficient concern that Scott Boyd, a contributing editor of MacTutor and a member of Apple's internal virus committee, asked and was given permission to review the article prior to publication. He indicated Apple's concern that an article of this sort that points out the system weaknesses can also give more ammunition to fuel future virsus makers. However, since three books have recently been published on how to create a computer virus, we feel the cat is out of the bag anyway so the protection this technology will allow developers is more vital than ever. Accordingly, we are publishing the article essentially unchanged from the author's version, except for a couple of places where Steven reveals "chinks in the armor" of Apple's system software. Scott did allow that many of Steven's techniques are being used in internal security programs at Apple so we feel this approach has merit. We invite comment on the problem that Scott and others at Apple must wrestle with: namely how much information should the public be given on the internal workings of Apple's system software and it's potential vulnerability to viral attack.-Ed]
User Interface
The program sends feedback to the screen and to a report file, but first the user has to select the report filename with a SFPutFile (Save As ) dialog. If AppleTalk is active, pressing Cancel will cause it to Quit; if its not active, pressing Cancel will cause it to stream the text to a direct-connect ImageWriter on the printer port. (Theres a bug in TML II that causes WriteLn output to the ImageWriter not to work, but itll be fixed soon. In the meantime, the user can send the report to a file or press Cancel to avoid creating a report file.) After selecting the report file, the user is presented with the Main Dialog.
Main Dialogs Scope of Work Buttons
The user may want to patrol only a few files, or all files in a folder, or all files on several disks bought in a store. Maybe the user doesnt have any particular files in mind, but wants to see whether or not any file on a partitioned hard disk is infected. The program cannot know in advance the scope of work to be done, so it asks the user in its Main Dialog.
Directories: The program puts up an SFGetFile (Open ) dialog to allow the user to select a file. The program patrols all files under the directory that contains the selected file. Then, under HFS, it recursively patrols all subdirectories. Then it returns to the SFGetFile dialog. This process repeats until the user presses the Cancel button.
Directory: Same as Directories, except that it doesnt patrol subdirectories.
Everything: The program loops thru all currently mounted volumes and patrols Directories (plural) on each one starting from the root directory.
Files: The program puts up an SFGetFile dialog to allow the user to select a file, patrols only that file and returns to the SFGetFile dialog. This process repeats until the user presses the Cancel button.
Quit: The program prints file and resource totals across all patrols before returning to the Finder.
User Interface: Options
The Main Dialog also has options the user (usually to get the users attention) and for the programmer (usually to help ease the non-trivial programming burden). Both kinds are controlled by check boxes.
Await Keypress: When errors are reported, the program pauses until the user presses a key.
Beep: The program beeps at you when suspicious conditions occur. Complaints get 1 beep, errors get 2 beeps, viral infections get 3 beeps and aborts get 4 beeps. (Most aborts are caused by programming errors, but they could also signify that the program is infected.)
Fingerprint: The program lists the fingerprints of all executable resources it knows about, except CODEs.
Fingerprint CODEs: The program lists the fingerprints of CODE resources too, which generates a lot of output.
Long Listing: The program lists all filenames to its report file, not just those that encountered errors.
Remove Viruses: This is a last resort option for users to recover applications whose backups and masters are lost or unreadable. There are 3 major reasons why you might want to direct users not to use it: concern as to your legal liability if it doesnt remove the virus properly (or damages uninfected applications), a desire to encourage the safer method (restoring from backups) so as to limit future headaches, and the possibility of using the infected disk as evidence in a criminal or civil prosecution (hard to do if you removed the virus as soon as you saw it).
Trace: The program traces its flow. You may want to turn on Await Keypress to step thru it slowly.
Program Design
SecurityPatrol was dually developed under MDS-compatible TML Pascal I, version 2.5 (TML 2.5) and MPW-compatible TML Pascal II, version 1.0.2 (TML II). Both versions are on the source code disk, with TML 2.5 filenames ending in .Pas and TMLII filenames ending in .p. The TML II files are the ones that appear in this article.
Major differences: The TML 2.5 version is roughly 50K and 3% faster, can output to a direct-connect ImageWriter on the printer port and types the report file as an MDS Edit document. The TML II version is roughly 60K, is much easier to develop in, has a larger text I/O window on larger monitors and types the report file as an MPW document.
The program is broken up into 4 major modules: a main program called SecurityPatrol and 3 UNITs called Globals, MainDlog and Patrol. The TML 2.5 versions of these 4 are 99% source code compatible; they differ only in the USES statement. In addition, the Globals UNIT of both versions include the Fingerprint.ipas file by means of the {$I} directive; that is, because its 100% source code compatible, its shared. The CodeSizeLimits UNIT is incompatible; there are different versions for TML 2.5 and TML II. Finally, there are BitProcs and PasLibIntf, which are used only in the TML 2.5 version to maintain source code compatibility with TML II.
The files were named such that, under TML 2.5, dependency relationships are properly observed if you simply compile them in alphabetical order. Under TML II, you can let the TML Project Manager and MPW Make facility handle that for you. (I edit the SecurityPatrol.Proj file to recompile Globals.p if Fingerprint.ipas has changed, and to give the Fingerprint and Globals CODE resources the preload and locked attributes.)
Within each module, the procedures are arranged alphabetically for easy lookup. Since they arent necessarily called in alphabetical order, some routines have to be declared FORWARD.
As far as conversion to other Pascals is concerned, the following may help: The OUTPUT in the PROGRAM header is a TML 2.5 convention to tell the compiler that the program will use WriteLn, etc. TML also defines a Text file variable called OUTPUT thats implicitly used by all screen output WriteLns, etc, and PasLibIntf procedures. INC and DEC are built-in functions to increment and decrement a variable by one. (Both are much faster than an assignment such as x := x + 1 because they generate the ADDQ and SUBQ instructions.) Thats all I can think of for now.
Program Flow
The MainDlog UNIT generates and maintains the Main Dialog display, keyboard equivalents and option check boxes internally. It returns to the main program when a Scope of Work button is pushed.
The SecurityPatrol main program manages program initialization, termination, text I/O and high-level interface between MainDlog and Patrol: Depending on the Scope of Work button pushed, it passes off to the proper routine of Patrol.
The Patrol UNIT contains service routines to scan volumes and/or directories. (Its been programmed to work under both MFS and HFS, but it hasnt been tested under the 64K ROMs.) Its heart and soul is the recursive procedure PatrolDir, which, at the appropriate times, calls 5 routines in Globals: DirectoryBegins, DirectoryEnds, PatrolBegins, PatrolEnds and ProcessFile. Another routine in Patrol, called PatrolFiles (which patrols files manually, a file at a time), also calls those same routines in Globals.
Globals is the largest UNIT and contains all the basic service routines. The first 4 routines just mentioned are currently given only trivial feedback duties at present. The 5th one (ProcessFile) is where all the action starts. If the file being processed contains CODE resources, ProcessFile calls ProcessCodes, which calls LookForKnownViruses and then looks for anomalies in the CODE 0 jump table. Then, for every executable resource type it knows about, ProcessFile calls ProcessRsrcs with the address of a routine to process that resource type. LookForKnownViruses and ProcessRsrcs call routines in Fingerprint to do resource identification. If the Fingerprint routines say that a resource is infected, LookForKnownViruses calls a Disinfected routine that knows how to restore the CODE 0 jump table before removing the infected resource(s); ProcessRsrcs calls RemovedRsrc directly to remove it.
Fingerprint is where the code database of good (trusted) and bad resource identification resides. A resources fingerprint is a set of any tests you desire. In this article, 3 tests are used as the fingerprint; on the MacTutor source code disk(s), 7 tests. As new viruses are created, Fingerprint is where you would insert new code to detect them.
Commentary on CodeSizeLimits.p
CodeSizeLimits is used by PreprocessSelf in the main program, below. The idea behind it is to prevent a virus from creating a new CODE resource or imbedding itself in one of the existing CODE resources.
Since TML II uses the standard MPW Link tool, which creates 9 CODE resources, the gSizeLimit array is 0..8 and gMaxCode is 8. The size limits for CODEs 1 thru 3 contain a little room for expansion, but not enough for even the tiny nVIR virus to fit into. When you have the program the way you want it, you should set these values to exactly the same size as the CODE resources, so there wont be any room for a virus to sneak into. I have never seen CODEs 4 thru 8 be any size other than exactly those shown, so I feel comfortable giving them no room for expansion. Unless you add, delete or move subroutines, gJTSize will always equal 1240, which represents 155 entries in the jump table. Under TML II, the main programs main procedure cannot be referenced as if it were an external subroutine, because MPW Link doesnt give us a symbolic name for it that we can reference. Tom Leonard suggested defining a subroutine just prior to the main programs main procedure and adding a value to skip over its contents. The value turned out to be $1A.
Commentary on Fingerprint.ipas
FgPr: Fingerprinting begins with a call to InitFgPr and then proceeds by (possibly multiple) calls to FgPr. This allows a data fork to be fingerprinted even when its larger than the I/O buffer. Calling TMLs built-in INC procedure twice is faster than the generic Pascal sWordPtr := sWordPtr + 2;, but not quite as fast as the new, equivalent TML II syntax: INC(LONGINT(sWordPtr),2);.
As mentioned above, a resources fingerprint is a set of tests, any tests you desire. (The only significant restriction is that the test value is a LONGINT.) To fit within MacTutor column width restrictions, the version printed in this article uses only size, checksum, and checkxor. The version on the source code disk(s) uses those 3, plus the number of odd, negative, and positive words, and an alternating checksum/checkdiff. You shouldnt use only the 3 tests in this article, because its possible to reverse-engineer an evil resource to have the same size, checksum and checkxor as a good one. The 7 tests used on the source code disk(s) make it a virtual certainty that, if two resources have the same fingerprint, they are bit-for-bit identical.
If you want to invest your energies into modifying this program, it wouldnt be very fruitful to devise exotic new fingerprint tests. If you did manage to implement CRC-16, for example, and someone discovered a new virus, how would you find out what its CRC-16 value is?? If no one else has your test, you would need an actual copy of the virus to find out! (Scary!) Its better that there should be just a few common tests, not too computationally intense, the union of which approaches certainty. It would be more fruitful if people devoted their efforts to searching more hiding places. In terms of the security patrol analogy, what we need are more guards on patrol, searching in more crevices, not more brands of flashlights.
Good and Evil: Any resource or data fork whose known flag is not set to TRUE will be reported as unknown. The Good and Evil routines set this flag. Calls to these routines form the code database, which presents a tabular appearance in the routines that follow.
Dont be misled by the name Good. I chose that as its name because its a natural antonym of Evil and also 4 letters long. It obviously cant know whether or not the original code from the software manufacturer contains malicious code, so its not known to be good in that sense of the term. In the context of what were doing, it actually means trusted.
To add the fingerprints of new trusted resources, just run the program against clean disks (masters) with the report being saved to a text file. After quitting the program, open the report file and the Fingerprint.ipas file. The MPW editor allows you to select everything within a parenthesis pair by double-clicking on the left or right parenthesis of the pair. Using that technique, its very easy to cut and paste back and forth previously unknown resource fingerprints from the report file to new IF Good( ) THEN EXIT; statements in the Fingerprint.ipas file.
SecurityPatrol runs noticeably faster if the fingerprint tests are done in reverse likelihood order. As far as which resources you mark as trusted and the order you test them is concerned, you have to consider what System levels the program will be run under, not just the level youre using for development. On the source code disk(s), I have included major resource fingerprints for Systems 4.2, 4.1 and 3.2.
KnownDataFork: Because there isnt a TRsrcRec associated with the data fork, this routine uses special globals, gKnown and gInfected. The commentary on Globals ProcessFile, below, discusses what kinds of files have their data forks fingerprinted.
LookForVirus_nVIR: Did you know this virus exists in 2 versions? The May 1988 issue of MacTutor described a 372 byte long version, but when I asked Howard Upchurch to send me a copy of nVIR, the one he sent was 422 bytes long. That was the strain that got onto the CD-ROM disk. Later I picked up the 372 byte strain quite by accident at Kinkos. (I notified the Washington DC area Kinkos about this, incidentally.)
LookForVirus_Scores: The first 12 bytes of the Scores virus CODE resource is used to hold the segment and jump table information, which vary according to which application its infecting; therefore, we skip those 12 bytes in the fingerprinting process.
Generic Process_xxxs: To shorten this article, only Process_ADBS is shown as an example of generic Process_xxx routines, which include ADBS CACH, CDEF, CODE, DRVR, DSAT, FKEY, FMTR, LDEF, MBDF, MDEF, MMAP, NBPC, PACK, PDEF, PTCH, ROv#, ROvr, SERD, WDEF, XCMD, XFCN, boot, cdev, mppc, snth and view (if it contains methods). The versions on the source code disk(s) include all of them except CODE, which wouldve been more work than I could do before the MacTutor publication deadline. Generic routines all call ProcessCurrRsrc to fingerprint the resource over its entire length and then test only for trusted (Good) resources of that type.
Special Case Process_xxxs: Routines that dont fingerprint over the entire length of the resource, or that also test for known viral (Evil) resources, are published in this article.
Process_DATA: Most innocuous DATA resources contain volatile data, so we default to the known flag to TRUE. The Scores DATA resource is simply a copy of the Scores CODE resource, so we also have to skip its first 12 bytes (see LookForVirus_Scores, above).
Process_INIT: My suspicion that the Scores INITs might also store data internally was confirmed when different copies of the same INITs yielded different fingerprints. By analyzing raw memory dumps, I was able to determine that they skipped over variable parts by means of the hardware instruction BRA We must also skip over these parts, or else our fingerprints will vary and we wont be able to recognize the INITs.
The test for $60 is to detect the beginning of a BRA instruction. If the next byte is non-zero, it was a BRA.S, and we have to skip over the number of bytes in that non-zero byte. If the next byte is zero, we have to skip over the number of bytes in the extension word that follows the zero byte.
To complicate the task of defense, future viruses may use other instructions to skip over variable data. Variable parts may be imbedded at different places in the resource, perhaps even in a variable way. If so, well just have to write tests for those situations too. Even if the virus succeeds at making the fingerprint method too complicated, we will always find, thru detailed analysis, other identifying characteristics that we can test for. The advantage of bending the fingerprint technique in this case is the relative certainty of identification, which counter-balances the dangers inherent in deleting resources based on appearance. Yes, I agree, all of this is very tedious, but the alternative is vulnerability.
Commentary on Globals.p
CONSTs: To look up the low memory global values in Inside Macintosh, omit the initial k. The kIOBufferSize constant is used primarily by the KnownDataFork routine in Fingerprint.ipas. Under TML II, its possible to use conditional compilation directives instead of the kProcessSelf constant, but the result wouldnt be TML 2.5 compatible (violating one of my goals). The kRsrc constants are arbitrary values used by InitRsrc, etc, below, to detect failure to call other Rsrc routines. This allows the program to display a complaint exactly diagnosing the problem, rather than going haywire by processing garbage. The values only significance is that theyre unlikely to occur randomly in memory. Just about any value other than 0 or -1 will do.
TYPEs: TCounts is used to keep counts in a consistent way; its used mainly by the ListCounts routine, below. TFeedback is used to keep track of what was written to the screen and report file. JTE means Jump Table Entry. TLoaded, TResIdOrIndex, TMainItem and TMainOpt are enumerations to make the code more readable; the TMains give Main Dialog items descriptive names that can also be used in indexing. TRsrcRec is used to keep track of everything about a resource; I didnt want to undertake removing infected resources unless I kept a lot of information about them and handled them consistently (see InitRsrc, below). The TScores stuff is to get the JTE in bytes 4-11 (starting from 0) of the Scores CODE resource. TWordPtr is used just about everywhere.
VARs: gAbortPatrol and the gEvt stuff are used in small event loops in AbortPatrolIfCmdPeriodPressed and AwaitKeypress. gCode0 is used during PreprocessSelf in the main program and whenever the current file of the patrol contains CODE resources. gCounts and gTotals are used to keep counts within a patrol and overall, respectively. The gCurr variables track the current file, resource, etc of the patrol; most of them are passed to Globals by Patrol so it can know where we are in the patrol, but some are set within Globals. gDisabled and gOption are used to communicate with the user thru the Main Dialog; if gDisabled is set, the corresponding option cannot be changed by the user. gDlogPtr is used to keep track of the DialogPtr while the Main Dialog is hidden (usually during a patrol). gGrafPtr is used to save and restore TML Pascals plain vanilla/Textbook text window while the MainDlog is on the screen; to save processing, its saved only once, in InitGlobals. gInd is used for consistent indention. gSecsBegins and gSecsEnds are used in PatrolBegins and PatrolEnds to time the wall-clock duration of a patrol. gSFGetPt and gSFPutPt are used to center the SFGetFile and SFPutFile in the middle of the screen.
CallProcPtr: ProcessFile calls ProcessRsrcs with the address of the routine (a ProcPtr) to process each resource type; ProcessRsrcs has to use this INLINE to call that routine.
AbortPatrolIfCmdPeriodPressed: The major difference between this routine and the one that follows is the fact that AwaitKeypress stops the patrol until a key is pressed, while AbortPatrolIfCmdPeriodPressed checks for command-period on the fly. Both set the gAbortPatrol flag if command-period was pressed.
AwaitKeypress: This pauses the program (like REPEAT UNTIL KEYPRESS, which exists under TML 2.5 but not under TML II). In addition, it detects user request for cancellation with command-period.
Comment routines and Error routines: Error routines indent 2 deep (using gInd), comments 3. Error routines also have significance as far as beeping and awaiting are concerned.
DirectoryBegins: If theres something you want to do only once for a volume, such as check its boot blocks, this is a good place to do it, subordinate to an IF (gCurrDirId = 2) THEN. (If gCurrDirId is 2, youre at the root directory of the volume.)
FixedCode0: The way a virus jumps back into applications gives us clues as to how we might repair them. So far, weve been lucky to have JTEs lying around to repair them with (using this routine). In the future, who knows? Unfortunately, theres no shortcut that doesnt involve disassembly to find out how to restore an application. If a new virus comes along, and if you dont do disassembly, but you want to code your own restoration code immediately, be sure to find out how to restore an application correctly from someone who has disassembled it. [Fake jump table entries is something we all should be concerned about. -Ed]
InitGlobals: This routine initializes the VARs defined above. Note that zeroing out a BOOLEAN sets it to FALSE and zeroing out a string sets it to the null string. The old way of centering a window or dialog was to use screenBits.bounds, but that was from the days when everyone had only 1 screen. Since gGrafPtr contains the plain vanilla/Textbook windows GrafPtr, using gGrafPtr^.portBits.bounds will get the screen size of whatever screen its in.
InitRsrc, GetRsrc, ReleaseRsrc and RemovedRsrc: These routines are a higher level interface to the Resource Manager. Their use cycle is InitRsrc, then GetRsrc, then, when youre done with it, ReleaseRsrc or RemovedRsrc. You may then return in a tighter circle to GetRsrc again. If you try to use these routines in any order other than this, they will report an error and abort the program. Also, if you want to see the program crash unpredictably, try releasing resources when processing Active Self and Active System.
JTEIsValid: TML 2.5 generates a longword compare with sign extension if you use $A9F0, which doesnt compare correctly with the constant because its not sign extended, hence the -22032.
LookForKnownViruses: This routine calls itself recursively during virus CODE removal because of the possibility of multiple infections. For example, an application could have CODEs 0 (jump table), 1 (application), 3 (Scores), 256 (nVIR) and 258 (Scores). As long as some infections are still being found and removed, I see no reason why the process should stop.
ProcessCodes: Sometimes an application contains a CODE 256, but isnt infected by nVIR. An example is PackIt. Note that PackIt also skips a CODE ResId, as an application infected with Scores does. Which CODE do you check to see if its infected with Scores? Also, Im told that MacMoney has a CODE resource thats exactly 7026 bytes long, like Scores, and that LaserSpeed installs atpl and DATA resources into your System file, like Scores.
These are the sorts of situations that made me start using fingerprinting in the first place, but still you have the problem of determining whether or not the application is actually infected. If a viral CODE resource is present but not linked-to by the jump table, then CODE 0 is not damaged, and you dont want to try to repair it! That would damage it! ProcessCodes calls the LookFor routines using the CODE resource pointed to by the first JTE. This greatly simplifies these otherwise confusing situations.
ProcessCodes also looks for irregularities in the jump table that might signal tampering. It warns the user if the jump table doesnt start with the first CODE resource, or if it ever descends (that could signal that an earlier resource was a new one added by a virus), or if it ever skips a resource ID as it ascends. (If the CODE resource pointed-to by the JTE stays the same, it avoids the test to see if JTEIsValid, which saves a considerable amount of I/O.) All applications infected by nVIR and/or Scores violate all 3 of these conditions.
Unfortunately, applications generated by Lightspeed often have jump tables that violate 2 or more of these conditions. I dont have Lightspeed, so I couldnt come up with an elegant way to deal with that. Im open to suggestions. The programs inelegant solution is to have an exception list of applications whose jump tables are allowed to jump around all over the place. (The exception list uses the beginning of the application name in case theres a version number appended.) Its inelegant because they lose the benefit of the jump table irregularities search. They could get infected by some future virus and this program wouldnt detect anything suspicious in their jump tables.
ProcessFile: At present, the program fingerprints the data forks of only MacsBug and compiler object files. The latter is to guard against the possibility of a virus that doesnt go away even after recompilation, because its original infection source is an altered object file. RELBs are MDS-compatible .Rel files and OBJs are MPW-compatible .o files.
Also, at one time ROM patches were in the data fork of the System file. Now that there are PTCH resources, this may have been eliminated. If anyone knows whether or not the System file data fork can still contain executable code, please let everyone know by telling MacTutor. The same goes for executable code in any other, lesser-known file types data fork, of course. [Apple's system file data fork does indeed include executable code so this is another "danger" point in the system design that should be fingerprinted. -Ed]
A significant amount of code is to assure that we dont open and close the Active Self and Active System files. If you try treating them like any other file, the program will probably crash.
The Note Pad Files data is in its data fork, and the Scrapbook Files data is in its resource fork. The Scores virus adds INIT resources and alters their Type and Creator to conscript them for use with the INIT 31 mechanism documented in IM IV-256. But it doesnt throw away their contents. (While infected, youre still able to use both DAs, because they continue to access those files by name.) Thats why the program doesnt delete a file if either fork contains anything at all. The same deletion code will throw away the bogus Desktop and Scores files if it succeeds at removing all their infectious resources, incidentally.
Finally, you may ask, why dont we search for and destroy nVIRs nVIR resources? Well, earlier (during LookForKnownViruses) it may have detected an nVIR CODE resource but, for some reason, was unable to restore the application. So far, this hasnt ever happened, but if it ever did, the program would not have gotten to the code that removes the nVIR resources. One of those, nVIR 2, contains the JTE necessary to fix CODE 0. Because nVIR resources are not in themselves infectious, and because empty ones can be used to inoculate against nVIR (see the May 1988 MacTutor), you dont want to delete nVIR resources willy-nilly. The only time its really safe to do so is after successfully restoring an infected applications CODE 0. This is true in only one place (at the end of Disinfected_nVIR).
ProcessRsrcs: Routines in Fingerprint.ipas decide which resources are infected. ProcessRsrcs takes that determination on faith and removes the resource if gOption[eRmVir] is on. (This is used as a wholesale resource type remover at the end of Disinfected_nVIR.)
Also, notice that we dont bump the index if a resource is removed. Thats because the resource following it now assumes the removed resources position in numerical order as far as Get1IndResource is concerned. This same consideration applies when a file is deleted. (See Patrols CallProcessFile discussion of gCurrFileDeleted, below.)
ShortHexDump: This doesnt look right, but it is. Even though 0 is $30 and A is $40, you have to add $37 to get from 10 to A.
Commentary on MainDlog.p
In order for SecurityPatrol to have no resource types except CODE, we have to build the Main Dialogs DITL in memory. Its not hard to do, keeps our private globals in synch with the dialog and is actually a lot of fun. Really.
VARs: The gHdl array and gDBtnRect are used to keep from having to call GetDItem all the time in MainDlogWorkRequested and FrameDefaultBtn, respectively. gDBtnRect is a Rect inset by 4 bits to the outside of the default buttons Rect.
ChkChk: This sets the check boxes to the values represented by the gOption BOOLEANs. Note that the ORD of FALSE is 0 if and the ORD of TRUE is 1, a fact thats also used in building the DITL in InitMainDlog (to keep the Dialog Items word-aligned).
FrameDefaultBtn: This routine frames the default button in accordance with the User Interface Guidelines chapter of Inside Macintosh.
InitMainDlog: This is the routine builds the DITL, calls NewDialog and sets up the private globals to speed up MainDlogWorkRequested. The sRect and sTitle local variable arrays are used to build the DITL with a FOR loop; this seems wordy, but actually uses fewer lines than doing them linearly, and is much more readable and maintainable.
KybdEquivsFilter: This routine is a filterProc to give every Main Dialog button and check box a keyboard equivalent.
MainDlogWorkRequested: This is the routine that actually manages the Main Dialog user interface. On entry, the dialog will always be hidden (InitMainDlog calls NewDialog with the visible parameter FALSE, and previous calls to will have hidden it before exiting. BringToFront is called before ShowWindow to keep the BringToFront from occurring visibly on screen and annoying the user; it doesnt hurt anything to have an invisible window momentarily the front window. The WHILE loop manages check boxes to free up the main program from having to worry about those details. The DITL was built with check boxes after buttons, so the WHILE loop will terminate when a Scope Of Work button is pushed. It then returns a TMainItem enumeration value (defined in Globals) to tell the main program which scope was requested, so that itll know which Patrol routine to call.
Commentary on Patrol.p
Patrol is based on code examples that have already been published elsewhere, notably Tech Notes 24, 66, 68, 69 and 77, Inside Macintosh and earlier issues of MacTutor. It adds 2 new features: floating a Working Directory with the scan and keeping files in a directory together. Floating a Working Directory is to avoid overflowing 255 characters of partial pathname if the directories are deeply nested. (A virus creator might deliberately hide a virus deep in a series of nested folders to rely on other anti-virus utilities possible reliance on full pathnames. Such a virus could very easily be launched by a document at the root level.) Ive tested the floating Working Directory feature to 52 levels deep of nested folders, each with 31-character-long folder names and different filenames in each folder. As for keeping files in a directory together, previous directory scan algorithms were set up to recursively scan subdirectories right in the middle of the files being scanned, so you would see files from the parent directory, then files from a child directory, then more files from the parent directory, etc. Patrol doesnt go to subdirectories until it has patrolled all files in the current directory. This also minimizes opening and closing Working Directories.
kPatsInitd: This is an arbitrary value used by Patrol routines to detect failure to call InitPatrols. This allows the program to display a complaint exactly diagnosing the problem, rather than going haywire by processing garbage. The values only significance is that its unlikely to occur randomly in memory. Just about any value other than 0 or -1 will do.
TOverlappingPBs: For reasons unknown, Apple chose to define a wide variety of parameter blocks with mostly-the-same/sometimes-different field names. For the limited uses in Patrol, the only major difference between HParamBlockRec in CInfoPBRec is whether or not youre calling PBGetCatInfo. Rather than continually moving data around to keep their fields in synch, this TYPE allows defining PBs with which both types field names can be used. This has been okay (so far) because the only PB values that Patrol uses happen to match up between the 2 types. Occasionally it can be a bit confusing, but it keeps field name usage usually correct.
VARs (private): gAppDirId and gAppVRefNum are used to detect Active Self. gInitdFlag is used with kPatsInitd (see above) to detect failure to call InitPatrols. gOnly1Deep is used in PatrolDir to avoid processing subdirectories when the Scope Of Work is Directory (singular). gOrigWDRefNum is used to remember the wdRefNum of the first directory of the patrol (see FloatWDDeeper and FloatWDShallower, below). gPBs is used to do the Low Level File Manager calls to do the patrol itself. gSFLst is passed to the SFGetFile dialogs in PatrolDirectories and PatrolFiles. gSysDirId and gSysVRefNum are used to detect Active System. gWDPBRec is used by FloatWDDeeper and FloatWDShallower to open and close working directories.
BuildDirname: This routine is called only when the current directory being patrolled changes. It uses a local variable of type TOverlappingPBs (see above) so as not to disturb the values in gPBs, which is still being used by the patrol. Starting from the new current directory (as pointed to by gCurrWDRefNum), it works backwards to the root directory of the volume; for example, if the new current directory is A:B:C:, it would build C:, then B:C:, then A:B:C:. ioFDirIndex is set to -1 because were getting info about directories. (How to set ioFDirIndex is documented in Tech Note #69.) Initializing the loop by setting ioDrParID to 0 has no direct effect on the PBGetCatInfo call, because thats a field thats returned by the File Manager. Instead, its being used in combination with ioDrDirID := ioDrParID; to set ioDrDirID to 0, which does affect the PBGetCatInfo call. (It will start the loop at the directory pointed-to by ioVRefNum, not some other directory on the same volume.) This may seem like a roundabout way to start getting folder names at gCurrWDRefNum, but the same line (ioDrDirID := ioDrParID;) continues the loop upward thru the folder hierarchy, so its actually pretty straightforward. The loop proceeds until ioDrDirID is 2, signifying the root directory of the volume. (Note that this works correctly even if gCurrWDRefNum points to a root directory, since the File Manager also returns ioDrDirID; the loop will have exactly one pass, beginning with ioDrDirID equal to 0 and ending with it equal to 2.) The routine simply terminates if an error occurs, such as gCurrDirname overflow. This is relatively acceptable because the program never uses the pathname to access a file during a patrol; instead, it uses gCurrWDRefNum and gCurrFilename to access the file, and treats gCurrDirname as commentary for the user. InitSecurityPatrol in the main program uses gCurrDirname in the ReWrite that creates the report file. This is unfortunately necessary because there isnt a parameter for a wdRefNum in the ReWrite statement. On the bright side, the user is unlikely to place the report file more that 255 characters of full pathname deep.
CallProcessFile: Patrol centralizes all calls to ProcessFile via this routine to make absolutely sure that gActiveSelf and gActiveSys get set properly and that gCurrFileDeleted gets reset to FALSE. Correctly setting gActiveSelf and gActiveSys every time is vital to keep Securitys ProcessFile routine from closing their resource forks. Resetting gCurrFileDeleted to FALSE every time is vital to prevent skipping the file following the one deleted, which would assume the numerical position of the deleted file within the patrol (pointed to by ioFDirIndex).
FloatWDDeeper and FloatWDShallower (similarities): The problem with using pathnames to access files is the fact that you have only 255 characters to name it with. Since filenames and folder names can be up to 31 characters long, its possible to overflow that limit with folders nested only 8 deep.. Patrol gets past this problem by floating a working directory. As a general rule, both of them close the working directory pointed-to by gCurrWDRefNum, use a DirId to open another working directory, and put the new wdRefNum into gCurrWDRefNum. The difference is what the newly created working directory represents.
FloatWDDeeper: This routine creates a working directory to move from a parent directory to a subdirectory using the DirId encountered in the patrol (namely, gPBs.fCPBRec.ioDrDirID).
FloatWDShallower: This routine creates a working directory to return from a subdirectory to its parent. To do this, FloatWDShallower could use an ioDrParId returned by PBGetCatInfo (see BuildDirname, above), but theres a much simpler method. PatrolDir calls itself recursively, so when it returns from patrolling a subdirectory, its pDrDirId parameter automatically reverts to the DirId of the parent. FloatWDShallower uses this more readily available value.
FloatWDDeeper and FloatWDShallower (exception): The only exception to the rule relates to the start directory of the patrol. If its the root directory, PatrolDir starts out with a true vRefNum, not a wdRefNum. This is documented in Tech Note #77, which seemed to be warning us never to pass a vRefNum to PBCloseWD. (It didnt say so explicitly, but I havent tried it to see what would happen.) To get around this problem, FloatWDDeeper makes an exception at the start of the floats (leaving the start directory open) and FloatWDShallower makes an exception at the end (restoring the start directory with gOrigWDRefNum).
GetActualDirId: This routine assures that gCurrDirId will always be accurate, even when PatrolDir is called with pDrDirId equal to 0. Even though Globals doesnt currently use gCurrDirId, Patrol uses it to detect the Active Self and Active System.
InitPatrols: This routine makes sure that all of Patrols globals are properly initialized. The ZeroFillRange calls implicitly set all BOOLEAN variables to FALSE, all strings to the null string and all ioCompletion fields to NIL. Using kFSFCBLen to see if HFS is active is documented in Tech Note #66. Calling GetVRefNum to get gSysVRefNum is documented in Tech Note #77. Calling PBHGetVInfo and referencing ioVFndrInfo[1] to get gSysDirId is documented in Tech Note #67. Referencing kBootDrive instead if HFS is not active is also in #77.
PatrolDir: This routine is the heart and soul of Patrol. Its techniques are documented in Tech Note #69. If you understand that, youll understand this, but PatrolDirs way of doing things is considerably different. It patrols files first (ignoring subdirectories), then patrols subdirectories (ignoring files). Patrolling files uses PBGetFInfo (note: not PBHGetFInfo). Patrolling subdirectories uses PBGetCatInfo. There are three major advantages of doing it that way:
(1) filenames appear in an unbroken list,
(2) the program can work even if HFS isnt active,
(3) its easier to patrol only 1 directory deep.
There are other differences from Tech Note #69: It calls FloatWDDeeper, FloatWDShallower and GetActualDirId, for reasons discussed above. Also, if gError <> NoErr after a recursive call, it backs out of the recursion rapidly until it reaches the start directory of the patrol; this saves a lot of unnecessary calls to FloatWDShallower, since doing only the last one has the same effect as doing them all.
PatrolFiles: Ive gone to considerable trouble here to correctly call DirectoryEnds and DirectoryBegins whenever the user selects a file in a different directory from the previously selected file. Thats the purpose of the imbedded procedure CallPrevDirEnd. If you dont go to that trouble, the TFeedback variables in Globals wont get reset properly and the new directory name wont get displayed on the screen nor in the report file.
Commentary on SecurityPatrol.p
SecurityPatrol uses standard Pascal text I/O to report its findings and progress to the user. (In TML terminology, the TML 2.5 version is called a plain vanilla application and the TML II version is called a Textbook application.) The main program also manages initialization, termination and high-level interface between MainDlog and Patrol.
Because theres no event loop, the program doesnt support DAs (on-purpose). It also specifically disables FKEYs. The only form of concurrent code that it doesnt disallow is MultiFinder, but you shouldnt run it under MultiFinder anyway, because you wont be able to examine open files, such as the DeskTop file and the concurrent applications themselves (the Finder, eg).
Theres a very good reason why I dont like the idea of concurrently executing code. Let me give you a hypothetical scenario: Suppose someone imbedded a virus in a WDEF and installed that WDEF into the DeskTop file of a disk. I dont know how the Finder does things, but suppose it allows a runtime override of definition procedures using the standard Resource Manager precedence (document --> application --> System file). Youre patrolling files in foreground under MultiFinder, you get an SFGetFile dialog to patrol Directories, and you insert the infected disk. In background, the Finder reads the DeskTop file of the disk and displays the disks window behind the SecurityPatrol window using the override WDEF. Youve just been infected, and SecurityPatrol hasnt even had a chance to look at the disk yet. My guess is that the published version of SecurityPatrol wouldnt find anything on that disk. Whether or not your version would is up to you.
Of course, that scenario relies on a presupposition that the Finder allows a runtime override of WDEFs, which it probably doesnt do. On the other hand, I dont know for sure that it doesnt, and I dont like the uncontrolled variable it represents. Even if the Finder wouldnt let a virus thru this way, maybe some other concurrent application or DA would. I didnt go extraordinarily out of my way to prevent all concurrent code (VBLs, AppleTalk, etc), but I suspect theres a security hole there somewhere. Im voicing my concern here to focus more minds than my own on the subject. [Apple has also thought of this problem and is working on it presently. They just don't want the wrong kind of minds to be focused on it, if you know what I mean! -Ed]
(OUTPUT): The presence of this phrase in the program header tells TML 2.5 that this is a plain vanilla application. Before the main procedure starts to execute, the runtime library will initialize all the managers it needs for text I/O, create the WriteLn window and put TML Pascal into its drag bar title. Because it initializes the managers, we dont. Its ignored by TML II.
kPreprocessSelf and kAwaitVerification: Having kPreprocessSelf turned on can get to be a real nuisance during development, but its an important safeguard and better than processing Active Self during the patrols. You may want to turn it off during development and back on again in the final cleanup before release to your friends and/or companys users. The code controlled by kAwaitVerification will be cleaned up in Version 2.0: All of SecurityPatrols own CODE resources except the one containing Fingerprint.ipas should be fingerprinted and tested in Fingerprint.ipas. The verification task presented to the user could then be made much simpler.
ScrDmpEnbPtr and ScrDmpEnbSave: These are used in InitSecurityPatrol and ExitSecurityPatrol to save, disable and restore FKEYs.
{$Z*}: A TML 2.5 to TML II upgrade issue not mentioned in the TML II manuals Appendix F is the fact that main programs routine names, when referenced from a UNIT, will not be found at Link time unless you explicitly define them externally with this directive. Its documented in Appendix C, but not as a difference.
InitSecurityPatrol: Textbook is a TML II routine in PasLibIntf, distributed by TML Systems with the compiler. It initializes all the managers it needs for text I/O, creates the WriteLn window and puts the applications name into its drag bar title. Without it TML II would WriteLn all over the desktop. Because it initializes the managers, we dont. For TML 2.5 users, a dummy version of Textbook is defined in the PasLibIntf.Pas UNIT on the source code disk(s).
The initial values of the Main Dialog check boxes are set between the calls to InitGlobals and InitMainDlog.
When AppleTalk is not active, the statement ReWrite(o,PRINTER:) is used to send the report file to the printer. Under TML 2.5, this prints to a direct-connect ImageWriter on serial port B in streaming text mode, which is even more draft than draft mode (the ImageWriter doesnt recognize curly quotes, for example). Under TML II, this feature doesnt work, and the report file output will be, in effect, thrown away. Tom Leonard is working on the problem. In the meantime, you can save the report to a text file and print it later with MPW, which has the distinct advantage of also being able to print to a LaserWriter.
TML 2.5 allows TextFace calls to affect the WriteLn output. TML II doesnt, but it doesnt hurt.
PreprocessSelf: These are the most stringent tests I could think of to guard against the programs own infection. Youre invited to add more. If you leave in the line that turns on gOption[eRmVir], the program will disinfect itself of all viruses it knows how to remove. If you take out that line, it will detect known viruses and abort, so you should tell your users always to run it from a write protected disk.
TML 2.5 Link puts a JMP d(PC) instruction at the beginning of CODE 1 that jumps into the actual start address of SecurityPatrol. Thats why it checks for $4EFA and offsets by the extension word that follows. TML II is unaffected by this test.
Wryte routines: These routines manage text I/O. Under TML 2.5, Write, WriteLn, etc, can only appear in the main program, not in UNITs. This limitation was done away with in TML II, but this way of doing things is compatible. Moreover, by centralizing output, it allows calling PLFlush on every WriteLn.
PLFlush is a TML II routine in PasLibIntf. (See (OUTPUT), above.) It flushes a files buffer. PLFlush(OUTPUT); assures that screen feedback will be current. Alternatively, you could call PLSetVBuf (OUTPUT, NIL, $40, 256); in InitSecurityPatrol, but I like this stick-shift level of control, and it seems to work a little better.
TML 2.5 used to print numbers with a default field width of 1. TML II now uses the ANSI-standard Pascal default field width (6, I think). To avoid inconsistent results between the 2 versions of the program, WryteNbr never uses the default field width, but instead specifies field width explicitly.
zzSecurityPatrol: See the end of the commentary on CodeSizeLimits.p, above.
The source code disk for this issue (see the MacTutor Mail Order Store Ad) contains both the TNL 2.5 and TML II versions with the complete system file resource fingerprints for previous system files. We recommend purchase of this disk, only $8!
Continued in next frame
|
|
Volume Number: | | 5
|
Issue Number: | | 2
|
Column Tag: | | Advanced Mac'ing
|
Security Patrol for Viruses (code)
{ Copyright Header
©1988 by Steve Seaquist. All rights reserved. Used by permission.
Use at your own risk. No warranty is expressed or implied. Neither Apple
Computer nor MacTutor endorse or warrant this program in any way, nor
are they responsible for its use or mis-use in any way.
This Macintosh virus-detecting program was originally published and explained
in the February 1989 issue of MacTutor magazine. Some aspects of its
design are important to security, and it uses some unusual techniques,
so please read the article. }
CodeSizeLimits.p
UNIT CodeSizeLimits;
INTERFACE
VAR
gJTSize: INTEGER;
gEntryPoint: LONGINT;
gSizeLimit:
ARRAY [0..8] OF LONGINT;
gMaxCode: INTEGER;
PROCEDURE GetCodeSizeLimits;
IMPLEMENTATION
PROCEDURE zzSecurityPatrol; EXTERNAL;
PROCEDURE GetCodeSizeLimits;
BEGIN
gEntryPoint := ORD4(@zzSecurityPatrol)+$1A;
gJTSize := 1240;
gMaxCode := 8;
gSizeLimit[0] := gJTSize + 16;
gSizeLimit[1] := 15700;
gSizeLimit[2] := 23900;
gSizeLimit[3] := 11200;
gSizeLimit[4] := 00844;
gSizeLimit[5] := 01908;
gSizeLimit[6] := 01606;
gSizeLimit[7] := 01822;
gSizeLimit[8] := 01312;
END;
END.
Fingerprint.ipas
{ File Fingerprint.ipas }
VAR
gFgPr1,gFgPr2,gFgPr3,gFgPr4,gFgPr5,gFgPr6,gFgPr7: LONGINT;
PROCEDURE CommentFgPr;
BEGIN
Wryte (: ();
WryteNbr (gFgPr1,5);
WryteChar(,);
WryteNbr (gFgPr2,9);
WryteChar(,);
WryteNbr (gFgPr3,6);
WryteChar(,);
WryteNbr (gFgPr4,5);
WryteChar(,);
WryteNbr (gFgPr5,5);
WryteChar(,);
WryteNbr (gFgPr6,5);
WryteChar(,);
WryteNbr (gFgPr7,9);
WryteChar());
END;
PROCEDURE CommentFgPrData;
BEGIN
ErrorBegins(Unknown data fork);
CommentFgPr;
ErrorEnds(0);
END;
PROCEDURE CommentFgPrRsrc(pRsrcPtr: TRsrcPtr);
BEGIN
IF (gFgPrTitle <> ) THEN
BEGIN
ErrorMsg(gFgPrTitle,0);
gFgPrTitle := ;
END;
CommentRsrcBegins(pRsrcPtr);
CommentFgPr;
WryteEoln;
END;
FUNCTION Evil { Edited so that }
(p1,p2,p3: LONGINT) { calls fit into }
: BOOLEAN; { MacTutor column }
BEGIN { width. Source }
Evil := FALSE; { code disk }
IF (gFgPr1 = p1) { version tests }
AND (gFgPr2 = p2) { all 7 gFgPrs. }
AND (gFgPr3 = p3) THEN
WITH gCurrRsrc DO
BEGIN
Evil := TRUE;
fKnown := TRUE;
fInfected := TRUE;
gInfected := TRUE;
END;
END;
PROCEDURE FgPr(pPtr: Ptr; pSize: Size);
VAR
i,sTemp: LONGINT;
sWordPtr: TWordPtr;
PROCEDURE FgPrTemp;
BEGIN
gFgPr2 := gFgPr2 + sTemp;
gFgPr3 := BXor(gFgPr3,sTemp);
gFgPr4 := gFgPr4 + ORD(ODD(sTemp));
gFgPr5 := gFgPr5 + ORD(sTemp< 0);
gFgPr6 := gFgPr6 + ORD(sTemp> 0);
IF ODD(i) THEN
gFgPr7 := gFgPr7 + sTemp
ELSE
gFgPr7 := gFgPr7 - sTemp;
END;
BEGIN
sWordPtr := TWordPtr(pPtr);
FOR i := 1 TO (pSize DIV 2) DO
BEGIN
sTemp := ORD4(sWordPtr^);
FgPrTemp;
INC(LONGINT(sWordPtr));
INC(LONGINT(sWordPtr));
END;
IF ODD(pSize) THEN
BEGIN
sTemp := BAnd(ORD4(sWordPtr^),$FF00);
FgPrTemp;
END;
gFgPr1 := gFgPr1 + pSize;
END;
FUNCTION Good { Edited so that }
(p1,p2,p3: LONGINT) { calls fit into }
: BOOLEAN; { MacTutor column }
BEGIN { width. Source }
Good := FALSE; { code disk }
IF (gFgPr1 = p1) { version tests }
AND (gFgPr2 = p2) { all 7 gFgPrs. }
AND (gFgPr3 = p3)
AND (gFgPr4 = p4)
AND (gFgPr5 = p5)
AND (gFgPr6 = p6)
AND (gFgPr7 = p7) THEN
WITH gCurrRsrc DO
BEGIN
Good := TRUE;
fKnown := TRUE;
fInfected := FALSE;
END;
END;
PROCEDURE InitFgPr;
BEGIN
gFgPr1 := 0;
gFgPr2 := 0;
gFgPr3 := 0;
gFgPr4 := 0;
gFgPr5 := 0;
gFgPr6 := 0;
gFgPr7 := 0;
END;
FUNCTION KnownDataFork: BOOLEAN;
VAR
sBytesRemaining: LONGINT;
sBytesThisPass: LONGINT;
PROCEDURE KnownIF
(p1,p2,p3,p4,p5,p6,p7: LONGINT);
BEGIN
IF (gFgPr1 = p1)
AND (gFgPr2 = p2)
AND (gFgPr3 = p3) THEN
EXIT(KnownDataFork);
END;
BEGIN
KnownDataFork := TRUE;
InitFgPr;
sBytesRemaining := gCurrEOF;
WHILE sBytesRemaining > 0 DO
BEGIN
IF (sBytesRemaining > kIOBufferSize) THEN
sBytesThisPass := kIOBufferSize
ELSE
sBytesThisPass := sBytesRemaining;
gError := FSRead(gCurrRefNum,
sBytesThisPass,
gCurrIOBuffer);
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt read data fork);
EXIT(KnownDataFork);
END;
FgPr(gCurrIOBuffer,sBytesThisPass);
sBytesRemaining :=
sBytesRemaining - sBytesThisPass;
END;
KnownIF( 512, 1888894, 23566);{DRVRRuntime}
KnownIF(34304,122743292,-30376);{Interface}
KnownIF( 2560, 9884035,-13755);{ObjLib}
KnownIF( 8704, 38905655, -5215);{PerformLib}
KnownIF(22016,115306063, 12985);{Runtime}
KnownIF( 6656, 28028983,-22709);{ToolLibs}
KnownIF( 3072, 12526030,-25732);{HyperXCMD}
KnownIF( 2560, 11236115,-31819);{SANELib}
KnownIF( 4608, 16761354,-16062);{SANELib881}
KnownIF(15872, 62109326,-18394);{TMLPasLib}
KnownIF(27634,204503778, -8384);{MacsBug 5.5}
KnownDataFork := FALSE;
END;
PROCEDURE LookForVirus_nVIR;
BEGIN
IF (gCurrRsrc.fResId <> 256) THEN
EXIT(LookForVirus_nVIR);
gCurrRsrc.fInfected := TRUE;
ProcessCurrRsrc;
IF Evil( 372, 1334566,-15078) THEN EXIT;
IF Evil( 422, 1445005,-22775) THEN EXIT;
END;
PROCEDURE LookForVirus_Scores;
BEGIN
IF (gCurrRsrc.fSize < 7026) THEN
EXIT(LookForVirus_Scores);
InitFgPr;
FgPr(Ptr(ORD4(gCurrRsrc.fHdl^)+12),7014);
IF gOption[eFgPr] THEN
BEGIN
gFgPrTitle := Short fingerprint;
CommentFgPrRsrc(@gCurrRsrc);
END;
IF Evil( 7014, 32071691, 16777) THEN EXIT;
END;
PROCEDURE ProcessCurrRsrc;
BEGIN
InitFgPr;
FgPr(gCurrRsrc.fHdl^,gCurrRsrc.fSize);
END;
PROCEDURE ProcessRemoveRsrc;
BEGIN
gCurrRsrc.fKnown := TRUE;
gCurrRsrc.fInfected := TRUE;
END;
PROCEDURE Process_ADBS;
BEGIN
ProcessCurrRsrc;
IF Good( 262, 946915, -549) THEN EXIT;
END;
PROCEDURE Process_DATA;
BEGIN
WITH gCurrRsrc DO
BEGIN
fKnown := TRUE;
IF (fSize < 7026) THEN
EXIT(Process_DATA);
InitFgPr;
FgPr(Ptr(ORD4(fHdl^)+12),7014);
IF gOption[eFgPr] THEN
BEGIN
gFgPrTitle := Short fingerprint;
CommentFgPrRsrc(@gCurrRsrc);
END;
END;
IF Evil( 7014, 32071691, 16777) THEN EXIT;
END;
PROCEDURE Process_INIT;
VAR
sOffset: INTEGER;
sPtr: Ptr;
BEGIN
WITH gCurrRsrc DO
BEGIN
InitFgPr;
IF (fResId = 6)
OR ((fResId = 10)
AND (gCurrFilename <> Vaccine))
OR (fResId = 17) THEN
BEGIN
sOffset := 0;
sPtr := fHdl^;
IF (sPtr^ = $60) THEN
BEGIN
INC(LONGINT(sPtr));
IF (sPtr^ = 0) THEN
BEGIN
INC(LONGINT(sPtr));
sOffset := 2 + TWordPtr(sPtr)^;
END
ELSE
sOffset := 2 + sPtr^;
END;
FgPr(Ptr(ORD4(fHdl^)+sOffset),
fSize-sOffset);
IF gOption[eFgPr] THEN
BEGIN
gFgPrTitle := Short fingerprint;
CommentFgPrRsrc(@gCurrRsrc);
END;
END
ELSE
FgPr(fHdl^,fSize);
END;
IF Good( 2234, 5918345, 24783) THEN EXIT;
IF Good( 2, 20085, 20085) THEN EXIT;
IF Good( 580, 2390534, -3964) THEN EXIT;
IF Good( 256, 624295,-11467) THEN EXIT;
IF Good( 276, 821588,-22226) THEN EXIT;
IF Good( 318, 1106224, 15474) THEN EXIT;
IF Good( 372, 1325194, 13502) THEN EXIT;
IF Good( 262, 889886,-25106) THEN EXIT;
IF Good( 5200, 16204911,-21859) THEN EXIT;
IF Good( 514, 2113878,-13890) THEN EXIT;
IF Good( 264, 920108, -860) THEN EXIT;
IF Good( 436, 1746895, -3577) THEN EXIT;
IF Good( 358, 1167886, 15360) THEN EXIT;
IF Good( 26, 102477, -9939) THEN EXIT;
IF Good( 944, 3348718,-13868) THEN EXIT;
IF Good( 820, 2799073,-29445) THEN EXIT;
IF Good( 572, 1840164, -5246) THEN EXIT;
IF Evil( 366, 1333180,-29162) THEN EXIT;
IF Evil( 416, 1443619, -5115) THEN EXIT;
IF Evil( 758, 3291138, 6608) THEN EXIT;
IF Evil( 1014, 4600985, 19785) THEN EXIT;
IF Evil( 474, 1932041, 18387) THEN EXIT;
END;
PROCEDURE Process_atpl;
BEGIN
ProcessCurrRsrc;
IF Good( 4874, 17745131, 2851) THEN EXIT;
IF Evil( 2410, 10235053,-25635) THEN EXIT;
END;
Globals.p
UNIT Globals;
INTERFACE
USES
MemTypes,QuickDraw,OSIntf,ToolIntf, PackIntf;
CONST
{---- Low Mem Globals ----}
kCurApName = $910;
kCurApRefNum = $900;
kBootDrive = $210;
kResLoad = $A5E;
kScrDmpEnb = $2F8;
kSFCBLen = $3F6;
kSPConfig = $1FB;
kSysMap = $A58;
kSysResName = $AD8;
{---- Other constants ----}
kIOBufferSize = 10000;
kProcessSelf = FALSE;
kRsrcHdlValid = 9876543;
kRsrcIsInitd = 3456789;
kZeroOutVirs = TRUE;
TYPE
TCountsPtr = ^TCountsRec;
TCountsRec =
RECORD
fDeleted: LONGINT;
fExamined: LONGINT;
fFiles: LONGINT;
fInfected: LONGINT;
fRemoved: LONGINT;
fResources: LONGINT;
END;
TFeedbackPtr = ^TFeedbackRec;
TFeedbackRec =
PACKED RECORD
fWroteDirname: BOOLEAN;
fWroteFilename: BOOLEAN;
END;
TJTEHdl = ^TJTEPtr;
TJTEPtr = ^TJTERec;
TJTERec =
RECORD
fOffset: INTEGER;
fSkip3F3C: INTEGER;
fSegId: INTEGER;
fSkipA9F0: INTEGER;
END;
TJTHdl = ^TJTPtr;
TJTPtr = ^TJTRec;
TJTRec =
RECORD
fAboveA5Size: LONGINT;
fBelowA5Size: LONGINT;
fNbrBytesInTable: LONGINT;
fTableOffset: LONGINT;
fJTEntry:
ARRAY [1..1] OF TJTERec;
END;
TLoaded =(eNotYet,eAlreadyLoaded,eWeLoadedIt);
TMainItem =
(eNotADlogItem,
eDirs,eDiry,eEvery,eFiles,eQuit,
eAwait,eBeeps,eFgPr,eFgPrC,
eLList,eRmVir,eTrace,
eMain,eOpts,eScOW,
eDBtn);
TMainOpt = ARRAY [eAwait..eTrace] OF BOOLEAN;
TPaocRec = PACKED ARRAY[1..1] OF CHAR;
TResIdOrIndex = (ResId,Index);
TRsrcPtr = ^TRsrcRec;
TRsrcRec =
RECORD
fFlag: LONGINT;
fHdl: Handle;
fInfected: BOOLEAN;
fKnown: BOOLEAN;
fLoaded: TLoaded;
fResAttrs: INTEGER;
fResId: INTEGER;
fResType: ResType;
fSize: Size;
fState: SignedByte;
END;
TScoresHdl = ^TScoresPtr;
TScoresPtr = ^TScoresRec;
TScoresRec =
RECORD
fOffsetToFirstJTE:INTEGER;
fNbrJTEsForRsrc: INTEGER;
fOldJTE: TJTERec;
END;
TWordHdl = ^TWordPtr;
TWordPtr = ^INTEGER;
VAR
gAAGlobals: SignedByte;
gAbortPatrol,gActiveSelf,gActiveSys: BOOLEAN;
gCode0: TRsrcRec;
gCounts: TCountsRec;
gCurrDInfo: DInfo;
gCurrDirId,gCurrEOF: LONGINT;
gCurrDirname: Str255;
gCurrIOBuffer: Ptr;
gCurrFileDeleted: BOOLEAN;
gCurrFilename: Str255;
gCurrFInfo: FInfo;
gCurrIndex: INTEGER;
gCurrRefNum: INTEGER;
gCurrRsrc: TRsrcRec;
gCurrVRefNum: INTEGER;
gCurrWDRefNum: INTEGER;
gDateTimeRec: DateTimeRec;
gDisabled: TMainOpt;
gDlogPtr: DialogPtr;
gError: OSErr;
gEvt: EventRecord;
gEvtMask: INTEGER;
gFgPrTitle: Str255;
gGrafPtr: GrafPtr;
gHFS: BOOLEAN;
gInd: STRING[10];
gInfected: BOOLEAN;
gInfectedWritten: BOOLEAN;
gOption: TMainOpt;
gPgmrname: Str255;
gReportFlags: TFeedbackRec;
gScreenFlags: TFeedbackRec;
gSecsBegins: LONGINT;
gSecsEnds: LONGINT;
gSFGetPt: Point;
gSFPutPt: Point;
gSFRep: SFReply;
gTotals: TCountsRec;
gZZGlobals: SignedByte;
FUNCTION Code0IsValid: BOOLEAN;
PROCEDURE CommentBegins;
PROCEDURE CommentFgPrRsrc(pRsrcPtr: TRsrcPtr);
PROCEDURE CommentRsrcBegins(pRsrcPtr: TRsrcPtr);
PROCEDURE DirectoryBegins;
PROCEDURE DirectoryEnds;
PROCEDURE ErrorBegins(pStr: Str255);
PROCEDURE ErrorEnds(pBeeps: INTEGER);
PROCEDURE ErrorOSErr(pStr: Str255);
PROCEDURE GetRsrc
(pRsrcPtr: TRsrcPtr;
pResType: ResType;
pInt: INTEGER;
pIntIs: TResIdOrIndex);
PROCEDURE InitGlobals;
PROCEDURE InitRsrc(pRsrcPtr: TRsrcPtr);
FUNCTION JTEIsValid(pJTEPtr: TJTEPtr): BOOLEAN;
PROCEDURE ListCounts(pPtr: TCountsPtr);
PROCEDURE LookForKnownViruses;
PROCEDURE PauseBriefly;
PROCEDURE PatrolBegins;
PROCEDURE PatrolEnds;
PROCEDURE ProcessCurrRsrc;
PROCEDURE ProcessFile;
PROCEDURE ReleaseRsrc(pRsrcPtr: TRsrcPtr);
PROCEDURE ShortHexDump(pPtr: Ptr;pNbrBytes: SignedByte);
PROCEDURE Trace(pStr: Str255);
PROCEDURE TraceNbr(pStr: Str255;pNbr: LONGINT);
PROCEDURE ZeroOut(pStart: Ptr;pCount: Size);
PROCEDURE ZeroOutRange(p1: Ptr;p2: Ptr);
IMPLEMENTATION
{$R-}
PROCEDURE ExitSecurityPatrol; EXTERNAL;
PROCEDURE Wryte(pStr: Str255); EXTERNAL;
PROCEDURE WryteChar(pChar: CHAR); EXTERNAL;
PROCEDURE WryteEoln; EXTERNAL;
PROCEDURE WryteFilename; EXTERNAL;
PROCEDURE WryteFilenameToScreenOnlyForNow;EXTERNAL;
PROCEDURE WryteLn(pStr: Str255); EXTERNAL;
PROCEDURE WryteNbr(pNbr:LONGINT;pNbrDigits:INTEGER);EXTERNAL;
PROCEDURE WryteType(pType: ResType); EXTERNAL;
PROCEDURE CallProcPtr(pProcPtr: ProcPtr);
INLINE
$205F, { MOVE.L (A7)+,A0 }
$4E90; { JSR (A0) }
PROCEDURE ErrorInfected
(pStr: Str255); FORWARD;
PROCEDURE ErrorMsg(pStr:Str255;pBeeps:INTEGER); FORWARD;
FUNCTION FixedCode0(pJTPtr: TJTEPtr): BOOLEAN; FORWARD;
PROCEDURE ProcessRsrcs(pResType: ResType;pProcPtr: ProcPtr);
FORWARD;
FUNCTION RemovedRsrc(pRsrcPtr: TRsrcPtr):BOOLEAN; FORWARD;
PROCEDURE TraceRsrc(pStr: Str255;
pRsrcPtr: TRsrcPtr); FORWARD;
{$S Fingerprint}
{$I Fingerprint.ipas }
{$S Globals}
PROCEDURE AbortPatrolIfCmdPeriodPressed;
BEGIN
WHILE GetNextEvent(gEvtMask,gEvt) DO
WITH gEvt DO
IF (what = nullEvent) THEN
LEAVE
ELSE IF (what = keyDown) THEN
IF (BAnd(modifiers,cmdKey)=cmdKey)
AND (BAnd(message,charCodeMask)=$2E)
THEN
BEGIN
gAbortPatrol := TRUE;
WryteLn(Patrol aborted);
LEAVE;
END;
END;
PROCEDURE AwaitKeypress;
BEGIN
WHILE TRUE DO
BEGIN
IF NOT(GetNextEvent(gEvtMask,gEvt)) THEN
CYCLE;
WITH gEvt DO
IF (what = keyDown) THEN
BEGIN
IF (BAnd(modifiers,cmdKey)=cmdKey)
AND (BAnd(message,charCodeMask)=$2E)
THEN
BEGIN
gAbortPatrol := TRUE;
WryteLn(Patrol aborted);
END;
LEAVE;
END;
END;
END;
FUNCTION Code0IsValid: BOOLEAN;
BEGIN
IF gOption[eTrace] THEN
Trace(Code0IsValid);
WITH TJTHdl(gCode0.fHdl)^^ DO
Code0IsValid :=
(gCode0.fSize >= 24) AND
(fAboveA5Size >= 40) AND
(fNbrBytesInTable >= 8) AND
(fTableOffset = 32) AND
(fAboveA5Size = fNbrBytesInTable+32) AND
((fNbrBytesInTable MOD 8) = 0);
END;
PROCEDURE CommentBegins;
BEGIN
Wryte(gInd);
Wryte(gInd);
Wryte(gInd);
END;
PROCEDURE CommentRsrcBegins(pRsrcPtr: TRsrcPtr);
BEGIN
CommentBegins;
WITH pRsrcPtr^ DO
BEGIN
WryteType(fResType);
WryteNbr (fResId,7);
Wryte ( ();
ShortHexDump(Ptr(ORD4(@fResAttrs)+1),1);
WryteChar());
END;
END;
PROCEDURE CountInfected;
BEGIN
IF gOption[eTrace] THEN
Trace(CountInfected);
INC(gCounts.fInfected);
INC(gTotals.fInfected);
END;
PROCEDURE DirectoryBegins;
BEGIN
IF gOption[eTrace] THEN
Trace(DirectoryBegins);
gReportFlags.fWroteDirname := FALSE;
gScreenFlags.fWroteDirname := FALSE;
END;
PROCEDURE DirectoryEnds;
BEGIN
IF gOption[eTrace] THEN
Trace(DirectoryEnds);
(*
Wryte (End of );
WryteLn(gCurrDirname);
*)
END;
FUNCTION Disinfected_nVIR: BOOLEAN;
VAR
snVIR2: TRsrcRec;
sCodeGone: BOOLEAN;
BEGIN
IF gOption[eTrace] THEN
Trace(Disinfected_nVIR);
Disinfected_nVIR := FALSE;
InitRsrc(@snVIR2);
GetRsrc (@snVIR2,nVIR,2,ResId);
WITH snVIR2 DO
BEGIN
IF (fFlag <> kRsrcHdlValid) THEN
BEGIN
ErrorInfected(No nVIR 2!);
ReleaseRsrc(@gCurrRsrc);
EXIT(Disinfected_nVIR);
END;
IF (fSize < 8) THEN
BEGIN
ErrorInfected(Too small nVIR 2!);
ReleaseRsrc(@gCurrRsrc);
ReleaseRsrc(@snVIR2);
EXIT(Disinfected_nVIR);
END;
MoveHHi(fHdl);
HLock (fHdl);
IF NOT(FixedCode0(TJTEPtr(fHdl^))) THEN
BEGIN
ReleaseRsrc(@gCurrRsrc);
ReleaseRsrc(@snVIR2);
EXIT(Disinfected_nVIR);
END;
Disinfected_nVIR := TRUE;
sCodeGone := RemovedRsrc(@gCurrRsrc);
ReleaseRsrc(@snVIR2);
ProcessRsrcs(nVIR,@ProcessRemoveRsrc);
IF sCodeGone
AND (Count1Resources(nVIR) = 0) THEN
ErrorMsg(nVIR removed,0)
ELSE
BEGIN
ErrorMsg(nVIR disinfected:,0);
CommentBegins;
Wryte (All of its resources are now );
Wryte (harmless, but some were not );
WryteLn(removed, for some reason.);
END;
END;
END;
FUNCTION Disinfected_Scores: BOOLEAN;
BEGIN
IF gOption[eTrace] THEN
Trace(Disinfected_Scores);
Disinfected_Scores := FALSE;
WITH gCurrRsrc DO
BEGIN
MoveHHi(fHdl);
HLock (fHdl);
WITH TScoresHdl(fHdl)^^ DO
IF NOT(FixedCode0(@fOldJTE)) THEN
BEGIN
ReleaseRsrc(@gCurrRsrc);
EXIT(Disinfected_Scores);
END;
Disinfected_Scores := TRUE;
IF RemovedRsrc(@gCurrRsrc) THEN
ErrorMsg(Scores removed,0)
ELSE
ErrorMsg(Scores disinfected,0);
END;
END;
PROCEDURE ErrorBegins(pStr: Str255);
BEGIN
WryteFilename;
Wryte (gInd);
Wryte (gInd);
Wryte (pStr);
END;
PROCEDURE ErrorEnds(pBeeps: INTEGER);
VAR
i,sBeeps: INTEGER;
BEGIN
IF gOption[eBeeps] THEN
BEGIN
IF (pBeeps > 4) THEN
sBeeps := 4
ELSE
sBeeps := pBeeps;
FOR i := 1 TO sBeeps DO
SysBeep(3);
END;
IF gOption[eAwait] THEN
BEGIN
WryteLn( (WAITING ON KEY PRESS));
AwaitKeypress;
END
ELSE
WryteEoln;
END;
PROCEDURE ErrorInfected(pStr: Str255);
BEGIN
IF NOT(gInfectedWritten) THEN
BEGIN
ErrorBegins(**!INFECTED!** );
WryteEoln;
gInfectedWritten := TRUE;
END;
IF (pStr <> ) THEN
BEGIN
CommentBegins;
Wryte(pStr);
ErrorEnds(3);
END;
END;
PROCEDURE ErrorMsg(pStr: Str255;pBeeps: INTEGER);
BEGIN
ErrorBegins(pStr);
ErrorEnds(pBeeps);
END;
PROCEDURE ErrorOSErr(pStr: Str255);
BEGIN
IF (pStr <> ) THEN
BEGIN
ErrorBegins(pStr);
WryteEoln;
END;
CommentBegins;
Wryte (OSErr code = );
WryteNbr(gError,1);
ErrorEnds(2);
END;
FUNCTION FixedCode0(pJTPtr: TJTEPtr): BOOLEAN;
BEGIN
FixedCode0 := FALSE;
IF gOption[eTrace] THEN
Trace(FixedCode0);
IF NOT(JTEIsValid(pJTPtr)) THEN
BEGIN
ErrorInfected(Bad Jump Table Entry!);
EXIT(FixedCode0);
END;
IF NOT(gOption[eRmVir]) THEN
BEGIN
ErrorInfected(Remove option off);
CommentBegins;
WITH pJTPtr^ DO
BEGIN
Wryte (Jumps to );
WryteNbr(fOffset,1);
Wryte ( of CODE );
WryteNbr(fSegId,1);
WryteEoln;
END;
ErrorMsg(Not removed,1);
EXIT(FixedCode0);
END;
WITH gCode0 DO
BEGIN
IF gOption[eTrace] THEN
BEGIN
Trace(About to restore CODE 0);
AbortPatrolIfCmdPeriodPressed;
IF gAbortPatrol THEN
EXIT(FixedCode0);
END;
TJTHdl(fHdl)^^.fJTEntry[1] := pJTPtr^;
IF (BAnd(fResAttrs,resProtected) <> 0)
AND (fResAttrs <> -1) THEN
BEGIN
SetResAttrs(fHdl,0);
ChangedResource(fHdl);
gError := ResError;
SetResAttrs(fHdl,fResAttrs);
END
ELSE
BEGIN
ChangedResource(fHdl);
gError := ResError;
END;
IF (gError <> NoErr) THEN
BEGIN
ErrorInfected(CODE 0 unchanged!);
IF (gError = wPrErr) THEN
ErrorMsg(Disk is locked,0)
ELSE
ErrorOSErr();
gError := 0;
EXIT(FixedCode0);
END;
WriteResource(fHdl);
gError := ResError;
IF (gError <> NoErr) THEN
BEGIN
ErrorInfected(CODE 0 unwritten!);
ErrorOSErr();
EXIT(FixedCode0);
END;
END;
FixedCode0 := TRUE;
END;
PROCEDURE GetRsrc
(pRsrcPtr: TRsrcPtr;
pResType: ResType;
pInt: INTEGER;
pIntIs: TResIdOrIndex);
VAR
sName: Str255;
sResLoad: BOOLEAN;
PROCEDURE CommentWhich;
BEGIN
CommentBegins;
WryteType(pResType);
WryteChar( );
WryteNbr (pInt,1);
IF (pIntIs = Index) THEN
Wryte ( (indexed));
WryteEoln;
END;
BEGIN
WITH pRsrcPtr^ DO
BEGIN
IF (fFlag <> kRsrcIsInitd) THEN
BEGIN
ErrorMsg(Logic error using GetRsrc,4);
AwaitKeypress;
ExitSecurityPatrol;
END;
fResType := pResType;
fResId := pInt;
sResLoad := (TWordPtr(kResLoad)^ <> 0);
IF (gActiveSelf OR gActiveSys) THEN
SetResLoad(FALSE);
IF (pIntIs = Index) THEN
BEGIN
IF gOption[eTrace] THEN
TraceRsrc(About to get ind,pRsrcPtr);
fHdl := Get1IndResource(pResType,pInt);
END
ELSE
BEGIN
IF gOption[eTrace] THEN
TraceRsrc(About to get,pRsrcPtr);
fHdl := Get1Resource(pResType,pInt);
END;
IF sResLoad THEN
BEGIN
IF (gActiveSelf OR gActiveSys) THEN
SetResLoad(TRUE);
IF (fHdl = NIL)
OR (ORD4(fHdl) = -1) THEN
BEGIN
gError := ResError;
ErrorOSErr(Couldnt get resource);
CommentWhich;
InitRsrc(pRsrcPtr);
EXIT(GetRsrc);
END;
fFlag := kRsrcHdlValid;
fResAttrs := GetResAttrs(fHdl);
IF (ResError <> NoErr) THEN
fResAttrs := -1;
IF (fHdl^ = NIL) THEN
BEGIN
LoadResource(fHdl);
fLoaded := eWeLoadedIt;
IF gOption[eTrace] THEN
Trace(We loaded it);
END
ELSE
BEGIN
fLoaded := eAlreadyLoaded;
IF gOption[eTrace] THEN
Trace(Already loaded);
END;
IF (fHdl^ = NIL) THEN
BEGIN
gError := ResError;
IF (gError <> NoErr) THEN
BEGIN
ErrorMsg(Couldnt load resource,0);
IF (gError = memFullErr) THEN
ErrorMsg(No room in heap zone,1)
ELSE
ErrorOSErr();
CommentWhich;
ReleaseRsrc(pRsrcPtr);
EXIT(GetRsrc);
END;
END;
fSize := SizeResource(fHdl);
END
ELSE
BEGIN
fFlag := kRsrcHdlValid;
fSize := MaxSizeRsrc(fHdl);
fLoaded := eNotYet;
IF gOption[eTrace] THEN
Trace(No-load get, loaded not yet);
END;
IF (pIntIs = Index) THEN
BEGIN
GetResInfo(fHdl,fResId,fResType,sName);
gError := ResError;
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt get resource id);
CommentWhich;
ReleaseRsrc(pRsrcPtr);
EXIT(GetRsrc);
END;
END;
IF sResLoad THEN
BEGIN
fState := HGetState(fHdl);
IF ((fResType = CODE)
AND (fResId = 0)) THEN
BEGIN
MoveHHi(fHdl);
HLock (fHdl);
END
ELSE
HNoPurge(fHdl);
END;
END;
IF gOption[eTrace] THEN
TraceRsrc(Got,pRsrcPtr);
INC(gCounts.fResources);
INC(gTotals.fResources);
END;
PROCEDURE InitGlobals;
VAR
sGetHdl,sPutHdl: DialogTHndl;
sGetSize,sPutSize,sScrnSize: Point;
BEGIN
ZeroOutRange(@gAAGlobals,@gZZGlobals);
gCurrIOBuffer := NewPtr(kIOBufferSize);
InitRsrc(@gCode0);
InitRsrc(@gCurrRsrc);
gEvtMask :=
everyEvent - (updateMask + activMask);
GetPort(gGrafPtr);
gInd := ;
sGetHdl := DialogTHndl(GetResource(DLOG,getDlgID));
IF (sGetHdl = NIL)
OR (LONGINT(sGetHdl) = -1) THEN
SetPt(sGetSize,304,104)
ELSE
BEGIN
IF (sGetHdl^ = NIL) THEN
LoadResource(Handle(sGetHdl));
sGetSize := sGetHdl^^.boundsRect.botRight;
ReleaseResource(Handle(sGetHdl));
END;
sPutHdl := DialogTHndl(GetResource(DLOG,putDlgID));
IF (sPutHdl = NIL)
OR (LONGINT(sPutHdl) = -1) THEN
SetPt(sPutSize,348,136)
ELSE
BEGIN
IF (sPutHdl^ = NIL) THEN
LoadResource(Handle(sPutHdl));
sPutSize := sPutHdl^^.boundsRect.botRight;
ReleaseResource(Handle(sPutHdl));
END;
WITH gGrafPtr^.portBits.bounds DO
BEGIN
sScrnSize.h := right-left;
sScrnSize.v := bottom-top;
END;
gSFGetPt.h := (sScrnSize.h-sGetSize.h) DIV 2;
gSFGetPt.v := (sScrnSize.v-sGetSize.v) DIV 2;
gSFPutPt.h := (sScrnSize.h-sPutSize.h) DIV 2;
gSFPutPt.v := (sScrnSize.v-sPutSize.v) DIV 2;
END;
PROCEDURE InitRsrc (pRsrcPtr: TRsrcPtr);
BEGIN
ZeroOut(Ptr(pRsrcPtr),SIZEOF(TRsrcRec));
pRsrcPtr^.fFlag := kRsrcIsInitd;
END;
FUNCTION JTEIsValid (pJTEPtr: TJTEPtr): BOOLEAN;
VAR
sCode: TRsrcRec;
BEGIN
IF gOption[eTrace] THEN
Trace(JTEIsValid);
JTEIsValid := FALSE;
WITH pJTEPtr^, sCode DO
BEGIN
InitRsrc(@sCode);
SetResLoad(FALSE);
GetRsrc(@sCode,CODE,fSegId,ResId);
SetResLoad(TRUE);
IF (fFlag <> kRsrcHdlValid) THEN
EXIT(JTEIsValid);
JTEIsValid :=
(fSkip3F3C = $3F3C) AND
(fSegId > 0) AND
(fSkipA9F0 = -22032) AND { $A9F0 }
(fSize > 0);
ReleaseRsrc(@sCode);
END;
END;
PROCEDURE ListCounts(pPtr: TCountsPtr);
BEGIN
IF gOption[eTrace] THEN
Trace(CountsListing);
WITH pPtr^ DO
BEGIN
WryteLn (Files:);
WryteNbr(fFiles, 6);
WryteLn ( processed);
WryteNbr(fExamined,6);
WryteLn ( examined);
WryteNbr(fDeleted, 6);
WryteLn ( deleted);
WryteLn (Resources:);
WryteNbr(fResources,6);
WryteLn ( processed);
WryteNbr(fInfected, 6);
WryteLn ( infected);
WryteNbr(fRemoved, 6);
WryteLn ( removed);
Wryte (Currently available memory is );
WryteNbr(MemAvail DIV 1024,1);
WryteLn (K.);
PauseBriefly;
END;
END;
PROCEDURE LookForKnownViruses;
VAR
sWeUsedToBeInfected: BOOLEAN;
PROCEDURE Get1stCode;
BEGIN
WITH TJTHdl(gCode0.fHdl)^^.fJTEntry[1] DO
BEGIN
GetRsrc(@gCurrRsrc,CODE,fSegId,ResId);
IF (gCurrRsrc.fFlag<>kRsrcHdlValid) THEN
BEGIN
ErrorInfected(Couldnt get 1st CODE);
InitRsrc(@gCurrRsrc);
EXIT(LookForKnownViruses);
END;
END;
END;
BEGIN
IF gOption[eTrace] THEN
Trace(LookForKnownViruses);
sWeUsedToBeInfected := FALSE;
Get1stCode;
WITH gCurrRsrc DO
BEGIN
LookForVirus_nVIR;
IF fInfected THEN
BEGIN
CountInfected;
IF fKnown AND (fSize = 372) THEN
ErrorInfected(nVIR 372 virus)
ELSE IF fKnown AND (fSize = 422) THEN
ErrorInfected(nVIR 422 virus)
ELSE
BEGIN
ErrorInfected(New nVIR virus!);
gFgPrTitle := ;
CommentFgPrRsrc(@gCurrRsrc);
END;
IF Disinfected_nVIR THEN
sWeUsedToBeInfected := TRUE;
Get1stCode;
END;
LookForVirus_Scores;
IF fKnown AND fInfected THEN
BEGIN
CountInfected;
ErrorInfected(Scores virus);
IF Disinfected_Scores THEN
sWeUsedToBeInfected := TRUE;
END
ELSE
ReleaseRsrc(@gCurrRsrc);
END;
IF sWeUsedToBeInfected THEN
LookForKnownViruses;
END;
PROCEDURE PatrolBegins;
BEGIN
IF gOption[eTrace] THEN
Trace(PatrolBegins);
WryteEoln;
WryteLn(*******************************);
ZeroOut(@gCounts,SIZEOF(TCountsRec));
GetDateTime(gSecsBegins);
END;
PROCEDURE PatrolEnds;
VAR
sMins,sSecs: INTEGER;
BEGIN
GetDateTime(gSecsEnds);
sSecs := gSecsEnds - gSecsBegins;
sMins := sSecs DIV 60;
sSecs := sSecs - (sMins * 60);
WryteEoln;
WryteLn(*******************************);
WryteEoln;
Wryte (End of patrol that took );
WryteNbr(sMins,1);
WryteChar(:);
IF (sSecs < 10) THEN
BEGIN
WryteChar(0);
WryteNbr (sSecs,1);
END
ELSE
WryteNbr (sSecs,2);
WryteEoln;
ListCounts(@gCounts);
END;
PROCEDURE PauseBriefly;
VAR
sTicks: LONGINT;
BEGIN
Delay(120,sTicks);
END;
PROCEDURE ProcessCodes;
VAR
i,sNbrEntries,sPrevId: INTEGER;
sWeirdCode0: BOOLEAN;
PROCEDURE CommentWhere;
BEGIN
CommentBegins;
Wryte (At entry );
WryteNbr(i,1);
WryteEoln;
END;
BEGIN
IF gOption[eTrace] THEN
Trace(ProcessCodes);
GetRsrc(@gCode0,CODE,0,ResId);
IF (gCode0.fFlag <> kRsrcHdlValid) THEN
BEGIN
ErrorMsg(Code rsrcs without CODE 0,1);
EXIT(ProcessCodes);
END;
IF NOT(Code0IsValid) THEN
BEGIN
ErrorMsg(Unexpected CODE 0 values,1);
ReleaseRsrc(@gCode0);
EXIT(ProcessCodes);
END;
LookForKnownViruses;
WITH TJTHdl(gCode0.fHdl)^^ DO
BEGIN
sNbrEntries := fNbrBytesInTable DIV 8;
sPrevId := 1;
sWeirdCode0 :=
(COPY(gCurrFilename,1,9)=Red Ryder) OR
(COPY(gCurrFilename,1,6)=Canvas ) OR
(COPY(gCurrFilename,1,9)=PageMaker);
FOR i := 1 TO sNbrEntries DO
WITH fJTEntry[i] DO
BEGIN
IF (fSkip3F3C = $3F3C)
AND (fSegId = sPrevId)
AND (fSkipA9F0 = -22032) THEN
CYCLE;
AbortPatrolIfCmdPeriodPressed;
IF gAbortPatrol THEN
LEAVE;
IF NOT(JTEIsValid(@fJTEntry[i])) THEN
BEGIN
ErrorMsg(CODE 0 has invalid JTE,1);
CommentWhere;
LEAVE;
END;
IF sWeirdCode0 THEN
BEGIN
sPrevId := fSegId;
CYCLE;
END;
IF (fSegId < sPrevId) THEN
BEGIN
ErrorMsg(JT not ascending,1);
CommentWhere;
LEAVE;
END;
INC(sPrevId);
IF (fSegId = sPrevId) THEN
CYCLE;
ErrorMsg(JT skips ResId,1);
CommentWhere;
LEAVE;
END;
END;
ReleaseRsrc(@gCode0);
END;
PROCEDURE ProcessFile;
VAR
sSaveC1T: INTEGER;
PROCEDURE ExitIfCantReadFork;
BEGIN
IF (gError <> NoErr) THEN
BEGIN
IF (gError = eofErr) THEN
{ no resource fork }
ELSE IF (gError = fnfErr) THEN
ErrorMsg(File not found,1)
ELSE IF (gError = nsvErr) THEN
ErrorMsg(No such volume,1)
ELSE IF (gError = opWrErr) THEN
ErrorMsg(CONCAT(Already in use. ,
(Dont use under MultiFinder!)),1)
ELSE
ErrorOSErr(Couldnt open file);
gError := NoErr;
EXIT(ProcessFile);
END;
END;
BEGIN
IF gOption[eTrace] THEN
Trace(ProcessFile);
AbortPatrolIfCmdPeriodPressed;
IF gAbortPatrol THEN
EXIT(ProcessFile);
INC(gCounts.fFiles);
INC(gTotals.fFiles);
gInfected := FALSE;
gInfectedWritten := FALSE;
gReportFlags.fWroteFilename := FALSE;
gScreenFlags.fWroteFilename := FALSE;
IF gOption[eLList] THEN
WryteFilename
ELSE
WryteFilenameToScreenOnlyForNow;
IF (LENGTH(gCurrFilename) > 0) THEN
IF (gCurrFilename[1] = .) THEN
BEGIN
ErrorMsg(Filename begins with .,1);
EXIT(ProcessFile);
END;
IF gActiveSelf AND NOT(kProcessSelf) THEN
EXIT(ProcessFile);
gCurrEOF := -1;
gError := FSOpen(gCurrFilename,gCurrWDRefNum, gCurrRefNum);
ExitIfCantReadFork;
gError := GetEOF(gCurrRefNum,gCurrEOF);
IF (gError = NoErr) THEN
BEGIN
WITH gCurrFInfo DO
IF (COPY(gCurrFilename,1,7)=MacsBug)
OR (fdType = RELB)
OR (fdType = OBJ ) THEN
IF NOT(KnownDataFork) THEN
CommentFgPrData;
gError := FSClose(gCurrRefNum);
IF (gError <> NoErr) THEN
ErrorOSErr(Couldnt close data fork);
END
ELSE
ErrorOSErr(Couldnt GetEOF);
IF gActiveSelf THEN
BEGIN
gCurrRefNum := TWordPtr(kCurApRefNum)^;
gError := NoErr;
END
ELSE IF gActiveSys THEN
BEGIN
gCurrRefNum := TWordPtr(kSysMap)^;
gError := NoErr;
END
ELSE
BEGIN
SetResLoad(FALSE);
gCurrRefNum := OpenRFPerm(gCurrFilename,gCurrWDRefNum,
fsRdWrPerm);
gError := ResError;
SetResLoad(TRUE);
ExitIfCantReadFork;
END;
IF (gCurrRefNum <> CurResFile) THEN
BEGIN
UseResFile(gCurrRefNum);
gError := ResError;
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt use resource fork);
gError := NoErr;
EXIT(ProcessFile);
END;
END;
INC(gCounts.fExamined);
INC(gTotals.fExamined);
IF (Count1Resources(CODE) > 0) THEN
ProcessCodes;
gFgPrTitle := Unknown Resource(s):;
ProcessRsrcs(ADBS,@Process_ADBS);
ProcessRsrcs(CACH,@Process_CACH);
ProcessRsrcs(CDEF,@Process_CDEF);
{etc}
IF gOption[eFgPr] THEN
BEGIN
gFgPrTitle := Fingerprint(s):;
ProcessRsrcs(ADBS,@ProcessCurrRsrc);
ProcessRsrcs(CACH,@ProcessCurrRsrc);
ProcessRsrcs(CDEF,@ProcessCurrRsrc);
IF gOption[eFgPrC] THEN
ProcessRsrcs(CODE,@ProcessCurrRsrc);
ProcessRsrcs(DATA,@ProcessCurrRsrc);
{etc}
ProcessRsrcs(nVIR,@ProcessCurrRsrc);
END;
IF gActiveSelf OR gActiveSys THEN
EXIT(ProcessFile);
sSaveC1T := Count1Types;
CloseResFile(gCurrRefNum);
IF NOT(gInfected) THEN
EXIT(ProcessFile);
WITH gCurrFInfo DO
BEGIN
IF ((gCurrFilename = Note Pad File)
OR (gCurrFilename = Scrapbook File))
AND (fdCreator = ZSYS)
AND gOption[eRmVir] THEN
BEGIN
fdType := ZSYS;
fdCreator := MACS;
fdFlags := 4096;
gError := SetFInfo(gCurrFilename,
gCurrWDRefNum,
gCurrFInfo);
IF (gError = NoErr) THEN
ErrorMsg(Reset to system document,0)
ELSE
ErrorOSErr(FInfo not reset);
EXIT(ProcessFile);
END;
END;
IF (gCurrEOF <> 0) THEN
BEGIN
ErrorMsg(File still has data fork,0);
ErrorMsg(File not deleted,1);
EXIT(ProcessFile);
END;
IF (sSaveC1T <> 0) THEN
BEGIN
ErrorMsg(File still has resources,0);
ErrorMsg(File not deleted,1);
EXIT(ProcessFile);
END;
ErrorMsg(File emptied,0);
gError := FSDelete(gCurrFilename,gCurrWDRefNum);
IF (gError = NoErr) THEN
BEGIN
gCurrFileDeleted := TRUE;
INC(gCounts.fDeleted);
INC(gTotals.fDeleted);
ErrorMsg(File deleted,1);
END
ELSE
ErrorOSErr(File not deleted);
END;
PROCEDURE ProcessRsrcs(pResType:ResType; pProcPtr: ProcPtr);
VAR
i,sIdx: INTEGER;
BEGIN
IF gOption[eTrace] THEN
Trace(ProcessRsrcs);
WITH gCurrRsrc DO
BEGIN
sIdx := 1;
FOR i := 1 TO Count1Resources(pResType) DO
BEGIN
AbortPatrolIfCmdPeriodPressed;
IF gAbortPatrol THEN
LEAVE;
GetRsrc(@gCurrRsrc,pResType,sIdx,Index);
IF (fFlag <> kRsrcHdlValid) THEN
BEGIN
INC(sIdx);
CYCLE;
END;
CallProcPtr(pProcPtr);
IF fInfected THEN
BEGIN
CountInfected;
ErrorInfected();
CommentRsrcBegins(@gCurrRsrc);
WryteLn( is an infection);
IF RemovedRsrc(@gCurrRsrc) THEN
BEGIN
ErrorMsg(Removed,0);
CYCLE;
END;
ErrorMsg(Not removed,1);
INC(sIdx);
CYCLE;
END;
IF NOT(fKnown) THEN
CommentFgPrRsrc(@gCurrRsrc);
ReleaseRsrc(@gCurrRsrc);
INC(sIdx);
END;
END;
END;
PROCEDURE ReleaseRsrc(pRsrcPtr: TRsrcPtr);
BEGIN
WITH pRsrcPtr^ DO
BEGIN
IF (fFlag <> kRsrcHdlValid) THEN
BEGIN
ErrorMsg(Error using ReleaseRsrc,4);
AwaitKeypress;
ExitSecurityPatrol;
END;
IF gOption[eTrace] THEN
TraceRsrc(About to release,pRsrcPtr);
IF (gActiveSelf OR gActiveSys) THEN
IF gOption[eTrace] THEN
Trace(Not Released)
ELSE
ELSE
BEGIN
HSetState(fHdl,fState);
ReleaseResource(fHdl);
IF gOption[eTrace] THEN
Trace(Released);
END;
InitRsrc(pRsrcPtr);
END;
END;
FUNCTION RemovedRsrc(pRsrcPtr: TRsrcPtr): BOOLEAN;
VAR
sBits0and7:LONGINT;
PROCEDURE ExitIfError(pStr: Str255);
BEGIN
gError := ResError;
IF (gError <> NoErr) THEN
BEGIN
ErrorMsg(pStr,0);
IF (gError = wPrErr) THEN
ErrorMsg(Disk is locked,0)
ELSE
ErrorOSErr();
CommentRsrcBegins(pRsrcPtr);
WryteLn( not removed);
ReleaseRsrc(pRsrcPtr);
EXIT(RemovedRsrc);
END;
END;
BEGIN
RemovedRsrc := FALSE;
IF gOption[eTrace] THEN
Trace(RemovedRsrc);
AbortPatrolIfCmdPeriodPressed;
IF gAbortPatrol
OR NOT(gOption[eRmVir]) THEN
BEGIN
ReleaseRsrc(pRsrcPtr);
EXIT(RemovedRsrc);
END;
WITH pRsrcPtr^ DO
BEGIN
IF (fFlag <> kRsrcHdlValid) THEN
BEGIN
ErrorMsg(Error using RemovedRsrc,4);
AwaitKeypress;
ExitSecurityPatrol;
END;
IF gOption[eTrace] THEN
BEGIN
TraceRsrc(About to remove,pRsrcPtr);
AbortPatrolIfCmdPeriodPressed;
IF gAbortPatrol THEN
EXIT(RemovedRsrc);
END;
IF NOT(fInfected) THEN
BEGIN
ErrorMsg(Tried to remove uninfected,4);
AwaitKeypress;
ExitSecurityPatrol;
END;
IF kZeroOutVirs AND (fHdl^ <> NIL) THEN
BEGIN
ZeroOut(fHdl^,fSize);
ChangedResource(fHdl);
gError := ResError;
IF (gError = NoErr) THEN
BEGIN
WriteResource(fHdl);
gError := ResError;
IF (gError <> NoErr) THEN
ErrorOSErr(Couldnt WriteResource);
END
ELSE
ErrorOSErr(Couldnt ChangedResource);
END;
sBits0and7 := BAnd(fResAttrs,$81);
SetResAttrs(fHdl,LoWord(sBits0and7));
RmveResource(fHdl);
ExitIfError(Couldnt remove resource);
UpdateResFile(gCurrRefNum);
ExitIfError(Couldnt update res file);
DisposHandle(fHdl);
InitRsrc(pRsrcPtr);
RemovedRsrc := TRUE;
IF gOption[eTrace] THEN
Trace(RemovedRsrc successful);
END;
INC(gCounts.fRemoved);
INC(gTotals.fRemoved);
END;
PROCEDURE ShortHexDump
(pPtr: Ptr;
pNbrBytes: SignedByte);
VAR
i: INTEGER;
sCh1,sCh2,sDigit: LONGINT;
sIdx: Ptr;
BEGIN
sIdx := pPtr;
FOR i := 1 TO pNbrBytes DO
BEGIN
sDigit := ORD4(sIdx^);
sCh1 := BSR(BAnd(sDigit,$F0),4);
sCh2 := BAnd(sDigit,$0F);
IF sCh1 > 9 THEN
WryteChar(CHR(sCh1 + $37))
ELSE
WryteChar(CHR(sCh1 + $30));
IF sCh2 > 9 THEN
WryteChar(CHR(sCh2 + $37))
ELSE
WryteChar(CHR(sCh2 + $30));
INC(LONGINT(sIdx));
END;
END;
PROCEDURE Trace(pStr: Str255);
BEGIN
ErrorBegins(pStr);
ErrorEnds(0);
END;
PROCEDURE TraceNbr(pStr: Str255;pNbr: LONGINT);
BEGIN
ErrorBegins(pStr);
WryteNbr(pNbr,1);
ErrorEnds(0);
END;
PROCEDURE TraceRsrc(pStr: Str255;pRsrcPtr: TRsrcPtr);
BEGIN
ErrorBegins(pStr);
WITH pRsrcPtr^ DO
BEGIN
WryteChar( );
WryteType(fResType);
WryteNbr (fResId,7);
END;
ErrorEnds(0);
END;
PROCEDURE ZeroOut(pStart: Ptr;pCount: Size);
VAR
i: INTEGER;
sIdx: Ptr;
BEGIN
sIdx := pStart;
FOR i := 1 TO pCount DO
BEGIN
sIdx^ := 0;
INC(LONGINT(sIdx));
END;
END;
PROCEDURE ZeroOutRange(p1: Ptr; p2: Ptr);
VAR
i: INTEGER;
sIdx: Ptr;
BEGIN
IF (ORD4(p1) < ORD4(p2)) THEN
sIdx := p1
ELSE
sIdx := p2;
FOR i := 1 TO ABS(ORD4(p2)-ORD4(p1))+1 DO
BEGIN
sIdx^ := 0;
INC(LONGINT(sIdx));
END;
END;
END.
MainDlog.p
UNIT MainDlog;
INTERFACE
USES
MemTypes,QuickDraw,OSIntf,ToolIntf, PackIntf,Globals;
PROCEDURE InitMainDlog;
FUNCTION MainDlogWorkRequested: TMainItem;
IMPLEMENTATION
{$R-}
CONST
{--------item range--------}
kItemFst = eDirs;
kItemLst = eDBtn;
{----item type subranges--}
kBtnFst = eDirs;
kBtnLst = eQuit;
kChkFst = eAwait;
kChkLst = eTrace;
kStatFst = eMain;
kStatLst = eScOW;
kUItmFst = eDBtn;
kUItmLst = eDBtn;
{--titled item subrange--}
kTItmFst = eDirs;
kTItmLst = eScOW;
TYPE
TDitmPtr = ^TDitmRec;
TDitmRec =
PACKED RECORD
fProcPtr: ProcPtr;
fRect: Rect;
fType: Byte;
fLen: Byte;
fData: INTEGER;
END;
VAR
gDBtnRect: Rect;
gHdl: ARRAY [eAwait..eTrace]
OF ControlHandle;
gRect: ARRAY [eAwait..eTrace]
OF Rect;
PROCEDURE ChkChk(pItem: TMainItem);
BEGIN
IF (pItem < kChkFst)
OR (pItem > kChkLst) THEN
BEGIN
SysBeep(3);
EXIT(ChkChk);
END;
SetCtlValue(gHdl[pItem],ORD(gOption[pItem]));
IF gDisabled[pItem] THEN
BEGIN
SetDItem(gDlogPtr,ORD(pItem),
ctrlItem+chkCtrl+itemDisable,
Handle(gHdl[pItem]),gRect[pItem]);
HiliteControl(gHdl[pItem],255);
END
ELSE
BEGIN
SetDItem(gDlogPtr,ORD(pItem),
ctrlItem+chkCtrl,
Handle(gHdl[pItem]),gRect[pItem]);
HiliteControl(gHdl[pItem],0);
END;
END;
PROCEDURE FrameDefaultBtn(pWindowPtr:WindowPtr; pItemNo: INTEGER);
VAR
sPenState: PenState;
BEGIN
GetPenState(sPenState);
PenSize (3,3);
FrameRoundRect(gDBtnRect,16,16);
SetPenState(sPenState);
END;
PROCEDURE InitMainDlog;
CONST
kTitleMax = 27;
kTItmLen = 42;
{ 14 + kTitleMax + ord(odd(kTitleMax)); }
kUItmLen = 14;
VAR
i: TMainItem;
sDitmPtr: TDitmPtr;
sDlogRect: Rect;
sHdl: Handle;
sNbrTItms: INTEGER;
sNbrUItms: INTEGER;
sRect: ARRAY [eDirs..eScOW]
OF Rect;
sSize: Size;
sTitle: ARRAY [eDirs..eScOW]
OF STRING[kTitleMax];
sType: INTEGER;
BEGIN
IF gOption[eTrace] THEN
Trace(InitMainDlog);
FOR i := kTItmFst TO kTItmLst DO
IF (i >= kBtnFst)
AND (i <= kBtnLst) THEN
BEGIN
SetRect (sRect[i], 266, 65, 346, 83);
OffsetRect(sRect[i],0, 27*(ORD(i)-ORD(kBtnFst)));
END
ELSE IF (i >= kChkFst)
AND (i <= kChkLst) THEN
BEGIN
SetRect (sRect[i], 24, 62, 185, 80);
OffsetRect(sRect[i],0, 20*(ORD(i)-ORD(kChkFst)));
END
ELSE
SetRect (sRect[i], 12, 42, 216, 60);
OffsetRect(sRect[eMain], 080,-30);
OffsetRect(sRect[eOpts], 000,000);
OffsetRect(sRect[eScOW], 216,000);
gDBtnRect := sRect[kItemFst];
InsetRect (gDBtnRect, -4, -4);
WITH sDlogRect, gSFGetPt DO
BEGIN
top := v - 10;
left := h - 10;
bottom := top + 210;
right := left + 368;
END;
sTitle[eDirs] := Directories;
sTitle[eDiry] := Directory;
sTitle[eEvery] := Everything;
sTitle[eFiles] := Files;
sTitle[eQuit] := Quit;
sTitle[eAwait] := Await Keypress;
sTitle[eBeeps] := Beep;
sTitle[eFgPr] := Fingerprint;
sTitle[eFgPrC] := Fingerprint CODEs;
sTitle[eLList] := Long Listing;
sTitle[eRmVir] := Remove Viruses;
sTitle[eTrace] := Trace;
sTitle[eMain] :=
Security Patrol Main Dialog;
sTitle[eOpts] := Options:;
sTitle[eScOW] := Scope Of Work:;
sNbrTItms := (ORD(kTItmLst)-ORD(kTItmFst))+1;
sNbrUItms := (ORD(kUItmLst)-ORD(kUItmFst))+1;
sHdl := NewHandle(2 + (sNbrTItms*kTItmLen) + (sNbrUItms*kUItmLen));
TWordPtr(sHdl^)^ := ORD(kItemLst) - 1;
sSize := 2;
FOR i := kItemFst TO kItemLst DO
BEGIN
IF gOption[eTrace] THEN
TraceNbr(sSize = ,sSize);
sDitmPtr := POINTER(ORD4(sHdl^)+sSize);
WITH sDitmPtr^ DO
IF (i >= kTItmFst)
AND (i <= kTItmLst) THEN
BEGIN
fProcPtr := NIL;
fRect := sRect[i];
IF (i <= kBtnLst) THEN
fType := ctrlItem + btnCtrl
ELSE IF (i <= kChkLst) THEN
fType := ctrlItem + chkCtrl
ELSE
fType := statText + itemDisable;
BlockMove(@sTitle[i],@fLen,
LENGTH(sTitle[i])+1);
sSize := sSize+14+fLen+ORD(ODD(fLen));
END
ELSE IF (i = eDBtn) THEN
BEGIN
fProcPtr := @FrameDefaultBtn;
fRect := gDBtnRect;
fType := userItem + itemDisable;
fLen := 0;
sSize := sSize + 14;
END
ELSE
SysBeep(60);
END;
SetHandleSize(sHdl,sSize);
gDlogPtr := NewDialog(NIL,sDlogRect,,
FALSE,dBoxProc,POINTER(-1),FALSE,0,sHdl);
FOR i := kChkFst TO kChkLst DO
BEGIN
GetDItem(gDlogPtr,ORD(i),sType,
Handle(gHdl[i]),gRect[i]);
IF gOption[eTrace] THEN
TraceNbr(ChkChking item ,ORD(i));
ChkChk(i);
END;
END;
FUNCTION KybdEquivsFilter(pDialogPtr: DialogPtr;
VAR pEventRec: EventRecord;VAR pItemHit: INTEGER): BOOLEAN;
VAR
sChar:
RECORD
CASE INTEGER OF
0:(F1,F2,F3: SignedByte;
Enum: TMainItem);
1:(L: LONGINT);
END;
sItem: TMainItem;
BEGIN
sItem := eNotADlogItem;
WITH pEventRec DO
IF (what = keyDown) THEN
BEGIN
sChar.L := BAnd(message,charCodeMask);
IF (sChar.L = $03)
OR (sChar.L = $0D) THEN
sItem := eDirs
ELSE IF (sChar.L = ORD(D))
OR (sChar.L = ORD(d)) THEN
sItem := eDiry
ELSE IF (sChar.L = ORD(E))
OR (sChar.L = ORD(e)) THEN
sItem := eEvery
ELSE IF (sChar.L = ORD(F))
OR (sChar.L = ORD(f)) THEN
sItem := eFiles
ELSE IF (sChar.L = ORD(Q))
OR (sChar.L = ORD(q))
OR ((BAnd(modifiers,cmdKey)<>0)
AND (sChar.L = ORD(.))) THEN
sItem := eQuit
ELSE
BEGIN
sChar.L :=
(sChar.L-ORD4(1)) + ORD(kChkFst);
IF (sChar.L >= ORD(kChkFst))
AND (sChar.L <= ORD(kChkLst)) THEN
IF NOT(gDisabled[sChar.Enum]) THEN
sItem := sChar.Enum;
END;
END;
IF (sItem = eNotADlogItem) THEN
KybdEquivsFilter := FALSE
ELSE
BEGIN
pItemHit := ORD(sItem);
KybdEquivsFilter := TRUE;
END;
END;
FUNCTION MainDlogWorkRequested: TMainItem;
VAR
sItem:
RECORD
CASE INTEGER OF
0:(Filler: SignedByte;
Enum: TMainItem);
1:(Int: INTEGER);
END;
BEGIN
IF gOption[eTrace] THEN
Trace(MainDlogWorkRequested);
BringToFront(gDlogPtr);
ShowWindow (gDlogPtr);
ModalDialog (@KybdEquivsFilter,sItem.Int);
WHILE (sItem.Enum >= kChkFst)
AND (sItem.Enum <= kChkLst) DO
BEGIN
gOption[sItem.Enum] :=
NOT(gOption[sItem.Enum]);
ChkChk(sItem.Enum);
IF (sItem.Enum = eFgPr) THEN
BEGIN
gDisabled[eFgPrC] := NOT(gOption[eFgPr]);
gOption [eFgPrC] := FALSE;
ChkChk(eFgPrC);
END;
ModalDialog (@KybdEquivsFilter,sItem.Int);
END;
HideWindow (gDlogPtr);
SetPort (gGrafPtr);
IF gOption[eTrace] THEN
TraceNbr(Returning ,ORD(sItem.Enum));
IF (sItem.Enum >= kItemFst)
AND (sItem.Enum <= kItemLst) THEN
MainDlogWorkRequested := sItem.Enum
ELSE
MainDlogWorkRequested := eQuit;
END;
END.
Patrol.p
UNIT Patrol;
INTERFACE
USES
MemTypes,QuickDraw,OSIntf,ToolIntf,PackIntf,Globals;
PROCEDURE BuildDirname;
PROCEDURE InitPatrols;
PROCEDURE PatrolDirectories(pOnly1Deep:BOOLEAN);
PROCEDURE PatrolEverything;
PROCEDURE PatrolFiles;
IMPLEMENTATION
{$R-}
CONST
kPatsInitd = -12345;
TYPE
TOverlappingPBs =
RECORD
CASE INTEGER OF
0: (fPBRec: HParamBlockRec);
1: (fCPBRec: CInfoPBRec);
END;
VAR
gAAPatImpl,gZZPatImpl: SignedByte;
gAppDirId,gInitdFlag,gSysDirId: LONGINT;
gAppVRefNum,gOrigWDRefNum,gSysVRefNum: INTEGER;
gOnly1Deep: BOOLEAN;
gPBs: TOverlappingPBs;
gSFLst: SFTypeList;
gWDPBRec: WDPBRec;
PROCEDURE BuildDirname;
VAR
sErr: OSErr;
sLen: INTEGER;
sName: Str255;
sPBs: TOverlappingPBs;
BEGIN
IF gOption[eTrace] THEN
Trace(BuildDirname);
WITH sPBs,fPBRec,fCPBRec DO
BEGIN
sPBs := gPBs;
ioNamePtr := @sName;
ioVRefNum := gCurrWDRefNum;
gCurrDirname := ;
IF gHFS THEN
BEGIN
ioFDirIndex := -1;
ioDrParID := 0;
REPEAT
ioDrDirId := ioDrParID;
sErr := PBGetCatInfo(@fCPBRec,FALSE);
IF (sErr <> NoErr) THEN
EXIT(BuildDirname);
sLen := LENGTH(sName)+1+LENGTH(gCurrDirname);
IF (sLen <= 255) THEN
gCurrDirname := CONCAT(sName,:,gCurrDirname);
UNTIL ioDrDirId = 2;
END
ELSE
BEGIN
sErr := PBGetVol(@fPBRec,FALSE);
IF (sErr = NoErr) THEN
gCurrDirname := CONCAT(sName,:);
END;
END;
END;
PROCEDURE CallProcessFile;
BEGIN
gActiveSelf :=
(gCurrVRefNum = gAppVRefNum) AND
(gCurrDirId = gAppDirId) AND
(gCurrFilename = StringPtr(kCurApName)^);
gActiveSys :=
(gCurrVRefNum = gSysVRefNum) AND
(gCurrDirId = gSysDirId) AND
(gCurrFilename = StringPtr(kSysResName)^);
gCurrFileDeleted := FALSE;
ProcessFile;
END;
PROCEDURE FloatWDDeeper(pDrDirId: LONGINT);
BEGIN
IF gOption[eTrace] THEN
TraceNbr(Begin FloatWDDeeper, WD = , ORD4(gCurrWDRefNum));
WITH gPBs,fCPBRec DO
BEGIN
IF NOT((pDrDirId=0) OR (pDrDirId=2)) THEN
BEGIN
gWDPBRec.ioVRefNum := gCurrWDRefNum;
gWDPBRec.ioWDDirId := 0;
gError := PBCloseWD(@gWDPBRec,FALSE);
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt close WD);
EXIT(FloatWDDeeper);
END;
END;
gWDPBRec.ioVRefNum := gCurrVRefNum;
gWDPBRec.ioWDDirId := ioDrDirId;
gError := PBOpenWD(@gWDPBRec,FALSE);
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt open subdir WD);
EXIT(FloatWDDeeper);
END;
gCurrWDRefNum := gWDPBRec.ioVRefNum;
END;
IF gOption[eTrace] THEN
TraceNbr(End FloatWDDeeper, WD = ,
ORD4(gCurrWDRefNum));
END;
PROCEDURE FloatWDShallower(pDrDirId: LONGINT);
BEGIN
IF gOption[eTrace] THEN
TraceNbr(Begin FloatWDShallower, WD = ,
ORD4(gCurrWDRefNum));
WITH gPBs,fCPBRec DO
BEGIN
gError := PBCloseWD(@gWDPBRec,FALSE);
gWDPBRec.ioVRefNum := gCurrWDRefNum;
gWDPBRec.ioWDDirId := 0;
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt close subdir WD);
EXIT(FloatWDShallower);
END;
IF (pDrDirId = 0)
OR (pDrDirId = 2) THEN
gCurrWDRefNum := gOrigWDRefNum
ELSE
BEGIN
gWDPBRec.ioVRefNum := gCurrVRefNum;
gWDPBRec.ioWDDirId := pDrDirId;
gError := PBOpenWD(@gWDPBRec,FALSE);
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt reopen WD);
EXIT(FloatWDShallower);
END;
gCurrWDRefNum := gWDPBRec.ioVRefNum;
END;
END;
IF gOption[eTrace] THEN
TraceNbr(End FloatWDShallower, WD = ,
ORD4(gCurrWDRefNum));
END;
PROCEDURE GetActualDirId(pDrDirId: LONGINT);
BEGIN
WITH gPBs,fCPBRec DO
BEGIN
ioDrDirId := pDrDirId;
ioFDirIndex := -1;
ioVRefNum := gOrigWDRefNum;
gError := PBGetCatInfo(@fCPBRec,FALSE);
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt GetActualDirId);
EXIT(GetActualDirId);
END;
gCurrDInfo := ioDrUsrWds;
gCurrDirId := ioDrDirId;
IF gOption[eTrace] THEN
TraceNbr(ActualDirID = ,gCurrDirId);
END;
END;
PROCEDURE InitPatrols;
BEGIN
IF gOption[eTrace] THEN
Trace(InitPatrols);
WITH gPBs,fPBRec,fCPBRec DO
BEGIN
ZeroOutRange(@gAAPatImpl,@gZZPatImpl);
gHFS := TWordPtr(kSFCBLen)^ > 0;
ioNamePtr := @gCurrFilename;
IF gHFS THEN
BEGIN
gError := GetVRefNum
(TWordPtr(kSysMap)^,gSysVRefNum);
IF (gError <> NoErr) THEN
ErrorOSErr(Couldnt get act sys vol);
ioVRefNum := gSysVRefNum;
gError := PBHGetVInfo(@fPBRec,FALSE);
IF (gError <> NoErr) THEN
ErrorOSErr(Couldnt get act sys dir);
IF (fPBRec.ioVFndrInfo[1] = 0) THEN
ErrorOSErr(Boot vol not Blessed);
gSysDirId := ioVFndrInfo[1];
gError := PBHGetVol(@fPBRec,FALSE);
IF (gError <> NoErr) THEN
ErrorOSErr(Couldnt get own DirId);
gAppDirId := ioDirId;
gCurrDirId := ioDirId;
gCurrWDRefNum := ioVRefNum;
ioVolIndex := 0;
gError := PBHGetVInfo(@fPBRec,FALSE);
IF (gError <> NoErr) THEN
ErrorOSErr(Couldnt get own VInfo);
gAppVRefNum := ioVRefNum;
END
ELSE
BEGIN
gSysVRefNum := TWordPtr(kBootDrive)^;
gError := PBGetVol(@fPBRec,FALSE);
IF (gError <> NoErr) THEN
ErrorOSErr(Couldnt get own Vol);
gAppVRefNum := ioVRefNum;
gCurrWDRefNum := ioVRefNum;
gAppDirId := 2;
gSysDirId := 2;
gCurrDirId := 2;
END;
BuildDirname;
gCurrFilename := StringPtr(kCurApName)^;
ioFDirIndex := 0;
ioDirId := 0;
ioVRefNum := gCurrWDRefNum;
gError := PBGetFInfo(@fPBRec,FALSE);
IF (gError <> NoErr) THEN
ErrorOSErr(Couldnt get own FInfo);
gCurrFInfo := ioFlFndrInfo;
gActiveSelf := TRUE;
gActiveSys := FALSE;
gWDPBRec.ioWDProcID := $50617472; {Patr}
gInitdFlag := kPatsInitd;
END;
END;
PROCEDURE PatrolDir(pDrDirId: LONGINT);
VAR
sIndex: INTEGER;
BEGIN
IF gOption[eTrace] THEN
TraceNbr(PatrolDir ,pDrDirId);
WITH gPBs,fCPBRec DO
BEGIN
IF (gInitdFlag <> kPatsInitd) THEN
BEGIN { shouldnt happen }
ErrorOSErr(InitPatrols not done);
EXIT(PatrolDir);
END;
IF gHFS THEN
BEGIN
gCurrDirId := pDrDirId;
GetActualDirId(pDrDirId);
IF (gError <> NoErr) THEN
EXIT(PatrolDir);
END
ELSE
gCurrDirId := 2;
BuildDirname;
DirectoryBegins;
sIndex := 1;
REPEAT
gCurrIndex := sIndex;
ioFDirIndex := sIndex;
ioDrDirId := 0;
ioVRefNum := gCurrWDRefNum;
gError := PBGetFInfo(@fPBRec,FALSE);
IF (gError = NoErr) THEN
BEGIN
gCurrFInfo := ioFlFndrInfo;
CallProcessFile;
END
ELSE IF (gError <> fnfErr) THEN
BEGIN
ErrorOSErr(Couldnt get a file);
EXIT(PatrolDir);
END;
IF NOT(gCurrFileDeleted) THEN
INC(sIndex);
UNTIL (gError <> NoErr) OR gAbortPatrol;
IF gAbortPatrol THEN
EXIT(PatrolDir);
IF (gError <> fnfErr) THEN
BEGIN
ErrorOSErr(Error at end of files);
EXIT(PatrolDir);
END;
gError := NoErr;
IF gHFS AND NOT(gOnly1Deep) THEN
BEGIN
sIndex := 1;
REPEAT
gCurrIndex := sIndex;
ioFDirIndex := sIndex;
ioDrDirId := 0;
ioVRefNum := gCurrWDRefNum;
gError := PBGetCatInfo(@fCPBRec,FALSE);
IF (gError = NoErr) THEN
BEGIN
IF BTst(ORD4(ioFlAttrib),4) THEN
BEGIN
FloatWDDeeper (pDrDirId);
IF (gError <> NoErr) THEN
EXIT(PatrolDir);
PatrolDir(ioDrDirId);
IF (gError <> NoErr)
AND (pDrDirId <> 0)
AND (pDrDirId <> 2) THEN
EXIT(PatrolDir);
FloatWDShallower(pDrDirId);
IF (gError <> NoErr) THEN
EXIT(PatrolDir);
END;
END
ELSE IF (gError <> fnfErr) THEN
BEGIN
ErrorOSErr(Couldnt get a dir);
EXIT(PatrolDir);
END;
INC(sIndex);
UNTIL (gError <> NoErr) OR gAbortPatrol;
IF gAbortPatrol THEN
EXIT(PatrolDir);
IF (gError <> fnfErr) THEN
BEGIN
ErrorOSErr(Error at end of subdirs);
EXIT(PatrolDir);
END;
gError := NoErr;
gCurrDirId := pDrDirId;
GetActualDirId(pDrDirId);
IF (gError <> NoErr) THEN
EXIT(PatrolDir);
BuildDirname;
END;
DirectoryEnds;
END;
END;
PROCEDURE PatrolDirectories(pOnly1Deep:BOOLEAN);
BEGIN
IF gOption[eTrace] THEN
Trace(PatrolDirectories );
WITH gPBs,fPBRec,fCPBRec,gSFRep DO
BEGIN
IF (gInitdFlag <> kPatsInitd) THEN
BEGIN { shouldnt happen }
ErrorOSErr(InitPatrols not done);
EXIT(PatrolDirectories);
END;
gOnly1Deep := pOnly1Deep;
SFGetFile
(gSFGetPt,,NIL,-1,gSFLst,NIL,gSFRep);
WHILE good DO
BEGIN
IF gHFS THEN
BEGIN
ioVRefNum := gSFRep.vRefNum;
ioVolIndex := 0;
gError := PBHGetVInfo(@fPBRec,FALSE);
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt get own VInfo);
LEAVE;
END;
gCurrVRefNum := ioVRefNum;
END
ELSE
gCurrVRefNum := vRefNum;
gCurrWDRefNum := vRefNum;
gOrigWDRefNum := vRefNum;
PatrolBegins;
PatrolDir(0);
PatrolEnds;
IF (gError <> NoErr) THEN
LEAVE;
SFGetFile
(gSFGetPt,,NIL,-1,gSFLst,NIL,gSFRep);
END;
gError := NoErr;
END;
END;
PROCEDURE PatrolEverything;
VAR
sIndex: INTEGER;
BEGIN
IF gOption[eTrace] THEN
Trace(PatrolEverything );
WITH gPBs,fPBRec,fCPBRec DO
BEGIN
IF (gInitdFlag <> kPatsInitd) THEN
BEGIN { shouldnt happen }
ErrorOSErr(InitPatrols not done);
EXIT(PatrolEverything);
END;
gOnly1Deep := FALSE;
PatrolBegins;
ioVRefNum := 0;
sIndex := 1;
REPEAT
gCurrIndex := sIndex;
ioVolIndex := sIndex;
gError := PBGetVInfo(@fPBRec,FALSE);
IF (gError = NoErr) THEN
BEGIN
gCurrVRefNum := ioVRefNum;
gCurrWDRefNum := ioVRefNum;
gOrigWDRefNum := ioVRefNum;
PatrolDir(2);
IF (gError <> NoErr) THEN
EXIT(PatrolEverything);
INC(sIndex);
END
ELSE IF (gError <> nsvErr) THEN
BEGIN
ErrorOSErr(Couldnt get a volume);
EXIT(PatrolEverything);
END;
UNTIL gError <> NoErr;
IF (gError <> nsvErr) THEN
BEGIN
ErrorOSErr(Error at end of volumes);
EXIT(PatrolEverything);
END;
gError := NoErr;
PatrolEnds;
END;
END;
PROCEDURE PatrolFiles;
VAR
sPrevDirId: LONGINT;
sPrevVRefNum: INTEGER;
sPrevWDRefNum:INTEGER;
PROCEDURE CallPrevDirEnd;
VAR
sTempDirId: LONGINT;
sTempVRefNum: INTEGER;
sTempWDRefNum:INTEGER;
BEGIN
IF (sPrevWDRefNum <> 0) THEN
BEGIN
sTempDirId := gCurrDirId;
sTempVRefNum := gCurrVRefNum;
sTempWDRefNum := gCurrWDRefNum;
gCurrDirId := sPrevDirId;
gCurrVRefNum := sPrevVRefNum;
gCurrWDRefNum := sPrevWDRefNum;
DirectoryEnds;
gCurrDirId := sTempDirId;
gCurrVRefNum := sTempVRefNum;
gCurrWDRefNum := sTempWDRefNum;
END;
END;
BEGIN
IF gOption[eTrace] THEN
Trace(PatrolFiles );
WITH gPBs,fPBRec,fCPBRec,gSFRep DO
BEGIN
IF (gInitdFlag <> kPatsInitd) THEN
BEGIN { shouldnt happen }
ErrorOSErr(InitPatrols not done);
EXIT(PatrolFiles);
END;
PatrolBegins;
sPrevWDRefNum := 0;
SFGetFile
(gSFGetPt,,NIL,-1,gSFLst,NIL,gSFRep);
WHILE good DO
BEGIN
IF gHFS THEN
BEGIN
ioVRefNum := gSFRep.vRefNum;
ioDrDirId := 0;
ioFDirIndex := -1;
gError := PBGetCatInfo(@fPBRec,FALSE);
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt get DirId);
LEAVE;
END;
gCurrDirId := ioDrDirId;
ioVolIndex := 0;
gError := PBHGetVInfo(@fPBRec,FALSE);
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt get VInfo);
LEAVE;
END;
gCurrVRefNum := ioVRefNum;
END
ELSE
BEGIN
gCurrDirId := 2;
gCurrVRefNum := vRefNum;
END;
gCurrFilename := fName;
gCurrIndex := 0;
gCurrWDRefNum := vRefNum;
IF (sPrevWDRefNum <> gCurrWDRefNum) THEN
BEGIN
CallPrevDirEnd;
BuildDirname;
DirectoryBegins;
sPrevDirId := gCurrDirId;
sPrevVRefNum := gCurrVRefNum;
sPrevWDRefNum := gCurrWDRefNum;
END;
ioDrDirId := gCurrDirId;
ioFDirIndex := gCurrIndex;
ioVRefNum := gCurrWDRefNum;
gError := PBGetFInfo(@fPBRec,FALSE);
IF (gError <> NoErr) THEN
BEGIN
ErrorOSErr(Couldnt get FInfo);
LEAVE;
END;
gCurrFInfo := ioFlFndrInfo;
CallProcessFile;
IF gAbortPatrol THEN
LEAVE;
SFGetFile
(gSFGetPt,,NIL,-1,gSFLst,NIL,gSFRep);
END;
gError := NoErr;
CallPrevDirEnd;
PatrolEnds;
END;
END;
END.
SecurityPatrol.p
PROGRAM SecurityPatrol(OUTPUT);
USES
MemTypes,QuickDraw,OSIntf,ToolIntf,PackIntf,CodeSizeLimits,Globals,
MainDlog,PasLibIntf,Patrol;
{$R-}
CONST
kPreprocessSelf = TRUE;
kAwaitVerification = FALSE;
VAR
gInitSecPatDone,gRptFileOpen: BOOLEAN;
gScrDmpEnbPtr: Ptr;
gScrDmpEnbSave: SignedByte;
o: Text;
PROCEDURE PreprocessSelf; FORWARD;
PROCEDURE Wryte(pStr: Str255); FORWARD;
PROCEDURE WryteChar(pChar: CHAR); FORWARD;
PROCEDURE WryteEoln; FORWARD;
PROCEDURE WryteLn(pStr: Str255); FORWARD;
PROCEDURE WryteNbr(pNbr:LONGINT;pNbrDigits:INTEGER);FORWARD;
PROCEDURE WryteType(pType: ResType); FORWARD;
{$Z*}
PROCEDURE ExitSecurityPatrol;
BEGIN
IF gOption[eTrace] THEN
Trace(ExitSecurityPatrol);
gScrDmpEnbPtr^ := gScrDmpEnbSave;
IF gRptFileOpen THEN
Close(o);
ExitToShell;
END;
{$Z-}
PROCEDURE InitSecurityPatrol;
VAR
sConfig: LONGINT;
BEGIN
MaxApplZone;
MoreMasters; MoreMasters; MoreMasters;
gInitSecPatDone := FALSE;
gRptFileOpen := FALSE;
gScrDmpEnbPtr := Ptr(kScrDmpEnb);
gScrDmpEnbSave := gScrDmpEnbPtr^;
gScrDmpEnbPtr^ := 0;
Textbook(@thePort);
Write (SecurityPatrol is a Mac virus );
Write (detector. );
TextFace([bold,extend]);
WriteLn(Use at your own risk. );
TextFace([]);
Write (The Save As... dialog below is );
WriteLn(to save error reports.);
PLFlush(OUTPUT);
PauseBriefly;
InitGlobals;
gOption[eBeeps] := TRUE; {override }
gDisabled[eFgPrC] := TRUE; { defaults}
InitMainDlog;
InitPatrols;
sConfig := BAnd(ORD4(Ptr(kSPConfig)^),$F);
IF (sConfig = useFree)
OR (sConfig = useAsync) THEN
SFPutFile
(gSFPutPt,Filename, or cancel to print,
SecurityPatrol Report,NIL,gSFRep)
ELSE
SFPutFile
(gSFPutPt,Filename, or cancel to quit,
SecurityPatrol Report,NIL,gSFRep);
WITH gSFRep DO
IF good THEN
BEGIN
gCurrWDRefNum := vRefNum;
BuildDirname;
ReWrite(o,CONCAT(gCurrDirname,fName));
END
ELSE
IF (sConfig = useFree)
OR (sConfig = useAsync) THEN
ReWrite(o,PRINTER:)
ELSE
BEGIN
WriteLn(Run cancelled.);
PLFlush(OUTPUT);
PauseBriefly;
ExitSecurityPatrol;
END;
gRptFileOpen := TRUE;
Wryte (This copy of Security Patrol 1.0 );
Wryte (is being maintained by );
TextFace([bold,extend]);
gPgmrname := <<your name here>>;
WryteLn(gPgmrname);
TextFace([]);
Wryte (The following run was done on );
GetTime(gDateTimeRec);
WITH gDateTimeRec DO
BEGIN
year := year mod 100;
WryteNbr (month,2);
WryteChar(/);
WryteNbr (day, 2);
WryteChar(/);
WryteNbr (year, 2);
Wryte ( at );
WryteNbr (hour, 2);
WryteChar(:);
IF minute < 10 THEN
WryteChar(0);
WryteNbr (minute,1);
WryteLn (.);
END;
IF kPreprocessSelf THEN
PreprocessSelf
ELSE
WryteLn(Didnt perform self-tests.);
gInitSecPatDone := TRUE;
END;
PROCEDURE PreprocessSelf;
VAR
i: INTEGER;
sC1Ptr,sEntActual,sEntShouldB,sOffActual,sOffShouldB:LONGINT;
sResType: ResType;
sSave: TMainOpt;
PROCEDURE Abort(pStr: Str255);
BEGIN
ErrorBegins(pStr);
WryteEoln;
CommentBegins;
WryteLn(Assuming infected.);
CommentBegins;
Wryte (Contact );
Wryte (gPgmrname);
WryteLn( to be sure.);
CommentBegins;
Wryte ((These msgs apply to );
Wryte (gCurrFilename);
WryteLn( itself, not to your system.));
CommentBegins;
gOption[eAwait] := TRUE;
gOption[eBeeps] := TRUE;
ErrorEnds(4);
ExitSecurityPatrol;
END;
BEGIN
BlockMove(@gOption,@sSave,SIZEOF(TMainOpt));
ZeroOut (@gOption, SIZEOF(TMainOpt));
gOption[eRmVir] := TRUE;
{ gOption[eTrace] := TRUE; }
IF gOption[eTrace] THEN
Trace(PreprocessSelf);
GetCodeSizeLimits;
GetRsrc(@gCode0,CODE,0,ResId);
IF (gCode0.fFlag <> kRsrcHdlValid) THEN
Abort(Unable to get own CODE 0);
IF NOT(Code0IsValid) THEN
Abort(Found unexpected CODE 0 header);
LookForKnownViruses;
FOR i := 1 TO Count1Types DO
BEGIN
Get1IndType(sResType,i);
IF (sResType = SIZE) THEN
BEGIN
IF (Count1Resources(SIZE) > 1) THEN
Abort(Too many SIZE resources);
GetRsrc(@gCurrRsrc,SIZE,1,Index);
IF (gCurrRsrc.fSize > 10) THEN
Abort(SIZE resource too large);
ReleaseRsrc(@gCurrRsrc);
END
ELSE IF (sResType <> CODE) THEN
BEGIN
ErrorBegins(Found a rsrc of type );
WryteType(sResType);
ErrorEnds(0);
Abort (Only CODE and SIZE allowed);
END;
END;
WITH TJTHdl(gCode0.fHdl)^^,fJTEntry[1] DO
BEGIN
IF (fNbrBytesInTable <> gJTSize) THEN
BEGIN
ErrorBegins(Jump table size is );
WryteNbr (fNbrBytesInTable,1);
Wryte (, should be );
WryteNbr (gJTSize,1);
ErrorEnds(0);
Abort (Invalid Jump Table size);
END;
IF NOT(JTEIsValid(@fJTEntry[1])) THEN
Abort(Invalid Jump Table entry);
IF (fSegId <> 1) THEN
BEGIN
ErrorBegins(Enters at CODE );
WryteNbr(fSegId,1);
Wryte (, should enter at CODE 1);
ErrorEnds(0);
Abort (Invalid start address);
END;
sOffActual := 4 + fOffset;
END;
WITH gCurrRsrc DO
BEGIN
GetRsrc(@gCurrRsrc,CODE,1,ResId);
IF (fFlag <> kRsrcHdlValid) THEN
Abort(Couldnt look at own CODE 1');
sC1Ptr := BAND($00FFFFFF,ORD4(fHdl^));
sEntActual := sC1Ptr + sOffActual;
IF (TWordPtr(sEntActual)^ = $4EFA) THEN
BEGIN
sOffActual :=
sOffActual+2+TWordPtr(sEntActual+2)^;
sEntActual := sC1Ptr + sOffActual;
END;
sEntShouldB := gEntryPoint;
sOffShouldB := sEntShouldB - sC1Ptr;
IF (sEntActual <> sEntShouldB) THEN
BEGIN
ErrorBegins(Invalid start address);
WryteEoln;
CommentBegins;
Wryte (Enters at address $);
ShortHexDump(@sEntActual,4);
Wryte ( (CODE 1 + $);
ShortHexDump(@sOffActual,4);
Wryte () --> $);
ShortHexDump(Ptr(sEntActual),4);
WryteEoln;
CommentBegins;
Wryte (Should enter at $);
ShortHexDump(@sEntShouldB,4);
Wryte ( (CODE 1 + $);
ShortHexDump(@sOffShouldB,4);
Wryte () --> $);
ShortHexDump(Ptr(sEntShouldB),4);
WryteEoln;
CommentBegins;
Wryte (CODE 1 begins at $);
ShortHexDump(@sC1Ptr,4);
ErrorEnds(0);
Abort (This is not a user error);
END;
ReleaseRsrc(@gCurrRsrc);
END;
ReleaseRsrc(@gCode0);
IF (Count1Resources(CODE)>gMaxCode+1) THEN
Abort(Too many CODE resources.);
IF kAwaitVerification THEN
BEGIN
ErrorBegins(The following are the );
Wryte (fingerprints of );
Wryte (gCurrFilename);
Wryte ( itself:);
ErrorEnds(0);
END;
FOR i := 0 TO gMaxCode DO
WITH gCurrRsrc DO
BEGIN
GetRsrc(@gCurrRsrc,CODE,i,ResId);
IF (fFlag <> kRsrcHdlValid) THEN
Abort(Couldnt look at next CODE);
IF (fSize > gSizeLimit[i]) THEN
BEGIN
ErrorBegins(Failed a CODE size test);
WryteEoln;
CommentRsrcBegins(@gCurrRsrc);
Wryte ( size is );
WryteNbr(fSize,1);
Wryte (, which exceeds its );
WryteNbr(gSizeLimit[i],1);
WryteLn ( size limit.);
Abort(May contain an imbedded virus);
END;
IF kAwaitVerification THEN
BEGIN
ProcessCurrRsrc;
CommentFgPrRsrc(@gCurrRsrc);
END;
ReleaseRsrc(@gCurrRsrc);
END;
IF kAwaitVerification THEN
BEGIN
ErrorBegins(Time to make a decision:);
WryteEoln;
CommentBegins;
WryteLn(Verify fingerprints if you can);
CommentBegins;
WryteLn(Press command-period to abort);
CommentBegins;
WryteLn(Press any other key to continue);
CommentBegins;
gOption[eAwait] := TRUE;
ErrorEnds(0);
IF gAbortPatrol THEN
BEGIN
PauseBriefly;
ExitSecurityPatrol;
END;
END;
WryteLn(Passed all current self-tests.);
PauseBriefly;
BlockMove(@sSave,@gOption,SIZEOF(TMainOpt));
END;
{$Z*}
PROCEDURE WriteFilenameToReport;
BEGIN
WITH gReportFlags DO
IF NOT(fWroteFilename) THEN
BEGIN
IF NOT(fWroteDirname) THEN
BEGIN
WriteLn(o,gCurrDirname);
fWroteDirname := TRUE;
END;
Write(o,gInd,gCurrFilename);
IF gActiveSelf THEN
BEGIN
Write(o, (Active Self));
IF gInitSecPatDone
AND NOT(kProcessSelf) THEN
Write(o, skipped);
END
ELSE IF gActiveSys THEN
Write(o, (Active System));
WriteLn(o);
fWroteFilename := TRUE;
END;
END;
PROCEDURE WriteFilenameToScreen;
BEGIN
WITH gScreenFlags DO
IF NOT(fWroteFilename) THEN
BEGIN
IF NOT(fWroteDirname) THEN
BEGIN
WriteLn(gCurrDirname);
fWroteDirname := TRUE;
END;
Write(gInd,gCurrFilename);
IF gActiveSelf THEN
BEGIN
Write( (Active Self));
IF gInitSecPatDone
AND NOT(kProcessSelf) THEN
Write( skipped);
END
ELSE IF gActiveSys THEN
Write( (Active System));
WriteLn;
PLFlush(OUTPUT);
fWroteFilename := TRUE;
END;
END;
PROCEDURE Wryte(pStr: Str255);
BEGIN
Write(pStr);
IF gRptFileOpen THEN
Write(o,pStr);
END;
PROCEDURE WryteChar(pChar: CHAR);
BEGIN
Write(pChar);
IF gRptFileOpen THEN
Write(o,pChar);
END;
PROCEDURE WryteEoln;
BEGIN
WriteLn;
PLFlush(OUTPUT);
IF gRptFileOpen THEN
BEGIN
WriteLn(o);
END;
END;
PROCEDURE WryteFilename;
BEGIN
IF gRptFileOpen THEN
WriteFilenameToReport;
WriteFilenameToScreen;
END;
PROCEDURE WryteFilenameToScreenOnlyForNow;
BEGIN
WriteFilenameToScreen;
END;
PROCEDURE WryteLn(pStr: Str255);
BEGIN
WriteLn(pStr);
PLFlush(OUTPUT);
IF gRptFileOpen THEN
BEGIN
WriteLn(o,pStr);
END;
END;
PROCEDURE WryteNbr(pNbr: LONGINT;pNbrDigits:INTEGER);
BEGIN
Write(pNbr:pNbrDigits);
IF gRptFileOpen THEN
Write(o,pNbr:pNbrDigits);
END;
PROCEDURE WryteType(pType: ResType);
BEGIN
Write(pType);
IF gRptFileOpen THEN
Write(o,pType);
END;
PROCEDURE zzSecurityPatrol;
BEGIN
END;
BEGIN
InitSecurityPatrol;
WHILE TRUE DO
BEGIN
gAbortPatrol := FALSE;
CASE MainDlogWorkRequested OF
eDirs: PatrolDirectories (FALSE);
eDiry: PatrolDirectories (TRUE);
eEvery: PatrolEverything;
eFiles: PatrolFiles;
OTHERWISE LEAVE;
END; {CASE}
END;
WryteEoln;
WryteLn(*******************************);
WryteEoln;
WryteLn (Totals over all patrols:);
ListCounts(@gTotals);
ExitSecurityPatrol;
END.
SecurityPatrol.Link (TML 2.5 only)
!PAS$Xfer
SecurityPatrol
PAS$Library
MacIntf
MacIntfGlue
BitProcs
CodeSizeLimits
Globals
MainDlog
PasLibIntf
Patrol
<
Globals/Globals
<
BitProcs/Fingerprint
Globals/Fingerprint
/End
SecurityPatrol.Proj (TML II Only)
SecurityPatrol.p.o
CodeSizeLimits.p
Fingerprint.ipas
Globals.p
MainDlog.p
Patrol.p
SecurityPatrol.p
TMLPascal SecurityPatrol.p
CodeSizeLimits.p.o
CodesSizeLimits.p
TMLPascal CodeSizeLimits.p
Globals.p.o
Fingerprint.ipas
Globals.p
TMLPascal Globals.p
MainDlog.p.o
Globals.p
MainDlog.p
TMLPasal MainDlog.p
Patrol.p.o
Globals.p
Patrol.p
TMLPascal Patrol.p
SecurityPatrol
CodeSizeLimits.p.o
Globals.p.o
MainDlog.p.o
Patrol.p.o
SecurityPatrol.p.o
Link -w -t APPL -c ????
-ra Fingerprint=$14
-ra Globals=$14
-ra Main=$14
SecurityPatrol.p.o
CodeSizeLimits.p.o
Globals.p.o
MainDlog.p.o
Patrol.p.o
"{Libraries}"Runtime.o
"{Libraries}"Interface.o
"{Libraries}"ObjLib.o
"{TMLPLibraries}"TMLPasLib.o
"{TMLPLibraries}"SANELib.o
-o SecurityPatrol