GNU/Linux >> LINUX-Kenntnisse >  >> Linux

C-Bibliothek zum Lesen der EXE-Version von Linux?

Die Version der Datei befindet sich im VS_FIXEDFILEINFO struct, aber Sie müssen es in den ausführbaren Daten finden. Es gibt zwei Möglichkeiten, das zu tun, was Sie wollen:

  1. Suchen Sie in der Datei nach der Signatur VERSION_INFO und lesen Sie den VS_FIXEDFILEINFO struct direkt.
  2. Finde .rsrc Abschnitt, analysieren Sie den Ressourcenbaum, suchen Sie die RT_VERSION Ressource, analysieren Sie sie und extrahieren Sie VS_FIXEDFILEINFO Daten.

Der erste ist einfacher, aber anfällig dafür, die Signatur zufällig an der falschen Stelle zu finden. Darüber hinaus befinden sich die anderen Daten, nach denen Sie fragen (Produktname, Beschreibung usw.), nicht in dieser Struktur, daher werde ich versuchen zu erklären, wie Sie die Daten auf die harte Tour erhalten.

Das PE-Format ist etwas kompliziert, daher füge ich den Code Stück für Stück ein, mit Kommentaren und mit minimaler Fehlerprüfung. Ich werde eine einfache Funktion schreiben, die die Daten auf die Standardausgabe ausgibt. Das Schreiben als richtige Funktion bleibt dem Leser als Übung überlassen :)

Beachten Sie, dass ich Offsets im Puffer verwenden werde, anstatt die Strukturen direkt zuzuordnen, um Portabilitätsprobleme im Zusammenhang mit der Ausrichtung oder dem Auffüllen der Strukturfelder zu vermeiden. Wie auch immer, ich habe den Typ der verwendeten Strukturen kommentiert (siehe Include-Datei winnt.h für Details).

Zuerst ein paar nützliche Deklarationen, sie sollten selbsterklärend sein:

typedef uint32_t DWORD;
typedef uint16_t WORD;
typedef uint8_t BYTE;

#define READ_BYTE(p) (((unsigned char*)(p))[0])
#define READ_WORD(p) ((((unsigned char*)(p))[0]) | ((((unsigned char*)(p))[1]) << 8))
#define READ_DWORD(p) ((((unsigned char*)(p))[0]) | ((((unsigned char*)(p))[1]) << 8) | \
    ((((unsigned char*)(p))[2]) << 16) | ((((unsigned char*)(p))[3]) << 24))

#define PAD(x) (((x) + 3) & 0xFFFFFFFC)

Dann eine Funktion, die die Versionsressource im ausführbaren Image findet (keine Größenprüfung).

const char *FindVersion(const char *buf)
{

Die erste Struktur in der EXE ist der MZ-Header (aus Gründen der Kompatibilität mit MS-DOS).

    //buf is a IMAGE_DOS_HEADER
    if (READ_WORD(buf) != 0x5A4D) //MZ signature
        return NULL;

Das einzige interessante Feld im MZ-Header ist der Offset des PE-Headers. Der PE-Header ist das Original.

    //pe is a IMAGE_NT_HEADERS32
    const char *pe = buf + READ_DWORD(buf + 0x3C);
    if (READ_WORD(pe) != 0x4550) //PE signature
        return NULL;

Eigentlich ist der PE-Header ziemlich langweilig, wir wollen den COFF-Header, der alle symbolischen Daten enthält.

    //coff is a IMAGE_FILE_HEADER
    const char *coff = pe + 4;

Wir brauchen nur die folgenden Felder von diesem.

    WORD numSections = READ_WORD(coff + 2);
    WORD optHeaderSize = READ_WORD(coff + 16);
    if (numSections == 0 || optHeaderSize == 0)
        return NULL;

Der optionale Header ist in einer EXE-Datei eigentlich obligatorisch und befindet sich direkt nach dem COFF. Die Magie ist für 32- und 64-Bit-Windows unterschiedlich. Ich gehe ab jetzt von 32 Bit aus.

    //optHeader is a IMAGE_OPTIONAL_HEADER32
    const char *optHeader = coff + 20;
    if (READ_WORD(optHeader) != 0x10b) //Optional header magic (32 bits)
        return NULL;

Hier kommt der interessante Teil:Wir wollen den Abschnitt Ressourcen finden. Es besteht aus zwei Teilen:1. den Abschnittsdaten, 2. den Abschnittsmetadaten.

Die Datenposition befindet sich in einer Tabelle am Ende des optionalen Headers, und jeder Abschnitt hat einen bekannten Index in dieser Tabelle. Der Ressourcenabschnitt befindet sich in Index 2, daher erhalten wir die virtuelle Adresse (VA) des Ressourcenabschnitts mit:

    //dataDir is an array of IMAGE_DATA_DIRECTORY
    const char *dataDir = optHeader + 96;
    DWORD vaRes = READ_DWORD(dataDir + 8*2);

    //secTable is an array of IMAGE_SECTION_HEADER
    const char *secTable = optHeader + optHeaderSize;

Um die Abschnittsmetadaten zu erhalten, müssen wir die Abschnittstabelle durchlaufen und nach einem Abschnitt namens .rsrc suchen .

    int i;
    for (i = 0; i < numSections; ++i)
    {
        //sec is a IMAGE_SECTION_HEADER*
        const char *sec = secTable + 40*i;
        char secName[9];
        memcpy(secName, sec, 8);
        secName[8] = 0;

        if (strcmp(secName, ".rsrc") != 0)
            continue;

Die Abschnittsstruktur hat zwei relevante Mitglieder:die VA des Abschnitts und den Offset des Abschnitts in die Datei (auch die Größe des Abschnitts, aber ich überprüfe es nicht!):

        DWORD vaSec = READ_DWORD(sec + 12);
        const char *raw = buf + READ_DWORD(sec + 20);

Nun der Offset in der Datei, der dem vaRes entspricht VA, die wir vorher bekommen haben, ist einfach.

        const char *resSec = raw + (vaRes - vaSec);

Dies ist ein Zeiger auf die Ressourcendaten. Alle einzelnen Ressourcen sind in Form eines Baums mit 3 Ebenen aufgebaut:1) Art der Ressource, 2) Bezeichner der Ressource, 3) Sprache der Ressource. Für die Version erhalten wir die allererste des richtigen Typs.

