COC Artikel

on Michael Vorburger's Personal Homepage
Home Personal Projects alpha ware

 

Dies ist der COC (Checksum on Code) Artikel wie ich ihn damals an das ATARI Magazin "ST Computer" geschickt habe. Er wurde von der Redaktion noch etwas angepasst und nicht 100% so abgedruckt, wie hier angegeben.

Der folgende Artikel kann auch als WinWord 2.0 Datei (inkl. Source, 7 Seiten, 35 KB) heruntergeladen werden. Weitere Seiten: [ COC Artikel ] COC C-Source ] COC Pascal Binding ]

Virenabwehr in eigenen Programmen

Veränderungen im Programmcode mittels Prüfsumme erkennen

sowie: Dauerhaftes Speichern von Variablen im Datensegment

Michael Vorburger (1993)

Das Problem der Computerviren ist leidig bekannt; Lösungsansätze kommen vor allem in Form von speziellen Antiviren-Programmen. Diese sind jedoch für die Benutzer nicht immer einfach zu handhaben. Bei der Installation oder Benutzung ist häufig Vorwissen erforderlich (Linkvirus? Bootsektor? Interrupt? Vektor-Überprüfung?). Zudem muss sich der Benutzer in grosser Selbstdisziplin üben; eine Diskette will immer zuerst getestet (neudeutsch: gescannt) sein. All dies sind Dinge, die einen Anfänger überfordern können und wohl auch von fortgeschrittenen Anwendern nicht immer eingehalten werden. Die Forderung liegt daher nahe, dass die Virenabwehr bereits beim (hoffentlich) sachverständigen Entwickler und nicht erst beim Endanwender einsetzen sollte. Hier wird deshalb ein Verfahren vorgestellt, mit dem Programme so geschrieben werden können, dass sie sich selbständig auf Virenbefall testen.

Grundlegende Arbeitsweise

Man überlege sich einmal folgendes: Ein Virus (genauer ein Link-Virus; und nur um diese geht es hier) muss auf jeden Fall, so geschickt und ‘intelligent’ er auch sein mag, das von ihm befallene Programm irgendwo verändern. Wenn also über den gesamten ausführbaren Maschinencode beim ersten Aufstarten eine Prüfsumme gebildet wird, kann durch Vergleich des jedesmal neu berechneten Wertes mit der ersten Prüfsumme festgestellt werden, ob ein Virus an einem Programm herumgebastelt hat.

Dem was sich hier so einfach anhört, steht in der praktischen Realisierung ein Problem gegenüber: Der Code im Speicher ist bei jedem neuen Start anders, da das Betriebssystem des Atari ein Programm an eine beliebige Stelle laden kann. Damit es dann dort auch läuft, wird es sogenannt reloziert, das heisst, sämtliche absoluten Sprünge und Bezüge werden umgerechnet. Dadurch verändern sich aber viele Stellen. Wie dieses Problem gelöst werden kann, wird später besprochen. Eine (zu) einfache Lösung wäre es, die Programmdatei einfach nochmals vom Medium einzulesen. Dies ist aber aus verschiedenen Gründen unsinnig (unnötige Verzögerung zweimaliges Lesen; durch Viren einfacher abzulenken als direkte Überprüfung im Speicher).

Die Idee ist natürlich nicht grundlegend neu; darauf gestossen bin ich durch einen Artikel in einer Fachzeitschrift ([1]). Die dort vorgestellten Routinen sind aber nur für MS-DOS Systeme zu gebrauchen, deshalb schrieb ich eine Adaptierung für TOS.

Anwendung der Routine

All jenen Leser, welche sich nicht weiter für die internen Details der Realisierung interessieren, soll in diesem Abschnitt gezeigt werden, wie sie ihre Programme einfach schützen können. Als Vorlage für das Vorgehen kann man sich ans Beispielprogramm DEMO.C halten.

Im Hauptteil des Programms braucht man keinerlei Rücksicht auf die Viruscheck-Funktion zu nehmen. Nur in der Initialisierungsphase muss irgendwann einmal die Prüfsummenfunktion aufgerufen werden. Wenn man ein bestehendes Projekt schützen will, sollte man daher so vorgehen:

1. COC.C in die Projektdatei einfügen

2. im Include-Teil des Hauptprogramms (zu Beginn irgendwo) #include COC.H einfügen

