Android Biometric – Solution (แนวทางแก้ไขป้องกันการหลบหลีกการพิสูจน์ตัวตน)

บทความนี้ได้มุ่งเน้นที่การพิสูจน์ตัวตนด้วยลายนิ้วมือใน Android และกล่าวถึงความสามารถในการหลบหลีกการพิสูจน์ตัวตนด้วยเครื่องมือที่ชื่อว่า Frida. เพื่อนำเสนอการตรวจสอบและปรับปรุงแอปพลิเคชั่นที่มีการเขียนโปรแกรมที่ไม่ปลอดภัย ให้มีความปลอดภัยมากขึ้น รวมไปถึงการใช้ CryptoObject ในการป้องกันการหลีกเลี่ยงการพิสูจน์ตัวตนด้วยลายนิ้วมือ และย้ำถึง Flow การ Implement เพื่อประยุกต์ใช้เทคนิคเหล่านี้ในการพัฒนาแอปพลิเคชันบนมือถือ

Warunyou Sunpachit และ Boonperm Mark
Cybersecurity consultant

หน้าจอตัวอย่างการพิสูจน์ตัวตนด้วยลายนิ้วมือ

ออกแบบให้หน้าจอมีการพิสูจน์ตัวตนด้วยลายนิ้วมือ ดังนี้ ถ้านิ้วมือไม่ถูกต้องให้ ตอบกลับคำว่า Authentication Failed! และถ้าใช้นิ้วที่ถูกต้องให้ตอบกลับคำว่า Authentication Success!

หน้าจอตัวอย่างการพิสูจน์ตัวตนด้วยลายนิ้วมือ

หลีกเลี่ยงการพิสูจน์ตัวตนด้วยลายนิ้วมือโดยใช้ Frida

จากหน้าจอดังกล่าวจะพบว่าการที่เราจะแสดงข้อความ Authentication Success! จะต้องใช้นิ้วที่ถูกต้องเท่านั้น ดังนั้นถ้าเราต้องการหลีกเลี่ยงการพิสูจน์ตัวตัวดังกล่าวไม่ทราบนิ้วที่ถูกต้องโดยใช้ script ของ Frida ดังนี้

function hookBiometricPrompt_authenticate() {
    var biometricPrompt = Java.use('android.hardware.biometrics.BiometricPrompt')['authenticate'].overload('android.os.CancellationSignal', 'java.util.concurrent.Executor', 'android.hardware.biometrics.BiometricPrompt$AuthenticationCallback');
    console.log("Hooking BiometricPrompt.authenticate()...");
    biometricPrompt.implementation = function(cancellationSignal, executor, callback) {
        console.log("[BiometricPrompt.BiometricPrompt()]: cancellationSignal: " + cancellationSignal + ", executor: " + ", callback: " + callback);
        var authenticationResultInst = getBiometricPromptAuthResult();
        callback.onAuthenticationSucceeded(authenticationResultInst);
    }
}

สามารถดาวน์ใช้งาน script จาก https://codeshare.frida.re/@Saket-taneja/biometricauthenticationbypassviaexceptionhandling/

จากการทดลอง Script ดังกล่าวสามารถหลีกเลี่ยงการพิสูจน์ตัวตนด้วยลายนิ้วมือดังรูป

ตัวอย่างการหลีกเลี่ยงการพิสูจน์ตัวตนด้วยลายนิ้วมือ

ตรวจสอบการเขียนโปรแกรมที่ไม่ปลอดภัย

    private void userAuthentication() {
        BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
                .setTitle("Biometric Login")
                .setSubtitle("Log in using your biometric credentials")
                .setNegativeButtonText("Cancel")
                .setConfirmationRequired(true)
                .build();

        BiometricPrompt biometricPrompt = new BiometricPrompt(this,
                Executors.newSingleThreadExecutor(), new BiometricPrompt.AuthenticationCallback() {
            @Override
            public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                // Authentication succeeded!
                notificationsViewModel.setText("Authentication Success!");
            }
            
            @Override
            public void onAuthenticationFailed() {
                super.onAuthenticationFailed();
                // Handle authentication failure
                notificationsViewModel.setText("Authentication Failed!");
            }
        });
        biometricPrompt.authenticate(promptInfo);
    }

