From 246fe9de3164609dd1b577ef8c1c5af5afe93ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C5=93ur?= Date: Tue, 25 Nov 2025 20:08:21 +0100 Subject: [PATCH] swiftlint lint --fix --- FrameworkClientApp/FishHook.swift | 26 +-- FrameworkClientApp/ViewController.swift | 32 ++-- IOSSecuritySuite.xcodeproj/project.pbxproj | 1 + IOSSecuritySuite/DebuggerChecker.swift | 50 +++--- IOSSecuritySuite/FileChecker.swift | 54 +++--- IOSSecuritySuite/FishHookChecker.swift | 167 +++++++++--------- IOSSecuritySuite/IOSSecuritySuite.swift | 45 +++-- IOSSecuritySuite/IntegrityChecker.swift | 94 +++++----- IOSSecuritySuite/JailbreakChecker.swift | 80 +++++---- IOSSecuritySuite/MSHookFunctionChecker.swift | 24 +-- IOSSecuritySuite/ModesChecker.swift | 4 +- IOSSecuritySuite/ProxyChecker.swift | 24 +-- .../ReverseEngineeringToolsChecker.swift | 56 +++--- IOSSecuritySuite/RuntimeHookChecker.swift | 20 +-- 14 files changed, 339 insertions(+), 338 deletions(-) diff --git a/FrameworkClientApp/FishHook.swift b/FrameworkClientApp/FishHook.swift index 1e8439d..2dfe4ad 100644 --- a/FrameworkClientApp/FishHook.swift +++ b/FrameworkClientApp/FishHook.swift @@ -46,19 +46,19 @@ private func replaceSymbolAtImage( var dataCmd: UnsafeMutablePointer! var symtabCmd: UnsafeMutablePointer! var dynamicSymtabCmd: UnsafeMutablePointer! - + guard var curCmdPointer = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: image)+UInt(MemoryLayout.size)) else { return } - + for _ in 0..(OpaquePointer(curCmd)) } - + curCmdPointer += Int(curCmd.pointee.cmdsize) } - + if linkeditCmd == nil || symtabCmd == nil || dynamicSymtabCmd == nil || dataCmd == nil { return } - + let linkedBase = slide + Int(linkeditCmd.pointee.vmaddr) - Int(linkeditCmd.pointee.fileoff) let symtab = UnsafeMutablePointer(bitPattern: linkedBase + Int(symtabCmd.pointee.symoff)) let strtab = UnsafeMutablePointer(bitPattern: linkedBase + Int(symtabCmd.pointee.stroff)) let indirectsym = UnsafeMutablePointer(bitPattern: linkedBase + Int(dynamicSymtabCmd.pointee.indirectsymoff)) - + if symtab == nil || strtab == nil || indirectsym == nil { return } - + for tmp in 0...size + MemoryLayout.size*Int(tmp)).assumingMemoryBound(to: section_64.self) - + // symbol_pointers sections if curSection.pointee.flags == S_LAZY_SYMBOL_POINTERS { replaceSymbolPointerAtSection(curSection, symtab: symtab!, strtab: strtab!, indirectsym: indirectsym!, slide: slide, symbolName: symbol, newMethod: newMethod, oldMethod: &oldMethod) @@ -109,11 +109,11 @@ private func replaceSymbolPointerAtSection( ) { let indirectSymVmAddr = indirectsym.advanced(by: Int(section.pointee.reserved1)) let sectionVmAddr = UnsafeMutablePointer(bitPattern: slide+Int(section.pointee.addr)) - + if sectionVmAddr == nil { return } - + for tmp in 0...size { let curIndirectSym = indirectSymVmAddr.advanced(by: tmp) if curIndirectSym.pointee == INDIRECT_SYMBOL_ABS || curIndirectSym.pointee == INDIRECT_SYMBOL_LOCAL { diff --git a/FrameworkClientApp/ViewController.swift b/FrameworkClientApp/ViewController.swift index fe24404..bf82cbf 100644 --- a/FrameworkClientApp/ViewController.swift +++ b/FrameworkClientApp/ViewController.swift @@ -17,14 +17,14 @@ class RuntimeClass { internal class ViewController: UIViewController { @IBOutlet weak var result: UITextView! - + override func viewDidAppear(_ animated: Bool) { var message = "" - + #if arch(arm64) message += executeChecksForArm64() #endif - + // Runtime Check let test = RuntimeClass.init() _ = test.runtimeModifiedFunction() @@ -35,7 +35,7 @@ internal class ViewController: UIViewController { selector: #selector(RuntimeClass.runtimeModifiedFunction), isClassMethod: false ) - + message += """ Jailbreak? \(IOSSecuritySuite.amIJailbroken()) Jailbreak with fail msg? \(IOSSecuritySuite.amIJailbrokenWithFailMessage()) @@ -51,7 +51,7 @@ internal class ViewController: UIViewController { Am I runtime hooked? \(amIRuntimeHooked) Am I proxied? \(IOSSecuritySuite.amIProxied()) """ - + result.text = message } } @@ -60,19 +60,19 @@ internal class ViewController: UIViewController { extension ViewController { func executeChecksForArm64() -> String { // executeAntiHook() - + // MSHook Check func msHookReturnFalse(takes: Int) -> Bool { return false /// add breakpoint at here to test `IOSSecuritySuite.hasBreakpointAt` } - + typealias FunctionType = @convention(thin) (Int) -> (Bool) func getSwiftFunctionAddr(_ function: @escaping FunctionType) -> UnsafeMutableRawPointer { return unsafeBitCast(function, to: UnsafeMutableRawPointer.self) } - + let funcAddr = getSwiftFunctionAddr(msHookReturnFalse) - + return """ Am I MSHooked? \(IOSSecuritySuite.amIMSHooked(funcAddr)) Application executable file hash value? \(IOSSecuritySuite.getMachOFileHashValue() ?? "") @@ -84,34 +84,34 @@ extension ViewController { Watchpoint? \(testWatchpoint()) """ } - + func testWatchpoint() -> Bool { - + // Uncomment these \/ and set a watch point to check the feature // var ptr = malloc(9) // var count = 3 return IOSSecuritySuite.hasWatchpoint() } - + func executeAntiHook() { typealias MyPrint = @convention(thin) (Any..., String, String) -> Void func myPrint(_ items: Any..., separator: String = " ", terminator: String = "\n") { print("print has been hooked") } - + let myprint: MyPrint = myPrint let myPrintPointer = unsafeBitCast(myprint, to: UnsafeMutableRawPointer.self) var oldMethod: UnsafeMutableRawPointer? - + // simulating hook replaceSymbol( "$ss5print_9separator10terminatoryypd_S2StF", newMethod: myPrintPointer, oldMethod: &oldMethod ) - + print("print hasn't been hooked") - + // antiHook IOSSecuritySuite.denySymbolHook("$ss5print_9separator10terminatoryypd_S2StF") print("print has been antiHooked") diff --git a/IOSSecuritySuite.xcodeproj/project.pbxproj b/IOSSecuritySuite.xcodeproj/project.pbxproj index 055c07d..55e2df4 100644 --- a/IOSSecuritySuite.xcodeproj/project.pbxproj +++ b/IOSSecuritySuite.xcodeproj/project.pbxproj @@ -281,6 +281,7 @@ /* Begin PBXShellScriptBuildPhase section */ 70B0BBC7226F3A5F000CFB39 /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); diff --git a/IOSSecuritySuite/DebuggerChecker.swift b/IOSSecuritySuite/DebuggerChecker.swift index 5de88e0..b5aadf4 100644 --- a/IOSSecuritySuite/DebuggerChecker.swift +++ b/IOSSecuritySuite/DebuggerChecker.swift @@ -15,64 +15,64 @@ internal class DebuggerChecker { var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()] var size = MemoryLayout.stride let sysctlRet = sysctl(&mib, UInt32(mib.count), &kinfo, &size, nil, 0) - + if sysctlRet != 0 { print("Error occurred when calling sysctl(). The debugger check may not be reliable") } - + return (kinfo.kp_proc.p_flag & P_TRACED) != 0 } - + static func denyDebugger() { // bind ptrace() let pointerToPtrace = UnsafeMutableRawPointer(bitPattern: -2) let ptracePtr = dlsym(pointerToPtrace, "ptrace") typealias PtraceType = @convention(c) (CInt, pid_t, CInt, CInt) -> CInt let ptrace = unsafeBitCast(ptracePtr, to: PtraceType.self) - + // PT_DENY_ATTACH == 31 let ptraceRet = ptrace(31, 0, 0, 0) - + if ptraceRet != 0 { print("Error occured when calling ptrace(). Denying debugger may not be reliable") } } - + #if arch(arm64) static func hasBreakpointAt( _ functionAddr: UnsafeRawPointer, functionSize: vm_size_t? ) -> Bool { let funcAddr = vm_address_t(UInt(bitPattern: functionAddr)) - + var vmStart: vm_address_t = funcAddr var vmSize: vm_size_t = 0 let vmRegionInfo = UnsafeMutablePointer.allocate( capacity: MemoryLayout.size/4 ) - + defer { vmRegionInfo.deallocate() } - + var vmRegionInfoCount: mach_msg_type_number_t = mach_msg_type_number_t(VM_REGION_BASIC_INFO_64) var objectName: mach_port_t = 0 - + let ret = vm_region_64( mach_task_self_, &vmStart, &vmSize, VM_REGION_BASIC_INFO_64, vmRegionInfo, &vmRegionInfoCount, &objectName ) - + if ret != KERN_SUCCESS { return false } - + let vmRegion = vmRegionInfo.withMemoryRebound( to: vm_region_basic_info_64.self, capacity: 1, { $0 } ) - + if vmRegion.pointee.protection == (VM_PROT_READ | VM_PROT_EXECUTE) { let armBreakpointOpcode = 0xe7ffdefe let arm64BreakpointOpcode = 0xd4200000 @@ -81,7 +81,7 @@ internal class DebuggerChecker { if let size = functionSize, size < judgeSize { judgeSize = size } - + for valueToOffset in 0..<(judgeSize / 4) { if (instructionBegin.advanced( by: Int(valueToOffset) @@ -92,31 +92,31 @@ internal class DebuggerChecker { } } } - + return false } - + static func hasWatchpoint() -> Bool { var threads: thread_act_array_t? var threadCount: mach_msg_type_number_t = 0 var hasWatchpoint = false - + if task_threads(mach_task_self_, &threads, &threadCount) == KERN_SUCCESS { var threadStat = arm_debug_state64_t() let capacity = MemoryLayout.size/MemoryLayout.size - + let threadStatPointer = withUnsafeMutablePointer(to: &threadStat, { $0.withMemoryRebound(to: natural_t.self, capacity: capacity, { $0 }) }) - + var count = mach_msg_type_number_t( MemoryLayout.size/MemoryLayout.size ) - + guard let threads = threads else { return false } - + for threadIndex in 0...size)) ) } - + return hasWatchpoint } #endif - + static func isParentPidUnexpected() -> Bool { let parentPid: pid_t = getppid() - + return parentPid != 1 // LaunchD is pid 1 } } diff --git a/IOSSecuritySuite/FileChecker.swift b/IOSSecuritySuite/FileChecker.swift index 77b5ca6..d943b0e 100644 --- a/IOSSecuritySuite/FileChecker.swift +++ b/IOSSecuritySuite/FileChecker.swift @@ -10,7 +10,7 @@ import Foundation internal class FileChecker { typealias CheckResult = (passed: Bool, failMessage: String) - + /** Used to store some information provided by statfs() */ @@ -20,7 +20,7 @@ internal class FileChecker { let isRoot: Bool let isReadOnly: Bool } - + /** Used to determine if a file access check should be in Write or Read-Only mode. */ @@ -28,7 +28,7 @@ internal class FileChecker { case readable case writable } - + /** Given a path, this method provides information about the associated volume. - Parameters: @@ -43,7 +43,7 @@ internal class FileChecker { assertionFailure("Failed to create a cString with path=\(path) encoding=\(encoding)") return nil } - + var statBuffer = statfs() /** Upon successful completion, the value 0 is returned; otherwise the @@ -51,7 +51,7 @@ internal class FileChecker { the error. */ let resultCode: Int32 = statfs(path, &statBuffer) - + if resultCode == 0 { let mntFromName: String = withUnsafePointer(to: statBuffer.f_mntfromname) { ptr -> String in return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)) @@ -59,7 +59,7 @@ internal class FileChecker { let mntOnName: String = withUnsafePointer(to: statBuffer.f_mntonname) { ptr -> String in return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)) } - + return MountedVolumeInfo(fileSystemName: mntFromName, directoryName: mntOnName, isRoot: (Int32(statBuffer.f_flags) & MNT_ROOTFS) != 0, @@ -68,7 +68,7 @@ internal class FileChecker { return nil } } - + /** This method provides information about all mounted volumes. - Returns: Returns nil, if getfsstat() does not return any filesystem statistics. @@ -76,12 +76,12 @@ internal class FileChecker { private static func getMountedVolumesViaGetfsstat() -> [MountedVolumeInfo]? { // If buf is NULL, getfsstat() returns just the number of mounted file systems. let count: Int32 = getfsstat(nil, 0, MNT_NOWAIT) - + guard count >= 0 else { assertionFailure("getfsstat() failed to return the number of mounted file systems.") return nil } - + var statBuffer: [statfs] = .init(repeating: .init(), count: Int(count)) let size: Int = MemoryLayout.size * statBuffer.count /** @@ -90,14 +90,14 @@ internal class FileChecker { set to indicate the error. */ let resultCode: Int32 = getfsstat(&statBuffer, Int32(size), MNT_NOWAIT) - + if resultCode > -1 { if count != resultCode { assertionFailure("Unexpected a resultCode=\(resultCode), was expecting=\(count).") } - + var result: [MountedVolumeInfo] = [] - + for entry: statfs in statBuffer { let mntFromName: String = withUnsafePointer(to: entry.f_mntfromname) { ptr -> String in return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)) @@ -105,18 +105,18 @@ internal class FileChecker { let mntOnName: String = withUnsafePointer(to: entry.f_mntonname) { ptr -> String in return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)) } - + let info = MountedVolumeInfo(fileSystemName: mntFromName, directoryName: mntOnName, isRoot: (Int32(entry.f_flags) & MNT_ROOTFS) != 0, isReadOnly: (Int32(entry.f_flags) & MNT_RDONLY) != 0) result.append(info) } - + if count != result.count { assertionFailure("Unexpected filesystems count=\(result.count), was expecting=\(count).") } - + return result } else { assertionFailure( @@ -125,7 +125,7 @@ internal class FileChecker { return nil } } - + /** Loops through the mounted volumes provided by Getfsstat() and searches for a match. - Parameters: @@ -144,7 +144,7 @@ internal class FileChecker { } return nil } - + /** Uses fopen() to check if an file exists and attempts to open it, in either Read-Only or Read-Write mode. - Parameters: @@ -156,7 +156,7 @@ internal class FileChecker { mode: FileMode) -> CheckResult? { // the 'a' or 'w' modes, create the file if it does not exist. let mode: String = FileMode.writable == mode ? "r+" : "r" - + if let filePointer: UnsafeMutablePointer = fopen(path, mode) { fclose(filePointer) return (false, "Suspicious file exists: \(path)") @@ -164,7 +164,7 @@ internal class FileChecker { return nil } } - + /** Uses stat() to check if a file exists. - returns: Returns nil, if stat() returns a non-zero result code. @@ -172,14 +172,14 @@ internal class FileChecker { static func checkExistenceOfSuspiciousFilesViaStat(path: String) -> CheckResult? { var statbuf: stat = stat() let resultCode = stat((path as NSString).fileSystemRepresentation, &statbuf) - + if resultCode == 0 { return (false, "Suspicious file exists: \(path)") } else { return nil } } - + /** Uses access() to check whether the calling process can access the file path, in either Read-Only or Write mode. - Parameters: @@ -195,14 +195,14 @@ internal class FileChecker { (path as NSString).fileSystemRepresentation, FileMode.writable == mode ? W_OK : R_OK ) - + if resultCode == 0 { return (false, "Suspicious file exists: \(path)") } else { return nil } } - + /** Checks if statvfs() considers the given path to be Read-Only. - Returns: Returns nil, if statvfs() gives a non-zero result. @@ -215,17 +215,17 @@ internal class FileChecker { assertionFailure("Failed to create a cString with path=\(path) encoding=\(encoding)") return nil } - + var statBuffer = statvfs() let resultCode: Int32 = statvfs(path, &statBuffer) - + if resultCode == 0 { return Int32(statBuffer.f_flag) & ST_RDONLY != 0 } else { return nil } } - + /** Checks if statvs() considers the volume associated with given path to be Read-Only. - Returns: Returns nil, if statfs() does not find the mounted volume. @@ -236,7 +236,7 @@ internal class FileChecker { ) -> Bool? { return getMountedVolumeInfoViaStatfs(path: path, encoding: encoding)?.isReadOnly } - + /** Checks if Getfsstat() considers the volume to be Read-Only. - Parameters: diff --git a/IOSSecuritySuite/FishHookChecker.swift b/IOSSecuritySuite/FishHookChecker.swift index 8c2dec2..7bd51da 100644 --- a/IOSSecuritySuite/FishHookChecker.swift +++ b/IOSSecuritySuite/FishHookChecker.swift @@ -5,7 +5,6 @@ // Created by jintao on 2020/4/24. // Copyright © 2020 wregula. All rights reserved. // https://github.com/TannerJin/anti-fishhook - // swiftlint:disable all import Foundation @@ -67,7 +66,7 @@ private func readUleb128(ptr: inout UnsafeMutablePointer, end: UnsafeMuta var result: UInt64 = 0 var bit = 0 var readNext = true - + repeat { if ptr == end { assert(false, "malformed uleb128") @@ -90,9 +89,9 @@ private func readSleb128(ptr: inout UnsafeMutablePointer, end: UnsafeMuta var result: Int64 = 0 var bit: Int = 0 var byte: UInt8 - + repeat { - if (ptr == end) { + if ptr == end { assert(false, "malformed sleb128") } byte = ptr.pointee @@ -100,9 +99,9 @@ private func readSleb128(ptr: inout UnsafeMutablePointer, end: UnsafeMuta bit += 7 ptr += 1 } while (byte & 0x80) == 1 - + // sign extend negative numbers - if ( (byte & 0x40) != 0 ) { + if (byte & 0x40) != 0 { result |= -1 << bit } return result @@ -117,11 +116,11 @@ internal class FishHookChecker { } } } - + @inline(__always) static func denyFishHook(_ symbol: String, at image: UnsafePointer, imageSlide slide: Int) { var symbolAddress: UnsafeMutableRawPointer? - + if SymbolFound.lookSymbol(symbol, at: image, imageSlide: slide, symbolAddress: &symbolAddress), let symbolPointer = symbolAddress { var oldMethod: UnsafeMutableRawPointer? FishHook.replaceSymbol(symbol, at: image, imageSlide: slide, newMethod: symbolPointer, oldMethod: &oldMethod) @@ -132,26 +131,26 @@ internal class FishHookChecker { // MARK: - SymbolFound internal class SymbolFound { static private let BindTypeThreadedRebase = 102 - + @inline(__always) static func lookSymbol(_ symbol: String, at image: UnsafePointer, imageSlide slide: Int, symbolAddress: inout UnsafeMutableRawPointer?) -> Bool { // target cmd var linkeditCmd: UnsafeMutablePointer! var dyldInfoCmd: UnsafeMutablePointer! var allLoadDylds = [String]() - + guard var curCmdPointer = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: image)+UInt(MemoryLayout.size)) else { return false } // all cmd for _ in 0.. 0) { + if lazyBindSize > 0 { if let lazyBindInfoCmd = UnsafeMutablePointer(bitPattern: UInt(linkeditBase + UInt64(dyldInfoCmd.pointee.lazy_bind_off))), lookLazyBindSymbol(symbol, symbolAddr: &symbolAddress, lazyBindInfoCmd: lazyBindInfoCmd, lazyBindInfoSize: lazyBindSize, allLoadDylds: allLoadDylds) { return true } } - + // look by NonLazyBindInfo let bindSize = Int(dyldInfoCmd.pointee.bind_size) - if (bindSize > 0) { + if bindSize > 0 { if let bindCmd = UnsafeMutablePointer(bitPattern: UInt(linkeditBase + UInt64(dyldInfoCmd.pointee.bind_off))), lookBindSymbol(symbol, symbolAddr: &symbolAddress, bindInfoCmd: bindCmd, bindInfoSize: bindSize, allLoadDylds: allLoadDylds) { return true } } - + return false } - + // LazySymbolBindInfo @inline(__always) private static func lookLazyBindSymbol(_ symbol: String, symbolAddr: inout UnsafeMutableRawPointer?, lazyBindInfoCmd: UnsafeMutablePointer, lazyBindInfoSize: Int, allLoadDylds: [String]) -> Bool { @@ -202,12 +201,12 @@ internal class SymbolFound { var foundSymbol = false var addend = 0 var type: Int32 = 0 - + Label: while ptr < lazyBindingInfoEnd { let immediate = Int32(ptr.pointee) & BIND_IMMEDIATE_MASK let opcode = Int32(ptr.pointee) & BIND_OPCODE_MASK ptr += 1 - + switch opcode { case BIND_OPCODE_DONE: continue @@ -225,7 +224,7 @@ internal class SymbolFound { // symbol case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: let symbolName = String(cString: ptr + 1) - if (symbolName == symbol) { + if symbolName == symbol { foundSymbol = true } while ptr.pointee != 0 { @@ -243,7 +242,7 @@ internal class SymbolFound { _ = readUleb128(ptr: &ptr, end: lazyBindingInfoEnd) // bind action case BIND_OPCODE_DO_BIND: - if (foundSymbol) { + if foundSymbol { break Label } else { continue @@ -253,10 +252,10 @@ internal class SymbolFound { return false } } - + assert(ordinal <= allLoadDylds.count) - - if (foundSymbol && ordinal >= 0 && allLoadDylds.count > 0), ordinal <= allLoadDylds.count, type != BindTypeThreadedRebase { + + if foundSymbol && ordinal >= 0 && allLoadDylds.count > 0, ordinal <= allLoadDylds.count, type != BindTypeThreadedRebase { let imageName = allLoadDylds[ordinal-1] var tmpSymbolAddress: UnsafeMutableRawPointer? if lookExportedSymbol(symbol, exportImageName: imageName, symbolAddress: &tmpSymbolAddress), let symbolPointer = tmpSymbolAddress { @@ -264,10 +263,10 @@ internal class SymbolFound { return true } } - + return false } - + // NonLazySymbolBindInfo @inline(__always) private static func lookBindSymbol(_ symbol: String, symbolAddr: inout UnsafeMutableRawPointer?, bindInfoCmd: UnsafeMutablePointer, bindInfoSize: Int, allLoadDylds: [String]) -> Bool { @@ -277,12 +276,12 @@ internal class SymbolFound { var foundSymbol = false var addend = 0 var type: Int32 = 0 - + Label: while ptr < bindingInfoEnd { let immediate = Int32(ptr.pointee) & BIND_IMMEDIATE_MASK let opcode = Int32(ptr.pointee) & BIND_OPCODE_MASK ptr += 1 - + switch opcode { case BIND_OPCODE_DONE: break Label @@ -300,7 +299,7 @@ internal class SymbolFound { // symbol case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: let symbolName = String(cString: ptr + 1) - if (symbolName == symbol) { + if symbolName == symbol { foundSymbol = true } while ptr.pointee != 0 { @@ -318,17 +317,17 @@ internal class SymbolFound { _ = readUleb128(ptr: &ptr, end: bindingInfoEnd) // do bind action case BIND_OPCODE_DO_BIND, BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: - if (foundSymbol) { + if foundSymbol { break Label } case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: - if (foundSymbol) { + if foundSymbol { break Label } else { _ = readUleb128(ptr: &ptr, end: bindingInfoEnd) } case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: - if (foundSymbol) { + if foundSymbol { break Label } else { _ = readUleb128(ptr: &ptr, end: bindingInfoEnd) // count @@ -339,7 +338,7 @@ internal class SymbolFound { case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB: _ = readUleb128(ptr: &ptr, end: bindingInfoEnd) case BIND_SUBOPCODE_THREADED_APPLY: - if (foundSymbol) { + if foundSymbol { // ImageLoaderMachO::bindLocation case BIND_TYPE_THREADED_REBASE assert(false, "maybe bind_type is BIND_TYPE_THREADED_REBASE, don't handle") return false @@ -354,9 +353,9 @@ internal class SymbolFound { return false } } - + assert(ordinal <= allLoadDylds.count) - if (foundSymbol && ordinal >= 0 && allLoadDylds.count > 0), ordinal <= allLoadDylds.count, type != BindTypeThreadedRebase { + if foundSymbol && ordinal >= 0 && allLoadDylds.count > 0, ordinal <= allLoadDylds.count, type != BindTypeThreadedRebase { let imageName = allLoadDylds[ordinal-1] var tmpSymbolAddress: UnsafeMutableRawPointer? if lookExportedSymbol(symbol, exportImageName: imageName, symbolAddress: &tmpSymbolAddress), let symbolPointer = tmpSymbolAddress { @@ -364,30 +363,30 @@ internal class SymbolFound { return true } } - + return false } - + // ExportSymbol @inline(__always) private static func lookExportedSymbol(_ symbol: String, exportImageName: String, symbolAddress: inout UnsafeMutableRawPointer?) -> Bool { var rpathImage: String? // @rpath - if (exportImageName.contains("@rpath")) { + if exportImageName.contains("@rpath") { rpathImage = exportImageName.components(separatedBy: "/").last } - + for index in 0..<_dyld_image_count() { // imageName let currentImageName = String(cString: _dyld_get_image_name(index)) if let tmpRpathImage = rpathImage { - if (!currentImageName.contains(tmpRpathImage)) { + if !currentImageName.contains(tmpRpathImage) { continue } - } else if (String(cString: _dyld_get_image_name(index)) != exportImageName) { + } else if String(cString: _dyld_get_image_name(index)) != exportImageName { continue } - + if let pointer = _lookExportedSymbol(symbol, image: _dyld_get_image_header(index), imageSlide: _dyld_get_image_vmaddr_slide(index)) { // found symbolAddress = UnsafeMutableRawPointer(mutating: pointer) @@ -395,13 +394,13 @@ internal class SymbolFound { } else { // not found, look at ReExport dylibs var allReExportDylibs = [String]() - + if let currentImage = _dyld_get_image_header(index), var curCmdPointer = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: currentImage)+UInt(MemoryLayout.size)) { - + for _ in 0.., imageSlide slide: Int) -> UnsafeMutableRawPointer? { @@ -430,19 +429,19 @@ internal class SymbolFound { var linkeditCmd: UnsafeMutablePointer! var dyldInfoCmd: UnsafeMutablePointer! var exportCmd: UnsafeMutablePointer! - + guard var curCmdPointer = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: image)+UInt(MemoryLayout.size)) else { return nil } // cmd for _ in 0.. UnsafeMutableRawPointer in let machO = image.withMemoryRebound(to: Int8.self, capacity: 1, { $0 }) let symbolAddress = machO.advanced(by: Int(readUleb128(ptr: &symbolLocation, end: end))) return UnsafeMutableRawPointer(mutating: symbolAddress) } - + switch flags & UInt64(EXPORT_SYMBOL_FLAGS_KIND_MASK) { case UInt64(EXPORT_SYMBOL_FLAGS_KIND_REGULAR): // runResolver is false by bind or lazyBind return returnSymbolAddress() case UInt64(EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL): - if (flags & UInt64(EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) != 0) { + if flags & UInt64(EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) != 0 { return nil } return returnSymbolAddress() case UInt64(EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE): - if (flags & UInt64(EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) != 0) { + if flags & UInt64(EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) != 0 { return nil } return UnsafeMutableRawPointer(bitPattern: UInt(readUleb128(ptr: &symbolLocation, end: end))) @@ -500,15 +499,15 @@ internal class SymbolFound { break } } - + return nil } - + // ExportSymbol @inline(__always) static private func lookExportedSymbolByTrieWalk(targetSymbol: String, start: UnsafeMutablePointer, end: UnsafeMutablePointer, currentLocation location: UnsafeMutablePointer, currentSymbol: String) -> UnsafeMutablePointer? { var ptr = location - + while ptr <= end { // terminalSize var terminalSize = UInt64(ptr.pointee) @@ -520,7 +519,7 @@ internal class SymbolFound { if terminalSize != 0 { return currentSymbol == targetSymbol ? ptr : nil } - + // children let children = ptr.advanced(by: Int(terminalSize)) if children >= end { @@ -529,18 +528,18 @@ internal class SymbolFound { } let childrenCount = children.pointee ptr = children + 1 - + // nodes for _ in 0.., imageSlide slide: Int, @@ -582,12 +581,12 @@ private class FishHook { var dataConstCmd: UnsafeMutablePointer! var symtabCmd: UnsafeMutablePointer! var dynamicSymtabCmd: UnsafeMutablePointer! - + guard var curCmdPointer = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: image)+UInt(MemoryLayout.size)) else { return } - + for _ in 0..(OpaquePointer(curCmd)) } - + curCmdPointer += Int(curCmd.pointee.cmdsize) } - + if linkeditCmd == nil || symtabCmd == nil || dynamicSymtabCmd == nil || (dataCmd == nil && dataConstCmd == nil) { return } - + let linkedBase = slide + Int(linkeditCmd.pointee.vmaddr) - Int(linkeditCmd.pointee.fileoff) let symtab = UnsafeMutablePointer(bitPattern: linkedBase + Int(symtabCmd.pointee.symoff)) let strtab = UnsafeMutablePointer(bitPattern: linkedBase + Int(symtabCmd.pointee.stroff)) let indirectsym = UnsafeMutablePointer(bitPattern: linkedBase + Int(dynamicSymtabCmd.pointee.indirectsymoff)) - + if symtab == nil || strtab == nil || indirectsym == nil { return } - + for segment in [dataCmd, dataConstCmd] { guard let segment else { continue } for tmp in 0.., symtab: UnsafeMutablePointer, @@ -650,11 +649,11 @@ private class FishHook { oldMethod: inout UnsafeMutableRawPointer?) { let indirectSymVmAddr = indirectsym.advanced(by: Int(section.pointee.reserved1)) let sectionVmAddr = UnsafeMutablePointer(bitPattern: slide+Int(section.pointee.addr)) - + if sectionVmAddr == nil { return } - + for tmp in 0...size { let curIndirectSym = indirectSymVmAddr.advanced(by: tmp) if curIndirectSym.pointee == INDIRECT_SYMBOL_ABS || curIndirectSym.pointee == INDIRECT_SYMBOL_LOCAL { @@ -662,7 +661,7 @@ private class FishHook { } let curStrTabOff = symtab.advanced(by: Int(curIndirectSym.pointee)).pointee.n_un.n_strx let curSymbolName = strtab.advanced(by: Int(curStrTabOff+1)) - + if String(cString: curSymbolName) == symbolName { oldMethod = sectionVmAddr!.advanced(by: tmp).pointee let err = vm_protect( diff --git a/IOSSecuritySuite/IOSSecuritySuite.swift b/IOSSecuritySuite/IOSSecuritySuite.swift index 1e6abb2..b609dc6 100644 --- a/IOSSecuritySuite/IOSSecuritySuite.swift +++ b/IOSSecuritySuite/IOSSecuritySuite.swift @@ -5,7 +5,7 @@ // Created by wregula on 23/04/2019. // Copyright © 2019 wregula. All rights reserved. // -// swiftlint:disable inclusive_language +// swiftlint:disable file_length import Foundation import MachO @@ -24,7 +24,7 @@ public class IOSSecuritySuite { public static func amIJailbroken() -> Bool { return JailbreakChecker.amIJailbroken() } - + /// This type method is used to determine the jailbreak status with a message which jailbreak indicator was detected /// /// Usage example @@ -42,7 +42,7 @@ public class IOSSecuritySuite { public static func amIJailbrokenWithFailMessage() -> (jailbroken: Bool, failMessage: String) { return JailbreakChecker.amIJailbrokenWithFailMessage() } - + /// This type method is used to determine the jailbreak status with a list of failed checks /// /// Usage example @@ -59,7 +59,7 @@ public class IOSSecuritySuite { failedChecks: [FailedCheckType]) { return JailbreakChecker.amIJailbrokenWithFailedChecks() } - + /// This type method is used to determine if application is run in emulator /// /// Usage example @@ -70,7 +70,7 @@ public class IOSSecuritySuite { public static func amIRunInEmulator() -> Bool { return EmulatorChecker.amIRunInEmulator() } - + /// This type method is used to determine if application is being debugged /// /// Usage example @@ -81,7 +81,7 @@ public class IOSSecuritySuite { public static func amIDebugged() -> Bool { return DebuggerChecker.amIDebugged() } - + /// This type method is used to deny debugger and improve the application resiliency /// /// Usage example @@ -91,7 +91,7 @@ public class IOSSecuritySuite { public static func denyDebugger() { return DebuggerChecker.denyDebugger() } - + /// This method is used to determine if application was launched by something /// other than LaunchD (i.e. the app was launched by a debugger) /// @@ -103,7 +103,7 @@ public class IOSSecuritySuite { public static func isParentPidUnexpected() -> Bool { return DebuggerChecker.isParentPidUnexpected() } - + /// This type method is used to determine if application has been tampered with /// /// Usage example @@ -123,7 +123,7 @@ public class IOSSecuritySuite { public static func amITampered(_ checks: [FileIntegrityCheck]) -> FileIntegrityCheckResult { return IntegrityChecker.amITampered(checks) } - + /// This type method is used to determine if there are any popular reverse engineering tools installed on the device /// /// Usage example @@ -134,7 +134,7 @@ public class IOSSecuritySuite { public static func amIReverseEngineered() -> Bool { return ReverseEngineeringToolsChecker.amIReverseEngineered() } - + /// This type method is used to determine the reverse engineered status with a list of failed checks /// /// Usage example @@ -151,7 +151,7 @@ public class IOSSecuritySuite { failedChecks: [FailedCheckType]) { return ReverseEngineeringToolsChecker.amIReverseEngineeredWithFailedChecks() } - + /// This type method is used to determine if `objc call` has been RuntimeHooked by for example `Flex` /// /// Usage example @@ -176,7 +176,7 @@ public class IOSSecuritySuite { renamed: "amIRuntimeHooked(dyldAllowList:detectionClass:selector:isClassMethod:)" ) public static func amIRuntimeHooked( - dyldWhiteList: [String], + dyldWhiteList: [String],// swiftlint:disable:this inclusive_language detectionClass: AnyClass, selector: Selector, isClassMethod: Bool @@ -188,7 +188,7 @@ public class IOSSecuritySuite { isClassMethod: isClassMethod ) } - + /// This type method is used to determine if `objc call` has been RuntimeHooked by for example `Flex` /// /// Usage example @@ -221,7 +221,7 @@ public class IOSSecuritySuite { isClassMethod: isClassMethod ) } - + /// This type method is used to determine if HTTP proxy was set in the iOS Settings. /// /// Usage example @@ -232,7 +232,7 @@ public class IOSSecuritySuite { public static func amIProxied() -> Bool { return ProxyChecker.amIProxied() } - + /// This type method is used to determine if the iDevice has lockdown mode turned on. /// /// Usage example @@ -265,7 +265,7 @@ public extension IOSSecuritySuite { static func amIMSHooked(_ functionAddress: UnsafeMutableRawPointer) -> Bool { return MSHookFunctionChecker.amIMSHooked(functionAddress) } - + /// This type method is used to get original `function_address` which has been hooked by `MSHook` /// /// Usage example @@ -287,7 +287,7 @@ public extension IOSSecuritySuite { static func denyMSHook(_ functionAddress: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? { return MSHookFunctionChecker.denyMSHook(functionAddress) } - + /// This type method is used to rebind `symbol` which has been hooked by `fishhook` /// /// Usage example @@ -301,7 +301,7 @@ public extension IOSSecuritySuite { static func denySymbolHook(_ symbol: String) { FishHookChecker.denyFishHook(symbol) } - + /// This type method is used to rebind `symbol` which has been hooked at one of image by `fishhook` /// /// Usage example @@ -323,7 +323,7 @@ public extension IOSSecuritySuite { ) { FishHookChecker.denyFishHook(symbol, at: image, imageSlide: slide) } - + /// This type method is used to get the SHA256 hash value of the executable file in a specified image /// /// - Attention: **Dylib only.** This means you should set Mach-O type as `Dynamic Library` in your *Build Settings*. @@ -346,7 +346,7 @@ public extension IOSSecuritySuite { static func getMachOFileHashValue(_ target: IntegrityCheckerImageTarget = .default) -> String? { return IntegrityChecker.getMachOFileHashValue(target) } - + /// This type method is used to find all loaded dylibs in the specified image /// /// - Attention: **Dylib only.** This means you should set Mach-O type as `Dynamic Library` in your /*Build Settings*. @@ -363,7 +363,7 @@ public extension IOSSecuritySuite { static func findLoadedDylibs(_ target: IntegrityCheckerImageTarget = .default) -> [String]? { return IntegrityChecker.findLoadedDylibs(target) } - + /// This type method is used to determine if there are any breakpoints at the function /// /// Usage example @@ -382,7 +382,7 @@ public extension IOSSecuritySuite { static func hasBreakpointAt(_ functionAddr: UnsafeRawPointer, functionSize: vm_size_t?) -> Bool { return DebuggerChecker.hasBreakpointAt(functionAddr, functionSize: functionSize) } - + /// This type method is used to detect if a watchpoint is being used. /// A watchpoint is a type of breakpoint that 'watches' an area of memory associated with a data item. /// @@ -403,4 +403,3 @@ public extension IOSSecuritySuite { } } #endif -// swiftlint:enable inclusive_language diff --git a/IOSSecuritySuite/IntegrityChecker.swift b/IOSSecuritySuite/IntegrityChecker.swift index 03fa5d4..2217c25 100644 --- a/IOSSecuritySuite/IntegrityChecker.swift +++ b/IOSSecuritySuite/IntegrityChecker.swift @@ -19,11 +19,11 @@ protocol Explainable { public enum FileIntegrityCheck { /// Compare current bundleID with a specified bundleID. case bundleID(String) - + /// Compare current hash value(SHA256 hex string) of `embedded.mobileprovision` with a specified hash value. /// Use command `"shasum -a 256 /path/to/embedded.mobileprovision"` to get SHA256 value on your macOS. case mobileProvision(String) - + /// Compare current hash value(SHA256 hex string) of executable file with a specified (Image Name, Hash Value). /// Only work on dynamic library and arm64. case machO(String, String) @@ -50,7 +50,7 @@ internal class IntegrityChecker { static func amITampered(_ checks: [FileIntegrityCheck]) -> FileIntegrityCheckResult { var hitChecks: [FileIntegrityCheck] = [] var result = false - + for check in checks { switch check { case .bundleID(let exceptedBundleID): @@ -70,27 +70,27 @@ internal class IntegrityChecker { } } } - + return (result, hitChecks) } - + private static func checkBundleID(_ expectedBundleID: String) -> Bool { if expectedBundleID != Bundle.main.bundleIdentifier { return true } - + return false } - + private static func checkMobileProvision(_ expectedSha256Value: String) -> Bool { guard let path = Bundle.main.path( forResource: "embedded", ofType: "mobileprovision" ) else { return false } - + let url = URL(fileURLWithPath: path) - + if FileManager.default.fileExists(atPath: url.path) { if let data = FileManager.default.contents(atPath: url.path) { // Hash: SHA256 @@ -98,16 +98,16 @@ internal class IntegrityChecker { data.withUnsafeBytes { _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) } - + if Data(hash).hexEncodedString() != expectedSha256Value { return true } } } - + return false } - + private static func checkMachO(_ imageName: String, with expectedSha256Value: String) -> Bool { #if arch(arm64) if let hashValue = getMachOFileHashValue(.custom(imageName)), hashValue != expectedSha256Value { @@ -123,7 +123,7 @@ internal class IntegrityChecker { public enum IntegrityCheckerImageTarget { /// Default image case `default` - + /// Custom image with a specified name case custom(String) } @@ -138,7 +138,7 @@ extension IntegrityChecker { return MachOParse().getTextSectionDataSHA256Value() } } - + // Find loaded dylib with a specified image target static func findLoadedDylibs(_ target: IntegrityCheckerImageTarget = .default) -> [String]? { switch target { @@ -165,7 +165,7 @@ private struct SegmentInfo { @inline(__always) private func convert16BitInt8TupleToString(int8Tuple: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)) -> String { let mirror = Mirror(reflecting: int8Tuple) - + return mirror.children.map { String(UnicodeScalar(UInt8($0.value as! Int8))) }.joined().replacingOccurrences(of: "\0", with: "") @@ -174,17 +174,17 @@ private func convert16BitInt8TupleToString(int8Tuple: (Int8, Int8, Int8, Int8, I private class MachOParse { private var base: UnsafePointer? private var slide: Int? - + init() { base = _dyld_get_image_header(0) slide = _dyld_get_image_vmaddr_slide(0) } - + init(header: UnsafePointer, slide: Int) { self.base = header self.slide = slide } - + init(imageName: String) { for index in 0..<_dyld_image_count() { if let cImgName = _dyld_get_image_name(index), String(cString: cImgName).contains(imageName), @@ -194,27 +194,27 @@ private class MachOParse { } } } - + private func vm2real(_ vmaddr: UInt64) -> UInt64? { guard let slide = slide else { return nil } - + return UInt64(slide) + vmaddr } - + func findLoadedDylibs() -> [String]? { guard let header = base else { return nil } - + guard var curCmd = UnsafeMutablePointer(bitPattern: UInt(bitPattern: header) + UInt(MemoryLayout.size)) else { return nil } - + var array: [String] = Array() var segCmd: UnsafeMutablePointer! - + for _ in 0.. SegmentInfo? { guard let header = base else { return nil } - + guard var curCmd = UnsafeMutablePointer( bitPattern: UInt(bitPattern: header) + UInt(MemoryLayout.size) ) else { return nil } - + var segCmd: UnsafeMutablePointer! - + for _ in 0.. SectionInfo? { guard let header = base else { return nil } - + guard var curCmd = UnsafeMutablePointer( bitPattern: UInt(bitPattern: header) + UInt(MemoryLayout.size) ) else { return nil } - + var segCmd: UnsafeMutablePointer! - + for _ in 0..( @@ -291,9 +291,9 @@ private class MachOParse { ) else { return nil } - + let secName = convert16BitInt8TupleToString(int8Tuple: sect.pointee.sectname) - + if secName == secname, let addr = vm2real(sect.pointee.addr) { let sectionInfo = SectionInfo(section: sect, addr: addr) @@ -302,28 +302,28 @@ private class MachOParse { } } } - + curCmd = UnsafeMutableRawPointer(curCmd).advanced(by: Int(curCmd.pointee.cmdsize)).assumingMemoryBound(to: segment_command_64.self) } - + return nil } - + func getTextSectionDataSHA256Value() -> String? { guard let sectionInfo = findSection(SEG_TEXT, secname: SECT_TEXT) else { return nil } - + guard let startAddr = UnsafeMutablePointer(bitPattern: Int(sectionInfo.addr)) else { return nil } - + let size = sectionInfo.section.pointee.size - + // Hash: SHA256 var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) _ = CC_SHA256(startAddr, CC_LONG(size), &hash) - + return Data(hash).hexEncodedString() } } diff --git a/IOSSecuritySuite/JailbreakChecker.swift b/IOSSecuritySuite/JailbreakChecker.swift index 8d5da68..12d8f29 100644 --- a/IOSSecuritySuite/JailbreakChecker.swift +++ b/IOSSecuritySuite/JailbreakChecker.swift @@ -5,7 +5,6 @@ // Created by wregula on 23/04/2019. // Copyright © 2019 wregula. All rights reserved. // -// swiftlint:disable function_body_length type_body_length line_length import Foundation import UIKit @@ -13,53 +12,54 @@ import Darwin // fork import MachO // dyld import ObjectiveC // NSObject and Selector +// swiftlint:disable:next type_body_length internal class JailbreakChecker { typealias CheckResult = (passed: Bool, failMessage: String) - + struct JailbreakStatus { let passed: Bool let failMessage: String // Added for backwards compatibility let failedChecks: [FailedCheckType] } - + static func amIJailbroken() -> Bool { return !performChecks().passed } - + static func amIJailbrokenWithFailMessage() -> (jailbroken: Bool, failMessage: String) { let status = performChecks() return (!status.passed, status.failMessage) } - + static func amIJailbrokenWithFailedChecks() -> (jailbroken: Bool, failedChecks: [FailedCheckType]) { let status = performChecks() return (!status.passed, status.failedChecks) } - + private static func performChecks() -> JailbreakStatus { var passed = true var failMessage = "" var failedChecks: [FailedCheckType] = [] - + for check in FailedCheck.allCases { let result = getResult(from: check) - + passed = passed && result.passed - + if !result.passed { failedChecks.append((check: check, failMessage: result.failMessage)) - + if !failMessage.isEmpty { failMessage += ", " } } - + failMessage += result.failMessage } - + return JailbreakStatus(passed: passed, failMessage: failMessage, failedChecks: failedChecks) - + func getResult(from check: FailedCheck) -> CheckResult { switch check { case .urlSchemes: @@ -88,7 +88,7 @@ internal class JailbreakChecker { } } } - + private static func canOpenUrlFromList(urlSchemes: [String]) -> CheckResult { for urlScheme in urlSchemes { if let url = URL(string: urlScheme) { @@ -99,7 +99,7 @@ internal class JailbreakChecker { } return (true, "") } - + // "cydia://" URL scheme has been removed. Turns out there is app in the official App Store // that has the cydia:// URL scheme registered, so it may cause false positive // "activator://" URL scheme has been removed for the same reason. @@ -112,7 +112,8 @@ internal class JailbreakChecker { ] return canOpenUrlFromList(urlSchemes: urlSchemes) } - + + // swiftlint:disable:next function_body_length private static func checkExistenceOfSuspiciousFiles() -> CheckResult { var paths = [ "/var/mobile/Library/Preferences/ABPattern", // A-Bypass @@ -183,7 +184,7 @@ internal class JailbreakChecker { "/Library/MobileSubstrate/DynamicLibraries", // DynamicLibraries directory in general "/var/mobile/Library/Preferences/me.jjolano.shadow.plist" ] - + // These files can give false positive in the emulator if !EmulatorChecker.amIRunInEmulator() { paths += [ @@ -196,7 +197,7 @@ internal class JailbreakChecker { "/usr/bin/ssh" ] } - + for path in paths { if FileManager.default.fileExists(atPath: path) { return (false, "Suspicious file exists: \(path)") @@ -214,10 +215,10 @@ internal class JailbreakChecker { return result } } - + return (true, "") } - + private static func checkSuspiciousFilesCanBeOpened() -> CheckResult { var paths = [ "/.installed_unc0ver", @@ -227,7 +228,7 @@ internal class JailbreakChecker { "/etc/apt", "/var/log/apt" ] - + // These files can give false positive in the emulator if !EmulatorChecker.amIRunInEmulator() { paths += [ @@ -236,7 +237,7 @@ internal class JailbreakChecker { "/usr/bin/ssh" ] } - + for path in paths { if FileManager.default.isReadableFile(atPath: path) { return (false, "Suspicious file can be opened: \(path)") @@ -252,10 +253,10 @@ internal class JailbreakChecker { return result } } - + return (true, "") } - + private static func checkRestrictedDirectoriesWriteable() -> CheckResult { let paths = [ "/", @@ -263,7 +264,7 @@ internal class JailbreakChecker { "/private/", "/jb/" ] - + if FileChecker.checkRestrictedPathIsReadonlyViaStatvfs(path: "/") == false { return (false, "Restricted path '/' is not Read-Only") } else if FileChecker.checkRestrictedPathIsReadonlyViaStatfs(path: "/") == false { @@ -271,7 +272,7 @@ internal class JailbreakChecker { } else if FileChecker.checkRestrictedPathIsReadonlyViaGetfsstat(name: "/") == false { return (false, "Restricted path '/' is not Read-Only") } - + // If library won't be able to write to any restricted directory the return(false, ...) is never reached // because of catch{} statement for path in paths { @@ -287,27 +288,27 @@ internal class JailbreakChecker { return (false, "Wrote to restricted path: \(path)") } catch {} } - + return (true, "") } - + private static func checkFork() -> CheckResult { let pointerToFork = UnsafeMutableRawPointer(bitPattern: -2) let forkPtr = dlsym(pointerToFork, "fork") typealias ForkType = @convention(c) () -> pid_t let fork = unsafeBitCast(forkPtr, to: ForkType.self) let forkResult = fork() - + if forkResult >= 0 { if forkResult > 0 { kill(forkResult, SIGTERM) } return (false, "Fork was able to create a new process (sandbox violation)") } - + return (true, "") } - + private static func checkSymbolicLinks() -> CheckResult { let paths = [ "/var/lib/undecimus/apt", // unc0ver @@ -319,7 +320,7 @@ internal class JailbreakChecker { "/usr/libexec", "/usr/share" ] - + for path in paths { do { let result = try FileManager.default.destinationOfSymbolicLink(atPath: path) @@ -328,11 +329,12 @@ internal class JailbreakChecker { } } catch {} } - + return (true, "") } - + private static func checkDYLD() -> CheckResult { + // swiftlint:disable line_length let suspiciousLibraries: Set = [ "systemhook.dylib", // Dopamine - hide jailbreak detection https://github.com/opa334/Dopamine/blob/dc1a1a3486bb5d74b8f2ea6ada782acdc2f34d0a/Application/Dopamine/Jailbreak/DOEnvironmentManager.m#L498 "roothideinit.dylib", @@ -362,19 +364,20 @@ internal class JailbreakChecker { "frida", "libcycript" ] - + // swiftlint:enable line_length + for index in 0..<_dyld_image_count() { let imageName = String(cString: _dyld_get_image_name(index)) - + // The fastest case insensitive contains check. for library in suspiciousLibraries where imageName.localizedCaseInsensitiveContains(library) { return (false, "Suspicious library loaded: \(imageName)") } } - + return (true, "") } - + private static func checkSuspiciousObjCClasses() -> CheckResult { if let shadowRulesetClass = objc_getClass("ShadowRuleset") as? NSObject.Type { let selector = Selector(("internalDictionary")) @@ -385,4 +388,3 @@ internal class JailbreakChecker { return (true, "") } } -// swiftlint:enable function_body_length type_body_length diff --git a/IOSSecuritySuite/MSHookFunctionChecker.swift b/IOSSecuritySuite/MSHookFunctionChecker.swift index c768599..0e005e2 100644 --- a/IOSSecuritySuite/MSHookFunctionChecker.swift +++ b/IOSSecuritySuite/MSHookFunctionChecker.swift @@ -77,7 +77,7 @@ internal class MSHookFunctionChecker { case adrp_x17(pageBase: UInt64) case add_x17(pageOffset: UInt64) case br_x17 - + @inline(__always) static fileprivate func translateInstruction( at functionAddr: UnsafeMutableRawPointer @@ -138,7 +138,7 @@ internal class MSHookFunctionChecker { } return nil } - + // pageBase @inline(__always) static private func getAdrpPageBase(_ functionAddr: UnsafeMutableRawPointer) -> UInt64 { @@ -160,7 +160,7 @@ internal class MSHookFunctionChecker { return UInt64(Int64(pcBase) + singExtend(imm)) } } - + @inline(__always) static func amIMSHooked(_ functionAddr: UnsafeMutableRawPointer) -> Bool { guard let firstInstruction = MSHookInstruction.translateInstruction(at: functionAddr) else { @@ -185,7 +185,7 @@ internal class MSHookFunctionChecker { return false } } - + @inline(__always) static func denyMSHook(_ functionAddr: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? { if !amIMSHooked(functionAddr) { @@ -219,13 +219,13 @@ internal class MSHookFunctionChecker { var vmRegionSize: vm_size_t = 0 var vmRegionInfoCount: mach_msg_type_number_t = mach_msg_type_number_t(VM_REGION_BASIC_INFO_64) var objectName: mach_port_t = 0 - + while true { if vmRegionAddress == 0 { // False address return nil } - + // Get VM region of designated address if vm_region_64( mach_task_self_, @@ -239,18 +239,18 @@ internal class MSHookFunctionChecker { // End of vm_regions or something wrong return nil } - + let regionInfo = UnsafeMutableRawPointer(vmRegionInfo).assumingMemoryBound( to: vm_region_basic_info_64.self ) - + // vm region of code if regionInfo.pointee.protection != (VM_PROT_READ | VM_PROT_EXECUTE) { // Memory protection level of executable region is always READ + EXECUTE vmRegionAddress += vmRegionSize continue } - + // ldr (Mobile Substrate) if case .ldr_x16 = firstInstruction { // Current vm region instruction address @@ -259,7 +259,7 @@ internal class MSHookFunctionChecker { var vmRegionInstAddr = vmRegionAddress // Last address of current vm region let vmRegionEndAddress = vmRegionAddress + vmRegionSize - + // Unlike substitute, When using substrate, branching address may resides anywhere in vm region. // So every region must be investigated to check whether it contains original function address. while vmRegionEndAddress >= vmRegionInstAddr { @@ -269,12 +269,12 @@ internal class MSHookFunctionChecker { ) else { continue } - + if UInt(bitPattern: instructionAddr.pointee) == 0 { vmRegionProcedureAddr = vmRegionInstAddr + 4 continue } - + if case .ldr_x16 = MSHookInstruction.translateInstruction( at: instructionAddr ), case .br_x16 = MSHookInstruction.translateInstruction( diff --git a/IOSSecuritySuite/ModesChecker.swift b/IOSSecuritySuite/ModesChecker.swift index ab438e3..d60e8c7 100644 --- a/IOSSecuritySuite/ModesChecker.swift +++ b/IOSSecuritySuite/ModesChecker.swift @@ -9,9 +9,9 @@ import Foundation internal class ModesChecker { - + static func amIInLockdownMode() -> Bool { return UserDefaults.standard.bool(forKey: "LDMGlobalEnabled") } - + } diff --git a/IOSSecuritySuite/ProxyChecker.swift b/IOSSecuritySuite/ProxyChecker.swift index 7712349..e2ea613 100644 --- a/IOSSecuritySuite/ProxyChecker.swift +++ b/IOSSecuritySuite/ProxyChecker.swift @@ -13,17 +13,17 @@ internal class ProxyChecker { guard let unmanagedSettings = CFNetworkCopySystemProxySettings() else { return false } - + let settingsOptional = unmanagedSettings.takeRetainedValue() as? [String: Any] - + guard let settings = settingsOptional else { return false } - - if(considerVPNConnectionAsProxy) { + + if considerVPNConnectionAsProxy { if let scoped = settings["__SCOPED__"] as? [String: Any] { for interface in scoped.keys { - + let names = [ "tap", "tun", @@ -31,17 +31,17 @@ internal class ProxyChecker { "ipsec", "utun" ] - - for name in names { - if(interface.contains(name)) { - print("detected: \(interface)") - return true - } + + if names.contains(where: { name in + interface.contains(name) + }) { + print("detected: \(interface)") + return true } } } } - + return (settings.keys.contains("HTTPProxy") || settings.keys.contains("HTTPSProxy")) } } diff --git a/IOSSecuritySuite/ReverseEngineeringToolsChecker.swift b/IOSSecuritySuite/ReverseEngineeringToolsChecker.swift index 31daea9..ecc3a58 100644 --- a/IOSSecuritySuite/ReverseEngineeringToolsChecker.swift +++ b/IOSSecuritySuite/ReverseEngineeringToolsChecker.swift @@ -11,27 +11,27 @@ import MachO // dyld internal class ReverseEngineeringToolsChecker { typealias CheckResult = (passed: Bool, failMessage: String) - + struct ReverseEngineeringToolsStatus { let passed: Bool let failedChecks: [FailedCheckType] } - + static func amIReverseEngineered() -> Bool { return !performChecks().passed } - + static func amIReverseEngineeredWithFailedChecks() -> (reverseEngineered: Bool, failedChecks: [FailedCheckType]) { let status = performChecks() return (!status.passed, status.failedChecks) } - + private static func performChecks() -> ReverseEngineeringToolsStatus { var passed = true var result: CheckResult = (true, "") var failedChecks: [FailedCheckType] = [] - + for check in FailedCheck.allCases { switch check { case .existenceOfSuspiciousFiles: @@ -45,17 +45,17 @@ internal class ReverseEngineeringToolsChecker { default: continue } - + passed = passed && result.passed - + if !result.passed { failedChecks.append((check: check, failMessage: result.failMessage)) } } - + return ReverseEngineeringToolsStatus(passed: passed, failedChecks: failedChecks) } - + private static func checkDYLD() -> CheckResult { let suspiciousLibraries: Set = [ "FridaGadget", @@ -63,31 +63,31 @@ internal class ReverseEngineeringToolsChecker { "cynject", "libcycript" ] - + for index in 0..<_dyld_image_count() { let imageName = String(cString: _dyld_get_image_name(index)) - + // The fastest case insensitive contains check. for library in suspiciousLibraries where imageName.localizedCaseInsensitiveContains(library) { return (false, "Suspicious library loaded: \(imageName)") } } - + return (true, "") } - + private static func checkExistenceOfSuspiciousFiles() -> CheckResult { let paths = [ "/usr/sbin/frida-server" ] - + for path in paths where FileManager.default.fileExists(atPath: path) { return (false, "Suspicious file found: \(path)") } - + return (true, "") } - + private static func checkOpenedPorts() -> CheckResult { let ports = [ 27042, // default Frida @@ -95,58 +95,58 @@ internal class ReverseEngineeringToolsChecker { 22, // OpenSSH 44 // checkra1n ] - + for port in ports where canOpenLocalConnection(port: port) { return (false, "Port \(port) is open") } - + return (true, "") } - + private static func canOpenLocalConnection(port: Int) -> Bool { func swapBytesIfNeeded(port: in_port_t) -> in_port_t { let littleEndian = Int(OSHostByteOrder()) == OSLittleEndian return littleEndian ? _OSSwapInt16(port) : port } - + var serverAddress = sockaddr_in() serverAddress.sin_family = sa_family_t(AF_INET) serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1") serverAddress.sin_port = swapBytesIfNeeded(port: in_port_t(port)) let sock = socket(AF_INET, SOCK_STREAM, 0) - + let result = withUnsafePointer(to: &serverAddress) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { connect(sock, $0, socklen_t(MemoryLayout.stride)) } } - + defer { close(sock) } - + if result != -1 { return true // Port is opened } - + return false } - + // EXPERIMENTAL private static func checkPSelectFlag() -> CheckResult { var kinfo = kinfo_proc() var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()] var size = MemoryLayout.stride let sysctlRet = sysctl(&mib, UInt32(mib.count), &kinfo, &size, nil, 0) - + if sysctlRet != 0 { print("Error occurred when calling sysctl(). This check may not be reliable") } - + if (kinfo.kp_proc.p_flag & P_SELECT) != 0 { return (false, "Suspicious PFlag value") } - + return (true, "") } } diff --git a/IOSSecuritySuite/RuntimeHookChecker.swift b/IOSSecuritySuite/RuntimeHookChecker.swift index aadeaa5..c5f4a54 100644 --- a/IOSSecuritySuite/RuntimeHookChecker.swift +++ b/IOSSecuritySuite/RuntimeHookChecker.swift @@ -15,7 +15,7 @@ internal class RuntimeHookChecker { FishHookChecker.denyFishHook("dladdr") #endif }() - + static func amIRuntimeHook( dyldAllowList: [String], detectionClass: AnyClass, @@ -28,41 +28,41 @@ internal class RuntimeHookChecker { } else { method = class_getInstanceMethod(detectionClass, selector) } - + guard let method = method else { // method not found return true } - + let imp = method_getImplementation(method) var info = Dl_info() - + _ = swiftOnceDenyFishHooK - + // dladdr will look through vm range of allImages for vm range of an Image that contains pointer // of method and return info of the Image if dladdr(UnsafeRawPointer(imp), &info) != 1 { return false } - + let impDyldPath = String(cString: info.dli_fname).lowercased() - + // at system framework if impDyldPath.contains("/System/Library".lowercased()) { return false } - + // at binary of app let binaryPath = String(cString: _dyld_get_image_name(0)).lowercased() if impDyldPath.contains(binaryPath) { return false } - + // at whiteList if let impFramework = impDyldPath.components(separatedBy: "/").last { return !dyldAllowList.map({ $0.lowercased() }).contains(impFramework) } - + // at injected framework return true }