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