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