GNU/Linux >> Tutoriels Linux >  >> Linux

Bibliothèque C pour lire la version EXE de Linux?

La version du fichier est dans le VS_FIXEDFILEINFO struct, mais vous devez le trouver dans les données exécutables. Il y a deux façons de faire ce que vous voulez :

  1. Recherchez la signature VERSION_INFO dans le fichier et lisez le VS_FIXEDFILEINFO structure directement.
  2. Trouvez le .rsrc section, analysez l'arborescence des ressources, trouvez le RT_VERSION ressource, parsez-la et extrayez le VS_FIXEDFILEINFO données.

La première est plus facile, mais susceptible de retrouver la signature par hasard au mauvais endroit. De plus, les autres données que vous demandez (nom du produit, description, etc.) ne sont pas dans cette structure, je vais donc essayer d'expliquer comment obtenir les données à la dure.

Le format PE est un peu alambiqué, donc je colle le code morceau par morceau, avec des commentaires et avec un minimum de vérification des erreurs. Je vais écrire une fonction simple qui envoie les données sur la sortie standard. L'écrire comme une fonction propre est laissé comme exercice au lecteur :)

Notez que j'utiliserai des décalages dans le tampon au lieu de mapper directement les structures pour éviter les problèmes de portabilité liés à l'alignement ou au remplissage des champs de structure. Quoi qu'il en soit, j'ai annoté le type des structures utilisées (voir le fichier d'inclusion winnt.h pour plus de détails).

D'abord quelques déclarations utiles, elles doivent être explicites :

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)

Puis une fonction qui trouve la ressource Version dans l'image exécutable (pas de contrôle de taille).

const char *FindVersion(const char *buf)
{

La première structure de l'EXE est l'en-tête MZ (pour la compatibilité avec MS-DOS).

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

Le seul champ intéressant dans l'en-tête MZ est le décalage de l'en-tête PE. L'en-tête PE est la vraie chose.

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

En fait, l'en-tête PE est assez ennuyeux, nous voulons l'en-tête COFF, qui contient toutes les données symboliques.

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

Nous avons juste besoin des champs suivants de celui-ci.

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

L'en-tête facultatif est en fait obligatoire dans un EXE et se trouve juste après le COFF. La magie est différente pour Windows 32 et 64 bits. Je suppose 32 bits à partir de maintenant.

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

Voici la partie intéressante :nous voulons trouver la section des ressources. Il comporte deux parties :1. les données de la section, 2. les métadonnées de la section.

L'emplacement des données se trouve dans un tableau à la fin de l'en-tête facultatif, et chaque section a un index bien connu dans ce tableau. La section ressource est en index 2, on obtient donc l'adresse virtuelle (VA) de la section ressource avec :

    //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;

Pour obtenir les métadonnées de la section, nous devons parcourir la table des sections à la recherche d'une section nommée .rsrc .

    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;

La structure de la section a deux membres pertinents :la VA de la section et le décalage de la section dans le fichier (également la taille de la section, mais je ne la vérifie pas !) :

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

Maintenant le décalage dans le fichier qui correspond au vaRes VA que nous avons avant est facile.

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

Il s'agit d'un pointeur vers les données de ressource. Toutes les ressources individuelles sont mises en place sous forme d'arborescence, à 3 niveaux :1) type de ressource, 2) identifiant de ressource, 3) langue de ressource. Pour la version, nous obtiendrons le tout premier du bon type.

Tout d'abord, nous avons un répertoire de ressources (pour le type de ressource), nous obtenons le nombre d'entrées dans le répertoire, à la fois nommées et non nommées et itérons :

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

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

Pour chaque entrée de ressource, nous obtenons le type de ressource et le supprimons s'il ne s'agit pas de la constante RT_VERSION (16).

            //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;

S'il s'agit d'une RT_VERSION, nous passons au répertoire de ressources suivant dans l'arborescence :

            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);

Et passez au niveau de répertoire suivant, nous ne nous soucions pas de l'identifiant. de celui-ci :

            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;

Le troisième niveau a la langue de la ressource. Nous nous en fichons non plus, alors prenez simplement le premier :

            //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;

