ARM平台UEFI固件隐藏项修改:从Setup模块解析到gsetupmod实战

发布时间:2026/9/8 18:07:41
ARM平台UEFI固件隐藏项修改:从Setup模块解析到gsetupmod实战 前阵子有位朋友拿了一台 Windows on ARM 的迷你主机问我ARM 平台能不能像 x86 那样挖出 BIOS 里的隐藏设置说实话搁半年前我会劝他别折腾不是因为 ARM 做不到而是手头的 BIOS 修改工具大多只认 x86 下的 UEFI 模块。gsetupmod 双架构发布之后这个局面不一样了——它把支持范围从传统的 x86_64 扩展到了 AArch64也就是能在 ARM 的 UEFI 固件里直接定位 Setup 模块、分析隐藏项逻辑并生成可用的定制模块。这篇文章我从实际动手的角度聊聊ARM 平台上的“改 BIOS”到底是怎么一回事以及完整操作流程里你会遇到哪些坑。先说清楚一个概念平时说的“改 BIOS 隐藏项”在 UEFI 时代并不是去改代码而是改一个叫 Setup 的运行时模块。这个模块会生成你在开机时看到的设置界面里面的每个选项长什么样、什么条件下显示、值存在哪全都由表单数据决定。gsetupmod 这类工具干的事情就是解包这种表单结构找到被厂商设置成隐藏或禁用的条目再重新打包回去。所以它不是那种“找字符串然后替换”的简单工具理解这一点后边你才知道怎么避开麻烦。为什么 ARM 平台也开始聊“改 BIOS”1.1 ARM 设备里的“BIOS”其实也是一套 UEFI很多刚接触 ARM 服务器或者 ARM 开发板的朋友第一反应是“ARM 不是用 uboot 吗哪来的 BIOS”。这个想法过时了。现在大量 ARM 设备跑的已经不是传统 uboot 那一套而是标准的 UEFI 固件比如 Ampere 的服务器主板、部分高通骁龙开发套件、Windows on ARM 笔记本甚至一些派类板卡都有 EDK2 移植的 UEFI 引导层。既然跑的是 UEFI设备里自然就有 Setup 界面。你在开机时按 F2 或者 Del 看到的那一整页设置菜单本质上是固件里一个叫 SetupUtility 的 DXE 驱动生成的。它跟 x86 主板的 BIOS 设置界面没有本质区别同样包含菜单、子菜单、复选框、下拉框同样把配置项分门别类地挂在不同页面下。也就是说ARM 平台早就具备了“改 BIOS 隐藏项”的软件基础真正缺的只是能处理 ARM64 模块的修改工具。如果你手头有一台跑 UEFI 的 ARM 设备可以开机进 Setup 看一眼然后跟同规格的 x86 主板设置页面对比。你会发现 ARM 平台能看到的设置项通常少很多CPU 电压、内存时序这种一般直接不给连串口重定向、PCIe 链路策略这种调试向的选项都很少露头。但模块文件里通常还在只是被判断逻辑“藏”起来了。1.2 隐藏选项是如何藏起来的我在刚开始研究 UEFI 固件时也以为厂商隐藏设置就是把某个字符串删掉。实际不是。UEFI 的窗体界面由一套叫 HIIHuman Interface Infrastructure的机制驱动设置页面的布局、控件类型、显示条件全部写在一段叫 IFR 的二进制数据里。每个选项是否显示往往由一条“SuppressIf”或者“GrayOutIf”条件决定。怎么理解呢你可以把 Setup 界面想象成一间大展厅每件展品都有一块牌子写着“什么条件下允许展出”。条件为真展品就藏起来条件为假展品才展示。这些条件读的通常是 NVRAM 变量里的某个字节比如“如果主板型号不是出厂旗舰款就把 CPU 频率控制项隐藏”。整个判断不需要厂商重新写代码只要在 UEFI 启动时把变量值读出来跟预设值比较就能决定这一屏显示什么。所以“开隐藏项”的本质是两条路第一改 IFR 里的条件判断把 SuppressIf 移除或者改成永远不成立第二改条件所读取的那个默认变量值让判断走向“允许显示”的分支。前者要处理二进制的结构偏移后者要确认 varstore 的偏移和默认值。两条路各有难度而 gsetupmod 发布双架构支持最主要的原因就是这两件事在 ARM64 固件里做起来更麻烦远不是把 x86 的模块直接拿过来处理就完事。1.3 谁需要和 ARM 的隐藏项较劲真正有动力去折腾 ARM 固件隐藏项的人角色都挺垂直。一类是固件开发或者底层测试工程师他们需要在不改代码、不动编译流程的前提下临时打开某些调试入口比如串口控制台、PCIe 错误报告、电源策略开关。一类是在 ARM Windows 设备上做系统适配的人有时候把设备装好系统后外围功能不正常想进固件打开某个控制器开关结果发现选项被隐藏了。还有一类是单纯的折腾党手上正好有 ARM 开发板或者迷你主机想看看同一颗处理器在不同固件策略下能释放出多少性能。不管属于哪一类你需要的不是“看到更多菜单”这个结果而是搞清楚菜单背后的逻辑。我见过有人花了半天时间打开了十几个隐藏项结果不知道每项对应什么作用最后把固件设置改乱了系统不稳定。所以这篇后面除了讲步骤我也会重点说每个关键动作是为了解决什么。gsetupmod 双架构这个“双”到底改在哪里2.1 Setup 模块的类型差异不是换套指令就能通吃很多人误以为 UEFI 模块就是普通 PE 可执行文件x86 和 ARM 的区别只是 CPU 指令集不一样工具支持 ARM64 无非是多认识一种指令。这个理解对了一半。UEFI 里的驱动模块确实是 PE32 格式x86_64 下机器类型是 0x8664AArch64 下机器类型是 0xAA64。工具要解包一个 ARM64 模块PE 头解析逻辑得跟着改光这一步就拦住了不少老工具。但这只是入口处的差异。真正麻烦的是模块内部的数据布局。x86 固件里的 Setup 模块长期以来形成了相对固定的排布方式很多 IFR 处理工具甚至带着一些隐式约定比如默认按小端解析变量偏移、默认按 x86 的引导方式处理扇区对齐。到了 ARM64 平台模块里头的各种指针、地址、大小字段虽然还是小端但 EFI 镜像在加载运行时的基址处理、代码段数据段的对齐方式都可能不一样这会导致工具在分析变量偏移时出现偏差。gsetupmod 双架构版本做的事一般是在同一个代码框架里把两套解析规则分开。遇到 0x8664 的模块走老一套逻辑遇到 0xAA64 就用另一套加载和偏移计算方法。这个设计思路本身很值得借鉴不要试图用同一套规则硬套两种不同架构的固件而是让工具先识别机器类型再选用对应的解析策略。2.2 从模块里找“门”IFR 结构和变量存储想在实际操作中不迷路你必须先知道 Setup 模块里藏着什么。用任意十六进制工具打开一个 UEFI DXE 驱动模块你会发现里面除了真正的可执行代码还有一大段看起来没什么规律的二进制数据。这段数据通常被封装在特定的资源 section 里里面就是 HII 表单。它由很多 tiny 的 opcode 组成每个 opcode 描述一个界面元素或者一条逻辑。比如一个普通的下拉框选项在 IFR 里会对应一个 OneOfOpcode后面跟着选项名、变量存储偏移、默认值一个隐藏逻辑会对应一条 SuppressIfOpcode括号里夹着一个表达式。gsetupmod 处理模块时做的事情就是把这段二进制按 opcode 顺序逐个解析给你一个可读的树形结构然后允许你按规则修改其中一些 opcode 的 key 字段。在这个过程里最重要的参考坐标是 varstore offset。它是某个设置项写入 NVRAM 时的偏移量。你修改隐藏项时可以改 SuppressIf 的条件值但前提是你要知道该条件跟哪个 varstore offset 绑定。偏移写错哪怕只差一个字节都可能把不相关的设置项一并改变。很多帖子叫你“搜索字符串然后改数值”这种粗暴做法在简单场景下能蒙对在 ARM64 固件里我劝你别试因为有太多地方需要同时改。2.3 双架构支持的固件形态对比单看模块类型平台固件大致可以分为 AMI 系、Insyde 系和纯 EDK2 定制系。不同厂商的 Setup 模块命名、section 封装方式、NVRAM 默认布局都不太一样。下面这个表格是我整理出来用于快速定位模块的参考不是绝对精确但能帮你少走很多弯路。固件体系常见架构Setup模块常见隐藏位置修改注意点AMI Aptiox86_64 / AArch64SetupUtility 驱动段或 Capsule 内模块名稳定IFR结构规整gsetupmod支持成熟Insyde H2Ox86_64 / AArch64Setup 相关 DXE 驱动命名不固定需先确认模块 GUID再找 IFR section纯 EDK2 定制AArch64 为主各平台自定义 DXE 驱动结构最不统一需要具体分析每个固件对刚接触 ARM 固件的人来说AMI 系的设备是最合适的练手对象因为模块位置和 IFR 结构相对容易预测。Insyde 系的 ARM 设备我遇到过模块命名经常是随机字符串得靠 GUID 搜索才能定位难度上了一个台阶。纯 EDK2 定制固件多见于开发板结构简单但每个板子都不同不适合作为第一次试验目标。实操一支 ARM 固件从提取到打开隐藏项3.1 准备阶段先把自己从“变砖”边缘拉回来动手之前最要紧的不是找工具而是备份原始固件。ARM 设备的固件存放方式和 x86 主板不大一样。x86 主板一般是 SPI Flash 芯片芯片容量常见 8MB 到 32MBARM 服务器或者开发板可能是 SPI NOR、eMMC 上的固件分区或者直接由 BootROM 从存储介质加载。所以在备份之前先确认这台上电后按 F2 能进的是不是 UEFI Setup再看固件更新包是否公开可下载。如果你有原厂固件更新包这是最安全的方式。把更新包下载下来解包后你会得到一个完整的固件镜像后续所有修改都在这个镜像上进行不需要先拆机。如果厂商没有提供可直接解包的镜像那你可以用 flashrom 读取比如 Linux 下执行# 先列出设备上可能的 flash 芯片 sudo flashrom -p internal --ifd -i bios # 在确认芯片和区域正确的前提下读取并保存 sudo flashrom -p internal -c W25Q128JV -r factory_backup.romflashrom 的-p internal参数要求主板 BIOS 中开启相应访问权限ARM 设备不一定支持这点要有心理准备。无论用哪种方式备份完一定要计算哈希值并存两份不同介质上。我自己的习惯是备份三份一份留着对比一份放在工作目录外一份刻进一个小 U 盘里吃灰。这看起来有些过度但真到刷砖需要回写的时候你会感谢当初多留了这份原始文件。3.2 用 UEFITool 把 Setup 模块从固件里“抠”出来拿到完整固件镜像后下一步是用 UEFITool 打开它。UEFITool 目前有图形版和命令行版图形版适合快速定位模块。打开镜像后你会看到左侧是一个树状结构从 Firmware Volume、File 一直展开到 Section。如果你不知道该找哪个模块直接按 CtrlF 搜索 “Setup” 或 “SetupUtility”。arm64 平台要注意一点有些固件里的模块被压缩过在 UEFITool 里显示为 Apriori 或者 Compression section。你需要先展开压缩段一层层进到 PE32 镜像或者 RAW section 里才能找到目标模块。如果真的找不到 Setup 字样可以换关键词搜索 IFR 表单里常见的文本比如 “Quiet Boot” 或 “Boot Timeout”能定位到包含 IFR 数据的模块。定位到模块后右键选择 “Export” 导出整个模块文件。记住导出的不只是可执行代码还包含 IFR section。接着使用 Universal IFR Extractor 之类的工具打开导出的模块导出成可读的 .txt 文件。如果你用的是命令行版本会遇到类似这样的操作# 用 IFR Extractor 导出模块中的 HII 表单内容 IFRExtractor-RS.exe setup_module.efi -o setup_ifr.txt导出后不要急着改。先在这个文本文件里搜索你想打开的功能的关键词比如 PCIe ASPM、SATA Mode、CPU 频率。看这些条目周围有没有包含 Suppress 或 GrayOut 的逻辑块同时记下它绑定的 varstore offset。这一步是将你“想改哪个功能”翻译成“具体改哪个数据位置”的关键。3.3 隐藏项修改的两种路径真正动手修改时我建议先从“改默认变量值”入手而不是直接删 SuppressIf。举个例子如果 IFR 里有个条件是“当 VarStore 偏移 0x1A20 的值等于 0x00 时隐藏该 CPU 调频项”那你先要找这个偏移对应的默认值存储位置把它改成 0x01 或其它不满足隐藏条件的值。这种改法的好处是 IFR 结构不用动后续出问题容易定位。如果修改默认值后选项还是没有出现那就得直接处理 IFR 里的条件块了。gsetupmod 一类的工具通常会给一个补丁配置让你描述“某偏移的多少长度字节改成什么值”然后在解析代码里自动重算 CRC 与相关指针。大体流程可以这么理解你给工具一份改动清单工具读取模块、解析 IFR、执行清单中的偏移修改、重新生成模块但不同工具的参数格式完全不同具体用哪个语法要以你拿到的工具文档为准。我见过不少人第一步就试图删除 SuppressIf 整条 opcode。删除操作在 IFR 里很危险因为后面的 opcode 偏移全部前移如果没有一并修改跳转或者引用关系会导致 Setup 菜单结构错乱。所以我个人的红线是能不删就不删能用默认值解决就不动 IFR 逻辑。假如实在必须改也尽量把条件里的比较值改成永不满足而不是删掉节点。3.4 重新封入固件并刷回设备修改完 Setup 模块后要把它放回完整的固件镜像里。用 UEFITool 的 Replace 功能选择原先导出的那个模块节点将修改后的模块文件替换进去然后另存为新固件。这里有一个很容易犯的错替换后的模块体积和原来不同整个 Firmware Volume 的长度会被改变如果固件分卷的大小布局不匹配刷入后大概率开不了机。多数 UEFI 固件的卷结构里预留了空闲空间体积微小的变化一般能自动吸收。但如果你修改导致模块增长明显建议先检查卷内是否有足够的 padding 空间没有的话编译器布局就需要调整。这也是为什么我一直强调只改必要字段避免在模块里额外插入数据。刷写方式取决于设备。开发板可以直接用 U-Boot 或 UEFI Shell 下的更新命令刷整片固件ARM 笔记本可能要依赖厂商的更新工具服务器主板通常提供 BIOS 更新 Capsule。不管哪种方式我都建议优先使用编程器或者支持校验的刷写工具刷完立即执行一次校验。如果板子有 Boot ROM 恢复机制先确认怎么进恢复模式再接电不要等到黑屏才开始查资料。修改过程中容易被坑的 4 个环节4.1 刷完以后的现象排查很多问题表现相似但原因差得远。我整理了一份自己在实验中会对照的排查表不一定覆盖所有情况但每一条都是实际踩过的。现象可能原因建议排查顺序刷入后 Setup 界面没有任何变化改错了模块或者 hidden 条件由其他模块控制重新核对导入的模块看是否与导出时一致选项出现了但灰色不可选条件由运行时状态决定不只是开局默认值检查 GrayOut 条件涉及的变量是否仍然不满足开机直接黑屏或反复重启固件卷布局被打乱或者模块内部引用了错误偏移回到原始备份重新修改重点查 IFR 是否被破坏设置能修改但重启后自动回到默认varstore 偏移写错修改项没有对应到预期变量对比原 IFR 与补丁后的 IFR 偏移刷写工具提示校验失败固件里带数字签名或完整度校验查询厂商更新机制确认是否允许无签名镜像最坑的是第一种因为你会怀疑自己是不是“白改了”。我遇到过一次原因不是改错模块而是设备上存在两份 Setup 模块一份在 Capsule 更新区一份在运行区我只改了运行区那份固件启动时加载的却是另一份。所以输出修改结果之前一定要先在 UEFITool 里确认你改的模块位置就是 Defaul t 加载列表里的那个。4.2 别碰 varstore 偏移这根线新手最容易犯的错误是找到某个选项的 IFR 条目后顺手把相邻的 offset 字段也改了想着“顺手把默认值也调一下”。在 IFR 里varstore offset 必须和 NVRAM 变量模板精确对应。移动它等于把一个功能的值写到了另一个功能的地址上。表现出来的问题非常隐蔽选项能正常打开系统启动看起来也正常但某个完全不相关的设置开始随机被重置。这种行为我们在固件调试界叫“踩到别人的 egg”。要避免很简单每个选项的改动都只针对它自己 IFR 条目里描述的偏移和目标值。动手前先用原始的 IFR 文件做一次 diff 生成基线改完后再 diff 一次确保除了目标位置没有其它字节被连带改动。gsetupmod 这类工具做修改时也建议一次只递交一个 Patch等刷机验证通过后再做第二个这样万一出问题你能很快定位是哪步引入的。4.3 ARM64 平台对字节序和机器类型的隐性要求ARM64 的 UEFI 模块虽然也是小端但在解析文件格式时比 x86 多一些坑。比如 PE 头里的 Machine 字段0xAA64 并不表示“所有 ARM 都一个样”还存在 AArch32 的模块部分兼容固件里两类模块共存。如果你的工具只支持 AArch64却碰上一个 AArch32 的驱动解析阶段就会出错。还有一点容易被忽略ARM64 UEFI 模块里使用相对寻址的指令比例比 x86 高模块在重定位后的实际运行地址经常要到 PE/COFF 的重定位表里才能算清楚。IFR 数据本身是纯数据不依赖地址所以处理起来还比较直接。可一旦模块里的补丁逻辑涉及到运行时函数调用比如你想注入一段自己的代码让某个选项在执行时动态变化那就必须理解重定位机制。我不会建议新手一开始就往模块里加代码先用静态的 IFR 修改把流程跑通等熟悉了模块布局再考虑动态逻辑。4.4 刷写验证时的安全启动与校验假失败现代 ARM 固件越来越多默认开启 Secure Boot至少也是处于“配置模式”。当固件里存有 PK/KEK 密钥时你修改过的镜像即使完整系统也未必认。表现往往是刷写工具能写入但重启后固件自动回滚或者在开机阶段弹一个“Image authentication failed”的提示。这不是你改坏了而是安全策略拦住了改动后的签名。这种情况下你可以在设备允许的范围内先清空 Secure Boot 密钥或切换到 Setup 模式再刷修改过的镜像。但我要提醒一句涉及 Secure Boot 和 TPM 的改动不要随便做这不光是技术风险的问题。设备一旦锁死恢复难度远高于普通 BIOS 设置除非你明确知道自己在做什么并且有可用编程器否则别碰这一块。想验证你的 IFR 修改是否逻辑正确还有一个更稳妥的办法不用马上刷真实设备先用 EDK2 的模拟环境或者支持 UEFI 的虚拟化方案启动修改后的固件镜像看看 Setup 菜单里是否出现目标选项。虽然 ARM64 的模拟不像 x86 那么顺手但总比刷黑一台真机好。进阶玩法与最后想说的经验5.1 ARM 上值得开的典型项如果你实在想找个练手目标我建议优先挑那些不影响安全又能直观看到效果的设置项。ARM 平台上有几类相对“安全”的选项值得打开用于调试的串口重定向、PCIe ASPM 控制、CPU 频率与功耗策略、部分平台的热管理目标值。这类选项大多在 IFR 里常年存在只是被不同 SKU 的固件策略隐藏而已。打开串口重定向这类功能后你能在系统启动早期看到一个完整的控制台日志对调试最有价值。PCIe ASPM 和电源策略相关项优先级稍低因为改了以后效果不容易立刻感知还可能引入稳定性隐患。我的建议是第一次练手时选择那种“不打开也不影响正常启动、打开了立刻能看到效果”的串口项确认整个流程都走通了再研究别的隐藏项。5.2 我自己动手前会给自己设几条红线改固件做了这些年我给自己定了几条硬性规则分享出来供参考。第一只在专门用来做实验的设备上操作绝不在日常使用的主力机上尝试。哪怕技术再自信固件刷写这件事总存在不可预知的因素。第二原始固件备份和管理员密码记录分开存放保证在设备完全无法开机时还能恢复。第三每次只改一个功能通过一次完整的刷写、验证、重启流程后再进入下一个改动。第四遇到任何带签名的固件区域先查清楚签名机制是否会影响整个镜像不确定就放弃。如果你只是拿到了开发板这类非主流设备也别觉得一定安全。开发板的固件更新链路经常不完整刷坏了可能连 BootROM 都不好救。5.3 把定制流程往后做成体系当你在一台 ARM 设备上成功打开一个隐藏项后会发现这件事真正有价值的地方不是那个选项本身而是你建立了一套“提取固件、解析模块、修改配置、验证固件”的方法。你可以把每次修改的 IFR 偏移、补丁内容写进配置文件存到 Git 里面。厂商日后发了新固件你可以用 diff 快速定位哪些模块发生了变化判断是否需要重新适配。更进一步还可以把整个流程嵌入到简单的 CI 任务里每次有新固件发布就自动解包、执行补丁、输出定制镜像。听上去很硬核做起来其实只在现有流程上加了几个自动化步骤。我以前在折腾 x86 固件时搭过类似流水线放在 ARM 平台上只要工具链支持了架构效果是一致的。有一点需要记住固件自动化构建完仍然必须保留人工验证环节很多问题只会在真机上电后才能暴露。gsetupmod 双架构发布最大的意义在我看来不是多了一个可用的固件修改工具而是把过去被认为“只有 x86 才有的玩法”平移到 ARM 平台。这种平移看起来只是修改了模块解析规则背后却是对两个平台固件设计差异的完整适配。如果你手上正好有 ARM 设备又不排斥拆固件这种脏活累活找个安全的实验机从备份原始固件开始走一遍过程可能会比结果更有意思。