Jump to content











Photo
* * * * * 1 votes

Boot into 3rd-party EFI application via BCD?

efi uefi chainloader grub2 bcd bcdedit

  • Please log in to reply
53 replies to this topic

#51 Computer Guru

Computer Guru

    Newbie

  • Members
  • 17 posts
  •  
    United States

Posted 06 January 2017 - 04:50 PM

If you're such a computer guru, as your name implies, then maybe you should answer your own question.

 

Sorry, did I say something to offend you? Anyway, the current question was raised by mihi; if you have nothing to contribute to the topic at hand, perhaps it is best to leave this discussion for those looking to contribute?



#52 Computer Guru

Computer Guru

    Newbie

  • Members
  • 17 posts
  •  
    United States

Posted 06 January 2017 - 05:14 PM



Sorry for hijacking this old thread, but I'd like to shed some light about what works and what does not (and ask if you have any ideas to improve it).

 

....

 

 

In case anyone has any more information, please share :-)

 

mihi

 

Hello @mihi! I had quite forgotten about this thread; it's been many years! I never did go down the route of launching 3rd party EFI applications via BCD, I just supplanted bootmgr entirely.

 

I think your problem stems from the fact that you're running a BOOT_APPLICATION. If you consider for a moment how the boot manager is working, it has exclusive control over the hardware (in many ways, it is the OS at that point). BOOT_APPLICATION is a Microsoft-specific subsystem used to run code at boot-time, but that code doesn't need to re-initialize the hardware, it can use the initialization already carried out by the bootloader. I presume since BOOTMGR didn't initialize/isn't using the serial console, you can use it OK (does it work if you have serial console debugging enabled?) but it has exclusive access to the video output and hasn't handed that off to you.

 

As I mentioned before, winload.efi is of subsystem type 0x10 (for which I cannot find a friendly name at the moment) - whereas BOOT_APPLICATION (0x6) actually predates all the EFI business entirely. The behavior of bootmgr when launching each of these different types differs, and I don't think it's possible to hand off control of the hardware to a BOOT_APPLICATION.

 

Try compiling as /SUBSYSTEM:0x10 or /SUBSYSTEM:16 and see if that is even allowed?

 

The only way I know of to run an EFI somewhat within the framework of the BCD/BOOTMGR is to load your application instead of bootmgr, which bcdedit can do

 

bcdedit /set {bootmgr} path /path/to/your/efi/file

 

But that's not really worth much. You can use that to install GRUB2 as the default system bootloader by specifying the path to grub.efi there, but it's not going to get Windows to give you a nice dual-boot menu between multiple EFI applications.



#53 mihi

mihi

    Newbie

  • Members
  • 22 posts
  •  
    Germany

Posted 06 January 2017 - 06:22 PM

Hello @ComputerGuru,
 
 
Thank you very much for your reply.
 
 

I think your problem stems from the fact that you're running a BOOT_APPLICATION. If you consider for a moment how the boot manager is working, it has exclusive control over the hardware (in many ways, it is the OS at that point). BOOT_APPLICATION is a Microsoft-specific subsystem used to run code at boot-time, but that code doesn't need to re-initialize the hardware, it can use the initialization already carried out by the bootloader. I presume since BOOTMGR didn't initialize/isn't using the serial console, you can use it OK (does it work if you have serial console debugging enabled?) but it has exclusive access to the video output and hasn't handed that off to you.


Yes, I'm aware that what I am doing is unsupported (and subject to break in the next Windows 10 Insider Build even), but I still tried to ask here, since it seems more people here are trying that kind of stuff, and perhaps somebody has reverse engineered already how the graphics initialization is passed down to the boot application (at least in the offsets that are passed in that structure I did not find any framebuffer offset or similar) or how to uninitialize the hardware. (I know it is possible since you can load the bootmanager from e. g. GRUB or EFI shell and then return back to it, and the graphics works again, so the information on how to get back is somewhere, unlike old Linux kernels which overwrote BIOS information in RAM so there was no way to get it back except reboot).
 
