XBRA - Vektorverbiegende Programme

In der ST-Computer wurde schon mehrfach das XBRA-Verfahren für “vektorverbiegende” Programme vorgestellt. Solche Programme ändern die Adressen von Unterprogrammen in bestimmten Systemvariablen des ST. Auf diese Art lassen sich etwa Treiber für RAM-, ROM- oder Harddisks oder auch eine neue Hardcopyroutine installieren. Da das neue Programm meist nur einen Teil der Aufgaben der alten Routine übernimmt (z.B. nur die Operationen auf ein bestimmtes Laufwerk), muß es sich die Adresse des alten Unterprogramms merken und dieses gegebenenfalls aufrufen. Beim XBRA-Verfahren wird nun dem Einsprungpunkt in die neue Routine (also der Adresse, die in die Systemvariable eingetragen wird) folgende Struktur vorangestellt:

XBMAGIC: dc.l "XBRA"
XB_ID:   dc.l "NAME"
XB_VEC:  dc.l 0
ENTRY:   ...

Das Langwort XB_MAGIC enthält den magischen Wert $58425241 (="XBRA"), um ein Programm nach dem XBRA-Standard überhaupt erkennen zu können. In XB ID ist ein vier Byte langer, programmspezifischer Name enthalten. Bei der Installation merkt sich das Programm die Adresse der alten Routine in XB VEC und schreibt in die betreffende Systemvariable die Adresse der neuen Routine (ENTRY). Da nun die Adresse der alten Routine direkt vor dem Einsprungpunkt in die neue steht, kann man, falls mehrere Programme nacheinander installiert wurden (z.B. RAM-Disk. ROM-Disk, Harddisktreiber...), leicht eine ganze Kette von Routinen zurückverfolgen. Es ist nun auch möglich, gezielt ein Programm aus dieser Kette zu entfernen und genau dies kann XBRA.TOS.

Kernstück des Programms ist die Funktion getxbra(). Man übergibt ihr in vec einen Zeiger auf den Zeiger auf die Routine (auf Deutsch: die Adresse der Systemvariablen). Der Inhalt der Systemvariablen und ihre Adresse werden zunächst auf Plausibilität geprüft, der Zugriff auf eine nichtexistierende oder ungerade Adresse würde nämlich zum Absturz führen. Sollte die in die Systemvariable eingetragene Routine dem XBRA-Standard entsprechen, so liefert die Funktion 1 zurück und legt in name die Programmkennung und in next die Adresse des nächsten Routinenzeigers (XB_VEC) ab, sonst wird 0 zurückgeliefert. Beim nächsten Aufruf von getxbra() kann der Wert von next wieder in vec übergeben werden, um die XBRA-Kette weiter zu verfolgen. Ein Beispiel für dieses Vorgehen finden Sie in der Funktion prnpage(). un_link() entfernt aus allen XBRA-Ketten die Routinen mit der Programmkennung id. Sollte dieselbe Kennung mehrmals in der Kette auftauchen, wird nur der erste Eintrag (d.h. das zuletzt installierte Programm) entfernt. Nicht alle Systemvektoren stehen an einer festen Adresse, manchmal muß diese auch erst erfragt werden. Ein Beispiel dafür findet sich in varinit().

Nach dem Start zeigt das Programm auf mehreren Bildschirmseiten die XBRA-Ketten für eine Reihe von wichtigen Systemvektoren an. Das Umschalten der Seiten ist durch direkte Eingabe der Seitennummer möglich. Will man nun ein Programm “aushängen”, so kann man nach der Anwahl der Option “R” die Programmidentifikation eingeben (Klein- und Großbuchstaben werden unterschieden). Nach einer Rückfrage entfernt das Programm die Routinen mit dieser ID aus allen Ketten. Mit der Es-cape-Taste können Sie das Programm beenden.

