|
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. It’s 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 system’s “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 wasn’t coded so much to be “user-friendly”; my main goal was to make it “security-stubborn”: It’s 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 it’s 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 it’s not active, pressing Cancel will cause it to stream the text to a direct-connect ImageWriter on the printer port. (There’s a bug in TML II that causes WriteLn output to the ImageWriter not to work, but it’ll 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 Dialog’s “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 doesn’t 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 doesn’t 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 user’s 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 doesn’t 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 it’s 100% source code compatible, it’s 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 aren’t 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 that’s implicitly used by all screen output WriteLn’s, 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.) That’s 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. (It’s been programmed to work under both MFS and HFS, but it hasn’t 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 resource’s 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 won’t 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 program’s main procedure cannot be referenced as if it were an external subroutine, because MPW Link doesn’t give us a symbolic name for it that we can reference. Tom Leonard suggested defining a subroutine just prior to the main program’s 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 it’s larger than the I/O buffer. Calling TML’s 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 resource’s 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 shouldn’t use only the 3 tests in this article, because it’s 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 wouldn’t 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!) It’s 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.
Don’t be misled by the name “Good”. I chose that as its name because it’s a natural antonym of Evil and also 4 letters long. It obviously can’t know whether or not the original code from the software manufacturer contains malicious code, so it’s not known to be “good” in that sense of the term. In the context of what we’re 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, it’s 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 you’re 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 isn’t 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 Kinko’s. (I notified the Washington DC area Kinko’s 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 it’s infecting; therefore, we skip those 12 bytes in the fingerprinting process.
“Generic” Process_xxx’s: 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 would’ve 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_xxx’s: Routines that don’t 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 won’t 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, we’ll 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, it’s possible to use conditional compilation directives instead of the kProcessSelf constant, but the result wouldn’t 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 they’re 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; it’s 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 TMain’s 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 didn’t 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 Pascal’s “plain vanilla”/“Textbook” text window while the MainDlog is on the screen; to save processing, it’s 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 there’s 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, you’re 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, we’ve been lucky to have JTEs lying around to repair them with (using this routine). In the future, who knows? Unfortunately, there’s no shortcut that doesn’t involve disassembly to find out how to restore an application. If a new virus comes along, and if you don’t 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” window’s GrafPtr, using gGrafPtr^.portBits.bounds will get the screen size of whatever screen it’s 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 you’re 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 doesn’t compare correctly with the constant because it’s 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 isn’t 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 it’s infected with Scores? Also, I’m told that MacMoney has a CODE resource that’s 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 don’t 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 doesn’t 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 don’t have Lightspeed, so I couldn’t come up with an elegant way to deal with that. I’m open to suggestions. The program’s 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 there’s a version number appended.) It’s inelegant because they lose the benefit of the jump table irregularities search. They could get infected by some future virus and this program wouldn’t 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 doesn’t 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 type’s 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 don’t 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 File’s data is in its data fork, and the Scrapbook File’s 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 doesn’t throw away their contents. (While infected, you’re still able to use both DAs, because they continue to access those files by name.) That’s why the program doesn’t 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 don’t we search for and destroy nVIR’s “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 hasn’t 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 don’t want to delete nVIR resources willy-nilly. The only time it’s really safe to do so is after successfully restoring an infected application’s 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 don’t bump the index if a resource is removed. That’s because the resource following it now assumes the removed resource’s position in numerical order as far as Get1IndResource is concerned. This same consideration applies when a file is deleted. (See Patrol’s CallProcessFile discussion of gCurrFileDeleted, below.)
ShortHexDump: This doesn’t 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 Dialog’s DITL in memory. It’s 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 button’s 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 that’s 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 doesn’t 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 it’ll 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.) I’ve 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 doesn’t 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 value’s only significance is that it’s 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 you’re 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 we’re 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 that’s a field that’s returned by the File Manager. Instead, it’s 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 it’s 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 isn’t 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 Security’s 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, it’s 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 there’s 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 it’s 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 didn’t say so explicitly, but I haven’t 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 doesn’t currently use gCurrDirId, Patrol uses it to detect the Active Self and Active System.
InitPatrols: This routine makes sure that all of Patrol’s 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, you’ll understand this, but PatrolDir’s 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 isn’t active,
(3) it’s 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: I’ve 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. That’s the purpose of the imbedded procedure CallPrevDirEnd. If you don’t go to that trouble, the TFeedback variables in Globals won’t get reset properly and the new directory name won’t 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 there’s no event loop, the program doesn’t support DAs (on-purpose). It also specifically disables FKEYs. The only form of concurrent code that it doesn’t disallow is MultiFinder, but you shouldn’t run it under MultiFinder anyway, because you won’t be able to examine open files, such as the DeskTop file and the concurrent applications themselves (the Finder, eg).
There’s a very good reason why I don’t 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 don’t 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). You’re 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 display’s the disk’s window behind the SecurityPatrol window using the override WDEF. You’ve just been infected, and SecurityPatrol hasn’t even had a chance to look at the disk yet. My guess is that the published version of SecurityPatrol wouldn’t 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 doesn’t do. On the other hand, I don’t know for sure that it doesn’t, and I don’t like the uncontrolled variable it represents. Even if the Finder wouldn’t let a virus thru this way, maybe some other concurrent application or DA would. I didn’t go extraordinarily out of my way to prevent all concurrent code (VBLs, AppleTalk, etc), but I suspect there’s a security hole there somewhere. I’m 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 don’t. It’s ignored by TML II.
kPreprocessSelf and kAwaitVerification: Having kPreprocessSelf turned on can get to be a real nuisance during development, but it’s 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 company’s users. The code controlled by kAwaitVerification will be cleaned up in Version 2.0: All of SecurityPatrol’s 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 manual’s Appendix F is the fact that main program’s routine names, when referenced from a UNIT, will not be found at Link time unless you explicitly define them externally with this directive. It’s 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 application’s name into its drag bar title. Without it TML II would WriteLn all over the desktop. Because it initializes the managers, we don’t. 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 doesn’t recognize curly quotes, for example). Under TML II, this feature doesn’t 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 doesn’t, but it doesn’t hurt.
PreprocessSelf: These are the most stringent tests I could think of to guard against the program’s own infection. You’re 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. That’s 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 file’s 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 gFgPr’s. } 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 gFgPr’s. } 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(‘Couldn’t 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(‘Couldn’t 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(‘Couldn’t 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(‘Couldn’t 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(‘Couldn’t 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. ‘, ‘(Don’t use under MultiFinder!)’),1) ELSE ErrorOSErr(‘Couldn’t 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(‘Couldn’t close data fork’); END ELSE ErrorOSErr(‘Couldn’t 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(‘Couldn’t 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(‘Couldn’t WriteResource’); END ELSE ErrorOSErr(‘Couldn’t ChangedResource’); END; sBits0and7 := BAnd(fResAttrs,$81); SetResAttrs(fHdl,LoWord(sBits0and7)); RmveResource(fHdl); ExitIfError(‘Couldn’t remove resource’); UpdateResFile(gCurrRefNum); ExitIfError(‘Couldn’t 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(‘ChkChk’ing 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(‘Couldn’t close WD’); EXIT(FloatWDDeeper); END; END; gWDPBRec.ioVRefNum := gCurrVRefNum; gWDPBRec.ioWDDirId := ioDrDirId; gError := PBOpenWD(@gWDPBRec,FALSE); IF (gError <> NoErr) THEN BEGIN ErrorOSErr(‘Couldn’t 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(‘Couldn’t 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(‘Couldn’t 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(‘Couldn’t 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(‘Couldn’t get act sys vol’); ioVRefNum := gSysVRefNum; gError := PBHGetVInfo(@fPBRec,FALSE); IF (gError <> NoErr) THEN ErrorOSErr(‘Couldn’t 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(‘Couldn’t get own DirId’); gAppDirId := ioDirId; gCurrDirId := ioDirId; gCurrWDRefNum := ioVRefNum; ioVolIndex := 0; gError := PBHGetVInfo(@fPBRec,FALSE); IF (gError <> NoErr) THEN ErrorOSErr(‘Couldn’t get own VInfo’); gAppVRefNum := ioVRefNum; END ELSE BEGIN gSysVRefNum := TWordPtr(kBootDrive)^; gError := PBGetVol(@fPBRec,FALSE); IF (gError <> NoErr) THEN ErrorOSErr(‘Couldn’t 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(‘Couldn’t 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 { shouldn’t 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(‘Couldn’t 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(‘Couldn’t 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 { shouldn’t 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(‘Couldn’t 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 { shouldn’t 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(‘Couldn’t 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 { shouldn’t 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(‘Couldn’t get DirId’); LEAVE; END; gCurrDirId := ioDrDirId; ioVolIndex := 0; gError := PBHGetVInfo(@fPBRec,FALSE); IF (gError <> NoErr) THEN BEGIN ErrorOSErr(‘Couldn’t 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(‘Couldn’t 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(‘Didn’t 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(‘Couldn’t 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(‘Couldn’t 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
- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine