GNU/Linux >> Znalost Linux >  >> Linux

Knihovna C pro čtení verze EXE z Linuxu?

Verze souboru je v VS_FIXEDFILEINFO struct, ale musíte jej najít ve spustitelných datech. Existují dva způsoby, jak dělat, co chcete:

  1. Vyhledejte v souboru podpis VERSION_INFO a přečtěte si VS_FIXEDFILEINFO přímo strukturovat.
  2. Najděte .rsrc analyzujte strom zdrojů a najděte RT_VERSION zdroj, analyzujte jej a extrahujte VS_FIXEDFILEINFO údaje.

První z nich je jednodušší, ale náchylnější k nalezení podpisu náhodou na nesprávném místě. Kromě toho ostatní údaje, které požadujete (název produktu, popis atd.), v této struktuře nejsou, takže se pokusím vysvětlit, jak získat data složitějším způsobem.

Formát PE je trochu spletitý, takže kód vkládám kousek po kousku, s komentáři a s minimální kontrolou chyb. Napíšu jednoduchou funkci, která vypíše data na standardní výstup. Napsat to jako správnou funkci je ponecháno jako cvičení na čtenáři :)

Všimněte si, že budu používat offsety ve vyrovnávací paměti místo přímého mapování struktur, abych se vyhnul problémům s přenositelností souvisejícími se zarovnáním nebo vyplněním polí struct. Každopádně jsem poznamenal typ použitých struktur (podrobnosti najdete v souboru winnt.h).

Nejprve několik užitečných deklarací, které by měly být samozřejmé:

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)

Poté funkce, která najde prostředek verze ve spustitelném obrazu (bez kontroly velikosti).

const char *FindVersion(const char *buf)
{

První strukturou v EXE je hlavička MZ (kvůli kompatibilitě s MS-DOS).

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

Jediné zajímavé pole v hlavičce MZ je offset hlavičky PE. Záhlaví PE je to pravé.

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

Ve skutečnosti je PE hlavička docela nudná, chceme hlavičku COFF, která má všechna symbolická data.

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

Z tohoto potřebujeme pouze následující pole.

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

Volitelná hlavička je ve skutečnosti v EXE povinná a je hned za COFF. Kouzlo je jiné pro 32 a 64 bitové Windows. Předpokládám 32 bitů odtud.

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

Zde přichází ta zajímavá část:chceme najít sekci zdrojů. Má dvě části:1. data sekce, 2. metadata sekce.

Umístění dat je v tabulce na konci volitelného záhlaví a každá sekce má v této tabulce dobře známý index. Sekce prostředků je v indexu 2, takže virtuální adresu (VA) sekce prostředků získáváme pomocí:

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

Abychom získali metadata sekce, musíme iterovat tabulku sekce a hledat sekci s názvem .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;

Struktura sekce má dva relevantní členy:VA sekce a offset sekce do souboru (také velikost sekce, ale nekontroluji to!):

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

Nyní offset v souboru, který odpovídá vaRes VA, kterou jsme dostali dříve, je snadné.

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

Toto je ukazatel na zdrojová data. Všechny jednotlivé zdroje jsou nastaveny ve formě stromu se 3 úrovněmi:1) typ zdroje, 2) identifikátor zdroje, 3) jazyk zdroje. Pro verzi získáme úplně první správného typu.

Nejprve máme adresář zdrojů (pro typ zdroje), získáme počet položek v adresáři, pojmenovaných i nepojmenovaných a iterujeme:

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

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

Pro každý záznam zdroje získáme typ zdroje a zahodíme jej, pokud to není konstanta 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;

Pokud se jedná o RT_VERSION, dostaneme se do dalšího adresáře prostředků ve stromu:

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

A přejděte na další úroveň adresáře, id nás nezajímá. tohoto:

            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;

Třetí úroveň má jazyk zdroje. Nám je to taky jedno, takže si vezměte první:

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