XBRA.TOS wurde mit TURBO C entwickelt. Die einzige dabei verwendete Besonderheit dieses Compilers ist die Definition des Typs ADDR als Zeiger auf einen Voidzeiger. Zeiger auf Objekte vom Typ void bezeichnen im ANSI-Standard Zeiger auf beliebige Objekte. Sie können eigentlich auch jeden anderen Zeigertyp verwenden. Auf die Verwendung von Prototypen habe ich bewußt verzichtet, da diese von den meisten Compilern für den ST sowieso nicht unterstützt werden. TURBO C-Benutzer können die Warnungen des Compilers einfach ignorieren. Da das Zeigergefummel in C nicht jedermanns Sache ist, habe ich, um die Übertragung in andere Sprachen zu erleichtern, die Funktion getxbra() auch in OMIKRON.BASIC implementiert. Beachten Sie bitte, daß Zugriffe auf die Systemvariablen unbedingt im Supervisormodus erfolgen müssen. (Bei Peeks und Pokes in OMIKRON.BASIC ist das immer der Fall.) In der BASIC-Version habe ich mir übrigens die Plausibilitätsprüfung gespart, der Interpreter fängt Systemfehler ohne Absturz ab.

Es bieten sich einige Erweiterungsmöglichkeiten an. Zum einen sollte die Liste der Systemvektoren noch wesentlich ergänzt werden (z.B. um die VBL-Slots). In [1] und [2] werden Sie dazu viele Anregungen finden. Zum anderen könnte man in der Funktion prnpage() am Ende der ID-Kette die Adresse der letzten Routine (*vec) ausgeben. Liegt dieser Wert im Bereich von SFC0000 bis $FEFFFE, so endet die Kette im ROM. Ist dies nicht der Fall, so ist das meist ein Hinweis auf ein Programm im Speicher, das nicht dem XBRA-Standard entspricht (immer vorausgesetzt, Sie verwenden wirklich ein ROM-TOS). Wenn Sie nur noch XBRA-Programme verwenden, so haben Sie damit sogar die Möglichkeit. Viren in Ihrem System zu erkennen.

Sollte Ihr ATARI bis zum letzten Byte mit residenten Utilities vollgestopft sein und XBRA zeigt nach dem Start dennoch keine einzige Kennung an, so heißt das nicht unbedingt, daß das Programm nicht funktioniert. Tatsächlich verwenden bisher nur sehr wenige Programme das XBRA-Verfahren. Außerdem versteckt ein einziges nicht dem Standard entsprechendes Programm alle Routinen, die vorher auf demselben Systemvektor installiert wurden. Noch ein Hinweis: Wenn Sie ein Programm aushängen. so wird es durchaus nicht aus dem Speicher entfernt. Klinken Sie also etwa einen RAM-Disk-Treiber aus, so können Sie zwar nicht mehr auf die Daten zugreifen, den belegten Speicher bekommen Sie aber trotzdem nicht zurück. Somit ist der praktische Wert dieses Programms eher gering, es soll vielmehr die Möglichkeiten des XBRA-Verfahrens demonstrieren.

Literatur:

[1] Jankowski/Reschke/Rabich: ATARI ST Profibuch, Sybex, 1987

[2] A. Esser: Die Systemvariablen des TOS, ST-Computer 11 & 12/88

/* XBRA.TOS
 * Programm zur Anzeige von XBRA-Programmen
 * im Speicher und zum gezielten Deaktivieren
 * einzelner Programme.
 * Entwickelt mit Turbo C 1.0, Andreas Kohler '89
 * (c) MAXON Computer 1989 
 */

#include <stdio.h>
#include <tos.h>
#include <string.h>
#include <ctype.h>

/* Bildschirmsteuerung */

#define Cls()       printf("\033E")
#define Nowrap()    printf("\033w")
#define Top()       printf("\033H\0331")

typedef void **ADDR;

typedef struct {
    char    xb_magic[4];
    char    xb_id[4];
    void    *xb_vec;
} XBRA;

typedef struct {
    char    name[13];
    ADDR    addr;
} SYSVAR;

