![]() |
|
#1
|
|||
|
|||
|
Need help unpacking Themida 3.x x64
Hey everyone,
It's been quite a few years since I last did any serious RE work and wow, things have moved on. I've been banging my head against a Themida 3.x target for a couple weeks now and figured it's time to ask for help before I lose my mind. The thing that surprised me most coming back is how little current material there is on Themida 3.x unpacking for x64. The n0pex3 and LCF-AT articles are solid foundations but they're from a while back, written against 32-bit targets and older Themida versions. Everything else I've found is either dead forum threads where OP never posts the solution, or YouTube videos with no commentary that skip the hard parts. If anyone knows of tutorials, writeups, or even Discord servers where this stuff gets discussed more actively, I'd owe you one. Anyway, here's what I've tried, what worked, and where I'm completely stuck. Writing this out partly to organize my own thoughts and partly hoping someone spots what I'm doing wrong. --- THE TARGET The main EXE, 31 MB, x64. Themida/WinLicense 3.x is what Detect It Easy tells me. There's a 15 MB .themida section and from what I can tell, a decent chunk of the original code got virtualized — I counted about 647 calls and jumps from .text back into the .themida section. Packed entry point is at RVA 0x528A058, in the .boot section. --- WHAT I TRIED #1 — MANUAL UNPACK WITH x64DBG Setup: latest x64dbg snapshot, ScyllaHide with the "Themida x64" profile. Had to pass sti exceptions through with Shift+F9 or the debugger chokes. Finding the OEP: I used the LCF-AT approach — hardware breakpoint on write to the last IAT entry, restart, run until it hits, then memory breakpoint on access to the .text section. Run again and it lands you near the OEP. After some poking around I confirmed the OEP at RVA 0x2A866C0. So far so good. The real pain started with the IAT. Themida 3.x IAT Obfuscation: From what I can tell (mostly from those n0pex3 articles), Themida messes with IAT calls in a few ways. In my binary I found three patterns: Pattern A (6 bytes): 90 E8 xx xx xx xx The "nop" followed by a call. The call goes to one of those multijump thunks. Since it's 6 bytes, in theory you can replace it with FF 15 [new_IAT_entry]. Pattern B (6 bytes): E8 xx xx xx xx 90 Same idea, nop just sits after the call instead of before. Pattern C (5 bytes): E8 xx xx xx xx Plain E8 call, no nop. The target is still a thunk but now you've only got 5 bytes, and FF 15 [addr] needs 6. Can't be done in-place without shifting everything that comes after. Scale of the wreckage: - 35 calls using Pattern A or B (patchable in theory) - 877 calls using Pattern C (5 bytes, not patchable in-place) - 1242 total thunks (621 pairs of FF 25 + push rdx/lea rdx) IAT sits at RVA 0x32EA320 and the import descriptors are at RVA 0x4326390. I dumped the process with Scylla, fixed the OEP in the PE header, fixed the import directory pointer (was pointing at the IAT instead of the descriptors), and patched those 35 calls. The dump doesn't start. Two problems: 1. ASLR. Every IAT entry in the dump contains a resolved address (0x7FFD...) from the original run. On restart, different base addresses, every single entry is wrong. The import table needs to be descriptor-based so the Windows loader fills it, but I don't know how to convert a resolved IAT back to descriptor form when the thunks are all mixed in. 2. Those 877 E8 calls. Even if I fix the IAT, those calls point at thunks that reference the old IAT addresses. Broken either way. I thought about building a trampoline section — leave the old thunks in place but redirect them to new IAT entries — but 877 of them manually is not happening, and I don't know of a tool that automates this for Themida's pattern. Scylla's IAT autosearch finds nothing at the OEP. I gather that's expected for Themida but I'm not sure what the standard workaround is. --- WHAT I TRIED THAT DIDN'T PAN OUT - Magicmida: got a partial dump that shows the splash screen then crashes. 647 VM references left unresolved and one IAT entry broken. Half-working isn't useful when you can't debug the other half. - Manual byte patching of all 877 calls: obviously impractical by hand, and even if I could, the 5→6 byte expansion shifts every relative offset in the binary. - Scylla IAT autosearch at various points during execution: never finds anything. The OEP simply doesn't have a normal IAT structure nearby. --- WHERE I COULD REALLY USE SOME HELP 1. What's the standard approach for bulk-converting the 5-byte E8 calls when you can't do it in-place? Is building a trampoline section the way to go, and if so, is there a script or tool that handles this for Themida targets? I can't be the first person to hit this. 2. How do people handle DLLs packed with Themida? Every tutorial and tool I've found assumes an EXE target. Are there Unicorn-based DLL unpackers out there, or is the approach completely different? 3. Are there any Themida 3.x x64 unpacking tutorials or writeups I've missed? I've searched exhaustively (or at least it feels that way) and come up nearly empty. Discord servers, obscure blogs, Russian forums, anything — happy to translate if needed. 4. Or, on a more practical level — is there a better tool than what I've been using? Something that handles the full pipeline for modern Themida on x64? Even commercial tools, I'm willing to pay if something actually works. Thanks for reading this far. Any pointers at all would be massively appreciated — even just "hey I ran into this too, here's what eventually worked" would make my week. Cheers. |
|
#2
|
|||
|
|||
|
You can try Unlicense, but it is not perfect unpacker. It needs some fixes because it recovers the iat at the wrong place and overwrites some initialization data there. And it does not devirtualize the code too. If VM integrity checks option is used then even after the unpack the VM will check the unpacked binary...
|
|
#3
|
|||
|
|||
|
Could you show me the software? I mean no harm and I’m just curious to see if it works as you said. I can’t unpack it anyway; I’m only checking the features.
|
|
#4
|
||||
|
||||
|
Any links to software?
|
|
#5
|
|||
|
|||
|
Quote:
1. Stolen OEP — DllEntryPoint is a Themida trampoline (lea rcx, [rcx+0x38]; jmp 0x10cb0) instead of real DllMain. The DLL can't initialize properly. 2. IAT placed at wrong RVA — unlicense overwrote initialization data with the rebuilt IAT (at 0x814000). The app's config/state data that was there got corrupted. 3. VM code untouched — .themida section (8.6 MB) still present with all virtualized code still running through the VM interpreter. VM integrity checks detect the modified binary and kill it. The exports were correctly patched though — InternalCheck/CheckConnect all had the right mov eax,1;ret patches baked in. |
| The Following User Says Thank You to 1ST For This Useful Post: | ||
niculaita (05-20-2026) | ||
|
#6
|
|||
|
|||
|
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 |
|
#7
|
|||
|
|||
|
Looks like the target devs know what they are doing. CM dongle + themida VM. I would not waste even a minute on this...
|
| The Following User Says Thank You to kernel For This Useful Post: | ||
niculaita (05-20-2026) | ||
|
#8
|
|||
|
|||
|
Quote:
![]() Themida is the main problem for me. Last edited by 1ST; 05-19-2026 at 03:21. |
|
#9
|
|||
|
|||
|
Quote:
Just not as obviously as for the 17th version, to avoid the TG account ban. Can be found after a little searching
|
|
#10
|
|||
|
|||
|
Quote:
|
|
#11
|
||||
|
||||
|
The E8 imports should be doable with a script (attaching a pe section and putting the jump-thunk there then point the E8s at that). There is also a tool for this, but I dont have it anymore, ImportFixer 1.2 I think it was called.
But the real problem is obviously the VM. There is no public way to defeat it. |
|
#12
|
|||
|
|||
|
Quote:
|
| The Following User Says Thank You to 1ST For This Useful Post: | ||
niculaita (05-20-2026) | ||
|
#13
|
|||
|
|||
|
For x64 bit direct imports i use WannabeUIF or quick unpack x64 to fix them.
|
![]() |
| Thread Tools | |
| Display Modes | |
|
|