freebsd-dev/sys/compat/ndis/subr_pe.c
Bill Paul 51d6d0952b Clean up and apply the fix for PR 83477. The calculation for locating
the start of the section headers has to take into account the fact
that the image_nt_header is really variable sized. It happens that
the existing calculation is correct for _most_ production binaries
produced by the Windows DDK, but if we get a binary with oddball
offsets, the PE loader could crash.

Changes from the supplied patch are:

- We don't really need to use the IMAGE_SIZEOF_NT_HEADER() macro when
  computing how much of the header to return to callers of
  pe_get_optional_header(). While it's important to take the variable
  size of the header into account in other calculations, we never
  actually look at anything outside the non-variable portion of the
  header. This saves callers from having to allocate a variable sized
  buffer off the heap (I purposely tried to avoid using malloc()
  in subr_pe.c to make it easier to compile in both the -D_KERNEL and
  !-D_KERNEL case), and since we're copying into a buffer on the
  stack, we always have to copy the same amount of data or else
  we'll trash the stack something fierce.

- We need <stddef.h> to get offsetof() in the !-D_KERNEL case.

- ndiscvt.c needs the IMAGE_FIRST_SECTION() macro too, since it does
  a little bit of section pre-processing.

PR: kern/83477
2005-10-26 18:46:27 +00:00

645 lines
16 KiB
C

