GNU/Linux >> Linux Esercitazione >  >> Linux

Libreria C per leggere la versione EXE da Linux?

La versione del file è in VS_FIXEDFILEINFO struct, ma devi trovarlo nei dati eseguibili. Ci sono due modi per fare quello che vuoi:

  1. Cerca la firma VERSION_INFO nel file e leggi il VS_FIXEDFILEINFO struct direttamente.
  2. Trova il .rsrc sezione, analizza l'albero delle risorse, trova il RT_VERSION risorsa, analizzala ed estrai il VS_FIXEDFILEINFO dati.

Il primo è più facile, ma suscettibile di trovare la firma per caso nel posto sbagliato. Inoltre, gli altri dati che chiedi (nome del prodotto, descrizione, ecc.) non sono in questa struttura, quindi cercherò di spiegarti come ottenere i dati nel modo più duro.

Il formato PE è un po' contorto, quindi incollo il codice pezzo per pezzo, con commenti e con il minimo controllo degli errori. Scriverò una semplice funzione che scarica i dati sullo standard output. Scriverlo come funzione propria è lasciato come esercizio al lettore :)

Si noti che utilizzerò gli offset nel buffer invece di mappare direttamente le strutture per evitare problemi di portabilità relativi all'allineamento o al riempimento dei campi della struttura. Ad ogni modo, ho annotato il tipo di struct utilizzato (vedi il file include winnt.h per i dettagli).

Prima alcune dichiarazioni utili, dovrebbero essere autoesplicative:

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)

Quindi una funzione che trova la risorsa Version nell'immagine eseguibile (nessun controllo delle dimensioni).

const char *FindVersion(const char *buf)
{

La prima struttura nell'EXE è l'intestazione MZ (per compatibilità con MS-DOS).

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

L'unico campo interessante nell'intestazione MZ è l'offset dell'intestazione PE. L'intestazione PE è la cosa reale.

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

In realtà, l'intestazione PE è piuttosto noiosa, vogliamo l'intestazione COFF, che contiene tutti i dati simbolici.

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

Abbiamo solo bisogno dei seguenti campi da questo.

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

L'intestazione facoltativa è in realtà obbligatoria in un EXE ed è subito dopo COFF. La magia è diversa per Windows a 32 e 64 bit. Presumo 32 bit da qui in poi.

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

Qui arriva la parte interessante:vogliamo trovare la sezione delle risorse. Ha due parti:1. i dati della sezione, 2. i metadati della sezione.

La posizione dei dati è in una tabella alla fine dell'intestazione facoltativa e ogni sezione ha un indice ben noto in questa tabella. La sezione delle risorse è nell'indice 2, quindi otteniamo l'indirizzo virtuale (VA) della sezione delle risorse con:

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

Per ottenere i metadati della sezione dobbiamo iterare la tabella delle sezioni cercando una sezione denominata .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 sezione struct ha due membri rilevanti:il VA della sezione e l'offset della sezione nel file (anche la dimensione della sezione, ma non la sto controllando!):

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

Ora l'offset nel file che corrisponde al vaRes VA che abbiamo ottenuto prima è facile.

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

Questo è un puntatore ai dati della risorsa. Tutte le singole risorse sono impostate sotto forma di albero, con 3 livelli:1) tipo di risorsa, 2) identificatore di risorsa, 3) lingua di risorsa. Per la versione otterremo la primissima del tipo corretto.

Innanzitutto, abbiamo una directory delle risorse (per il tipo di risorsa), otteniamo il numero di voci nella directory, sia con nome che senza nome e ripetiamo:

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

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

Per ogni voce di risorsa otteniamo il tipo di risorsa e lo scartiamo se non è la costante 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;

Se è una RT_VERSION arriviamo alla successiva directory di risorse nell'albero:

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

E vai al livello di directory successivo, non ci interessa l'id. di questo:

            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;

Il terzo livello ha la lingua della risorsa. Nemmeno a noi interessa, quindi prendi solo il primo:

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

