☰
网卡MAC地址硬刷实战:NVM写入、校验和与批量回滚
2026/10/23 17:50:01 网站建设 项目流程

简介:面向需要在网卡层面修改物理地址的用户,这套工具包围绕 MAC 地址硬刷展开,适用于黑群晖洗白、虚拟机网卡标识统一、硬件故障后地址修复等场景,涉及刷写工具调用、参数配置与芯片适配,更适合具备网络与硬件基础的中高级用户动手实践。压缩包共收录 197 个文件,约 4.52MB,以 bin、c、mac、cfg、exe、txt、bat 为主:exe 与 bat 承担刷写执行与批处理调用,cfg、ini 存放参数,c、h 源码与 bin、eep 固件数据对应不同芯片方案的适配逻辑,另配 docx 说明与 txt 记录操作要点。已有 2552 人学习下载。读者可获取覆盖多种网卡芯片型号的硬刷方案、可复用的执行脚本与配置模板,以及流程与风险提示文档,便于动手前理清原理、判断网卡是否在支持范围内,并对照资料完成地址读取、写入与验证的链路。

1. MAC 地址硬刷:改注册表不算本事,写进 NVM 才算数

某实验室处理一批二手网卡,装机后系统里 MAC 全是出厂默认或者干脆重复,用ip link set改完当场能用,重启或者modprobe -r再加载,立刻打回原形。很多人以为是自己命令写错了,其实是改的地方不对——改的是内核里那张内存表,掉电就没了。MAC 地址硬刷要动的是网卡上的非易失存储(NVM/EEPROM/efuse),把 6 个字节真正落到芯片里,这样不管换系统、换驱动、还是把卡插到另一台机器上,读出来的都是你写进去的那个值。

这套工具解决的就是“软件层改了不生效、不持久”的问题,适合做设备批次管理、实验室网络隔离测试、老网卡回收复用的从业者。它的边界也很清楚:不是所有网卡都能写,写了不备份基本等于赌运气。

2. 三层改动分清楚:软改、NVM 写入与厂商工具各自的边界

拿到一个 MAC 地址硬刷工具,第一件事不是急着插卡开刷,而是先搞清楚它到底动的是哪一层。市面上挂着“MAC 修改”名头的工具,实际落点差得很远:有的只是帮你改注册表,有的是往网卡的 MAC 寄存器里写一个值,还有的才是真正读写 NVM。三者的持久性、风险、可回滚性完全不在一个量级,混着用就是各种玄学问题的源头。

2.1 操作系统层的软改只动内存里的过滤表

Linux 下最常见的ip link set dev eth0 address 02:11:22:33:44:55,走的路径是内核网络子系统调用驱动的ndo_set_mac_address回调。这个回调在多数驱动里做两件事:把 6 个字节拷进net_device->dev_addr,再写进网卡芯片的接收过滤寄存器。前者在内存里,后者在寄存器里,两者都经不起一次完整的断电。Windows 那边改注册表NetworkAddress字符串值再禁用启用网卡,本质也一样,驱动初始化时把字符串解析成字节数组写进寄存器。

这类改动为什么“看起来成功了”?因为寄存器确实变了,收包过滤立即按新地址工作。但驱动 probe 的时候有一个固定动作:从 NVM 里把出厂 MAC 读出来,覆盖掉驱动结构体里的默认值。所以你重启、重载驱动、甚至只是让网卡进了一次 D3 电源状态再回来,值就被刷回去了。这不是 bug,是设计如此。

软改的价值在于快和零风险,适合临时联调、容器网络命名空间里造多个虚拟接口。但它永远解决不了“这台机器换系统后 MAC 变了导致授权失效”这种问题,因为授权校验读的往往就是驱动从 NVM 里拿到的那个值。判断你手上的工具属于哪一层,最简单的办法:改完之后把机器电源线拔掉等 30 秒再上电,如果值回滚了,那就是软改。

2.2 NVM 与 efuse 才是硬刷的真实落点

