i cracked a $200 software protection in a day with xcopy

Disclaimer: This is only academic security research. I do not condone piracy. I purchased a valid license for this software and performed this analysis on my property. This article exists to document security implementation errors, not to enable piracy. Support developers – buy their software.

Github repo: vmfunc/puzzle

tl;dr

I spent a day analyzing Enigma Protector – a $200 commercial software security system used by thousands of vendors. RSA cryptographic signatures, hardware-bound licensing, anti-debugging, VM-based code obfuscation. Critical Enterprise Security Theatre.

Then I noticed that the protected installer completely extracts the unprotected payload to disk.

xcopy /E "C:\Program Files\...\product" .\crack\

This is complete crack. Copy the installed files. They run on any machine. No keygen required, no binary patching, no cryptanalysis.

The $200 security was defeated by a command shipped with DOS 3.2 in 1986.

It’s a case study in why threat modeling matters more than fancy cryptography, and why “military-grade encryption” means nothing when you leave the back door open.


target overview

Bass Bully Premium VST3 - Landing Page Screenshot
bass bully premium – A VST3 synthesizer plugin. protected by puzzle keeperA commercial software security system that costs $250+ and promises serious security.

From their marketing:

“Enigma Protector is a powerful tool designed to protect executable files from illegal copying, hacking, modification and analysis.”

we’ll see about that.

We have a known valid license:

Key:  GLUJ-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-V99KP3
HWID: 3148CC-XXXXXX
Name: Bass Bully

Our goal: understand security and build proper crack


static analysis

First, let’s see what we are dealing with:

import pefile

pe = pefile.PE(r"Bass Bully Premium_Installer_win64.exe")

print(f"Machine:     {'x64' if pe.FILE_HEADER.Machine == 0x8664 else 'x86'}")
print(f"Sections:    {pe.FILE_HEADER.NumberOfSections}")
print(f"Entry Point: 0x{pe.OPTIONAL_HEADER.AddressOfEntryPoint:X}")
print(f"Image Base:  0x{pe.OPTIONAL_HEADER.ImageBase:X}")
Machine:     x64
Sections:    9
Entry Point: 0x16485D0
Image Base:  0x140000000

That entry point is questionable. 0x16485D0 The path in the binary is.. typical of packed executables where the actual entry point is hidden. Normal events start around 0x1000,

string hunting

with open(pe_path, 'rb') as f:
    data = f.read()

for target in [b'Enigma Protector', b'enigmaprotector']:
    offset = 0
    while (idx := data.find(target, offset)) != -1:
        print(f"0x{idx:08X}: {target.decode()}")
        offset = idx + 1
0x0040972B: Enigma Protector
0x00409746: Enigma Protector
0x00409786: Enigma Protector
0x00409BA8: Enigma Protector
0x00409BC3: Enigma Protector
0x0040A038: Enigma Protector
0x0040A053: Enigma Protector
0x004099BF: enigmaprotector
0x00409DDA: enigmaprotector

Affirmation: Puzzle Keeper. Now we know what we are dealing with.

What about the network?

Does he call home too?

imports = [entry.dll.decode() for entry in pe.DIRECTORY_ENTRY_IMPORT]
kernel32.dll, user32.dll, advapi32.dll, oleaut32.dll, gdi32.dll,
shell32.dll, version.dll, ole32.dll, COMDLG32.dll, MSVCP140.dll, ...

No winhttp.dll, wininet.dllOr ws2_32.dll, Offline verification only. All crypto is local, so theoretically extractable.

This is good news!! Online verification will require MITM or server emulation. Offline means everything we need is in the binary.


puzzle protector interior

Enigma Protector is a business security system that provides:

  1. code virtualization – Converts x86/x64 to proprietary VM bytecode
  2. anti debugging , IsDebuggerPresentTime check, hardware BP detection
  3. anti tampering – CRC checks packed sections
  4. Registration API – HWID-bound licensing with RSA signatures

You can read about all these features on their documentation page. They are very good at explaining what they protect against. They didn’t think that no one would use it on payload.
Puzzle security and motivation to purchase - diagram showing security levels

Registration API

According to Enigma’s SDK, these functions are exposed:

int EP_RegCheckKey(const char* name, const char* key);
const char* EP_RegHardwareID(void);
void EP_RegSaveKey(const char* name, const char* key);
void EP_RegLoadKey(char* name, char* key);

These are not normal exportsThey are solved dynamically once the puzzles are revealed. you just can’t GetProcAddress them from outside. You must either:

  • Hook them up at runtime after unpacking
  • Pattern scans unpacked memory
  • Be a complete clown and don’t use them in your payload (definitely not foreshadowing)

entry point

Using Capstone to isolate entry point:

from capstone import Cs, CS_ARCH_X86, CS_MODE_64

entry_rva = pe.OPTIONAL_HEADER.AddressOfEntryPoint
entry_offset = pe.get_offset_from_rva(entry_rva)

with open(pe_path, 'rb') as f:
    f.seek(entry_offset)
    code = f.read(64)

