View Single Post
  #2  
Old 05-18-2026, 23:26
1ST 1ST is offline
Family
 
Join Date: Apr 2010
Location: Jordan
Posts: 99
Rept. Given: 47
Rept. Rcvd 225 Times in 24 Posts
Thanks Given: 6
Thanks Rcvd at 4 Times in 4 Posts
1ST Reputation: 200-299 1ST Reputation: 200-299 1ST Reputation: 200-299
Quote:
Originally Posted by Archer View Post
Any links to software?
THE TARGET

Main EXE, 31 MB, x64. Detect It Easy identifies it as Themida/WinLicense 3.x. Packed entry point is at RVA 0x528A058 (.boot section). There's a 15 MB .themida section with the VM interpreter, and about 647 calls/jumps from .text into .themida — a good chunk of code is virtualized.

There's also a DLL (7 MB, Delphi x64, same Themida version) that handles license checking via CodeMeter.

The license check chain:
- EXE loads the DLL
- DLL's DllMain loads WibuCm64.dll dynamically (LoadLibrary/GetProcAddress, not static imports)
- DllMain spawns a Delphi background thread: OemRegUtils.TLicenseChecker.Execute
- TLiceChecker calls CodeMeter APIs: CmGetRemoteContext2, CmCryptSimS, CmGetInfo, CmGetBoxesS
- CmCryptSimS does hardware challenge-response against the dongle
- Without the dongle's private key: DllMain returns FALSE, app shows error 6/902, exits

---

WHAT I'VE TRIED

===== 1. WibuCm64.dll PROXY DLL =====

Replaced WibuCm64.dll with my own DLL that exports all 182 Cm* functions but returns fake data. Extracted the real export ordinals from System32\WibuCm64.dll. Key ordinals:

ord_33: CmCryptSimS
ord_44: CmGetRemoteContext2
ord_46: CmGetBoxContentsS
ord_48: CmGetBoxes
ord_49: CmGetBoxesS
ord_50: CmGetInfo
ord_51: CmGetInfoS
ord_52: CmDecryptPioDataS

Compiled with MinGW (-nostdlib, direct kernel32 calls to keep it small). Key interceptors:

CmCryptSimS: return 0, zero-fill output buffer
CmGetRemoteContext2: forward to real DLL, inject fake context if empty
CmGetBoxesS: return 2 fake boxes
CmGetInfo: return 0, zero-fill output

Problem: MinGW exports functions alphabetically, so ord_50 became CmCryptSimS instead of CmGetInfo. Wrote a Python script (fix_ordinals.py) that parses the PE export directory and swaps address table entries + name ordinals to match the real DLL's ordinal layout.

Result: Splash screen appears. But TLiceChecker's CmCryptSimS validation can't be satisfied without the dongle's private key — error 6 still fires. The proxy approach gets past the initial check but fails the background thread's deeper crypto validation.

Also had to handle Themida's CRC integrity checks. Themida uses rep movsb/rep movsd for CRC — any runtime patch triggers a mismatch. Installed a VEH handler that catches access violations from these instructions and skips them:

VehHandler: if exception address points to F3 A4 or F3 A5, skip the instruction

This prevents Themida from detecting patches, but doesn't solve the core crypto problem.

===== 2. winspool.drv PROXY FOR OxyCheck64 =====

OxyCheck64.dll is another license-checking DLL. Replaced it by proxying winspool.drv (DLL proxy technique — rename real winspool, place our DLL). Our proxy has a DllMain that:
- Installs CRC VEH bypass
- Starts a polling thread (every 5ms for 5 seconds)
- The thread finds CKEngine.dll and OxyCheck64.dll via GetModuleHandle
- Patches their exports via VirtualProtect + memcpy

Patches applied:
InternalCheck -> mov eax, 1; ret
CheckConnect -> xor eax, eax; ret
CodemeterReconnect -> mov eax, 1; ret
DllFunc11 -> mov eax, 1; ret
DllFunc12 -> mov eax, 1; ret
DllFunc13 -> xor eax, eax; ret
SetOemCheckThreadMode -> ret (nop the thread spawner)

Race condition: the polling thread races against Themida's unpacking. If we patch too early (before Themida finishes), CRC catches us. If too late, DllMain has already returned FALSE. Timing is unreliable.

Tried improving this with LdrRegisterDllNotification callback (fires BEFORE DllMain of loaded DLLs), but even with pre-DllMain patches, TLiceChecker still fails CmCryptSimS validation.

===== 3. x64dbg MANUAL UNPACKING =====

Setup: x64dbg snapshot, ScyllaHide with "Themida x64" profile, sti exceptions passed through.

OEP found at RVA 0x2A866C0 using the LCF-AT method (HW BP on last IAT write, then memory BP on .text access).

Themida 3.x IAT patterns found:

Pattern A (6 bytes): 90 E8 xx xx xx xx -> replaceable with FF 15 [IAT]
Pattern B (6 bytes): E8 xx xx xx xx 90 -> replaceable with FF 15 [IAT]
Pattern C (5 bytes): E8 xx xx xx xx -> NOT replaceable in-place. FF 15 needs 6 bytes.

Scale of the problem:
- 35 calls using Pattern A/B (patchable)
- 877 calls using Pattern C (NOT patchable in-place)
- 1242 total thunks (621 FF 25 + 621 push rdx/lea rdx pairs)
- IAT at RVA 0x32EA320

Dumped with Scylla, fixed OEP, fixed import directory pointer, patched 35 calls. Dump doesn't start. Two issues:
1. ASLR: IAT entries contain resolved 0x7FFD addresses from dump session
2. 877 E8 calls still reference old thunk-backed IAT → broken on restart

Scylla IAT autosearch finds nothing at OEP — expected for Themida.

===== 4. MAGICMIDA =====

Tried Magicmida (Themida/WinLicense unpacker). It produced a partial dump — the splash screen shows briefly, then the EXE crashes. The dump had:
- 1 broken IAT entry: one kernel32 API that Magicmida couldn't resolve, stubbed with ExitProcess/Sleep placeholder
- 647 VM references left unresolved: JMPs/CALLs from .text into .themida that Magicmida couldn't handle
- The crash happens right after splash — likely the broken IAT entry or a VM integrity check

Result: half-working dump, not useful for further analysis. The VM virtualization side wasn't addressed at all.

===== 5. BOBALKKAGI =====

Found on GitHub, uses Unicorn Engine to emulate Themida's entry point and rebuild imports. Had to modify it:
- Replaced distorm3 with capstone (distorm3 needs MSVC build tools)
- Downgraded setuptools<70 (pkg_resources removed in newer versions)

Result: Finds OEP correctly (0x2A866C0), but unwrapping phase crashes with UC_ERR_FETCH_UNMAPPED. Unicorn follows a call into .themida and hits unmapped addresses. Also, bobalkkagi is EXE-only — doesn't support DLL targets.

===== 6. UNLICENSE (Frida-based dynamic unpacker) =====

Found https://github.com/ergrelet/unlicense — uses Frida to dynamically unpack Themida 2.x/3.x targets, handles x64 EXE and DLL. Downloaded the PyInstaller release (v0.4.0, 49MB).

On the EXE (OxyDetective.exe): Successfully found OEP (0x2E866C0), resolved 883 imports, rebuilt IAT, produced unpacked_OxyDetective.exe (91.5 MB). Proper descriptor-based import table with 10 DLLs. The unpacked EXE ran for 10-15 seconds then crashed with "EJSONParseException" — got past Qt initialization to where it tries parsing config files.

On the DLL (CKEngine.dll): Found OEP at 0x410D90, resolved 116 imports, produced unpacked_CKEngine.dll (18.1 MB). Exports were correctly patched by the runtime proxies during unlicense's run. But:

Problems:
1. Stolen OEP: DllEntryPoint is Themida's trampoline (lea rcx, [rcx+0x38]; jmp stub), not real DllMain
2. IAT placed at wrong RVA (0x814000): overwrites initialization data
3. VM code untouched: .themida section (8.6 MB) still present, VM integrity checks detect modified binary
4. Only 7 DLLs in import table: missing many needed APIs (unlicense couldn't resolve Themida-encrypted entries)
Result: error 902 on load

---

WHERE I'M STUCK

1. IAT at scale: 877 5-byte E8 calls can't be converted in-place. The themida-rebuilder trampoline approach is the right solution but I need to complete the pipeline.

2. VM integrity: Even with imports fixed, Themida's VM code still checksums the binary. The unpacked binary from unlicense ran for 10-15 seconds before crashing — further than anything else — but VM integrity kills it.

3. Timing the patches: Patching CKEngine exports before TLiceChecker spawns. DLL load notification (LdrRegisterDllNotification) helps but the crypto validation in CmCryptSimS remains the root issue.

4. Full unpack vs. live patching: Is it even realistic to produce a clean unpacked PE for this target? Or should I focus on runtime instrumentation (hook GetProcAddress/LdrLoadDll at load time)?

---

WHAT I'D LOVE HELP WITH

1. Has anyone successfully used the module-list format to generate a correct module map? Any common mistakes that cause unresolved IAT entries?

2. For the VM integrity problem — if Themida's "VM integrity checks" option is enabled, does any unpacked binary survive? Or is the only path to keep the VM runtime intact and patch around it?

3. Any other tools or techniques for Themida 3.x x64 that I've missed? I've tried: Magicmida, manual Scylla dump, bobalkkagi, unlicense, proxy DLLs, and runtime patching.


Key addresses (main EXE - OxyDetective.exe):
Packed EP: RVA 0x528A058
OEP: RVA 0x2A866C0
.themida section: RVA 0x434E000, ~15.6 MB
VM cross-references: ~647
Pattern C calls (5-byte): 877
IAT: RVA 0x32EA320
Import descriptors: RVA 0x4326390

Key addresses (license DLL - CKEngine.dll):
Packed: 7 MB, same Themida 3.x version
OEP (from unlicense): RVA 0x10D90 (stolen — trampoline to real init)
IAT (from unlicense): RVA 0x814000
Exports: 26 (InternalCheck, CheckConnect, CodemeterReconnect, DllFunc1-13, etc.)

Thanks for reading. Any pointers appreciated.


Download: hxxps://mega.nz/file/KXwxhJhZ#TzoBW1F8Jbfbm9BQk_gsGAn1gbaE7a-dADQkDv3tLWQ
Reply With Quote