Skip to content

Add VMProtect Devirtualizer - #8

Merged
Colton1skees merged 1 commit into
masterfrom
feat/vmp-devirt
Mar 29, 2025
Merged

Add VMProtect Devirtualizer#8
Colton1skees merged 1 commit into
masterfrom
feat/vmp-devirt

Conversation

@Colton1skees

@Colton1skees Colton1skees commented Mar 29, 2025

Copy link
Copy Markdown
Owner

This PR implements an LLVM based VMProtect devirtualizer into the framework. This work was a joint effort between @duk-37 and I.

Some notable features of this PR:

  • The devirtualizer is static, requiring only an input RVA to begin devirtualization.
  • We implement devirtualization in a semi-generic way, meaning that we don't manually pattern match handlers or do any kind of bytecode disassembly. Instead we lift VM handlers directly to LLVM-IR, treating them as state transformers that get inlined.
  • The devirtualization pipeline is nearly identical to the iterative control flow graph exploration algorithm previously implemented in Dna. We build a "virtual control flow graph" using the virtual program counter, and iteratively explore/lift/optimize until the full control flow graph is recovered.
  • Once the full control flow graph is recovered, we perform some cleanup (optimizing away vmprotect's virtual context structure, enabling more optimizations).
  • We implement a Mixed Boolean-Arithmetic (MBA) deobfuscation pass based on Simplifier
  • Several new or improved optimization passes are added, namely store to load propagation, binary section constant concretization, instruction combining, MBA deobfuscation, and opaque stack variable elimination. The most interesting part of these passes is that we've combined store to load propagation, constant concretization, and constant propagation into one combined fixed point pass to address phase ordering issues - yielding a speedup by several orders of magnitude.
  • We cache and reuse optimized subsets of the virtual control flow graph during the iterative exploration pipeline, boosting performance.

The devirtualizer is at a stage where it generally works on VMP 3.x (with all obfuscation completely removed), but this is not a "complete" release - there are still features that need to be added for this to be a plug and play tool with readable output. Mainly better support for recompilation, generalizing some patterns, and testing on more real world code is necessary.

Co-authored-by: name <37@cmail.nu>
@Colton1skees
Colton1skees merged commit 39d9de0 into master Mar 29, 2025
@CR3Swapper

CR3Swapper commented May 26, 2026

Copy link
Copy Markdown

VMProtect uses the native stack for its virtual stack and vm-context. Stores to the stack therefore become indistinguishable from legitimate, original semantics that store values to the stack. Are you able to distinguish which stores are to be kept?

This is important because prologs are usually virtualized which create stack displacements. If the stores to the stack are not dropped then there will be dangling expressions associated with the store. This keeps obfuscation around...

Take this code for example, a vmexit-indirect-call will be performed for the call to atoi. You can prune some of the stack stores which are into redspace computed at the point of the call, but some of the stores will be within the allocated stack frame. How do you generically determine that those stores to the stack are VM related and not the original semantics? They could be stack arguments (ignoring atoi does not take any stack arguments).

push    rbx
sub     rsp, 30h
mov     rax, cs:__security_cookie
xor     rax, rsp
mov     [rsp+38h+var_10], rax
mov     [rsp+38h+var_18], 1Eh
cmp     ecx, 1
jle     short loc_14000110D
mov     rcx, [rdx+8]
call    cs:atoi 
; more code ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants