المصادقة والأمان

الكشف عن التهديدات في وقت التشغيل: فحص الجذر والمحاكي والتلاعب

16 دقيقة الدرس 11 من 12

الكشف عن التهديدات في وقت التشغيل: فحص الجذر والمحاكي والتلاعب

تواجه تطبيقات الجوال في بيئة الإنتاج فئةً من التهديدات تنشأ كلياً في وقت التشغيل — على الجهاز نفسه — وليس في حركة مرور الشبكة أو استجابات الخادم. الأجهزة المخترَقة (Root/Jailbreak) والمحاكيات والثنائيات المعدَّلة (APK/IPA) تُتيح للمهاجمين تجاوز تدفقات المصادقة، واعتراض الأسرار المخزَّنة في الذاكرة، وحقن وظائف بديلة، وإعادة تعبئة التطبيق. الكشف عن هذه الحالات مبكراً، قبل تنفيذ أي عمل حساس، هو طبقة أساسية في استراتيجية الدفاع المتعمق.

في Flutter تُعدّ حزمة flutter_jailbreak_detection المرجعَ الأول، إذ تُغلّف الفحوصات الأصلية لكلٍّ من Android (مؤشرات الجذر) وiOS (مؤشرات الـ Jailbreak). تجمع نتائجها مع مؤشرات يدوية لبناء سياسة أمان وقت التشغيل التي تحدد كيفية تصرف التطبيق عند الكشف عن بيئة معادية.

ملاحظة: لا يوجد فحص منفرد يصعب تجاوزه تماماً. يستطيع المهاجمون المتقدمون تعطيل طرق الكشف الفردية. الهدف هو رفع تكلفة الهجوم بشكل ملحوظ وحماية الغالبية العظمى من حالات التهديد الواقعية (أدوات الجذر الجاهزة، الروبوتات الآلية، المطلعون غير المتحوطين)، لا تحقيق أمان مثالي.

إضافة الحزمة

أضف التبعية إلى pubspec.yaml، ثم شغّل flutter pub get:

pubspec.yaml

dependencies:
  flutter_jailbreak_detection: ^1.9.0

على Android تفحص الحزمة وجود تطبيقات SuperUser والثنائيات المعروفة لإدارة الجذر (Magisk وKingRoot وغيرهما) وأقسام /system القابلة للكتابة وتوقيعات البناء للمفاتيح التجريبية. على iOS تتحقق من وجود Cydia ومسارات الهروب غير المعتادة من الصندوق الرملي والقدرة على الكتابة خارج نطاق التطبيق.

إجراء فحوصات الكشف

جميع استدعاءات الواجهة البرمجية غير متزامنة ويجب انتظارها. يتمثّل أفضل الممارسات في تشغيل جميع الفحوصات بالتوازي باستخدام Future.wait وتجميع النتائج قبل عرض أي واجهة حساسة:

security_check_service.dart — الفحوصات المتوازية

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_jailbreak_detection/flutter_jailbreak_detection.dart';

class SecurityCheckResult {
  final bool isRooted;
  final bool isDeveloperMode;
  final bool isEmulator;
  final bool isTampered;

  const SecurityCheckResult({
    required this.isRooted,
    required this.isDeveloperMode,
    required this.isEmulator,
    required this.isTampered,
  });

  bool get isCompromised => isRooted || isEmulator || isTampered;
}

class SecurityCheckService {
  /// شغّل جميع الفحوصات بالتوازي؛ لا تُلقي استثناءً — تُعيد true في حالة الخطأ.
  static Future<SecurityCheckResult> runAll() async {
    // تخطّ الفحوصات في وضع التطوير للسماح بالتطوير الطبيعي.
    if (kDebugMode) {
      return const SecurityCheckResult(
        isRooted: false,
        isDeveloperMode: false,
        isEmulator: false,
        isTampered: false,
      );
    }

    final results = await Future.wait([
      _checkRooted(),
      _checkDeveloperMode(),
      _checkEmulator(),
      _checkTampering(),
    ]);

    return SecurityCheckResult(
      isRooted: results[0],
      isDeveloperMode: results[1],
      isEmulator: results[2],
      isTampered: results[3],
    );
  }

  static Future<bool> _checkRooted() async {
    try {
      return await FlutterJailbreakDetection.jailbroken;
    } catch (_) {
      return true; // الفشل الآمن
    }
  }

  static Future<bool> _checkDeveloperMode() async {
    try {
      return await FlutterJailbreakDetection.developerMode;
    } catch (_) {
      return true;
    }
  }

  static Future<bool> _checkEmulator() async {
    // مؤشر يدوي: تكشف محاكيات Android عن خصائص بناء معروفة.
    if (Platform.isAndroid) {
      const emulatorFingerprints = [
        'generic',
        'unknown',
        'google_sdk',
        'emulator',
        'Android SDK built for x86',
      ];
      try {
        final cpuInfo = await File('/proc/cpuinfo').readAsString();
        return emulatorFingerprints
            .any((fp) => cpuInfo.toLowerCase().contains(fp.toLowerCase()));
      } catch (_) {
        return false;
      }
    }
    return false; // محاكيات iOS هي simulators — محجوبة من متجر التطبيقات أصلاً
  }

