Note: Read/Write UEFI variables under Windows 7 and later

簡單講, 用 GetFirmwareEnvironmentVariable 可以讀取 UEFI 的變數, SetFirmwareEnvironmentVariable 可以寫回變數內容! 很簡單的, 但是如果 Google 會發現一堆人求救需要 Example code, 我自己歸納有幾點問題要克服

  • 系統到底是不是用UEFI mode 安裝的, 有的系統實際上安裝因為開了CSM 模式, 最後裝出來的系統是走legacy mode, 如標準UEFI 目前大家應該都只有 FAT32 Filesystem support 但是很多人把install 檔案放到 NTFS system 上面, 根本就是透過 legacy mode 裝起來的. MSDN 有說. 透過下面的dummy variable string & dummy guid 可以透過 ERROR code 判斷到底是不是 UEFI mode 的系統!

call the function with a dummy firmware environment name such as an empty string (“") for the lpName parameter and a dummy GUID such as “{00000000-0000-0000-0000-000000000000}" for the lpGuid parameter. On a legacy BIOS-based system, or on a system that supports both legacy BIOS and UEFI where Windows was installed using legacy BIOS, the function will fail with ERROR_INVALID_FUNCTION. On a UEFI-based system, the function will fail with an error specific to the firmware, such as ERROR_NOACCESS, to indicate that the dummy GUID namespace does not exist.

  • 執行權限的問題實際上他需要比較高的權限才能執行, 執行帳號要有 SE_SYSTEM_ENVIRONMENT_NAME 的權限, 或是程式內要求提權
  • 然後就是 UEFI Variable 的 Name String & GUID, 這個可以透過 DXE App DumpVar(Phoenix)/DumpVariable(Intel) or uefidump(Ubuntu Linux) 等工具, 但是這些只能觀察到變數的 Name String & GUID, 變數到底是怎樣組成的結構無法得知! 比較好的方式是跟BIOS Engineer 合作 取得 Header File 才能理解哪些變數有啥功能, 跟位於struct 中的offset

下面就是簡單的範例, 範例中的 Name String & GUID 是針對某特定版本的機種, 所以實際上無法完全直接換到不同型號的主機板上測試

 

#include "stdafx.h"


void RasiePrivileges(void)
{
	HANDLE hToken;
	TOKEN_PRIVILEGES tkp;

	if (!OpenProcessToken(GetCurrentProcess(), 
			TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, 
			&hToken)) {
				printf("Failed OpenProcessToken\r\n");
				return;
	}

	LookupPrivilegeValue(NULL, SE_SYSTEM_ENVIRONMENT_NAME,
		&tkp.Privileges[0].Luid);
	tkp.PrivilegeCount = 1;
	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	
	DWORD len;
	AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, &len);

	if (GetLastError() != ERROR_SUCCESS) {
		printf("Failed RasiePrivileges()\r\n");
		return;
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	BYTE Val[4096];
	DWORD dwLen = 0;
	const TCHAR name[] = TEXT("BootOrder");
	const TCHAR guid[] = TEXT("{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}");
	const TCHAR system[] = TEXT("System");
	const TCHAR sys_guid[] = TEXT("{E947FCf9-DD01-4965-B808-32A7B6815657}");
	const unsigned int offset_TPM = 11; // TPM On/Off offset

#if 0
	const TCHAR name[] = TEXT("");
	const TCHAR guid[] = TEXT("{00000000-0000-0000-0000-000000000000}");
#endif

	printf("Change Boot Order\r\n");
	RasiePrivileges();

	dwLen = GetFirmwareEnvironmentVariable(
		name, guid, Val, 4096);

	if (dwLen == 0)
	{
		DWORD dwErr = GetLastError();
		printf("Failed, GetFirmwareEnvironmentVariable(), GetLastError return %d(0x%08x)\r\n",
			dwErr, dwErr);
		return 1;
	}

	printf("Dump BootOrder variable, Len:%d(0x%02x)\r\n", dwLen, dwLen);
	for(UINT32 i = 0; i < dwLen; i++) {
		printf("%02x", Val[i]);
		if (i%16 == 0 && i != 0) {
			printf("\r\n");
		} else {
			putchar(' ');
		}
	}

	printf("\r\nExchange BootOrder Item1 & Item2\r\n");
	BYTE Tmp[2];
	Tmp[0] = Val[0];
	Tmp[1] = Val[1];
	
	Val[0] = Val[2];
	Val[1] = Val[3];
	
	Val[2] = Tmp[0];
	Val[3] = Tmp[1];

	if (SetFirmwareEnvironmentVariable(name, guid, Val, dwLen) == TRUE) {
		printf("Success!! SetFirmwareEnvironmentVariable\r\n");
	} else {
		printf("Failed!! SetFirmwareEnvironmentVariable\r\n");
	}

	printf("Read TPM Support state\r\n");
	dwLen = GetFirmwareEnvironmentVariable(
		system, sys_guid, Val, 4096);

	if (dwLen == 0)
	{
		DWORD dwErr = GetLastError();
		printf("Failed, GetFirmwareEnvironmentVariable(), GetLastError return %d(0x%08x)\r\n",
			dwErr, dwErr);
		return 2;
	}
	
	if (Val[offset_TPM]) {
		printf("TPM Support is Enabled, will change to Disable\r\n");
	} else {
		printf("TPM Support is Disabled, will change to Enable\r\n");
	}
	Val[offset_TPM] = (Val[offset_TPM]) ? 0 : 1;

	if (SetFirmwareEnvironmentVariable(system, sys_guid, Val, dwLen) == TRUE) {
		printf("Success!! Change TPM Support Setting\r\n");
	} else {
		printf("Failed!! Can't change TPM Support Setting\r\n");
	}
	return 0;
}
廣告

21 回應 to “Note: Read/Write UEFI variables under Windows 7 and later”

  1. Thanks. I successfully used your code excerpt to get to the UEFI environment.

  2. 能否請問編譯的環境需求為何,謝謝您

    • 編譯環境沒有特殊需求, 基本上Visual Studio 或是其他有辦法呼叫 Windows API的程式語言 應該都可以,
      我自己是用VS2008 的 VC2008 ~ or VS2013
      要注意的 MSDN 上有說, 這個API 是屬於 desktop apps only , 所以不能用在 Windows Store 的App裏面

  3. Hi 感謝這篇的文章,受益良多!大大請問一下,要如何知道lpName
    與lpGuid呢?想寫程式來做測試,再麻煩您如果有空再指導一下,謝謝!
    https://msdn.microsoft.com/zh-tw/library/ms724325(v=vs.85).aspx

  4. 應該是有辦法撈出 name & Guid 但是 那個變數的結構可能是 structure 的 最好的辦法是你取得主機板或是筆電廠商的支援!

  5. 主板的bios被刷了以后sdio wifi就用不了了,网上别人用
    cp /sys/firmware/efi/efivars/nvram-74b00bd9-805a-4d61-b51f-43268123d113 的方法装linux的驱动,我获得了一个别人的版本,不知道可不可以用这种方法刷回去呢?

    • Hi bb, 我先假設你的問題有下述條件
      1. 因為你是SDIO WIFI 如果是一般的系統很少用上 SDIO WIFI 好像只有少數 上網本 會用, 所以我猜測是某家的上網本
      2. 按照你說的問題 刷了一個版本的BIOS 後就用不了
      這有兩個疑問
      A. 用dmesg 看得到該裝置嗎 driver 被載入嗎?
      B. 你看的網上用的方法是否跟你是同一個上網本呢? 如果是應該是有機會的 如果不是 我想應該不見得能解決
      3. 你這個上網本能裝 Windows 嗎? 能裝本來廠商的工具嗎? 如果能的話 建議你裝回Windows 跟廠商工具測試你的WIFI 是否能正常用 如果可以的話 這時在換回 Linux 應該也會動的
      因為Windows 現在可以切換WIFI/BT/WAN 等無線裝置Enable/Disable 所以極有可能因為你刷了一個BIOS 造成那個設定區域改變 一直把你的WIFI device 處於Disable 的狀態了

  6. 问题被我解决了,是这样的,bios有这样一块区域记录着sdio芯片的一些固件配置包括mac等类似这样的明文:#Sample variables file for BCM94324A1 iPA+iLNA FCBGA REF board
    # NV VER: 0.2.3.0.XV1
    # 20130829 Change Note:
    # Enable Out of band GPIO for connected standby
    # 20130809 Change Note:
    # REMOVE 2×2 BTC effect BIT
    # Change CCODE to XV/1
    # 20130816 Change Note:
    # Change PA parameters for TSSI
    # 20130830 Change Note:
    # Change Power-per-rate settings
    # 20130903 Change Note:
    # Change Power-per-rate settings
    devid=0x4374
    boardtype=0x5f0
    boardrev=0x1200
    boardflags=0x201
    #boardflags2=0x00800000(后面省略n字节)
    而且没有压缩,在bios的nvram里,我已经整个模块通过编程器搞进去了,之前是因为不知道这段代码的地址在哪,所以迟迟不能解决,我网上找到了一些山寨笔记本的这种代码,山寨笔记本竟然统统用一个mac,而且是写在操作系统里的……晕……,搞清楚了数据结构,我就直接刷进去了……谢谢您的帮助,这个函数还是不怎么会用,GUID和名字我都不知道,但是我敢肯定,肯定依靠的是这个,因为网上一些黑客给这种机子装linux的时候,就是这样干的……cp /sys/firmware/efi/efivars/nvram-74b00bd9-805a-4d61-b51f-43268123d113 貌似在linux里只需要知道guid就可以了根本不需要知道名字……

    • 其實在bios 裡面也是靠uuid 操作的 然後那個cp 就是在linux 下面操作那個 efivar 他應該是一塊類似string的記憶體或是一個 block 聽起來像是某家開發固定的版本 其他家就是改改畫面 這種天朝很多呀 實際上國外也不少 我有遇過義大利要求協助他們改某些東西的要求

      • 我玩的比较另类吧,是这样的,我自己花了400rmb买了一个尸体的笔记本电脑,屏幕和触摸碎掉,自己换了内屏,触摸屏网上找不到z8500的cpu,2g 64g的笔记本,我自己做bga换成了8g 128g的然后发现bios无法启动,然后查了cpu的手册,改了bios,好不容易能开机了,发现没WiFi坏了,昨天修好了wifi……这个bios我刷了不下60次吧,幸好我有一些汇编的功底,大概能猜到bios想干嘛……

  7. 強大 牛人 要給你一百個讚

    • 还是有很多前辈给我启示才这样的,有人给苹果换过emmc升级硬碟,有人给t100ta装linux,然后读到了驱动信息,我根据这些指引才弄到的,现在的笔记本bios不好刷哦,都是1.8v的电压,害得我还搭了一个电平转换电路……整个过程差不多耗费了我1个月,包括换屏,换芯片,改bios,刷bios,改bios,刷bios,解锁隐藏选项,再刷bios……从以前的bios里找回驱动程序,再整合刷bios……好累哦喵,uefi很多东西还需要去弄懂,我在想,下一步是不是要刷个黑苹果玩玩……我喜欢Intel Atom的新cpu,带电8-10小时的笔记本哎,敲代码什么的,带着很方便呢!至于编译或者工作,完全可以交给云服务器完成啦,毕竟计算力在家里的工作站主机,这说白了只是个终端……我可能更偏向底层和硬件吧,以后有什么关于硬件底层这边的问题,arm、x86或fpga这些的问题,欢迎来交流哦

  8. 感激 XD 以後就請教你這個牛人了 你太強大了 這樣都能改bios

    • 你就别取笑我咯……好么?刚才详细的翻了下你的博客,发现你涉猎极广,51,arm 8086,好像无孔不入啊……我和系统级的东西打交道还是太少了……很汗颜啊……而且感觉兄弟也是性情中人啊,指点江山的……对政治貌似也颇有见解啊……相信我们可以成为很好的朋友的……

      • 沒有取笑你的意思呀 我是因為工作才會去看這個 你這種算是一種maker 呢 真心佩服 好奇你的UEFI BIOS 是拿到源馬 還是直接改binary

  9. 不知道你做过破解吗?确实bios源码泄露了,可是这个版本的bios已经是3个平台版本之后的事情了,源代码的参考价值很弱,甚至连启动代码都不同了,以前的笔记本是有南桥北桥的,而这一代变成了soc,英特尔想走的路线是手机和平板什么的lpddr3又自带内存控制器,完全不同了,所以没源码泄露,只能静态反编译,好在bios程序没有加花加壳这种恶心的东西,静态就完全够用了设置菜单解锁改了几个判定,内存容量bios写好的,也被隐藏了,毕竟soc支持的内存bios肯定要写,然后找得数据结构接进去了……最讨厌的是这段wifi的固件,貌似是华硕自己的程序,先把固件放在bios的一段里,然后通过他自己的程序把这段映射到nvram区域,然后映射(读取?)到内存,Windows的驱动靠读取内存里的映射段来获取……而不是默认写到uefi的nvram区域里,要知道就连Windows的引导都是写在nvram里的,当然这应该是规范。这样做貌似有好处吧,如果你在Windows下不小心改了这段nvram,不影响原来的东西,这段区域还包含出厂日期,序列号等一些信息,也都是明文ascii的还有一些序列号我也不知道哪里的,反正抄过来就是咯,貌似不影响使用,没序列号照样用,bios如果刷直接刷原厂的程序,除了wifi不能用,蓝牙啊其他啊都好好的……

    • Crack 只有略懂 偶而工作需求會用一下 😛 我只能說你運氣不錯 而且實力強大 基本上BIOS 現在很多都有壓縮 不是直接可以改的 你現在改的這些資料 只是驅動的configuration 剛好它規劃可以切一塊放置這個

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s

%d 位部落客按了讚: