简介:这是一款基于VB6开发的EXE反编译工具,面向Visual Basic开发者,可将编译后的EXE程序还原为VB源代码,适用于学习他人编程思路、排查旧工程问题及逆向工程场景。工具包共30个文件,核心源码以bas、frm、cls等模块为主,涵盖反编译器主控界面、内存映射、PE结构解析、P-Code与Native代码还原等功能逻辑;txt说明文档用于指导配置与使用,并附有界面截图,压缩包整体约278KB,体积小巧、适合快速上手。目前已有735人学习下载,对于想深入了解VB6编译产物结构和反编译原理的开发者颇具参考价值。通过学习这套源码,读者可以掌握PE文件格式解析、API调用追踪、窗体控件重建等实用技巧,了解反编译工具从二进制流读取到源码输出的完整流程,还可借鉴其模块划分与错误处理思路,为自研调试辅助工具或逆向工程实践打下扎实基础。
1. EXE反编译工具VB版:反编译 exe 为 VB 源码,先把还原边界讲清楚
你大概率遇到过这种情况:十年前同事用 Visual Basic 6 写了个内部工具,源码早就丢了,只剩一个 exe;或者你在网上下了个小软件,想看看它的窗体布局和逻辑实现。标题里“反编译exe为vb源码”这个说法,落到 VB6 老项目上,实际要回答三个问题:这个 exe 是 P-Code 还是 Native Code 编译的、窗体能不能还原成 .frm、代码逻辑能不能还原成可读的 VB 语句。用 VB 生态里那套反编译工具链来做,P-Code 的目标可能还原出接近源码的逻辑,窗体布局几乎能 1:1 提取;Native Code 则只能退到资源提取加反汇编。这篇文章的价值在于:拿到陌生 exe 后先判断值不值得反编译,再照着步骤走,不在注定无解的方向上浪费半天。
2. 先搞懂 VB6 exe 的两张面孔:P-Code 与 Native Code 决定反编译天花板
2.1 先用三个命令和一个 Python 脚本摸清编译类型
VB6 的工程属性里有一页“编译”,里面两个关键选项:编译为 P-Code、编译为本机代码(Native Code)。VB6 默认是 Native Code,很多人不知道这一点,所以拿到 exe 就按“能还原成源码”的预期去操作,结果必然翻车。反编译前第一件事不是打开工具,而是先做一次侦察,判断目标到底是哪种编译产物。
# 1. 看文件类型与架构 file old_tool.exe # 2. 抓 VB6 典型运行时特征(注意 -el 是 UTF-16LE) strings -el old_tool.exe | grep -iE "msvbvm|visual basic|vb6" | head -20 # 3. 看导入表里有哪些运行时 DLL objdump -p old_tool.exe | grep -iE "DLL Name|Subsystem" | head -20file命令确认 PE 架构是 32 位还是 64 位,VB6 只有 32 位程序,如果显示 64 位就直接排除 VB6 原生目标。strings -el里的-e l表示按 16 位小端字符编码提取,VB6 内部字符串是 Unicode,默认的 ASCII 提取会漏掉大量线索。objdump -p看导入表,出现msvbvm60.dll时基本可以确定是 VB6 程序,但还不能区分 P-Code 与 Native,因为两种模式下 VB6 运行时都会被链接进去。
#!/usr/bin/env python3 # vb_probe.py —— 简易 VB6 exe 摸底脚本 import sys import pefile def probe(path: str): pe = pefile.PE(path) print(f"[*] 模块机器码: {pe.FILE_HEADER.Machine:04X} 子系统: {pe.OPTIONAL_HEADER.Subsystem}") # 导入表里找 VB6 运行时入口 ThunRTMain if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"): for entry in pe.DIRECTORY_ENTRY_IMPORT: dll = entry.dll.decode(errors="ignore").lower() if "msvbvm" in dll or "vba" in dll: print(f"[*] 发现 VB 运行时: {entry.dll.decode(errors='ignore')}") for imp in entry.imports: name = imp.name.decode(errors="ignore") if imp.name else f"ord:{imp.ordinal}" if "ThunRTMain" in name or "rtc" in name.lower(): print(f" - 入口/运行时函数: {name}") # 资源段顶层命名 if hasattr(pe, "DIRECTORY_ENTRY_RESOURCE"): for res in pe.DIRECTORY_ENTRY_RESOURCE.entries: name = res.name.string if res.name else f"ID:{res.id}" print(f"[*] 资源: {name}") pe.close() if __name__ == "__main__": probe(sys.argv[1])脚本依赖pefile,用pip install pefile装上就能跑。ThunRTMain是所有 VB6 程序的标准入口点,从它是否存在可以确认目标确实是原生 VB6 而不是伪装品。资源段里的ID:16一般是版本信息,ID:24是清单,如果看到大量自定义命名的资源,说明程序可能有多个窗体资源或第三方控件,这也是后续窗体还原的重要线索。
2.2 两种编译产物的反编译上限对比
判断出类型之后,要立刻调整预期。P-Code 和 Native Code 在反编译难度上完全不是一个量级,网上很多“反编译 exe 为 VB 源码”的工具,实际只对 P-Code 有效,对 Native Code 只能做到资源提取。
| 对比项 | P-Code | Native Code |
|---|---|---|
| 编译方式 | 编译成 VB6 解释执行的字节码 | 编译成 x86 机器指令直接运行 |
| 窗体还原 | 可完整还原 .frm 与 .frx | 同样可还原,窗体资源始终存在 |
| 逻辑还原 | 能还原成近似源码的 VB 伪代码 | 只能得到汇编,配合反汇编器分析 |
| 变量名 | 局部变量名全部丢失,变为 var_xx | 全部丢失,且更难推断类型 |
| 流程结构 | If/For/While/Select 结构可还原 | 只能靠手工从汇编推断 |
| 典型工具 | VBDecompiler 等 P-Code 反编译 | IDA / Ghidra 等反汇编器 |
P-Code 本质是字节码,解释器是msvbvm60.dll,反编译器通过查 opcode 映射表把字节码翻译回 VB 语句,所以能还原出 If、For、Select 这些结构。Native Code 是编译后的 x86 指令,没有字节码映射表可查,还原 VB 源码级别基本不可能。
这里有个很现实的结论:如果目标是维护老系统、修改界面、找回业务逻辑,P-Code 目标值得投入,还原出来的代码可以直接在 VB6 里打开窗体继续改;如果目标是 Native Code,真正可行的路线是“窗体布局提取 + 汇编级逻辑分析”,把界面还原出来,逻辑部分手工重写,而不是奢望一键得到源码。
3. 从 exe 里还原 .frm 窗体与控件:资源提取才是高性价比第一步
3.1 VBReFormer 还原窗体:三步操作与导出选项
判断完编译类型,先别急着反编译代码,第一步是提取窗体资源。VB6 的窗体在 exe 里不是被编译成机器码,而是以结构化资源的形式存在,包含窗体尺寸、控件类型、控件位置、属性值、事件名映射。这也是为什么窗体还原的完整度远高于代码还原。常见做法是用 VBReFormer 这类工具打开 exe,左侧会列出 exe 内部的窗体列表。
操作步骤:
- 打开 exe,左侧 Forms 树里看到 MainForm、LoginForm 这些窗体名;
- 右键目标窗体,选择导出窗体,保存到指定目录;
- 导出完成后检查目录里是否有同名的 .frm 和 .frx,两个文件缺一不可。
导出时一般有这些选项需要理解:
| 导出选项 | 作用 | 建议 |
|---|---|---|
| 导出窗体 .frm | 生成文本格式的窗体源码 | 必须开启 |
| 导出资源 .frx | 生成二进制资源文件 | 必须开启 |
| 导出全部窗体 | 批量处理多窗体程序 | 维护老系统时建议全量导出 |
| 控件数组拆分 | 把控件数组按索引拆开 | 默认不拆,保持原结构 |
我一般会先导出一个窗体看看 .frm 内容是否完整,再决定是否批量导出。如果单个 .frm 里控件属性大量缺失,说明工具对某些第三方控件的解析不完整,此时批量导出也只是拿到一堆残缺文件。
3.2 导出后 .frm 与 .frx 各是什么
导出的 .frm 是纯文本,可以直接用记事本打开,看到的是标准 VB6 窗体描述语法:
Begin VB.Form MainForm Caption = "库存管理" ClientHeight = 6000 ClientLeft = 120 ClientTop = 465 ClientWidth = 9600 Begin VB.TextBox txtUser Height = 375 Left = 2400 TabIndex = 1 Top = 1200 Width = 2415 End Begin VB.CommandButton cmdLogin Caption = "登录" Height = 495 Left = 6000 TabIndex = 2 Top = 1800 Width = 1695 End End这个文本结构和 VB6 编辑器里 .frm 的存储格式完全一致,理论上可以直接拿来作为窗体源码继续编辑。注意.frx文件是二进制,不能直接打开,它保存的是窗体里嵌入的图片、图标、OLE 对象、某些控件的扩展属性。.frm 里如果有Picture = "MainForm.frx":0000这样的写法,冒号后面的十六进制数就是该资源在 .frx 文件里的偏移量,重构工程时这个偏移不能改动。
3.3 为什么窗体布局能完整还原
窗体还原度高不是因为工具“破解”了什么,而是 VB6 编译器的设计使然。VB6 把窗体定义、控件布局、事件过程名这些信息以资源形式打包进 exe,运行时由msvbvm60.dll负责加载和创建窗口。反编译工具做的只是把这段结构化资源反序列化,重新写成 .frm 文本。所以哪怕是 Native Code 编译的 exe,窗体提取的成功率也非常高。
这就带来一个实际价值:很多时候业务方的真实诉求是“程序还能用,但界面想改改、想加个按钮、想导出数据”,这种改动根本不需要还原全部代码逻辑,只要把 .frm 提取出来重新建一个工程、把界面改好,再想方设法对接原有输入输出即可。P-Code 目标可以直接反编译逻辑,Native Code 目标至少先把窗体拿回来,这是性价比最高的一步。
4. 用 VBDecompiler 反编译 P-Code 逻辑:关键参数与输出解读
4.1 反编译模式的关键参数
窗体还原之后,P-Code 目标就可以进入逻辑反编译环节。VBDecompiler 是这类工具里最常见的选择,打开 exe 后它会自动识别编译类型,重点要设置的是以下几个参数,直接决定输出质量:
| 参数项 | 可选值 | 说明 |
|---|---|---|
| 反编译模式 | P-Code / Native / 自动 | 已确认是 P-Code 时手工指定,避免误走 Native 分支 |
| 导出 .frm | 开 / 关 | 与窗体提取工具互为备份,一般保持开启 |
| 变量表输出 | 开 / 关 | 开启后伪代码里会附带变量槽位映射,便于分析 |
| 行号还原 | 开 / 关 | P-Code 保留行号信息,开启后能大致对应原始代码位置 |
| 控件数组索引还原 | 开 / 关 | 还原控件数组下标时保持原始引用关系 |
| ActiveX 属性解析 | 开 / 关 | 涉及第三方控件时开启,否则属性全是数值 |
模式参数是最容易踩的坑。有些人拿到 exe 直接保持默认的自动模式,工具判断失误走了 Native 分支,输出一堆汇编,然后得出“这工具不行”的结论。实际上先用第 2 章的脚本确认 P-Code,再手工指定模式,输出质量截然不同。
4.2 反编译输出的真实形态:伪代码里为什么全是 var_28
P-Code 反编译的结果通常长这样,这段是典型的还原形态,不是原始源码:
Private Sub cmdOK_Click() Dim var_28 As Integer Dim var_1C As String var_1C = __vbaStrCopy(Me.txtUser.Text) If __vbaStrCmp(var_1C, "admin") = 0 Then var_28 = 1 Else var_28 = 0 End If If var_28 = 1 Then Me.lblMsg.Caption = "ok" Else Me.lblMsg.Caption = "no" End If End Sub注意几个特征:事件名cmdOK_Click能还原出来,这是从窗体资源的事件映射表里提取的;局部变量名全部变成var_28、var_1C这种形式,因为 VB6 编译器在编译 P-Code 时抹掉了局部符号名,只保留栈槽位和偏移量;字符串常量"admin"、"ok"能保留,它们存在于常量表里;__vbaStrCopy、__vbaStrCmp是 VB6 运行时的辅助函数,会原样留在伪代码里。看懂这些,你就能判断反编译结果的质量,而不是看到 var_xx 就觉得是工具坏了。
4.3 哪些名字能还原、哪些会彻底消失
很多人在反编译后花大量时间试图恢复变量名,我的建议是不要做无谓投入。先搞清楚名字来源,才知道哪些值得恢复、哪些彻底找不到。
- 能还原:窗体名、控件名、事件过程名、模块名、字符串常量、函数名(取决于编译时是否保留调试符号)、全局变量名(有时从 GUID 或初始化代码推断)。
- 彻底丢失:局部变量名、注释、原始排版和空行、With 语句结构(可能被展开成多次赋值)、部分布尔表达式的短路方式。
- 可能还原但不可靠:函数参数名、函数返回值类型、自定义类型字段名。
实际维护中,局部变量名可以用语义推断重新命名,比如var_1C明显是用户名,var_28是标志位。P-Code 反编译的价值在于流程结构完整,业务逻辑可读,而不是做到和源码逐字一致。还原工程时,重点核对“流程是否完整、判断条件是否正确、分支是否齐平”,名字可以后补。
5. Native Code 反编译的四个常见翻车现场:避坑与排查
5.1 现象一:反编译结果全是汇编——你选错战场了
现象:VBDecompiler 设置成 P-Code 模式,结果输出窗口里是大量十六进制和汇编指令,或者直接提示无法识别。
原因:目标实际是 Native Code 编译,exe 里根本没有字节码表,反编译器无从映射。
解决:接受现实,切换工具链。用 IDA 或 Ghidra 加载 exe,定位ThunRTMain入口,从入口往下梳理主流程;配合第 3 章提取的 .frm 窗体布局来定位事件处理函数的边界。Native 反编译的目标是安全审计和逻辑还原,不是恢复到 VB 源码。我自己的习惯是:先看窗体控件名称,比如窗体里有个cmdLogin按钮,那就去反汇编代码里搜cmdLogin对应的字符串或地址引用,往往能找到按钮事件的处理逻辑起点。
5.2 现象二:窗体还原成功,重新编译却报 frx 资源错乱
现象:.frm 能打开,但工程编译时提示Invalid Picture,或者窗体加载后图片位置全是错的。
原因:.frx 文件缺失,或者 .frm 里引用的偏移量与 .frx 实际内容对不上。很多提取工具导出多窗体时,会把所有窗体的图片资源合并成一个 .frx,或者把相对路径写错。
解决:重新用原始工具导出时选择“每个窗体独立 .frx”,保证目录里只有一个同名 .frx;检查 .frm 里Picture = "MainForm.frx":xxxx的偏移,如果打开 .frx 后对应偏移处不是预期图片头,说明资源错位。此时不要手工改偏移,回到工具栏重新导出一次,比手工修补可靠得多。
5.3 现象三:字符串全是乱码
现象:从 exe 里搜出来的中文明明能看懂,但反编译结果里字符串变成一堆乱码。
原因:VB6 程序的字符串在 PE 文件里以 UTF-16LE 编码存储。用strings默认 ASCII 模式提取,中文和混排的英文会切碎。
解决:回到第 2 章的命令,用strings -el重新提取,或者用iconv -f UTF-16LE -t UTF-8 file -o out.txt转换。P-Code 反编译工具一般会自动处理编码,但 Native Code 场景下手动提取字符串是必经之路,把提取出的字符串表当索引,在汇编代码里搜索引用这些字符串的位置,是定位逻辑的最快方式。
5.4 现象四:exe 被压缩壳包装,或者根本不是 VB
现象:工具提示不是一个有效的 VB6 程序,或者反编译出的内容毫无 VB 特征。
原因:这个标题下常见的翻车现场,是目标 exe 经过 UPX 压缩、WinRAR 自解压打包,或者根本是易语言、VB.NET、Python 打包的产物。易语言程序和 VB6 的窗体结构完全不同,必须用易语言自己的反编译工具链;Python 打出来的 exe 在导入表里能看到 python3x.dll 或 PyInstaller 区段特征;WinRAR SFX 自解压包本质是个压缩包,先解压拿到里面真正的组件再谈反编译。
解决:先执行第 2 章的探测脚本,看清导入表和资源段特征再动手。UPX 加壳的程序先upx -d脱壳,脱壳后重新探测;自解压包用 7-Zip 解包后再判断;易语言目标直接换工具链。这一步花十分钟,能避免在错误方向上浪费几天。尤其是易语言程序,网上经常被误当成 VB 工具的目标,但两者编译产物结构差异巨大,VB 反编译工具对它基本无效。
6. 把反编译结果拼回可编译的 VB6 工程:工程整理与验证技巧
6.1 重组 .vbp / .frm / .bas 的依赖关系
窗体提取和代码反编译完成后,最后一步是把零散文件拼回一个可打开的 VB6 工程。VB6 工程文件 .vbp 是纯文本,核心就是引用关系和文件列表。反编译工具有时会生成一个残缺的 .vbp,但手工补全更可靠,模板如下:
Type=Exe Reference=*\G{00020430-0000-0000-C000-000000000046}#<Ver>#0#...#StdOle2.tlb Form=MainForm.frm Form=LoginForm.frm Module=Module1.bas IconForm="MainForm" Startup="MainForm" [MS Visual Basic] ExeName32=recovered_app.exeForm=行对应 .frm,Module=行对应 .bas,Reference=行是对 COM 组件和 OCX 控件的引用。最实用的整理技巧是:先打开 VB6,新建一个空工程,再用“添加文件”把反编译出的 .frm 和 .bas 逐项加进去,让 IDE 自动生成正确的 .vbp,而不是手工维护文本里的 GUID 引用。第三方 OCX 缺失时,编译会报“找不到许可证”或 “不能加载”,这时候去系统里装对应的 OCX 或把引用行注释掉,先保证工程能打开。
6.2 用 PowerShell 做一次引用完整性检查
拼完工程先别急着双击运行,我用一段 PowerShell 脚本做文本级校验,检查文件缺失和窗体块结构是否平衡。这个脚本适合在重构工程前跑一遍,省得在 IDE 里被报错反复打断:
param( [string]$vbpPath = "recovered_app.vbp" ) $vbpDir = Split-Path $vbpPath $vbp = Get-Content $vbpPath foreach ($line in $vbp) { if ($line -match '^(Form|Module)=(.+)$') { $file = Join-Path $vbpDir $matches[2] if (-not (Test-Path $file)) { Write-Warning "缺失文件: $file" } } } Get-ChildItem $vbpDir -Filter *.frm | ForEach-Object { $lines = Get-Content $_.FullName -Encoding Default $open = ($lines | Select-String '^\s*Begin ').Count $close = ($lines | Select-String '^\s*End ').Count if ($open -ne $close) { Write-Warning "$($_.Name) 块不平衡: $open 个 Begin / $close 个 End" } $frxRefs = ($lines | Select-String '\.frx":|\.[Ww][Aa][Vv]":|\.ico":') if ($frxRefs) { Write-Host "$($_.Name) 包含资源引用,请确认 .frx 存在且未改名" } }-Encoding Default这个参数值得注意。VB6 在中文系统下保存 .frm 时常用 GB2312 编码,PowerShell 5 默认按 UTF-8 读取会乱码,这一行能避免误判。脚本只做文本级检查,块平衡不保证语法正确,但 Begin/End 不配对通常意味着资源提取时窗体被截断,需要回到提取步骤重新导出。
6.3 反编译后必做的差分验证
反编译产物绝对不能直接当源码投进生产。我的固定流程是:用原 exe 跑一遍典型业务路径,记录窗口标题、对话框文案、输出文件的格式;再拿还原工程编译出的新 exe 跑同一批输入,对比结果。这个对拍测试不用做成自动化,重点是核心业务路径,比如登录、保存、导出、统计。P-Code 反编译的流程结构可信度较高,但边界条件和超时逻辑容易还原出错,对拍时多留意 If 条件里比较的数值。
对 Native Code 目标,反编译产物更接近“带注释的汇编”,维护策略完全不同,通常只提取窗体并手工重写业务逻辑,不要试图恢复完整源码。经历过几次反编译维护任务后,我的血泪经验是:看到 exe 先探测、再提取窗体、后反编译逻辑,这个顺序不能反。反了就是拿 P-Code 模式硬啃 Native 目标,一上午过去只得到一堆汇编,最终还要回到起点。希望帮到你。
本文还有配套的精品资源,点击获取