E arriviamo alla risorsa reale, beh, in realtà una struttura che contiene la posizione e la dimensione della risorsa reale, ma non ci interessa la dimensione.

            DWORD verVa = READ_DWORD(verDir);

Questo è il VA della versione resouce, che viene facilmente convertito in un puntatore.

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

E fatto! Se non viene trovato restituire NULL .

        }
        return NULL;
    }
    return NULL;
}

Ora che la risorsa della versione è stata trovata, dobbiamo analizzarla. In realtà è un albero (cos'altro) di coppie "nome" / "valore". Alcuni valori sono ben noti ed è quello che stai cercando, basta fare qualche prova e scoprirai quali.

NOTA :Tutte le stringhe sono memorizzate in UNICODE (UTF-16) ma il mio codice di esempio esegue la conversione stupida in ASCII. Inoltre, nessun controllo per overflow.

La funzione porta il puntatore alla risorsa della versione e l'offset in questa memoria (0 per cominciare) e restituisce il numero di byte analizzati.

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

Prima di tutto l'offset deve essere un multiplo di 4.

    offs = PAD(offs);

Quindi otteniamo le proprietà del nodo dell'albero delle versioni.

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

Il nome del nodo è una stringa Unicode con terminazione zero.

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

Più padding, se necessario:

    offs = PAD(offs);

Se type non è 0, allora è una stringa 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);
    }

Altrimenti, se il nome è VS_VERSION_INFO allora è un VS_FIXEDFILEINFO struct. Altrimenti sono dati binari.

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

Sto solo stampando la versione del file e del prodotto, ma puoi trovare facilmente gli altri campi di questa struttura. Fai attenzione al endian misto ordine.

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

Ora esegui la chiamata ricorsiva per stampare l'intero albero.

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

E ancora un po' di imbottitura prima di tornare.

    return PAD(offs);
}

Infine, come bonus, un main funzione.

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

L'ho testato con alcuni EXE casuali e sembra funzionare bene.


Conosco pev è uno strumento su Ubuntu che ti consente di vedere queste informazioni, insieme a molte altre informazioni sull'intestazione PE. So anche che è scritto in C. Forse vorrai dargli un'occhiata. Un po' dalla sua sezione della storia nei documenti:

pev è nato nel 2010 da una semplice esigenza:un programma per scoprire la versione (File Version) di un file PE32 e che potesse essere eseguito in Linux. Questo numero di versione è memorizzato nella sezione Risorse (.rsrc) ma al momento abbiamo ha deciso di cercare semplicemente la stringa nell'intero binario, senza alcuna ottimizzazione.

Successivamente abbiamo deciso di analizzare il file PE32 fino a raggiungere .rsrcsection e ottenere il campo Versione file. Per fare ciò, wealized dobbiamo analizzare l'intero file e abbiamo pensato di poter stampare anche tutti i campi e i valori...

Fino alla versione 0.40, pev era un programma unico per analizzare le intestazioni e le sezioni PE (ora readpe è responsabile di questo). Nella versione 0.50 ci siamo concentrati sull'analisi del malware e abbiamo suddiviso pev in vari programmi oltre a una libreria, chiamata libpe. Attualmente tutti i programmi pev usano libpe.


Linux
  1. Linux:come leggere da /proc/$pid/mem sotto Linux?

  2. appunti di linux leggi/scrivi in ​​C

  3. Linux:c'è una lettura o una ricezione dal socket con timeout?

  4. Come aggiornare Qt installato in Linux da una versione a una successiva

  5. Usa una libreria C in Swift su Linux

Quale versione di Linux sto utilizzando?

Come controllare la versione della libreria libc su Debian Linux

Come installare l'ultima versione di OpenSSL da Source su Linux

Come trovare la versione di Virtualbox dalla riga di comando in Linux

Come posso leggere da /proc/$pid/mem sotto Linux?

Installa Linux da Linux