int varnum;
SYSVAR vars[] = {
    {"midivec", 01},        {"vkbdvec", 01},
    {"vmiderr", 01},        {"statvec", 01},
    {"mousevec", 01},       {"clockvec", 01},
    {"joyvec", 01},         {"midisys", 01},
    {"ikbdsys", 01},        {"Trace", 0x241},
    {"Line A", 0x281},      {"Line F", 0x2C1},
    {"Gemdos", 0x841},      {"Bios", 0xB41},
    {"Xbios", 0xB81},       {"etv_timer",0x4001},
    {"etv_critic", 0x4041}, {"etv_term", 0x4081},
    {"resvector", 0x42A1},  {"hdv_init", 0x46A1},
    {"swv_vec", 0x46E1},    {"hdv_bpb", 0x4721},
    {"hdv_rw", 0x4761},     {"hdv_boot", 0x47A1},
    {"hdv_mediach", 0x47E1},{"exec_os", 0x4FE1},
    {"dump_vec", 0x5021},   {"prt_stat", 0x5061},
    {"prt_vec", 0x50Al},    {"aux_stat", 0x50E1},
    {"aux_vec", 0x5121},    {"", 01}
};

void varinit()
{
    ADDR    lp;
    SYSVAR  *sv; 

    sv = vars;
    lp = (ADDR) Kbdvbase(); 
    for (; ! sv->addr; sv++)
        sv->addr = lp++;
}

int getxbra(vec, name, next)
ADDR vec; 
char *name;
ADDR *next;
{
    XBRA *xbp;

    if ((long) vec % 21)    /* ungerade Adresse */
        return (0); 
    if ((long) *vec % 21) 
        return (0); 
    if ((long) *vec < sizeof(XBRA)) 
        return (0); 
    xbp = (XBRA *) *vec; 
    xbp--;
    if (! strncmp(xbp->xb_magic, "XBRA", 4))
    {
        strncpy(name, xbp->xb_id, 4); 
        name[4] = '\0';
        *next = &(xbp->xb_vec); 
        return (1);
    }
    return (0);
}

void un_link(id) 
char *id;
{
    register int i; 
    char name[5];
    ADDR next, old;

    for (i = 0; i < varnum; i++)
    {
        old = vars[i].addr;
        while (getxbra (old, name, &next))
        {
            if (! strncmp(name, id, 4))
            {
                *old = *next; 
                break;
            }
            old = next;
        }
    }
}

void prnpage(n) 
int n;
{
    register int i;
    ADDR next; 
    char name[5];

    Cls();
    printf(" 1... = Seitenwahl, R = Eintrag ");
    printf("entfernen, <ESC> = Abbruch "); 
    for (i = 24 * n; i < varnum && i < (n+1) * 24; i++)
    {
        printf("\n%12s $%041X ",vars[i].name, vars[i].addr); 
        next = vars[i].addr;
        while (getxbra(next, name, &next)) 
            printf("%4s ", name);
    }
}

main()
{
    long oldstack; 
    int page, c; 
    char rmid[5];

    Nowrap();
    oldstack = Super(NULL); 
    varinit();

    for (varnum = 0; vars[varnum].addr; varnum++) ;
    prnpage(page = 0);

    while ((c = toupper( (int) Cnecin())) != 27)
    {
        if (c >= '1' && c <= '9')
        {
            if ((c - '1') * 24 <= varnum)
            {
                page = c - '1';
                prnpage(page);
            }
        }
        else if (c == 'R')
        {
            Top();
            printf(" Entfernen: ");
            scanf("%4s", rmid);
            Top();
            printf(" \'%s\' entfernen (J/N) ? ",rmid);
            if (toupper( (int) Cnecin()) == 'J') 
                un_link(rmid); 
            prnpage(page);
        }
    }
    Super((void *) oldstack);
}
' getxbra() in OMIKRON.BASIC 
' Vec%l, Name$ und Nxt%l sind global, da 
' Rückgabeparameter in Fkt. nicht erlaubt

DEF FN Getxbra%
    LOCAL L%L,I%
    L%L= LPEEK(Vec%L)
    IF ( LPEEK(L%L-12)=$58425241) THEN
        Name$=""
        FOR I%=-8 TO -5
            Name$=Name$+ CHR$( PEEK(L%L+I%)) 
        NEXT I%
        Nxt%L=L%L-4 
        RETURN 1 
    ENDIF 
RETURN 0

Andreas Kohler
Links

Copyright-Bestimmungen: siehe Über diese Seite