md = Cs(CS_ARCH_X86, CS_MODE_64)
base = pe.OPTIONAL_HEADER.ImageBase + entry_rva

for insn in md.disasm(code, base):
    print(f"0x{insn.address:X}: {insn.mnemonic:8} {insn.op_str}")
0x1416485D0: jmp      0x1416485da      ; skip garbage bytes
0x1416485D2: add      byte ptr [rsi + 0x40], dl
0x1416485D8: add      byte ptr [rax], al
0x1416485DA: push     rax              ; real code starts here
0x1416485DB: push     rcx
0x1416485DC: push     rdx
0x1416485DD: push     rbx
0x1416485DE: push     rbp
0x1416485DF: push     rsi
0x1416485E0: push     rdi
0x1416485E1: push     r8
0x1416485E3: push     r9

The JMP-over-garbage pattern is classic anti-disassembly. Linear disassemblers will attempt to decode the garbage bytes in between jmp And its goal is to create nonsense. The real unpacker starts here 0x1416485DA With a standard register preservation sequence before calling the Enigma loader.


Step 3: Key Format Analysis

We have a known valid key. Let’s understand its structure before attempting to break it down.

GLUJ-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-V99KP3

structure breakdown

  • 8 groups separated by dash
  • Groups 0-6: 4 letters each
  • Group 7: 6 characters (large – potential checksum/signature)
  • Character Set: 0-9, A-Z (Base36)

base36 decoding

key = "GLUJ-QE58-U3Z4-RQTJ-K7GJ-JXZ5-CVK5-V99KP3"
groups = key.split('-')

for i, group in enumerate(groups):
    val = int(group, 36)
    bits = val.bit_length()
    print(f"[{i}] {group:6} = {val:10} (0x{val:08X}) {bits:2} bits")
[0] GLUJ   =     774811 (0x000BD29B) 20 bits
[1] QE58   =    1231388 (0x0012CA1C) 21 bits
[2] U3Z4   =    1404832 (0x00156FA0) 21 bits
[3] RQTJ   =    1294471 (0x0013C087) 21 bits
[4] K7GJ   =     942787 (0x000E62C3) 20 bits
[5] JXZ5   =     930497 (0x000E32C1) 20 bits
[6] CVK5   =     600773 (0x00092AC5) 20 bits
[7] V99KP3 = 1890014727 (0x70A75607) 31 bits  <- significantly larger

interesting. Group 7 is much larger than the others. This is probably a short cryptographic signature.

cryptographic structure

The main structure looks like this:

[    DATA: ~143 bits     ] [ SIGNATURE: 31 bits ]
 Groups 0-6 (7 x ~20 bits)   Group 7 (truncated)

Enigma uses RSA for signing. The full signature would be too big, but they truncate it to fit the key format. This means:

  1. The public key is embedded in the protected binary.
  2. Key Verification = RSA Signature Verification
  3. keygen will be required to extract and factoring the RSA modulus

RSA with small key size is technically factorable with sufficient computation. But it’s a lot of work… well, you’ll see.

HWID format

hwid = "3148CC-059521"
parts = hwid.split('-')
# Two 24-bit values = 48 bits total hardware fingerprint

HWID is derived from hardware attributes (CPU ID, disk serial, MAC address, etc.). The key is cryptographically tied to this value, so a key created for one machine will not work on another machine.

This is actually decent security! If they used it correctly! (He did not complain)


spindle

At this point I’m preparing to either factorize the RSA key or do runtime hooking to bypass the verification. Then I thought: Wait, what are we really protecting here?

Let me just quickly check the installed VSTs…

Analysis of installed VST

vst_path = r"C:\Program Files\Common Files\VST3\Bass Bully VST\Bass Bully Premium.vst3"
vst_dll = vst_path + r"\Contents\x86_64-win\Bass Bully Premium.vst3"
pe_vst = pefile.PE(vst_dll)

print(f"Size: {os.path.getsize(vst_dll):,} bytes")
print("Imports:")
for entry in pe_vst.DIRECTORY_ENTRY_IMPORT:
    print(f"  {entry.dll.decode()}")
Size: 7,092,736 bytes
Imports:
  KERNEL32.dll
  USER32.dll
  GDI32.dll
  SHELL32.dll
  ole32.dll
  OLEAUT32.dll
  MSVCP140.dll
  WINMM.dll
  IMM32.dll
  dxgi.dll
  VCRUNTIME140.dll
  VCRUNTIME140_1.dll
  api-ms-win-crt-runtime-l1-1-0.dll
  ...

wait. Where are the puzzle imports?

hunting for safety

with open(vst_dll, 'rb') as f:
    data = f.read()

for term in [b'Enigma', b'EP_Reg', b'Registration', b'HWID', b'enigma']:
    count = data.count(term)
    print(f"{term.decode():15} : {count} occurrences")
Enigma          : 0 occurrences
EP_Reg          : 0 occurrences
Registration    : 0 occurrences
HWID            : 0 occurrences
enigma          : 0 occurrences

Zero.

