
A quick experiment or two with the real Windows version suggests that the hash function in question is just MD5, repackaged as an array of four little-endian 32-bit words instead of 16 bytes.
431 lines
14 KiB
C
431 lines
14 KiB
C
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <uchar.h>
|
|
#include <err.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "fake-lib.h"
|
|
|
|
uint32_t MsiGetFileVersionW(const char16_t *filename,
|
|
char16_t *version, uint32_t *version_size,
|
|
char16_t *language, uint32_t *language_size)
|
|
{
|
|
char *fname = ascii(filename, true);
|
|
uint32_t toret = 1006; /* ERROR_FILE_INVALID == 'no version info found' */
|
|
int fd = -1;
|
|
void *mapv = MAP_FAILED;
|
|
|
|
fd = open(fname, O_RDONLY);
|
|
if (fd < 0)
|
|
err(1, "%s: open", fname);
|
|
struct stat st;
|
|
if (fstat(fd, &st) < 0)
|
|
err(1, "%s: fstat", fname);
|
|
size_t fsize = st.st_size;
|
|
mapv = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
if (mapv == MAP_FAILED)
|
|
err(1, "%s: mmap", fname);
|
|
unsigned char *map = (unsigned char *)mapv;
|
|
|
|
if (le(map, fsize, 0, 2) != (('Z'<<8) | 'M')) {
|
|
warnx("MsiGetFileInfo(%s) -> no MZ", fname);
|
|
goto cleanup;
|
|
}
|
|
unsigned pe_pos = le(map, fsize, 0x3c, 4);
|
|
if (le(map, fsize, pe_pos, 4) != (('E'<<8) | 'P')) {
|
|
warnx("MsiGetFileInfo(%s) -> no PE", fname);
|
|
goto cleanup;
|
|
}
|
|
pe_pos += 4; /* skip to the main header */
|
|
unsigned nsections = le(map, fsize, pe_pos + 2, 2);
|
|
unsigned opthdr_size = le(map, fsize, pe_pos + 16, 2);
|
|
unsigned opthdr_pos = pe_pos + 20;
|
|
/* bool sixtyfourbit = le(map, fsize, opthdr_pos, 2) == 0x020B; */
|
|
unsigned secthdr_pos = opthdr_pos + opthdr_size;
|
|
while (nsections > 0) {
|
|
if (le(map, fsize, secthdr_pos+0, 1) == '.' &&
|
|
le(map, fsize, secthdr_pos+1, 1) == 'r' &&
|
|
le(map, fsize, secthdr_pos+2, 1) == 's' &&
|
|
le(map, fsize, secthdr_pos+3, 1) == 'r' &&
|
|
le(map, fsize, secthdr_pos+4, 1) == 'c' &&
|
|
le(map, fsize, secthdr_pos+5, 1) == 0)
|
|
goto found_resource_section;
|
|
secthdr_pos += 0x28;
|
|
nsections--;
|
|
}
|
|
warnx("MsiGetFileInfo(%s) -> no .rsrc", fname);
|
|
goto cleanup;
|
|
|
|
found_resource_section:;
|
|
unsigned rsrc_size = le(map, fsize, secthdr_pos+8, 4);
|
|
unsigned rsrc_offset = le(map, fsize, secthdr_pos+20, 4);
|
|
unsigned rsrc_vaddr = le(map, fsize, secthdr_pos+12, 4);
|
|
|
|
unsigned res_dir_offset = rsrc_offset;
|
|
unsigned nnamed, nid;
|
|
nnamed = le(map, fsize, res_dir_offset+12, 2);
|
|
nid = le(map, fsize, res_dir_offset+14, 2);
|
|
for (unsigned i = nnamed; i < nnamed+nid; i++) {
|
|
unsigned id = le(map, fsize, res_dir_offset + 16 + 8*i, 4);
|
|
unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4);
|
|
if (id == 16 && (entry & 0x80000000)) {
|
|
res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF);
|
|
goto found_versioninfo_toplevel;
|
|
}
|
|
}
|
|
warnx("MsiGetFileInfo(%s) -> no top-level numeric key 16 for versioninfo", fname);
|
|
goto cleanup;
|
|
|
|
found_versioninfo_toplevel:
|
|
nnamed = le(map, fsize, res_dir_offset+12, 2);
|
|
nid = le(map, fsize, res_dir_offset+14, 2);
|
|
for (unsigned i = 0; i < nnamed+nid; i++) {
|
|
unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4);
|
|
if (entry & 0x80000000) {
|
|
res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF);
|
|
goto found_versioninfo_2ndlevel;
|
|
}
|
|
}
|
|
warnx("MsiGetFileInfo(%s) -> no 2nd-level subdir", fname);
|
|
goto cleanup;
|
|
|
|
found_versioninfo_2ndlevel:
|
|
nnamed = le(map, fsize, res_dir_offset+12, 2);
|
|
nid = le(map, fsize, res_dir_offset+14, 2);
|
|
for (unsigned i = 0; i < nnamed+nid; i++) {
|
|
unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4);
|
|
if (!(entry & 0x80000000)) {
|
|
res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF);
|
|
goto found_versioninfo_3rdlevel;
|
|
}
|
|
}
|
|
warnx("MsiGetFileInfo(%s) -> no 3rd-level resource data", fname);
|
|
goto cleanup;
|
|
|
|
found_versioninfo_3rdlevel:;
|
|
unsigned versioninfo_offset = le(map, fsize, res_dir_offset, 4);
|
|
unsigned versioninfo_size = le(map, fsize, res_dir_offset+4, 4);
|
|
versioninfo_offset = rsrc_offset + versioninfo_offset - rsrc_vaddr;
|
|
|
|
unsigned name_offset = versioninfo_offset + 6;
|
|
const char *next_name_chr = "VS_VERSION_INFO";
|
|
do {
|
|
if (le(map, fsize, name_offset, 2) != *next_name_chr)
|
|
goto cleanup; /* identifying string didn't match */
|
|
name_offset += 2;
|
|
} while (*next_name_chr++);
|
|
unsigned fixed_offset = (name_offset + 3) & ~3;
|
|
if (le(map, fsize, fixed_offset, 4) != 0xFEEF04BDU) {
|
|
warnx("MsiGetFileInfo(%s) -> no VS_FIXEDFILEINFO magic number", fname);
|
|
goto cleanup;
|
|
}
|
|
int four_part_version[4];
|
|
four_part_version[0] = le(map, fsize, fixed_offset + 10, 2);
|
|
four_part_version[1] = le(map, fsize, fixed_offset + 8, 2);
|
|
four_part_version[2] = le(map, fsize, fixed_offset + 14, 2);
|
|
four_part_version[3] = le(map, fsize, fixed_offset + 12, 2);
|
|
unsigned child_offset = fixed_offset +
|
|
le(map, fsize, versioninfo_offset+2, 2);
|
|
unsigned lcid;
|
|
while (child_offset < versioninfo_offset + versioninfo_size) {
|
|
unsigned this_child_offset = child_offset;
|
|
child_offset += le(map, fsize, child_offset, 2);
|
|
child_offset = (child_offset + 3) &~ 3;
|
|
if (child_offset <= this_child_offset) {
|
|
warnx("MsiGetFileInfo(%s) -> bad length field", fname);
|
|
goto cleanup;
|
|
}
|
|
const char *next_name_chr = "VarFileInfo";
|
|
name_offset = this_child_offset + 6;
|
|
do {
|
|
if (le(map, fsize, name_offset, 2) != *next_name_chr)
|
|
goto this_is_not_a_varfileinfo;
|
|
name_offset += 2;
|
|
} while (*next_name_chr++);
|
|
unsigned subchild_offset = (name_offset + 3) & ~3;
|
|
|
|
next_name_chr = "Translation";
|
|
name_offset = subchild_offset + 6;
|
|
do {
|
|
if (le(map, fsize, name_offset, 2) != *next_name_chr) {
|
|
warnx("MsiGetFileInfo(%s) -> child not called 'Translation'", fname);
|
|
goto cleanup;
|
|
}
|
|
name_offset += 2;
|
|
} while (*next_name_chr++);
|
|
subchild_offset = (name_offset + 3) & ~3;
|
|
lcid = le(map, fsize, subchild_offset, 2);
|
|
goto success;
|
|
this_is_not_a_varfileinfo:
|
|
continue;
|
|
}
|
|
warnx("MsiGetFileInfo(%s) -> no VarFileInfo found", fname);
|
|
goto cleanup;
|
|
|
|
success:;
|
|
char verbuf[256], langbuf[256];
|
|
snprintf(verbuf, sizeof(verbuf), "%d.%d.%d.%d",
|
|
four_part_version[0], four_part_version[1],
|
|
four_part_version[2], four_part_version[3]);
|
|
snprintf(langbuf, sizeof(langbuf), "%u", lcid);
|
|
warnx("MsiGetFileInfo(%s) -> version %s lang %s", fname, verbuf, langbuf);
|
|
if (version)
|
|
c16cpy(version, version_size, verbuf);
|
|
if (language)
|
|
c16cpy(language, language_size, langbuf);
|
|
toret = 0;
|
|
|
|
cleanup:
|
|
if (mapv != MAP_FAILED)
|
|
munmap(mapv, fsize);
|
|
if (fd != -1)
|
|
close(fd);
|
|
sfree(fname);
|
|
return toret;
|
|
}
|
|
|
|
struct MsiHash {
|
|
uint32_t structure_size;
|
|
uint32_t hash_words[4];
|
|
};
|
|
|
|
uint32_t MsiGetFileHashW(const char16_t *filename, uint32_t options,
|
|
struct MsiHash *hash)
|
|
{
|
|
char *fname = ascii(filename, true);
|
|
int fd = -1;
|
|
void *mapv = MAP_FAILED;
|
|
uint32_t toret;
|
|
|
|
fd = open(fname, O_RDONLY);
|
|
if (fd < 0)
|
|
err(1, "%s: open", fname);
|
|
struct stat st;
|
|
if (fstat(fd, &st) < 0)
|
|
err(1, "%s: fstat", fname);
|
|
size_t fsize = st.st_size;
|
|
mapv = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
if (mapv == MAP_FAILED)
|
|
err(1, "%s: mmap", fname);
|
|
|
|
MD5Simple(mapv, fsize, hash->hash_words);
|
|
warnx("MsiGetFileHash(%s) -> %08x:%08x:%08x:%08x", fname,
|
|
(unsigned)hash->hash_words[0], (unsigned)hash->hash_words[1],
|
|
(unsigned)hash->hash_words[2], (unsigned)hash->hash_words[3]);
|
|
toret = 0;
|
|
|
|
cleanup:
|
|
if (mapv != MAP_FAILED)
|
|
munmap(mapv, fsize);
|
|
if (fd != -1)
|
|
close(fd);
|
|
sfree(fname);
|
|
return toret;
|
|
}
|
|
|
|
typedef struct MsiTypePrefix {
|
|
enum { MAIN, VIEW, RECORD } type;
|
|
} MsiTypePrefix;
|
|
|
|
typedef struct MsiMainCtx {
|
|
MsiTypePrefix t;
|
|
|
|
char *tempdir;
|
|
char *outfile;
|
|
|
|
char **args;
|
|
int nargs, argsize;
|
|
} MsiMainCtx;
|
|
|
|
|
|
uint32_t MsiOpenDatabaseW(const char16_t *filename,
|
|
const char16_t *persist,
|
|
MsiMainCtx **out_ctx)
|
|
{
|
|
MsiMainCtx *ctx = snew(MsiMainCtx);
|
|
ctx->t.type = MAIN;
|
|
ctx->outfile = ascii(filename, true);
|
|
close(open(ctx->outfile, O_CREAT | O_WRONLY, 0666));
|
|
ctx->outfile = realpath(ctx->outfile, NULL);
|
|
unlink(ctx->outfile);
|
|
ctx->tempdir = dupcat(ctx->outfile, "-msiXXXXXX", (const char *)NULL);
|
|
if (!mkdtemp(ctx->tempdir))
|
|
err(1, "%s: mkdtemp", ctx->tempdir);
|
|
ctx->nargs = 0;
|
|
ctx->argsize = 16;
|
|
ctx->args = snewn(ctx->argsize, char *);
|
|
ctx->args[ctx->nargs++] = dupcat("sh", (const char *)NULL);
|
|
ctx->args[ctx->nargs++] = dupcat("-c", (const char *)NULL);
|
|
ctx->args[ctx->nargs++] = dupcat("cd \"$0\" && \"$@\"",
|
|
(const char *)NULL);
|
|
ctx->args[ctx->nargs++] = dupcat(ctx->tempdir, (const char *)NULL);
|
|
ctx->args[ctx->nargs++] = dupcat("msibuild", (const char *)NULL);
|
|
ctx->args[ctx->nargs++] = dupcat(ctx->outfile, (const char *)NULL);
|
|
*out_ctx = ctx;
|
|
return 0;
|
|
}
|
|
|
|
uint32_t MsiDatabaseImportW(MsiMainCtx *ctx, const char16_t *folder,
|
|
const char16_t *file)
|
|
{
|
|
assert(ctx->t.type == MAIN);
|
|
system_argv("sh", "-c", "cd \"$0\" && cp \"$1\" \"$2\"",
|
|
ascii(folder, true), ascii(file, true), ctx->tempdir,
|
|
(const char *)NULL);
|
|
if (ctx->nargs + 2 >= ctx->argsize) {
|
|
ctx->argsize = ctx->nargs * 5 / 4 + 16;
|
|
ctx->args = sresize(ctx->args, ctx->argsize, char *);
|
|
}
|
|
ctx->args[ctx->nargs++] = dupcat("-i", (const char *)NULL);
|
|
ctx->args[ctx->nargs++] = dupcat(ctx->tempdir, "/", ascii(file, true),
|
|
(const char *)NULL);
|
|
return 0;
|
|
}
|
|
|
|
typedef struct MsiView {
|
|
MsiTypePrefix t;
|
|
|
|
FILE *fp;
|
|
char *targetdir;
|
|
MsiMainCtx *ctx;
|
|
} MsiView;
|
|
|
|
uint32_t MsiDatabaseOpenViewW(MsiMainCtx *ctx, const char16_t *query,
|
|
MsiView **outview)
|
|
{
|
|
assert(ctx->t.type == MAIN);
|
|
MsiView *view = snew(MsiView);
|
|
view->t.type = VIEW;
|
|
view->ctx = ctx;
|
|
char *cquery = ascii(query, false);
|
|
if (!strcmp(cquery, "SELECT `Name`, `Data` FROM `_Streams`"))
|
|
view->fp = NULL; /* special case */
|
|
else {
|
|
if (!strcmp(cquery, "SELECT `Name`, `Data` FROM `Binary`")) {
|
|
view->fp = fopen(dupcat(ctx->tempdir, "/", "Binary.idt",
|
|
(const char *)NULL), "a");
|
|
view->targetdir = dupcat(ctx->tempdir, "/", "Binary",
|
|
(const char *)NULL);
|
|
} else if (!strcmp(cquery, "SELECT `Name`, `Data` FROM `Icon`")) {
|
|
view->fp = fopen(dupcat(ctx->tempdir, "/", "Icon.idt",
|
|
(const char *)NULL), "a");
|
|
view->targetdir = dupcat(ctx->tempdir, "/", "Icon",
|
|
(const char *)NULL);
|
|
} else
|
|
errx(1, "unrecognised query: %s", cquery);
|
|
if (!view->fp)
|
|
err(1, "open");
|
|
if (mkdir(view->targetdir, 0777) < 0)
|
|
err(1, "%s: mkdir", view->targetdir);
|
|
}
|
|
*outview = view;
|
|
return 0;
|
|
}
|
|
|
|
uint32_t MsiViewExecute(MsiView *view, void *params)
|
|
{
|
|
assert(view->t.type == VIEW);
|
|
return 0;
|
|
}
|
|
|
|
typedef struct MsiRecord {
|
|
MsiTypePrefix t;
|
|
|
|
char *name, *data;
|
|
} MsiRecord;
|
|
|
|
MsiRecord *MsiCreateRecord(uint32_t nparams)
|
|
{
|
|
MsiRecord *rec = snew(MsiRecord);
|
|
rec->t.type = RECORD;
|
|
|
|
if (nparams != 2)
|
|
errx(1, "bad MsiCreateRecord param count %u", (unsigned)nparams);
|
|
rec->name = rec->data = NULL;
|
|
return rec;
|
|
}
|
|
|
|
uint32_t MsiRecordSetStringW(MsiRecord *rec, uint32_t field, char16_t *value)
|
|
{
|
|
assert(rec->t.type == RECORD);
|
|
if (field != 1)
|
|
errx(1, "bad MsiRecordSetString param index %u", (unsigned)field);
|
|
rec->name = ascii(value, false);
|
|
return 0;
|
|
}
|
|
|
|
uint32_t MsiRecordSetStreamW(MsiRecord *rec, uint32_t field, char16_t *path)
|
|
{
|
|
assert(rec->t.type == RECORD);
|
|
if (field != 2)
|
|
errx(1, "bad MsiRecordSetStream param index %u", (unsigned)field);
|
|
rec->data = ascii(path, true);
|
|
return 0;
|
|
}
|
|
|
|
uint32_t MsiViewModify(MsiView *view, uint32_t mode, MsiRecord *rec)
|
|
{
|
|
assert(view->t.type == VIEW);
|
|
assert(rec->t.type == RECORD);
|
|
if (view->fp) {
|
|
system_argv("sh", "-c", "cp \"$0\" \"$1\"/\"$2\"",
|
|
rec->data, view->targetdir, rec->name,
|
|
(const char *)NULL);
|
|
fprintf(view->fp, "%s\t%s\r\n", rec->name, rec->name);
|
|
} else {
|
|
MsiMainCtx *ctx = view->ctx;
|
|
if (ctx->nargs + 3 >= ctx->argsize) {
|
|
ctx->argsize = ctx->nargs * 5 / 4 + 16;
|
|
ctx->args = sresize(ctx->args, ctx->argsize, char *);
|
|
}
|
|
ctx->args[ctx->nargs++] = dupcat("-a", (const char *)NULL);
|
|
ctx->args[ctx->nargs++] = dupcat(rec->name, (const char *)NULL);
|
|
ctx->args[ctx->nargs++] = dupcat(rec->data, (const char *)NULL);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t MsiCloseHandle(MsiTypePrefix *t)
|
|
{
|
|
if (t->type == VIEW) {
|
|
MsiView *view = (MsiView *)t;
|
|
if (view->fp)
|
|
fclose(view->fp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t MsiDatabaseCommit(MsiMainCtx *ctx)
|
|
{
|
|
assert(ctx->t.type == MAIN);
|
|
printf("commit:");
|
|
for (int i = 0; i < ctx->nargs; i++) {
|
|
printf(" '");
|
|
for (const char *p = ctx->args[i]; *p; p++) {
|
|
if (*p == '\'')
|
|
printf("'\\''");
|
|
else
|
|
putchar(*p);
|
|
}
|
|
printf("'");
|
|
}
|
|
printf("\n");
|
|
|
|
if (ctx->nargs + 1 >= ctx->argsize) {
|
|
ctx->argsize = ctx->nargs * 5 / 4 + 16;
|
|
ctx->args = sresize(ctx->args, ctx->argsize, char *);
|
|
}
|
|
ctx->args[ctx->nargs++] = NULL;
|
|
system_argv_array(ctx->args);
|
|
return 0;
|
|
}
|