BiometricPrompt คือคลาสใน Android ที่ให้ความสามารถในการใช้งานการรับรองความถูกต้องของผู้ใช้งาน (Authentication) ผ่านวิธีการ biometric ได้แก่ ลายนิ้วมือ ใบหน้า เป็นต้น ในการใช้งาน BiometricPrompt ต้องสร้าง PromptInfo ที่ระบุรายละเอียดการรับรองความถูกต้องและ กำหนด AuthenticationCallback ที่จะเรียกเมื่อมีการรับรองความถูกต้องที่สำเร็จ หรือไม่สำเร็จ

ในการทำงานข้างต้น biometricPrompt.authenticate(promptInfo) จะทำการเริ่มกระบวนการรับรองความถูกต้องผ่าน biometric ถ้าสำเร็จ onAuthenticationSucceeded จะถูกเรียก แต่ถ้าไม่สำเร็จ onAuthenticationFailed จะถูกเรียก

            public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                // Authentication succeeded!
                notificationsViewModel.setText("Authentication Success!");
            }

จาก Code ข้างต้นพบว่าถ้าเราสามารถเรียกใช้งาน onAuthenticationSucceeded ได้ จะสามารถผ่านการพิสูจน์ตัวตนได้ทันที ดังนั้น Script ของ Frida จึงใช้โอกาสนี้ในการเรียกฟังก์ชันดังกล่าว สามารถตรวจสอบวิธีการโจมตีรูปแบบต่าง ๆ ได้ตามบทความ Biometric Authentication – Android

การใช้ป้องกันการหลีกเลี่ยงการพิสูจน์ตัวตนโดยใช้ CryptoObject

การเขียนโปรแกรมให้มีการพิสูจน์ตัวตนอย่าปลอดภัยนั้น ควรมีการใช้งาน CryptoObject ดังต่อไปนี้

  1. แอปพลิเคชันจะสร้างกุญแจ (Key) และถูกจัดเก็บไว้ใน KeyStore โดยมี setUserAuthenticationRequired และ setInvalidatedByBiometricEnrollment ตั้งเป็นจริง (true). นอกจากนี้, setUserAuthenticationValidityDurationSeconds จะต้องถูกตั้งเป็น -1.
  2. กุญแจ (Key) นี้ถูกใช้เพื่อเข้ารหัสข้อมูล (Encrypt) ที่ใช้ในการยืนยันตัวผู้ใช้ (เช่น ข้อมูลเซสชัน หรือ โทเค็นการยืนยันตัวตน)
  3. จะต้องมีการใช้ลายนิ้วมือหรือใบหน้าที่ถูกต้องก่อนเท่านั้น ที่กุญแจจะได้รับการปล่อยออกจาก KeyStore เพื่อนำไปถอดรหัสข้อมูล (Decrypt) ซึ่งจะตรวจสอบความถูกต้องผ่านวิธีการ authenticate และ CryptoObject.

วิธีการนี้ทำให้ไม่สามารถหลีกเลี่ยงได้ แม้ว่าจะอยู่บนอุปกรณ์ที่ Root แล้ว เนื่องจากกุญแจจาก KeyStore สามารถใช้งานได้เฉพาะหลังจากยืนยันตัวตนด้วยข้อมูลชีวภาพที่สำเร็จ

อธิบายเพิ่มเติมข้อ (1)

เมื่อกำหนด .setUserAuthenticationRequired(false) ใน Android KeyStore แสดงว่า กุญแจที่เราสร้างขึ้นจะสามารถใช้งานได้โดยไม่ต้องมีการยืนยันตัวตนจากผู้ใช้ หมายความว่าแอปพลิเคชันของเรานั้นจะสามารถใช้กุญแจจาก KeyStore ได้โดยไม่จำเป็นต้องมีการยืนยันตัวตนของผู้ใช้ ไม่ว่าจะเป็นการยืนยันผ่านรหัสผ่าน รหัส PIN หรือการยืนยันตัวตนผ่านข้อมู Biometric

