1

I'd like to read the BootOrder UEFI variable with Python, by using the kernel32.dll function GetFirmwareEnvironmentVariableW. This fails:

from ctypes import *

def errcheck(result, func, args):
    print(result)
    print(WinError(get_last_error()))

kernel32 = WinDLL('kernel32', use_last_error=True)
GetFirmwareEnvironmentVariable = kernel32.GetFirmwareEnvironmentVariableW
GetFirmwareEnvironmentVariable.restype = c_int
GetFirmwareEnvironmentVariable.argtypes = [c_wchar_p, c_wchar_p, c_void_p, c_int]
GetFirmwareEnvironmentVariable.errcheck = errcheck
buf = "                "
GetFirmwareEnvironmentVariable("BootOrder", "{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}", buf, 16)
print(buf)

with:

[WinError 1] Incorrect function.

I can imagine that initializing buf with " ", is not the right way to do it.

How to pass a buffer pointer correctly to a WinAPI function with ctypes?

And how to read this UEFI variable with Python?

Basj
  • 41,386
  • 99
  • 383
  • 673
  • [`create_string_buffer`](https://docs.python.org/3/library/ctypes.html#ctypes.create_string_buffer) seems like it'd do what you want. Something like `buf = cast(create_string_buffer(BUFFER_SIZE), c_void_p))`. *If* that is the cause of the error. After some quick research, that error seems to be very generic and covers many things. – Carcigenicate Jun 23 '20 at 14:34
  • @Carcigenicate do you mean something like `buf = cast(create_string_buffer(1024), c_void_p)` `GetFirmwareEnvironmentVariable("BootOrder", "{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}", buf, 1024)`? Then how to read the content? `print(buf)` only shows the pointer. Do you achieve to read the UEFI variable content with this on your computer? – Basj Jun 23 '20 at 15:03
  • I get a privilege error when I try to run it, so I can't get valid data. If you cast `buf` back to a `c_char_p` after filling it though, you should be able to use `buf.value` or `buf.raw` to get a `bytes` object of data. – Carcigenicate Jun 23 '20 at 15:16
  • Actually, you seem to be able to pass the `create_string_buffer` buffer directly to the API function without a cast, then use `buf.raw` to get the results after. Again, I can't test to get valid data to make sure nothings getting mangled, but I don't get errors either. – Carcigenicate Jun 23 '20 at 15:26
  • I have the same privilege problem now, even if I run python.exe from a administrator cmd.exe. strange! Do you have the same @Carcigenicate? – Basj Jun 23 '20 at 17:09
  • Yes, I was getting "A required privilege is not held by the client.". It seems to require more privilege settings than just running from an admin prompt. The best answer I could find was along the lines of [this](https://stackoverflow.com/questions/27362404/createprocessasuser-gives-a-required-privilege-is-not-held-by-the-client-whi). Class started though, so I didn't dig any further. – Carcigenicate Jun 23 '20 at 17:16
  • From the Microsoft doc page that you link to: "To read a firmware environment variable, the user account that the app is running under must have the SE_SYSTEM_ENVIRONMENT_NAME privilege. A Universal Windows app must be run from an administrator account and follow the requirements outlined in Access UEFI firmware variables from a Universal Windows App." – Carcigenicate Jun 23 '20 at 17:22
  • I wonder how to start a python script with this privilege. Such things are always difficult! – Basj Jun 23 '20 at 17:24
  • If you can wait half a year, I'll likely have been tested on this stuff by then XD. I'm not quite there yet though. We haven't gotten into modifying user privileges yet. I think its permissions associated with the running account though, not just the instance of the console. – Carcigenicate Jun 23 '20 at 17:27
  • The closest I can get it: Search for Local Security Policy. Then Local Policies -> User Rights Assignment ->Modify firmware environment values -> Add your account. I can't get it to work though even after adding it. I may need to restart my computer first. According to the docs though, that's the privilege that you need. – Carcigenicate Jun 23 '20 at 17:44
  • 2
    SE_SYSTEM_ENVIRONMENT_NAME privilege is granted to Administrators (usually) but it isn't *enabled* by default. See https://learn.microsoft.com/en-us/windows/win32/secauthz/enabling-and-disabling-privileges-in-c--. You have more work to do to port *that* to Python – Mark Tolonen Jun 24 '20 at 17:17
  • I finally found a way @MarkTolonen, I'll post an answer when everything works perfectly – Basj Jun 24 '20 at 18:39
  • 1
    @Carcigenicate I have found the solution and I posted an answer. Thanks again for your help. PS: it's already in use here :) https://github.com/josephernest/rebootnow – Basj Jun 24 '20 at 22:09

1 Answers1

2

As mentioned by @Carcigenicate in comments, there are two parts:

  • use a buffer (created with create_string_buffer) to get the output as bytes in buf.raw

    from ctypes import *
    kernel32 = ctypes.WinDLL('kernel32')
    GetFirmwareEnvironmentVariable = kernel32.GetFirmwareEnvironmentVariableW
    buf = ctypes.create_string_buffer(128)
    length = GetFirmwareEnvironmentVariable("BootOrder", "{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}", buf, 128)
    print(buf.raw)
    
  • get the right privileges to get the UEFI variables

    import win32api, win32security
    htoken = win32security.OpenProcessToken(win32api.GetCurrentProcess(), win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY)
    newPrivileges = [(win32security.LookupPrivilegeValue(None, win32security.SE_SYSTEM_ENVIRONMENT_NAME), win32security.SE_PRIVILEGE_ENABLED)]
    win32security.AdjustTokenPrivileges(htoken, 0, newPrivileges)
    
Basj
  • 41,386
  • 99
  • 383
  • 673