PE/VS Multiboot Header Repairer (C++)

This forums is for OS project announcements including project openings, new releases, update notices, test requests, and job openings (both paying and volunteer).
Post Reply
User avatar
Ameise
Member
Member
Posts: 61
Joined: Fri Jul 16, 2010 7:46 am
Location: Chicago

PE/VS Multiboot Header Repairer (C++)

Post by Ameise »

I wrote this tool because my multiboot header in my PE kernel was becoming corrupt - after a certain point of linking OBJs into the binary, VS stops ignoring that your entry point should be at the top of .text, and it seems to become random. This corrects that. If it's already correct, it should not do anything. It will be expanded in the future.

Code: Select all

/* PE Executable Multiboot Header Repair Tool */
/* This tool is release as-is to be free of use for anyone. Myself, the maker,
*  am not liable for any damage or harm that may result from its use.
*  This library includes a component from the multiboot header, part-verbatim.
*  The license to the multiboot header file is included below.
*/

/* multiboot.h - Multiboot header file. */
/* Copyright (C) 1999,2003,2007,2008,2009  Free Software Foundation, Inc.
*
*  Permission is hereby granted, free of charge, to any person obtaining a copy
*  of this software and associated documentation files (the "Software"), to
*  deal in the Software without restriction, including without limitation the
*  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
*  sell copies of the Software, and to permit persons to whom the Software is
*  furnished to do so, subject to the following conditions:
*
*  The above copyright notice and this permission notice shall be included in
*  all copies or substantial portions of the Software.
*
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL ANY
*  DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
*  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
*  IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

struct multiboot_header
{
	/* Must be MULTIBOOT_MAGIC - see above. */
	unsigned int magic;

	/* Feature flags. */
	unsigned int flags;

	/* The above fields plus this one must equal 0 mod 2^32. */
	unsigned int checksum;

	/* These are only valid if MULTIBOOT_AOUT_KLUDGE is set. */
	unsigned int header_addr;
	unsigned int load_addr;
	unsigned int load_end_addr;
	unsigned int bss_end_addr;
	unsigned int entry_addr;

	/* These are only valid if MULTIBOOT_VIDEO_MODE is set. */
	unsigned int mode_type;
	unsigned int width;
	unsigned int height;
	unsigned int depth;
};

#define NDEBUG

#include <stdio.h>

/* This library attempts to repair a multiboot header in a PE file
*  that has become corrupted, for instance by linking too many OBJ
*  files into your kernel. This causes your header to shift (even
*  though it should not) and becomes unreadable. This scans for
*  your header, repairs its header_addr and entry_addr values,
*  and rewrites it. This application is set up as such so that it
*  is easier to integrate as a post-build step in Visual C++.
*
*  USAGE:  PE_REWRITER file
*
*  ERROR CODES:
*     1 - Could not open given file.
*     2 - No multiboot header was found.
*     3 - Invalid number of arguments
*/

const bool REWRITE_LOAD_END_ADDR	= true;
const bool REWRITE_LOAD_ADDR		= true;
const bool REWRITE_ENTRY_ADDR		= true;
const bool REWRITE_HEADER_ADDR		= true;

const unsigned int IMAGE_OFFSET	= 0x00100000;
const unsigned int MB_HEADER	= 0x1BADB002;
const unsigned int TEXT_OFFSET  = 0x00001000;