โดยทั่วไป, ควรกำหนดให้ .setUserAuthenticationRequired เป็น true เพาระต้องการให้กุญแจสามารถใช้งานได้เฉพาะเมื่อผู้ใช้ยืนยันตัวตน ซึ่งจะช่วยเพิ่มความปลอดภัยในการจัดการกับข้อมูลที่ถูกเข้ารหัสด้วยกุญแจนี้.

เมื่อกำหนด .setInvalidatedByBiometricEnrollment(false) ใน Android KeyStore เรากำลังระบุว่า กุญแจ (Key) ที่สร้างจะจะยังใช้งานอยู่ได้เป็นปกติ ถึงแม้ว่ามีการเพิ่มลายนิ้วมือ หรือหน้าตาใหม่ (Biometric) เข้าไปในเครื่อง

ในทางกลับกัน, ถ้าคุณตั้ง .setInvalidatedByBiometricEnrollment(true) กุญแจจะถูกทำให้เป็นโมฆะหากผู้ใช้เพิ่มหรือลบข้อมูลการรับรองความถูกต้องทางชีวภาพใหม่, เช่น เพิ่มลายนิ้วมือหรือลบลายนิ้วมือที่มีอยู่. สิ่งนี้จะช่วยให้แน่ใจว่า ข้อมูลที่ถูกเข้ารหัสด้วยกุญแจนั้นจะไม่สามารถถูกเข้าถึงได้โดยผู้ที่ไม่ได้รับอนุญาตหลังจากที่ข้อมูลการรับรองความถูกต้องทางชีวภาพถูกเปลี่ยนแปลง.

            // Generate a new secret key using the KeyGenerator
            KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
            KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(keystoreAlias,
                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                    .setUserAuthenticationRequired(true) //Only valid passcode and fingerprint
                    .setInvalidatedByBiometricEnrollment(true)//Invalidate New fingerprint
                    .build();
            keyGenerator.init(keySpec);

อธิบายเพิ่มเติมข้อ (2)

กุญแจ (Key) นี้จะถูกใช้เพื่อเข้ารหัสข้อมูลที่ใช้ในการยืนยันตัวผู้ใช้ (เช่น ข้อมูลเซสชัน หรือ โทเค็นการยืนยันตัวตน ที่ถูกฝังเอาไว้ในตัว Mobile application)

   private String encryptToken(String token, SecretKey secretKey) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);

            byte[] iv = cipher.getIV(); // Get the IV after initializing the cipher
            byte[] encryptedBytes = cipher.doFinal(token.getBytes(StandardCharsets.UTF_8));

            // Combine the IV and encrypted bytes
            byte[] combinedBytes = new byte[iv.length + encryptedBytes.length];
            System.arraycopy(iv, 0, combinedBytes, 0, iv.length);
            System.arraycopy(encryptedBytes, 0, combinedBytes, iv.length, encryptedBytes.length);

            // Convert the combined bytes to a Base64-encoded string
            return Base64.getEncoder().encodeToString(combinedBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

โค้ดข้างต้นเป็นตัวอย่างการเข้ารหัส Token ด้วยอัลกอลึทึม AES โดยใช้กุญแจที่เรียกว่า “secretKey” ดังนี้

  1. สร้าง instance ของ Cipher โดยใช้ AES/CBC/PKCS7Padding
  2. ด้วย cipher.init(Cipher.ENCRYPT_MODE, secretKey) Cipher จะถูกกำหนดเป็นโหมดการเข้ารหัส (Encrypt) และกุญแจ “secretKey” จะถูกใช้สำหรับการเข้ารหัส.
  3. cipher.getIV() ใช้เพื่อได้รับค่า Initial Vector (IV) หลังจากกำหนดค่า Cipher IV คือ บล็อกข้อมูลแรกที่ใช้ในการเข้ารหัส และจำเป็นต้องมีการสุ่มในการเข้ารหัสแบบบล็อก
  4. cipher.doFinal(token.getBytes(StandardCharsets.UTF_8)) นำโทเค็นที่เป็น string แล้วแปลงเป็นชุดข้อมูลแบบ byte และทำการเข้ารหัส
  5. หลังจากนั้น IV และบล็อกข้อมูลที่ถูกเข้ารหัสจะถูกนำมาต่อกัน (concatenate) ด้วย System.arraycopy ซึ่งสร้างอาร์เรย์ข้อมูลใหม่ที่ประกอบด้วย IV และบล็อกข้อมูลที่ถูกเข้ารหัส.
  6. สุดท้าย, อาร์เรย์ข้อมูลนั้นจะถูกแปลงเป็น string ที่ถูกเข้ารหัสด้วย Base64 ด้วย Base64.getEncoder().encodeToString(combinedBytes)

อธิบายเพิ่มเติมข้อ (3)

สร้าง CryotoObject ร่วมกับการพิสูจน์ตัวตนด้วย Biometric

  1. สร้างตัวแปร encryptionCipher ที่จะถูกใช้เป็น Cipher (คือคลาสที่ใช้เพื่อการเข้ารหัสและการถอดรหัส)
  2. คำสั่ง encryptionCipher.init(Cipher.ENCRYPT_MODE, secretKey) จะกำหนด Cipher ที่ได้จากขั้นตอนก่อนหน้าให้เข้าสู่โหมดการเข้ารหัส (ENCRYPT_MODE) ด้วยกุญแจลับ (secretKey) ที่ได้กำหนดมา
  3. BiometricPrompt.CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(encryptionCipher) สร้างCryptoObject ใหม่ขึ้นมา โดยใช้ Cipher ที่เรากำหนดไว้ โดย CryptoObject นี้สามารถใช้กับ BiometricPrompt เวลาผู้ใช้งานเอาลายนิ้วมือ หน้าตา เพื่อพิสูจน์ตัวตน
       SecretKey secretKey = generateSecretKeyFromKeystore(keystoreAlias);

        // Encrypt the token using the secret key and create a CryptoObject
        Cipher encryptionCipher;
        try {
            encryptionCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            encryptionCipher.init(Cipher.ENCRYPT_MODE, secretKey);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
            e.printStackTrace();
            // Handle the error case accordingly
            return;
        }

        BiometricPrompt.CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(encryptionCipher);

กำหนดให้ cryptoObject ให้ใช้งานได้

biometricPrompt.authenticate(promptInfo, cryptoObject) เป็นคำสั่งที่ทำการรับรองความถูกต้องของผู้ใช้ด้วยข้อมูล Biometrics และถ้าผ่านการรับรองความถูกต้อง จะยอมให้ cryptoObject (ที่ถูกส่งเป็นอาร์กิวเมนต์) สามารถใช้งานได้

  • promptInfo คืออ็อบเจ็กต์ที่บรรจุรายละเอียดเกี่ยวกับ UI ของป๊อปอัปรับรองความถูกต้องทางชีวภาพ, ซึ่งอาจรวมถึงข้อความที่จะแสดงขึ้น, หัวข้อ, และอื่น ๆ
  • cryptoObject เป็นอ็อบเจ็กต์ที่ได้จาก BiometricPrompt.CryptoObject ซึ่งบรรจุ encryptionCipher ที่ใช้ในการเข้ารหัสหรือการถอดรหัสข้อมูล การทำงานของ encryptionCipher นี้จะถูกปลดล็อกเมื่อผู้ใช้ผ่านการรับรองความถูกต้องทางชีวภาพ.

ดังนั้น, biometricPrompt.authenticate(promptInfo, cryptoObject) จะแสดงป๊อปอัป แสดงให้ผู้ใช้ให้เอาลายนิ้วมือมาทาบ และพร้อมกับนั้น, จะล็อก encryptionCipher ที่อยู่ใน cryptoObject ไว้จนกว่าผู้ใช้จะใช้ลายนิ้วมือที่ถูกต้อง

biometricPrompt.authenticate(promptInfo, cryptoObject);

ใช้งาน cryptoObject เมื่อใช้ลายนิ้วมือที่ถูกต้อง

ในโค้ด onAuthenticationSucceeded เป็นเมธอดที่จะถูกเรียกขึ้นผ่านการพิสูจน์ตัวตนโดยใช้นิ้วที่ถูกต้องสำเร็จ

  • @NonNull BiometricPrompt.AuthenticationResult result คือผลลัพธ์จากการรับรองความถูกต้องทางชีวภาพ. อ็อบเจ็กต์ AuthenticationResult นี้จะบรรจุ CryptoObject ที่เกี่ยวข้องกับการรับรองความถูกต้องนี้.
  • strongCryptoObject(secretKey, result) เป็นเมธอดที่มีการใช้งาน CryptoObject ที่ได้จากการใช้นิ้วถูกต้อง อาร์กิวเมนต์ที่ใช้กับเมธอดนี้คือ secretKey และ result (ผลลัพธ์การรับรองความถูกต้อง) เพื่อนำไปถอดรหัส เช่น ข้อมูลเซสชัน หรือ โทเค็นการยืนยันตัวตน ที่ถูกฝังเอาไว้ในตัว Mobile application
            public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                // Handle authentication success
                strongCryptoObject(secretKey, result);
            }

