feat: Gemini AI (doc OCR + face match) + role-assignment endpoints
- GeminiService (integrations/gemini, @Global): extractDocument (OCR fields) + faceMatch; graceful if no GEMINI_API_KEY - documents: face-match now real via Gemini; POST /admin/documents/:id/ocr extracts fields for CS auto-fill - roles: PATCH /admin/users/:id/role (admin) + POST /platform/users/role (bootstrap via x-platform-secret) - config: gemini + platform.secret; .env.example entries Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@@ -0,0 +1,149 @@
|
||||
|
||||
import UIKit
|
||||
import Flutter
|
||||
import FirebaseCore
|
||||
import GoogleMaps
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
|
||||
if let config = loadConfig() {
|
||||
if let apiKey = config["APIKey"] as? String {
|
||||
print("googleMapsAPIKey: \(Constants.googleMapsAPIKey)")
|
||||
GMSServices.provideAPIKey(apiKey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FirebaseApp.configure()
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
|
||||
// Perform security checks *before* showing the main UI
|
||||
if SecurityChecks.isDeviceCompromised() { // Call Objective-C method
|
||||
showSecurityAlert()
|
||||
return false // Prevent app from launching further
|
||||
}
|
||||
//setup method channel
|
||||
setupMethodChannel()
|
||||
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
func setupMethodChannel(){
|
||||
guard let controller = window?.rootViewController as? FlutterViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
let channel = FlutterMethodChannel(name: "com.mobileapp.store.ride/security",
|
||||
binaryMessenger: controller.binaryMessenger)
|
||||
channel.setMethodCallHandler { call, result in
|
||||
switch call.method {
|
||||
case "isNativeRooted":
|
||||
// We already check this in isDeviceCompromised,
|
||||
// so we can just return the result directly.
|
||||
result(SecurityChecks.isDeviceCompromised())
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func showSecurityAlert() {
|
||||
guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else {
|
||||
// Handle the case where rootVC is nil (extremely unlikely, but good practice)
|
||||
exit(0)
|
||||
// return
|
||||
}
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Security Warning".localized, // Use localized strings!
|
||||
message: "Compromised device detected. Exiting.".localized,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
// Add an "OK" button with an obfuscated action
|
||||
alert.addAction(UIAlertAction(title: "OK".localized, style: .destructive) { _ in
|
||||
// More robust exit (see explanation below)
|
||||
self.obfuscatedExit()
|
||||
})
|
||||
|
||||
// Use a presentationDetents to prevent dismissing
|
||||
if #available(iOS 15.0, *) {
|
||||
alert.popoverPresentationController?.sourceView = rootVC.view
|
||||
alert.popoverPresentationController?.sourceRect = CGRect(x: rootVC.view.bounds.midX, y: rootVC.view.bounds.midY, width: 0, height: 0)
|
||||
alert.popoverPresentationController?.permittedArrowDirections = []
|
||||
alert.presentationController?.delegate = self
|
||||
if let presentationController = alert.presentationController as? UISheetPresentationController {
|
||||
presentationController.detents = [.medium()] // Set a fixed height if desired
|
||||
presentationController.preferredCornerRadius = 20;
|
||||
presentationController.prefersEdgeAttachedInCompactHeight = true;// Set corner radius
|
||||
presentationController.prefersGrabberVisible = false // Optional: show a grabber
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
|
||||
rootVC.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// Obfuscated Exit (Example - you'll need a more robust solution)
|
||||
func obfuscatedExit() {
|
||||
// This is a *very* basic example. A real obfuscation would be MUCH more complex.
|
||||
let selector = NSSelectorFromString(String(format: "%@%@", "ex", "it:"))
|
||||
if responds(to: selector) {
|
||||
perform(selector, with: 0) // Call exit(0) indirectly
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Secure API Key Storage (Example)
|
||||
func getAPIKey() -> String {
|
||||
// DO NOT store API keys directly in your code!
|
||||
// Load it from a more secure location, such as:
|
||||
// 1. A configuration file that is *not* checked into source control.
|
||||
// 2. Keychain Services (recommended for iOS).
|
||||
// 3. A backend server (best option).
|
||||
|
||||
// This is just a placeholder. Replace with your actual key loading.
|
||||
// In a real app, you'd fetch this from a secure source.
|
||||
guard let filePath = Bundle.main.path(forResource: "keys", ofType: "plist"),
|
||||
let plist = NSDictionary(contentsOfFile: filePath),
|
||||
let apiKey = plist["google_maps_api_key"] as? String else {
|
||||
fatalError("Couldn't find keys or API Key") // Ensure a crash if not found
|
||||
}
|
||||
|
||||
return apiKey
|
||||
}
|
||||
func loadConfig() -> [String: Any]? {
|
||||
guard let path = Bundle.main.path(forResource: "Config", ofType: "plist"),
|
||||
let config = NSDictionary(contentsOfFile: path) as? [String: Any] else {
|
||||
return nil // Or handle the error appropriately
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
// Add for prevent dismissing
|
||||
extension AppDelegate: UIAdaptivePresentationControllerDelegate {
|
||||
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Add String extension
|
||||
extension String {
|
||||
var localized: String {
|
||||
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 387 B |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 493 B |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 734 B |
|
After Width: | Height: | Size: 853 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon_share.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon_support.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon_user.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon_wallet.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>APIKey</key>
|
||||
<string>AIzaSyCf2mW2h0HD8ZYjwh4VOa2ladw6MJkCDTM</string>
|
||||
<key>Environment</key>
|
||||
<string>development</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// Constants1.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by Hamza Aleghwairyeen on 20/02/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Constants {
|
||||
static let googleMapsAPIKey = "AIzaSyD0a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q"
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>googlechromes</string>
|
||||
<string>comgooglemaps</string>
|
||||
</array>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>This app requires contacts access to function properly.</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>com.googleusercontent.apps.594687661098-8e26699cris2k3nj5msj1osi59it9kpf</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Tripz</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Tripz</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>106</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>5.0.996</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string></string>
|
||||
<key>FirebaseAppDelegateProxyEnabled</key>
|
||||
<string>NO</string>
|
||||
<key>GMSApiKey</key>
|
||||
<string>YOUR_API_KEY</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string></string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Sovo app requires access to your camera in order to scan QR codes and capture images
|
||||
for uploading and access to connect to a call.</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>Use Face ID to securely authenticate payment accounts.</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>This app needs access to your location to provide you with the best ride experience.
|
||||
Your location data will be used to find the nearest available cars and connect you with
|
||||
the closest captain for efficient and convenient rides.</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>This app needs access to location.</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>This app needs access to your location to provide you with the best ride experience.
|
||||
Your location data will be used to find the nearest available cars and connect you with
|
||||
the closest captain for efficient and convenient rides.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app requires access to your microphone to record audio, allowing you to add
|
||||
voice recordings to your photos and videos and access to connect to a call.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Explanation of why your app needs access to the photo library.</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>location</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// JailbreakDetection.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by Hamza Aleghwairyeen on 15/02/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class JailbreakDetection {
|
||||
static func isJailbroken() -> Bool {
|
||||
let paths = [
|
||||
"/Applications/Cydia.app",
|
||||
"/Library/MobileSubstrate/MobileSubstrate.dylib",
|
||||
"/usr/sbin/sshd",
|
||||
"/etc/apt"
|
||||
]
|
||||
for path in paths {
|
||||
if FileManager.default.fileExists(atPath: path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// KeychainHelper.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by Hamza Aleghwairyeen on 12/03/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
class KeychainHelper {
|
||||
static let shared = KeychainHelper()
|
||||
|
||||
private init() {}
|
||||
|
||||
// حفظ البيانات في Keychain
|
||||
func save(key: String, value: String) {
|
||||
let data = value.data(using: .utf8)!
|
||||
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key,
|
||||
kSecValueData as String: data,
|
||||
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
||||
]
|
||||
|
||||
SecItemDelete(query as CFDictionary) // حذف أي قيمة قديمة بنفس المفتاح
|
||||
SecItemAdd(query as CFDictionary, nil) // إضافة القيمة الجديدة
|
||||
}
|
||||
|
||||
// استرجاع البيانات من Keychain
|
||||
func get(key: String) -> String? {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key,
|
||||
kSecReturnData as String: kCFBooleanTrue!,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne
|
||||
]
|
||||
|
||||
var dataTypeRef: AnyObject?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
|
||||
|
||||
guard status == errSecSuccess, let data = dataTypeRef as? Data else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
|
||||
// حذف البيانات من Keychain
|
||||
func delete(key: String) {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key
|
||||
]
|
||||
|
||||
SecItemDelete(query as CFDictionary)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Localizable.strings
|
||||
// Runner
|
||||
//
|
||||
// Created by Hamza Aleghwairyeen on 12/03/2025.
|
||||
//
|
||||
|
||||
"Security Warning" = "تحذير أمني";
|
||||
"Compromised device detected. Exiting." = "تم اكتشاف جهاز مُخترق. جارٍ الخروج.";
|
||||
"OK" = "موافق";
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>destination</key>
|
||||
<string>export</string>
|
||||
<key>method</key>
|
||||
<string>release-testing</string>
|
||||
<key>signingStyle</key>
|
||||
<string>automatic</string>
|
||||
<key>stripSwiftSymbols</key>
|
||||
<true/>
|
||||
<key>teamID</key>
|
||||
<string>63CVT8G5P8</string>
|
||||
<key>thinning</key>
|
||||
<string><none></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,2 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
#import "SecurityChecks.h"
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// SecurityChecks.h
|
||||
// Runner
|
||||
//
|
||||
// Created by Hamza Aleghwairyeen on 12/03/2025.
|
||||
//
|
||||
|
||||
//#ifndef SecurityChecks_h
|
||||
//#define SecurityChecks_h
|
||||
//
|
||||
//
|
||||
//#endif /* SecurityChecks_h */
|
||||
|
||||
|
||||
|
||||
// SecurityChecks.h
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h> // Import UIKit
|
||||
|
||||
@interface SecurityChecks : NSObject
|
||||
|
||||
+ (BOOL)isDeviceCompromised; // Combined check
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,197 @@
|
||||
//
|
||||
// SecurityChecks.m
|
||||
// Runner
|
||||
//
|
||||
// Created by Hamza Aleghwairyeen on 12/03/2025.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
|
||||
#import "SecurityChecks.h"
|
||||
#import <sys/stat.h>
|
||||
#include <dlfcn.h>
|
||||
#import <sys/sysctl.h>
|
||||
#include <mach-o/dyld.h> // For _dyld_image_count and _dyld_get_image_name
|
||||
#include <unistd.h> // for fork()
|
||||
#include <sys/socket.h> //for socket, connect
|
||||
#include <netinet/in.h> // for sockaddr_in
|
||||
#include <arpa/inet.h> // for inet_pton
|
||||
|
||||
@implementation SecurityChecks
|
||||
|
||||
+ (BOOL)isDeviceJailbroken {
|
||||
// (Same jailbreak checks as before - from previous responses)
|
||||
// 1. Check for common jailbreak files
|
||||
NSArray *jailbreakPaths = @[
|
||||
@"/Applications/Cydia.app",
|
||||
// #if !TARGET_IPHONE_SIMULATOR
|
||||
// @"/bin/bash",
|
||||
// #endif
|
||||
@"/Library/MobileSubstrate/MobileSubstrate.dylib",
|
||||
@"/bin/bash",
|
||||
@"/usr/sbin/sshd",
|
||||
@"/etc/apt",
|
||||
@"/private/var/lib/apt/",
|
||||
@"/private/var/tmp/cydia.log",
|
||||
];
|
||||
|
||||
for (NSString *path in jailbreakPaths) {
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||
NSLog(@"Jailbreak file detected: %@", path);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check if we can write outside the sandbox (a strong indicator)
|
||||
NSError *error = nil;
|
||||
NSString *testPath = @"/private/jailbreaktest.txt";
|
||||
[@"test" writeToFile:testPath atomically:YES encoding:NSUTF8StringEncoding error:&error];
|
||||
|
||||
if (!error) {
|
||||
// If we can write, it's jailbroken. Remove the file immediately.
|
||||
[[NSFileManager defaultManager] removeItemAtPath:testPath error:nil];
|
||||
NSLog(@"Sandbox write test indicates jailbreak.");
|
||||
return YES;
|
||||
}
|
||||
//3: Check for existence of the symbolic link /Applications.
|
||||
struct stat s;
|
||||
if (lstat("/Applications", &s) == 0) {
|
||||
if (S_ISLNK(s.st_mode)) {
|
||||
NSLog(@"Symbolic link /Applications exists, jailbroken");
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
//4:Check for dyld Libraries
|
||||
uint32_t count = _dyld_image_count();
|
||||
for (uint32_t i = 0 ; i < count ; ++i) {
|
||||
const char *dyld = _dyld_get_image_name(i);
|
||||
if (strstr(dyld, "MobileSubstrate") != NULL || strstr(dyld, "libcycript") != NULL) {
|
||||
NSLog(@"Suspicious dyld library found: %s", dyld);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
//5 Check if fork() is available. Sandboxed apps should not be able to call fork().
|
||||
#if !TARGET_IPHONE_SIMULATOR // Don't run this on the simulator
|
||||
int pid = fork();
|
||||
if (pid == 0)
|
||||
{
|
||||
//in child, exit immediately to avoid crashing.
|
||||
exit(0);
|
||||
} else if (pid > 0)
|
||||
{
|
||||
NSLog(@"Fork available. Likely jailbroken.");
|
||||
return YES; // Fork succeeded; likely jailbroken
|
||||
}
|
||||
#endif
|
||||
|
||||
return NO; // No jailbreak indicators found
|
||||
}
|
||||
+ (BOOL)isDebuggerAttached {
|
||||
int mib[4];
|
||||
struct kinfo_proc info;
|
||||
size_t size = sizeof(info);
|
||||
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PID;
|
||||
mib[3] = getpid();
|
||||
|
||||
sysctl(mib, 4, &info, &size, NULL, 0);
|
||||
|
||||
return (info.kp_proc.p_flag & P_TRACED) != 0;
|
||||
}
|
||||
|
||||
// Check for Frida's default port
|
||||
+ (BOOL)isFridaListeningOnDefaultPort {
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock == -1) {
|
||||
return NO; // Couldn't create socket
|
||||
}
|
||||
|
||||
struct sockaddr_in sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(27042); // Default Frida port
|
||||
inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr);
|
||||
|
||||
if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) != -1) {
|
||||
NSLog(@"Frida default port (27042) is open.");
|
||||
close(sock);
|
||||
return YES;
|
||||
}
|
||||
|
||||
close(sock);
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
+ (BOOL)isFridaDetected {
|
||||
int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
|
||||
size_t length;
|
||||
struct kinfo_proc *procs, *proc;
|
||||
|
||||
sysctl(name, 3, NULL, &length, NULL, 0);
|
||||
procs = malloc(length);
|
||||
sysctl(name, 3, procs, &length, NULL, 0);
|
||||
|
||||
for (proc = procs; (char *)proc < (char *)procs + length; proc++) {
|
||||
if (strstr(proc->kp_proc.p_comm, "frida") != NULL) {
|
||||
free(procs);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
free(procs);
|
||||
return NO;
|
||||
}
|
||||
// Check for loaded dylibs that indicate Frida
|
||||
+(BOOL) checkForFridaDylib{
|
||||
uint32_t count = _dyld_image_count();
|
||||
for (uint32_t i = 0 ; i < count ; ++i) {
|
||||
const char *dyld = _dyld_get_image_name(i);
|
||||
if (strstr(dyld, "FridaGadget") != NULL) { // Look for FridaGadget
|
||||
NSLog(@"FridaGadget dylib found: %s", dyld);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Check process name
|
||||
+ (BOOL)checkProcessName {
|
||||
NSString* processName = [[NSProcessInfo processInfo] processName];
|
||||
if ([processName containsString:@"Frida"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
// A combined check for jailbreak and Frida
|
||||
+ (BOOL)isDeviceCompromised {
|
||||
if ([SecurityChecks isDeviceJailbroken]) {
|
||||
return YES;
|
||||
}
|
||||
if ([SecurityChecks isFridaListeningOnDefaultPort]) {
|
||||
return YES;
|
||||
}
|
||||
if ([SecurityChecks checkForFridaDylib]) {
|
||||
return YES;
|
||||
}
|
||||
if([SecurityChecks checkProcessName]){
|
||||
return YES;
|
||||
}
|
||||
// if ([SecurityChecks isDebuggerAttached]) {
|
||||
// return YES;
|
||||
// }
|
||||
// if ([SecurityChecks isFridaDetected]) {
|
||||
// return YES;
|
||||
// }
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||