[BACK]Return to subr_pe.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / sys / compat / ndis

File: [cvs.NetBSD.org] / src / sys / compat / ndis / subr_pe.c (download)

Revision 1.7, Fri Feb 3 23:39:59 2012 UTC (2 years, 7 months ago) by christos
Branch: MAIN
CVS Tags: yamt-pagecache-base9, yamt-pagecache-base8, yamt-pagecache-base7, yamt-pagecache-base6, yamt-pagecache-base5, yamt-pagecache-base4, tls-maxphys-base, tls-maxphys, tls-earlyentropy-base, tls-earlyentropy, rmind-smpnet-nbase, rmind-smpnet-base, rmind-smpnet, riastradh-xf86-video-intel-2-7-1-pre-2-21-15, riastradh-drm2-base3, riastradh-drm2-base2, riastradh-drm2-base1, riastradh-drm2-base, riastradh-drm2, netbsd-7-base, netbsd-7, netbsd-6-base, netbsd-6-1-RELEASE, netbsd-6-1-RC4, netbsd-6-1-RC3, netbsd-6-1-RC2, netbsd-6-1-RC1, netbsd-6-1-4-RELEASE, netbsd-6-1-3-RELEASE, netbsd-6-1-2-RELEASE, netbsd-6-1-1-RELEASE, netbsd-6-1, netbsd-6-0-RELEASE, netbsd-6-0-RC2, netbsd-6-0-RC1, netbsd-6-0-5-RELEASE, netbsd-6-0-4-RELEASE, netbsd-6-0-3-RELEASE, netbsd-6-0-2-RELEASE, netbsd-6-0-1-RELEASE, netbsd-6-0, netbsd-6, matt-nb6-plus-nbase, matt-nb6-plus-base, matt-nb6-plus, khorben-n900, jmcneill-usbmp-base9, jmcneill-usbmp-base8, jmcneill-usbmp-base7, jmcneill-usbmp-base6, jmcneill-usbmp-base5, jmcneill-usbmp-base4, jmcneill-usbmp-base3, jmcneill-usbmp-base2, jmcneill-usbmp-base10, agc-symver-base, agc-symver, HEAD
Changes since 1.6: +6 -5 lines

ansi prototypes

/*-
 * 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>
#ifdef __FreeBSD__
__FBSDID("$FreeBSD: src/sys/compat/ndis/subr_pe.c,v 1.7.2.3 2005/03/31 04:24:36 wpaul Exp $");
#endif
#ifdef __NetBSD__
__KERNEL_RCSID(0, "$NetBSD: subr_pe.c,v 1.7 2012/02/03 23:39:59 christos Exp $");
#endif


/*
 * 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>
#include <sys/lock.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 <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(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(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(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,
	    sizeof(image_optional_header));

	return(0);
}

/*
 * Return a copy of the file header. Contains the number of
 * sections in this image.
 */

int
pe_get_file_header(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);

	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(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_section_header *)((vm_offset_t)nt_hdr +
	    sizeof(image_nt_header));

	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(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(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(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(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_section_header *)((vm_offset_t)nt_hdr +
	    sizeof(image_nt_header));

	/*
	 * 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(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_section_header *)((vm_offset_t)nt_hdr +
	    sizeof(image_nt_header));

	for (i = 0; i < sections; i++) {
		if (!strcmp ((char *)&sect_hdr->ish_name, name)) {
			memcpy( (char *)hdr, (char *)sect_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(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(
	vm_offset_t		imgbase,
	image_import_descriptor	*desc,
	const 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))) {
			memcpy( (char *)desc, (char *)imp_desc,
			    sizeof(image_import_descriptor));
			return(0);
		}
		imp_desc++;
	}

	return (ENOENT);
}

int
pe_get_messagetable(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(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(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(vm_offset_t imgbase, const 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);
}