2012-09-12 18 views
8

¿Existe alguna biblioteca que pueda usar en Linux que devuelva las propiedades de un archivo EXE de Windows que se enumeran en la pestaña Versión del Explorador? Estos son campos como Nombre del producto, Versión del producto, Descripción, etc.C biblioteca para leer la versión EXE de Linux?

Para mi proyecto, el archivo EXE solo se puede leer desde la memoria, no desde un archivo. Me gustaría evitar escribir el archivo EXE en el disco.

+6

http://stackoverflow.com/questions/1291570/native-linux-app-to-edit-win32-pe-like-reshacker?rq=1 – piokuc

+0

no estoy seguro de que entiendo la restricción de la El archivo EXE solo se puede leer desde la memoria.Tampoco entiendo el comentario de que le gustaría evitar escribir el archivo EXE en el disco. Las diversas propiedades EXE que describe son recursos, cadenas, almacenados en la sección de recursos de un EXE o DLL. Entonces la mecánica básica es leer el archivo EXE o DLL buscando la sección de recursos y luego analizar a través de la sección de recursos buscando la versión específica, etc., los recursos que desee y mostrarlos. –

+0

¿Está metiendo un ejecutable ejecutable en otra máquina (de Linux a Windows), quizás a través de un acceso DMA firewire? – ixe013

Respuesta

21

La versión del archivo está en la estructura VS_FIXEDFILEINFO, pero debe encontrarla en los datos ejecutables. Hay dos formas de hacer lo que quiere:

  1. Búsqueda de la firma version_info en el archivo y lea la VS_FIXEDFILEINFO directamente struct.
  2. Busque la sección .rsrc, analice el árbol de recursos, encuentre el recurso RT_VERSION, analícelo y extraiga los datos VS_FIXEDFILEINFO.

El primero es más fácil, pero es susceptible de encontrar la firma por casualidad en el lugar equivocado. Además, los demás datos que solicite (nombre del producto, descripción, etc.) no se encuentran en esta estructura, por lo que intentaré explicar cómo obtener los datos de la manera más difícil.

El formato PE es un poco intrincado, así que voy a pegar el código pieza por pieza, con comentarios y con una mínima comprobación de errores. Escribiré una función simple que arroja los datos a la salida estándar. Escribirlo como una función adecuada se deja como un ejercicio para el lector :)

Tenga en cuenta que utilizaré desplazamientos en el búfer en lugar de mapear las estructuras directamente para evitar problemas de portabilidad relacionados con la alineación o el relleno de los campos estructurales . De todos modos, he anotado el tipo de las estructuras utilizadas (vea incluir el archivo winnt.h para más detalles).

Primero unas pocas declaraciones útiles, deben explicarse por sí mismo:

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) 

A continuación, una función que se encuentra el recurso de versión en la imagen ejecutable (no cheques de tamaño).

const char *FindVersion(const char *buf) 
{ 

La primera estructura en el EXE es el encabezado MZ (para compatibilidad con MS-DOS).

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

El único campo interesante en el encabezado MZ es el desplazamiento del encabezado PE. El encabezado PE es real.

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

En realidad, la cabecera PE es bastante aburrida, queremos que el encabezado COFF, que tienen todos los datos simbólicos.

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

Solo necesitamos los siguientes campos de este.

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

El encabezado opcional en realidad es obligatoria en un archivo EXE y es justo después de la COFF. La magia es diferente para Windows de 32 y 64 bits. Asumo 32 bits a partir de ahora.

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

Aquí viene la parte interesante: queremos encontrar la sección de recursos. Tiene dos partes: 1. los datos de la sección, 2. los metadatos de la sección.

ubicación los datos están en una tabla al final de la cabecera opcional, y cada sección tiene un índice bien conocido en esta tabla. sección de recursos está en el índice 2, por lo que obtener la dirección virtual (VA) de la sección de recursos 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; 

Para obtener la sección de metadatos que necesitamos para recorrer la tabla sección en busca de una sección denominada .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 sección de estructura tiene dos miembros relevantes: el VA de la sección y el desplazamiento de la sección en el archivo (también el tamaño de la sección, pero no estoy comprobando que!):

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

Ahora el desplazamiento en el archivo que corresponde al VA vaRes que obtuvimos antes es fácil.

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

Este es un puntero a los datos del recurso.Todos los recursos individuales se configuran en forma de árbol, con 3 niveles: 1) tipo de recurso, 2) identificador de recurso, 3) idioma de recurso. Para la versión obtendremos la primera del tipo correcto.

En primer lugar, tenemos un directorio de recursos (para el tipo de recurso), obtenemos el número de entradas en el directorio, ambos con y sin nombre y repetir:

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

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

Para cada entrada de recursos se obtiene la tipo de recurso y descartarlo si no es 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; 

Si se trata de un RT_VERSION llegamos a la siguiente directorio de recursos en el árbol:

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

y pasar al siguiente nivel de directorio, que no se preocupan por el ello. de este:

  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; 

El tercer nivel tiene el idioma del recurso. No nos importa tampoco, así que sólo tienes que tomar la primera:

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

Y llegamos al recurso real, bueno, en realidad una estructura que contiene la ubicación y el tamaño de los recursos reales, pero que no lo hacen importa el tamaño

  DWORD verVa = READ_DWORD(verDir); 

Esa es la VA de la versión de recursos, que se convierte en un puntero fácilmente.

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

Y listo! Si no se encuentra, devuelve NULL.

 } 
     return NULL; 
    } 
    return NULL; 
} 

Ahora que se encuentra el recurso de versión, tenemos que analizarlo. En realidad, es un árbol (qué más) de pares "nombre"/"valor". Algunos valores son bien conocidos y eso es lo que está buscando, simplemente haga una prueba y descubrirá cuáles.

NOTA: Todas las cadenas se almacenan en UNICODE (UTF-16) pero mi código de muestra hace la conversión tonta en ASCII. Además, no hay controles de desbordamiento.

La función toma el puntero al recurso de versión y el desplazamiento en esta memoria (0 para los principiantes) y devuelve el número de bytes analizados.

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

En primer lugar el desplazamiento tiene que ser un múltiplo de 4.

offs = PAD(offs); 

Entonces conseguimos las propiedades del nodo versión árbol.

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

El nombre del nodo es una cadena Unicode terminada en cero.

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

Más relleno, si es NECESARIO:

offs = PAD(offs); 

Si type no es 0, entonces es un conjunto de datos de la versión de cadena.

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

lo demás, si el nombre es VS_VERSION_INFO entonces es una estructura VS_FIXEDFILEINFO. De lo contrario, son datos binarios.

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

Solo estoy imprimiendo la versión del archivo y el producto, pero puede encontrar fácilmente los otros campos de esta estructura. Tenga cuidado con el orden endian mixto.

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

Ahora haga la llamada recursiva para imprimir el árbol completo.

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

Y algo más de relleno antes de volver.

return PAD(offs); 
} 

Por último, como un bono, una función main.

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

Lo he probado con algunos EXEs aleatorios y parece funcionar bien.

+0

¡Respuesta impresionante! Este enlace va a ser mi respuesta cuando alguien pregunte "¿Qué tan bueno es stackoverflow?" – TheCodeArtist

+0

¡Fantástico! Gracias. – craig65535

+0

Creé una esencia que combina todos los listados C en un único archivo de código fuente en C. He verificado que compila y funciona. Puede encontrarlo aquí: https://gist.github.com/djhaskin987/d1860a7d98193913bcfa – djhaskin987

1

Instalar winelib http://www.winehq.org/docs/winelib-guide/index Este es el puerto de MS Windows API para otros sistemas, incluido linux.

A continuación, utilice MS Windows API. Como GetFileVersionInfo http://msdn.microsoft.com/en-us/library/windows/desktop/ms647003(v=vs.85).aspx
O cualquier otra función.

