// Frida hook for the native ExchangeKey credential-token crypto (Windows CNG / bcrypt.dll). // Traces the ECDH secret agreement, the KDF (with its parameter list), symmetric-key import, and // encrypt/hash so the 26-byte v8 credential-token derivation can be reconstructed in managed code. // Reverse-engineering aid only — observes the native client; nothing is shipped from here. 'use strict'; function resolve(modName, fnName) { let m = null; try { m = Process.getModuleByName(modName); } catch (e) { try { m = Module.load(modName); } catch (e2) { return null; } } try { return m.findExportByName(fnName); } catch (e) { return null; } } function dump(label, ptr, len) { if (ptr.isNull() || len <= 0) { console.log(label + ' '); return; } const n = Math.min(len, 256); console.log(label + ' (' + len + ' bytes)\n' + hexdump(ptr, { length: n, header: false, ansi: false })); } function hook(modName, fnName, onEnter, onLeave) { const addr = resolve(modName, fnName); if (!addr) { console.log('[skip] ' + modName + '!' + fnName + ' not found'); return; } Interceptor.attach(addr, { onEnter: onEnter, onLeave: onLeave }); console.log('[hooked] ' + modName + '!' + fnName); } // BCryptOpenAlgorithmProvider(phAlgorithm, pszAlgId, pszImplementation, dwFlags) — names every algo used. hook('bcrypt.dll', 'BCryptOpenAlgorithmProvider', function (a) { console.log('[OpenAlgorithmProvider] algId=' + (a[1].isNull() ? '?' : a[1].readUtf16String())); }); // BCryptSecretAgreement(hPrivKey, hPubKey, *phAgreedSecret, flags) hook('bcrypt.dll', 'BCryptSecretAgreement', function (a) { console.log('[SecretAgreement] hPriv=' + a[0] + ' hPub=' + a[1]); }); // Decode a BCryptBufferDesc parameter list (used by BCryptDeriveKey) into (type -> bytes). function dumpParamList(pParamList) { if (pParamList.isNull()) { console.log(' paramList '); return; } const cBuffers = pParamList.add(4).readU32(); // ULONG ulVersion; ULONG cBuffers; const pBuffers = pParamList.add(8).readPointer(); // BCryptBuffer* pBuffers; const names = { 0: 'HASH_ALGORITHM', 1: 'SECRET_PREPEND', 2: 'SECRET_APPEND', 3: 'HMAC_KEY', 4: 'TLS_PRF_LABEL', 5: 'TLS_PRF_SEED', 6: 'SECRET_HANDLE', 8: 'SP80056A_CONCAT', 0xD: 'LABEL', 0xE: 'CONTEXT', 0xF: 'SALT', 0x10: 'ITERATION_COUNT' }; console.log(' paramList cBuffers=' + cBuffers); for (let i = 0; i < cBuffers; i++) { const b = pBuffers.add(i * 16); // { ULONG cbBuffer; ULONG BufferType; PVOID pvBuffer; } const cb = b.readU32(); const type = b.add(4).readU32(); const pv = b.add(8).readPointer(); const tn = names[type] || ('0x' + type.toString(16)); if (type === 0 || type === 4 || type === 0xD) { // string-ish (hash alg name / label) console.log(' [' + tn + '] ' + (pv.isNull() ? '?' : pv.readUtf16String())); } else { dump(' [' + tn + ']', pv, cb); } } } // BCryptDeriveKey(hSecret, pwszKDF, *pParamList, pbDerivedKey, cbDerivedKey, *pcbResult, flags) hook('bcrypt.dll', 'BCryptDeriveKey', function (a) { this.kdf = a[1].isNull() ? '?' : a[1].readUtf16String(); this.outKey = a[3]; this.pcb = a[5]; console.log('[DeriveKey] KDF=' + this.kdf + ' cbDerivedKey=' + a[4].toInt32()); dumpParamList(a[2]); }, function () { const n = this.pcb.isNull() ? 0 : this.pcb.readU32(); dump('[DeriveKey] derived', this.outKey, n); }); hook('bcrypt.dll', 'BCryptDeriveKeyPBKDF2', function (a) { console.log('[PBKDF2] cbPassword=' + a[2].toInt32() + ' cbSalt=' + a[4].toInt32() + ' iter=' + a[5]); dump(' password', a[1], a[2].toInt32()); dump(' salt', a[3], a[4].toInt32()); }); // BCryptGenerateSymmetricKey(hAlg, *phKey, pbKeyObject, cbKeyObject, pbSecret, cbSecret, flags) — the actual key bytes. hook('bcrypt.dll', 'BCryptGenerateSymmetricKey', function (a) { dump('[GenerateSymmetricKey] keyBytes', a[4], a[5].toInt32()); }); // BCryptEncrypt(hKey, pbIn, cbIn, *pPad, pbIV, cbIV, pbOut, cbOut, *pcbResult, flags) hook('bcrypt.dll', 'BCryptEncrypt', function (a) { this.out = a[6]; this.pcb = a[8]; dump('[Encrypt] plaintext', a[1], a[2].toInt32()); dump('[Encrypt] IV', a[4], a[5].toInt32()); }, function () { const n = this.pcb.isNull() ? 0 : this.pcb.readU32(); dump('[Encrypt] ciphertext', this.out, n); }); // Hash path (in case the token is a keyed hash rather than a cipher). hook('bcrypt.dll', 'BCryptHashData', function (a) { dump('[HashData] input', a[1], a[2].toInt32()); }); hook('bcrypt.dll', 'BCryptFinishHash', function (a) { this.out = a[1]; this.cb = a[2].toInt32(); }, function () { dump('[FinishHash] digest', this.out, this.cb); }); // ---- NCrypt (CNG key-storage layer) — the likely home of the ECDH ExchangeKey + token crypto ---- // NCryptSecretAgreement(hPrivKey, hPubKey, *phAgreedSecret, dwFlags) hook('ncrypt.dll', 'NCryptSecretAgreement', function (a) { console.log('[NCryptSecretAgreement] hPriv=' + a[0] + ' hPub=' + a[1]); console.log(' backtrace (addr -> module+offset):'); Thread.backtrace(this.context, Backtracer.ACCURATE).slice(0, 14).forEach(function (addr) { const m = Process.findModuleByAddress(addr); if (m) { console.log(' ' + addr + ' ' + m.name + '+0x' + addr.sub(m.base).toString(16)); } else { console.log(' ' + addr + ' '); } }); }); // NCryptDeriveKey(hSharedSecret, pwszKDF, *pParameterList, pbDerivedKey, cbDerivedKey, *pcbResult, dwFlags) hook('ncrypt.dll', 'NCryptDeriveKey', function (a) { this.kdf = a[1].isNull() ? '?' : a[1].readUtf16String(); this.outKey = a[3]; this.pcb = a[5]; console.log('[NCryptDeriveKey] KDF=' + this.kdf + ' cbDerivedKey=' + a[4].toInt32()); dumpParamList(a[2]); }, function () { const n = this.pcb.isNull() ? 0 : this.pcb.readU32(); dump('[NCryptDeriveKey] derived', this.outKey, n); }); // NCryptEncrypt(hKey, pbInput, cbInput, *pPaddingInfo, pbOutput, cbOutput, *pcbResult, dwFlags) hook('ncrypt.dll', 'NCryptEncrypt', function (a) { this.out = a[4]; this.pcb = a[6]; dump('[NCryptEncrypt] plaintext', a[1], a[2].toInt32()); }, function () { const n = this.pcb.isNull() ? 0 : this.pcb.readU32(); dump('[NCryptEncrypt] ciphertext', this.out, n); }); // NCryptImportKey(hProvider, hImportKey, pszBlobType, *pParameterList, *phKey, pbData, cbData, dwFlags) hook('ncrypt.dll', 'NCryptImportKey', function (a) { console.log('[NCryptImportKey] blobType=' + (a[2].isNull() ? '?' : a[2].readUtf16String())); dump(' blob', a[5], a[6].toInt32()); }); // NCryptExportKey(hKey, hExportKey, pszBlobType, *pParameterList, pbOutput, cbOutput, *pcbResult, dwFlags) hook('ncrypt.dll', 'NCryptExportKey', function (a) { this.blobType = a[2].isNull() ? '?' : a[2].readUtf16String(); this.out = a[4]; this.pcb = a[6]; }, function () { const n = this.pcb.isNull() ? 0 : this.pcb.readU32(); console.log('[NCryptExportKey] blobType=' + this.blobType); dump(' blob', this.out, n); }); hook('ncrypt.dll', 'NCryptOpenStorageProvider', function (a) { console.log('[NCryptOpenStorageProvider] ' + (a[1].isNull() ? '?' : a[1].readUtf16String())); }); // BCrypt EC key operations (in case the ECDH is bcrypt but uses import/export rather than DeriveKey). hook('bcrypt.dll', 'BCryptImportKeyPair', function (a) { console.log('[BCryptImportKeyPair] blobType=' + (a[2].isNull() ? '?' : a[2].readUtf16String()) + ' cb=' + a[5].toInt32()); dump(' blob', a[4], a[5].toInt32()); }); hook('bcrypt.dll', 'BCryptExportKey', function (a) { this.blobType = a[2].isNull() ? '?' : a[2].readUtf16String(); this.out = a[3]; this.pcb = a[5]; }, function () { const n = this.pcb.isNull() ? 0 : this.pcb.readU32(); console.log('[BCryptExportKey] blobType=' + this.blobType); dump(' blob', this.out, n); }); console.log('=== CNG ExchangeKey crypto hooks installed ===');