3. in der globalen Variable SaPrProgName[] den Dateinamen der ausführbaren Programmdatei ablegen; zum Beispiel char SaPrProgName[] = "DEMO.TOS";

4. irgendwo die Funktion SaPrSelfTest() aufrufen; ist der Rückgabewert ungleich Null, so ist etwas nicht in Ordnung.

Ein negatives Vorzeichen des Rückgabewertes zeigt an, dass eine Programmänderung vorliegt (Virusinfektion!), bei positivem Rückgabewert liegt ein Problem vor, aufgrund dessen die Prüfsumme nicht berechnet werden kann. Die genaue Bedeutung kann untenstehender Tabelle entnommen werden:

-4

Prüfsumme stimmt nicht mehr, Programmcode wurde verändert

-3

Länge der Programmdatei oder eines Segments hat sich verändert

-2

Informationen in Basepage und Programmheader stimmen nicht überein

0

alles OK, entweder Prüfsumme erstmalig berechnet und erfolgreich gespeichert oder Prüfsumme stimmt mit erster Berechnung überein

1

Programmdatei konnte nicht für nur Lese- (beim normalen Test) oder Lese- und Schreibzugriff (beim ersten Starten) geöffnet werden; wahrscheinlich SaPrProgName falsch gesetzt

2

Prüfsumme kann wegen GEMDOS-Fehler nicht in Programmdatei geschrieben werden

In DEMO.C ist der englische Wortlaut dieser Fehlermeldungen im Stringfeld err[] gespeichert, um nach dem Aufruf von SaPrSelfTest ausgegeben zu werden. In eigenen Programmen sollte jedoch eine Überprüfung auf ungleich Null genügen. Falls ein Fehler auftrat, sollte der Benutzer mit einer Meldung im Stil von ‘Dieses Programm wurde verändert. Wahrscheinlich liegt eine Virusinfektion vor!’ gewarnt werden.

Im Prinzip braucht sich der Programmierer bei dieser Lösung nicht darum zu kümmern, ob die Prüfroutine nun zum ersten Mal benutzt wird oder ob es bereits der einmillionste Durchlauf ist (wie wär's mit einer Meldung 'Gratuliere & herzlichen Dank für Ihre Treue! Sie haben dieses Programm gerade zum 1000 Mal gestartet'...). Falls man dies aber trotzdem in Erfahrung bringen will, muss nur getestet werden, ob die Prüfsumme noch gleich Null ist (also noch nie berechnet wurde) oder einen anderen Wert hat. Da dies in einer lokalen Variable des Moduls COC.C gespeichert ist, muss dem Compiler mit extern long SaPrLongs[2]; erst ein Mal klargemacht werden, dass man gedenkt, auf diese Variable zuzugreifen. Wenn SaPrLongs[0] dann gleich Null ist, handelt es sich um den ersten Aufruf des Programms.

Zum Schluss hier noch ein allgemeiner Tip (kann für alle in Module aufgeteilten Projekte benutzt werden), um die Turn-Around Zeit des Compilers zu beschleunigen: Einmal compilierte Module liegen im Output-Verzeichnis (sehen Sie unter Options/Compiler.../Output directory nach, wo dies bei Ihnen liegt) als Objektdateien (*.O) vor. Diese können direkt in die Projektdatei eingebunden werden (wie dies mit dem Startupcode PCSTART.O ja immer geschieht). Compilieren Sie also COC.C einmal mit Compile und kopieren Sie COC.O dann ins Bibliotheks-Verzeichnis (Options/Linker.../Library directory) um in den Projektdateien COC.O statt COC.C für schnellere Compilierung verwenden zu können.

Technische Realisierung

Wie arbeitet SaPrSelfTest() denn nun? Wie oben bereits angesprochen, kann nicht einfach eine Prüfsumme über das Programm im Speicher gebildet werden. Das Betriebssystem (genauer: das GEMDOS) reloziert ein Programm nämlich beim Laden. Darunter ist folgendes zu verstehen: Wenn (in Maschinencode) im Programm steht ‘lade die Variable aus Adresse 1000 in ein Prozessorregister’, läuft das natürlich nur so lange gut, wie die gewünschte Variable wirklich an Speicherstelle 1000 steht. Da dorthin aber vielleicht bereits ein anderes Programm geladen wurde, geht unser Computer viel geschickter vor: Der Compiler/Linker schreibt sagt nur ‘lade die Variable aus (Programmanfang+734 Bytes) in ein Register’. Beim Einladen sorgt dann das Betriebssystem durch relozieren dafür, dass unser Programm an der betreffenden Stelle geändert wird, indem zur relativen Adresse der Variable die Anfangsadresse des Programms hinzu addiert wird. Wo diese Umrechnungen vorgenommen werden müssen, entnimmt GEMDOS einer speziellen Tabelle, der Relozierungstabelle, die auch in der Programmdatei enthalten ist.

Mit diesem Wissen kann man eine Prüfsummenroutine schreiben, die diese Relozierungen wieder rückgängig macht. Dies darf natürlich nicht im Programmcode direkt geschehen, ein Puffer tut's aber genauso. Die Abschnitte zwischen den Relozierungspunkten kann man in einer einfach Schleife durchlaufen. COC arbeitet also ungefähr in folgender Weise:

Die Abbruchbedingung wurde hier der Einfachheit halber weggelassen; Schluss ist einfach, wenn HowManyBytesToTest (Konstante, in COC.C definiert) Bytes getestet wurden. Es darf aber nie zuviel überprüft werden, maximal (und das ist gleichzeitig die sinnvollste Variante, deshalb im Listing so gewählt) das ganze Textsegment (der Maschinencode des ausführbaren Programms). Dahinter kommt das Datensegment (Initialisierungswerte für Variablen), das sich aber sehr wohl ändern darf und daher nicht in die Prüfsummenberechnung miteinbezogen werden sollte.

Hier ist immer von Prüfsummenbildung die Rede. Wie geschieht das genau? Nun, eigentlich werden zwei Checksummen benutzt; einmal eine fortlaufende Exklusiv-Oder Verknüpfung und parallel dazu eine Addition ohne Überlauf. Wie man beim Studium des Flussdiagramms wohl schon geahnt hat, wird in Longword-Schritten gearbeitet. Deshalb wird auch bloss bis ‘knapp vor’ dem nächsten Relozierungspunkt die Prüfsumme gebildet; soweit wie es mit Longwords nämlich gerade noch geht.

Wie man aus der Tabelle der Fehlermeldungen oben ersehen kann, werden auch noch andere Dinge überprüft. Wenn man den Programmheader schon mal eingelesen hat, könnte man die Länge der Segmente sowie der ganzen Programmdatei gleich auch speichern (SaPrLens[4]) und vergleichen. Zusätzlich wird noch kontrolliert, ob die Angaben über die Segmentlängen in Basepage und Header übereinstimmen.

Bekannte Probleme

Die Routine ist relativ sicher programmiert und sollte sich nicht so ohne weiteres im Programm-Nirwana verlaufen (sprich: abstürzen). Es gibt jedoch einen Fall, der nicht abgefangen wird: Offsets für die nächste Relozierung dürf(t)en niemals kleiner als 4 sein (siehe dazu Erklärung der Relozierungsinformationen in [2]), macht ja eigentlich auch keinen Sinn. Zu meiner grössten Überraschung fanden sich jedoch in vielen grösseren (kommerziellen) Programmdateien 2er Offsets. Meine PureC und PurePascal compilierten Projekte haben diese Eigenheit jedoch nie. Falls solche Offsets auftreten, ist das Verhalten der Prüfsummenfunktion undefiniert.

Ebenfalls nicht abgefangen werden Programmdateien gänzlich ohne Relozierungstabelle (ph_absflag im Programmheader !=0). Gemäss den Empfehlungen von Atari sollte in diesem Fall aber sowieso eine leere Relozierungstabelle verwendet werden (erster Offset gleich 0L, siehe auch [2]). Offensichtlich gibt es jedoch Programme, welche diese Empfehlung missachten (z.B. KSPREAD4 und UVK_5_3; vielleicht vollständig in Assembler programmiert).

Um derartige ‘faule Eier’ aufzuspüren, gibt es das Programm CHECK, das die Relozierungsinformationen einer ausführbaren Datei auf Herz und Nieren auseinandernimmt. Es ist aus Platzgründen nicht abgedruckt; auf der Monatsdiskette ist es jedoch enthalten.

Dauerhaftes speichern einer Variable im Datensegment

Praktisch als Nebenprodukt fällt bei dieser Routine ein nützlicher Trick ab: Die Prüfsumme und die Längeninformationen wird ja nicht in einer separaten Datei gespeichert (erstens würde es sich doch nicht lohnen, wegen 24 Bytes einen ganzen Cluster zu ‘verbraten’ und zweitens ist die folgende Lösung einfach eleganter), sondern direkt ins Datensegment der Programmdatei geschrieben. Auf dem Mac werden Programmeinstellungen schon längst so gespeichert, Windows verwendet externe *.INI-Dateien (mit standardisiertem Aufbau) und auf dem Atari sind die *.INF Dateien (chaotisch und überall anders) weit verbreitet. Dabei wäre es doch so einfach:

Variablen, die beim nächsten Programmstart automatisch mit dem alten Wert initialisiert sein sollen (in unserem Beispiel SaPrLongs[] & SaPrLens[]), müssen im DATA-Segment des Programms liegen. Für eine Hochsprache bedeutet dies, dass nur initialisierte globale Variablen dauerhaft gespeichert werden können. Lokale Variablen werden auf dem Stack abgelegt, ihr Inhalt ist nirgends in der Programmdatei abgelegt und globale nicht initialisierte Variablen liegen im BSS-Segment (das es auf einem Datenträger gar nicht gibt, wird erst im Speicher aufgebaut).

Man braucht nun nur die Programmdatei zu öffnen, den Dateizeiger mit einem Seek an die richtige Stelle zu positionieren und die Variable jetzt auszugeben. Die ‘richtige Stelle’ ist gar nicht so schwer zu berechnen: Absolute Adresse der Variable im Speicher minus Beginn des Textsegements plus Länge des Programmheaders.

Zur eigenen Anwendung soll das Beispielprogramm SAVE_VAR.C studiert werden.

Wie sicher ist diese Methode?

Bezüglich Überlistung oder Umgehung dieses Verfahrens ist folgendes zu sagen: Erstens: Absolute Sicherheit gibt es prinzipiell in keinem datenverarbeitenden System (wer das Gegenteil behauptet, ist ein Bluffer). Zweitens: Diese Methode kann in mindestens zwei Fällen umgangen werden: Einem ‘Hacker’, der ‘von Hand’ diesen Schutzmechanismus ausser Gefecht setzen will (etwa um einen Kopierschutz lahmzulegen oder Copyrights oder Seriennummern zu ändern), wird dies ein leichtes sein. Wer sich dagegen schützen will, sollte sich - nachdem er sich vor Augen geführt hat, dass ein Hacker mit genügender Verbissenheit und Zeitaufwand immer zum Ziel gelangen wird - schon eher mal mit Hardware-Dongles beschäftigen (siehe [3]). Im zweiten Fall (das eigentliche Problem) lässt sich folgendes Szenario ausdenken: Ein Virus kennt den Code dieses Schutzmechanismus. Theoretisch kann er in einem Programm nun die Routine suchen, irgendwo die Adresse der Prüfsummen auslesen und diese nach seinen Veränderungen neu berechnen. Theoretisch deshalb, weil ein solcher Virus schon recht kompliziert wäre und damit gross würde (ein Problem für Viren, die möglichst klein und unauffällig bleiben müssen). Trotzdem sei es jedem Programmierer empfohlen, die Routine da und dort etwas umzubauen, damit möglichst viele verschiedene Versionen existieren.

Zum Schluss...

...bleibt nur noch zu sagen, dass in Zukunft hoffentlich viele Software-Entwickler Ihre Programme ‘virensicher(er)’ machen werden (mit diesem oder anderen Verfahren). Denn Computerviren sind eine Gefahr - aber eine, die man mit geeigneten Mitteln gut bekämpfen kann.

Literatur

[1] c't magazin für computer technik, Ausgabe Juli '92, S.146ff

[2] ATARI Profibuch ST-STE-TT, 10.Auflage 1991, Sybex-Verlag

[3] ST-Computer ???? oder c't /// irgendwo war mal was über ein programmierbares Dongle (für den Atari?) drin

 

Go to TOP of page
Page last modified on 03-Mai-98
© Copyright 1998-99 homepage@vorburger.ch [E-MAIL]

Site hosted by
ItaWeb, Peruggia (Italy)

KISSfp FrontPage Add-On
KISSfp FrontPage

  URL: http://www.vorburger.ch/projects/coc/article.html