← ST-Computer 06 / 1991

Fast-File- und Link-Viren-Finder

Programmierpraxis

Vor einiger Zeit war es mal wieder soweit: Ich suchte das Backup von einem Source-Text auf einer der 10 Partitions meiner Megafile 60. Aber in welchem Ordner befand es sich?
Eine Szene später: Habe ich nun einen Link- Virus auf der Platte oder nicht? Sagrotan starten und warten, ...

Wie sich der geneigte Leser sicherlich vorstellen kann, habe ich für beide Probleme eine Lösung anzubieten - und zwar in Form eines Fast-File-Finders bzw. des Link-Virus-Finders: Der Fast-File-Finder (FFF) ist in der Lage, die gesamte Festplatte nach einem Dateinamen zu durchsuchen. Ganz nebenbei ermittelt er auch noch, wieviel Platz auf jeder Partition noch frei ist.

Der Link-Virus-Finder (LVF) durchsucht alle ausführbaren Programme auf der Festplatte nach Link-Viren. Er tut dies, indem er den Programm-Header mit den Mustern des VCS und des Milzbrand-Virus' vergleicht. Beim jedem Programm wird zudem eine Prüfsumme über die ersten 256 Bytes berechnet, mit welcher bei jedem neuerlichen Start vom LVF verglichen wird. Ach ja, der LVF braucht für meine 10 Partitions (mit über 2800 Dateien in mehr als 280 Ordnern) etwa 13.5s, um festzustellen, ob Link-Viren vorhanden sind oder nicht. Ich denke, daß ist im Vergleich zu 15 min bei Sagrotan angenehm schnell...

Kommen wir nun zum Eigentlichen: dem Programm. Wie man leicht feststellen kann, ist das Programm in Assembler geschrieben. Es besteht aus einigen Teilen, die man auch prima einzeln verwenden kann. Deshalb glaube ich, daß jeder Assembler-Programmierer etwas von dem Listing hat. Es ist recht gut dokumentiert, so daß man es wohl auf jeden fortgeschrittenen Assembler-Programmierer ohne weiteres loslassen kann.

Da der FFF und der LVF in wesentlichen Teilen gleich sind, gibt es nur ein Listing, aus welchem man mit dem Flag virus wahlweise den FFF oder den LVF erzeugen kann.

Um ein Laufwerk nach Link-Viren oder Dateien zu durchsuchen, wird zuerst die Routine set_drive für das zu durchsuchende Laufwerk aufgerufen. Diese Routine ermittelt den BPB des Laufwerkes, liest die FAT und ermittelt so ganz nebenbei noch dessen freien Speicherplatz.

Nun wird read_dir aufgerufen. Diese Routine liest das Root-Directory des Laufwerkes ein und ruft für jeden Ordner die Routine read_sub_dir auf. Diese Routine liest dann rekursiv alle weiteren Ordner ein.

Der Speicherplatz für die Ordner wird durch die Routine get_mem ermittelt, die eine Heap-Verwaltung darstellt. Das Prinzip eines solchen Heaps ist sehr einfach: Man hat einen Zeiger auf einen großen Speicherblock - den Heap. Will nun jemand Speicher alloziieren. wird dieser Wert des Zeigers zurückgegeben. Das ist die Adresse des Speicherblockes, den man angefordert hat. Bevor das Unterprogramm verlassen wird, erhöht man diesen Zeiger noch um die Größe des angeforderten Speicherblockes, so daß bei einem erneuten Aufruf der Zeiger hinter den ersten Speicherblock zeigt.

Noch zwei Dinge gilt es zu beachten: Die Größe des Speicherblockes sollte aufgerundet werden, damit dieser stets auf einer geraden Adresse anfängt. Und man sollte abfragen, ob der Zeiger hinter den Speicherblock zeigt, der für den Heap reserviert ist. Wenn dem so ist, reicht der Speicher nicht, und das Programm wird abgebrochen.

Jetzt wird sich der eine oder andere eventuell noch fragen: Wie kann man denn Speicherblöcke wieder freigeben? Die Antwort ist ganz einfach: gar nicht! Das ist bei unserem Directory-Baum auch gar nicht nötig, da wir vor jedem erneuten Einlesen eines Baumes den Heap-Pointer einfach wieder zurück auf den Anfang setzen und somit alle Blöcke freigegeben haben.

So, nach diesem Ausflug in die Heap-Verwaltung kommen wir zur Routine hunt_dir. Sie durchsucht einen eingelesenen Directory-Baum nach Dateien bzw. die Dateien nach Viren. Auch diese Routine funktioniert rekursiv, d.h. wenn ein Ordner gefunden wird, ruft sie sich mit einem Zeiger auf den Ordner und der Anzahl der Dateien des Ordners erneut auf. Wenn der Ordner komplett durchsucht wurde, wird zurückgekehrt, und es geht weiter.

In dieser Routine besteht auch der größte Unterschied zwischen dem FFF und dem LVF. Während der FFF den Dateinamen nur mit seiner Suchmaske vergleicht und den Dateinamen mit Pfad gegebenenfalls ausgibt, hat der LVF erheblich mehr zu tun. Wenn ein erster Vergleich der Extensions „PR?“, „TO?“, „TT?“, „AC?\ „AP?“, „DR?“ (Treiber), „SY?“ (Festplattentreiber!) positiv ist. wird der erste Sektor dieser Datei eingelesen. Bei einer Programmdatei müssen am Anfang die Bytes $601A stehen. Nun wird der Anfang des TEXT-Segmentes mit den zwei bekannten Link-Viren verglichen.

