解除VBA工程密码:从vbaProject.bin定位DPB修改的完整指南
简介面向 Excel VBA 工程密码遗忘场景下的应急解锁工具说明包适合有 VBA 基础、希望了解 Windows API 与内存机制的技术人员使用。文档以图文与代码注释结合的方式讲解如何绕过工程密码验证并特别处理“工程不可查看”的情况核心思路是通过 hook user32.dll 中的 DialogBoxParamA 函数配合 VirtualProtect 修改内存保护属性再以 MoveMemory 替换与恢复函数入口字节。资源包含 1 个 docx 说明文档压缩包约 15KB篇幅精简重点集中在 API 地址获取、函数前六个字节备份、Hook 与恢复过程等关键步骤并附带完整 VBA 示例代码。目前已有 1862 人浏览学习。对想深入理解 VBA 底层调用、程序保护机制与内存修补原理的读者是一份可对照练习的参考笔记但文中所涉方法仅适用于本人拥有密码权限的文件应用时需注意合法边界。1. 解除VBA工程密码先搞清楚你动的是哪一层保护接手遗留的 Excel 宏文档时打开 VBA 编辑器弹出一个密码框而交接人早就联系不上这是很多人第一次遇到“解除 VBA 工程密码”的真实场景。标题里的“VBA 工程密码”指的是 Office 里 VBA 项目的访问口令——设置了它别人按下 AltF11 就只能看到工程名看不到任何模块代码。解除它不是破解文件打开密码也不是暴力猜口令而是把工程数据里“有密码”的校验标记去掉让代码重新可查看、可修改、可迁移。适合正在被历史宏卡住的人也适合准备批量清理手头宏资源的开发者。整篇会从原理说到工具再落到参数和坑。2. VBA工程密码藏在哪里从文件结构到可解除的原理2.1 OOXML与OLEvbaProject.bin才是真正的战场很多人第一反应是去 Word 或 Excel 的 XML 里找密码那是白费劲。Office 2007 之后xlsm、docm、pptm 这类带宏的文件本质上是一个 ZIP 压缩包里面装着各种 XML 部件但 VBA 本身不放在这些 XML 里而是作为一个整体二进制流存在文件名固定叫 vbaProject.bin常见位置是 xl/vbaProject.binExcel或 word/vbaProject.binWord。这个文件是 OLE 复合文档格式里面装着 VB 工程的代码、模块信息、窗体、引用以及工程属性。所以解除密码的第一步不是打开 Office而是打开这个 ZIP 容器把 vbaProject.bin 抽出来。理解这一点非常关键很多人拿着 Office 自带的宏编辑器折腾半天是因为找错了对象。而老版本 Office2003 及更早的 xls/doc 本身就是 OLE 复合文档不需要解 ZIP直接用十六进制编辑器打开原文件也能看到同样的结构。原理是一样的区别只在于容器。OLE 复合文档内部像一个迷你的文件系统里面有目录流、存储和流对象。vbaProject.bin 里最重要的几个流是 PROJECT文本工程属性、dir模块和工程信息、以及真正存代码的流。密码信息就藏在 dir 流末尾的 PROJECTLOCK 结构里。理解了这一层就能解释为什么它能被直接绕过。2.2 DPB校验块为什么改两个字节就能绕过密码VBA 工程密码并不是把整个代码用密钥加密一遍。更准确地说它是一道“查看权限”的开关检查到正确的密码后Office 才把代码流映到 VBA 编辑器里。这个开关的状态记录在 dir 流尾部的 PROJECTLOCK 结构中。未加密工程通常记录为 DPx 之类的标记而加密工程记录为 DPB后面附带一串校验信息。Office 读取工程文件时会先解析 PROJECTLOCK如果读到未加密标记就直接放行如果读到 DPB就要求输入密码并校验。问题在于这个校验并不是一个强签名老版本 VBA 格式甚至允许“长度字段为零”时跳过整个保护结构。于是有了两种非常直接的解除方式一是把 DPB 改成 DPx让 Office 把工程当成未加锁二是把 DPB 后面跟的二进制长度字段清零让解析逻辑认为保护块不存在。这两种方式都不碰代码流所以理论上代码不会丢。为什么 Office 到今天还没有把这个口子完全堵死主要原因是兼容性。VBA 工程格式从 Office 97 一直延续到现在新版本要读取 20 年前生成的 vbaProject.bin不能随便改变校验结构而识别未加锁标记又是安全校验的第一步。所以补丁式的修改在很多版本上仍然有效。这个“改标记”的做法不是去计算密码而是让 Office 压根不去验证这也是它比跑字典快得多的原因。需要补充一个边界此方法不能解除文件打开密码不能解除工作簿或文档结构保护更不能让你合法访问别人有意保护的代码。它只解决“工程查看锁”这一件事。如果文件本身设置了打开密码必须先拿到或解除文件层密码才能走到这一步。每一次修改之前都把原文件复制一份作为备份——这不是建议而是底线因为后续操作如果改错位置可能让 Office 直接拒绝打开文件。2.3 合法边界与通用流程先备份、再定位、后修改整个解除流程可以归纳为四步复制备份、取出 vbaProject.bin、定位并修改 DPB 校验标记、回写重打包。看起来简单但每一步都有细节决定成败。先说备份不要把“解除前备份”和“解除后版本管理”混为一谈。我一般会在同一个目录下生成 original.xlsm 和 unlocked.xlsm 两个文件全程只操作 unlocked.xlsm原文件不动。这样即使改坏了也能一键回到起点。取出 vbaProject.bin 有两个常规入口命令行 unzip 或者图形化解压工具。对于不熟悉命令行的用解压工具打开 xlsm 文件找到 vbaProject.bin 单独拖出来即可。如果解压工具有“压缩方式”相关设置尽量选“不修改压缩方式”的提取这样回写时更容易保持原包结构。命令行方式会在第 3 章给出具体命令。定位修改时需要用十六进制编辑器。这类工具很多不用追求新版本能显示二进制和 ASCII 就行。打开 vbaProject.bin 后搜索字符串 DPB。如果搜到了继续观察它周围字节如果同时看到 DPx说明这个工程当前没有加保护什么都不用改。这里有个常见误解很多人以为密码是以明文形式存在文件里的所以搜“密码字符串”去找实际上没有明文密码只有经过混淆的校验数据。搜 DPB 是最直接有效的入口。3. 手工解除用十六进制编辑器改出无密码工程3.1 从xlsm/docm里拆出vbaProject.bin命令行的拆包命令如下。我以 Excel 宏文件为例Word 的 docm 结构类似只是路径从 xl 换成 word。mkdir unlocked_dir unzip locked.xlsm -d unlocked_dir ls unlocked_dir/xl/vbaProject.binmkdir unlocked_dir创建解压目录unzip locked.xlsm -d unlocked_dir不会把 ZIP 里的文件撒得满目录都是而是按包内目录结构放到 unlocked_dir 下最后确认 vbaProject.bin 确实在 xl/ 路径中。如果系统提示没装 unzip也可以用 Python 一行完成解包但不关键工具顺手即可。参数注意如果包内还有 vbaProjectSignature.bin签名文件不要动它只提取 vbaProject.bin 做修改。locked.xlsm 建议使用完整路径或先进入文件所在目录否则解包路径会让人找半天。解包之后先不要关终端后面回写还要用。3.2 定位DPB块并解析长度字段用十六进制编辑器打开 unlocked_dir/xl/vbaProject.bin。按下 CtrlF在搜索类型里选 ASCII 字符串输入 DPB。正常加密工程会在文件的某个偏移处找到这个三字节序列通常在文件末尾附近的 dir 流区域内。记录这个偏移值我习惯写成 0x???? 的格式并截图或抄下来。找到 DPB 后不要急着改先观察它前后的字节。受保护结构一般长这样前面是版本或标志字节DPB 是三个连续 ASCII 字符随后是 4 字节的小端长度值表示后面加密数据的字节数。这 4 字节长度通常是个比较大的数字比如文件剩余几千字节那么它以小端序写成类似 xx 00 00 00 的形式。如果看到 DPB 后面不是这种结构而是紧跟着可读文本那很可能你搜到的是误报——文件名、模块名里也可能出现 DPB 这三个字母。为了定位准确建议在搜索结果里查看偏移再用十六进制面板从 DPB 往后数 4 个字节把这 4 个字节读出来。小端序换算公式是值 byte0 byte1 * 256 byte2 * 65536 byte3 * 16777216。如果算出来的值加上 DPB 偏移接近文件大小说明结构正确可以动手了。如果你搜索时发现文件里只有 DPx 没有 DPB那本来就无密码如果 DPB 出现多处以最后一次出现为准。因为 dir 流末尾的 PROJECTLOCK 是最后一个结构前面的可能是编译缓存里的相似字节。3.3 两种修改路线DPB改名与长度清零路线一把 DPB 改成 DPx。在十六进制编辑器里定位到 DPB 的偏移把第三个字节从 42ASCII 的 B改成 78ASCII 的 x保存。这样 PROJECTLOCK 读到的标记变成未加密形式很多老版本工程会直接放行。这个做法优点是改动最小缺点是部分新版 Office 会对“内容被篡改”做二次校验反而弹出修复提示。路线二长度清零。保持 DPB 不变把它后面 4 个字节全部改成 00 00 00 00。解析逻辑读到保护块长度为零时会认为当前没有需要校验的密码数据从而跳过整个口令验证。这个做法在我的实测里对新旧版本兼容性更好但一定要确认长度字段确实指向保护数据而不是代码流的长度。如果改错位置代码区会被截断工程可能直接损坏。两种方法可以做一个对比路线改什么适合场景主要风险DPB 改名第 3 字节 B→x格式较老、结构简单新版本二次校验可能报损坏长度清零DPB 后 4 字节置 0各种版本推荐先试偏移必须准确改错伤代码实际操作时我的习惯是先用长度清零试一次如果 Office 提示不可读取再用 DPB 改名路线从备份重做。两种方法改完都直接保存 vbaProject.bin不要顺手做“另存为其他格式”保持文件原始格式。3.4 回打包与打开验证压缩方式与修复提示修改完 vbaProject.bin 后回到 unlocked_dir 目录把它重新打包成 xlsm。这里最大的坑是ZIP 打包方式的差异会让 Office 拒绝识别。最稳妥的简单方式是保持不压缩cd unlocked_dir zip -0 -r ../unlocked.xlsm .-0表示不压缩-r表示递归包含目录和文件。注意最后还有一个点表示把当前目录内容打包而不是把 unlocked_dir 这层目录也打进去。如果打包后文件体积明显大于原始文件是正常的因为没压缩Office 对不压缩的 ZIP 完全兼容。如果你用的解压工具自带打包功能选择“存储”级别也是同理。回包之后复制一份 unlocked.xlsm 用于验证。双击打开文件按 AltF11 进入 VBA 编辑器如果能看到模块和代码说明成功。如果弹出“发现不可读取的内容”或“是否修复”说明刚才的修改有风险先点否再回到备份重做一次换另一种路线。千万不要在那个提示窗里点“修复”因为修复过程可能把整个 VBA 工程清空。另外很多现代 Office 会记住“受保护的视图”状态导致文件处于只读预览VBA 编辑器看起来打不开。请先确认 Excel 底部或顶部没有“启用编辑”横幅文件若处于受保护视图工程也会被限制。点击“启用编辑”后再验证才能得到真实结果。4. 批量化与脚本化用Python自动处理多个VBA工程4.1 为什么这个需求适合脚本化手工方法适合处理一个文件但实际需求往往是一批某部门交接下来几十个带宏的 xlsm都要清理掉查看密码或者是同一个模板派生的多个副本设置密码时用了同一个口令现在要批量去掉。面对这种场景一个个用十六进制编辑器改会让人崩溃。VBA 工程密码解除的核心操作在二进制层面非常固定搜 DPB改标记。这正好适合脚本化。尤其适合的典型场景有三个定期从老宏目录收集代码、把外部宏文件整理进自有代码库、以及为团队批量重置丢失的工程密码。脚本要做的不是“猜出密码”而是通过重打包生成一个新文件原文件保持不动。这个思维和手工流程一模一样只是把定位与修改交给代码避免人工记偏移。4.2 脚本骨架定位、修改、回写下面这个脚本是我常用的雏形去掉了具体项目路径只保留核心逻辑。它能处理 xlsm、docm、xlam 这类基于 ZIP 的 Office 文件。import sys import zipfile def find_dpb_offset(data: bytes) - int: 在二进制流中定位 DPB 标记取最后出现位置 return data.rfind(bDPB) def patch_vba(data: bytes, mode: str zerolen) - bytes: 修改 vbaProject.bin 中的工程保护标记 off find_dpb_offset(data) if off -1: print([!] 未找到 DPB文件可能未加密或结构不同) return data buf bytearray(data) if mode rename: # 路线一DPB 改为 DPx标记为未加锁工程 buf[off 2] 0x78 print(f[*] 已把 DPB{off:#x} 改为 DPx) elif mode zerolen: # 路线二DPB 后的 4 字节长度字段清零 for i in range(off 3, off 7): buf[i] 0 print(f[*] 已清零 DPB{off:#x} 后的长度字段) else: raise ValueError(mode 必须是 rename 或 zerolen) return bytes(buf) def unpack_and_patch(src: str, dst: str, mode: str zerolen) - None: 读取源压缩包替换 vbaProject.bin 后写出新文件 with zipfile.ZipFile(src, r) as zin: with zipfile.ZipFile(dst, w) as zout: for item in zin.infolist(): raw zin.read(item.filename) if item.filename.endswith(vbaProject.bin): raw patch_vba(raw, mode) # 保留原始压缩方式和元数据避免 Office 兼容性问题 zout.writestr(item, raw) if __name__ __main__: src_file, dst_file sys.argv[1], sys.argv[2] patch_mode sys.argv[3] if len(sys.argv) 3 else zerolen unpack_and_patch(src_file, dst_file, patch_mode) print(f[] 完成{dst_file})逻辑说明先读入源 ZIP 的每个条目只对路径以 vbaProject.bin 结尾的条目做二进制修改其他 XML 部件原样回写。这样不会破坏包内其他内容。find_dpb_offset 用 rfind 取最后一次出现的 DPB对应 dir 流末尾的 PROJECTLOCK 结构同时避开可能出现在模块名里的误报。patch_vba 按照前面说的两种模式修改。参数说明第一个参数是源文件路径第二个是输出文件路径第三个可选参数是修改模式默认 zerolen可以手动改成 rename。输出文件必须和源文件不同我一般写 temp_unlocked.xlsm验证成功后再覆盖正式命名。writestr 时没有手动设置 compress_type而是沿用 item.compress_type这让每个条目的压缩方式与原本一致是避免 Office“文件损坏”提示的关键。如果你要批量处理只需要在外面套一层目录遍历import glob for f in glob.glob(*.xlsm): unpack_and_patch(f, f.replace(.xlsm, _unlocked.xlsm), zerolen)这个循环会为当前目录下的每个 xlsm 生成一个带 _unlocked 后缀的新文件原始文件保持不动。建议不要直接在原文件上覆盖因为一旦脚本对结构判断错误你还能从原文件从头再来。4.3 脚本的边界老版xls文件与解密后仍需修复脚本对 xlsm、docm、pptm 这类 ZIP 容器有效但对于老版 .xls、.doc、.ppt 无效因为它们是 OLE 复合文档不是一个 ZIP 包。处理老版文件时我一般直接复制一份用十六进制编辑器打开在全文里搜 DPB然后手工改。网上虽然也有直接处理 OLE 的脚本但需要额外解析 dir 流踩坑成本比手工高除非你确定要长期处理几十个老文件否则不必为一次需求引入新依赖。另外脚本执行成功不等于文件就能打开。有些文件在 VBA 工程密码之外还有一层工作簿保护或文档保护打开后 Excel 会提示某些区域被锁定VBA 编辑器虽然能进代码也可能已被隐藏。更常见的边界是如果文件还开着或者有外接程序占用脚本读源文件时会报 PermissionError请先关掉所有 Office 进程再运行。最后再强调一次脚本不处理打开密码有打开密码的文档要先在 Office 里解除否则后面一切都不成立。5. 解除VBA工程密码的常见问题与避坑指南5.1 修改后Office提示文件损坏、拒绝打开现象按前面步骤改完并重打包双击文件Office 弹窗说“文件已损坏是否修复”点“是”后所有宏代码没了。原因常见三类。第一DPB 偏移找错或长度清零多清了几字节破坏了 dir 流第二重打包时用了默认压缩级别导致 Office 的 ZIP 解析器不认第三改到中途保存时编辑器自动追加了 EOF 或换了编码。解决立即放弃当前文件从备份重新解包。先检查偏移DPB 后第 4 个字节的值应该小于文件长度若反了就是找错位置。回包时用 zip -0 或者脚本方式保留原始压缩方式。如果一定要在十六进制编辑器里保存注意确认编辑器没有悄悄换成 UTF-8。养成“只操作副本失败就回滚”的习惯这一条能解决绝大部分损坏问题。5.2 代码窗口一片空白现象能进 VBA 编辑器能看见模块名称但双击模块代码区空无一物。原因最常见的是原工程勾选了“保护工程并查看工程属性”但它不影响查看模块真正造成空白的是修改时误伤到了 dir 流里的模块名表或者原工程代码本身做了隐藏处理。还有一种情况文件被压缩工具重新打包后模块流被“优化”掉了。解决先试试能不能从工程里导出模块如果导出来是空的回到原始加密文件输入正确密码后正常导出。如果原始文件密码已不可得就只能从备份或交接资料中找回代码。提醒一句解除工程密码能绕过查看锁但如果对方在代码层做了隐藏处理解除密码只是一部分工作。5.3 文件还有打开密码VBA编辑器直接进不去现象输入文件打开密码才能看到内容但按下 AltF11 还是提示“工程不可查看”。原因文件打开密码是一道比 VBA 工程密码更靠前的锁。ZIP 容器本身被 Office 应用层加密VBA 工程数据也被卷入了文件级加密直接改 vbaProject.bin 里的 DPB 标记没用因为 Office 要先验证文件解密才会把 vbaProject.bin 交给 VBA 引擎解析。解决先确认文档的打开密码是否已知。知道密码就正常打开另存为无密码的副本再对副本做 VBA 解除。不知道打开密码那不是本文能解决的范围请先与文件所有者确认授权。一定不要用“取消密码保护”的旁门左道去处理别人有打开密码的文档既不稳定也容易跨过安全边界。5.4 同一份patch后的文件在不同Office版本下表现不一致现象同一个 xlsm在某台机器的 Office 2010 上能正常打开换到 Office 2019 打开却提示工程损坏或者干脆看不到模块。原因老版本 Office 对 ZIP 结构和 dir 流末尾的 PROJECTLOCK 解析很宽松改标记就放行新版本加了更多一致性校验遇到改动会认为文件被篡改。解决交叉验证时以原文件生成的 Office 版本为准。如果文件是老版本生成的优先用 DPx 改名路线如果新版本生成优先用长度清零。实在不确定就在备份上把两种路线各做一遍然后用本机 Office 验证。之后你的归档文件应该明确记录“用哪个 Office 版本验证过”避免换机器后误判。5.5 改完宏能运行但一打开工程属性又自动加回密码现象VBA 密码清掉后使用几天又发现工程被锁定密码框重新出现。原因文件里很可能有一段自保护宏在 Workbook_Open 或 Document_Open 事件里执行锁定工程的代码也可能来自外接程序注入。这不是修改没生效而是运行时又被保护了。解决打开 VBA 编辑器后先检查 ThisWorkbook 里有没有 ActiveWorkbook.VBProject.Protection 相关的代码。先停用所有外接程序再测试。确认是文件自身逻辑后删除或注释掉自锁代码并另存一份不带该逻辑的干净版本。清密码前也建议先关闭其他外接程序避免它们干扰验证结果。6. 验证与进阶解除后如何确认代码完整并改造为团队可维护的方案6.1 解除后必做的完整性验证密码清掉只是第一步确认代码完整才是真正收口。我的验证顺序固定为六步打开解除后的文件启用编辑按 AltF11 进入 VBA 编辑器。查看工程资源管理器对照资源清单核对模块数量少了模块说明没改干净或代码隐藏。逐一打开每个代码窗口按 CtrlA 全选看代码行数是否合理。右键工程名称进入工程属性切到保护页签确认“查看工程属性”复选框未被选中。在立即窗口输入 ?ThisWorkbook.VBProject.Protection 并回车返回值应为 0。运行一次无副作用的宏比如弹一个 MsgBox确认编译通过。验证项预期结果失败处理模块数量与资源清单一致检查 dir 流是否损坏回到备份代码窗口每模块都有代码检查是否被属性隐藏尝试导出工程属性保护页复选框未选中手动取消勾选并保存Protection 属性0说明仍有运行时保护逻辑运行验证无报错检查缺失引用打开引用对话框补引用这里最有迷惑性的是第 5 项。即使你在工程属性里看到没勾选运行时也可能动态修改 Protection 值所以一步不能少。6.2 从一次性解除到规范化管理导出模块与版本管理解除成功后我习惯立刻把所有模块导出一份放进代码仓库里。具体操作不复杂在 VBA 编辑器的工程资源管理器里右键每个模块选择“导出文件”能导出 .bas标准模块、.cls类模块、.frm窗体三类。导出的文本文件可以直接纳入版本管理日后再也不用从加密文件里救人。这个习惯来自一次翻车经历。某次我图省事直接在原文件上做修改结果 Office 提示修复我手快点了“是”VBA 工程被清空代码全丢。后来那套逻辑我对着旧打印稿重写了大半天。从此再处理这类工程一定先复制副本、再操作、最后验证解除密码只是获得访问权把代码变成普通人可读的文本文件才算真正把东西攥在手里。如果你的团队还要长期维护这些宏建议把“工程密码”从技术保护方案里去掉。VBA 工程密码本来就不是可靠的安全措施它充其量是一道心理锁真要防外部人改代码应该走文件权限、宏签名和统一的版本发布流程。让每个人手里的副本都是可编辑的源码库出问题时从库里恢复比什么密码都有用。希望这套从原理到验证的流程能帮到你拿到代码、修通遗留工程。本文还有配套的精品资源点击获取