This sample uses the code from References to Asymmetric Key Pairs Used For Multiple Purposes with Semgrep and takes a dynamic approach to intercept the cryptographic operations at runtime (including encryption, decryption, signing, and verification) to demonstrate the misuse of an asymmetric key pair for multiple purposes.
if(Java.available){Java.perform(function(){// --- Configuration Constants ---constCRYPTO_OP_ENCRYPT_DECRYPT="encryption/decryption";constCRYPTO_OP_SIGN_VERIFY="sign/verify";// Storage for tracking key usage: { key_toString: usage_type }constusedKeys={};// --- Core Reflection Setup Function ---/** * Initializes and returns the reflected Method object for Object.toString(). */functiongetToStringMethodRef(){try{constObject=Java.use('java.lang.Object');consttoStringMethod=Object.class.getDeclaredMethod("toString",[]);toStringMethod.setAccessible(true);returntoStringMethod;}catch(e){console.log("ā CRITICAL SETUP ERROR: Failed to prepare toString reflection: "+e);returnnull;}}// Execute the reflection setup once at the startconsttoStringMethodRef=getToStringMethodRef();if(!toStringMethodRef){console.log("ā Script halted: Reflection setup failed.");return;}try{// --- Java Class References ---constCipher=Java.use("javax.crypto.Cipher");constSignature=Java.use("java.security.Signature");// --- Helper Functions ---/** * Safely invokes the Java toString() method on the key/certificate and logs usage. */functionlogKeyDetails(key,certificate,cryptoOp){// If a certificate is provided, use its PublicKey for trackinglettrackingKey=key;if(certificate){try{trackingKey=certificate.getPublicKey();console.log(" Tracking Key: Certificate's Public Key");}catch(e){console.log(` ERROR: Failed to get Public Key from Certificate: ${e}`);return;}}if(trackingKey===null){console.log(" ERROR: Key object is null, skipping tracking.");return;}letjavaToStringResult="UNKNOWN_KEY_ID";try{// Invoke toString() using the pre-calculated reference.// Pass the instance (trackingKey) and an empty array of arguments ([]).javaToStringResult=toStringMethodRef.invoke(trackingKey,[]);}catch(e){// Fallback to native pointer if reflection failstry{javaToStringResult=trackingKey.toPointer().toString();}catch(pe){javaToStringResult=`ERROR_ID_${Date.now()}`;}console.log(` WARNING: Reflection failed. Tracking ID: ${javaToStringResult}`);}console.log(` ${cryptoOp} with key: "${javaToStringResult}"`);// --- Usage Tracking Logic ---conststoredOp=usedKeys[javaToStringResult];if(storedOp!==undefined&&storedOp!==cryptoOp){console.log("!!! WARNING: This key is used for multiple, conflicting purposes: "+storedOp+" and "+cryptoOp);}elseif(storedOp===undefined){// Store the key's first detected usageusedKeys[javaToStringResult]=cryptoOp;}}/** * Logs the current Java stack trace. */functionlogStackTrace(){console.log(" Stack Trace:");constexception=Java.use("java.lang.Exception").$new();conststackTraceElements=exception.getStackTrace();for(leti=0;i<stackTraceElements.length;i++){constelement=stackTraceElements[i];if(i<10){// Limit stack depth for cleaner outputconsole.log(" "+element.toString());}}console.log(" --- End Stack Trace ---");}// --- HOOKS: Cipher (Encryption/Decryption) ---Cipher.init.overload('int','java.security.Key').implementation=function(opmode,key){console.log("\nš *** Cipher.init(Key) HOOKED ***");logKeyDetails(key,null,CRYPTO_OP_ENCRYPT_DECRYPT);logStackTrace();this.init(opmode,key);};Cipher.init.overload('int','java.security.cert.Certificate').implementation=function(opmode,certificate){console.log("\nš *** Cipher.init(Certificate) HOOKED ***");logKeyDetails(null,certificate,CRYPTO_OP_ENCRYPT_DECRYPT);logStackTrace();this.init(opmode,certificate);};// --- HOOKS: Signature (Sign/Verify) ---Signature.initSign.overload('java.security.PrivateKey').implementation=function(key){console.log("\nāļø *** Signature.initSign(PrivateKey) HOOKED ***");logKeyDetails(key,null,CRYPTO_OP_SIGN_VERIFY);logStackTrace();this.initSign(key);};Signature.initVerify.overload('java.security.PublicKey').implementation=function(key){console.log("\nā *** Signature.initVerify(PublicKey) HOOKED ***");logKeyDetails(key,null,CRYPTO_OP_SIGN_VERIFY);logStackTrace();this.initVerify(key);};Signature.initVerify.overload('java.security.cert.Certificate').implementation=function(certificate){console.log("\nš *** Signature.initVerify(Certificate) HOOKED ***");logKeyDetails(null,certificate,CRYPTO_OP_SIGN_VERIFY);logStackTrace();this.initVerify(certificate);};console.log("ā Frida script loaded and cryptographic APIs hooked successfully!");}catch(e){console.log("ā Failed to load hooks (make sure target process is running): "+e);}});}else{console.log("ā Java is not available. Ensure Frida is attached to a JVM process.");}
The test fails because the same asymmetric key pair is used across different groups of cryptographic operations.
A single RSA key pair is performing both encryption and decryption, as well as signing and verification, which violates the requirement that an asymmetric key be restricted to one purpose class.
In the sample output:
the private key instance (android.security.keystore2.AndroidKeyStoreRSAPrivateKey) is consistently identified by the instance ID @3818961
the matching public key instance (OpenSSLRSAPublicKey) is identified by its modulus beginning with a41226cf3ca5b...
These two objects form one key pair in the Android Keystore. Their appearances across different operations show the misuse.
Following the private key reference @3818961, the first usage appears during decryption: