/* restable.c - Decoding PE and NE resource tables * * Copyright (C) 1998 Oskar Liljeblad * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include /* ? */ #include "gettext.h" /* Gnulib */ #define _(s) gettext(s) #define N_(s) gettext_noop(s) #include "common/intutil.h" #include "xalloc.h" /* Gnulib */ #include "xvasprintf.h" /* Gnulib */ #include "minmax.h" /* Gnulib */ #include "common/error.h" #include "wrestool.h" #include "win32.h" #include "fileread.h" static bool decode_pe_resource_id (WinLibrary *, WinResource *, uint32_t); static bool decode_ne_resource_id (WinLibrary *, WinResource *, uint16_t); static WinResource *list_ne_type_resources (WinLibrary *, int *); static WinResource *list_ne_name_resources (WinLibrary *, WinResource *, int *); static WinResource *list_pe_resources (WinLibrary *, Win32ImageResourceDirectory *, int, int *); static int calc_vma_size (WinLibrary *); static void do_resources_recurs (WinLibrary *, WinResource *, WinResource *, WinResource *, WinResource *, const char *, const char *, const char *, DoResourceCallback); static char *get_resource_id_quoted (WinResource *); static WinResource *find_with_resource_array(WinLibrary *, WinResource *, const char *); static WinResource *list_resources (WinLibrary *fi, WinResource *res, int *count); static bool compare_resource_id (WinResource *wr, const char *id); /* Check whether access to a PE_SECTIONS is allowed */ #define RETURN_IF_BAD_PE_SECTIONS(ret, module) \ do { \ void* pe_sec_; \ RETURN_IF_BAD_POINTER(ret, PE_HEADER(module)->optional_header); \ RETURN_IF_BAD_POINTER(ret, PE_HEADER(module)->file_header.number_of_sections); \ pe_sec_ = PE_SECTIONS(module); \ RETURN_IF_BAD_OFFSET(ret, pe_sec_, sizeof(Win32ImageSectionHeader) \ * PE_HEADER(module)->file_header.number_of_sections); \ } while(0) /* do_resources: * Do something for each resource matching type, name and lang. */ void do_resources (WinLibrary *fi, const char *type, const char *name, const char *lang, DoResourceCallback cb) { WinResource *type_wr; WinResource *name_wr; WinResource *lang_wr; type_wr = malloc(sizeof(WinResource)*3); name_wr = type_wr + 1; lang_wr = type_wr + 2; memset(type_wr, 0, sizeof(WinResource)*3); do_resources_recurs(fi, NULL, type_wr, name_wr, lang_wr, type, name, lang, cb); free(type_wr); } /* what is each entry in this directory level for? type, name or language? */ #define WINRESOURCE_BY_LEVEL(x) ((x)==0 ? type_wr : ((x)==1 ? name_wr : lang_wr)) /* does the id of this entry match the specified id? */ #define LEVEL_MATCHES(x) (x == NULL || x ## _wr->id[0] == '\0' || compare_resource_id(x ## _wr, x)) static void do_resources_recurs (WinLibrary *fi, WinResource *base, WinResource *type_wr, WinResource *name_wr, WinResource *lang_wr, const char *type, const char *name, const char *lang, DoResourceCallback cb) { int c, rescnt; WinResource *wr; /* get a list of all resources at this level */ wr = list_resources (fi, base, &rescnt); if (wr == NULL) return; /* process each resource listed */ for (c = 0 ; c < rescnt ; c++) { /* (over)write the corresponding WinResource holder with the current */ memcpy(WINRESOURCE_BY_LEVEL(wr[c].level), wr+c, sizeof(WinResource)); if ((base && (wr[c].level <= base->level)) || (wr[c].level >= 3)) { warn(_("%s: resource structure malformed"), fi->name); return; } /* go deeper unless there is something that does NOT match */ if (LEVEL_MATCHES(type) && LEVEL_MATCHES(name) && LEVEL_MATCHES(lang)) { if (wr->is_directory) do_resources_recurs (fi, wr+c, type_wr, name_wr, lang_wr, type, name, lang, cb); else cb(fi, wr+c, type_wr, name_wr, lang_wr); } } /* since we're moving back one level after this, unset the * WinResource holder used on this level */ memset(WINRESOURCE_BY_LEVEL(wr[0].level), 0, sizeof(WinResource)); } void print_resources_callback (WinLibrary *fi, WinResource *wr, WinResource *type_wr, WinResource *name_wr, WinResource *lang_wr) { const char *type, *offset; int32_t id; size_t size; char *type_quoted, *name_quoted, *lang_quoted; /* get named resource type if possible */ type = NULL; if (parse_int32(type_wr->id, &id)) type = res_type_id_to_string(id); /* get offset and size info on resource */ offset = get_resource_entry(fi, wr, &size); if (offset == NULL) return; type_quoted = get_resource_id_quoted(type_wr); name_quoted = get_resource_id_quoted(name_wr); lang_quoted = get_resource_id_quoted(lang_wr); printf(_("--type=%s --name=%s%s%s [%s%s%soffset=0x%x size=%zu]\n"), type_quoted, name_quoted, (lang_wr->id[0] != '\0' ? _(" --language=") : ""), lang_quoted, (type != NULL ? "type=" : ""), (type != NULL ? type : ""), (type != NULL ? " " : ""), (uint32_t) (offset - fi->memory), size); free(type_quoted); free(name_quoted); free(lang_quoted); } /* return the resource id quoted if it's a string, otherwise just return it */ static char * get_resource_id_quoted (WinResource *wr) { if (wr->numeric_id || wr->id[0] == '\0') return xstrdup(wr->id); return xasprintf("'%s'", wr->id); } static bool compare_resource_id (WinResource *wr, const char *id) { if (wr->numeric_id) { int32_t cmp1, cmp2; if (id[0] == '+') return false; if (id[0] == '-') id++; if (!parse_int32(wr->id, &cmp1) || !parse_int32(id, &cmp2) || cmp1 != cmp2) return false; } else { if (id[0] == '-') return false; if (id[0] == '+') id++; if (strcmp(wr->id, id)) return false; } return true; } static bool decode_pe_resource_id (WinLibrary *fi, WinResource *wr, uint32_t value) { if (value & IMAGE_RESOURCE_NAME_IS_STRING) { /* numeric id */ int c, len; uint16_t *mem = (uint16_t *) (fi->first_resource + (value & ~IMAGE_RESOURCE_NAME_IS_STRING)); /* copy each char of the string, and terminate it */ RETURN_IF_BAD_POINTER(false, *mem); len = mem[0]; RETURN_IF_BAD_OFFSET(false, &mem[1], sizeof(uint16_t) * len); len = MIN(mem[0], WINRES_ID_MAXLEN); for (c = 0 ; c < len ; c++) wr->id[c] = mem[c+1] & 0x00FF; wr->id[len] = '\0'; } else { /* Unicode string id */ /* translate id into a string */ snprintf(wr->id, WINRES_ID_MAXLEN, "%" PRIu32, value); } wr->numeric_id = (value & IMAGE_RESOURCE_NAME_IS_STRING ? false:true); return true; } void * get_resource_entry (WinLibrary *fi, WinResource *wr, size_t *size) { if (fi->is_PE_binary) { Win32ImageResourceDataEntry *dataent; dataent = (Win32ImageResourceDataEntry *) wr->children; RETURN_IF_BAD_POINTER(NULL, *dataent); *size = dataent->size; RETURN_IF_BAD_OFFSET(NULL, fi->memory + dataent->offset_to_data, *size); return fi->memory + dataent->offset_to_data; } else { Win16NENameInfo *nameinfo; int sizeshift; nameinfo = (Win16NENameInfo *) wr->children; sizeshift = *((uint16_t *) fi->first_resource - 1); *size = nameinfo->length << sizeshift; RETURN_IF_BAD_OFFSET(NULL, fi->memory + (nameinfo->offset << sizeshift), *size); return fi->memory + (nameinfo->offset << sizeshift); } } static bool decode_ne_resource_id (WinLibrary *fi, WinResource *wr, uint16_t value) { if (value & NE_RESOURCE_NAME_IS_NUMERIC) { /* numeric id */ /* translate id into a string */ snprintf(wr->id, WINRES_ID_MAXLEN, "%d", value & ~NE_RESOURCE_NAME_IS_NUMERIC); } else { /* ASCII string id */ unsigned char len; char *mem = (char *) NE_HEADER(fi->memory) + NE_HEADER(fi->memory)->rsrctab + value; /* copy each char of the string, and terminate it */ RETURN_IF_BAD_POINTER(false, *mem); len = mem[0]; RETURN_IF_BAD_OFFSET(false, &mem[1], sizeof(char) * len); memcpy(wr->id, &mem[1], len); wr->id[len] = '\0'; } wr->numeric_id = (value & NE_RESOURCE_NAME_IS_NUMERIC ? true:false); return true; } static WinResource * list_pe_resources (WinLibrary *fi, Win32ImageResourceDirectory *pe_res, int level, int *count) { WinResource *wr; unsigned int out_c; int dirent_c, rescnt; Win32ImageResourceDirectoryEntry *dirent = (Win32ImageResourceDirectoryEntry *) (pe_res + 1); /* count number of `type' resources */ RETURN_IF_BAD_POINTER(NULL, *dirent); rescnt = pe_res->number_of_named_entries + pe_res->number_of_id_entries; *count = 0; if (rescnt == 0) return NULL; /* allocate WinResource's */ wr = xmalloc(sizeof(WinResource) * rescnt); /* fill in the WinResource's */ out_c = 0; for (dirent_c = 0 ; dirent_c < rescnt ; dirent_c++) { RETURN_IF_BAD_POINTER(NULL, dirent[dirent_c]); wr[out_c].this = pe_res; wr[out_c].level = level; wr[out_c].is_directory = (dirent[dirent_c].u2.s.data_is_directory); /* Require data to point somewhere after the directory */ if (dirent[dirent_c].u2.s.offset_to_directory < sizeof(Win32ImageResourceDirectory)) continue; wr[out_c].children = fi->first_resource + dirent[dirent_c].u2.s.offset_to_directory; /* fill in wr->id, wr->numeric_id */ if (!decode_pe_resource_id (fi, wr + out_c, dirent[dirent_c].u1.name)) continue; ++out_c; ++(*count); } if (out_c == 0) { free(wr); return NULL; } return wr; } static WinResource * list_ne_name_resources (WinLibrary *fi, WinResource *typeres, int *count) { int c, rescnt; WinResource *wr; Win16NETypeInfo *typeinfo = (Win16NETypeInfo *) typeres->this; Win16NENameInfo *nameinfo = (Win16NENameInfo *) typeres->children; /* count number of `type' resources */ RETURN_IF_BAD_POINTER(NULL, typeinfo->count); *count = rescnt = typeinfo->count; if (rescnt == 0) return NULL; /* allocate WinResource's */ wr = xmalloc(sizeof(WinResource) * rescnt); /* fill in the WinResource's */ for (c = 0 ; c < rescnt ; c++) { RETURN_IF_BAD_POINTER(NULL, nameinfo[c]); wr[c].this = nameinfo+c; wr[c].is_directory = false; wr[c].children = nameinfo+c; wr[c].level = 1; /* fill in wr->id, wr->numeric_id */ if (!decode_ne_resource_id (fi, wr + c, (nameinfo+c)->id)) { free(wr); return NULL; } } return wr; } static WinResource * list_ne_type_resources (WinLibrary *fi, int *count) { size_t c, rescnt; WinResource *wr; Win16NETypeInfo *typeinfo; /* count number of `type' resources */ typeinfo = (Win16NETypeInfo *) fi->first_resource; RETURN_IF_BAD_POINTER(NULL, *typeinfo); for (rescnt = 0 ; typeinfo->type_id != 0 ; rescnt++) { if (((char *) NE_TYPEINFO_NEXT(typeinfo))+sizeof(uint16_t) > fi->memory + fi->total_size) { warn(_("%s: resource table invalid, ignoring remaining entries"), fi->name); break; } typeinfo = NE_TYPEINFO_NEXT(typeinfo); RETURN_IF_BAD_POINTER(NULL, *typeinfo); } *count = rescnt; if (rescnt == 0) return NULL; /* allocate WinResource's */ wr = xmalloc(sizeof(WinResource) * rescnt); /* fill in the WinResource's */ typeinfo = (Win16NETypeInfo *) fi->first_resource; for (c = 0 ; c < rescnt ; c++) { wr[c].this = typeinfo; wr[c].is_directory = (typeinfo->count != 0); wr[c].children = typeinfo+1; wr[c].level = 0; /* fill in wr->id, wr->numeric_id */ if (!decode_ne_resource_id (fi, wr + c, typeinfo->type_id)) { free(wr); return NULL; } typeinfo = NE_TYPEINFO_NEXT(typeinfo); } return wr; } /* list_resources: * Return an array of WinResource's in the current * resource level specified by res. */ static WinResource * list_resources (WinLibrary *fi, WinResource *res, int *count) { if (res != NULL && !res->is_directory) return NULL; if (fi->is_PE_binary) { return list_pe_resources(fi, (Win32ImageResourceDirectory *) (res == NULL ? fi->first_resource : res->children), (res == NULL ? 0 : res->level+1), count); } else { return (res == NULL ? list_ne_type_resources(fi, count) : list_ne_name_resources(fi, res, count)); } } static Win32ImageDataDirectory * get_data_directory_entry (WinLibrary *fi, unsigned int entry) { Win32ImageNTHeaders *pe_header; pe_header = PE_HEADER(fi->memory); RETURN_IF_BAD_POINTER(NULL, pe_header->optional_header.magic); if (pe_header->optional_header.magic == OPTIONAL_MAGIC_PE32) { Win32ImageOptionalHeader *optional_header = &(pe_header->optional_header); RETURN_IF_BAD_POINTER(false, optional_header->data_directory[entry]); return optional_header->data_directory + entry; } else if (pe_header->optional_header.magic == OPTIONAL_MAGIC_PE32_64) { Win32ImageOptionalHeader64 *optional_header = (Win32ImageOptionalHeader64*)&(pe_header->optional_header); RETURN_IF_BAD_POINTER(false, optional_header->data_directory[entry]); return optional_header->data_directory + entry; } else { return NULL; } } /* read_library: * * Read header and get resource directory offset in a Windows library * (AKA module). */ bool read_library (WinLibrary *fi) { /* check for DOS header signature `MZ' */ RETURN_IF_BAD_POINTER(false, MZ_HEADER(fi->memory)->magic); if (MZ_HEADER(fi->memory)->magic == IMAGE_DOS_SIGNATURE) { DOSImageHeader *mz_header = MZ_HEADER(fi->memory); RETURN_IF_BAD_POINTER(false, mz_header->lfanew); if (mz_header->lfanew < sizeof (DOSImageHeader)) { warn(_("%s: not a PE or NE library"), fi->name); return false; } /* falls through */ } RETURN_IF_BAD_OFFSET(false, MZ_HEADER(fi->memory), sizeof(Win32ImageNTHeaders)); /* check for OS2 (Win16) header signature `NE' */ RETURN_IF_BAD_POINTER(false, NE_HEADER(fi->memory)->magic); if (NE_HEADER(fi->memory)->magic == IMAGE_OS2_SIGNATURE) { OS2ImageHeader *header = NE_HEADER(fi->memory); uint16_t *alignshift; RETURN_IF_BAD_POINTER(false, header->rsrctab); RETURN_IF_BAD_POINTER(false, header->restab); if (header->rsrctab >= header->restab) { warn(_("%s: no resource directory found"), fi->name); return false; } fi->is_PE_binary = false; alignshift = (uint16_t *) ((uint8_t *) NE_HEADER(fi->memory) + header->rsrctab); fi->first_resource = ((uint8_t *) alignshift) + sizeof(uint16_t); RETURN_IF_BAD_POINTER(false, *(Win16NETypeInfo *) fi->first_resource); return true; } /* check for NT header signature `PE' */ RETURN_IF_BAD_POINTER(false, PE_HEADER(fi->memory)->signature); if (PE_HEADER(fi->memory)->signature == IMAGE_NT_SIGNATURE) { Win32ImageSectionHeader *pe_sections; Win32ImageDataDirectory *dir; Win32ImageNTHeaders *pe_header; int d; /* allocate new memory */ fi->total_size = calc_vma_size(fi); if (fi->total_size <= 0) { /* calc_vma_size has reported error */ return false; } fi->memory = xrealloc(fi->memory, fi->total_size); /* relocate memory, start from last section */ pe_header = PE_HEADER(fi->memory); RETURN_IF_BAD_PE_SECTIONS(false, fi->memory); pe_sections = PE_SECTIONS(fi->memory); /* we don't need to do OFFSET checking for the sections. * calc_vma_size has already done that */ for (d = pe_header->file_header.number_of_sections - 1; d >= 0 ; d--) { Win32ImageSectionHeader *pe_sec = pe_sections + d; if (pe_sec->characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) continue; //if (pe_sec->virtual_address + pe_sec->size_of_raw_data > fi->total_size) /* Protect against memory moves overwriting the section table */ if ((uint8_t*)(fi->memory + pe_sec->virtual_address) < (uint8_t*)(pe_sections + pe_header->file_header.number_of_sections)) { warn(_("%s: invalid sections layout"), fi->name); return false; } RETURN_IF_BAD_OFFSET(0, fi->memory + pe_sec->virtual_address, pe_sec->size_of_raw_data); RETURN_IF_BAD_OFFSET(0, fi->memory + pe_sec->pointer_to_raw_data, pe_sec->size_of_raw_data); if (pe_sec->virtual_address != pe_sec->pointer_to_raw_data) { memmove(fi->memory + pe_sec->virtual_address, fi->memory + pe_sec->pointer_to_raw_data, pe_sec->size_of_raw_data); } } /* find resource directory */ dir = get_data_directory_entry (fi, IMAGE_DIRECTORY_ENTRY_RESOURCE); if (dir == NULL) return false; if (dir->size == 0) { warn(_("%s: file contains no resources"), fi->name); return false; } fi->first_resource = ((uint8_t *) fi->memory) + dir->virtual_address; fi->is_PE_binary = true; return true; } /* other (unknown) header signature was found */ warn(_("%s: not a PE or NE library"), fi->name); return false; } /* calc_vma_size: * Calculate the total amount of memory needed for a 32-bit Windows * module. Returns -1 if file was too small. */ static int calc_vma_size (WinLibrary *fi) { Win32ImageSectionHeader *seg; size_t c, segcount, size; size = 0; RETURN_IF_BAD_POINTER(-1, PE_HEADER(fi->memory)->file_header.number_of_sections); segcount = PE_HEADER(fi->memory)->file_header.number_of_sections; /* If there are no segments, just process file like it is. * This is (probably) not the right thing to do, but problems * will be delt with later anyway. */ if (segcount == 0) return fi->total_size; RETURN_IF_BAD_PE_SECTIONS(-1, fi->memory); seg = PE_SECTIONS(fi->memory); RETURN_IF_BAD_POINTER(-1, *seg); for (c = 0 ; c < segcount ; c++) { RETURN_IF_BAD_POINTER(0, *seg); size = MAX(size, seg->virtual_address + seg->size_of_raw_data); /* I have no idea what misc.virtual_size is for... */ size = MAX(size, seg->virtual_address + seg->misc.virtual_size); seg++; } return size; } static WinResource * find_with_resource_array(WinLibrary *fi, WinResource *wr, const char *id) { int c, rescnt; WinResource *return_wr; wr = list_resources(fi, wr, &rescnt); if (wr == NULL) return NULL; for (c = 0 ; c < rescnt ; c++) { if (compare_resource_id (&wr[c], id)) { /* duplicate WinResource and return it */ return_wr = xmalloc(sizeof(WinResource)); memcpy(return_wr, &wr[c], sizeof(WinResource)); /* free old WinResource */ free(wr); return return_wr; } } return NULL; } WinResource * find_resource (WinLibrary *fi, const char *type, const char *name, const char *language, int *level) { WinResource *wr; *level = 0; if (type == NULL) return NULL; wr = find_with_resource_array(fi, NULL, type); if (wr == NULL || !wr->is_directory) return wr; *level = 1; if (name == NULL) return wr; wr = find_with_resource_array(fi, wr, name); if (wr == NULL || !wr->is_directory) return wr; *level = 2; if (language == NULL) return wr; wr = find_with_resource_array(fi, wr, language); return wr; }