# vid repairs VMProtect imports by proving them in an emulator, and admits the rest

> vid is a Rust tool that reads a live protected Windows process, emulates the VM stub until each protected transfer resolves to a real import, and writes a new PE with a readable import table. It refuses to guess: anything it cannot prove is left alone and reported.

**colby57/vid** — An x86/x64 import recovery and PE rebuilding tool for binaries protected with VMProtect (all versions)

- Repository: https://github.com/colby57/vid
- Stars: 590 · Forks: 82
- Language: Rust
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/colby57-vid

## A tool that only rewrites what it can prove

vid, whose old name was VMP-Imports-Deobfuscator, reconstructs VMProtect-obfuscated PE imports from a live Windows process and writes a readable, loadable PE image with a new import table.

The scope statement in the README is unusually blunt for a tool in this area. It says the tool is intentionally narrow, that it repairs imports and the instructions that reference them, and that it is not a generic unpacker and does not devirtualize protected application logic. Later it repeats the boundary in the limitations list: it does not bypass anti-debugging, suspend the target, repair arbitrary packer damage, preserve overlays, or devirtualize protected program code.

That constraint is the design. The problem with recovering imports from a VMProtect stub is that the stub computes the import at run time, so a static reader sees arithmetic where a call should be. vid sidesteps the general problem by not solving it in general. It runs the protected code in an emulator, watches for the moment the stub calls an import or transfers control, and only then knows what the original instruction was supposed to be.

The result is a tool that will leave a file partly wrong and tell you so, which is the behaviour you want from something that rewrites a binary. The primary language is Rust, the licence is MIT with the text in the LICENSE file, and the only release so far is v0.1.0 on 2026-08-17, the same day as the last push.

## Building it, and the dependency list that explains the approach

The build is a plain cargo invocation from the repository root.

```powershell
rustup show
cargo build --release
.\target\release\vid.exe --help
```

The resulting executable is written to target\release\vid.exe, and there is a rust-toolchain.toml at the top of the repository, so the toolchain is pinned rather than whatever happens to be installed. Cargo.toml declares edition 2024 with rust-version 1.97, which puts a floor on the compiler you need.

The dependency list is the interesting part, because it explains the mechanism. iced-x86 is the x86 disassembler and assembler, which is what lets the tool read a protected instruction and write a replacement one. unicorn-engine provides the emulator with the arch_x86 feature, and that is the component that runs the stub. clap handles the command line, and spdlog-rs does the logging, which is where the warning output in the troubleshooting section comes from.

The windows crate is included with a specific set of features, and the list explains the tool's behaviour in the field: Win32_System_Diagnostics_Debug and Win32_System_Diagnostics_ToolHelp for process and module enumeration, Win32_System_Threading and Win32_System_SystemInformation, and Win32_Foundation. The README says the tool intentionally requests only process query and memory-read permissions, and the feature list is consistent with that claim.

So the architecture in one line: read the live process, disassemble the protected section, emulate it until the transfer resolves, patch the bytes, and write a new PE.

## The workflow: attach, let the stub settle, then pick the sections

The quick start in the README has a step that is easy to skim past and important to follow. You start or attach to the protected program and let VMProtect finish initializing its protected imports. Then you inspect the available sections.

```powershell
.\target\release\vid.exe --pid 10856 --list-sections
```

The --list-sections option prints section numbers, names, RVAs, sizes, permissions, characteristics and entropy, then exits. That last field, entropy, is the selection heuristic. If one to three executable sections have entropy above 7.0, vid can select them automatically.

```powershell
.\target\release\vid.exe --pid 10856 --new-entry-rva 0x1000
```

And the naming rule is fixed: for sample.exe the default output name is sample.vid.exe. The --new-entry-rva option changes AddressOfEntryPoint in the rebuilt file only, and the README is explicit that it does not resume or otherwise change the live process.

That last point is worth pausing on, because it is the sort of thing people assume works the other way round. The tool is a reader of the live process and a writer of a new file. It does not patch the running program, and it cannot make a protected process continue past its own initialisation.

## What the rewrite table actually says about the VM stub

The table of protected behaviour and rebuilt form is the closest thing to a specification of what the tool does, and it is more revealing than a feature list.

When the VM stub ultimately calls an import and returns, the rebuilt form is a call through the import address table. When the stub removes the caller's return and transfers permanently, the rebuilt form is an unconditional jump through the import address table. When the stub returns an imported address in a register, the rebuilt form is a move of that import address into the register.

Then there are the two cases where the import is already known and only the address changed. An existing x86 absolute IAT operand becomes the same instruction with the rebuilt absolute import address, and an existing x64 RIP-relative IAT operand becomes the same instruction with a new RIP-relative displacement. That distinction between the two architectures is exactly the kind of detail that a tool gets wrong if it is written without the assembler in the loop.

The last row is the one that shows the philosophy. Random protected bytes after a rewritten transfer get an explicit jump to the proven continuation, and the unreachable residue is left untouched. The tool does not try to make the whole section look clean. It proves a continuation, jumps to it, and leaves everything else alone.

The before and after screenshot in the README was captured from IDA Pro 9.3 with a real x64 MASM executable with PDB information, processed by VMProtect Ultimate 3.9.4 build 2285 with Import Protection enabled, recovered from its running process and launched successfully after rebuilding. The functions stay at the same RVAs beginning at 0x1017 in both halves. Absolute image bases differ because the protected process was captured after ASLR, which the README explains rather than hides.

## Partial recovery is reported in three separate numbers

The troubleshooting section is the most useful part of the README for anyone who will use this tool on a real sample, because it documents what failure looks like.

```text
[warn] Partial VM recovery | rewritten 2233 of 2332 | left unchanged 99 | unresolved destinations 1
[warn] Direct VM rewrites | applied 2232 of 2233 | left unchanged 1
[warn] Conventional rewrites | applied 133 of 134 | covered by protected rewrites 1
```

The README explains each line. The first means emulation could not prove the import or continuation for every protected transfer. The second means an import was identified but its direct IAT instruction could not fit safely at the original location or could not reproduce the observed stack effect. The third reports conventional references that occupied bytes replaced by a proven protected rewrite, and those bytes are patched only once so the two rewrite paths do not fight over the same location.

The summary of the behaviour is that all applicable transfers are rebuilt and unresolved instructions remain unchanged in the output image. The common causes listed are capturing the process before VMProtect initializes its imports, selecting the wrong VM section, or encountering an unsupported stub form.

The tool's advice is to let the process reach stable original code and verify the selected sections if the remaining protected transfers matter for execution. And there is a hard stop: if no import can be recovered at all, there is no import table to build, and vid stops without writing an output file.

That behaviour is the design in miniature. A partial rebuild still produces a file, and the counts tell you how much to trust it.

## Command line surface, and the sections you can select

The usage is one required argument and a short option list.

```text
vid.exe --pid <PID> [OPTIONS]
```

The options cover four categories. Selection of the target: --pid is a required non-zero target process ID, and --module inspects a loaded module instead of the main executable, with case-insensitive matching that accepts a file name, an extensionless name, or a path. That last point is a practical detail, since a DLL is often known by its base name only.

Selection of the protected regions: --vm-sections takes one to three executable VM sections using either 1-based numbers or names, and names and numbers cannot be mixed. The limit of three is a hard constraint from the limitations list, and it is stated twice in the README.

Output control: --section-name sets the name of the rebuilt import section, defaulting to .vid and required to be 1 to 8 printable ASCII bytes. --output sets an explicit path, creates missing parent directories automatically, and cannot overwrite the module being inspected. --new-entry-rva takes a decimal or 0x-prefixed hexadecimal value that must point into an executable section.

Two flags are for the tool itself, -h and --help, and -V and --version. And --list-sections can be combined with --module, but conflicts with the options that modify the output.

The examples show the PowerShell backtick line continuation, the numbered form and the named form of --vm-sections, and a DLL rebuild in another process. The README notes that names containing PowerShell metacharacters must be quoted.

## The hard boundaries, and where this is the wrong tool

The limitations section of the README is worth reading in full before you plan around this.

The target must remain alive and its protected import state must be initialized and stable while the snapshot is read. Only x86 and x64 PE images are supported. At most three VM sections can be selected in one run. A protected transfer is patched only when a direct rewrite is proven, and unresolved transfers remain unchanged while the rest of the image is rebuilt.

And then the list of things it does not do: it does not bypass anti-debugging, does not suspend the target, does not repair arbitrary packer damage, does not preserve overlays, and does not devirtualize protected program code.

That last one is the one to internalise. vid gives you a loadable file with a real import table. The protected logic in that file is still protected logic, and if the VM bytecode was the interesting part of your analysis, vid has not helped with that.

There is also an access condition, and the README handles it directly. If you get an access denied error, run vid at the same integrity level as the target, since protected or elevated processes may require a terminal running with administrator rights. This is a practical consequence of the tool reading another process's memory, and it means the analysis environment has to match the target's, which is not always convenient.

So the wrong-tool cases are: a target you cannot keep running long enough, a protection scheme that is not VMProtect, an ARM or other non-x86 binary, and any analysis where you need the program logic decompiled rather than its imports restored.

## Conclusion

Adopt vid if you analyse VMProtect protected binaries and need a loadable image with real imports rather than an opaque stub, and you can run the target under the same integrity level. Do not adopt it expecting a general unpacker, since the README is explicit that it does not devirtualize program logic, repair arbitrary packer damage or bypass anti-debugging. Before you rely on it, read the troubleshooting section on partial recovery and count the unresolved transfers in the warning output, because a partial rebuild still produces a file, and check that the entropy heuristic picked the right VM sections with --list-sections before you trust the result.

## FAQ

### What does the vid tool do?

vid reconstructs VMProtect-obfuscated PE imports from a live Windows process and writes a readable, loadable PE image with a new import table. The README states it is intentionally narrow, repairing imports and the instructions that reference them, and that it is not a generic unpacker.

### How do I build vid?

From the repository root run rustup show, then cargo build --release, and the executable is written to target\release\vid.exe. The project pins its toolchain in rust-toolchain.toml and Cargo.toml requires rust-version 1.97 with edition 2024.

### How does vid choose which sections to rebuild?

If one to three executable sections have entropy above 7.0, vid can select them automatically. Otherwise you list the sections with --list-sections, which prints numbers, names, RVAs, sizes, permissions, characteristics and entropy, and pass one to three with --vm-sections using numbers or names, which cannot be mixed.

### What happens if vid cannot recover every import?

The tool reports counts in its warning output, for example rewritten 2233 of 2332 with left unchanged 99, and unresolved transfers remain unchanged in the output image while the rest is rebuilt. If no import can be recovered at all, there is no import table to build and vid stops without writing an output file.

### Does vid work on ARM binaries?

No. The limitations state that only x86 and x64 PE images are supported, and at most three VM sections can be selected in one run.

## Sources

- [colby57/vid on GitHub](https://github.com/colby57/vid)
- [Issues](https://github.com/colby57/vid/issues)
- [License: MIT](https://github.com/colby57/vid/blob/main/LICENSE)
- [README](https://github.com/colby57/vid/blob/main/README.md)
- [Releases](https://github.com/colby57/vid/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/colby57-vid