Nunca lo hice, pero comenzaría con estos hallazgos.

En cuanto al archivo exe en la restricción de memoria, ¿puede copiarlo en el disco RAM?

+0

¿Requiere esta solución que WINE esté instalado en la máquina en la que se está utilizando la aplicación? ¿Winelib es una biblioteca estática o tiene una versión de biblioteca estática para que un ejecutable pueda ser portado a otras máquinas Linux sin instalar WINE en esa máquina? ¿Es esto independiente de la distribución de Linux? –

+0

@RichardChambers La aplicación AFAIK compilada con winelib requiere que Wine se ejecute. – PiotrNycz

0

Si tiene que leerlo de memoria, creo que tiene que implementar algo usted mismo. Un buen comienzo es la wrestool:

wrestool --type=16 -x --raw Paint.NET.3.5.10.Install.exe 

Está disponible en Ubuntu en icoutils. La información de la versión es solo una serie de cadenas en un archivo de recursos incrustado en el ejecutable. Creo que puedes echarle un vistazo al código fuente de wrestool y comprobar cómo encuentran el inicio del bloque de recursos. Una vez que encuentre los recursos de VERSION_INFO, podrá averiguar cómo traducirlos a información imprimible (utilizando un editor hexadecimal, es legible).

icoutils es GPL ... por lo que no puede simplemente obtener parte de él, no sin contaminar su programa. Pero el código fuente es gratuito, por lo que puede verificar lo que hicieron y escribir una solución personalizada.

El formato de archivo PE también está disponible en Internet. Puede comenzar aquí: http://msdn.microsoft.com/library/windows/hardware/gg463125

+0

El encabezado PE y el recurso son diferentes y casi sin relacionar, me temo ... – ixe013

+1

Sí, son dos cosas diferentes. El encabezado PE del ejecutable contiene muchas secciones. Una de estas secciones es de recursos. Si encuentra el inicio de la sección de recursos, el formato del archivo de recursos incrustado es importante. Ya lo hacen en wrestool para extraer iconos y cadenas de los ejecutables de Windows. La información de versión que desea también está allí. – nmenezes

1

Aquí hay un parche para el código que admite PE32 +. Probado en algunos archivos y parece funcionar.

//optHeader is a IMAGE_OPTIONAL_HEADER32 
const char *optHeader = coff + 20; 
WORD magic = READ_WORD(optHeader); 
if (magic != 0x10b && magic != 0x20b) 
    return NULL; 

//dataDir is an array of IMAGE_DATA_DIRECTORY 
const char *dataDir = optHeader + (magic==0x10b ? 96: 112); 
DWORD vaRes = READ_DWORD(dataDir + 8*2); 
2

pev es una herramienta en Ubuntu que le permite ver esta información, junto con una gran cantidad de otra información de la cabecera PE. También sé que está escrito en C. Maybe you'll want to have a look at it. Un poco de su history section en la documentación:

pev ha nacido en 2010 a partir de una simple necesidad: un programa para averiguar la versión (Versión del archivo) de un archivo PE32 y que se podría ejecutar en Linux. Este número de versión se almacena en la sección Recursos (.rsrc), pero en el momento hemos decidido simplemente buscar la cadena en el binario completo , sin ninguna optimización.

Más tarde hemos decidido analizar el archivo PE32 hasta llegar a la sección .rsrc y obtener el campo Versión del archivo. Con el fin de hacer eso, nos dimos cuenta de debemos analizar el archivo entero y pensamos que si pudiéramos imprimir todos los campos y valores, así ...

Hasta la versión 0.40, pev era un programa único para analizar la Cabeceras PE y secciones (ahora readpe es responsable de esto). En la versión 0.50, se centró en el análisis de malware y pev dividido en varios programas más allá de una biblioteca, llamada libpe. Actualmente, todos los programas pev usan libpe.

Cuestiones relacionadas