Yes, serial console even works if I enabled serial debugging for BCDEDIT (then I can see the serial output in Windbg, similar to what I see when invoking memtest.efi). And ConIn also works and I'm pretty sure that the boot manager also initialized it before, so I assume it does something special with graphics (and not rely on the standard EFI_GOP graphics) while it relies on EFI services for serial console and input.
 

As I mentioned before, winload.efi is of subsystem type 0x10 (for which I cannot find a friendly name at the moment) - whereas BOOT_APPLICATION (0x6) actually predates all the EFI business entirely.


According to https://msdn.microso...339(VS.85).aspx, 16 is IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION, and when I compile 64-bit with /SUBSYSTEM:BOOT_APPLICATION, I get 16 in dependency walker (the same as winload.efi was), so at least the subsystem type was correct.

 

The behavior of bootmgr when launching each of these different types differs, and I don't think it's possible to hand off control of the hardware to a BOOT_APPLICATION.
 
Try compiling as /SUBSYSTEM:0x10 or /SUBSYSTEM:16 and see if that is even allowed?


I think with /SUBSYSTEM:BOOT_APPLICATION (in 64-bit architecture) I'm fine.
 

The only way I know of to run an EFI somewhat within the framework of the BCD/BOOTMGR is to load your application instead of bootmgr, which bcdedit can do
 
bcdedit /set {bootmgr} path /path/to/your/efi/file
 
But that's not really worth much. You can use that to install GRUB2 as the default system bootloader by specifying the path to grub.efi there, but it's not going to get Windows to give you a nice dual-boot menu between multiple EFI applications.


In my scenario, the Windows boot manager resides on a USB key anyway (/efi/boot/bootx64.efi) and not in firmware variables, so I could achieve the same by just overwriting that file. :)

 

Best regards,

 

mihi



#54 imbushuo

imbushuo
  • Members
  • 1 posts
  •  
    United States

Posted 03 February 2018 - 03:18 AM

Sorry for hijacking this old thread, but I'd like to shed some light about what works and what does not (and ask if you have any ideas to improve it).

 

  • Unless you want to use Secure Boot, it does not matter if your BOOT_APPLICATION is signed or not - just /set nointegritychecks Yes (on your boot entry, not on {bootmgr}). For your BOOT_APPLICATION bcd entry you can /copy {memdiag} and change the path.
  • As others already pointed out, your application needs to use /SUBSYSTEM:BOOT_APPLICATION. You can use Visual Studio 2015 (Community) to compile for this subsystem: while the value does not exist in the subsystem list, you can just set the subsystem to unset and add /SUBSYSTEM:BOOT_APPLICATION as custom linker option. If you want a template how to link against GNU EFI llibrary with Visual Studio, you can use uefi-simple as a template (In case you want to run and test it in QEMU from Visual Studio, you will have to tweak debug.vbs so that it copies the Windows bootloader and a pre-built BCD into the boot image and change the destination file name inside the boot image so that bootx64.efi is not overwritten with your binary).
  • The entry point of the BOOT_APPLICATION takes one parameter of type PBOOT_APPLICATION_PARAMETER_BLOCK (The folks of the Reactos project have reverse engineered this struct and many related ones) and its return value is a normal NTSTATUS/EFI_STATUS. Apart from the signature ("BOOT APP") and version (2), the BOOT_APPLICATION_PARAMETER_BLOCK struct mainly contains offsets (in bytes from the start of the struct) to the BL_FIRMWARE_DESCRIPTOR (which has a version number of 2 and include the normal ImageHandle and SystemTable variables every UEFI programmer knows) and to the BL_APPLICATION_ENTRY (which is mainly useful since it is the start of a linked list of BCD parameters that are set for the image - so you can set your own custom:0x... options with bcdedit and read the values from your BOOT_APPLICATION).
  • When calling UEFI IntializeLib, don't pass the ImageHandle on first call, since it will use the ImageHandle to determine the correct memory allocation type - and it does not know what is correct for BOOT_APPLICATION. When you pass NULL, it will use EfiBootServicesData, which works fine.

 

And now this is where my problems start:

 