/*-
* Copyright (c) 2003
* Bill Paul <wpaul@windriver.com>. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Bill Paul.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* This file contains routines for relocating and dynamically linking
* executable object code files in the Windows(r) PE (Portable Executable)
* format. In Windows, anything with a .EXE, .DLL or .SYS extention is
* considered an executable, and all such files have some structures in
* common. The PE format was apparently based largely on COFF but has
* mutated significantly over time. We are mainly concerned with .SYS files,
* so this module implements only enough routines to be able to parse the
* headers and sections of a .SYS object file and perform the necessary
* relocations and jump table patching to allow us to call into it
* (and to have it call back to us). Note that while this module
* can handle fixups for imported symbols, it knows nothing about
* exporting them.
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/errno.h>
#ifdef _KERNEL
#include <sys/systm.h>
extern int ndis_strncasecmp(const char *, const char *, size_t);
#define strncasecmp(a, b, c) ndis_strncasecmp(a, b, c)
#else
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#endif
#include <compat/ndis/pe_var.h>
static vm_offset_t pe_functbl_match(image_patch_table *, char *);
/*
* Check for an MS-DOS executable header. All Windows binaries
* have a small MS-DOS executable prepended to them to print out
* the "This program requires Windows" message. Even .SYS files
* have this header, in spite of the fact that you're can't actually
* run them directly.
*/
int
pe_get_dos_header(imgbase, hdr)
vm_offset_t imgbase;
image_dos_header *hdr;
{
uint16_t signature;
if (imgbase == 0 || hdr == NULL)
return (EINVAL);
signature = *(uint16_t *)imgbase;
if (signature != IMAGE_DOS_SIGNATURE)
return (ENOEXEC);
bcopy ((char *)imgbase, (char *)hdr, sizeof(image_dos_header));
return(0);
}
/*
* Verify that this image has a Windows NT PE signature.
*/
int
pe_is_nt_image(imgbase)
vm_offset_t imgbase;
{
uint32_t signature;
image_dos_header *dos_hdr;
if (imgbase == 0)
return (EINVAL);
signature = *(uint16_t *)imgbase;
if (signature == IMAGE_DOS_SIGNATURE) {
dos_hdr = (image_dos_header *)imgbase;
signature = *(uint32_t *)(imgbase + dos_hdr->idh_lfanew);
if (signature == IMAGE_NT_SIGNATURE)
return(0);
}
return(ENOEXEC);
}
/*
* Return a copy of the optional header. This contains the
* executable entry point and the directory listing which we
* need to find the relocations and imports later.
*/
int
pe_get_optional_header(imgbase, hdr)
vm_offset_t imgbase;
image_optional_header *hdr;
{
image_dos_header *dos_hdr;
image_nt_header *nt_hdr;
if (imgbase == 0 || hdr == NULL)
return(EINVAL);
if (pe_is_nt_image(imgbase))
return (EINVAL);
dos_hdr = (image_dos_header *)(imgbase);
nt_hdr = (image_nt_header *)(imgbase + dos_hdr->idh_lfanew);
bcopy ((char *)&nt_hdr->inh_optionalhdr, (char *)hdr,
nt_hdr->inh_filehdr.ifh_optionalhdrlen);
return(0);
}
/*
* Return a copy of the file header. Contains the number of
* sections in this image.
*/
int
pe_get_file_header(imgbase, hdr)
vm_offset_t imgbase;
image_file_header *hdr;
{
image_dos_header *dos_hdr;
image_nt_header *nt_hdr;
if (imgbase == 0 || hdr == NULL)
return(EINVAL);
if (pe_is_nt_image(imgbase))
return (EINVAL);
dos_hdr = (image_dos_header *)imgbase;
nt_hdr = (image_nt_header *)(imgbase + dos_hdr->idh_lfanew);
/*
* Note: the size of the nt_header is variable since it
* can contain optional fields, as indicated by ifh_optionalhdrlen.
* However it happens we're only interested in fields in the
* non-variant portion of the nt_header structure, so we don't
* bother copying the optional parts here.
*/
bcopy ((char *)&nt_hdr->inh_filehdr, (char *)hdr,
sizeof(image_file_header));
return(0);
}
/*
* Return the header of the first section in this image (usually
* .text).
*/
int
pe_get_section_header(imgbase, hdr)
vm_offset_t imgbase;
image_section_header *hdr;
{
image_dos_header *dos_hdr;
image_nt_header *nt_hdr;
image_section_header *sect_hdr;
if (imgbase == 0 || hdr == NULL)
return(EINVAL);
if (pe_is_nt_image(imgbase))
return (EINVAL);
dos_hdr = (image_dos_header *)imgbase;
nt_hdr = (image_nt_header *)(imgbase + dos_hdr->idh_lfanew);
sect_hdr = IMAGE_FIRST_SECTION(nt_hdr);
bcopy ((char *)sect_hdr, (char *)hdr, sizeof(image_section_header));
return(0);
}
/*
* Return the number of sections in this executable, or 0 on error.
*/
int
pe_numsections(imgbase)
vm_offset_t imgbase;
{
image_file_header file_hdr;
if (pe_get_file_header(imgbase, &file_hdr))
return(0);
return (file_hdr.ifh_numsections);
}
/*
* Return the base address that this image was linked for.
* This helps us calculate relocation addresses later.
*/
vm_offset_t
pe_imagebase(imgbase)
vm_offset_t imgbase;
{
image_optional_header optional_hdr;
if (pe_get_optional_header(imgbase, &optional_hdr))
return(0);
return (optional_hdr.ioh_imagebase);
}
/*
* Return the offset of a given directory structure within the
* image. Directories reside within sections.
*/
vm_offset_t
pe_directory_offset(imgbase, diridx)
vm_offset_t imgbase;
uint32_t diridx;
{
image_optional_header opt_hdr;
vm_offset_t dir;
if (pe_get_optional_header(imgbase, &opt_hdr))
return(0);
if (diridx >= opt_hdr.ioh_rva_size_cnt)
return(0);
dir = opt_hdr.ioh_datadir[diridx].idd_vaddr;
return(pe_translate_addr(imgbase, dir));
}
vm_offset_t
pe_translate_addr(imgbase, rva)
vm_offset_t imgbase;
vm_offset_t rva;
{
image_optional_header opt_hdr;
image_section_header *sect_hdr;
image_dos_header *dos_hdr;
image_nt_header *nt_hdr;
int i = 0, sections, fixedlen;
if (pe_get_optional_header(imgbase, &opt_hdr))
return(0);
sections = pe_numsections(imgbase);
dos_hdr = (image_dos_header *)imgbase;
nt_hdr = (image_nt_header *)(imgbase + dos_hdr->idh_lfanew);
sect_hdr = IMAGE_FIRST_SECTION(nt_hdr);
/*
* The test here is to see if the RVA falls somewhere
* inside the section, based on the section's start RVA
* and its length. However it seems sometimes the
* virtual length isn't enough to cover the entire
* area of the section. We fudge by taking into account
* the section alignment and rounding the section length
* up to a page boundary.
*/
while (i++ < sections) {
fixedlen = sect_hdr->ish_misc.ish_vsize;
fixedlen += ((opt_hdr.ioh_sectalign - 1) -
sect_hdr->ish_misc.ish_vsize) &
(opt_hdr.ioh_sectalign - 1);
if (sect_hdr->ish_vaddr <= (uint32_t)rva &&
(sect_hdr->ish_vaddr + fixedlen) >
(uint32_t)rva)
break;
sect_hdr++;
}
if (i > sections)
return(0);
return((vm_offset_t)(imgbase + rva - sect_hdr->ish_vaddr +
sect_hdr->ish_rawdataaddr));
}
/*
* Get the section header for a particular section. Note that
* section names can be anything, but there are some standard
* ones (.text, .data, .rdata, .reloc).
*/
int
pe_get_section(imgbase, hdr, name)
vm_offset_t imgbase;
image_section_header *hdr;
const char *name;
{
image_dos_header *dos_hdr;
image_nt_header *nt_hdr;
image_section_header *sect_hdr;
int i, sections;
if (imgbase == 0 || hdr == NULL)
return(EINVAL);
if (pe_is_nt_image(imgbase))
return (EINVAL);
sections = pe_numsections(imgbase);
dos_hdr = (image_dos_header *)imgbase;
nt_hdr = (image_nt_header *)(imgbase + dos_hdr->idh_lfanew);
sect_hdr = IMAGE_FIRST_SECTION(nt_hdr);
for (i = 0; i < sections; i++) {
if (!strcmp ((char *)&sect_hdr->ish_name, name)) {
bcopy((char *)sect_hdr, (char *)hdr,
sizeof(image_section_header));
return(0);
} else
sect_hdr++;
}
return (ENOEXEC);
}
/*
* Apply the base relocations to this image. The relocation table
* resides within the .reloc section. Relocations are specified in
* blocks which refer to a particular page. We apply the relocations
* one page block at a time.
*/
int
pe_relocate(imgbase)
vm_offset_t imgbase;
{
image_section_header sect;
image_base_reloc *relhdr;
uint16_t rel, *sloc;
vm_offset_t base;
vm_size_t delta;
uint32_t *lloc;
uint64_t *qloc;
int i, count;
vm_offset_t txt;
base = pe_imagebase(imgbase);
pe_get_section(imgbase, &sect, ".text");
txt = pe_translate_addr(imgbase, sect.ish_vaddr);
delta = (uint32_t)(txt) - base - sect.ish_vaddr;
pe_get_section(imgbase, &sect, ".reloc");
relhdr = (image_base_reloc *)(imgbase + sect.ish_rawdataaddr);
do {
count = (relhdr->ibr_blocksize -
(sizeof(uint32_t) * 2)) / sizeof(uint16_t);
for (i = 0; i < count; i++) {
rel = relhdr->ibr_rel[i];
switch (IMR_RELTYPE(rel)) {
case IMAGE_REL_BASED_ABSOLUTE:
break;
case IMAGE_REL_BASED_HIGHLOW:
lloc = (uint32_t *)pe_translate_addr(imgbase,
relhdr->ibr_vaddr + IMR_RELOFFSET(rel));
*lloc = pe_translate_addr(imgbase,
(*lloc - base));
break;
case IMAGE_REL_BASED_HIGH:
sloc = (uint16_t *)pe_translate_addr(imgbase,
relhdr->ibr_vaddr + IMR_RELOFFSET(rel));
*sloc += (delta & 0xFFFF0000) >> 16;
break;
case IMAGE_REL_BASED_LOW:
sloc = (uint16_t *)pe_translate_addr(imgbase,
relhdr->ibr_vaddr + IMR_RELOFFSET(rel));
*sloc += (delta & 0xFFFF);
break;
case IMAGE_REL_BASED_DIR64:
qloc = (uint64_t *)pe_translate_addr(imgbase,
relhdr->ibr_vaddr + IMR_RELOFFSET(rel));
*qloc = pe_translate_addr(imgbase,
(*qloc - base));
break;
default:
printf ("[%d]reloc type: %d\n",i,
IMR_RELTYPE(rel));
break;
}
}
relhdr = (image_base_reloc *)((vm_offset_t)relhdr +
relhdr->ibr_blocksize);
} while (relhdr->ibr_blocksize);
return(0);
}
/*
* Return the import descriptor for a particular module. An image
* may be linked against several modules, typically HAL.dll, ntoskrnl.exe
* and NDIS.SYS. For each module, there is a list of imported function
* names and their addresses.
*
* Note: module names are case insensitive!
*/
int
pe_get_import_descriptor(imgbase, desc, module)
vm_offset_t imgbase;
image_import_descriptor *desc;
char *module;
{
vm_offset_t offset;
image_import_descriptor *imp_desc;
char *modname;
if (imgbase == 0 || module == NULL || desc == NULL)
return(EINVAL);
offset = pe_directory_offset(imgbase, IMAGE_DIRECTORY_ENTRY_IMPORT);
if (offset == 0)
return (ENOENT);
imp_desc = (void *)offset;
while (imp_desc->iid_nameaddr) {
modname = (char *)pe_translate_addr(imgbase,
imp_desc->iid_nameaddr);
if (!strncasecmp(module, modname, strlen(module))) {
bcopy((char *)imp_desc, (char *)desc,
sizeof(image_import_descriptor));
return(0);
}
imp_desc++;
}
return (ENOENT);
}
int
pe_get_messagetable(imgbase, md)
vm_offset_t imgbase;
message_resource_data **md;
{
image_resource_directory *rdir, *rtype;
image_resource_directory_entry *dent, *dent2;
image_resource_data_entry *rent;
vm_offset_t offset;
int i;
if (imgbase == 0)
return(EINVAL);
offset = pe_directory_offset(imgbase, IMAGE_DIRECTORY_ENTRY_RESOURCE);
if (offset == 0)
return (ENOENT);
rdir = (image_resource_directory *)offset;
dent = (image_resource_directory_entry *)(offset +
sizeof(image_resource_directory));
for (i = 0; i < rdir->ird_id_entries; i++){
if (dent->irde_name != RT_MESSAGETABLE) {
dent++;
continue;
}
dent2 = dent;
while (dent2->irde_dataoff & RESOURCE_DIR_FLAG) {
rtype = (image_resource_directory *)(offset +
(dent2->irde_dataoff & ~RESOURCE_DIR_FLAG));
dent2 = (image_resource_directory_entry *)
((uintptr_t)rtype +
sizeof(image_resource_directory));
}
rent = (image_resource_data_entry *)(offset +
dent2->irde_dataoff);
*md = (message_resource_data *)pe_translate_addr(imgbase,
rent->irde_offset);
return(0);
}
return(ENOENT);
}
int
pe_get_message(imgbase, id, str, len, flags)
vm_offset_t imgbase;
uint32_t id;
char **str;
int *len;
uint16_t *flags;
{
message_resource_data *md = NULL;
message_resource_block *mb;
message_resource_entry *me;
uint32_t i;
pe_get_messagetable(imgbase, &md);
if (md == NULL)
return(ENOENT);
mb = (message_resource_block *)((uintptr_t)md +
sizeof(message_resource_data));
for (i = 0; i < md->mrd_numblocks; i++) {
if (id >= mb->mrb_lowid && id <= mb->mrb_highid) {
me = (message_resource_entry *)((uintptr_t)md +
mb->mrb_entryoff);
for (i = id - mb->mrb_lowid; i > 0; i--)
me = (message_resource_entry *)((uintptr_t)me +
me->mre_len);
*str = me->mre_text;
*len = me->mre_len;
*flags = me->mre_flags;
return(0);
}
mb++;
}
return(ENOENT);
}
/*
* Find the function that matches a particular name. This doesn't
* need to be particularly speedy since it's only run when loading
* a module for the first time.
*/
static vm_offset_t
pe_functbl_match(functbl, name)
image_patch_table *functbl;
char *name;
{
image_patch_table *p;
if (functbl == NULL || name == NULL)
return(0);
p = functbl;
while (p->ipt_name != NULL) {
if (!strcmp(p->ipt_name, name))
return((vm_offset_t)p->ipt_wrap);
p++;
}
printf ("no match for %s\n", name);
/*
* Return the wrapper pointer for this routine.
* For x86, this is the same as the funcptr.
* For amd64, this points to a wrapper routine
* that does calling convention translation and
* then invokes the underlying routine.
*/
return((vm_offset_t)p->ipt_wrap);
}
/*
* Patch the imported function addresses for a given module.
* The caller must specify the module name and provide a table
* of function pointers that will be patched into the jump table.
* Note that there are actually two copies of the jump table: one
* copy is left alone. In a .SYS file, the jump tables are usually
* merged into the INIT segment.
*/
int
pe_patch_imports(imgbase, module, functbl)
vm_offset_t imgbase;
char *module;
image_patch_table *functbl;
{
image_import_descriptor imp_desc;
char *fname;
vm_offset_t *nptr, *fptr;
vm_offset_t func;
if (imgbase == 0 || module == NULL || functbl == NULL)
return(EINVAL);
if (pe_get_import_descriptor(imgbase, &imp_desc, module))
return(ENOEXEC);
nptr = (vm_offset_t *)pe_translate_addr(imgbase,
imp_desc.iid_import_name_table_addr);
fptr = (vm_offset_t *)pe_translate_addr(imgbase,
imp_desc.iid_import_address_table_addr);
while (nptr != NULL && pe_translate_addr(imgbase, *nptr)) {
fname = (char *)pe_translate_addr(imgbase, (*nptr) + 2);
func = pe_functbl_match(functbl, fname);
if (func)
*fptr = func;
#ifdef notdef
if (*fptr == 0)
return(ENOENT);
#endif
nptr++;
fptr++;
}
return(0);
}