ตัวอย่างของเมธอด authenticateUser ซึ่งกำหนดให้ใช้ cryptoObject จากที่กล่าวมาทั้งหมดข้างต้น

    private void authenticateUser() {
        SecretKey secretKey = generateSecretKeyFromKeystore(keystoreAlias);

        // Encrypt the token using the secret key and create a CryptoObject
        Cipher encryptionCipher;
        try {
            encryptionCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            encryptionCipher.init(Cipher.ENCRYPT_MODE, secretKey);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
            e.printStackTrace();
            // Handle the error case accordingly
            return;
        }

        BiometricPrompt.CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(encryptionCipher);

        // Pass the cryptoObject to the BiometricPrompt
        BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
                .setTitle("Biometric Login")
                .setSubtitle("Log in using your biometric credentials")
                .setNegativeButtonText("Cancel")
                .setConfirmationRequired(true)
                .build();

        BiometricPrompt biometricPrompt = new BiometricPrompt(DashboardFragment.this,
                Executors.newSingleThreadExecutor(), new BiometricPrompt.AuthenticationCallback() {
            @Override
            public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
                super.onAuthenticationError(errorCode, errString);
                // Handle authentication error
            }


            public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                // Handle authentication success

                strongCryptoObject(secretKey, result);
            }


            @Override
            public void onAuthenticationFailed() {
                super.onAuthenticationFailed();
                // Handle authentication failure
            }
        });

        biometricPrompt.authenticate(promptInfo, cryptoObject);
    }