While input (ST->ConIn) works fine (as well as most other BootServices like serial ports or filesystem access or even shutdown), everything that tries to access the screen (i. e. GRAPHICS_OUTPUT_PROTOCOL or SIMPLE_TEXT_OUTPUT_PROTOCOL) freezes the machine instead. Probably the boot loader initialized the graphics card strangely or did some non-standard memory mapping? (After exiting from the boot application and exiting from the boot manager, it works again, so it is not permanently damaged...). I also tried to debug the boot manager with WinDbg, but that debug support seems to be limited (no way to search for ASCII strings, but searching for DWORDs works fine; breakpoints sometimes work but most of the time don't, etc.) so I gave up on this one, too.

 

So while it is technically possible to LoadImage and StartImage another normal EFI binary, that one will probably also freeze quickly and be of no real use.

 

If you want to experiment with "what works", here is a small example that can shut down the machine or dump EFI variables to the serial console:

#include <efi.h>
#include <efilib.h>

typedef struct {
	CHAR8 Signature[8]; // "BOOT APP"
	UINT32 Version; // 2
	UINT32 Irrelevant[7];
	UINT32 AppEntryOffset;
	UINT32 Irrelevant2;
	UINT32 FirmwareParametersOffset;
} BOOT_APPLICATION_PARAMETER_BLOCK;

typedef struct {
	UINT32 Type;
	UINT32 DataOffset;
	UINT32 DataSize;
	UINT32 ListOffset;
	UINT32 NextEntryOffset;
	UINT32 Empty;
} BL_BCD_OPTION;

typedef struct {
	CHAR8 Signature[8]; // "BTAPENT"
	EFI_GUID Guid;
	UINT32 Unknown[5];
	BL_BCD_OPTION BcdData;
} BL_APPLICATION_ENTRY;

typedef struct {
	UINT32 Version; // 2
	UINT32 Padding;
	EFI_HANDLE ImageHandle;
	EFI_SYSTEM_TABLE *SystemTable;
} BL_FIRMWARE_DESCRIPTOR;


void SerWrite(EFI_SERIAL_IO_PROTOCOL* ser, CHAR16* buffer) {
	CHAR8 buf[100];
	UINTN len = StrLen(buffer), i;
	if (len > 100) len = 100;
	for (i = 0; i < len; i++) {
		buf[i] = (CHAR8)buffer[i];
	}
	ser->Write(ser, &len, buf);
}

void DumpVars(EFI_SERIAL_IO_PROTOCOL* ser, BOOT_APPLICATION_PARAMETER_BLOCK* BootAppParameters) {
	BL_APPLICATION_ENTRY* appentry = (BL_APPLICATION_ENTRY*)(((UINTN)BootAppParameters) + BootAppParameters->AppEntryOffset);
	BL_BCD_OPTION* option = &appentry->BcdData;
	UINTN firstOption = (UINTN)option;
	CHAR16 buffer[40];
	CHAR8* data;
	UINT32 i;

	while (TRUE) {
		SPrint(buffer, 40, L"%08x: ", option->Type);
		SerWrite(ser, buffer);
		data = (CHAR8*)(((UINTN)option) + option->DataOffset);
		if ((option->Type & 0x0F000000) == 0x02000000 && option->DataSize > 0) {
			SerWrite(ser, L"\"");
			SerWrite(ser, (UINT16*)data);
			SerWrite(ser, L"\"");
		}
		else if (((option->Type & 0x0F000000) == 0x03000000 && option->DataSize == 16) || ((option->Type & 0x0F000000) == 0x04000000 && option->DataSize % 16 == 0)) {
			for (i = 0; i < option->DataSize; i += 16, data += 16) {
				SPrint(buffer, sizeof(buffer), L"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} ", *((UINT32*)data), *((UINT16*)(data + 4)), *((UINT16*)(data + 6)),
					*((UINT8*)(data + 8)), *((UINT8*)(data + 9)), *((UINT8*)(data + 10)), *((UINT8*)(data + 11)),
					*((UINT8*)(data + 12)), *((UINT8*)(data + 13)), *((UINT8*)(data + 14)), *((UINT8*)(data + 15)));
				SerWrite(ser, buffer);
			}
		}
		else if (((option->Type & 0x0F000000) == 0x05000000 && option->DataSize == 8) || ((option->Type & 0x0F000000) == 0x07000000 && option->DataSize % 8 == 0)) {
			for (i = 0; i < option->DataSize; i += 8) {
				SPrint(buffer, 40, L"%ld ", *((UINT32*)(data + i)));
				SerWrite(ser, buffer);
			}
		}
		else if ((option->Type & 0x0F000000) == 0x06000000 && option->DataSize == 2 && data[0] == 0 && data[1] == 0) {
			SerWrite(ser, L"false");
		}
		else if ((option->Type & 0x0F000000) == 0x06000000 && option->DataSize == 2 && data[0] == 1 && data[1] == 0) {
			SerWrite(ser, L"true");
		}
		else {
			for (i = 0; i < option->DataSize; i++) {
				SPrint(buffer, 40, L"%02x ", *((UINT8*)(data + i)));
				SerWrite(ser, buffer);
			}
		}
		SerWrite(ser, L"\r\n");
		if (option->NextEntryOffset == 0)
			return;
		option = (BL_BCD_OPTION*)(firstOption + option->NextEntryOffset);
	}
}


EFI_STATUS BootApplicationEntryPoint(BOOT_APPLICATION_PARAMETER_BLOCK* BootAppParameters) {
	BL_FIRMWARE_DESCRIPTOR* fwdesc = (BL_FIRMWARE_DESCRIPTOR*)(((UINTN)BootAppParameters) + BootAppParameters->FirmwareParametersOffset);
	EFI_HANDLE ImageHandle = fwdesc->ImageHandle;
	EFI_SYSTEM_TABLE* SystemTable = fwdesc->SystemTable;
	EFI_GUID serGUID = EFI_SERIAL_IO_PROTOCOL_GUID;
	EFI_INPUT_KEY key;
	UINTN Event;
	EFI_SERIAL_IO_PROTOCOL* ser;
	InitializeLib(NULL, SystemTable);
	InitializeLib(ImageHandle, SystemTable);
	BS->SetWatchdogTimer(0, 0, 0, NULL);
	LibLocateProtocol(&serGUID, &ser);
	ser->Reset(ser);
	ser->SetAttributes(ser, 115200, 0, 0, DefaultParity, 8, DefaultStopBits);
	CHAR16* header = L"Hello, serial!\r\nD to dump variables, R to reboot, H to halt, or X to exit\r\n\r\n";
	SerWrite(ser, header);
	ST->ConIn->Reset(SystemTable->ConIn, TRUE);
	while (TRUE) {
		BS->WaitForEvent(1, &ST->ConIn->WaitForKey, &Event);
		ST->ConIn->ReadKeyStroke(ST->ConIn, &key);
		switch (key.UnicodeChar) {
		case 'd':
		case 'D':
			DumpVars(ser, BootAppParameters);
			break;
		case 'h':
		case 'H':
			RT->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
			break;
		case 'r':
		case 'R':
			RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
			break;
		case 'x':
		case 'X':
			return EFI_SUCCESS;
			break;
		}
	}
}

In case anyone has any more information, please share :-)

 

mihi

 

I don't know if anyone cares about that a few years later. But recently I am working on this.

 

In general, bootmgr/bootmgfw has its own application context other than EFI application context. That means, bootmgr/bootmgfw takes control of interrupt services, paged memory (yes, bootmgr has paged memory), exception vectors and other things. 

 

So in order to run normal UEFI applications, you have to perform a section of platform-specific code (I implemented only in ARM32, which is reverse-engineered from developrmenu.efi from a private Windows Phone Adaption Kit). See my example here: https://github.com/imbushuo/boot-shim

 

After performing platform-specific initialization code, you takes control of certain things, have interrupt services & exception vector disabled. Then you can do whatever you want with EFI protocols. Simple text input/out works for me.

 

Things will be easier on x86 / amd64 platforms. I think there is a reference implementation in ReactOS's UEFI library. IDA Pro is helpful to this too. Load symbols, check functions with name started "ArchInitializeContext"... and "ArchSwitchContext".


Edited by imbushuo, 03 February 2018 - 03:20 AM.






Also tagged with one or more of these keywords: efi, uefi, chainloader, grub2, bcd, bcdedit

0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users