Catch.

$ strings "Bass Bully Premium.vst3" | grep -i enigma
$ strings "Bass Bully Premium.vst3" | grep -i regist

Nothing. no output… ?????????

You are kidding.

VST has no security at all. This is a clean JUCE framework build. No puzzle runtime. No license callback. No security of any kind.

He protected the installer. No payload. Installer. Not actual product.

I can’t even be mad. It’s really hilarious.


Vulnerability

What’s going on over here:

+-------------------------------------------------------------------+
|                    ENIGMA PROTECTOR                               |
|  +--------------------------------------------------------------+ |
|  |  Installer.exe                                               | |
|  |  [x] RSA key verification                                    | |
|  |  [x] HWID binding                                            | |
|  |  [x] Anti-debug, anti-tamper                                 | |
|  |  [x] Code virtualization                                     | |
|  |                        |                                     | |
|  |                        v                                     | |
|  |  +--------------------------------------------------------+  | |
|  |  |  Payload (extracted on install)                        |  | |
|  |  |  - Bass Bully Premium.vst3  <- ZERO PROTECTION lol     |  | |
|  |  |  - Bass Bully Premium.rom   <- NOT EVEN ENCRYPTED      |  | |
|  |  +--------------------------------------------------------+  | |
|  +--------------------------------------------------------------+ |
+-------------------------------------------------------------------+

The entire security stack only controls whether installer Let’s go. Once the files are on disk, protection is essentially useless.

It’s like putting a safe door on a tent.

what should they have done

The puzzle would have been effective if VST had checked the license itself:

bool VST_Init() {
    char key[256], name[256];
    EP_RegLoadKey(name, key);

    if (!EP_RegCheckKey(name, key)) {
        ShowTrialNag();
        return false;
    }

    CreateThread(NULL, 0, LicenseWatchdog, NULL, 0, NULL);
    return true;
}

Instead, VST does not have EP_Reg* Call. No license check. No callback. Nothing. It just… moves.


Crack

The crack is embarrassingly simple. I spent hours analyzing RSA key formats for this…

extremely sophisticated exploitation

xcopy /E "C:\Program Files\Common Files\VST3\Bass Bully VST" .\crack\
copy "C:\ProgramData\Bass Bully VST\Bass Bully Premium\*.rom" .\crack\

That’s it. That’s crack. Copy files. They work on any machine as the actual product has no license checks.

I wrote a Python script to automate this because I have some self-respect:

#!/usr/bin/env python3
import shutil
from pathlib import Path

VST_SRC = Path(r"C:\Program Files\Common Files\VST3\Bass Bully VST\Bass Bully Premium.vst3")
ROM_SRC = Path(r"C:\ProgramData\Bass Bully VST\Bass Bully Premium\Bass Bully Premium.rom")

def extract():
    out = Path("crack_package")
    out.mkdir(exist_ok=True)
    shutil.copytree(VST_SRC, out / "Bass Bully Premium.vst3", dirs_exist_ok=True)
    shutil.copy2(ROM_SRC, out / "Bass Bully Premium.rom")
    print("[+] done")

if __name__ == "__main__":
    extract()

Use:

python patcher.py

Load into fl studio. No registration. No one is upset. Nothing. Because there is no investigation.


For Science: The Hook Approach

We also wrote a DLL that adds validation to Enigma at runtime. This is completely unnecessary given the vulnerability, but I’ve already done the research so here it is:

#include 
#include 

static int (WINAPI *Real_EP_RegCheckKey)(LPCSTR, LPCSTR) = NULL;

int WINAPI Hooked_EP_RegCheckKey(LPCSTR name, LPCSTR key) {
    return 1;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) {
    if (reason == DLL_PROCESS_ATTACH) {
        Sleep(2000);
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach(&(PVOID&)Real_EP_RegCheckKey, Hooked_EP_RegCheckKey);
        DetourTransactionCommit();
    }
    return TRUE;
}

This approach works well. Completely unnecessary. There is no security in the payload.
Screenshot showing hook approach
Screenshot showing hook approach results


lessons learned

for developers

  1. Protect the payload, not the installer – If users need installed files to run your software, those files require runtime protection
  2. defense in depth – Do not depend on any one layer. VST should call EP_RegCheckKey on load
  3. threat model correctly – Ask “What happens after installation?” If the answer is “Nothing checks the license”, you have a problem.
  4. periodic verification – One-time checking is trivially bypassed by file copying

for reversers

  1. Always check payload first – Before plunging into complex crypto, verify what you’re actually securing
  2. Simplest attack wins – When not to factor RSA xcopy works
  3. security !=security – Expensive security systems are useless if implemented incorrectly
  4. Sometimes the cracks write themselves – Not every goal requires sophisticated techniques

conclusion

Enigma Protector’s $250 security was defeated by:

xcopy /E "C:\Program Files\..." .\crack\

The security itself works fine – RSA signing, HWID binding, anti-debug. But this only protects the installer. The payload runs completely unsecured.

250 dollars for this…



<a href

Leave a Comment