NVM 是一块挂在网卡控制器上的非易失存储,常见形态有三种:SPI NOR flash(容量几百 KB 到几 MB,除了 MAC 还放 PXE Option ROM、温度校准、厂商配置)、串行 EEPROM(老千兆卡上常见的 93 系列小容量器件,128 到 512 字节)、以及 efuse(一次性可编程,只能把 0 烧成 1,烧错无法恢复)。这三种的写入方式和风险完全不同,工具在动手前必须能识别出来,否则就是拿卡当试验品。

镜像布局也不是随便放。以常见的一款 PCIe 千兆控制器为例,NVM 前 0x00 到 0x0F 通常是标识和版本,MAC 地址区可能在 0x00、0x100 或更靠后的位置,尾部或者固定偏移处有一个 16 位校验和字段。控制器上电后会做一次自检:把整块镜像按 16 位字累加,结果必须等于一个约定的魔数(有的要求 0xFFFF,有的要求 0xBABA,具体看驱动源码里的宏定义)。校验不过,控制器就把自己标成“配置无效”,驱动 probe 阶段直接报错退出,网卡在系统里就消失了。

还有一个容易翻车的细节:MAC 地址在 NVM 里的字节序不一定是从低到高。有些控制器按55:44:33:22:11:02这种逆序存放,你照着界面上的顺序写进去,读出来就是反的。我一般会先读出原始镜像,在二进制里搜一下当前 MAC 的正序和逆序两种字节串,哪个能搜到就按哪个来,这一步比查文档快得多。

2.3 工具选型:开源 ethtool 路线与厂商 DOS/EFI 路线怎么选

真正能干硬刷活的工具不多,大致分四条路,各有各的适用面和坑。

路线典型手段覆盖范围主要限制
内核驱动接口ethtool -E写、ethtool -e读驱动实现了set_eeprom的型号新版本驱动大量移除了写回调,直接返回不支持
厂商 DOS/EFI 工具做成启动盘,在纯 DOS 或 UEFI Shell 下运行厂商自家芯片覆盖最全需要单独准备启动介质,工具不跨厂商通用
直接操作 SPIflashrom一类工具读写 SPI flashflash 未被控制器隐藏的板型多数网卡的 flash 被控制器接管,外部访问不到
自写脚本 + 驱动调试接口通过ethtool -e/-E或 debugfs 分块读写需要自己算偏移和校验偏移和算法要自己摸,错一个字节就砖

选型逻辑其实很简单:先确认驱动还认不认写操作,认就用ethtool -E,一条命令的事;不认就退到厂商的离线工具,做启动盘进 DOS;如果连厂商工具都没有,那就只剩 SPI 直读这一条路,但这条路要求你能物理接触到 flash 芯片,且控制器没有把它锁住。

我一般会按“风险从低到高”排:先在一张同型号的报废卡上把全流程跑通,确认偏移、字节序、校验算法三件事都对,再上真卡。批量场景下这一步尤其不能省,因为不同批次的卡可能用的是不同的 NVM 版本,布局有细微差别。

提示:efuse 类型的存储没有“再写一次”的机会,动手前务必确认型号。用ethtool -i看到的驱动名和固件版本只能作为线索,最终判定要看控制器手册或者拆开看丝印。

3. 动手之前:把网卡型号、NVM 布局和校验算法摸清楚

硬刷这件事,八成的失败发生在按下写入命令之前。真正花时间的不是写,是确认这块卡能不能写、镜像多大、MAC 在哪个偏移、校验怎么算。这一章的四步做完,后面写的时候心里才有底。

3.1 用 lspci 和 ethtool 定位控制器与驱动

先确定你面对的是哪颗控制器、绑的哪个驱动。这两条信息决定了后面所有的工具选择。

# 列出所有以太网控制器,-nn 显示厂商和设备 ID lspci -nn | grep -i ethernet # 假设网卡在 03:00.0,看它绑的驱动、固件版本和 PCI 地址 ethtool -i eth0 # 读一段 PCI 配置空间,确认 BAR 映射和是否被隐藏 lspci -s 03:00.0 -xxx | head -20

lspci -nn输出的方括号里是厂商ID:设备ID,这个组合是查手册的唯一钥匙。ethtool -i给出的driver字段告诉你内核用的是哪个模块,firmware-version有时候能反推出 NVM 的版本号——如果同型号卡之间这个值不一样,说明它们 NVM 布局可能不同,不能套用同一份偏移表。

lspci -xxx打印的是配置空间前 256 字节,重点看 BAR0 的基地址和 Command 寄存器里的 Memory Space 使能位。这一步主要是排查“卡在但驱动不认”的情况:如果设备 ID 读出来是ffff,说明卡根本没被枚举到,问题在硬件不在 NVM。

3.2 读出完整 NVM 镜像并确认长度

读之前先把接口 down 掉,避免读的过程中有寄存器访问冲突。

# 关闭接口,防止读取时被上层干扰 ip link set dev eth0 down # 读 512 字节原始数据到文件,raw on 表示输出二进制而不是十六进制文本 ethtool -e eth0 raw on offset 0 length 512 > nvm_orig.bin # 确认文件大小,正常应该是 512 的整数倍 ls -l nvm_orig.bin # 看一眼头部,判断是不是全 0xFF(空片)或者全 0x00(读失败) xxd -l 64 nvm_orig.bin

offset和length两个参数要看驱动的实现。有的驱动只允许按 4 字节对齐读,有的限制单次最多读 128 字节,还有的只暴露前 256 字节。如果一次读不满,就分段读再拼接,别指望一条命令拿到全镜像。

读出来的文件如果全是0xFF,两种可能:这块卡用的是 efuse 而不是 EEPROM,驱动读不到;或者驱动本身没实现读回调,返回的是缓冲区初始值。用xxd看头部有没有合理的版本号、配置字,基本就能区分。

3.3 定位 MAC 偏移与校验和字段

这一步是整个流程里最需要耐心的部分。三种定位手段,从快到慢依次试。

第一种,已知当前 MAC 的情况下直接搜字节串。当前 MAC 用ip link show eth0看,注意要区分正序和逆序。

# 正序搜索,比如当前 MAC 是 00:1b:21:aa:bb:cc xxd -p nvm_orig.bin | tr -d '\n' | grep -bo '001b21aabbcc' # 逆序搜索 xxd -p nvm_orig.bin | tr -d '\n' | grep -bo 'ccbbaa211b00'

grep -bo输出的数字是十六进制串里的字符偏移,除以 2 才是字节偏移。命中位置一般就是 MAC 字段的起始地址。如果两种顺序都搜不到,说明这块卡的 MAC 不在 NVM 里,可能是从控制器内部的一次性存储读的,硬刷这条路走不通。

第二种,找校验和字段。校验和通常在镜像的固定位置,比如最后两个字节,或者紧跟在版本号之后。把镜像按 16 位字全加一遍,看看当前总和与常见魔数的差值,差值就是校验和字段该有的值,再反查这个值在镜像里的位置。这个办法对不上就说明校验算法不是简单累加,得去翻驱动源码。

第三种,翻驱动源码。drivers/net/ethernet/下面找对应驱动,搜EEPROM、NVM、mac_addr这些关键词,偏移宏和校验函数通常写得很直白。这是最靠谱的办法,缺点是慢。

注意:定位到偏移后先别写,用十六进制编辑器手工改一份镜像出来,跟原镜像做cmp -l对比,确认只有预期的那几个字节变了。

4. 从 dump 到回写:一次完整的硬刷流程拆解

前面把地形摸清了,这一章就是照着走。整个流程分备份、改字节、回写、冷启动验证四段,每一段都有对应的检查点,任何一段没过就不要往下走。

4.1 备份:把原厂镜像和校验值一起落盘

备份不是简单 cp 一份,要把元信息一起存下来,否则出问题的时候你连“原来长什么样”都说不清。

#!/usr/bin/env bash # 备份脚本:把网卡信息、原始 NVM 镜像、校验值一起归档 set -euo pipefail DEV="${1:?用法: nvm_backup.sh <网卡名,如 eth0>}" STAMP="$(date +%Y%m%d_%H%M%S)" PREFIX="nvm_${DEV}_${STAMP}" # 1. 保存驱动与设备信息,出问题时用来比对 ethtool -i "$DEV" > "${PREFIX}.info.txt" lspci -nn | grep -i ethernet >> "${PREFIX}.info.txt" # 2. 关接口后读原始镜像 ip link set dev "$DEV" down ethtool -e "$DEV" raw on offset 0 length 512 > "${PREFIX}.bin" # 3. 记录哈希,回滚后要用它验证是否完全恢复 sha256sum "${PREFIX}.bin" | tee "${PREFIX}.sha256" echo "备份完成: ${PREFIX}.bin"

脚本里的set -euo pipefail是关键,任何一步失败立即中止,避免在接口没关的情况下继续读。ethtool -i的输出一定要存,因为驱动版本会影响-E的行为,出问题时这是唯一能回溯的线索。

备份文件至少存两份,一份在本机,一份在别的介质上。血泪经验是:刷坏了之后你想从同一块卡上再读一次原镜像,已经读不出来了。

4.2 改字节:生成新镜像并重算校验和

改镜像这一步,核心是“改完必须重算校验和”,漏了这一步,写进去的卡大概率直接不认。

import struct import sys # 这两个常量必须按你的控制器实际情况改 MAC_OFFSET = 0x100 # MAC 字段在镜像中的字节偏移 CSUM_OFFSET = 0x1FE # 校验和字段偏移,通常在镜像末尾 CSUM_TARGET = 0xBABA # 校验魔数,常见取值 0xFFFF 或 0xBABA def set_mac(buf: bytearray, offset: int, mac: str, reverse: bool = False) -> None: """把 MAC 字符串写进镜像,reverse=True 表示按逆序存放""" parts = [int(x, 16) for x in mac.split(':')] if len(parts) != 6 or any(p < 0 or p > 255 for p in parts): raise ValueError(f"MAC 格式不合法: {mac}") raw = bytes(parts) buf[offset:offset + 6] = raw[::-1] if reverse else raw def fix_checksum(buf: bytearray, csum_off: int, target: int) -> int: """先清零校验字段,再按小端 16 位字累加,回填差值""" if len(buf) % 2 != 0: raise ValueError("镜像长度必须是偶数") buf[csum_off:csum_off + 2] = b'\x00\x00' total = 0 for i in range(0, len(buf), 2): total += struct.unpack_from('<H', buf, i)[0] csum = (target - (total & 0xFFFF)) & 0xFFFF struct.pack_into('<H', buf, csum_off, csum) return csum if __name__ == '__main__': src, dst, mac = sys.argv[1], sys.argv[2], sys.argv[3] with open(src, 'rb') as f: data = bytearray(f.read()) set_mac(data, MAC_OFFSET, mac, reverse=False) # 字节序按第 3 章的探测结果决定 value = fix_checksum(data, CSUM_OFFSET, CSUM_TARGET) with open(dst, 'wb') as f: f.write(data) print(f"新镜像已生成: {dst}, 校验和写入 0x{value:04X}")

set_mac里的reverse参数就是前面说的字节序问题,第一次用某型号卡时,建议两种都生成一份,比对哪一份读回来是对的。fix_checksum先把校验字段清零再累加,这一步不能省——如果带着旧的校验值累加,算出来的新值永远差一截。

CSUM_TARGET这个魔数没有通用值。判断办法:拿原始镜像跑一遍累加,如果结果是 0xFFFF,那目标就是 0xFFFF;如果你把校验字段清零后累加得到 X,而原镜像该位置的值是 Y,且(X + Y) & 0xFFFF等于某个固定值,那个固定值就是目标。

