menu
close_24px

BLOG

How to Secure KMM Apps: Root/Jailbreak Detection & SSL Pinning Explained

Learn how to secure KMM apps with root/jailbreak detection & SSL pinning. Explore implementation, bypass techniques, and best practices for mobile app security.
  • Posted on: Aug 20, 2025
  • By Jeel Patel
  • Read time 6 Mins Read
  • Last updated on: Aug 20, 2025

In the first blog of the KMM series, we introduced Kotlin Multiplatform Mobile (KMM) and its cross-platform advantages.

In this part, we go deeper into mobile security in KMM apps, focusing on:

  • Root/Jailbreak detection
  • SSL pinning
  • How attackers bypass these protections
  • Practical tips to defend against bypasses

But, before that, let’s quickly recap what KMM is.

Kotlin Multiplatform Mobile (KMM) enables developers to write shared code for both Android and iOS, while still maintaining platform-specific implementations where necessary.

For the sake of simplicity, we have divided this blog into two sections:

  1. Implementation: Key techniques to secure your KMM app.
  2. Bypass: How attackers circumvent these protections and how to defend against them.

🔑 Key takeaway Security is not a one-time implementation. It’s an ongoing cycle of hardening → monitoring → defending against new bypass techniques.

KMM application security

Security is a critical concern in KMM applications, especially for protecting sensitive data and preventing unauthorised access.

While the two essential security mechanisms used in mobile applications, i.e., Root/Jailbreak detection and SSL pinning, help improve security, attackers often attempt to bypass them using various methods.

Risk

Impact if exploited

Example

Root/Jailbreak

Attackers gain elevated privileges

Extract sensitive data, alter API calls

Weak SSL pinning

Man-in-the-middle (MITM) attacks

User credentials intercepted

Missing security layers

Non-compliance with security standards

Banking apps failing PCI DSS audits

Section 1: Implementation

1. Root/Jailbreak detection

Root detection helps prevent attackers from running the application on a compromised device. A rooted or jailbroken device provides elevated privileges that allow attackers to modify app behaviour,  extract sensitive information, or manipulate API requests.

Since KMM supports both Android and iOS, root detection requires platform-specific implementations.

Why detect root/jailbreak?

  • Prevent unauthorised access to app data.
  • Mitigate tampering with app logic.
  • Comply with security standards (e.g., banking apps).

Root detection in Android

Root detection in Android can be implemented using various checks:

  • Checking for su binary

    The presence of su (Superuser) binary indicates that the device is rooted.

  • Detecting commonly used root apps

    Apps like Magisk, SuperSU, and KingoRoot are often installed on rooted devices.

  • Checking for read/write access in protected directories

    If an application can write to system directories, the device is likely rooted.

  • Checking for system properties

    Modifications in system properties can indicate root access.

Sample implementation (Android)

override fun isDeviceRooted(): Boolean {
       return checkForSuBinary() || checkForDangerousProps() || checkForRWPaths() || checkForRootPackages()
   }
 
   private fun checkForSuBinary(): Boolean {
       val paths = arrayOf(
           "/system/app/Superuser.apk",
           "/sbin/su",
           "/system/bin/su",
           "/system/xbin/su",
           "/data/local/xbin/su",
           "/data/local/bin/su",
           "/system/sd/xbin/su",
           "/system/bin/failsafe/su",
           "/data/local/su",
           "/su/bin"
       )

 private fun checkForRootPackages(): Boolean {
       val packages = arrayOf(
           "com.noshufou.android.su",
           "com.thirdparty.superuser",
           "eu.chainfire.supersu",
           "com.koushikdutta.superuser",
           "com.zachspong.temprootremovejb",
           "com.ramdroid.appquarantine"
       )
 
       return packages.any { packageName ->
           try {
               val pm = context.packageManager
               pm.getPackageInfo(packageName, 0)
               true
           } catch (e: Exception) {
               false
           }
       }
   }
}

 

Jailbreak detection in iOS

iOS jailbreak detection can be done using:

  • Checking for the existence of Cydia or other jailbreak apps like Sileo or Zebra.
  • Checking for writable access to system directories, such as /private.
  • Attempting to execute shell commands, which are normally restricted.

Sample implementation (iOS) [Platform.ios.kt]

 override val jailbreakDetectionResult: JailbreakDetectionResult
        get() = checkJailbreak()

    private fun checkJailbreak(): JailbreakDetectionResult {
        val jailbreakPaths = mapOf(
            "/Applications/Cydia.app" to "Cydia app detected",
            "/Library/MobileSubstrate/MobileSubstrate.dylib" to "MobileSubstrate detected",
            "/bin/bash" to "Bash shell detected",
            "/usr/sbin/sshd" to "SSH daemon detected",
            "/etc/apt" to "APT package manager detected"
        )
}

2. SSL pinning