  static Future<bool> _checkTampering() async {
    // مؤشر عدم التطابق في التوقيع: في APK معدَّل يتغير مفتاح التوقيع.
    // يجب مقارنة تجزئة شهادة الإصدار المعروفة في التطبيق الإنتاجي.
    // هذه الدالة نموذجية فقط؛ استبدلها بمنطق تثبيت الشهادة الخاص بك.
    return false;
  }
}

تطبيق سياسة أمان وقت التشغيل

بمجرد الحصول على SecurityCheckResult، طبّق السياسة قبل الانتقال إلى أي شاشة مصادَق عليها. النمط الشائع هو تشغيل الفحوصات في main() أو initState للودجت الجذري وتقييد التنقل بناءً على النتيجة:

main.dart — تقييد بدء التطبيق على نتيجة الأمان

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final securityResult = await SecurityCheckService.runAll();

  runApp(
    MyApp(securityResult: securityResult),
  );
}

class MyApp extends StatelessWidget {
  final SecurityCheckResult securityResult;
  const MyApp({super.key, required this.securityResult});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: securityResult.isCompromised
          ? const CompromisedDeviceScreen()
          : const AuthGateScreen(),
    );
  }
}

class CompromisedDeviceScreen extends StatelessWidget {
  const CompromisedDeviceScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const [
            Icon(Icons.security, size: 64, color: Colors.red),
            SizedBox(height: 16),
            Text(
              'هذا الجهاز لا يستوفي متطلبات الأمان.',
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }
}

المؤشرات اليدوية لتعزيز فحوصات الحزمة

حزمة flutter_jailbreak_detection قاعدة قوية، لكن يجب إضافة طبقات مؤشرات إضافية:

  • فحص بصمة البناء: على Android، اقرأ android.os.Build.FINGERPRINT وارفض القيم التي تحتوي على generic أو unknown أو sdk — وهي شائعة في المحاكيات وإصدارات التطوير.
  • فحص ADB عبر الشبكة: الجهاز الذي يعمل عليه تصحيح USB ومتصل بـ ADB عبر الشبكة يمثّل سطح هجوم ذو امتيازات. يمكن الكشف عن ذلك من خلال علامة android.provider.Settings.Secure.ADB_ENABLED عبر قناة منصة (platform channel).
  • التحقق من توقيع APK: قارن تجزئة شهادة توقيع APK الجاري تشغيله بتجزئة SHA-256 المعروفة لإصدارك. أي تعارض يشير إلى ثنائي معاد تعبئته.
  • الكشف عن الخطاطيف (Hooks): افحص جدول الدوال بحثاً عن قفزات غير متوقعة (متقدم — يتم عادةً عبر كود نيتيف في مكوّن JNI).
  • حجب لقطات الشاشة: على الشاشات الحساسة، استدعِ علامة نافذة FLAG_SECURE عبر قناة منصة لمنع لقطات الشاشة والتسجيل.
نصيحة: اجمع نتيجة flutter_jailbreak_detection مع مؤشراتك اليدوية في سلسلة &&/||. لا تعتمد على إشارة واحدة فقط. سيحاول المهاجمون تعطيل كل فحص على حدة؛ الإشارات المتداخلة أصعب بكثير في التحايل عليها في آنٍ واحد.

خيارات السياسة: الحجب الكامل أو التدهور التدريجي

لا يجب أن تكون سياسة الأمان في وقت التشغيل ثنائية. ضع في اعتبارك استجابة متدرجة:

  • الحجب الكامل: عرض CompromisedDeviceScreen وإنهاء التطبيق. الأنسب للتطبيقات المالية والصحية والحكومية حيث يتطلب الامتثال التنظيمي ذلك.
  • تقييد الميزات: السماح للمستخدم بالدخول مع تعطيل الميزات عالية المخاطر (المصادقة البيومترية، تدفقات الدفع، عرض المستندات الحساسة). مناسب لتطبيقات الإنتاجية.
  • التسجيل الصامت: تسجيل الحالة الشاذة في الخادم دون إخطار المستخدم. مفيد لجمع معلومات التهديدات دون الإضرار بتجربة المستخدم الشرعي الذي يمتلك جهازاً مُجذَّراً.
  • تعزيز المصادقة: اشتراط خطوة إعادة مصادقة (كلمة مرور أو OTP) بدلاً من الاعتماد على البيومترية التي يمكن انتحالها على الأجهزة المُجذَّرة.
تحذير: لا تخزّن SecurityCheckResult في مكان يمكن تعديله بسهولة عبر سكريبت Frida (مثل متغير منطقي ثابت بسيط). ضعّها في كائن قيمة غير قابل للتغيير، وأعِد تشغيل الفحوصات دورياً خلال الجلسة — لا تخزّن النتائج مؤقتاً بعد دورة تسجيل دخول واحدة.

ملخص

يُعدّ الكشف عن التهديدات في وقت التشغيل طبقةً حيوية في أمان تطبيقات الجوال. من خلال الجمع بين flutter_jailbreak_detection والمؤشرات اليدوية — فحوصات بصمة المحاكي وفحص خصائص البناء والتحقق من توقيع APK — تبني نظام كشف متعدد الإشارات. يضمن تطبيق سياسة أمان وقت التشغيل بناءً على هذه الإشارات عدم تنفيذ أي عمليات حساسة على أجهزة مخترَقة، مما يرفع مستوى المنع أمام المهاجمين الساعين إلى عكس هندسة تطبيقك أو استغلاله.