IOSSecuritySuite – iOS Platform Security And Anti-Tampering Swift Library
313
iOS Security Suite is an advanced and easy-to-use platform security & anti-tampering library written in pure Swift! If you are developing for iOS and you want to protect your app according to the OWASP MASVS standard, chapter v8, then this library could save you a lot of time.
What ISS detects:
Jailbreak (even the iOS 11+ with brand new indicators!)
After adding ISS to your project, you will also need to update your main Info.plist. There is a check in jailbreak detection module that uses canOpenURL(_:) method and requires specifying URLs that will be queried.
The simplest method returns True/False if you just want to know if the device is jailbroken or jailed
if IOSSecuritySuite.amIJailbroken() { print("This device is jailbroken") } else { print("This device is not jailbroken") }
Verbose, if you also want to know what indicators were identified
let jailbreakStatus = IOSSecuritySuite.amIJailbrokenWithFailMessage() if jailbreakStatus.jailbroken { print("This device is jailbroken") print("Because: \(jailbreakStatus.failMessage)") } else { print("This device is not jailbroken") }
The failMessage is a String containing comma-separated indicators as shown on the example below: Cydia URL scheme detected, Suspicious file exists: /Library/MobileSubstrate/MobileSubstrate.dylib, Fork was able to create a new process
Verbose & filterable, if you also want to for example identify devices that were jailbroken in the past, but now are jailed
let jailbreakStatus = IOSSecuritySuite.amIJailbrokenWithFailedChecks() if jailbreakStatus.jailbroken { if (jailbreakStatus.failedChecks.contains { $0.check == .existenceOfSuspiciousFiles }) && (jailbreakStatus.failedChecks.contains { $0.check == .suspiciousFilesCanBeOpened }) { print("This is real jailbroken device") } }
Debugger detector module
let amIDebugged: Bool = IOSSecuritySuite.amIDebugged()
Deny debugger at all
IOSSecuritySuite.denyDebugger()
Emulator detector module
let runInEmulator: Bool = IOSSecuritySuite.amIRunInEmulator()
Reverse engineering tools detector module
let amIReverseEngineered: Bool = IOSSecuritySuite.amIReverseEngineered()
System proxy detector module
let amIProxied: Bool = IOSSecuritySuite.amIProxied()
// If we want to deny symbol hook of Swift function, we have to pass mangled name of that function denySymbolHook("$s10Foundation5NSLogyySS_s7CVarArg_pdtF") // denying hooking for the NSLog function NSLog("Hello Symbol Hook")
denySymbolHook("abort") abort()
MSHook detector module
Bool { return false } // Defining FunctionType : @convention(thin) indicates a “thin” function reference, which uses the Swift calling convention with no special “self” or “context” parameters. typealias FunctionType = @convention(thin) (Int) -> (Bool) // Getting pointer address of function we want to verify func getSwiftFunctionAddr(_ function: @escaping FunctionType) -> UnsafeMutableRawPointer { return unsafeBitCast(function, to: UnsafeMutableRawPointer.self) } let funcAddr = getSwiftFunctionAddr(someFunction) let amIMSHooked = IOSSecuritySuite.amIMSHooked(funcAddr)”>
// Defining FunctionType : @convention(thin) indicates a “thin” function reference, which uses the Swift calling convention with no special “self” or “context” parameters. typealias FunctionType = @convention(thin) (Int) -> (Bool)
// Getting pointer address of function we want to verify func getSwiftFunctionAddr(_ function: @escaping FunctionType) -> UnsafeMutableRawPointer { return unsafeBitCast(function, to: UnsafeMutableRawPointer.self) }
let funcAddr = getSwiftFunctionAddr(someFunction) let amIMSHooked = IOSSecuritySuite.amIMSHooked(funcAddr)
MSHook deny module
() // Getting original function address let funcDenyDebugger: FunctionType = denyDebugger let funcAddr = unsafeBitCast(funcDenyDebugger, to: UnsafeMutableRawPointer.self) if let originalDenyDebugger = denyMSHook(funcAddr) { // Call the original function with 1337 as Int argument unsafeBitCast(originalDenyDebugger, to: FunctionType.self)(1337) } else { denyDebugger() }”>
// Function declaration func denyDebugger(value: Int) { }
// Defining FunctionType : @convention(thin) indicates a “thin” function reference, which uses the Swift calling convention with no special “self” or “context” parameters. typealias FunctionType = @convention(thin) (Int)->()
// Getting original function address let funcDenyDebugger: FunctionType = denyDebugger let funcAddr = unsafeBitCast(funcDenyDebugger, to: UnsafeMutableRawPointer.self)
if let originalDenyDebugger = denyMSHook(funcAddr) { // Call the original function with 1337 as Int argument unsafeBitCast(originalDenyDebugger, to: FunctionType.self)(1337) } else { denyDebugger() }
File integrity verifier module
// Determine if application has been tampered with if IOSSecuritySuite.amITampered([.bundleID("biz.securing.FrameworkClientApp"), .mobileProvision("2976c70b56e9ae1e2c8e8b231bf6b0cff12bbbd0a593f21846d9a004dd181be3"), .machO("IOSSecuritySuite", "6d8d460b9a4ee6c0f378e30f137cebaf2ce12bf31a2eef3729c36889158aa7fc")]).result { print("I have been Tampered.") } else { print("I have not been Tampered.") }
// Manually verify SHA256 hash value of a loaded dylib if let hashValue = IOSSecuritySuite.getMachOFileHashValue(.custom("IOSSecuritySuite")), hashValue == "6d8d460b9a4ee6c0f378e30f137cebaf2ce12bf31a2eef3729c36889158aa7fc" { print("I have not been Tampered.") } else { print("I have been Tampered.") }
// Check SHA256 hash value of the main executable // Tip: Your application may retrieve this value from the server if let hashValue = IOSSecuritySuite.getMachOFileHashValue(.d efault), hashValue == "your-application-executable-hash-value" { print("I have not been Tampered.") } else { print("I have been Tampered.") }
Breakpoint detection module
() let func_denyDebugger: FunctionType = denyDebugger // `: FunctionType` is a must let func_addr = unsafeBitCast(func_denyDebugger, to: UnsafeMutableRawPointer.self) let hasBreakpoint = IOSSecuritySuite.hasBreakpointAt(func_addr, functionSize: nil) if hasBreakpoint { print("Breakpoint found in the specified function") } else { print("Breakpoint not found in the specified function") }”>
func denyDebugger() { // Set breakpoint here }
typealias FunctionType = @convention(thin) ()->() let func_denyDebugger: FunctionType = denyDebugger // `: FunctionType` is a must let func_addr = unsafeBitCast(func_denyDebugger, to: UnsafeMutableRawPointer.self) let hasBreakpoint = IOSSecuritySuite.hasBreakpointAt(func_addr, functionSize: nil)
if hasBreakpoint { print("Breakpoint found in the specified function") } else { print("Breakpoint not found in the specified function") }
Watchpoint detection module
Bool{ // lldb: watchpoint set expression ptr var ptr = malloc(9) // lldb: watchpoint set variable count var count = 3 return IOSSecuritySuite.hasWatchpoint() }”>
// Set a breakpoint at the testWatchpoint function func testWatchpoint() -> Bool{ // lldb: watchpoint set expression ptr var ptr = malloc(9) // lldb: watchpoint set variable count var count = 3 return IOSSecuritySuite.hasWatchpoint() }
Security considerations
Before using this and other platform security checkers, you have to understand that:
Including this tool in your project is not the only thing you should do in order to improve your app security! You can read a general mobile security whitepaper here.
Detecting if a device is jailbroken is done locally on the device. It means that every jailbreak detector may be bypassed (even this)!
Swift code is considered to be harder to manipulate dynamically than Objective-C. Since this library was written in pure Swift, the IOSSecuritySuite methods shouldn’t be exposed to Objective-C runtime (which makes it more difficult to bypass ). You have to know that attacker is still able to MSHookFunction/MSFindSymbol Swift symbols and dynamically change Swift code execution flow.
It’s also a good idea to obfuscate the whole project code, including this library. See Swiftshield
Contribution
Yes, please! If you have a better idea or you just want to improve this project, please text me on Twitter or Linkedin. Pull requests are more than welcome!