Wenn kein Link-Virus vorhanden ist, wird eine Prüfsumme über die ersten 256 Bytes der Datei berechnet. Dies klingt zwar nach sehr wenig, wenn man bedenkt, daß z.B. Sagrotan eine CRC-Prüfsumme über die gesamte Datei berechnet, reicht aber, da in den ersten 256 Bytes die Länge der einzelnen Programmsegmente steht, welche durch einen Virus an sich immer verändert wird. Zudem muß sich ein Virus am Programmanfang aufrufen. und über genau diese Stelle wird die Prüfsumme ja auch berechnet. Wie man sieht, wird zwar Zeit gespart, aber die Sicherheit ist immer noch gewährleistet.

Nach der Berechnung der Prüfsumme wird in der Vergleichsliste nach dem Programm gesucht. Wenn das Programm bereits in der Liste steht, wird die Prüfsumme in der Liste gesucht, denn für jedes Programm merkt sich der LVF bis zu sieben Prüfsummen. Das ist sehr praktisch, wenn man z.B. verschiedene Programmversionen auf derFestplatte hat. Wenn die Prüfsumme nicht in der Liste steht, wird gefragt, ob die neue Prüfsumme mit in die Liste aufgenommen werden soll. Steht der Programmname noch nicht in der Liste, wird ein neuer Eintrag für das Programm angelegt und eine entsprechende Meldung ausgegeben. Die Vergleichsliste wird beim Start vom LVF automatisch geladen, und sie kann vor dem Programmende natürlich auch geschrieben werden.

Sowohl LVF als auch auf FFF können in der Kommandozeile einige Parameter übergeben werden.

Mit einem am Anfang der Kommandozeile wird die Ausgabe der Dateinamen unterdrückt. Beim LVF spart es einfach Zeit, beim FFF ist es lediglich dann praktisch, wenn man nur wissen wollte, wieviel „*.TXT“-Dateien z.B. auf der Festplatte stehen, aber nicht, wo sie sind. Eventuell hat man den FFF auch nur gestartet, um festzustellen, auf welcher Partition noch Platz ist, denn das wird ja praktischerweise auch ausgegeben.

Mit einem „+“ wird beim LVF eine unbekannte Datei oder eine unbekannte Prüfsumme automatisch, d.h. ohne Rückfrage, übernommen. Das ist insbesondere dann praktisch, wenn man den LVF zum ersten Mal, also ohne die „LVF.DAT"-Datei startet.

Sowohl beim LVF als auch beim FFF kann man mit „:x“ nur das Laufwerk „x“ durchsuchen lassen. Beispiel: „:C“, es wird nur das Laufwerk C: durchsucht. Ansonsten wird ab Laufwerk „C:“ bis zum letzten benutzten Laufwerk alles durchsucht - also üblicherweise die gesamte Festplatte.

Beim FFF kann man nun noch eine Suchmaske angeben. Diese kann ein kompletter Dateiname wie z.B. „TEST.DOC' sein oder aber auch nur ein Teil davon („T*.D?C“), wie beim GEMDOS also.

Literatur:

[1] Scheibenkleister II. MAXON Computer
[2] Atari ST Profibuch, Sybex
[3] Anleitung zum Turbo Ass

