Aaron Li's Blog

In solitude, where we are least alone.

一名萌新程序员.


Learning everyday, let tomorrow be a better today.

用 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折腾记

一段时间没有折腾什么东西,感觉人都颓废了。前一阵蜗牛星际炸雷了之后,网上很多小机箱搞的很火爆。鉴于一直想搞一个NAS但又苦于昂贵的价格,赶上这班车直接就剁了一个A款。选择A款的原因,其实是因为便宜,在一个有个外面板不会直接漏硬盘在外面,网上有很多对比评测。黑裙的系统是918(其实马爸爸家的卖家都会给装好),google上也有很多就不复读了。下面正文开始。1. 利用Frp内网穿透迫于没有公网IP,以及黑裙,只能走反向代理的方式从外网访问,这时候Frp就很重要了。要使用Frpc,首先需要安装d...…

继续阅读
更早的文章

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

1. 系统安装安装好系统的可以略过。这里选择的系统是chainsx提供的64位系统ubuntu-18.04-arm64。这个系统相比官方的armhf系统优势在于配置简单,官方提供的ubuntu只默认支持Raspberry Pi 2,3B与3B+均需要根据官方的文档进行更改。且此系统默认的apt源是清华的源,国内下载速度快。SD卡烧录工具:Win32DiskImager。以下说明均按照ubuntu系统,centos系统目前还没有尝试过。注:默认用户名为ubuntu2. Aria2安装sudo...…

继续阅读
Top