4.3 回写与冷启动验证

写之前再确认一次接口是 down 的,写完必须彻底断电,不是重启。

# 1. 确认接口已关闭 ip link set dev eth0 down # 2. 写入新镜像,magic 参数按驱动要求填,很多驱动不校验这个值 ethtool -E eth0 magic 0x3f1c offset 0x00 length 512 < nvm_new.bin # 3. 直接关机,而不是 reboot poweroff

ethtool -E的三个要素:magic是驱动用来校验“你确实知道自己在干什么”的一个值,填错了会被拒绝;offset是起始偏移;length是长度,一般跟镜像大小一致。标准输入重定向给-E的写法要求数据是纯二进制,不能用十六进制文本。

写完直接poweroff是必须的动作。很多控制器的 NVM 只在 PCI probe 阶段读一次,reboot走的是热复位,部分平台的 3.3V 待机供电不断,控制器内部状态没清干净,读到的还是旧值。拔电源线等 30 秒,或者关机后按一下电源键放电,这一步别偷懒。

上电后按顺序验证三件事:ip link show看接口在不在、ethtool -e重新 dump 一遍看字节是不是新的、ping一下同网段设备确认收发正常。三件事都过,这次硬刷才算完成。

5. 硬刷翻车现场:五类症状的排查路径

硬刷这事儿,成功的过程千篇一律,失败的姿势五花八门。下面这五类是我见过频率最高的,每一类都按“现象 → 原因 → 解决”拆开,遇到对得上的直接照着排。

5.1 写不进去,返回 Operation not supported

现象:执行ethtool -E立刻报Cannot set EEPROM data: Operation not supported,连写入的机会都没有。

原因:驱动根本没有实现ndo_set_eeprom回调。近几年的内核版本里,出于安全和维护成本考虑,大量驱动把写回调删掉了,只保留读。你手上的工具再新也没用,因为内核这一层就把路堵死了。

解决:换路线。优先找厂商提供的离线刷写工具,做成启动盘在 DOS 或 UEFI Shell 下跑,这类工具直接操作 BAR 映射,不经过内核。如果厂商没提供,退到 SPI 直读方案,用flashrom一类工具在物理层读写。临时也可以试老内核或者自己编译带写回调的驱动模块,但这只适合实验室环境,不适合批量。

5.2 写进去了但重启又变回旧 MAC

现象:写完当场ip link show显示新地址,ping也通,一断电重启就回到出厂值。

原因:最常见的是系统层面还有别的东西在覆盖。比如 NetworkManager 的 connection 配置里存了cloned-mac-address,或者 udev 规则里有MACADDR设置,开机时又写了一遍。另一个可能是写的时候没真正落到 NVM,只是写进了控制器的影子寄存器。

解决:先查覆盖源。grep -rn "mac" /etc/NetworkManager/和grep -rn "MACADDR" /etc/udev/rules.d/两条命令扫一遍,把相关配置清掉再测。如果配置干净还是回滚,说明写入没落地,回去确认ethtool -E的返回码是不是 0,以及写完之后ethtool -e重新 dump 出来的是不是新值——不是新值就说明写操作被驱动静默忽略了。

5.3 网卡从系统里彻底消失

现象:刷完上电,lspci还能看到设备,但ip link里没有对应接口,dmesg里一堆 probe 失败。

原因:镜像写坏了,校验和对不上,控制器把自己标成配置无效;或者 MAC 字段被写成了全 0、全 FF、组播地址(第一字节最低位为 1),驱动初始化时直接拒绝。

解决:先看dmesg | grep -i -E "eth|nvm|eeprom",如果报的是校验错误,说明校验算法或者偏移搞错了,用备份镜像回滚。回滚的办法取决于还有没有写入通道:驱动读回调通常还在,ethtool -e能读就能配合-E写回去,前提是-E还支持。如果-E也不支持了,就只能上 SPI 直读工具把备份烧回去。

