Replacing the HWID in VMProtect 3.8.4

About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.

This research is published for educational and defensive purposes. The target sample is anonymized throughout.


Replacing the HWID in VMProtect 3.8.4

The approach

VMProtectGetCurrentHWID

From analysis of the leaked VMProtect source, the HWID is composed as shown below. Each component is hashed with SHA-1 and the first 4 bytes of each result are taken; the combined result is then stored with an RC5-encrypted random key. When VMProtectGetCurrentHWID is called, the whole thing is assembled and presented as Base64.

HWID composition from the leaked source

GetComputerNameW

Some of the functions in the diagram get virtualized when compiled, but there’s an entry point we can still hook: GetComputerNameW.

Break on GetComputerNameW and swap the HWID in memory via the stack:

Entry point at GetComputerNameW

Implementation

Compile the target and debug it with OllyDbg:

Debugging the compiled target in OllyDbg

Break on GetComputerNameW and inspect the stack — but don’t break at the function prologue.

[ebp+0x8]+0x8 is the pointer to the structure that holds the HWID encryption result. At this point it isn’t fully written yet — only the CPUID part has been computed. So set a breakpoint at the function’s return instead (again, not at the prologue):

Breakpoint at the function return

Once it hits, the data has been written. Follow the structure pointer in the memory window:

Following the structure pointer

  • pointer+0x4 — where the HWID encryption is stored
  • pointer+0x8 — total HWID group count
  • pointer+0x9 — old-method group count

Follow the HWID encryption location (pointer+0x4):

Following the HWID encryption location

  • pointer+0x4 — pointer to the RC5-encrypted HWID data

The RC5-encrypted HWID data pointer

  • pointer+0xC — pointer to the RC5 encryption key

The RC5 key pointer

With those two blobs you can decrypt the HWID:

Decrypting with the two blobs

The same two locations can also be patched to replace the HWID outright.


Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).

Get in touch