Et nous arrivons à la vraie ressource, eh bien, en fait une structure qui contient l'emplacement et la taille de la vraie ressource, mais nous ne nous soucions pas de la taille.

            DWORD verVa = READ_DWORD(verDir);

C'est la VA de la ressource de version, qui est facilement convertie en pointeur.

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

Et.. Voila! Si non trouvé, retournez NULL .

        }
        return NULL;
    }
    return NULL;
}

Maintenant que la ressource de version est trouvée, nous devons l'analyser. Il s'agit en fait d'un arbre (what else) de paires "nom" / "valeur". Certaines valeurs sont bien connues et c'est ce que vous recherchez, faites juste un test et vous saurez lesquelles.

REMARQUE :Toutes les chaînes sont stockées en UNICODE (UTF-16) mais mon exemple de code effectue la conversion stupide en ASCII. De plus, aucune vérification de débordement.

La fonction prend le pointeur vers la ressource de version et le décalage dans cette mémoire (0 pour commencer) et renvoie le nombre d'octets analysés.

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

Tout d'abord, le décalage doit être un multiple de 4.

    offs = PAD(offs);

Ensuite, nous obtenons les propriétés du nœud de l'arborescence des versions.

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

Le nom du nœud est une chaîne Unicode terminée par zéro.

    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;
    }

Plus de rembourrage, si nécessaire :

    offs = PAD(offs);

Si type n'est pas 0, alors il s'agit d'une donnée de version de chaîne.

    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);
    }

Sinon, si le nom est VS_VERSION_INFO alors c'est un VS_FIXEDFILEINFO structure. Sinon, ce sont des données binaires.

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

J'imprime simplement la version du fichier et du produit, mais vous pouvez facilement trouver les autres champs de cette structure. Méfiez-vous du boutien mixte commande.

            //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;
    }

Effectuez maintenant l'appel récursif pour imprimer l'arbre complet.

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

Et un peu plus de rembourrage avant de revenir.

    return PAD(offs);
}

Enfin, en prime, un main fonction.

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;
}

Je l'ai testé avec quelques EXE aléatoires et cela semble fonctionner très bien.


Je connais pev est un outil sur Ubuntu qui vous permet de voir ces informations, ainsi que de nombreuses autres informations d'en-tête PE. Je sais aussi qu'il est écrit en C. Peut-être voudrez-vous y jeter un œil. Un peu de sa section historique dans la documentation :

pev est né en 2010 d'un simple besoin :un programme pour connaître la version (File Version) d'un fichier PE32 et qui pourrait être exécuté sous Linux. Ce numéro de version est stocké dans la section Resources (.rsrc) mais à l'époque nous avons a décidé de rechercher simplement la chaîne dans le binaire entier, sans aucune optimisation.

Plus tard, nous avons décidé d'analyser le fichier PE32 jusqu'à atteindre .rsrcsection et d'obtenir le champ Version du fichier. Pour ce faire, nous avons réalisé que nous devions analyser l'intégralité du fichier et nous avons pensé que si nous pouvions également imprimer tous les champs et valeurs...

Jusqu'à la version 0.40, pev était un programme unique pour analyser les en-têtes et les sections PE (c'est désormais readpe qui en est responsable). Dans la version 0.50, nous nous sommes concentrés sur l'analyse des logiciels malveillants et avons divisé pev en divers programmes au-delà d'une bibliothèque, appelée libpe. Actuellement, tous les programmes pev utilisent libpe.


Linux
  1. Linux – Comment lire depuis /proc/$pid/mem sous Linux ?

  2. Installation non standard (installation de Linux à partir de Linux) ?

  3. Comment calculer l'utilisation CPU d'un processus par PID sous Linux à partir de C ?

  4. presse-papiers linux lire/écrire en C

  5. Linux :y a-t-il une lecture ou une réception à partir du socket avec un délai d'attente ?

Quelle version de Linux est-ce que j'utilise ?

Comment vérifier la version de la bibliothèque libc sur Debian Linux

Comment installer la dernière version d'OpenSSL à partir de la source sous Linux

Comment trouver la version de Virtualbox à partir de la ligne de commande sous Linux

Convertisseur de version PDF Linux

Comment lire/écrire tous les paramètres du BIOS à partir de la CLI Linux ?