5.4 驱动加载报 Invalid NVM checksum 但卡还在

现象:接口能起来,但速率协商异常,或者只有百兆,dmesg里有明确的 checksum 告警。

原因:校验和字段的位置算对了,但累加范围错了。有的控制器只对配置区做校验,不包含 PXE 区;有的要求按大端累加;还有的把校验和本身排除在累加范围外。范围差一个字节,结果就全错。

解决:拿原始镜像反推。把原始镜像的校验字段清零后,按不同的范围(全镜像、前 256 字节、前 0x100 字节)分别累加,看哪种范围加上原校验值等于魔数。试出正确范围后写进脚本,别靠猜。

5.5 两台机器 MAC 撞车引发交换机告警

现象:批量刷完之后,交换机日志里刷 MAC flapping,两台机器的流量互相顶掉,网络时通时断。

原因:批量脚本里 MAC 是写死的常量,或者用了自增但没做唯一性校验。MAC 地址全球唯一靠的是 OUI 分配,自己造地址必须保证本地管理位(第一字节的 bit1)置 1,且整个内网内不重复。

解决:批量场景下把 MAC 生成逻辑改成“取一个固定前缀 + 设备序列号哈希后 3 字节”,并在写入前查一遍内网 ARP 表去重。第一字节用02、06、0A、0E这类本地管理地址开头,避免撞上真实厂商的 OUI。刷完先小批量接入,观察交换机日志半天再铺开。

6. 批量刷写与可回滚验证:把后悔药提前备好

单卡刷写跑通之后,真正的考验在批量。几十上百张卡,靠手工一条条敲命令迟早出事,但脚本化也不是把单卡命令套个 for 循环就完事。我吃过一次亏:批量脚本跑到第 37 张的时候,那批卡的 NVM 版本跟前一批不一样,偏移差了 0x10,结果那 37 张里有一半写成了别的字段,校验和倒是算对了,卡能起来但 PXE 启动全废。从那以后我每次批量之前都强制走一遍幂等检查。

第一件事是把“型号识别”做进脚本。同型号不等于同 NVM 布局,ethtool -i的firmware-version字段和lspci -nn的设备 ID 组合起来做一次分桶,不同桶用不同的偏移表。

#!/usr/bin/env bash # 批量前分桶:按 设备ID + 固件版本 归类,不同类用不同参数 set -euo pipefail for dev in $(ls /sys/class/net | grep -E '^eth'); do ids=$(lspci -nn -s "$(basename "$(readlink -f /sys/class/net/$dev/device)")" | grep -o '\[[0-9a-f]\{4\}:[0-9a-f]\{4\}\]') fw=$(ethtool -i "$dev" | awk '/firmware-version/{print $2}') echo "${dev},${ids},${fw}" done | sort | uniq -c

这段脚本输出的是“每种组合有多少张卡”,uniq -c的计数就是分桶结果。数量少的桶要单独拿出来人工确认,别图省事一把梭。

第二件事是验证关卡。单卡刷完靠肉眼,批量必须靠脚本卡三道关:

关卡检查内容不通过的处置
写入前备份镜像的 sha256 是否已落盘、镜像长度是否为预期值中止该卡,跳过
写入后重新 dump 的内容与目标镜像逐字节cmp是否一致用备份回滚,标记该卡
冷启动后接口存在、MAC 正确、能 ping 通网关隔离该卡,进人工排查队列

cmp这一步很多人省,觉得ethtool -E返回 0 就万事大吉。实际上驱动返回成功只代表“命令收下了”,不代表“字节写进去了”。回读比对是唯一能证明写成功的证据。

最后说一个我自己的习惯:每刷完一批,我会随机抽两张卡,用备份镜像原样写回去,再读一遍确认跟备份的 sha256 完全一致。这个动作专门验证“回滚通道还活着”。硬刷最怕的不是写错,是写错之后没有退路。回滚演练看着多余,真出事的时候它就是那根救命绳。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询