Zuerst haben wir ein Ressourcenverzeichnis (für den Ressourcentyp), wir erhalten die Anzahl der Einträge im Verzeichnis, sowohl benannte als auch unbenannte, und iterieren:

        WORD numNamed = READ_WORD(resSec + 12);
        WORD numId = READ_WORD(resSec + 14);

        int j;
        for (j = 0; j < numNamed + numId; ++j)
        {

Für jeden Ressourceneintrag erhalten wir den Ressourcentyp und verwerfen ihn, wenn es nicht die RT_VERSION-Konstante (16) ist.

            //resSec is a IMAGE_RESOURCE_DIRECTORY followed by an array
            // of IMAGE_RESOURCE_DIRECTORY_ENTRY
            const char *res = resSec + 16 + 8 * j;
            DWORD name = READ_DWORD(res);
            if (name != 16) //RT_VERSION
                continue;

Wenn es sich um eine RT_VERSION handelt, gelangen wir zum nächsten Ressourcenverzeichnis im Baum:

            DWORD offs = READ_DWORD(res + 4);
            if ((offs & 0x80000000) == 0) //is a dir resource?
                return NULL;
            //verDir is another IMAGE_RESOURCE_DIRECTORY and 
            // IMAGE_RESOURCE_DIRECTORY_ENTRY array
            const char *verDir = resSec + (offs & 0x7FFFFFFF);

Und gehen Sie weiter zur nächsten Verzeichnisebene, die ID ist uns egal. davon:

            numNamed = READ_WORD(verDir + 12);
            numId = READ_WORD(verDir + 14);
            if (numNamed == 0 && numId == 0)
                return NULL;
            res = verDir + 16;
            offs = READ_DWORD(res + 4);
            if ((offs & 0x80000000) == 0) //is a dir resource?
                return NULL;

Die dritte Ebene hat die Sprache der Ressource. Uns auch egal, also schnapp dir einfach den ersten:

            //and yet another IMAGE_RESOURCE_DIRECTORY, etc.
            verDir = resSec + (offs & 0x7FFFFFFF);                    
            numNamed = READ_WORD(verDir + 12);
            numId = READ_WORD(verDir + 14);
            if (numNamed == 0 && numId == 0)
                return NULL;
            res = verDir + 16;
            offs = READ_DWORD(res + 4);
            if ((offs & 0x80000000) != 0) //is a dir resource?
                return NULL;
            verDir = resSec + offs;

Und wir kommen zur eigentlichen Ressource, na ja, eigentlich eine Struktur, die den Ort und die Größe der wirklichen Ressource enthält, aber die Größe interessiert uns nicht.

            DWORD verVa = READ_DWORD(verDir);

Das ist die VA der Versionsressource, die einfach in einen Zeiger umgewandelt wird.

            const char *verPtr = raw + (verVa - vaSec);
            return verPtr;

Und fertig! Wenn nicht gefunden, geben Sie NULL zurück .

        }
        return NULL;
    }
    return NULL;
}

Nachdem die Versionsressource gefunden wurde, müssen wir sie analysieren. Es ist eigentlich ein Baum (was sonst) von Paaren "Name" / "Wert". Einige Werte sind bekannt und das ist es, wonach Sie suchen, machen Sie einfach einen Test und Sie werden herausfinden, welche.

HINWEIS :Alle Zeichenfolgen werden in UNICODE (UTF-16) gespeichert, aber mein Beispielcode führt die dumme Konvertierung in ASCII durch. Auch keine Überprüfung auf Überlauf.

Die Funktion nimmt den Zeiger auf die Versionsressource und den Offset in diesen Speicher (0 für den Anfang) und gibt die Anzahl der analysierten Bytes zurück.

int PrintVersion(const char *version, int offs)
{

Zunächst einmal muss der Offset ein Vielfaches von 4 sein.

    offs = PAD(offs);

Dann erhalten wir die Eigenschaften des Versionsbaumknotens.

    WORD len    = READ_WORD(version + offs);
    offs += 2;
    WORD valLen = READ_WORD(version + offs);
    offs += 2;
    WORD type   = READ_WORD(version + offs);
    offs += 2;

Der Name des Knotens ist eine nullterminierte Unicode-Zeichenfolge.

    char info[200];
    int i;
    for (i=0; i < 200; ++i)
    {
        WORD c = READ_WORD(version + offs);
        offs += 2;

        info[i] = c;
        if (!c)
            break;
    }

Mehr Polsterung, falls nötig:

    offs = PAD(offs);

Wenn type nicht 0 ist, dann handelt es sich um eine String-Version data.

    if (type != 0) //TEXT
    {
        char value[200];
        for (i=0; i < valLen; ++i)
        {
            WORD c = READ_WORD(version + offs);
            offs += 2;
            value[i] = c;
        }
        value[i] = 0;
        printf("info <%s>: <%s>\n", info, value);
    }

Sonst, wenn der Name VS_VERSION_INFO ist dann ist es ein VS_FIXEDFILEINFO Struktur. Andernfalls handelt es sich um binäre Daten.

    else
    {
        if (strcmp(info, "VS_VERSION_INFO") == 0)
        {

Ich drucke nur die Version der Datei und des Produkts, aber Sie können die anderen Felder dieser Struktur leicht finden. Hüten Sie sich vor dem mixed endian bestellen.

            //fixed is a VS_FIXEDFILEINFO
            const char *fixed = version + offs;
            WORD fileA = READ_WORD(fixed + 10);
            WORD fileB = READ_WORD(fixed + 8);
            WORD fileC = READ_WORD(fixed + 14);
            WORD fileD = READ_WORD(fixed + 12);
            WORD prodA = READ_WORD(fixed + 18);
            WORD prodB = READ_WORD(fixed + 16);
            WORD prodC = READ_WORD(fixed + 22);
            WORD prodD = READ_WORD(fixed + 20);
            printf("\tFile: %d.%d.%d.%d\n", fileA, fileB, fileC, fileD);
            printf("\tProd: %d.%d.%d.%d\n", prodA, prodB, prodC, prodD);
        }
        offs += valLen;
    }

Führen Sie nun den rekursiven Aufruf durch, um den vollständigen Baum zu drucken.

    while (offs < len)
        offs = PrintVersion(version, offs);

Und etwas mehr Polsterung vor der Rückkehr.

    return PAD(offs);
}