************************************************* * Link-Virus-Finder V1.5 * * und Fast-File-Finder V1.5 * * (c)1991 by Maxon Computer * * von X-soft, Markus Fritze * * entwickelt mit Turbo-Ass V1.4 * ************************************************* ;Schalter: virus EQU 0 ;0:Fast-File-Finder, <>0:Link-Virus-Finder IF virus OUTPUT 'LVF.TTP' ;Link-Virus-Finder ELSE OUTPUT 'FFF.TTP' ;Fast-File-Finder ENDC OPT F+,W+ ;Warnungen an,Fast-Load an max_prgs EQU 1000 ;max.1000 Programme im LVF max_sektorsize EQU 8192 ;max.SektorgröPe ;A6-Register zeigt auf das BSS-Segment BASE A6,BSS ;Struktur des BIOS-Parameter-Blocks definieren PART 'BPB-Struktur' RSRESET recsiz:RS.W 1 ;Bytes pro Sektor clsiz: RS.W 1 ;Sektoren pro Cluster clsizb:RS.W 1 ;Bytes pro Cluster rdlen: RS.W 1 ;Länge des Root-Directories fsiz: RS.W 1 ;Länge einer FAT fatrec:RS.W 1 ;Startsektor der 2.FAT datrec:RS.W 1 ;Starts, des 1.freien Sektors numcl: RS.W 1 ;Gesamtanzahl der Cluster bflags:RS.W 1 ;Bit0=1 bei 16-bit-FAT ENDPART _drvbits EQU $04C2 ;Die Bitmaske der Laufwerke PART 'main' anfang: movea.l 4(SP),A0 ;Ptr auf die Basepage movea.l $18(A0),A6 ;Ptr auf den BSS-Bereich lea own_stack(PC),SP pea init_text(PC) bsr print_line ;Startup-Meldung IF virus lea data_buff(A6),A0 move.l A0,data_base(A6) clr.w -(SP) pea fname(A6) move.w #$3D,-(SP) trap #1 ;Fopen() addq.l #8,SP move.l D0,D7 bmi.s anfang1 ;Fehler move.l data_base(A6),-(SP) move.l #max_prgs*32,-(SP) move.w D7,-(SP) move.w #$3F,-(SP) trap #1 ;Fread() - Datenblock einlesen lea 12(SP),SP move.w D7,-(SP) move.w #$3E,-(SP) trap #1 ;Fclose() addq.l #4,SP anfang1: ENDC bsr convert ;Commandline auswerten clr.l -(SP) move.w #$20,-(SP) trap #1 ;Supervisormode an move.l D0,2(SP) move.l _drvbits.w,D7 ;Variable auslesen trap #1 ;Usermode wieder an addq.l #6,SP move.w D7,D1 ;_drvbits nach D1 IF !virus lea search_mask(A6),A1 ;Suchmaske lea search_chars(A6),A2 ;Suchdaten ENDC moveq #$18,D7 ;Ordner & Vol-Label ignorieren moveq #0,D5 ;Bytes der gefundenen Dateien moveq #0,D4 ;Anzahl der gefundenen Dateien moveq #0,D3 ;Anzahl der Ordner moveq #2,D2 ;mit Drive C geht es los tst.w D6 ;keine Laufwerksangabe? bmi.s loop ;Genau! => move.w D6,D2 ;das spezielle Laufwerk testen loop: btst D2,D1 ;Laufwerk vorhanden? beq.s loop1 ;Nein! => movea.l #heap,A0 adda.l A6,A0 move.l A0,heap_pnt(A6) ;Heap zurücksetzen move.w D2,D0 bsr set_drive ;Laufwerk anmelden, FAT lesen movem.l D1-D3,-(SP) move.l D0,D1 pea drive_text(PC) bsr print_line moveq #'A',D0 add.b D2,D0 bsr chrout ;aktuelles Drive ausgeben pea drive_text2(PC) bsr print_line moveq #7,D2 ;max.8 Stellen ausgeben bsr dez_out ;freien Speicher ausgeben pea drive_text3(PC) bsr print_line ;"Bytes frei" movem.l (SP)+,D1-D3 bsr read_dir ;Kompletten Baum einlesen lea pfad(A6),A3 move.b #'A',(A3) add.b D2,(A3)+ ;Laufwerkskennung move.b #':',(A3)+ move.b #'\',(A3)+ ;Root-Pfad festlegen move.w D1,-(SP) bsr hunt_dir ;Baum durchsuchen move.w (SP)+,D1 loop1: tst.w D6 ;Laufwerksangabe? bpl.s loop2 ;Ja! => Fertig addq.w #1,D2 cmp.w #32,D2 ;bis Bit 32 wird hochgezählt bne.s loop ;noch nicht alle? Stimmt! => loop2: IF virus moveq #0,D1 move.w virus_count(A6),D1 ;Viren gefunden? beq.s loop3 ;Nein! => moveq #4,D2 ;max.5 Stellen ausgeben bsr dez_out pea viren_text(A6) bsr print_line ;verseuchte Dateien gefunden loop3: ENDC moveq #0,D1 move.w D4,D1 ;Anzahl der 'Hits' moveq #5,D2 ;max.6 Stellen ausgeben bsr dez_out pea files_text(A6) bsr print_line move.l D5,D1 ;Länge aller Dateien zusammen moveq #7,D2 ;max.8 Stellen ausgeben bsr dez_out pea files_text2(A6) bsr print_line moveq #0,D1 move.w D3,D1 ;Anzahl der Ordner moveq #4,D2 ;max.5 Stellen ausgeben bsr dez_out pea folders_text(A6) bsr.s print_line move.w #7,-(SP) trap #1 ;auf Taste warten addq.l #2,SP IF virus bclr #5,D0 cmp.b #'J',D0 ;Schreiben? bne.s _exit ;Nein! => movea.l data_base(A6),A4 movea.l A4,A3 ;Datenbasis merken code: tst.l (A4) ;Ende der Liste? beq.s code2 ;Ja! => lea 24(A4),A4 bra.s code code2: suba.l A3,A4 ;Länge aller Daten clr.w -(SP) pea fname(A6) move.w #$3C,-(SP) trap #1 ;Fcreate() addq.l #8,SP move.l D0,D7 bmi.s _exit ;Fehler move.l A3,-(SP) ;Basisadresse der Daten move.l A4,-(SP) ;Länge move.w D7,-(SP) move.w #$40,-(SP) trap #1 ;Fwrite() lea 12(SP),SP move.w D7,-(SP) move.w #$3E,-(SP) trap #1 ;Fclose() addq.l #4,SP ENDC _exit: pea exit_text(A6) bsr.s print_line ;Cursor wieder aus, etc. clr.w -(SP) trap #1 ;Programmende ENDPART ************************************************* * String auf dem Stack ausgeben ************************************************* PART 'print_line' print_line: movem.l D0-D2/A0-A2,-(SP) move.l 4+6*4(SP),-(SP) move.w #$09,-(SP) trap #1 ;Cconws() addq.l #6,SP movem.l (SP)+,D0-D2/A0-A2 move.l (SP)+,(SP) ;Stack korrigieren rts ENDPART ************************************************* * Zeichen in D0 ausgeben ************************************************* PART 'chrout' chrout: movem.l D0-D2/A0-A2,-(SP) move.w D0,-(SP) move.w #$02,-(SP) trap #1 ;Cconout() addq.l #4,SP movem.l (SP)+,D0-D2/A0-A2 rts ENDPART ********************************************** * positive Dezimal-Zahl in D1 * * ohne Führungsnullen ausgeben * * max.Anzahl der Stellen minus 1 in D2 * ********************************************** PART 'dez_out' dez_out: movem.l D0-D4/A1,-(SP) lea dez_tab(PC),A1 ;Zeiger auf die Tabelle move.w D2,D0 ;Anzahl der Stellen-1 add.w D0,D0 ;mal 4 add.w D0,D0 ;(schneller als LSL.W #2,D0!) lea 4(A1,D0.w),A1 ;Ptr auf die Stellenzahl moveq #0,D4 ;führende Nullen ignorieren dez_out1: move.l -(A1),D3 ;Tabellenwert moveq #-'0',D0 ;wird zu -'1',-'2',-'3',... dez_out2: sub.l D3,D1 ;SUB bis zum Unterlauf dbcs D0,dez_out2 ;Unterlauf? Nein! => neg.w D0 ;z.B. -'1' => '1' cmp.b #'0',D0 ;eine Null? beq.s dez_out4 ;Ja! => moveq #-1,D4 ;ab nun werden auch Nullen dez_out3: bsr.s chrout ;Zeichen in D0 ausgeben dez_out5: add.l D3,D1 ;den Unterlauf zurück dbra D2,dez_out1 ;schon alle Stellen? movem.l (SP)+,D0-D4/A1 rts dez_out4: tst.b D4 ;Nullen ausgeben? bne.s dez_out3 ;Ja! => tst.w D2 ;letzte Ziffer? bne.s dez_out5 ;Nein! => dann ignorieren moveq #'0',D0 ;wenn der gesamte Wert 0 ist, bra.s dez_out3 ;zumindest eine 0 ausgeben! dez_tab: DC.L 1,10,100,1000,10000,100000 DC.L 1000000,10000000,100000000,1000000000 ENDPART ********************************************* * convert() - Die Commandline auswerten * * * * Dabei wird die Variable "flag" gesetzt, * * das D6-Register (Laufwerk) definiert und * * die Suchmaske und der Suchstring aus dem * * Filenamen zusammengesetzt. * ********************************************* PART 'convert' convert: lea anfang-128+1(A6),A0 ;Commandline clr.b flag(A6) ;alle Flags löschen moveq #-1,D6 ;keine Laufwerksangabe convert1: cmpi.b #' ',(A0)+ ;Spaces überlesen beq.s convert1 subq.l #1,A0 cmpi.b #'-',(A0) ;folgt noch was? bne.s convert2 ;Nein! => addq.l #1,A0 bset #0,flag(A6) ;Files nur zählen convert2: cmpi.b #' ',(A0)+ ;Spaces überlesen beq.s convert2 subq.l #1,A0 IF virus cmpi.b #'+',(A0) ;folgt noch was? bne.s convert3 ;Nein! => addq.l #1,A0 bset #1,flag(A6) ;alle Files übernehmen convert3: cmpi.b #' ',(A0)+ ;Spaces überlesen beq.s convert3 subq.l #1,A0 cmpi.b #':',(A0) bne.s convert8 addq.l #1,A0 ;Doppelpunkt ignorieren convert8: moveq #0,D0 move.b (A0),D0 bmi.s convert14 ;Code>127 => kein Laufwerk bclr #5,D0 subi.b #'A',D0 bmi.s convert14 ;<'A' => kein Laufwerk cmp.b #32,D0 bhs.s convert14 ;>maxdrive => kein Laufwerk move.w D0,D6 ;Laufwerksangabe merken convert14: rts ELSE cmpi.b #':',(A0) bne.s convert6 moveq #0,D0 move.b 1(A0),D0 addq.l #2,A0 bclr #5,D0 cmp.b #32,D0 bhs.s convert6 ;>maxdrive => kein Laufwerk move.w D0,D6 ;Laufwerksangabe merken convert6: cmpi.b #' ',(A0)+ ;Spaces überlesen beq.s convert6 subq.l #1,A0 lea search_mask(A6),A1 ;Suchmaske lea search_chars(A6),A2 ;Suchdaten clr.l (A1)+ clr.l (A1)+ ;alle Suchmasken löschen clr.l (A1) subq.l #8,A1 moveq #0,D2 ;kein Allquantor moveq #7,D1 ;max.8 Zeichen Filename convert7: move.b (A0)+,D0 beq convert19 ;Ende des Filenames cmp.b #' ',D0 ;Space als Ende? beq convert19 ;Ja! => cmp.b #13,D0 ;CR als Ende? beq convert19 ;Ja! => cmp.b #'.',D0 ;Start der Extension? beq.s convert12 ;Ja! => cmp.b #'?',D0 ;Existenzquantor? bne.s convert8 ;Nein! => tst.w D2 ;Allquantor schon angegeben? bne.s convert11 ;Ja! => sf (A1)+ sf (A2)+ ;Zeichen ignorieren bra.s convert11 ;weiter => convert8: cmp.b #'*',D0 ;Allquantor? bne.s convert9 ;Nein! => moveq #-1,D2 ;Allquantor bra convert7 convert9: tst.w D2 ;Allquantor schon angegeben? bne.s convert11 ;Ja! => cmp.b #'a',D0 blo.s convert10 cmp.b #'z',D0 bhi.s convert10 bclr #5,D0 ;in Großbuchstaben wandeln convert10: move.b D0,(A2)+ ;Zeichen übernehmen st (A1)+ ;Maske dazu setzen convert11: dbra D1,convert7 ;max.8 Zeichen bra convert13 convert12: tst.w D2 ;Allquantor angegeben? bne convert13 ;Ja! => move.b #' ',(A2)+ ;Filenamen mit ' ' st (A1)+ ;auffüllen dbra D1,convert12 convert13: lea search_mask+8(A6),A1 ;Suchmaske lea search_chars+8(A6),A2 ;Suchdaten moveq #2,D1 ;max.3 Zeichen Extension convert14: move.b (A0)+,D0 beq.s convert17 ;Ende des Filenames cmp.b #' ',D0 ;Space als Ende? beq.s convert17 ;Ja! => cmp.b #13,D0 ;CR als Ende? beq.s convert17 cmp.b #'?',D0 ;Existenzquantor? bne.s convert15 ;Nein! => sf (A1)+ sf (A2)+ ;Zeichen ignorieren bra.s convert18 ;weiter => convert15: cmp.b #'*',D0 ;Allquantor? beq convert19 ;Nein! => cmp.b #'a',D0 blo.s convert16 cmp.b #'z',D0 bhi.s convert16 bclr #5,D0 ;in Großbuchstaben wandeln convert16: move.b D0,(A2)+ ;Zeichen übernehmen st (A1)+ ;Maske dazu setzen dbra D1,convert14 ;schon alle max. 8 Zeicher bra convert19 convert17: move.b #' ',(A2)+ ;Extension mit st (A1)+ ;Space auffüllen convert18: dbra D1,convertl7 convert19: rts ENDC ENDPART ************************************************* * hunt_dir() - Kompletten Dir-Baum ab A0 * * durchsuchen, dabei steht die Anzahl der * * Einträge im Directory in D0. * * * * Diese Routine ruft sich selbst rekursiv * * auf, um auch beliebig tiefe Ordnerebene zu * * durchsuchen. * * * * Der Baum muß durch read_dir() im Speicher * * liegen, d.h. bereits mit Pointern ver- * * kettet sein. * ************************************************* PART 'hunt_dir' hunt_dir: subq.w #1,D0 ;für DBRA hunt_dir1: move.w D7,D1 ;Illegale Fileattr-Bits and.b 11(A0),D1 ;Flags dazu bne hunt_dir3 ;Datei nicht finden! => cmpi.b #$E5,(A0) ;gelöschte Datei? beq hunt_dir2 ;Ja! => IF !virus move.l (A0),D1 beq hunt_dir4 ;Ende des Directories => and.l (A1),D1 cmp.l (A2),D1 ;Zeichen 1-4 des Filesnames bne hunt_dir3 move.l 4(A0),D1 and.l 4(A1),D1 cmp.l 4(A2),D1 ;Zeichen 5-8 des Filenamens bne hunt_dir3 move.l 8(A0),D1 and.l 8(A1),D1 cmp.l 8(A2),D1 ;die Extension vergleichen bne hunt_dir3 btst #0,flag(A6) ;Dateien nur zählen? bne.s print_fname4 ;Ja! => bsr print_fname ;Filenamen ausgeben print_fname4: addq.w #1,D4 ;Dateien zählen movep.w 31(A0),D1 move.b 30(A0),D1 swap D1 ;Dateilänge movep.w 29(A0),D1 ;von Intel move.b 28(A0),D1 ;nach 68000er add.l D1,D5 ;zur Gesamtlänge ELSE tst.b (A0) beq hunt_dir4 ;Ende des Directories => addq.w #1,D4 ;eine Datei mehr gefunden move.w 8(A0),D1 ;Zeichen 1+2 der Ext. holen cmp.w #'PR1,D1 ;PRG, PRX, etc. beq.s found_file cmp.w #'TO',D1 ;TOS beq.s found_file cmp.w #'TT',D1 ;TTP beq.s found_file cmp.w #'AC',D1 ;ACC, ACX, etc. beq.s found_file cmp.w #'AP',D1 ;APP beq.s found_file cmp.w #'DR',D1 ;DRV (Treiber) beq.s found_file cmp.w #'SY',D1 ;SYS (Plattentreiber!) bne hunt_dir3 found_file: move.l D0,-(SP) regs REG D1-D4/D6-A2/A4-A6 movem.l regs,-(SP) movep.w 27(A0),D0 move.b 26(A0),D0 ;Cluster ins Intel-Format move.w drive(A6),-(SP) ;aktuelles Drive subq.w #2,D0 mulu clsiz(A5),D0 ;mal Sektoren pro Cluster add.w datrec(A5),D0 ;+ erster freier Sektor move.w D0,-(SP) ;= abs. Sektor move.w #1,-(SP) ;einen Sektor einlesen pea sektor_buffer(A6) ;in den Buffer move.l #$040000,-(SP) trap #13 ;Rwabs() - Sektor einlesen lea 14(SP),SP tst.l D0 bmi _exit lea sektor_buffer(A6),A2 moveq #0,D0 cmpi.w #$601A,(A2) ;Programmkennung? bne.s count_loop1 ;Nein! => lea 28(A2),A0 ;Zeiger auf den Programmstart cmpi.w #$487A,(A0)+ ;PEA bne.s no_virus ;Nein! => cmpi.w #$FFFE,(A0)+ ;*-2(PC) bne.s no_virus ;Nein! => cmpi.w #$4EF9,(A0) ;c't-Virus? beq virus2 ;JA! =» cmpi.l #$207A0006,(A0)+ ;VCS-Virus? bne.s no_virus ;Nein! =>. cmpi.l #$4EFB8800,(A0) beq virus1 ;JA! =» no_virus: moveq #127,D2 count_loop: eor.w D2,D0 ;Prüfsumme errechnen sub.w (A2)+,D0 addq.w #1,D0 dbra D2,count_loop tst.w D0 bne.s count_loop1 moveq #213,D0 ;die Prüfsumme ist NIE 0! count_loop1: movem.l (SP),regs ;Chksumme in D0 tst.l D0 ;Prüfsumme=0? beq check_it6 ;dann kein Programm! => movea.l data_base(A6),A1 bra.s check_it0 check_it2: lea 24(A4),A1 ;der nächste Eintrag movea.l A2,A0 ;auf den Filenamen zurück check_it0: movea.l A0,A2 ;A0 retten movea.l A1,A4 ;A1 retten tst.l (A1) ;Tabelle ist leer, bzw. Ende beq.s check_it3 ;Neuer Eintrag nötig! => moveq #4,D1 ;nur 10 (!) Buchstaben check_it1: cmpm.w (A1)+,(A0)+ ;vergleichen dbne D1,check_it1 bne.s check_it2 ;ungleich => lea 10(A4),A1 ;Zeiger auf die Prüfsumme moveq #6,D1 ;max.7 Prüfsummen check_it5: tst.w (A1) beq.s check_it4 ;Prüfsumme nicht gefunden cmp.w (A1)+,D0 dbeq D1,check_it5 ;weiter vergleichen => lea -12(A1),A1 beq.s check_it8 ;Prüfsumme ist ok! => check_it4: movea.l A2,A0 bsr print_fname ;Filenamen ausgeben pea fehler_text(A6) bsr print_line ;Meldung machen! btst #1,flag(A6) ;stets übernehmen? bne.s check_it7 ;Ja! => movem.l D0/D2/A0-A2,-(SP) move.w #7,-(SP) trap #1 ;auf Taste warten addq.l #2,SP bclr #5,D0 move.w D0,D1 movem.l (SP)+,D0/D2/A0-A2 cmp.b #'J',D1 ;Übernehmen? bne.s check_it6 ;Nein! => check_it7: move.w D0,(A1) ;Prüfsumme kopieren addq.w #1,D5 bra.s check_it6 check_it3: move.l (A2)+,(A4)+ ;neuen Eintrag move.l (A2)+,(A4)+ ;Filenamen kopieren move.w (A2)+,(A4)+ ;(nur 10 Buchstaben!) move.w D0,(A4) ;l.Prüfsumme addq.w #1,D5 movem.l (SP),regs bsr print_fname ;Filenamen ausgeben pea new_text(A6) bsr print_line bra.s check_it6 check_it8: btst #0,flag(A6) ;Ausgabe? bne.s check_it6 ;Nein! => movem.l (SP),regs bsr print_fname ;Filenamen ausgeben bra.s check_it6 virus1: movem.l (SP),regs ;Filenamen nach A0 lea virus1_text(A6),A4 ;VCS-Linkvirus bra.s check_it9 virus2: movem.l (SP),regs ;Filenamen nach A0 lea virus2_text(A6),A4 ;Milzbrand-Virus check_it9: bsr.s print_fname ;Fname ausgeben pea virus_text(A6) bsr print_line ;Meldung machen! move.l A4,-(SP) bsr print_line addq.w #1,virus_count(A6) ;Virenanzahl check_it6: movem.l (SP)+,regs move.l (SP)+,D0 ENDC hunt_dir3: btst #4,11(A0) ;ein Ordner? beq.s hunt_dir2 ;Nein! => cmpi.b #$E5,(A0) ;Gelöscht? beq.s hunt_dir2 ;Ja! => cmpi.w #'. ',(A0) beq.s hunt_dir2 ;Dummy-Einträge ignorieren cmpi.w #'..',(A0) beq.s hunt_dir2 movem.l D0/A0/A3,-(SP) REPT 8 move.b (A0)+,(A3)+ ENDR move.b #'.',(A3)+ REPT 3 move.b (A0)+,(A3)+ ;in den Pfad kopieren ENDR addq.l #1,A0 move.b #'\',(A3)+ move.l (A0)+,D0 ;Baumlänge in Einträgen movea.l (A0),A0 bsr hunt_dir addq.w #1,D3 ;INC Anzahl der Ordner movem.l (SP)+,D0/A0/A3 hunt_dir2: lea 32(A0),A0 ;=> nächsten Eintrag dbra D0,hunt_dir ;alle Einträge durch? hunt_dir4: rts ENDPART ************************************************* * Filenamen ab A0 ausgeben * ************************************************* PART 'print_fname' print_fname: movem.l D0-D2/A0-A3,-(SP) REPT 8 move.b (A0)+,(A3)+ ;Filenamen ENDR move.b #'.',(A3)+ REPT 3 move.b (A0)+,(A3)+ ;an den Pfad anhängen ENDR btst #4,(A0) ;ein Ordner? beq.s print_fname0 move.b #'\',(A3)+ ;dann auch so abschließen print_fname0: move.b #13,(A3)+ ;CR move.b #10,(A3)+ ;LF clr.b (A3) lea pfad(A6),A3 print_fname1: moveq #0,D0 move.b (A3)+,D0 ;Zeichen holen beq.s print_fname2 ;Ende des Pfades => cmp.b #' ',D0 ;Spaces ignorieren beq.s print_fname1 ;nächstes Zeichen => cmp.b #'.',D0 ;Extension erreicht? bne.s print_fname3 ;Nein! => cmpi.b #' ',(A3) ;es folgt ein " "? beq.s print_fname1 ;dann den ignorieren print_fname3: bsr chrout ;das Zeichen ausgeben bra.s print_fname1 print_fname2: movem.l (SP)+,D0-D2/A0-A3 rts ENDPART ************************************************* * read_dir() - Kompletten Dir-Baum einlesen * * (ab A0 liegt er, Länge in D0) * * Diese Routine liest das Root-Directory ein * * und ruft dann bei jedem Ordner die Routine * * "read_sub_dir" auf, welche sich selbst * * wieder rekursiv aufrufen kann. * ************************************************* PART 'read_dir' read_dir: movem.l D1-D7/A1-A6,-(SP) move.w rdlen(A5),D0 ;Länge des Root-Dirs mulu recsiz(A5),D0 ;mal Sektorgröße bsr get_mem ;Speicher anfordern movea.l D0,A4 ;Zeiger auf das ROOT-Dir movea.l D0,A3 ;Anfang des Root-Dir move.w drive(A6),-(SP) ;aktuelles Drive move.w fatrec(A5),D1 add.w fsiz(A5),D1 move.w D1,-(SP) ;Startsektor des Root-Dir move.w rdlen(A5),-(SP) ;Länge der FAT move.l A4,-(SP) ;in den Buffer move.l #$040000,-(SP) trap #13 ;Rwabs() - DIR einlesen lea 14(SP),SP tst.l D0 bmi _exit move.w rdlen(A5),D7 ;Länge des Root-Dir mulu recsiz(A5),D7 ;mal Sektorgröße lsr.l #5,D7 ;\32 Bytes (Eintraggröße) subq.w #1,D7 ;Gesamtanzahl der Eintrag-1 hunt_dir_loop: btst #4,11(A4) ;ein Ordner? beq.s hunt_dir_loop1 ;Nein! => cmpi.b #$E5,(A4) ;gelöschter Ordner? beq.s hunt_dir_loop1 ;Ja! => bsr.s read_sub_dir hunt_dir_loop1: lea 32(A4),A4 ;nächster Eintrag dbra D7,hunt_dir_loop ;alle Einträge? movea.l A3,A0 ;Anfangsadresse des Dirs move.w rdlen(A5),D0 ;Länge des Dirs in Bytes mulu recsiz(A5),D0 ;errechnen lsr.l #5,D0 ;\32 Bytes (Eintragsgröße) movem.l (SP)+,D1-D7/A1-A6 rts ENDPART ************************************************* * read_sub_dir() - Unterverzeichnisse ab A4 * * rekursiv einlesen * * A4 zeigt auf den Ordner, der eingelesen * * werden soll. Es wird die Clusternummer * * ermittelt, dann geht's los. Damit * * "hunt_dir" das alles richtig hinbekommt, * * werden die Einträge mit Langworten * * (Zeigern) verkettet. Dazu steht im Ordner- * * eintrag (der ist 32 Bytes lang) bei Offset * * 16 ein Langwort, welches auf den Ordner * * zeigt. Beim Offset 20 steht die max. Anzahl * * an Einträgen die in diesem Ordner möglich * * sind. Diese Angabe wird von "hunt_dir" * * ebenfalls benötig. * ************************************************* PART 'read_sub_dir' read_sub_dir: movem.l D0-A6,-(SP) movep.w 27(A4),D3 move.b 26(A4),D3 ;Clusternr. im Intel-Format movea.l A4,A3 ;Ptr auf Hauptdirectory merken moveq #0,D5 ;Clusteranzahl des Sub-Dirs read_sub_dir1: moveq #0,D0 move.w clsizb(A5),D0 ;Bytes pro Cluster bsr get_mem ;Speicher anfordern movea.l D0,A4 ;Zeiger auf den Cluster tst.w D5 bne.s read_sub_dir5 move.l A4,16(A3) ;Zeiger auf Sub-Dir read_sub_dir5: move.w drive(A6),-(SP) ;akt.Drv move.w D3,D0 ;akt.Clusternummer subq.w #2,D0 mulu clsiz(A5),D0 ;mal Sektoren pro Cluster add.w datrec(A5),D0 ;+ erster freier Sektor move.w D0,-(SP) ;= abs. Sektor move.w clsiz(A5),-(SP) ;Cluster einlesen move.l A4,-(SP) ;in den Buffer move.l #$040000,-(SP) trap #13 ;Rwabs() - Cluster einlesen lea 14(SP),SP tst.l D0 bmi _exit move.w clsizb(A5),D7 ;Bytes pro Cluster lsr.w #5,D7 ;\32 Bytes (Eintragsgröße) subq.w #1,D7 ;für DBRA read_sub_dir2: btst #4,11(A4) ;ein Ordner? beq.s read_sub_dir3 ;Nein! => cmpi.b #$E5,(A4) ;gelöscht? beq.s read_sub_dir3 ;Ja! => cmpi.w #'. ',(A4) beq.s read_sub_dir3 ;ignore Dummy-Einträge cmpi.w #'..',(A4) beq.s read_sub_dir3 bsr.s read_sub_dir read_sub_dir3: lea 32(A4),A4 ;nächster Eintrag dbra D7,read_sub_dir2 ;alle Einträge? addq.w #1,D5 ;Clusteranzahl des Sub-Dir move.w D3,D0 add.w D0,D0 ;mal 2, als Zeiger auf die FAT movea.l fat_adr(A6),A0 ;Ptr: decodierte FAT move.w 0(A0,D0.w),D3 ;Nr des Folgeclusters bpl.s read_sub_dir1 ;Ende? Nein! => mulu clsizb(A5),D5 ;Bytes pro Cluster lsr.l #5,D5 ;Größe des Sub-Dir in Bytes move.l D5,12(A3) ;Anzahl der mgl.Einträge movem.l (SP)+,D0-A6 rts ENDPART ************************************************* * Laufwerk D0 als akt.Laufwerk anmelden, FAT * * einlesen. Der freie Speicherplatz wird in * * D0 zurückgegeben * * WICHTIG: A5 zeigt nach "set_drive" STETS * * auf den BPB des aktuellen Laufwerkes. * * Davon wird in "read_dir" ausgegangen. * ************************************************* PART 'set_drive' set_drive: movem.l D1-A4,-(SP) move.w D0,drive(A6) ;akt.Laufwerk setzen bsr.s get_bpb ;BPB-Adresse holen moveq #0,D0 move.w numcl(A5),D0 add.l D0,D0 ;Gesamtzahl der Cluster*2 bsr get_mem ;Speicher anfordern move.l D0,fat_adr(A6) move.w fsiz(A5),D0 ;Länge der FAT mulu recsiz(A5),D0 ;mal Bytes pro Sektor bsr get_mem ;Speicher anfordern move.l D0,fat_buffer(A6) move.w drive(A6),-(SP) ;aktuelles Drive move.w fatrec(A5),-(SP) ;Anfang der 2.FAT move.w fsiz(A5),-(SP) ;Länge der FAT move.l fat_buffer(A6),-(SP) move.l #$040000,-(SP) trap #13 ;2.FAT komplett einlesen lea 14(SP),SP tst.l D0 bmi _exit bsr.s wandel_fat ;FAT ins 68000er-Format movea.l fat_adr(A6),A0 ;Ptr: decodierte FAT addq.l #4,A0 ;erste 2 Cluster ignorieren move.w numcl(A5),D1 subq.w #3,D1 ;2 Cluster abziehen (DBRA!) moveq #0,D0 ;Anzahl der freien Cluster set_drive1: tst.w (A0)+ ;ein freier Cluster? bne.s set_drive2 ;Nein! => addq.w #1,D0 ;INC freie Cluster set_drive2: dbra D1,set_drive1 ;alle gezählt? mulu clsizb(A5),D0 ;mal Bytes pro Cluster movem.l (SP)+,D1-A4 rts ENDPART ************************************************* * BPB des akt.Laufwerkes nach A5 * ************************************************* PART 'get_bpb' get_bpb: move.w drive(A6)(SP) move.w #7,-(SP) trap #13 ;Getbpb(drive) addq.l #4,SP move.l D0,akt_bpb(A6) beq _exit movea.l D0,A5 ;Zeiger auf den BPB rts ENDPART ************************************************* * FAT vom 12-bit- bzw. 16-bit-Intel-Format * * ins 16-bit-68000er-Format * ************************************************* PART 'wandel_fat' wandel_fat: movea.l fat_buffer(A6),A0 ;Ptr: FAT movea.l fat_adr(A6),A1 ;Ptr: decodierte FAT move.w fsiz(A5),D0 ;Länge der FAT mulu recsiz(A5),D0 ;mal Bytes pro Sektor move.w bflags(A5),D1 ;Flags holen btst #0,D1 ;12-bit-FAT? beq.s wandel_fat2 ;Ja! => lsr.w #1,D0 ;Anzahl der Worte subq.w #1,D0 ;für DBRA wandel_fat1: movep.w 1(A0),D1 move.b (A0),D1 ;Intel-Wandl. bei 16-bit-FAT addq.l #2,A0 move.w D1,(A1)+ dbra D0,wandel_fat1 rts wandel_fat2: divu #3,D0 ;12-bit-FAT wandeln wandel_fat3: movep.w 1(A0),D1 move.b (A0),D1 ;Intel-Word holen and.w #$0FFF,D1 ;Bit 12-15 sind unwichtig cmp.w #$0FF0,D1 ;Nummer $FF0-$FFF? blo.s wandel_fat4 ;Nein! => or.w #$F000,D1 ;Vorzeichen erweitern wandel_fat4: move.w D1,(A1)+ ;Cluster merken movep.w 2(A0),D1 move.b 1(A0),D1 ;Intel-Word holen lsr.w #4,D1 ;Bit 0-3 sind unwichtig cmp.w #$0FF0,D1 ;Nummer $FF0-$FFF? blo.s wandel_fat5 ;Nein! => or.w #$F000,D1 ;Vorzeichen erweitern wandel_fat5: move.w D1,(A1)+ ;Cluster merken addq.l #3,A0 ;3 Bytes sind fertig dbra D0,wandel_fat3 ;schon alle Tripel? rts ENDPART ************************************************* * D0=get_mem(Byteanzahl in D0) * * Speicher vom Heap anfordern * ************************************************* PART 'get_mem' get_mem: move.l A0,-(SP) addq.l #1,D0 ;EVEN and.b #-2,D0 move.l heap_pnt(A6),D1 ;alter Heap-Pointer exg D0,D1 add.l D1,heap_pnt(A6) ;Platz auf dem Heap movea.l anfang-256+4(PC),A0 ;Speicherende cmpa.l heap_pnt(A6),A0 ;Speicherobergrenze? blo _exit ;Ja! => raus => movea.l (SP)+,A0 rts ENDPART ************************************************* * Ab hier: das DATA-Segment * ************************************************* DATA IF virus files_text: DC.B ' Dateien, ',0 files_text2: DC.B ' geänderte bzw. neue Dateien.',13,10,0 folders_text: DC.B ' Ordner sind vorhanden.' DC.B ' Schreiben?',13,10,0 init_text: DC.B 27,'E',27,'e' DC.B 'LVF V1.5 (Link-Virus-Finder)',13,10 DC.B '(c) 1989 by X-soft,' DC.B ' Markus Fritze',13,10,10,0 fehler_text: DC.B 'Prüfsumme ist fehlerhaft' DC.B ' (Übernehmen?)',13,10,0 virus_text: DC.B 7,'Datei enthält ' DC.B ' wahrscheinlich ',0 virus1_text: DC.B 'einen VCS-Linkvirus!',13,10,7,0 virus2_text: DC.B 'den Milzbrand-Linkvirus!',13,10,7,0 viren_text: DC.B ' wahrscheinlich verseuchte' DC.B ' Dateien gefunden!',7,13,10,0 new_text: DC.B 'Datei wurde hinzugefügt.',13,10,0 fname: DC.B 'LVF.DAT',0 ELSE files_text: DC.B ' Dateien mit insgesamt ',0 files_text2: DC.B ' Bytes gefunden.',13,10,0 folders_text: DC.B ' Ordner sind vorhanden.' DC.B ' Taste drücken.',13,10,0 init_text: DC.B 27,'E',27,'e' DC.B 'FFF V1.5 (Fast-File-Finder)',13,10 DC.B '(c) 1989 by E-soft, Markus Fritze' DC.B 13,10,10,0 ENDC exit_text: DC.B 27,'E',27,'f',0 drive text: DC.B 'Laufwerk ',0 drive_text2:DC.B ': (',0 drive_text3:DC.B ' Bytes frei.)',13,10,0 ********************************************** * Ab hier: das BSS-Segment * ********************************************** BSS DS.L 1024 ;4k Stack f. rekursive Suche own_stack: DS.L 0 drive: DS.W 1 ;aktuelles Laufwerk akt_bpb: DS.L 1 ;Ptr: BPB des akt.Laufwerks fat_buffer: DS.L 1 ;Adresse der gelesenen FAT fat_adr: DS.L 1 ;Adresse der decodierten FAT virus_count:DS.W 1 ;Anz. der gefundenen Viren flag: DS.B 1 ;Bit 0=1: keine Fileausgabe ;Bit 1=1: neue Programme übernehmen EVEN pfad: DS.B 256 ;Platz für den Suchpfad heap_pnt: DS.L 1 IF virus data_base: DS.L 1 ;Ptr: File-Daten im RAM data_buff: DS.B 24*max_prgs ;Prg-Buffer sektor_buffer:DS.B max_sektorsize ;Sektorbuf. ELSE search_mask:DS.B 12 ;Suchmaske (Joker) search_chars:DS.B 12 ;Suchzeichen ENDC heap: DS.L 0 ;freier Speicher ab hier END