vb6decompiler 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/PKG-INFO +317 -279
  2. vb6decompiler-0.2.0/vb6decompiler.egg-info/PKG-INFO → vb6decompiler-0.2.2/README.md +290 -279
  3. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/pyproject.toml +11 -1
  4. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/setup.cfg +4 -4
  5. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/tests/test_core.py +1 -1
  6. vb6decompiler-0.2.2/tests/test_decompiler_scripts.py +119 -0
  7. vb6decompiler-0.2.0/README.md → vb6decompiler-0.2.2/vb6decompiler.egg-info/PKG-INFO +58 -1
  8. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler.egg-info/SOURCES.txt +1 -0
  9. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/LICENSE +0 -0
  10. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/tests/test_blob.py +0 -0
  11. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/tests/test_cli.py +0 -0
  12. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/tests/test_datareport.py +0 -0
  13. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/tests/test_frm.py +0 -0
  14. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/tests/test_hygiene.py +0 -0
  15. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/tests/test_target.py +0 -0
  16. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler/__init__.py +0 -0
  17. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler/blob.py +0 -0
  18. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler/cli.py +0 -0
  19. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler/datareport.py +0 -0
  20. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler/frm.py +0 -0
  21. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler/literals.py +0 -0
  22. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler/mask.py +0 -0
  23. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler/pe.py +0 -0
  24. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler/procs.py +0 -0
  25. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler/sqlstitch.py +0 -0
  26. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler.egg-info/dependency_links.txt +0 -0
  27. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler.egg-info/entry_points.txt +0 -0
  28. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler.egg-info/requires.txt +0 -0
  29. {vb6decompiler-0.2.0 → vb6decompiler-0.2.2}/vb6decompiler.egg-info/top_level.txt +0 -0
