三星BIOS隐藏选项修改:从固件提取到SPI刷回
发布时间:2026/10/26 3:38:41 锦皓数字建站

简介SamsungBIOS 是一份围绕三星笔记本 BIOS/UEFI 下载与排错整理的 Shell 工具合集面向需要找回旧版固件、排查安全启动及更新失败问题的进阶用户和维修人员。由于三星长期隐藏公开下载渠道、SW Update 自动更新又存在失败风险这类资料可用于辅助定位型号对应的 BIOS 条目并梳理恢复思路。压缩包共 13 个文件约 1.04MB以 txt 清单与说明、md 文档、exe 可执行工具为主并配有 sh 与 cmd 脚本及 xml 数据文本和 XML 便于检索版本与缓存记录脚本和可执行程序侧重批量获取、筛选与更新辅助结构紧凑适合按用途取用。已有 541 人浏览学习。其价值在于把零散的 BIOS 清单、缓存数据与查找/处理工具集中归档读者可据此比对不同时期的版本线索、理解固件列表的整理流程并在官方渠道缺失时获得可操作的排错参考尤其适合具备命令行基础和固件维护经验的人群。1. 三星 BIOS 工具能改什么、不能改什么从一台风扇狂转的过保机器说起一台用了三年的三星笔记本开机风扇满速、电池充到 80% 就停、电源管理里能调的项少得可怜进系统看 EC 数据一切正常。这种时候能救命的往往不是重装系统而是把固件里被厂商隐藏的那几个 Setup 选项放出来。所谓「三星 BIOS 工具」实操中指的是一个流程而非某一个软件从官方更新包里把固件提取出来、用 UEFI 解析工具读懂 Setup 表单、把隐藏项对应的变量改掉出问题时再用编程器把芯片刷回去。它适合两类人手上有一台过保机器、想把最后一点可配置性榨出来的用户以及需要批量维护同型号机器的运维。不适合指望点两下就完事的人——这条链路上每一步都能把机器变成砖头我见过太多人卡在第一步拆包就放弃也见过人改完变量重启直接黑屏。先把边界说清楚能改的是厂商用 Setup 表单暴露过、只是被隐藏的选项改不了的是被写进微码或硬件里的东西。2. 从官方更新包到裸镜像三星 BIOS 提取的三步与四个校验点拿到手的第一个东西通常是一个几十 MB 的.exe双击能跑、进度条走完提示重启但你没法直接把它塞进解析器。真正要的是里面那个 8MB、16MB 或 32MB 的裸镜像。这一步做不干净后面所有分析都是在错误的输入上做无用功——我在这一步踩过的坑比后面加起来都多。2.1 先认固件血统刷写器名字比机型型号更可靠同一家厂商不同年份、不同产品线的机器固件供应商可能完全不一样靠型号去猜是最容易翻车的方式。判断血统有个几乎不会错的办法看更新包里带的刷写器叫什么名字。# 把更新包解开一层看里面有哪些可执行文件和驱动 mkdir -p work/step1 7z x BIOS_Update.exe -owork/step1 -y /dev/null find work/step1 -maxdepth 3 -type f -printf %f\n | sort -u | head -50这段命令只做一件事把自解压壳剥一层列出内部文件名。参数-y是全部覆盖-o指定输出目录。真正要看的是文件名里的关键字内部文件名特征对应固件血统后续处理方式AFUWIN、AFUDOS、AFUEFIAMI Aptio 系列IFR 里变量通常叫Setup用 AMI 的结构规则解析H2OFFT、H2OSDE、InsydeFlashInsyde H2O 系列变量名常见Setup或SetupVolatile注意双份Phlash、WinPhlash、PHLASH16较早的 Phoenix 系结构老、工具链也老很多现成解析器不支持只有一堆.dll和一个Flashtool.exe厂商封装过的自有刷写器需要继续往下拆通常镜像藏在资源段或同目录.bin里血统决定了三件事能不能走官方刷新通道回滚、用哪种方式提取变量、以及 Setup 表的 VarStore 命名习惯。这三件事任何一件判断错后面都会白干。2.2 用 7z 与 binwalk 把更新包拆到 8/16/32MB 的裸镜像遇到 Inno Setup 或 InstallShield 封装的包7z只能剥到一半剩下的要靠专门的解包器或者「先安装一遍再去临时目录捞」这个笨办法。笨办法往往最有效安装程序在真正写芯片之前一定会把镜像释放到磁盘上。# 路线 AInno Setup 壳用 innoextract 直接展开 innoextract -d work/inno BIOS_Update.exe 2/dev/null || true # 路线 B跑一遍安装器但在它提示重启以完成时不要重启去临时目录捞 # Linux 下可先用 wine 跑Windows 下直接看 %TEMP% 和 %ProgramData% find work /tmp -type f \ \( -iname *.fd -o -iname *.bin -o -iname *.rom -o -iname *.cap -o -iname *.wph \) \ -printf %s\t%p\n 2/dev/null | sort -n | tail -20-printf %s\t%p\n把文件大小放在第一列sort -n按大小排序。BIOS 镜像的大小几乎总是 2 的整数次幂——4MB、8MB、16MB、32MB最后几行里那个大小规整的文件基本就是目标。如果捞出来的是 1MB 出头的小文件多半是 EC 固件或者某个子区域别急着当成完整镜像用。捞到候选文件后用binwalk看内部结构# -y 只输出指定签名避免刷屏这里关注固件卷和压缩段 binwalk -y firmware volume -y lzma -y efi work/step1/image.bin | head -30如果输出里能连续看到多个固件卷说明是完整镜像如果只有一个孤零零的卷、其余全是 0xFF那大概率是裁剪过的升级补丁刷进去会缺模块。这一步的判据不是「有没有卷」而是「卷的分布是否覆盖整个文件」。2.3 一段脚本判断拿到的是完整 SPI 镜像还是升级补丁肉眼看不靠谱写个脚本把四个校验点一次跑完。这四个点分别是文件大小是否为 2 的幂、固件卷签名_FVH的数量、描述符区签名是否存在、尾部是否为擦除态填充。#!/usr/bin/env python3 # check_image.py —— 判断拆出来的是完整 SPI 镜像还是升级补丁 import sys, struct, hashlib from pathlib import Path FV_SIG b_FVH # 固件卷签名位于卷头 0x28 DESC_SIG 0x0FF0A55A # 描述符区常见签名位于镜像偏移 0x10 def find_fv(data: bytes): offs, i [], data.find(FV_SIG) while i ! -1: base i - 0x28 # 卷头起点 签名位置 - 0x28 if base 0 and data[base:base 16] b\x00 * 16: # 卷头前 16 字节是 ZeroVector offs.append(base) i data.find(FV_SIG, i 1) return offs def main(path: str): data Path(path).read_bytes() size len(data) print(f文件 : {path}) print(f大小 : {size} 字节 / {size / 1024 / 1024:.1f} MB) print(f是否 2 的幂: {size (size - 1) 0}) print(fMD5 : {hashlib.md5(data).hexdigest()}) desc struct.unpack(I, data[0x10:0x14])[0] if size 0x14 else 0 print(f描述符签名: 0x{desc:08X} {命中 if desc DESC_SIG else 未命中}) fvs find_fv(data) print(f固件卷数量: {len(fvs)}) for o in fvs[:8]: print(f FV 0x{o:08X}) tail data[-0x1000:] print(f尾部擦除态: {tail.count(0xFF) / len(tail):.1%} 为 0xFF) if __name__ __main__: main(sys.argv[1])逻辑很直白find_fv扫全文件找_FVH再用前 16 字节全零这个条件排除误命中struct.unpack(I, ...)按小端读 4 字节描述符签名尾部统计 0xFF 占比用来判断这块区域是不是从没被写过。参数上唯一要注意的是data[base:base16]这个校验——某些厂商会把 ZeroVector 填成别的值如果发现卷数明显偏少可以临时把这个条件放宽成「前 16 字节里 0x00 占多数」再跑一遍。判定标准我一般这么用大小是 2 的幂、固件卷数量 ≥ 3、尾部 0xFF 占比 90%三条同时满足才当成完整镜像往下走只满足一两条的回去重新找文件别硬着头皮往下解析。备份这一步也别省把原始更新包和拆出来的镜像各存一份到别处这是后面唯一的后悔药。3. 锁定 Setup 表单用 UEFI 解析工具把隐藏选项换算成字节偏移有了干净镜像下一步是找到「那个想改的选项在哪个字节上」。这一步的难点不在于工具而在于理解三层结构之间的对应关系固件卷里装着 FFS 文件某个 FFS 文件是 Setup 驱动Setup 驱动里带着 HII 表单表单里的每一个 Question 都指向一个 VarStore 加一个偏移。搞不清这层嵌套就会一直在 GUID 里打转。3.1 固件卷、FFS 文件与 HII 表单的三层对应关系从外往里拆一个 SPI 镜像里通常有若干个固件卷Firmware Volume每个卷内是一串按 8 字节对齐的 FFS 文件每个 FFS 文件有自己的 16 字节 GUID 和一个类型字段其中类型为0x07的是 DXE 驱动。Setup 就是其中一个 DXE 驱动它编译进去的 HII 资源包里描述了所有可见和隐藏的选项。真正决定「改哪个字节」的是 VarStore。一个 VarStore 对应固件里的一份变量存储可能是 NV 变量掉电保存也可能是 volatile 的临时区。IFR 提取器把 HII 资源包翻译成人类能读的表单文本后每个 Question 下面会写清楚VarStore: 0x1, VarOffset: 0x5A3, Size: 1这三个数就是全部答案找到 VarStore 0x1 对应的变量名然后往那个变量的第0x5A3字节写值。要注意一个反直觉的点表单里看到的选项顺序和变量里的字节顺序完全无关。选项排在前面的不一定偏移小某个选项在一台机器上是0x5A3换一个 BIOS 版本可能就漂到0x5B7。所以任何抄来的偏移都只能用一次升级过 BIOS 就必须重新导一遍 IFR。3.2 遍历固件卷找 Setup 模块GUID 不可信字符串才可信网上流传的各种「Setup 模块 GUID」基本都是针对某一台机器的直接拿去搜大概率搜不到。可靠的定位办法是内容特征Setup 模块通常是体积最大的几个 DXE 驱动之一并且内部一定包含大量选项名字符串。#!/usr/bin/env python3 # ffs_scan.py —— 遍历固件卷内的 FFS 文件按大小排序打印 import struct, sys from pathlib import Path def uefi_guid(b: bytes) - str: # UEFI GUID 的前三个字段是小端存储后两个字段是字节序 d1, d2, d3 struct.unpack(IHH, b[:8]) return f{d1:08x}-{d2:04x}-{d3:04x}-{b[8:10].hex()}-{b[10:16].hex()} def find_fv(data: bytes): offs, i [], data.find(b_FVH) while i ! -1: base i - 0x28 if base 0 and data[base:base 16] b\x00 * 16: offs.append(base) i data.find(b_FVH, i 1) return offs def walk(data: bytes, fv_off: int): fv_len struct.unpack(Q, data[fv_off 0x20:fv_off 0x28])[0] hdr_len struct.unpack(H, data[fv_off 0x30:fv_off 0x32])[0] off, end fv_off hdr_len, min(fv_off fv_len, len(data)) while off 24 end: hdr data[off:off 24] if hdr b\xff * 24: break guid uefi_guid(hdr[:16]) ftype hdr[18] # 0x07 DXE driver size int.from_bytes(hdr[20:23], little) if size 24 or off size end: break yield off, guid, ftype, size off (size 7) ~7 # FFS 文件 8 字节对齐 def main(path: str): data Path(path).read_bytes() rows [] for fv in find_fv(data): for off, guid, ftype, size in walk(data, fv): rows.append((size, off, guid, ftype)) rows.sort(reverseTrue) print(f{大小:8} {偏移:10} 类型 GUID) for size, off, guid, ftype in rows[:25]: print(f{size:8} 0x{off:08X} 0x{ftype:02X} {guid}) if __name__ __main__: main(sys.argv[1])walk里几个关键取值fv_off 0x20是卷长度字段8 字节小端fv_off 0x30是卷头长度2 字节FFS 文件头固定 24 字节其中第 18 字节是类型、第 20 到 22 字节是 3 字节小端长度。(size 7) ~7是做 8 字节向上对齐少写这一步会在第二个文件处就错位然后一路解出乱码。跑完之后按大小排序看前几名逐个用strings验证# 把候选模块切出来看里面有没有大量选项名字符串 dd ifimage.bin ofcand.bin bs1 skip$((0x1A0000)) count$((0x40000)) statusnone strings -n 5 cand.bin | grep -icE enable|disable|battery|charge|fan|thermalskip和count换成上一步打印的偏移和大小。命中数量在几十上百的基本就是 Setup只有个位数的是别的驱动。这一步比看 GUID 靠谱得多也是我现在的固定流程。3.3 从 IFR 的 VarOffset 到 efivarfs 里的真实字节把 Setup 模块单独切出来后丢给 IFR 提取器UEFITool 那类解析器通常自带或者用 Universal IFR Extractor 一类的独立工具会得到一份纯文本。里面长这样Form Set: Advanced VarStore: VarStoreId 0x1, Name: Setup, Guid: 8f2c1a44-...-9b3e Question: Battery Charge Limit VarStore: 0x1, VarOffset: 0x5A3, Size: 1 One Of Option: Disabled, Value: 0x0 One Of Option: 80 Percent, Value: 0x1 One Of Option: 100 Percent, Value: 0x2读法是VarStoreId 0x1对应变量名SetupVarOffset 0x5A3是这个选项在这份变量里的字节偏移Size 1表示一字节Value是可选值。所以「把电池充电上限改成 80%」这件事本质就是把名为Setup的变量的第0x5A3字节从0x00写成0x01。有两个细节必须提前确认否则会改出问题Size 2或Size 4的选项字节序是小端写入时要按长度拼字节不能只写第一个字节。同一个表单里可能存在多个 VarStoreVarOffset是相对于所属 VarStore 的不要拿 VarStore 0x2 的偏移去改名为Setup的变量。提示IFR 里的VarOffset是相对变量数据区的偏移。在 efivarfs 里读到的变量文件前 4 个字节是属性头真正的数据从第 5 个字节开始换算时别把这 4 个字节算进去。4. 改一个字节并活着回来efivarfs 写变量与 SPI 编程器刷回到这一步手上应该有三样东西一份确认过血统的完整镜像、一个 Setup 模块的 IFR 文本、一个明确的「变量名 偏移 目标值」。接下来分两条路走——能在系统里改成功的就别动硬件系统里改不动的再上编程器。4.1 备份先做三份编程器读三次与逐字节比对只要涉及 SPI第一件事永远是读出来存好。我的习惯是同一颗芯片连读三次三份互相md5sum对比两份一致才认为读数可信。听起来啰嗦但夹子接触不良时读出来的数据是「看起来正常但每隔几百字节错一个位」的你不比对根本发现不了。# 用 flashrom 一类开源刷写工具读取编程器型号按实际接的选 for i in 1 2 3; do flashrom -p ch341a_spi -r dump_$i.bin /dev/null 21 md5sum dump_$i.bin done-p ch341a_spi指定编程器类型CH341A 那类 USB 编程器-r是读出到文件。三次结果里出现两个相同的 MD5就以那份为准三次全不一样别抱侥幸先解决接触问题。顺手把镜像按区域切开存好后面回滚时能只回滚 BIOS 区不用整片覆盖# 常见的 16MB 布局描述符 4KB ME 区 BIOS 区具体边界以自己 dump 为准 dd ifdump_1.bin ofdesc.bin bs4096 count1 statusnone dd ifdump_1.bin ofregion_bios.bin bs1M skip5 count11 statusnone这些skip/count只是示意实际边界要从描述符里的区域表读出来不同机型差得很远。切完之后至少验证一下 BIOS 区里能扫到_FVH扫不到说明边界猜错了。4.2 Linux 下直接改 Setup 变量的完整命令序列在支持 efivarfs 的系统上Setup 变量就是/sys/firmware/efi/efivars/下的一个文件文件名是变量名-大写的GUID。先确认它存在# 挂载点正常的话这一步能直接列出 Setup 变量 mount -t efivarfs none /sys/firmware/efi/efivars 2/dev/null || true ls /sys/firmware/efi/efivars | grep -i ^Setup- # 前 4 字节是属性后面才是数据区 hexdump -C /sys/firmware/efi/efivars/Setup-8f2c1a44-XXXX-XXXX-XXXX-XXXXXXXX9b3e | head -5hexdump那一行的作用是确认变量长度和你预期的偏移范围对得上——如果变量总长只有 200 字节而你要改0x5A3那说明找错变量了立刻停手。确认无误后改一个字节的标准流程是这样的GUID8f2c1a44-XXXX-XXXX-XXXX-XXXXXXXX9b3e VAR/sys/firmware/efi/efivars/Setup-$GUID cp $VAR ./Setup.bak # 备份含 4 字节属性头的完整副本 chattr -i $VAR 2/dev/null || true # 部分内核会把变量文件设为 immutable python3 patch_var.py ./Setup.bak 0x5A3 0x01 cat ./Setup.new $VAR # 用 cat 覆写不要用 直接截断patch_var.py负责偏移换算和写入#!/usr/bin/env python3 # patch_var.py —— 在变量副本的指定偏移写入指定值输出 Setup.new import sys from pathlib import Path src, off_s, val_s sys.argv[1], sys.argv[2], sys.argv[3] off, val int(off_s, 0), int(val_s, 0) # 支持 0x 前缀 raw bytearray(Path(src).read_bytes()) data raw[4:] # 跳过 4 字节属性头 if off len(data): sys.exit(f偏移 0x{off:X} 超出变量长度 {len(data)}先确认变量名) old data[off] data[off] val raw[4:] data Path(Setup.new).write_bytes(bytes(raw)) print(f0x{off:X}: 0x{old:02X} - 0x{val:02X}输出 Setup.new)参数说明第一个参数是备份文件路径第二个是偏移0x开头按十六进制解析第三个是目标值。这里刻意保留了属性头不动因为属性决定了变量是 NV、BS 还是 RT改错属性会让固件在下次启动时直接忽略这个变量。写完立刻读回来核对一次值没变就说明写进去了重启后如果值又回到原样那基本可以判定这个变量受保护该走 SPI 路线了。注意不要在电池电量低或者外接电源不稳的时候做这一步。变量写入过程中掉电最轻的结果是这项设置回到默认最重的是变量区校验失败、每次开机都弹 CMOS 错误。4.3 夹子还是拆焊把改过的镜像写回 SPI 芯片系统里改不动的情况就把改好的镜像整个写回去。这一步的第一个决策是「用 SOIC-8 夹子在线夹」还是「拆下来焊到转接板」。我的经验是8 脚小容量芯片、板子上没有并联的强拉低器件夹子能成16 脚、或者夹上之后读出来三次全不一致的老老实实拆焊。在线夹的操作顺序# 1) 彻底断电拔适配器、拔电池等 30 秒让电容放完 # 2) 接好夹具先读一次确认能通信 flashrom -p ch341a_spi -r check.bin # 3) 擦除 - 写入 - 校验三步分开跑任何一步报错就停 flashrom -p ch341a_spi -E flashrom -p ch341a_spi -w mod.bin flashrom -p ch341a_spi -v mod.bin-E是整片擦除-w是写入-v是校验。分三步跑的好处是出错时能立刻定位是擦除失败、写入失败还是校验失败混在一起跑只会得到一个模糊的报错。有两个电压问题必须提前确认否则会烧芯片老机器的 SPI 芯片多是 3.3V近几年不少机型换成了 1.8V 的芯片用 3.3V 编程器直连会直接击穿。不确定就拿万用表量一下 VCC 引脚或者看芯片丝印查数据手册。夹具供电时笔记本的电池和适配器必须都断开两路电源同时供会打架。写完之后第一次开机可能特别慢屏幕黑十几秒才出 Logo中间还可能自动重启两三次。这是管理引擎区域在做初始化属于正常现象。如果超过两分钟还没画面直接断电回滚到你那份三读一致的原始 dump别反复试。5. 三星 BIOS 修改的五个翻车现场现象、原因与解法这一章是我自己踩过的坑按「现象 → 原因 → 解决」记下来。每一条都真实发生过而且都不止一次。5.1 改完变量重启选项自己变回去了现象efivarfs里写入成功读回来也是新值重启进系统再看字节又变回0x00设置界面里选项还是灰的。原因三种可能。一是改错了变量有的机型同时存在Setup和SetupVolatile后者掉电即失改了等于没改。二是这个选项在 POST 阶段会被固件从默认值模块重新计算变量只是缓存。三是变量带认证属性写入被接受但校验不过下次启动被回滚。解决先在 IFR 文本里确认 Question 所属的 VarStore 名字再看这个 VarStore 的类型是不是 NV。确认无误后改用 SPI 路线直接改镜像里 Setup 驱动的默认值区域让固件每次启动都读到新默认。判断是不是「被重算」有个简单办法改完之后不要重启直接在当前系统里读一次如果当前值是对的、重启后变了就是 POST 覆盖只能走镜像。5.2 刷完不开机风扇转一下就停现象编程器写入和校验都通过装回去按电源键风扇转一两秒就停屏幕全程黑重复几次都一样。原因九成是镜像不匹配。常见的是把别的机型或者别的 BIOS 版本的镜像写了进去其次是只覆盖了 BIOS 区但描述符区或管理引擎区和这个版本的固件对不上固件在早期初始化阶段就挂了。还有一种少见情况写入时没做整片擦除旧数据残留在未覆盖的扇区里校验用的是新数据、实际执行读到的是混合内容。解决立刻回滚自己那份三读一致的原始 dump整片写入不要只写区域。回滚后能开机再逐区比对先把描述符区、管理引擎区保持原样只替换 BIOS 区一次只动一个区。这个「逐区替换」的排查方法很笨但它是唯一能把问题锁定到具体区域的办法。5.3 编程器读三份 dump三份都不一样现象连读三次三个 MD5 全不同但每份都能被解析器识别出固件卷看起来「像是好的」。原因夹子接触电阻大、线太长、编程器供电不足或者芯片在板上被其它器件拉低。这种错误最阴险的地方在于它不报错只是默默读到错误数据你拿这份数据去改、去写回等于把错误固化。解决按代价从小到大试——缩短杜邦线到 10cm 以内、降低 SPI 时钟、给编程器换独立供电、最后拆焊。拆焊之后如果三份一致了就说明问题在板上并联器件。这一步没有捷径我试过用「读两次比对差异位置」的方式蒙混过去结果差异正好落在我后来要改的那个区域白折腾一晚上。5.4 写变量报 Input/output error 或 Permission denied现象cat ./Setup.new $VAR报Operation not permitted或者Input/output error文件根本没写进去。原因Permission denied多半是变量文件带了 immutable 属性或者内核开了锁定模式Input/output error通常是固件层拒绝了这次写入——变量是认证变量、当前不在可写状态或者长度和原有变量对不上。解决先chattr -i去掉不可变位再试还不行就看dmesg尾部固件拒绝一般会留下痕迹。长度问题好排查写入前后的文件大小必须完全一致多一个字节少一个字节都会被拒。如果确认是认证变量系统内这条路就堵死了直接转 SPI。5.5 镜像解析正常刷进去却校验失败现象解析器能正常列出所有固件卷IFR 也导得出来但flashrom -v校验阶段报错或者写到 99% 卡住。原因镜像长度和芯片容量不匹配是最常见的一条——把 16MB 的镜像往 8MB 的芯片上写工具不会拦你。其次是镜像尾部带了厂商附加的签名块或填充块实际有效长度比文件长度短。还有一种是芯片有写保护位状态寄存器里的 BP 位前几个扇区写不进去。解决先用编程器读出芯片的真实容量把镜像按容量截断或补齐补0xFF不要补0x00。写保护的话在写入前清一下状态寄存器。这一步做完还是失败就换一颗同型号的新芯片试我遇到过一次是芯片本身某几个扇区已经写坏换了本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。