From 816c1e7cec17b4254a9f99e7b8ff9f0f25979782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Soukal?= Date: Tue, 27 Aug 2024 10:17:15 +0200 Subject: [PATCH 1/2] Added enableScreenCaptureGuard option --- .../com/aheaditec/freerasp/FreeraspPlugin.kt | 5 +++++ .../freerasp/handlers/MethodCallHandler.kt | 6 +++--- .../freerasp/handlers/TalsecThreatHandler.kt | 18 ++++++++++++++-- .../com/aheaditec/freerasp/utils/Utils.kt | 21 ++++++++++++------- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/FreeraspPlugin.kt b/android/src/main/kotlin/com/aheaditec/freerasp/FreeraspPlugin.kt index bda94d4..47fe4f3 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/FreeraspPlugin.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/FreeraspPlugin.kt @@ -1,6 +1,8 @@ package com.aheaditec.freerasp +import android.app.Activity import android.content.Context +import android.view.WindowManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner @@ -19,6 +21,7 @@ class FreeraspPlugin : FlutterPlugin, ActivityAware, LifecycleEventObserver { private var methodCallHandler: MethodCallHandler = MethodCallHandler() private var context: Context? = null private var lifecycle: Lifecycle? = null + private var activity: Activity? = null override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { val messenger = flutterPluginBinding.binaryMessenger @@ -38,6 +41,8 @@ class FreeraspPlugin : FlutterPlugin, ActivityAware, LifecycleEventObserver { lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding).also { it.addObserver(this) } + + TalsecThreatHandler.guardActivity(binding.getActivity() as Activity?) } override fun onDetachedFromActivity() { diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt index e6d47de..e9b6c15 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt @@ -72,11 +72,11 @@ internal class MethodCallHandler : MethodCallHandler { private fun start(call: MethodCall, result: MethodChannel.Result) { runResultCatching(result) { val config = call.argument("config") - val talsecConfig = Utils.toTalsecConfigThrowing(config) + val extendedTalsecConfig = Utils.toExtendedTalsecConfigThrowing(config) context?.let { - TalsecThreatHandler.start(it, talsecConfig) + TalsecThreatHandler.start(it, extendedTalsecConfig) } ?: throw IllegalStateException("Unable to run Talsec - context is null") result.success(null) } } -} +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt index 35b381a..5696d52 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt @@ -1,6 +1,8 @@ package com.aheaditec.freerasp.handlers +import android.app.Activity import android.content.Context +import android.view.WindowManager import com.aheaditec.freerasp.Threat import com.aheaditec.talsec_security.security.api.Talsec import com.aheaditec.talsec_security.security.api.TalsecConfig @@ -13,15 +15,21 @@ import io.flutter.plugin.common.EventChannel.EventSink internal object TalsecThreatHandler { private var eventSink: EventSink? = null private var isListening = false + private var enableScreenCaptureGuard = false /** * Initializes [Talsec] and starts the [PluginThreatHandler] listener with this object. * * @param context The Android application context. */ - internal fun start(context: Context, config: TalsecConfig) { + internal fun start(context: Context, extendedConfig: Pair) { attachListener(context) - Talsec.start(context, config) + + if (extendedConfig.first.isProd && extendedConfig.second) { + enableScreenCaptureGuard = true + } + + Talsec.start(context, extendedConfig.first) } /** @@ -124,6 +132,12 @@ internal object TalsecThreatHandler { PluginThreatHandler.listener = null } + internal fun guardActivity(activity: Activity?) { + if (enableScreenCaptureGuard) { + activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + } + /** * Sends any cached detected threats to the listener. * diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/utils/Utils.kt b/android/src/main/kotlin/com/aheaditec/freerasp/utils/Utils.kt index e4678ff..27939a7 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/utils/Utils.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/utils/Utils.kt @@ -6,7 +6,7 @@ import org.json.JSONObject internal class Utils { companion object { - fun toTalsecConfigThrowing(configJson: String?): TalsecConfig { + fun toExtendedTalsecConfigThrowing(configJson: String?): Pair { if (configJson == null) { throw JSONException("Configuration is null") } @@ -31,12 +31,19 @@ internal class Utils { isProd = json.getBoolean("isProd") } - return TalsecConfig( - packageName, - certificateHashes.toTypedArray(), - watcherMail, - alternativeStores.toTypedArray(), - isProd + var enableScreenCaptureGuard = false + if (json.has("enableScreenCaptureGuard")) { + enableScreenCaptureGuard = json.getBoolean("enableScreenCaptureGuard") + } + + return Pair( + TalsecConfig( + packageName, + certificateHashes.toTypedArray(), + watcherMail, + alternativeStores.toTypedArray(), + isProd + ), enableScreenCaptureGuard ) } } From 5d250482a9fa6015ba1e5a3e027dfbcf2cc6025b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Soukal?= Date: Tue, 27 Aug 2024 17:22:57 +0200 Subject: [PATCH 2/2] WIP --- android/build.gradle | 2 +- android/src/main/AndroidManifest.xml | 4 +++- .../com/aheaditec/freerasp/CaptureType.kt | 6 ++++++ .../kotlin/com/aheaditec/freerasp/Threat.kt | 2 ++ .../freerasp/handlers/MethodCallHandler.kt | 16 ++++++++++++++ .../freerasp/handlers/TalsecThreatHandler.kt | 17 +++++++++++++++ lib/src/models/android_config.dart | 3 +++ lib/src/models/android_config.g.dart | 3 +++ lib/src/models/talsec_config.g.dart | 3 +-- lib/src/talsec.dart | 9 ++++++++ lib/src/threat_callback.dart | 21 +++++++++++++++++++ 11 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 android/src/main/kotlin/com/aheaditec/freerasp/CaptureType.kt diff --git a/android/build.gradle b/android/build.gradle index dbe2ff9..43e7b9b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -31,7 +31,7 @@ android { namespace("com.aheaditec.freerasp") } - compileSdkVersion 33 + compileSdkVersion 34 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index b299bf1..570ec40 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,3 +1,5 @@ + package="com.aheaditec.freerasp"> + + diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/CaptureType.kt b/android/src/main/kotlin/com/aheaditec/freerasp/CaptureType.kt new file mode 100644 index 0000000..184acac --- /dev/null +++ b/android/src/main/kotlin/com/aheaditec/freerasp/CaptureType.kt @@ -0,0 +1,6 @@ +package com.aheaditec.freerasp + +internal sealed class CaptureType(val value: Int) { + object Unknown : CaptureType(1115787534) + +} diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/Threat.kt b/android/src/main/kotlin/com/aheaditec/freerasp/Threat.kt index ee3a2d0..411e627 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/Threat.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/Threat.kt @@ -31,4 +31,6 @@ internal sealed class Threat(val value: Int) { object SystemVPN : Threat(659382561) object DevMode : Threat(45291047) + + object ScreenCapture : Threat(669512294) } \ No newline at end of file diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt index e9b6c15..ff79563 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt @@ -1,6 +1,7 @@ package com.aheaditec.freerasp.handlers import android.content.Context +import com.aheaditec.freerasp.CaptureType import com.aheaditec.freerasp.runResultCatching import com.aheaditec.freerasp.utils.Utils import io.flutter.Log @@ -38,6 +39,7 @@ internal class MethodCallHandler : MethodCallHandler { it.setMethodCallHandler(this) } + TalsecThreatHandler.attachMethod(sink) this.context = context } @@ -47,6 +49,7 @@ internal class MethodCallHandler : MethodCallHandler { fun destroyMethodChannel() { methodChannel?.setMethodCallHandler(null) methodChannel = null + TalsecThreatHandler.detachMethod() this.context = null } @@ -79,4 +82,17 @@ internal class MethodCallHandler : MethodCallHandler { result.success(null) } } + + private val sink = object : MethodSink { + override fun onScreenCaptureDetected(captureType: CaptureType) { + methodChannel?.invokeMethod( + "onScreenCaptureDetected", + mapOf(Pair("type", captureType.value)) + ) + } + } + + internal interface MethodSink { + fun onScreenCaptureDetected(captureType: CaptureType) + } } \ No newline at end of file diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt index 5696d52..9e3d4f1 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt @@ -2,7 +2,9 @@ package com.aheaditec.freerasp.handlers import android.app.Activity import android.content.Context +import android.os.Build import android.view.WindowManager +import com.aheaditec.freerasp.CaptureType import com.aheaditec.freerasp.Threat import com.aheaditec.talsec_security.security.api.Talsec import com.aheaditec.talsec_security.security.api.TalsecConfig @@ -14,6 +16,7 @@ import io.flutter.plugin.common.EventChannel.EventSink */ internal object TalsecThreatHandler { private var eventSink: EventSink? = null + private var methodSink: MethodCallHandler.MethodSink? = null private var isListening = false private var enableScreenCaptureGuard = false @@ -124,6 +127,10 @@ internal object TalsecThreatHandler { flushThreatCache(eventSink) } + internal fun attachMethod(methodSink: MethodCallHandler.MethodSink) { + this.methodSink = methodSink + } + /** * Called when a listener unsubscribes from the event channel. */ @@ -132,10 +139,20 @@ internal object TalsecThreatHandler { PluginThreatHandler.listener = null } + internal fun detachMethod() { + methodSink = null + } + internal fun guardActivity(activity: Activity?) { if (enableScreenCaptureGuard) { activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_SECURE) } + + if (Build.VERSION.SDK_INT >= 34) { + Activity.ScreenCaptureCallback { + methodSink?.onScreenCaptureDetected(CaptureType.Unknown) + } + } } /** diff --git a/lib/src/models/android_config.dart b/lib/src/models/android_config.dart index 5a8356e..7eb7092 100644 --- a/lib/src/models/android_config.dart +++ b/lib/src/models/android_config.dart @@ -11,6 +11,7 @@ class AndroidConfig { required this.packageName, required this.signingCertHashes, this.supportedStores = const [], + this.enableScreenCaptureGuard = false, }) { ConfigVerifier.verifyAndroid(this); } @@ -30,4 +31,6 @@ class AndroidConfig { /// List of supported sources where application can be installed from. final List supportedStores; + + final bool enableScreenCaptureGuard; } diff --git a/lib/src/models/android_config.g.dart b/lib/src/models/android_config.g.dart index 021f8a8..4e4efd1 100644 --- a/lib/src/models/android_config.g.dart +++ b/lib/src/models/android_config.g.dart @@ -16,6 +16,8 @@ AndroidConfig _$AndroidConfigFromJson(Map json) => ?.map((e) => e as String) .toList() ?? const [], + enableScreenCaptureGuard: + json['enableScreenCaptureGuard'] as bool? ?? false, ); Map _$AndroidConfigToJson(AndroidConfig instance) => @@ -23,4 +25,5 @@ Map _$AndroidConfigToJson(AndroidConfig instance) => 'packageName': instance.packageName, 'signingCertHashes': instance.signingCertHashes, 'supportedStores': instance.supportedStores, + 'enableScreenCaptureGuard': instance.enableScreenCaptureGuard, }; diff --git a/lib/src/models/talsec_config.g.dart b/lib/src/models/talsec_config.g.dart index f901fc5..41aaa56 100644 --- a/lib/src/models/talsec_config.g.dart +++ b/lib/src/models/talsec_config.g.dart @@ -12,8 +12,7 @@ TalsecConfig _$TalsecConfigFromJson(Map json) => TalsecConfig( androidConfig: json['androidConfig'] == null ? null : AndroidConfig.fromJson( - json['androidConfig'] as Map, - ), + json['androidConfig'] as Map), iosConfig: json['iosConfig'] == null ? null : IOSConfig.fromJson(json['iosConfig'] as Map), diff --git a/lib/src/talsec.dart b/lib/src/talsec.dart index 25ad8cf..11a7006 100644 --- a/lib/src/talsec.dart +++ b/lib/src/talsec.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:ffi'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; @@ -180,6 +181,14 @@ class Talsec { break; } }); + + methodChannel.setMethodCallHandler((call) { + if (call.method == "onScreenCaptureDetected") { + final value = call.arguments as int; + + callback.onScreenCaptureDetected?.call(CaptureType.fromInt(value)); + } + }); } /// Removes instance of latest [ThreatCallback]. Also cancels diff --git a/lib/src/threat_callback.dart b/lib/src/threat_callback.dart index f91b553..036cace 100644 --- a/lib/src/threat_callback.dart +++ b/lib/src/threat_callback.dart @@ -33,6 +33,7 @@ class ThreatCallback { this.onSecureHardwareNotAvailable, this.onSystemVPN, this.onDevMode, + this.onScreenCaptureDetected }); /// This method is called when a threat related dynamic hooking (e.g. Frida) @@ -80,4 +81,24 @@ class ThreatCallback { /// This method is called whe the device has Developer mode enabled final VoidCallback? onDevMode; + + void Function(CaptureType type)? onScreenCaptureDetected; +} + +enum CaptureType { + unknown(1115787534), + screenshot(0), + recording(0), + mirroring(0); + + final int value; + + const CaptureType(this.value); + + static CaptureType fromInt(int value) { + switch (value) { + case 1115787534: return CaptureType.unknown; + default: return CaptureType.unknown; + } + } }