Aaron Li's Blog

In solitude, where we are least alone.

一名萌新程序员


Press on avatar to see the blogs.

用 Windows Application 访问 UEFI variable

0. 前言

参与过UEFI编程的同学可能会对“Variable”有所了解。现行的UEFI Spec 2.7 Chap 8.2详细描述了Variable Services。

UEFI中很多module中都应用了Variable Service,作为一种Runtime Service,Variable可以向OS传递信息。

那么在与OS联调的时候,如何在OS中读取Variable呢?本文将描述如何在Windows下读取和写入UEFI中的Variable(参考MFST的online doc Access UEFI firmware variables from a Universal Windows App)。

1. 准备工作

Windows下做UEFI的同学都对Visual Studio不陌生,如果没有就准备一个(需要支持Windows API),本文作者使用的是Visual Studio 2015。 我们还需要一台非安装在Legacy Bios上的Windows 8/10系统,这里有条件的同学可以直接装一台系统,没有的可以在Virtual Box上安装一个Windows(注意打开EFI,Virtual Box 6.0可能会存在EFI系统不能正常安装Windows,可以使用5.2的版本)

2. 程序提权 (Grant Privilege)

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. [1]

根据MFST online doc(上面引用),首先我们需要对Universal APP进行提权,赋予APP SE_SYSTEM_ENVIRONMENT_NAME 权限,注意这个APP需要运行在Admin下(使用管理员权限运行)。

  HANDLE   hToken;
  PHANDLE  phToken = &hToken;
  //
  // Grant Privilege
  //
  if (!OpenProcessToken(GetCurrentProcess(),
    TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, phToken))
  {
    printf("OpenProcessToken failed!\n");
    return 0;
  }
  if (!SetPrivilege(hToken, SE_SYSTEM_ENVIRONMENT_NAME, TRUE)) 
  {
    printf("Please Try run as Administrator.\n");
    return 0;
  }

SetPrivilege函数为MFST例程,参见Enabling and Disabling Privileges in C++

OpenProcessToken 为 Windows API,参见OpenProcessToken function

GetCurrentProcess 为 Windows API,参见GetCurrentProcess function

不使用管理员权限运行会报ERROR_NOT_ALL_ASSIGNED错误:

The token does not have the specified privilege. Please Try run as Administrator.

3. Get/Set Variable

进入正题,在提权之后,APP可以通过GetFirmwareEnvironmentVariable与SetFirmwareEnvironmentVariable对Variable进行访问了。

对其进行简单的封装:

DWORD
GetVariable(
  _In_ LPCSTR lpName,
  _In_ LPCSTR lpGuid,
  _Out_writes_bytes_to_opt_(nSize, return) PVOID pBuffer,
  _In_ DWORD    nSize
) {
  DWORD Status;
  DWORD VariableSize = 0;
  //
  // Read Variable
  //
  VariableSize = GetFirmwareEnvironmentVariable(
    lpName,
    lpGuid,
    pBuffer,
    nSize
  );

  Status = GetLastError();
  if (Status == ERROR_SUCCESS) {
    printf("Get %s success.\n", lpName);
  }
  else if (Status == ERROR_INVALID_FUNCTION) {
    printf("System in Lagency Mode.\n");
  }
  else {
    printf("Error: 0x%x\n", Status);
  }
  return VariableSize;
}
BOOL
SetVariable(
  _In_ LPCSTR lpName,
  _In_ LPCSTR lpGuid,
  _In_reads_bytes_opt_(nSize) PVOID pValue,
  _In_ DWORD    nSize
) {
  DWORD Status;
  //
  // Write Variable
  //
  SetFirmwareEnvironmentVariable(
    lpName,
    lpGuid,
    pValue,
    nSize
  );

  Status = GetLastError();
  if (Status == ERROR_SUCCESS) {
    printf("Set %s success.\n", lpName);
    return TRUE;
  }
  else if (Status == ERROR_INVALID_FUNCTION) {
    printf("System in Lagency Mode.\n");
  }
  else {
    printf("Error: 0x%x\n", Status);
  }
  return FALSE;
}

如果运行在Legacy Bios下,会报ERROR_INVALID_FUNCTION错误:

System in Lagency Mode. Incorrect function.

4. Format Error

GetLastError()还有很多return值,我们并不能一一将其列举,详见:System Error Codes。 但可以将其转化为格式化打印输出,这里直接在上面的封装中进行修改(只修改GetVariable())。 MFST提供了FormatMessage API来处理所有的系统Error,详见:FormatMessage function

DWORD
GetVariable(
  _In_ LPCSTR lpName,
  _In_ LPCSTR lpGuid,
  _Out_writes_bytes_to_opt_(nSize, return) PVOID pBuffer,
  _In_ DWORD    nSize
) {
  DWORD Status;
  DWORD VariableSize = 0;
  //
  // Read Variable
  //
  VariableSize = GetFirmwareEnvironmentVariable(
    lpName,
    lpGuid,
    pBuffer,
    nSize
  );

  Status = GetLastError();
  if (Status == ERROR_SUCCESS) {
    printf("Get %s success.\n", lpName);
  }
  else if (Status == ERROR_INVALID_FUNCTION) {
    printf("System in Lagency Mode.\n");
  }
  else {
      UINT8 * pBuff;
      FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        Status,
        0,
        (LPTSTR)&pBuff,
        0, 
        NULL);
      printf("%s\n", pBuff);
  }
  return VariableSize;
}

可能会出现的情况: ERROR_ENVVAR_NOT_FOUND : 没有要访问的Variable

The system could not find the environment option that was entered.

ERROR_PRIVILEGE_NOT_HELD: 没有取得相应的权限(参见Chap2)

A required privilege is not held by the client.

Reference

Access UEFI firmware variables from a Universal Windows App
UEFI Spec 2.7
Enabling and Disabling Privileges in C++
OpenProcessToken function
GetCurrentProcess function
System Error Codes
FormatMessage function

最近的文章

蜗牛星际+Frp+Caddy+Cloudflare折腾记

黑裙如何实现外网访问?本文详细介绍使用Frp内网穿透,Caddy反向代理,Cloudflare CDN加速的方法。

继续阅读
更早的文章

用树莓派 Raspberry Pi 远程下载 (aria2)

用树莓派做远程下载机,可以在外出时方便下载,回家直接看电影。本文详细介绍如何在树莓派上安装并配置Aria2,配置网页服务器并部署AriaNg管理界面,获取公网IP并公网映射或实现内网穿透,以及配置硬盘挂载和SMB服务。

继续阅读
Top