@@ -1,279 +1,317 @@
1
- Metadata-Version: 2.4
2
- Name: vb6decompiler
3
- Version: 0.2.0
4
- Summary: Evidence-based recovery of native-compiled VB6 programs: procedures, literals, SQL, form blobs, DataReports
5
- Author: vb6decompiler authors
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/47Hunter47/vb6decompiler
8
- Keywords: vb6,reverse-engineering,decompiler,pe,sql-server
9
- Requires-Python: >=3.11
10
- Description-Content-Type: text/markdown
11
- License-File: LICENSE
12
- Provides-Extra: pe
13
- Requires-Dist: pefile>=2023.2.7; extra == "pe"
14
- Provides-Extra: asm
15
- Requires-Dist: capstone>=5; extra == "asm"
16
- Provides-Extra: dev
17
- Requires-Dist: pytest>=8; extra == "dev"
18
- Dynamic: license-file
19
-
20
- # vb6decompiler
21
-
22
- Recovering what a native-compiled VB6 program actually does, with evidence.
23
-
24
- Commercial VB6 decompilers stop at one of two walls: they give you forms without
25
- code, or code without the semantics that make it portable. This toolkit was built
26
- while recovering a large native-compiled VB6 application (111 MB, ~34,000
27
- procedures) whose source was lost. Every claim below is a measurement, and every
28
- measurement has a command that reproduces it.
29
-
30
- The point is not another disassembler. VB6 is not plain x86 at the level that
31
- matters: strings are length-prefixed `BSTR`s stored inside `.text`, procedures are
32
- entered through a fixed runtime prologue, most calls go through `MSVBVM60` vtable
33
- dispatch, forms are serialised into the executable as binary property blobs, and
34
- reports live in separate VB6 DLLs as `DataReport` blobs. Off-the-shelf tools read
35
- none of that. These modules do.
36
-
37
- Nothing here points at the program it was developed against: no target name, no
38
- host, no database name, no account, no credential, no absolute install path.
39
- **You supply every path.**
40
-
41
- ## What it does
42
-
43
- | Stage | Command | Evidence it produces |
44
- |---|---|---|
45
- | PE surface of an install | `vb6d pe <dir>` | imports, exports, VB6 runtime dependency, ActiveX/COM classification |
46
- | Procedure inventory | `vb6d procs <exe>` | object table: objects and method counts, entry points verified by prologue |
47
- | String / SQL harvest | `vb6d literals <exe or listing dir>` | `BSTR` (UTF-16LE) literals the ASCII scanners miss, with code addresses |
48
- | SQL reconstruction | `vb6d stitch <dir or listing>` | fragments joined into whole statements, with explicit "not provable statically" buckets |
49
- | Form design | `vb6d frm <dir>` | controls, geometry, `Visible`, control arrays, `SSTab` pages from `.frm` text |
50
- | Form geometry from binary | `vb6d blob <exe> --validate` | geometry for forms that have no `.frm`, calibrated against `.frm` ground truth |
51
- | DataReport layout | `vb6d datareport <dll or dir>` | report sections, controls, captions recovered from the runtime blobs |
52
- | Form icons from `.frx` | `scripts/frx_icon.py <frx dir> <out> [--validate <frm dir>]` | standalone `.ico` per form, extracted from the leading ICON record without the executable; coverage cross-checked against `.frm` Icon references |
53
- | Credential gate | `vb6d mask <dir>` | files rewritten in place: connection strings redacted, then an audit counting what is still live |
54
-
55
- ## You fill these in
56
-
57
- Every entry point takes a path; nothing is baked in. Real-target tests read five
58
- variables and skip when they are unset.
59
-
60
- | Variable | What you put in it |
61
- |---|---|
62
- | `VB6D_TARGET` | your VB6 executable, e.g. `D:/App/App.exe` or `/opt/app/App.exe` |
63
- | `VB6D_LISTINGS` | a directory of decompiler output: listing text carrying `'VA` address comments (the shape `docs/formats.md` §4 reads), plus `.frm` and `.frx` files |
64
- | `VB6D_FRM_DIR` | a directory holding `.frm` design text — ground truth for `--validate` |
65
- | `VB6D_REPORT_DIR` | the install directory holding the report DLLs |
66
- | `VB6D_SCRUB_DENYLIST` | optional: a private file, one name per line — names you refuse to publish. The hygiene test fails if any of them appears in this repository. |
67
-
68
- Set them for one command, or export them for a session:
69
-
70
- ```bash
71
- # bash / zsh
72
- export VB6D_TARGET="/opt/app/App.exe"
73
- export VB6D_LISTINGS="$HOME/work/decompile"
74
- export VB6D_FRM_DIR="$HOME/work/decompile"
75
- export VB6D_REPORT_DIR="/opt/app"
76
- ```
77
-
78
- ```powershell
79
- # PowerShell
80
- $env:VB6D_TARGET = "D:/App/App.exe"
81
- $env:VB6D_LISTINGS = "D:/work/decompile"
82
- $env:VB6D_FRM_DIR = "D:/work/decompile"
83
- $env:VB6D_REPORT_DIR = "D:/App"
84
- ```
85
-
86
- ## Why not an existing tool
87
-
88
- Measured on the same target (`docs/tool-matrix.md` has the full table, including
89
- how each number was obtained):
90
-
91
- * **Semi-VB-Decompiler** — the only free tool that recovers procedure *bodies*. It
92
- is single-threaded, overflows its `Integer` procedure counter above 32,767
93
- procedures (`Error 6`), needs a PE `LAA` bit patch to use more than 2 GB, and
94
- writes one event name for every body in a form. Bodies are pseudo-source
95
- (`var_64`, `arg_C`), not compilable code.
96
- * **VBReFormer** — forms and structure, expression bodies withheld without a
97
- licence; cannot even accept the 111 MB executable (transport limit).
98
- * **Ghidra / Rizin / IDA** — no VB6 knowledge: no `BSTR` literals, no procedure
99
- table, no form blobs, no `DataReport` blobs.
100
- * **Generic RE engines** — correct for C/C++/Rust/Go, blind to VB6 layout. One such
101
- engine found 6,875 functions of 34,159 (20 %, stopped at its own instruction cap)
102
- and 59 strings with zero SQL, because its string scanner does not read UTF-16LE
103
- literals.
104
-
105
- None of these is wrong; they are aimed at C/C++/Rust/Go binaries. The gap this
106
- repository fills is VB6-specific structure, and it fills it with reproducible
107
- counts rather than vendor percentages.
108
-
109
- ## Install
110
-
111
- ```
112
- git clone <this repo> && cd vb6decompiler
113
- python -m pip install -e .
114
- ```
115
-
116
- Python 3.11+. **Nothing outside the standard library is required** — every parser
117
- here is hand-written, which is the point (a library wrapper would hide the layout).
118
- `pefile` and `capstone` are optional and used only as a second opinion while
119
- developing:
120
-
121
- ```
122
- python -m pip install -e ".[pe,asm]"
123
- ```
124
-
125
- ## Coding agents
126
-
127
- The repository ships as a plugin for coding agents as well as a pip package, so
128
- an agent can drive `vb6d` without a tutorial:
129
-
130
- ```
131
- claude plugin marketplace add 47Hunter47/vb6decompiler
132
- claude plugin install vb6decompiler@vb6decompiler
133
- ```
134
-
135
- One skill lives at `.agents/skills/vb6decompiler/SKILL.md` — the location both
136
- Claude Code and Codex read — and says when the tool applies, how to parse its
137
- output and which traps to avoid; the plugin manifests point at that single copy.
138
- Agents editing the repository itself follow `AGENTS.md`.
139
-
140
- ## Quick start
141
-
142
- ```
143
- vb6d pe <install dir> # what is this install made of
144
- vb6d procs <app.exe> # object table: objects, method counts
145
- vb6d procs <app.exe> --validate # are the entry points real prologues
146
- vb6d procs <app.exe> --names <listing dir> # attach decompiler names/addresses
147
- vb6d literals <app.exe> # BSTR/UTF-16 literals with addresses
148
- vb6d literals <listing dir> --sql-only # literals a decompiler listing produced
149
- vb6d stitch <listing dir> # join fragments, classify what stayed open
150
- vb6d frm <listing dir> # .frm controls, geometry, arrays, SSTab
151
- vb6d blob <app.exe> --validate --frm-dir <frm dir>
152
- vb6d datareport <install dir> # report layouts from the report DLLs
153
- vb6d mask . # mask credentials in your artefacts
154
- vb6d mask . --dry-run # report only; exit 1 if anything is live
155
- ```
156
-
157
- Exit codes: 0 = completed, whatever it found; 1 = mask audit not clean; 2 =
158
- unusable input (one line on stderr, no traceback — set `VB6D_DEBUG=1` for the
159
- traceback when reporting a bug).
160
-
161
- What the reference build (111 MB, 802 forms) produced while this toolkit was
162
- developed, so you can tell at a glance whether your own numbers look sane:
163
-
164
- | Stage | Result |
165
- |---|---|
166
- | object table | 834 objects, 34,415 methods, 0.4 s |
167
- | literals from the image | 167,564 in 4.6 s (6,171 with a SQL verb) |
168
- | literals from a listing | 441,607 in 12,480 procedures |
169
- | statements | 10,691 starts → 8,745 closed in 4,109 procedures, 494 tables |
170
- | `.frm` design | 369 forms, 30,000 controls, 126 forms with control arrays |
171
- | form blobs | 801 blobs, 24,967/24,967 controls (100 %), 367/369 forms |
172
- | DataReports | 437 layouts (413 in report DLLs + 24 embedded in the executable) |
173
-
174
- A different program legitimately gives different numbers. The tests assert
175
- invariants and lower bounds, not these totals.
176
-
177
- ## Using it well
178
-
179
- Run the stages in pipeline order; each one tells you whether the next is worth
180
- running. `docs/pipeline.md` states what every stage may claim and what it must
181
- never be used to claim.
182
-
183
- 1. `vb6d pe <install dir>` first. It costs seconds and says what you have: which
184
- binaries carry VB6 code, which are redistributables, where the report DLLs are.
185
- 2. `vb6d procs`, then `--validate` before trusting anything downstream. Method
186
- counts are cheap; entry points proven by their runtime prologue are evidence.
187
- Addresses and names only exist once a decompiler listing is merged in with
188
- `--names`.
189
- 3. `vb6d literals` on the executable gives UTF-16 text with addresses; on a
190
- listing it gives procedure ownership, and that ownership is what makes
191
- `vb6d stitch` possible at all.
192
- 4. `vb6d stitch` reports what stayed open as open. The gap buckets are not noise:
193
- `OPEN_QUOTE` means a run-time value is missing, and no smarter regex closes
194
- it.
195
- 5. `vb6d frm` reads the design text; `vb6d blob --validate` calibrates the binary
196
- reader against it. Trust blob geometry for forms that have no `.frm` exactly
197
- as far as that calibration holds.
198
- 6. Count embedded reports when you run `vb6d datareport`. A scan limited to
199
- `rpt*.dll` misses 24 of the 437 layouts the reference build carries inside its
200
- own executable.
201
- 7. `vb6d mask . --dry-run` before sharing anything you produced. A non-zero exit
202
- means the artefacts are not publishable, not that you should retry quietly.
203
-
204
- Things that cost days to learn:
205
-
206
- * Procedure names do not exist in a native build. A decompiler's names are
207
- guesses and `.frm` event names repeat; treat every name as a hint and every
208
- prologue-verified address as the better fact.
209
- * If a string scanner reports zero SQL on a VB6 binary, ask whether it reads
210
- UTF-16LE at all. One engine reported 59 strings and zero SQL on a build that
211
- holds 167,564 `BSTR` literals, 6,171 of them with a SQL verb.
212
- * Compare shapes, not totals: literals per procedure, closed versus open
213
- statements, controls per form. Totals scale with the program; the tests assert
214
- invariants for the same reason.
215
- * When a free decompiler refuses a large executable, run `scripts/semi_laa.py`
216
- on a copy. The Large Address Aware bit is a property of the file, not of the
217
- tool's competence.
218
- * Three-state flags such as `Visible` and `Locked` have defaults that look like
219
- explicit values, and `docs/pitfalls.md` lists that class of trap with the
220
- measurement that proved each one.
221
-
222
- ## Honest limits
223
-
224
- * Procedure **names** are not recoverable from a native build; only addresses and
225
- the names a decompiler guessed.
226
- * Bodies recovered by a third-party decompiler are pseudo-source. Expression-level
227
- fidelity does not exist in any free tool measured here.
228
- * SQL that is built at run time (concatenated from variables) cannot be closed
229
- statically. `vb6d stitch` reports those gaps as gaps instead of guessing.
230
- * Geometry recovered from the binary is calibrated, not exact for forms that
231
- changed after the `.frm` in hand was written; `--validate` says so per form.
232
- * The toolkit is calibrated on native-compiled builds; a P-Code build is outside
233
- that calibration and was not tested here. If `vb6d procs --validate` reports no
234
- real prologues in your build, suspect a P-Code compile before suspecting the
235
- toolkit.
236
- * The parsers know the grammar of the reference build. Another program ships
237
- different controls, property sets and report sections, so expect more `unknown`
238
- lines than the reference numbers show. An un-calibrated construct is reported as
239
- unknown, never guessed (`docs/formats.md` §7), and no stage drops a file
240
- silently: failures are counted and named.
241
-
242
- ## Layout
243
-
244
- ```
245
- vb6decompiler/ the library (one module per stage, stdlib only except pefile/capstone)
246
- scripts/ standalone helpers (semi_laa.py: set the LAA bit on a copy;
247
- frx_icon.py: extract the leading ICON record of a .frx;
248
- win32_gui_probe.py: drive vendor GUI tools headlessly when the
249
- measurement only exists in a window)
250
- tests/ unit tests + target tests that skip unless VB6D_TARGET is set
251
- docs/ formats.md (structures), pitfalls.md (traps), tool-matrix.md,
252
- pipeline.md (stages and what each may claim), RULES.md
253
- .agents/skills/ the vb6decompiler skill — what a coding agent installs
254
- .claude-plugin/ Claude Code plugin + marketplace manifests (skills point at it)
255
- .codex-plugin/ Codex plugin manifest (same single skill copy)
256
- ```
257
-
258
- Run the tests:
259
-
260
- ```
261
- pip install -e ".[dev]"
262
- pytest -q # unit tests, no target needed
263
- VB6D_TARGET="C:/path/App.exe" pytest -q # + real-target invariants
264
- VB6D_LISTINGS="C:/path/decompile" pytest -q # + listing/stitching tests
265
- VB6D_FRM_DIR="C:/path/decompile" pytest -q # + blob calibration
266
- VB6D_REPORT_DIR="C:/path/install" pytest -q # + DataReport parsing
267
- ```
268
-
269
- `docs/formats.md` is the structure reference: every offset, every grammar, and the
270
- fields that look documented but are not what they claim (`lpMethods` in a native
271
- build, `lpGuiTable`). `docs/pitfalls.md` is the second most useful file: it lists
272
- the traps that cost days (double-`Integer` overflow, `OriginalFirstThunk` vs
273
- `FirstThunk`, CP1254 vs UTF-16, `Visible` defaults, blob property sorting) with the
274
- measurement that
275
- proved each one.
276
-
277
- ## Licence
278
-
279
- MIT. See `LICENSE`.
1
+ Metadata-Version: 2.4
2
+ Name: vb6decompiler
3
+ Version: 0.2.2
4
+ Summary: Evidence-based recovery of native-compiled VB6 programs: procedures, literals, SQL, form blobs, DataReports
5
+ Author: vb6decompiler authors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/47Hunter47/vb6decompiler
8
+ Keywords: vb6,reverse-engineering,decompiler,pe,sql-server
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Security
16
+ Classifier: Topic :: Software Development :: Disassemblers
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Provides-Extra: pe
21
+ Requires-Dist: pefile>=2023.2.7; extra == "pe"
22
+ Provides-Extra: asm
23
+ Requires-Dist: capstone>=5; extra == "asm"
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # vb6decompiler
29
+
30
+ [![PyPI](https://img.shields.io/pypi/v/vb6decompiler)](https://pypi.org/project/vb6decompiler/)
31
+ [![Tests](https://github.com/47Hunter47/vb6decompiler/actions/workflows/test.yml/badge.svg)](https://github.com/47Hunter47/vb6decompiler/actions/workflows/test.yml)
32
+ [![Python](https://img.shields.io/pypi/pyversions/vb6decompiler)](https://pypi.org/project/vb6decompiler/)
33
+
34
+ Recovering what a native-compiled VB6 program actually does, with evidence.
35
+
36
+ Commercial VB6 decompilers stop at one of two walls: they give you forms without
37
+ code, or code without the semantics that make it portable. This toolkit was built
38
+ while recovering a large native-compiled VB6 application (111 MB, ~34,000
39
+ procedures) whose source was lost. Every claim below is a measurement, and every
40
+ measurement has a command that reproduces it.
41
+
42
+ The point is not another disassembler. VB6 is not plain x86 at the level that
43
+ matters: strings are length-prefixed `BSTR`s stored inside `.text`, procedures are
44
+ entered through a fixed runtime prologue, most calls go through `MSVBVM60` vtable
45
+ dispatch, forms are serialised into the executable as binary property blobs, and
46
+ reports live in separate VB6 DLLs as `DataReport` blobs. Off-the-shelf tools read
47
+ none of that. These modules do.
48
+
49
+ Nothing here points at the program it was developed against: no target name, no
50
+ host, no database name, no account, no credential, no absolute install path.
51
+ **You supply every path.**
52
+
53
+ ## What it does
54
+
55
+ | Stage | Command | Evidence it produces |
56
+ |---|---|---|
57
+ | PE surface of an install | `vb6d pe <dir>` | imports, exports, VB6 runtime dependency, ActiveX/COM classification |
58
+ | Procedure inventory | `vb6d procs <exe>` | object table: objects and method counts, entry points verified by prologue |
59
+ | String / SQL harvest | `vb6d literals <exe or listing dir>` | `BSTR` (UTF-16LE) literals the ASCII scanners miss, with code addresses |
60
+ | SQL reconstruction | `vb6d stitch <dir or listing>` | fragments joined into whole statements, with explicit "not provable statically" buckets |
61
+ | Form design | `vb6d frm <dir>` | controls, geometry, `Visible`, control arrays, `SSTab` pages from `.frm` text |
62
+ | Form geometry from binary | `vb6d blob <exe> --validate` | geometry for forms that have no `.frm`, calibrated against `.frm` ground truth |
63
+ | DataReport layout | `vb6d datareport <dll or dir>` | report sections, controls, captions recovered from the runtime blobs |
64
+ | Form icons from `.frx` | `scripts/frx_icon.py <frx dir> <out> [--validate <frm dir>]` | standalone `.ico` per form, extracted from the leading ICON record without the executable; coverage cross-checked against `.frm` Icon references |
65
+ | Second-opinion decompiler run | `scripts/ghidra_bridge.py --ghidra <dir> --bin <exe> --targets <csv> --out <dir>` | one pseudo-C file per procedure: our bounds and names, engine ceilings as flags, the JDK requirement read from the install before anything is imported |
66
+ | Literal visibility check | `scripts/literal_visibility.py --corpus <dir> --literals <csv> --out <csv>` | measured visible / catalogued per procedure — an address token (`DAT_`, `PTR_PTR_`) **or** inlined text counts, with a denominator instead of a hunch |
67
+ | Pseudo-C inventories | `scripts/pseudoc_inventory.py --corpus <dir> --out-dir <dir>` | literal order, indirect-call object+slot, ordered calls with line numbers, branch conditions, unnamed-global census |
68
+ | Credential gate | `vb6d mask <dir>` | files rewritten in place: connection strings redacted, then an audit counting what is still live |
69
+
70
+ The `scripts/` helpers are in the repository, not in the pip install — take a checkout
71
+ for those. The three Ghidra-side helpers additionally expect a **local** Ghidra install
72
+ and `pip install pyghidra`; the library and the `vb6d` CLI stay standard-library only.
73
+
74
+ ## You fill these in
75
+
76
+ Every entry point takes a path; nothing is baked in. Real-target tests read five
77
+ variables and skip when they are unset.
78
+
79
+ | Variable | What you put in it |
80
+ |---|---|
81
+ | `VB6D_TARGET` | your VB6 executable, e.g. `D:/App/App.exe` or `/opt/app/App.exe` |
82
+ | `VB6D_LISTINGS` | a directory of decompiler output: listing text carrying `'VA` address comments (the shape `docs/formats.md` §4 reads), plus `.frm` and `.frx` files |
83
+ | `VB6D_FRM_DIR` | a directory holding `.frm` design text — ground truth for `--validate` |
84
+ | `VB6D_REPORT_DIR` | the install directory holding the report DLLs |
85
+ | `VB6D_SCRUB_DENYLIST` | optional: a private file, one name per line — names you refuse to publish. The hygiene test fails if any of them appears in this repository. |
86
+
87
+ Set them for one command, or export them for a session:
88
+
89
+ ```bash
90
+ # bash / zsh
91
+ export VB6D_TARGET="/opt/app/App.exe"
92
+ export VB6D_LISTINGS="$HOME/work/decompile"
93
+ export VB6D_FRM_DIR="$HOME/work/decompile"
94
+ export VB6D_REPORT_DIR="/opt/app"
95
+ ```
96
+
97
+ ```powershell
98
+ # PowerShell
99
+ $env:VB6D_TARGET = "D:/App/App.exe"
100
+ $env:VB6D_LISTINGS = "D:/work/decompile"
101
+ $env:VB6D_FRM_DIR = "D:/work/decompile"
102
+ $env:VB6D_REPORT_DIR = "D:/App"
103
+ ```
104
+
105
+ ## Why not an existing tool
106
+
107
+ Measured on the same target (`docs/tool-matrix.md` has the full table, including
108
+ how each number was obtained):
109
+
110
+ * **Semi-VB-Decompiler** — the only free tool that recovers procedure *bodies*. It
111
+ is single-threaded, overflows its `Integer` procedure counter above 32,767
112
+ procedures (`Error 6`), needs a PE `LAA` bit patch to use more than 2 GB, and
113
+ writes one event name for every body in a form. Bodies are pseudo-source
114
+ (`var_64`, `arg_C`), not compilable code.
115
+ * **VBReFormer** — forms and structure, expression bodies withheld without a
116
+ licence; cannot even accept the 111 MB executable (transport limit).
117
+ * **Ghidra / Rizin / IDA** — no VB6 knowledge: no `BSTR` literals, no procedure
118
+ table, no form blobs, no `DataReport` blobs.
119
+ * **Generic RE engines** — correct for C/C++/Rust/Go, blind to VB6 layout. One such
120
+ engine found 6,875 functions of 34,159 (20 %, stopped at its own instruction cap)
121
+ and 59 strings with zero SQL, because its string scanner does not read UTF-16LE
122
+ literals.
123
+
124
+ None of these is wrong; they are aimed at C/C++/Rust/Go binaries. The gap this
125
+ repository fills is VB6-specific structure, and it fills it with reproducible
126
+ counts rather than vendor percentages.
127
+
128
+ ## Install
129
+
130
+ ```
131
+ pip install vb6decompiler
132
+ ```
133
+
134
+ Or work from a checkout:
135
+
136
+ ```
137
+ git clone <this repo> && cd vb6decompiler
138
+ python -m pip install -e .
139
+ ```
140
+
141
+ Python 3.11+. **Nothing outside the standard library is required** — every parser
142
+ here is hand-written, which is the point (a library wrapper would hide the layout).
143
+ `pefile` and `capstone` are optional and used only as a second opinion while
144
+ developing:
145
+
146
+ ```
147
+ python -m pip install -e ".[pe,asm]"
148
+ ```
149
+
150
+ ## Coding agents
151
+
152
+ The repository ships as a plugin for coding agents as well as a pip package, so
153
+ an agent can drive `vb6d` without a tutorial:
154
+
155
+ ```
156
+ claude plugin marketplace add 47Hunter47/vb6decompiler
157
+ claude plugin install vb6decompiler@vb6decompiler
158
+ ```
159
+
160
+ One skill lives at `.agents/skills/vb6decompiler/SKILL.md` — the location both
161
+ Claude Code and Codex read — and says when the tool applies, how to parse its
162
+ output and which traps to avoid; the plugin manifests point at that single copy.
163
+ Agents editing the repository itself follow `AGENTS.md`.
164
+
165
+ ## Quick start
166
+
167
+ ```
168
+ vb6d pe <install dir> # what is this install made of
169
+ vb6d procs <app.exe> # object table: objects, method counts
170
+ vb6d procs <app.exe> --validate # are the entry points real prologues
171
+ vb6d procs <app.exe> --names <listing dir> # attach decompiler names/addresses
172
+ vb6d literals <app.exe> # BSTR/UTF-16 literals with addresses
173
+ vb6d literals <listing dir> --sql-only # literals a decompiler listing produced
174
+ vb6d stitch <listing dir> # join fragments, classify what stayed open
175
+ vb6d frm <listing dir> # .frm controls, geometry, arrays, SSTab
176
+ vb6d blob <app.exe> --validate --frm-dir <frm dir>
177
+ vb6d datareport <install dir> # report layouts from the report DLLs
178
+ vb6d mask . # mask credentials in your artefacts
179
+ vb6d mask . --dry-run # report only; exit 1 if anything is live
180
+ ```
181
+
182
+ Exit codes: 0 = completed, whatever it found; 1 = mask audit not clean; 2 =
183
+ unusable input (one line on stderr, no traceback — set `VB6D_DEBUG=1` for the
184
+ traceback when reporting a bug).
185
+
186
+ What the reference build (111 MB, 802 forms) produced while this toolkit was
187
+ developed, so you can tell at a glance whether your own numbers look sane:
188
+
189
+ | Stage | Result |
190
+ |---|---|
191
+ | object table | 834 objects, 34,415 methods, 0.4 s |
192
+ | literals from the image | 167,564 in 4.6 s (6,171 with a SQL verb) |
193
+ | literals from a listing | 441,607 in 12,480 procedures |
194
+ | statements | 10,691 starts → 8,745 closed in 4,109 procedures, 494 tables |
195
+ | `.frm` design | 369 forms, 30,000 controls, 126 forms with control arrays |
196
+ | form blobs | 801 blobs, 24,967/24,967 controls (100 %), 367/369 forms |
197
+ | DataReports | 437 layouts (413 in report DLLs + 24 embedded in the executable) |
198
+
199
+ A different program legitimately gives different numbers. The tests assert
200
+ invariants and lower bounds, not these totals.
201
+
202
+ ## Using it well
203
+
204
+ Run the stages in pipeline order; each one tells you whether the next is worth
205
+ running. `docs/pipeline.md` states what every stage may claim and what it must
206
+ never be used to claim.
207
+
208
+ 1. `vb6d pe <install dir>` first. It costs seconds and says what you have: which
209
+ binaries carry VB6 code, which are redistributables, where the report DLLs are.
210
+ 2. `vb6d procs`, then `--validate` before trusting anything downstream. Method
211
+ counts are cheap; entry points proven by their runtime prologue are evidence.
212
+ Addresses and names only exist once a decompiler listing is merged in with
213
+ `--names`.
214
+ 3. `vb6d literals` on the executable gives UTF-16 text with addresses; on a
215
+ listing it gives procedure ownership, and that ownership is what makes
216
+ `vb6d stitch` possible at all.
217
+ 4. `vb6d stitch` reports what stayed open as open. The gap buckets are not noise:
218
+ `OPEN_QUOTE` means a run-time value is missing, and no smarter regex closes
219
+ it.
220
+ 5. `vb6d frm` reads the design text; `vb6d blob --validate` calibrates the binary
221
+ reader against it. Trust blob geometry for forms that have no `.frm` exactly
222
+ as far as that calibration holds.
223
+ 6. Count embedded reports when you run `vb6d datareport`. A scan limited to
224
+ `rpt*.dll` misses 24 of the 437 layouts the reference build carries inside its
225
+ own executable.
226
+ 7. `vb6d mask . --dry-run` before sharing anything you produced. A non-zero exit
227
+ means the artefacts are not publishable, not that you should retry quietly.
228
+
229
+ Things that cost days to learn:
230
+
231
+ * Procedure names do not exist in a native build. A decompiler's names are
232
+ guesses and `.frm` event names repeat; treat every name as a hint and every
233
+ prologue-verified address as the better fact.
234
+ * If a string scanner reports zero SQL on a VB6 binary, ask whether it reads
235
+ UTF-16LE at all. One engine reported 59 strings and zero SQL on a build that
236
+ holds 167,564 `BSTR` literals, 6,171 of them with a SQL verb.
237
+ * Compare shapes, not totals: literals per procedure, closed versus open
238
+ statements, controls per form. Totals scale with the program; the tests assert
239
+ invariants for the same reason.
240
+ * When a free decompiler refuses a large executable, run `scripts/semi_laa.py`
241
+ on a copy. The Large Address Aware bit is a property of the file, not of the
242
+ tool's competence.
243
+ * Three-state flags such as `Visible` and `Locked` have defaults that look like
244
+ explicit values, and `docs/pitfalls.md` lists that class of trap with the
245
+ measurement that proved each one.
246
+
247
+ ## Honest limits
248
+
249
+ * Procedure **names** are not recoverable from a native build; only addresses and
250
+ the names a decompiler guessed.
251
+ * Bodies recovered by a third-party decompiler are pseudo-source. Expression-level
252
+ fidelity does not exist in any free tool measured here.
253
+ * SQL that is built at run time (concatenated from variables) cannot be closed
254
+ statically. `vb6d stitch` reports those gaps as gaps instead of guessing.
255
+ * An indirect call through an object global (`(*DAT_x)[slot]`) has no statically
256
+ resolvable target either: the bytes stored at such a global are 0 or a small
257
+ non-address token, never a vtable pointer, and a VB6 symbol record carries no
258
+ name for the global. `scripts/pseudoc_inventory.py` counts them and ranks them
259
+ for a run-time trace; nothing in this repo closes them statically.
260
+ * Geometry recovered from the binary is calibrated, not exact for forms that
261
+ changed after the `.frm` in hand was written; `--validate` says so per form.
262
+ * The toolkit is calibrated on native-compiled builds; a P-Code build is outside
263
+ that calibration and was not tested here. If `vb6d procs --validate` reports no
264
+ real prologues in your build, suspect a P-Code compile before suspecting the
265
+ toolkit.
266
+ * The parsers know the grammar of the reference build. Another program ships
267
+ different controls, property sets and report sections, so expect more `unknown`
268
+ lines than the reference numbers show. An un-calibrated construct is reported as
269
+ unknown, never guessed (`docs/formats.md` §7), and no stage drops a file
270
+ silently: failures are counted and named.
271
+
272
+ ## Layout
273
+
274
+ ```
275
+ vb6decompiler/ the library (one module per stage, stdlib only except pefile/capstone)
276
+ scripts/ standalone helpers (semi_laa.py: set the LAA bit on a copy;
277
+ frx_icon.py: extract the leading ICON record of a .frx;
278
+ win32_gui_probe.py: drive vendor GUI tools headlessly when the
279
+ measurement only exists in a window; ghidra_bridge.py +
280
+ literal_visibility.py + pseudoc_inventory.py: run a generic
281
+ decompiler over a validated procedure list, measure what its
282
+ output actually shows, and harvest order/slot/call inventories)
283
+ tests/ unit tests + target tests that skip unless VB6D_TARGET is set
284
+ docs/ formats.md (structures), pitfalls.md (traps), tool-matrix.md,
285
+ pipeline.md (stages and what each may claim), RULES.md
286
+ .agents/skills/ the vb6decompiler skill — what a coding agent installs
287
+ .claude-plugin/ Claude Code plugin + marketplace manifests (skills point at it)
288
+ .codex-plugin/ Codex plugin manifest (same single skill copy)
289
+ ```
290
+
291
+ Run the tests:
292
+
293
+ ```
294
+ pip install -e ".[dev]"
295
+ pytest -q # unit tests, no target needed
296
+ VB6D_TARGET="C:/path/App.exe" pytest -q # + real-target invariants
297
+ VB6D_LISTINGS="C:/path/decompile" pytest -q # + listing/stitching tests
298
+ VB6D_FRM_DIR="C:/path/decompile" pytest -q # + blob calibration
299
+ VB6D_REPORT_DIR="C:/path/install" pytest -q # + DataReport parsing
300
+ VB6D_SCRUB_DENYLIST="C:/path/names.txt" pytest -q # + scan for your target own names
301
+ ```
302
+
303
+ The hygiene test needs no target: it looks for private addresses, home paths, e-mail and
304
+ UNC shares, and it adds a name scan only when you point `VB6D_SCRUB_DENYLIST` at a list
305
+ kept **outside** the repository (the names are yours, they do not belong in public).
306
+
307
+ `docs/formats.md` is the structure reference: every offset, every grammar, and the
308
+ fields that look documented but are not what they claim (`lpMethods` in a native
309
+ build, `lpGuiTable`). `docs/pitfalls.md` is the second most useful file: it lists
310
+ the traps that cost days (double-`Integer` overflow, `OriginalFirstThunk` vs
311
+ `FirstThunk`, CP1254 vs UTF-16, `Visible` defaults, blob property sorting) with the
312
+ measurement that
313
+ proved each one.
314
+
315
+ ## Licence
316
+
317
+ MIT. See `LICENSE`.