Zum Schluss noch als Bonus ein main Funktion.

int main(int argc, char **argv)
{
    struct stat st;
    if (stat(argv[1], &st) < 0)
    {
        perror(argv[1]);
        return 1;
    }

    char *buf = malloc(st.st_size);

    FILE *f = fopen(argv[1], "r");
    if (!f)
    {
        perror(argv[1]);
        return 2;
    }

    fread(buf, 1, st.st_size, f);
    fclose(f);

    const char *version = FindVersion(buf);
    if (!version)
        printf("No version\n");
    else
        PrintVersion(version, 0);
    return 0;
}

Ich habe es mit ein paar zufälligen EXEs getestet und es scheint gut zu funktionieren.


Ich kenne pev ist ein Tool auf Ubuntu, mit dem Sie diese Informationen zusammen mit vielen anderen PE-Header-Informationen anzeigen können. Ich weiß auch, dass es in C geschrieben ist. Vielleicht möchten Sie es sich ansehen. Ein bisschen aus dem Verlaufsabschnitt in der Dokumentation:

pev wurde 2010 aus einem einfachen Bedürfnis heraus geboren:ein Programm zum Ermitteln der Version (Dateiversion) einer PE32-Datei, das unter Linux ausgeführt werden kann entschieden, einfach ohne Optimierung nach der Zeichenfolge in der gesamten Binärdatei zu suchen.

Später haben wir uns entschieden, die PE32-Datei zu parsen, bis wir den Abschnitt .rsrc erreichen und das Feld File Version erhalten. Um das zu tun, müssen wir die gesamte Datei parsen und dachten uns, ob wir auch alle Felder und Werte ausdrucken könnten...

Bis Version 0.40 war pev ein einzigartiges Programm zum Parsen der PE-Header und -Abschnitte (jetzt ist readpe dafür zuständig). In Version 0.50 haben wir uns auf die Malware-Analyse konzentriert und pev in verschiedene Programme aufgeteilt, die über eine Bibliothek namens libpe hinausgehen. Derzeit verwenden alle pev-Programme libpe.


Linux
  1. Linux – Wie liest man unter Linux aus /proc/$pid/mem?

  2. Nicht standardmäßige Installation (Installieren von Linux von Linux)?

  3. Bestimmen Sie die Betriebssystemversion, Linux und Windows aus Powershell

  4. Konverter für Linux-PDF-Versionen

  5. Gibt es eine Möglichkeit, die BIOS-Version von Linux abzurufen?

Welche Version von Linux verwende ich?

So überprüfen Sie die Version der libc-Bibliothek unter Debian Linux

So installieren Sie die neueste OpenSSL-Version von Source unter Linux

So finden Sie die Virtualbox-Version über die Befehlszeile in Linux

Wie lese ich unter Linux aus /proc/$pid/mem?

Installieren Sie Linux von Linux