A dostáváme se ke skutečnému zdroji, no, vlastně struktuře, která obsahuje umístění a velikost skutečného zdroje, ale na velikosti nám nezáleží.

            DWORD verVa = READ_DWORD(verDir);

To je VA zdroje verze, která se snadno převede na ukazatel.

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

A hotovo! Pokud není nalezen, vraťte NULL .

        }
        return NULL;
    }
    return NULL;
}

Nyní, když je zdroj verze nalezen, musíme jej analyzovat. Je to vlastně strom (co jiného) párů „jméno“ / „hodnota“. Některé hodnoty jsou dobře známé a to je to, co hledáte, stačí udělat test a zjistíte jaké.

POZNÁMKA :Všechny řetězce jsou uloženy v UNICODE (UTF-16), ale můj ukázkový kód provádí hloupou konverzi do ASCII. Také žádné kontroly přetečení.

Funkce vezme ukazatel na zdroj verze a offset do této paměti (0 pro začátečníky) a vrátí počet analyzovaných bajtů.

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

Nejprve musí být offset násobkem 4.

    offs = PAD(offs);

Poté získáme vlastnosti uzlu stromu verzí.

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

Název uzlu je řetězec Unicode zakončený nulou.

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

Další odsazení, je-li to nutné:

    offs = PAD(offs);

Pokud type není 0, pak se jedná o údaje o verzi řetězce.

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

Jinak, pokud je název VS_VERSION_INFO pak je to VS_FIXEDFILEINFO strukturovat. Jinak jde o binární data.

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

Právě tisknu verzi souboru a produktu, ale ostatní pole této struktury najdete snadno. Dejte si pozor na smíšený endian objednávka.

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

Nyní proveďte rekurzivní volání pro tisk celého stromu.

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

A ještě nějaké vycpávky před návratem.

    return PAD(offs);
}

Nakonec jako bonus main funkce.

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

Testoval jsem to s několika náhodnými EXE a zdá se, že to funguje dobře.


Znám pev je nástroj na Ubuntu, který vám umožňuje zobrazit tyto informace spolu se spoustou dalších informací z hlavičky PE. Také vím, že je to napsané v C. Možná se na to budete chtít podívat. Něco ze sekce historie v dokumentech:

pev se zrodil v roce 2010 z jednoduché potřeby:programu pro zjištění verze (verze souboru) souboru PE32, který lze spustit v Linuxu. Toto číslo verze je uloženo v sekci Zdroje (.rsrc), ale v současné době rozhodl se jednoduše vyhledat řetězec v celém binárním souboru, bez jakékoli optimalizace.

Později jsme se rozhodli analyzovat soubor PE32, dokud nedosáhnete .rsrcsection a získáte pole Verze souboru. Abychom to udělali, musíme provést analýzu celého souboru a napadlo nás, že bychom mohli vytisknout také všechna pole a hodnoty...

Do verze 0.40 byl pev jedinečným programem pro analýzu PE headersand sekcí (nyní je za to zodpovědný readpe). Ve verzi 0.50 jsme se zaměřili na analýzu malwaru a rozdělili pev do různých programů mimo knihovnu, nazvanou libpe. V současné době všechny programy pev používají libpe.


Linux
  1. Linux – Jak číst z /proc/$pid/mem pod Linuxem?

  2. Nestandardní instalace (instalace Linuxu z Linuxu)?

  3. Jak upgradovat Qt nainstalované v linuxu z jedné verze na vyšší

  1. Zjistěte verzi OS, Linux a Windows z Powershell

  2. Převaděč verzí PDF pro Linux

  3. Jak číst/zapisovat všechna nastavení BIOSu z Linux CLI?

  1. Jak mohu číst z /proc/$pid/mem pod Linuxem?

  2. Nainstalujte Linux z Linuxu

  3. Existuje způsob, jak získat verzi systému BIOS z Linuxu?