File size: 11,977 Bytes
ce847d4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
"""
Extract the static IV string from DLL and find how key derivation works.
Key findings from disassembly:
1. Static 30-byte string at RVA 0x02725C60 used as IV (truncated to 16)
2. SHA256(combined) used as AES key material
3. Combined = some_function(key_string, iv_from_data, flag)
4. Function at 0x18006c3d0 combines key + iv_prefix
Need to:
a) Read the static IV string
b) Disassemble function 0x18006c3d0 to understand combination
c) Try decryption
"""
import struct, hashlib
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
DLL_PATH = r"c:\Users\MattyMroz\Desktop\PROJECTS\ONEOCR\ocr_data\oneocr.dll"
MODEL_PATH = r"c:\Users\MattyMroz\Desktop\PROJECTS\ONEOCR\ocr_data\oneocr.onemodel"
with open(DLL_PATH, "rb") as f:
dll = f.read()
with open(MODEL_PATH, "rb") as f:
model = f.read()
# Parse PE sections for RVA → file offset mapping
e_lfanew = struct.unpack_from('<I', dll, 0x3c)[0]
num_sections = struct.unpack_from('<H', dll, e_lfanew + 6)[0]
opt_size = struct.unpack_from('<H', dll, e_lfanew + 20)[0]
sections_off = e_lfanew + 24 + opt_size
sections = []
for i in range(num_sections):
so = sections_off + i * 40
name = dll[so:so+8].rstrip(b'\x00').decode('ascii', errors='replace')
vsize = struct.unpack_from('<I', dll, so + 8)[0]
vrva = struct.unpack_from('<I', dll, so + 12)[0]
rawsize = struct.unpack_from('<I', dll, so + 16)[0]
rawoff = struct.unpack_from('<I', dll, so + 20)[0]
sections.append((name, vrva, vsize, rawoff, rawsize))
print(f"Section {name}: RVA=0x{vrva:08x} VSize=0x{vsize:08x} Raw=0x{rawoff:08x} RawSize=0x{rawsize:08x}")
def rva_to_foff(rva):
for name, vrva, vsize, rawoff, rawsize in sections:
if vrva <= rva < vrva + rawsize:
return rawoff + (rva - vrva)
return None
IMAGE_BASE = 0x180000000
TEXT_VA = 0x1000
TEXT_FILE_OFFSET = 0x400
def text_rva_to_file(rva):
return rva - TEXT_VA + TEXT_FILE_OFFSET
# 1. Read the static 30-byte string at RVA 0x02725C60
# The LEA instruction was at RVA 0x0015baac:
# lea rdx, [rip + 0x25ca1ad]
# RIP = 0x0015baac + 7 = 0x0015bab3
# Target RVA = 0x0015bab3 + 0x25ca1ad = ?
target_rva = 0x0015bab3 + 0x25ca1ad
print(f"\nStatic IV string RVA: 0x{target_rva:08x}")
foff = rva_to_foff(target_rva)
print(f"File offset: 0x{foff:08x}" if foff else "NOT FOUND")
if foff:
static_iv_30 = dll[foff:foff+30]
print(f"Static IV (30 bytes): {static_iv_30.hex()}")
print(f"Static IV (30 chars): {static_iv_30}")
static_iv_16 = static_iv_30[:16]
print(f"Static IV truncated to 16: {static_iv_16.hex()}")
print(f"Static IV truncated (repr): {static_iv_16}")
# Also check nearby strings for context
if foff:
print(f"\nContext around static IV string (foff-16 to foff+48):")
for i in range(-16, 64):
c = dll[foff+i]
print(f" +{i:3d}: 0x{c:02x} ({chr(c) if 32 <= c < 127 else '.'})")
# 2. Read the first 16 bytes of encrypted data (the "prefix" extracted before key derivation)
header_offset = struct.unpack_from('<Q', model, 0)[0]
prefix_16 = model[8:24]
print(f"\nData prefix (first 16 bytes after offset): {prefix_16.hex()}")
print(f"Data prefix repr: {prefix_16}")
# 3. Disassemble the key combination function at 0x18006c3d0
# RVA = 0x0006c3d0
combo_rva = 0x0006c3d0
combo_foff = rva_to_foff(combo_rva)
print(f"\nKey combination function RVA: 0x{combo_rva:08x}, file: 0x{combo_foff:08x}" if combo_foff else "NOT FOUND")
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = False
if combo_foff:
code = dll[combo_foff:combo_foff + 0x200]
print(f"\n{'='*100}")
print(f"Key combination function at RVA 0x{combo_rva:08x}")
print(f"{'='*100}")
for insn in md.disasm(code, IMAGE_BASE + combo_rva):
foff2 = rva_to_foff(insn.address - IMAGE_BASE)
line = f" {insn.address - IMAGE_BASE:08x} ({foff2:08x}): {insn.bytes.hex():<40s} {insn.mnemonic:<10s} {insn.op_str}"
if insn.mnemonic == 'ret':
print(line)
break
print(line)
# 4. Now let's also check what string is compared when key is empty
# At 0x0015b88d: lea rdx, [rip + 0x25b59db]
# RIP = 0x0015b88d + 7 = 0x0015b894
# Target = 0x0015b894 + 0x25b59db = ?
empty_key_str_rva = 0x0015b894 + 0x25b59db
empty_foff = rva_to_foff(empty_key_str_rva)
if empty_foff:
s = dll[empty_foff:empty_foff+64].split(b'\x00')[0]
print(f"\nDefault key comparison string: {s}")
# 5. Try decryption with various key derivation methods
print("\n" + "="*100)
print("DECRYPTION ATTEMPTS WITH STATIC IV AND DERIVED KEY")
print("="*100)
KEY_RAW = b'kj)TGtrK>f]b[Piow.gU+nC@s""""""4'
# Encrypted header: model[8 : header_offset]
enc_header = model[8:header_offset]
# The prefix/IV: first 16 bytes
data_prefix = enc_header[:16]
# The ciphertext: remaining bytes
ciphertext = enc_header[16:]
MAGIC = 0x252b081a4a
from Crypto.Cipher import AES
def try_dec(aes_key, iv, ct, label):
"""Try AES-256-CFB128 decryption."""
try:
cipher = AES.new(aes_key, AES.MODE_CFB, iv=iv, segment_size=128)
pt = cipher.decrypt(ct[:256])
if len(pt) >= 24:
magic = struct.unpack_from('<Q', pt, 0x10)[0]
if magic == MAGIC:
print(f" *** SUCCESS *** {label}")
print(f" First 64 bytes: {pt[:64].hex()}")
return True
else:
unique = len(set(pt[:64]))
# Only print if somewhat promising
if unique < 45 or magic & 0xFF == 0x4a:
print(f" {label}: magic=0x{magic:016x}, unique_64={unique}")
except Exception as e:
print(f" {label}: ERROR {e}")
return False
if foff:
iv = static_iv_16
# Try 1: SHA256(key + data_prefix)
combined1 = KEY_RAW + data_prefix
aes_key1 = hashlib.sha256(combined1).digest()
try_dec(aes_key1, iv, ciphertext, "SHA256(key + data_prefix)")
# Try 2: SHA256(data_prefix + key)
combined2 = data_prefix + KEY_RAW
aes_key2 = hashlib.sha256(combined2).digest()
try_dec(aes_key2, iv, ciphertext, "SHA256(data_prefix + key)")
# Try 3: SHA256(key) with static IV
aes_key3 = hashlib.sha256(KEY_RAW).digest()
try_dec(aes_key3, iv, ciphertext, "SHA256(key) + static_iv")
# Try 4: Raw key with static IV
try_dec(KEY_RAW, iv, ciphertext, "raw_key + static_iv")
# Try 5: SHA256(key + data_prefix) on full enc_header (no prefix removal)
try_dec(aes_key1, iv, enc_header, "SHA256(key+prefix) + full_header")
try_dec(aes_key2, iv, enc_header, "SHA256(prefix+key) + full_header")
# Try 6: Maybe prefix is NOT stripped from ciphertext for BCrypt
try_dec(aes_key3, iv, enc_header, "SHA256(key) + static_iv + full_header")
try_dec(KEY_RAW, iv, enc_header, "raw_key + static_iv + full_header")
# Also try the full static_iv_30 string as both key and IV source
# Maybe the static string IS the key, and data_prefix IS the IV
try_dec(hashlib.sha256(static_iv_30).digest(), data_prefix, ciphertext, "SHA256(static30) + data_prefix_iv")
# What if key derivation involves the static string too?
# SHA256(key + static_string)
combined3 = KEY_RAW + static_iv_30
aes_key6 = hashlib.sha256(combined3).digest()
try_dec(aes_key6, data_prefix, ciphertext, "SHA256(key + static30) + prefix_iv")
try_dec(aes_key6, iv, ciphertext, "SHA256(key + static30) + static_iv")
# What if the function combines key with static string, and data_prefix is IV?
# Try many concatenation variants
variants = [
(KEY_RAW + data_prefix, iv, ciphertext, "key||prefix"),
(data_prefix + KEY_RAW, iv, ciphertext, "prefix||key"),
(KEY_RAW + static_iv_16, iv, ciphertext, "key||static16"),
(KEY_RAW + static_iv_30, iv, ciphertext, "key||static30"),
(static_iv_16 + KEY_RAW, iv, ciphertext, "static16||key"),
(static_iv_30 + KEY_RAW, iv, ciphertext, "static30||key"),
(KEY_RAW + data_prefix, data_prefix, ciphertext, "key||prefix, iv=prefix"),
(data_prefix + KEY_RAW, data_prefix, ciphertext, "prefix||key, iv=prefix"),
]
for combo, iv_used, ct, desc in variants:
aes_key = hashlib.sha256(combo).digest()
try_dec(aes_key, iv_used, ct, f"SHA256({desc})")
# Maybe the function at 0x06c3d0 does something more complex
# Let's also try: the "combined" is just the key (no IV involvement),
# and the function just copies/formats the key
# With different IV sources
# Try with BCrypt API directly
print("\n--- BCrypt API tests with static IV ---")
import ctypes
bcrypt = ctypes.windll.bcrypt
def bcrypt_dec(key_bytes, iv_bytes, ct_bytes, label):
hAlg = ctypes.c_void_p()
status = bcrypt.BCryptOpenAlgorithmProvider(ctypes.byref(hAlg), "AES", None, 0)
if status != 0:
print(f" {label}: OpenAlg failed {status}")
return None
mode = "ChainingModeCFB".encode('utf-16-le') + b'\x00\x00'
bcrypt.BCryptSetProperty(hAlg, "ChainingMode", mode, len(mode), 0)
block_len = ctypes.c_ulong(16)
bcrypt.BCryptSetProperty(hAlg, "MessageBlockLength",
ctypes.byref(block_len), 4, 0)
hKey = ctypes.c_void_p()
kb = (ctypes.c_byte * len(key_bytes))(*key_bytes)
status = bcrypt.BCryptGenerateSymmetricKey(
hAlg, ctypes.byref(hKey), None, 0, kb, len(key_bytes), 0)
if status != 0:
bcrypt.BCryptCloseAlgorithmProvider(hAlg, 0)
print(f" {label}: GenKey failed {status}")
return None
ct_buf = (ctypes.c_byte * len(ct_bytes))(*ct_bytes)
iv_buf = (ctypes.c_byte * len(iv_bytes))(*iv_bytes)
out_size = ctypes.c_ulong(0)
status = bcrypt.BCryptDecrypt(
hKey, ct_buf, len(ct_bytes), None,
iv_buf, len(iv_bytes), None, 0,
ctypes.byref(out_size), 0)
if status != 0:
bcrypt.BCryptDestroyKey(hKey)
bcrypt.BCryptCloseAlgorithmProvider(hAlg, 0)
print(f" {label}: Decrypt size query failed {status:#x}")
return None
pt_buf = (ctypes.c_byte * out_size.value)()
iv_buf2 = (ctypes.c_byte * len(iv_bytes))(*iv_bytes)
result = ctypes.c_ulong(0)
status = bcrypt.BCryptDecrypt(
hKey, ct_buf, len(ct_bytes), None,
iv_buf2, len(iv_bytes), pt_buf, out_size.value,
ctypes.byref(result), 0)
bcrypt.BCryptDestroyKey(hKey)
bcrypt.BCryptCloseAlgorithmProvider(hAlg, 0)
if status != 0:
print(f" {label}: Decrypt failed {status:#x}")
return None
pt = bytes(pt_buf[:result.value])
if len(pt) >= 24:
magic = struct.unpack_from('<Q', pt, 0x10)[0]
if magic == MAGIC:
print(f" *** BCrypt SUCCESS *** {label}")
print(f" First 64: {pt[:64].hex()}")
return pt
return pt
# BCrypt tests with various key derivations
for combo_data, desc in [
(KEY_RAW, "raw_key"),
(hashlib.sha256(KEY_RAW).digest(), "SHA256(key)"),
(hashlib.sha256(KEY_RAW + data_prefix).digest(), "SHA256(key+prefix)"),
(hashlib.sha256(data_prefix + KEY_RAW).digest(), "SHA256(prefix+key)"),
]:
for iv_data, iv_desc in [(iv, "static16"), (data_prefix, "data_prefix")]:
for ct_data, ct_desc in [(ciphertext, "ct_no_prefix"), (enc_header, "full_header")]:
result = bcrypt_dec(combo_data, iv_data, ct_data[:512],
f"key={desc}, iv={iv_desc}, ct={ct_desc}")
if result:
magic = struct.unpack_from('<Q', result, 0x10)[0] if len(result) >= 24 else 0
if magic == MAGIC:
print("FOUND THE CORRECT PARAMETERS!")
print("\nDone.")
|