Using VMProtect on UEFI binaries

As you may or may not know, the latest version of VMProtect (as of writing of this post, 3.7) does not support protection of UEFI binaries at all. When you try to load a UEFI application or a driver, you will be presented with an unsupported subsystem error.

Screenshot of the error

However, VMProtect has supported the protection of Windows kernel mode drivers for quite some time. This means that, with a bit of trickery, you can use it on almost any PE file imaginable, including UEFI binaries.

The trick is extremely simple: just change the subsystem, use VMProtect, and then change it back.

PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileBase;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(fileBase + dosHeader->e_lfanew);
ntHeaders->OptionalHeader.Subsystem = targetSubsystem;

Valid values are in the table bellow.

Constant Value Description
IMAGE_SUBSYSTEM_UNKNOWN 0 An unknown subsystem
IMAGE_SUBSYSTEM_NATIVE 1 Device drivers and native Windows processes
IMAGE_SUBSYSTEM_WINDOWS_GUI 2 The Windows graphical user interface (GUI) subsystem
IMAGE_SUBSYSTEM_WINDOWS_CUI 3 The Windows character subsystem
IMAGE_SUBSYSTEM_OS2_CUI 5 The OS/2 character subsystem
IMAGE_SUBSYSTEM_POSIX_CUI 7 The Posix character subsystem
IMAGE_SUBSYSTEM_NATIVE_WINDOWS 8 Native Win9x driver
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9 Windows CE
IMAGE_SUBSYSTEM_EFI_APPLICATION 10 An Extensible Firmware Interface (EFI) application
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER 11 An EFI driver with boot services
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER 12 An EFI driver with run-time services
IMAGE_SUBSYSTEM_EFI_ROM 13 An EFI ROM image
IMAGE_SUBSYSTEM_XBOX 14 XBOX
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION 16 Windows boot application

Keep in mind that only code mutation and virtualization will work. You cannot use file packing, resource protection, memory protection, or anti-debug, as these would cause your binary to have imports from the Windows kernel, which obviously won’t be resolved in a UEFI environment.

If you put in a little more effort, you can even set up Visual Studio post-build scripts (if you are using VS, that is) to automatically run VMProtect.

:: Usage as follows:
:: call "$(SolutionDir)scripts\post_build.bat" "$(TargetDir)$(ProjectName).dll" "$(TargetDir)sakura.efi"

echo Changing subsystem to Windows driver...
call "%~dp0change_subsystem.exe" "%1" 0x1

echo Running VMProtect...
call "C:\Program Files\VMProtect Professional\VMProtect_Con.exe" "%1" "%2" -pf "%~dp0main_config.vmp"

echo Changing subsystem back to EFI application...
call "%~dp0change_subsystem.exe" "%2" 0xA

echo All done

Don’t forget to avoid options that might require adding additional imports into the binary.

<?xml version="1.0" encoding="UTF-8" ?>
<Document Version="2">
    <Protection InputFileName="input_file.dll" Options="65736" VMCodeSectionName=".uwu" OutputFileName="sakura_protected.efi">
        <Messages />
        <Folders />
        <Procedures>
        </Procedures>
        <Objects />
    </Protection>
    <Script />
</Document>

You can see this setup in action in this video.