DEV Community

Ajmal Hasan
Ajmal Hasan

Posted on • Updated on

Implementing Root Detection and File Existence Security Checks in React Native (iOS) - 2

1. Setting Up the Folder Structure and Files

  1. Create the RootDetection Group in Xcode:

    • Open your React Native project in Xcode.
    • In the Project Navigator, right-click on your project’s folder (usually named ios/[YourProjectName]).
    • Select New Group and name it RootDetection.
    • This group will hold both the Objective-C and Swift files (JailbreakDetector.m and JailbreakDetector.swift), organizing your jailbreak detection code in one place.
  2. Add JailbreakDetector.swift to the RootDetection Group:

    • Right-click on RootDetection and select New File.
    • Choose Swift File and name it JailbreakDetector.swift.
    • Xcode will prompt you to create a bridging header if you don’t already have one. Accept the prompt, and Xcode will automatically create the bridging header file.

Image description

  1. Add JailbreakDetector.m to the RootDetection Group:
    • Right-click on RootDetection again and select New File.
    • Choose Objective-C File and name it JailbreakDetector.m.

Now, you have the following structure under the RootDetection group:

   RootDetection/
   ├── JailbreakDetector.m
   └── JailbreakDetector.swift
Enter fullscreen mode Exit fullscreen mode

2. Writing the Swift Jailbreak Detection Logic in JailbreakDetector.swift

The JailbreakDetector.swift file contains various checks to identify if the device is jailbroken. Here’s the complete code:

import Foundation
import UIKit
import React

@objc(JailbreakDetector)
class JailbreakDetector: NSObject {

  @objc static func requiresMainQueueSetup() -> Bool {
      return true
  }

  @objc
  func isJailbroken(_ resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
    if hasJailbreakIndicators() || canOpenSuspiciousURIs() {
      resolver(true)
    } else {
      resolver(false)
    }
  }

  // Check if any indicators of jailbreak are found
  private func hasJailbreakIndicators() -> Bool {
    return hasWritableSystemDirectory() ||
           hasSuspiciousPaths(jailbreakPaths) ||
           hasSuspiciousPaths(appPaths) ||
           hasSuspiciousPaths(fridaPaths)
  }

  // Check if the system directory is writable
  private func hasWritableSystemDirectory() -> Bool {
    let testString = "Jailbreak Test"
    let path = "/private/jailbreak.txt"
    do {
      try testString.write(toFile: path, atomically: true, encoding: .utf8)
      try FileManager.default.removeItem(atPath: path)
      return true
    } catch {
      return false
    }
  }

  // Check if any suspicious paths exist
  private func hasSuspiciousPaths(_ paths: [String]) -> Bool {
    for path in paths {
      if FileManager.default.fileExists(atPath: path) {
        return true
      }
    }
    return false
  }

  // Check if any suspicious URI schemes can be opened
  private func canOpenSuspiciousURIs() -> Bool {
    let uriSchemes = ["cydia://package/com.example.package", "cydia://", "undecimus://", "sileo://", "zebra://", "dopamine://"]
    for scheme in uriSchemes {
      if let url = URL(string: scheme), UIApplication.shared.canOpenURL(url) {
        return true
      }
    }
    return false
  }

  // Constants for commonly found jailbreak and reverse engineering paths
  private let jailbreakPaths = [
    "/bin/bash", "/usr/sbin/sshd", "/etc/apt", "/private/var/lib/apt/",
    "/Library/MobileSubstrate/DynamicLibraries", "/private/var/lib/cydia",
    "/var/cache/apt", "/var/lib/cydia", "/var/log/syslog", "/var/tmp/cydia.log",
    "/Applications/Icy.app", "/Applications/FakeCarrier.app", "/Applications/IntelliScreen.app",
    "/Applications/SBSettings.app", "/Applications/MxTube.app", "/Applications/RockApp.app"
  ]

  private let appPaths = [
    "/Applications/FakeCarrier.app", "/Applications/Icy.app", "/Applications/IntelliScreen.app",
    "/Applications/SBSettings.app", "/Applications/Dopamine.app", "/Applications/Cydia.app",
    "/Applications/Sileo.app", "/Applications/Zebra.app"
  ]

  private let fridaPaths = [
    "/data/local/tmp/frida-server", "/usr/lib/frida/frida-server", "/usr/lib/frida/frida-agent.dylib"
  ]
}
Enter fullscreen mode Exit fullscreen mode

3. Creating the Bridging Header in JailbreakDetector.m

The JailbreakDetector.m file serves as the bridging file to expose the Swift JailbreakDetector module to React Native.

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(JailbreakDetector, NSObject)
RCT_EXTERN_METHOD(isJailbroken:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)rejecter)
@end
Enter fullscreen mode Exit fullscreen mode

Why This Bridging Header is Necessary

The bridging header enables Swift classes to be exposed to Objective-C, allowing React Native’s JavaScript code to access them. This makes it possible to call the isJailbroken method from JavaScript.

4. Using Jailbreak Detection in JavaScript

Check 2.1
https://dev.to/ajmal_hasan/implementing-root-detection-and-file-existence-security-checks-in-react-native-android-59o8

Explanation of Security Checks

  • Swift Jailbreak Detection: Uses JailbreakDetector to perform a comprehensive jailbreak check on iOS.
  • Exit Condition: If the app detects a jailbroken device, it shows a warning and exits using RNExitApp.

Conclusion

With this setup, your app can detect jailbroken devices on iOS, adding an essential layer of security. This prevents the app from running in compromised environments, protecting sensitive data and improving overall security.

ANDROID PART -->

Top comments (0)