SSL pinning prevents man-in-the-middle (MITM) attacks by ensuring that only trusted certificates are accepted, even if an attacker has installed a custom certificate authority (CA) on the device.

SSL pinning in Android

Android uses OkHttpClient with a custom certificate pinning configuration.

Certificate pinning logic

val certificatePinner = CertificatePinner.Builder()
       .add(hostname, "sha256/47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") // Original pin
       .add(hostname, "sha256/Cl7dc6nofBuxRWuGgnZc9Fi/VYDPg608JSN91g/wQXA=")
// Your pin
   .add(hostname, "sha256/wMSCtagZr+ada2dTKz2S7x2fU6YgXGn/a9pBqyEHu7U=") // Burp Pin    
   .build()

  • CertificatePinner: Specifies the valid certificate fingerprints for the server hostname. If the server certificate does not match these fingerprints, the connection will fail.
  • Hostname: jsonplaceholder.typicode.com.
  • Pins: The SHA-256 hashes of valid server certificates.

Making the API request

val client = OkHttpClient.Builder()
          .certificatePinner(certificatePinner)
         .build()

val request = Request.Builder()
        .url("https://$hostname/users")
       .build()

val response = client.newCall(request).execute()

  • OkHttpClient: Configured with the certificate pinning logic.

  • Request: A simple HTTP GET request to fetch user data.

  • Response: Captures the response body as a string.

 

SSL pinning in iOS

In iOS, SSL pinning is typically implemented using a custom URLSessionDelegate that intercepts and validates server trust during network requests.

Below is a real-world implementation that combines SSL pinning with proxy detection. This ensures the app not only trusts specific certificates but also terminates execution if a network proxy is detected (a common method used by attackers to intercept traffic).

Sample implementation (iOS)

static let shared = BlockProxyAndSSLPinning()
   private let pinnedKey = "sha256/47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=" // jsonplaceholder.typicode.com
   static func setup() {
       // 1. Check for proxy immediately
       if hasProxy() {
           fatalError("Proxy detected! Disable proxy to use the app.")
       }
       // 2. Configure SSL pinning for all requests
       URLProtocol.registerClass(BlockAllRequestsProtocol.self)
   }
   static func hasProxy() -> Bool {
       guard let settings = CFNetworkCopySystemProxySettings()?.takeRetainedValue() as? [String: Any] else {
           return false
       }
       return !(settings["HTTPProxy"] as? String ?? "").isEmpty
   }
   // SSL Pinning Validation
   func validate(challenge: URLAuthenticationChallenge) -> Bool {
       guard let trust = challenge.protectionSpace.serverTrust else { return false }

       // Verify certificate
       var error: CFError?
       guard SecTrustEvaluateWithError(trust, &error) else { return false }
       // Get server's public key
       guard let serverKey = SecTrustCopyKey(trust),
             let keyData = SecKeyCopyExternalRepresentation(serverKey, nil) else {
           return false
       }
       // Calculate SHA256
       let data = keyData as Data
       var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
       data.withUnsafeBytes { _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) }
       let serverKeyHash = "sha256/" + Data(hash).base64EncodedString()


       return serverKeyHash == pinnedKey
   }
}

SSL pinning logic

  • Uses SecTrustEvaluateWithError() to verify the server’s certificate.

  • Extracts the public key from the certificate and hashes it using SHA256.

  • Compares the resulting hash with a predefined pinned value (pinnedKey).

SSL pinning ensures your app only trusts specific certificates, reducing MITM risk.

Android SSL pinning example (OkHttp3)

val certificatePinner = CertificatePinner.Builder()
    .add(hostname, "sha256/47DEQpj8HBSa+/TIm...") 
    .build()

iOS SSL pinning with proxy detection

func validate(challenge: URLAuthenticationChallenge) -> Bool { // verify SSL certs & compare SHA256 hash }

Section 2: Bypass techniques

Root/Jailbreak detection bypass techniques

 

1. Bypassing root/jailbreak detection with Frida

Frida is a powerful dynamic instrumentation tool that allows attackers to hook into running applications and modify their behaviour at runtime.

Steps to bypass root detection using Frida

1. Start the Frida server on the device
adb push frida-server /data/local/tmp/
adb shell chmod 755 /data/local/tmp/frida-server
adb shell ./data/local/tmp/frida-server &

image (4)-1

2. Attach Frida to the target application
frida -U -n com.example.kmm_test -e "console.log('Frida attached');"

image (5)

3. Run a Frida script to override root detection functions
Java.perform(function() {
    var rootCheck = Java.use("com.example.kmm_test.RootDetector");
    rootCheck.isDeviceRooted.implementation = function() {
        console.log("Bypassing root detection");
        return false;
    };
});

2. Bypassing root/jailbreak detection with Objection

Objection is a runtime mobile exploitation framework built on Frida, allowing security researchers to bypass security mechanisms easily.

Steps to bypass root/jailbreak detection using Objection

1. Attach Objection to the app
objection -g com.example.kmm_test explore
2. Disable root detection
android root disable

image (6)-1

Successfully bypassed ✅

image (7)-2

3. Disable jailbreak detection
ios jailbreak disable

image (8)

Successfully bypassed ✅

image (9)

Bypassing root/jailbreak detection at a glance

 

Tool

Platform

Method

Outcome

Frida

Android/iOS

Hook root/jailbreak checks

Rooted device appears “safe”

Objection

Both

Disable detection at runtime

Bypasses checks instantly

SSL pinning bypass techniques

SSL pinning ensures that only specific certificates are trusted by the application, preventing Man-in-the-Middle (MITM) attacks. Attackers often try to bypass SSL pinning to intercept and modify API requests.

1. Bypassing SSL pinning with Frida

When you try the objection tool to bypass SSL pinning, you will get error messages and will not be able to bypass.

image (10)

Not able to bypass using Objection ❌

image (11)-1

Now we use a Frida script to bypass SSL pinning and target a particular OkHttp3 function and change the return value.

Steps to bypass SSL pinning

 

1. Start the Frida server
adb shell ./data/local/tmp/frida-server &
2. Use an SSL pinning bypass script
frida -U -f org.example.project -e "
Java.perform(function () {
    try {
        var CertificatePinner = Java.use('okhttp3.CertificatePinner');

        // Hook the overloaded method: check$okhttp(String, Function0)
        CertificatePinner.check$okhttp.overload('java.lang.String', 'kotlin.jvm.functions.Function0').implementation = function (host, certChainCleaner) {
            console.log('[+] Bypassing OkHTTPv3 CertificatePinner check$okhttp');
            console.log('    Host: ' + host);
            // You can return without throwing any exceptions to bypass the check
            return;
        };

        console.log('[*] OkHTTPv3 pinning bypass hooked successfully');
    } catch (err) {
        console.log('[!] Exception: ' + err.message);
    }
}); "

This Frida script is designed to bypass SSL pinning in Android applications that use OkHttp3. Specifically, it targets the CertificatePinner class and hooks the check$okhttp method, which is responsible for validating the server’s SSL certificate against a predefined pin. 

By overriding this method’s implementation, the script ensures that no exceptions are thrown during certificate validation, effectively disabling the pinning mechanism. This allows traffic interception tools like Burp Suite to inspect HTTPS traffic without being blocked by certificate mismatches. The use of Frida enables this modification dynamically at runtime without the need to recompile the APK.

image (12)

Successfully bypassed SSL pinning ✅

image (13)

2. Bypassing SSL pinning with Objection for iOS

Steps to bypass SSL pinning

 

1. Attach Objection to the app
objection -g com.example.kmm_test explore
2. Disable SSL pinning in iOS
ios sslpinning disable

Successfully bypassed iOS SSL pinning

image (14)

Bypassing SSL pinning at a glance

 

Tool

Platform

Method

Outcome

Frida

Android

Hook CertificatePinner

Disables SSL checks

Objection

iOS

Command: ios sslpinning disable

SSL pinning bypassed

Quick comparison: Implementation vs. bypass

 

Mechanism

Implementation method

Common bypass tools

Mitigation

Root/Jailbreak detection

System checks (binaries, directories, packages)

Frida, Objection

Obfuscation, RASP

SSL pinning

Certificate hash verification

Frida, Objection

Dynamic pinning, Cert transparency logs

Best practices: Staying ahead of attackers

✅ Combine root/jailbreak detection + SSL pinning for layered defense

✅ Regularly update detection logic against new bypass scripts

✅ Use runtime application self-protection (RASP) for stronger enforcement

✅ Always conduct penetration testing before app releases

Conclusion

KMM streamlines cross-platform mobile development, but robust security depends on platform-specific implementations. Root detection and SSL pinning are vital tools in your defence arsenal, but they must be implemented carefully and continuously updated.

While no system is foolproof, layering security features and understanding how attackers bypass them puts you a step ahead.

🚧 “Defence isn’t just building walls, it’s knowing where attackers climb them.”

Stay secure and always test your apps like an attacker would!

💡Final tip: Always conduct penetration testing to identify vulnerabilities before attackers do!

Frequently asked questions (FAQs)

 

1. Is SSL pinning foolproof?

No. Attackers can still bypass with Frida or Objection. That’s why layered security and continuous testing are essential.

2. Do all apps need jailbreak/root detection?

Not all apps need jailbreak/root detection, but apps handling sensitive data (banking, fintech, healthcare) should implement it.

3. What’s the best way to test my implementation?

Use tools like Frida and Objection yourself during penetration testing to validate defenses and test your implementation.