การใช้ cryptoObject เป็นส่วนหนึ่งในการถอดรหัส

จากตัวอย่างมี สองส่วนหลัก คือ strongCryptoObject และ decryptToken ทำให้สามารถถอดรหัสข้อมูลที่เรา Encrypt ไว้ เช่น ข้อมูลเซสชัน หรือ โทเค็นการยืนยันตัวตน ที่ถูกฝังเอาไว้ในตัว Mobile application เพื่อเข้าสู้การใช้งานได้ ส่วนสำคัญคือการใช้งาน cryptoObject นั้นคือ result.getCryptoObject()).getCipher()).getParameters() เป็นส่วนหนึ่งในการสร้าง Cipher เพื่อการถอดรหัส

    private void strongCryptoObject(SecretKey secretKey, BiometricPrompt.AuthenticationResult result){
        // Retrieve the cipher from the AuthenticationResult
        try {
            Cipher decryptionCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            decryptionCipher.init(Cipher.DECRYPT_MODE, secretKey,
                    Objects.requireNonNull(Objects.requireNonNull(result.getCryptoObject()).getCipher()).getParameters());

            // Decrypt the token using the decryption cipher
            String decryptedToken = decryptToken(eToken, secretKey, decryptionCipher);

            if (decryptedToken != null) {
                // Successfully decrypted the token
                requireActivity().runOnUiThread(() -> {
                    // Show the decrypted token in a message box (AlertDialog)
                    AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
                    builder.setTitle("Decrypted Token")
                            .setMessage(decryptedToken)
                            .setPositiveButton("OK", null)
                            .show();
                });
            } else {
                // Failed to decrypt the token
                throw new RuntimeException("Failed to decrypt the token");
            }
        } catch (Exception e) {
            e.printStackTrace();
            requireActivity().runOnUiThread(() -> {
                // Show an error message in a message box (AlertDialog)
                AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
                builder.setTitle("Error")
                        .setMessage(e.getMessage())
                        .setPositiveButton("OK", null)
                        .show();
            });
        }
    }

    private String decryptToken(String encryptedToken, SecretKey secretKey, Cipher cipher) {

        try {
            byte[] combinedBytes = Base64.getDecoder().decode(encryptedToken);

            // Extract the IV and encrypted bytes from the combined bytes
            byte[] ivBytes = new byte[16]; // Assuming the IV size is 16 bytes
            byte[] encryptedBytes = new byte[combinedBytes.length - ivBytes.length];
            System.arraycopy(combinedBytes, 0, ivBytes, 0, ivBytes.length);
            System.arraycopy(combinedBytes, ivBytes.length, encryptedBytes, 0, encryptedBytes.length);

            // Set the IV parameter for the decryption cipher
            IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);

            // Decrypt the token using the provided cipher and secret key
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);

            // Convert the decrypted bytes to a string using UTF-8 encoding
            String decryptedToken = new String(decryptedBytes, StandardCharsets.UTF_8);
            Log.d("DashboardFragment", "Decrypted token: " + decryptedToken);

            return decryptedToken;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

