""" OneOCR .onemodel decryption using Windows BCrypt CNG API directly. Replicates the exact behavior of oneocr.dll's Crypto.cpp. Known from DLL analysis: - BCryptOpenAlgorithmProvider with L"AES" - BCryptSetProperty L"ChainingMode" = L"ChainingModeCFB" - BCryptGetProperty L"BlockLength" (→ 16) - BCryptSetProperty L"MessageBlockLength" = 16 (→ CFB128) - BCryptGenerateSymmetricKey with raw key bytes - BCryptDecrypt - SHA256Hash function exists (uses BCryptCreateHash/BCryptHashData/BCryptFinishHash) """ import ctypes import ctypes.wintypes as wintypes import struct import hashlib import zlib from collections import Counter import math import os # ═══════════════════════════════════════════════════════════════ # Windows BCrypt API via ctypes # ═══════════════════════════════════════════════════════════════ bcrypt = ctypes.WinDLL("bcrypt") BCRYPT_ALG_HANDLE = ctypes.c_void_p BCRYPT_KEY_HANDLE = ctypes.c_void_p NTSTATUS = ctypes.c_long # Constants BCRYPT_AES_ALGORITHM = "AES" BCRYPT_SHA256_ALGORITHM = "SHA256" BCRYPT_CHAINING_MODE = "ChainingMode" BCRYPT_CHAIN_MODE_CFB = "ChainingModeCFB" BCRYPT_BLOCK_LENGTH = "BlockLength" BCRYPT_MESSAGE_BLOCK_LENGTH = "MessageBlockLength" def check_status(status, msg=""): if status != 0: raise OSError(f"BCrypt error 0x{status & 0xFFFFFFFF:08x}: {msg}") def bcrypt_sha256(data: bytes) -> bytes: """Compute SHA256 using Windows BCrypt API.""" hAlg = BCRYPT_ALG_HANDLE() status = bcrypt.BCryptOpenAlgorithmProvider( ctypes.byref(hAlg), ctypes.c_wchar_p(BCRYPT_SHA256_ALGORITHM), None, 0) check_status(status, "SHA256 OpenAlgorithmProvider") hHash = ctypes.c_void_p() status = bcrypt.BCryptCreateHash(hAlg, ctypes.byref(hHash), None, 0, None, 0, 0) check_status(status, "CreateHash") status = bcrypt.BCryptHashData(hHash, data, len(data), 0) check_status(status, "HashData") hash_out = (ctypes.c_ubyte * 32)() status = bcrypt.BCryptFinishHash(hHash, hash_out, 32, 0) check_status(status, "FinishHash") bcrypt.BCryptDestroyHash(hHash) bcrypt.BCryptCloseAlgorithmProvider(hAlg, 0) return bytes(hash_out) def bcrypt_aes_cfb_decrypt(ciphertext: bytes, key: bytes, iv: bytes, message_block_length: int = 16) -> bytes: """Decrypt using AES-CFB via Windows BCrypt CNG API. message_block_length: 1 for CFB8, 16 for CFB128 """ hAlg = BCRYPT_ALG_HANDLE() status = bcrypt.BCryptOpenAlgorithmProvider( ctypes.byref(hAlg), ctypes.c_wchar_p(BCRYPT_AES_ALGORITHM), None, 0) check_status(status, "AES OpenAlgorithmProvider") # Set chaining mode to CFB mode_str = BCRYPT_CHAIN_MODE_CFB mode_buf = ctypes.create_unicode_buffer(mode_str) mode_size = (len(mode_str) + 1) * 2 # UTF-16 with null terminator status = bcrypt.BCryptSetProperty( hAlg, ctypes.c_wchar_p(BCRYPT_CHAINING_MODE), mode_buf, mode_size, 0) check_status(status, "SetProperty ChainingMode") # Set message block length (feedback size) mbl = ctypes.c_ulong(message_block_length) status = bcrypt.BCryptSetProperty( hAlg, ctypes.c_wchar_p(BCRYPT_MESSAGE_BLOCK_LENGTH), ctypes.byref(mbl), ctypes.sizeof(mbl), 0) check_status(status, f"SetProperty MessageBlockLength={message_block_length}") # Generate symmetric key hKey = BCRYPT_KEY_HANDLE() key_buf = (ctypes.c_ubyte * len(key))(*key) status = bcrypt.BCryptGenerateSymmetricKey( hAlg, ctypes.byref(hKey), None, 0, key_buf, len(key), 0) check_status(status, "GenerateSymmetricKey") # Prepare IV (BCrypt modifies it during decryption, so use a copy) iv_buf = (ctypes.c_ubyte * 16)(*iv) # Prepare input/output buffers ct_buf = (ctypes.c_ubyte * len(ciphertext))(*ciphertext) pt_buf = (ctypes.c_ubyte * len(ciphertext))() result_len = ctypes.c_ulong(0) # Decrypt status = bcrypt.BCryptDecrypt( hKey, ct_buf, len(ciphertext), None, iv_buf, 16, pt_buf, len(ciphertext), ctypes.byref(result_len), 0) check_status(status, "BCryptDecrypt") # Cleanup bcrypt.BCryptDestroyKey(hKey) bcrypt.BCryptCloseAlgorithmProvider(hAlg, 0) return bytes(pt_buf[:result_len.value]) def entropy(data: bytes) -> float: """Shannon entropy (bits per byte).""" if not data: return 0.0 freq = Counter(data) total = len(data) return -sum((c / total) * math.log2(c / total) for c in freq.values()) def hex_dump(data: bytes, offset: int = 0, max_lines: int = 8) -> str: lines = [] for i in range(0, min(len(data), max_lines * 16), 16): hex_part = " ".join(f"{b:02x}" for b in data[i:i+16]) ascii_part = "".join(chr(b) if 32 <= b < 127 else "." for b in data[i:i+16]) lines.append(f" {offset+i:08x}: {hex_part:<48s} {ascii_part}") return "\n".join(lines) def check_decrypted(data: bytes, label: str) -> bool: """Check if decrypted data looks valid. Return True if promising.""" if not data or len(data) < 16: return False ent = entropy(data[:min(4096, len(data))]) u32_le = struct.unpack_from("f]b[Piow.gU+nC@s""""""4' KEY_SHA256 = bcrypt_sha256(KEY_RAW) print("=" * 80) print("OneOCR Decryption via Windows BCrypt CNG API") print("=" * 80) print(f"\nKey (raw): {KEY_RAW.hex()}") print(f"Key (SHA256): {KEY_SHA256.hex()}") print(f"Python hashlib SHA256: {hashlib.sha256(KEY_RAW).digest().hex()}") print(f"BCrypt SHA256 match: {KEY_SHA256 == hashlib.sha256(KEY_RAW).digest()}") # Read file with open(MODEL_PATH, "rb") as f: full_data = f.read() filesize = len(full_data) header_offset = struct.unpack_from("