int __cdecl main (int argc, char *argv[])
{
	/* Test arguments for validity */
	if (argc < 2)
	{
		printf("Usage: rewriter file.\n");
		return 3;
	}

	if ((REWRITE_LOAD_END_ADDR || REWRITE_LOAD_ADDR || REWRITE_ENTRY_ADDR || REWRITE_HEADER_ADDR) == false)
	{
		printf("Nothing to do!\n");
		return 0;
	}

	/* Load the file */
	FILE *kernel = fopen(argv[1], "r+b");
	if (kernel == NULL)
	{
		printf("Couldn't open Kernel File.\n");
		return 1;
	}
	fseek(kernel, 0, SEEK_SET);

	/* Stupid way of figuring out where the code actually ends.
	   I'm using 64-bit integers to scan in-case someone has a
	   64-bit variable with null values near the end of the code.
	*/
	unsigned int kernel_length;
	if (REWRITE_LOAD_END_ADDR)
	{
		unsigned long long null_test;
		fseek(kernel, 0, SEEK_END);
		do {
			if (fseek(kernel, -8, SEEK_CUR) != 0 || fread(&null_test, 8, 1, kernel) != 1 || fseek(kernel, -8, SEEK_CUR) != 0)
			{
				printf("This file has no multiboot header. Aborting.\n");
				return 2;
			}
		} while (null_test == 0);
		kernel_length = ftell(kernel) + 8;
		fseek(kernel, 0, SEEK_SET);
	}

	/* Scan through the file to find the multiboot magic number.
	*  This is not the fastest way, but it works.
	*/
	unsigned int test_against;
	do {
		if (fread(&test_against, 4, 1, kernel) != 1 || fseek(kernel, -3, SEEK_CUR) != 0)
		{
			printf("This file has no multiboot header. Aborting.\n");
			return 2;
		}
	} while (test_against != MB_HEADER);
	fseek(kernel, -1, SEEK_CUR); 
	unsigned int header_location = ftell(kernel);

	/* Now we load the header into memory */

	multiboot_header header;
	fread(&header, sizeof(multiboot_header), 1, kernel);

	/* This is where we edit the header */

	if (REWRITE_HEADER_ADDR)
		header.header_addr		= IMAGE_OFFSET + header_location;
	if (REWRITE_ENTRY_ADDR)
		header.entry_addr		= IMAGE_OFFSET + header_location + sizeof(multiboot_header);
	if (REWRITE_LOAD_ADDR)
		header.load_addr		= IMAGE_OFFSET + TEXT_OFFSET;
	if (REWRITE_LOAD_END_ADDR)
		header.load_end_addr	= IMAGE_OFFSET + kernel_length;

	/* Now we write it back where it was. */

	fseek(kernel, header_location, SEEK_SET);
	fwrite(&header, sizeof(multiboot_header), 1, kernel);

	/* Clean up */

	fclose(kernel);
	return 0;
}
User avatar
neon
Member
Member
Posts: 1567
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

Re: PE/VS Multiboot Header Repairer (C++)

Post by neon »

Hello,

There is nothing in the PE format that says the entry point must be at a specific location. By default, its at 0x400 bytes in the image do to that being the offset to the beginning of .text. Its not random; the entry point can just get moved and functions placed before it in the binary.

You can set up the multiboot header so its always properly aligned and set up to always point to your kernel entry point.

To note on your program, I like the idea of a program repairing the multiboot header. On the other hand im not sure if itll help users. If the multiboot header has gotten corrupt, how does the user know other parts of the kernel image is not corrupt?

Just my 2 cents, thats all :)
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
User avatar
Ameise
Member
Member
Posts: 61
Joined: Fri Jul 16, 2010 7:46 am
Location: Chicago

Re: PE/VS Multiboot Header Repairer (C++)

Post by Ameise »

It's not quite that it became corrupt, it's that the Linker may have moved it somewhere it shouldn't be (as was my case once it started adding object files) and hence the addresses in the multiboot header became invalidated. This just corrects those addresses. I'm also working on the PE header so it will automatically set the size of the image it needs to load.
User avatar
Firestryke31
Member
Member
Posts: 550
Joined: Sat Nov 29, 2008 1:07 pm
Location: Throw a dart at central Texas
Contact:

Re: PE/VS Multiboot Header Repairer (C++)

Post by Firestryke31 »

Have you considered sticking the multiboot header in the PE's DOS stub (which is always at the beginning of the file)?
Owner of Fawkes Software.
Wierd Al wrote: You think your Commodore 64 is really neato,
What kind of chip you got in there, a Dorito?
User avatar
Ameise
Member
Member
Posts: 61
Joined: Fri Jul 16, 2010 7:46 am
Location: Chicago

Re: PE/VS Multiboot Header Repairer (C++)

Post by Ameise »

I tried that; however then mbchk rejected it on account of the header_addr being before load_addr. It doesn't make a huge difference one way or the other, unless I fully rebuild the binary, which at some point I will do.
Post Reply