ทดสอบหลบหลีกการพิสูจน์ตัวตน

จากหน้าจอหน้าข้างล่างเป็นแบบจำลองในกรณีที่ Mobile application ของเรานั้นได้ลงทะเบียนกับ Server เพื่อจะขอเปิดใช้งานการพิสูจน์ตัวตนแบบ Biometric เรียบร้อย โดย Server จะทำการส่ง Token กลับมา (ช่องแรก) และ Mobile App ของเราสร้าง Key ขึ้นมาให้จัดเก็บอยู่ใน Keystore (ช่องสอง) และใช้ Key นั้นเข้ารหัส Token เก็บเอาไว้ใน Mobile App (ช่องสาม) สั่งเกตุว่าถ้าเราใช้นิ้วที่ถูกต้องจะสามารถถอดรหัส Token เพื่อไปยืนยันกับหลังบ้านได้

ตัวอยากหน้าจอการพิสูจน์ตัวตนโดยใช้ CryptObject ร่วมด้วย

ทดลองใช้ Script ของ Frida พบว่าไม่สามารถหลบหลีกการพิสูจน์ตัวตนโดยใช้ Biometric ได้แล้ว ไม่ปรากฏหน้าต่างแสดง Token ที่ถูกถอดรหัสตามภาพข้างต้น

ใช้ Frida เพื่อหลบหลีกการพิสูจน์ตัวตนแต่ไม่สำเร็จ

การประยุกต์ใช้ใน Mobile Application

ตัวอย่างแอปที่ใช้ศึกษาเป็นแนวทางได้คือ https://github.com/android/security-samples/tree/main/BiometricLoginKotlin สำหรับ ตัวอย่าง Flowchart แบบคร่าวของการ enable biometric authentication บนแอปอย่างปลอดภัย สามารถติดตามละเอียดเพิ่มเติมได้ที่บทความ https://blog.itselectlab.com/?p=13549

ตัวอย่าง Flowchart แบบคร่าวของการ login ด้วย biometric authentication บนแอปอย่างปลอดภัย

อ้างอิง