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:
- Búsqueda de la firma version_info en el archivo y lea la
VS_FIXEDFILEINFO
directamente struct.
- 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.
http://stackoverflow.com/questions/1291570/native-linux-app-to-edit-win32-pe-like-reshacker?rq=1 – piokuc
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. –
¿Está metiendo un ejecutable ejecutable en otra máquina (de Linux a Windows), quizás a través de un acceso DMA firewire? – ixe013