flutter_rasp 1.0.0
flutter_rasp: ^1.0.0 copied to clipboard
A comprehensive RASP (Runtime Application Self-Protection) plugin for Flutter. Detect root, jailbreak, emulators, debuggers, hooks, tampering, VPN, and more.
Flutter RASP #
A comprehensive RASP (Runtime Application Self-Protection) plugin for Flutter. Protect your app against reverse engineering, tampering, and runtime attacks with zero external SDK dependencies.
Features #
| Threat | Android | iOS | Description |
|---|---|---|---|
| Root / Jailbreak | ✅ | ✅ | Detects rooted devices, su binaries, Cydia, sandbox escape |
| Emulator / Simulator | ✅ | ✅ | Identifies emulators via build properties and environment |
| Debugger | ✅ | ✅ | Detects attached debuggers (JDWP, ptrace) |
| Hooks (Frida/Xposed) | ✅ | ✅ | Scans for Frida, Xposed, Cycript, Substrate |
| App Integrity | ✅ | ✅ | Verifies signing certificates, bundle IDs, and installation source |
| VPN | ✅ | ✅ | Detects active VPN connections |
| Developer Mode | ✅ | ❌ | Checks if developer options or ADB are enabled |
| Screen Capture Protection | ✅ | ✅ | Blocks screenshots and screen recording |
Getting Started #
Installation #
Add flutter_rasp to your pubspec.yaml:
dependencies:
flutter_rasp: ^1.0.0
Platform Requirements #
| Platform | Minimum Version |
|---|---|
| Android | API 24 (Android 7.0) |
| iOS | 13.0 |
This plugin does not require any additional permissions.
Usage #
Initialization #
Initialize the SDK once at app startup. This configures and starts monitoring automatically:
import 'package:flutter_rasp/flutter_rasp.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterRasp.instance.initialize(
config: const RaspConfig(
policy: ThreatPolicy.high,
monitoringInterval: Duration(seconds: 5),
),
onThreatDetected: (Set<Threat> threats) {
for (final threat in threats) {
debugPrint('Threat detected: ${threat.name}');
}
},
);
runApp(const MyApp());
}
Threat Policies #
Policies control which threats cause the app to be terminated at the native level before any Dart code can react. Threats not in the policy's exitThreats are reported via onThreatDetected.
| Policy | Exit Threats | Use Case |
|---|---|---|
ThreatPolicy.none |
None | Report only, no enforcement |
ThreatPolicy.low |
tampered | Minimal protection |
ThreatPolicy.medium |
root, hook, tampered | Balanced protection |
ThreatPolicy.high |
root, hook, tampered, debug | Maximum protection |
Custom policies:
const policy = ThreatPolicy(
exitThreats: {Threat.root, Threat.tampered, Threat.vpn},
);
ThreatCallback #
Instead of (or in addition to) the generic onThreatDetected, you can use ThreatCallback to handle each threat individually:
await FlutterRasp.instance.initialize(
config: const RaspConfig(policy: ThreatPolicy.medium),
threatCallback: ThreatCallback(
onRoot: () => debugPrint('Device is rooted!'),
onEmulator: () => debugPrint('Running on emulator!'),
onDebug: () => debugPrint('Debugger attached!'),
onHook: () => debugPrint('Hooks detected!'),
onTampered: () => debugPrint('App was tampered!'),
onVpn: () => debugPrint('VPN is active!'),
onDeveloperMode: () => debugPrint('Developer mode enabled!'),
),
);
You can also attach or replace callbacks at runtime:
FlutterRasp.instance.attachListener(
ThreatCallback(
onRoot: () => navigateToBlockedScreen(),
),
);
FlutterRasp.instance.detachListener();
Note: At least one of
onThreatDetectedorthreatCallbackmust be provided. Both can be used simultaneously.
Development vs Production #
Use ThreatPolicy.none during development so the app is never terminated by the native enforcement layer. All threats are still detected and reported via callbacks, but no exit action is taken:
await FlutterRasp.instance.initialize(
config: const RaspConfig(
policy: ThreatPolicy.none,
monitoringInterval: Duration(seconds: 5),
),
onThreatDetected: (threats) {
debugPrint('Detected: $threats');
},
);
Note: When debugging on a physical device via Xcode WiFi, the remote debugging tunnel creates a network interface that is detected as a VPN connection. This is a false positive caused by Xcode, not by an actual VPN. This only happens during development — release builds on real devices are not affected.
For production, use .medium or .high to terminate the app on critical threats:
await FlutterRasp.instance.initialize(
config: const RaspConfig(
policy: ThreatPolicy.high,
),
threatCallback: ThreatCallback(
onVpn: () => showVpnWarning(),
),
);
Full Scan #
final result = await FlutterRasp.instance.scanAll();
if (result.isCompromised) {
debugPrint('Threats detected: ${result.detectedThreats}');
}
Individual Checks #
final rasp = FlutterRasp.instance;
if (await rasp.isRooted()) {}
if (await rasp.isEmulator()) {}
if (await rasp.isDebugged()) {}
if (await rasp.isHooked()) {}
if (await rasp.isTampered()) {}
if (await rasp.isVpnConnected()) {}
if (await rasp.isDeveloperMode()) {}
Screen Capture Protection #
await FlutterRasp.instance.blockScreenCapture(true);
await FlutterRasp.instance.blockScreenCapture(false);
final isBlocked = await FlutterRasp.instance.isScreenCaptureBlocked();
App Integrity Configuration #
For real repackaging protection, provide your app's signing certificate hashes and bundle identifiers. Without this configuration, the integrity check only verifies the installation source. With it, the plugin compares the app's runtime signature against your expected values.
Android #
Provide your signing certificate SHA-256 hashes in Base64 format:
const config = RaspConfig(
androidConfig: AndroidRaspConfig(
signingCertHashes: ['YOUR_BASE64_SHA256_HASH'],
),
);
How to get your signing certificate hash:
keytool -list -v -keystore your-keystore.jks -alias your-alias
Copy the SHA256 fingerprint (e.g., AB:CD:EF:...), remove the colons, decode the hex to bytes, and encode as Base64. Or use this one-liner:
keytool -list -v -keystore your-keystore.jks -alias your-alias 2>/dev/null \
| grep SHA256 \
| awk '{print $2}' \
| tr -d ':' \
| xxd -r -p \
| base64
For the Android debug keystore:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android 2>/dev/null \
| grep SHA256 \
| awk '{print $2}' \
| tr -d ':' \
| xxd -r -p \
| base64
You can also customize which app stores are considered trusted:
const config = RaspConfig(
androidConfig: AndroidRaspConfig(
signingCertHashes: ['YOUR_BASE64_SHA256_HASH'],
supportedStores: [
'com.android.vending',
'com.amazon.venezia',
],
),
);
Default trusted stores: Google Play, Amazon Appstore, Huawei AppGallery, Samsung Galaxy Store.
iOS #
Provide your Apple Team ID and expected bundle identifiers:
const config = RaspConfig(
iosConfig: IosRaspConfig(
teamId: 'YOUR_TEAM_ID',
bundleIds: ['com.yourcompany.yourapp'],
),
);
How to get your Team ID:
- Go to Apple Developer Account
- Your Team ID is displayed under Membership Details
- It's a 10-character alphanumeric string (e.g.,
A1B2C3D4E5)
Full Example #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterRasp.instance.initialize(
config: const RaspConfig(
policy: ThreatPolicy.high,
monitoringInterval: Duration(seconds: 10),
androidConfig: AndroidRaspConfig(
signingCertHashes: ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='],
),
iosConfig: IosRaspConfig(
teamId: 'A1B2C3D4E5',
bundleIds: ['com.yourcompany.yourapp'],
),
),
onThreatDetected: (Set<Threat> threats) {
for (final threat in threats) {
debugPrint('Threat: ${threat.name}');
}
},
);
runApp(const MyApp());
}
API Reference #
FlutterRasp #
| Method | Return Type | Description |
|---|---|---|
instance |
FlutterRasp |
Singleton instance |
initialize({config, onThreatDetected, threatCallback}) |
Future<void> |
Initialize SDK and start monitoring |
scanAll({enabledThreats}) |
Future<RaspResult> |
Run full security scan |
isRooted() |
Future<bool> |
Check root/jailbreak |
isEmulator() |
Future<bool> |
Check emulator/simulator |
isDebugged() |
Future<bool> |
Check debugger |
isHooked() |
Future<bool> |
Check hooks (Frida/Xposed) |
isTampered() |
Future<bool> |
Check app integrity |
isVpnConnected() |
Future<bool> |
Check VPN |
isDeveloperMode() |
Future<bool> |
Check developer mode |
blockScreenCapture(bool) |
Future<void> |
Toggle screen protection |
isScreenCaptureBlocked() |
Future<bool> |
Check screen protection state |
attachListener(ThreatCallback) |
void |
Replace the current threat callback |
detachListener() |
void |
Remove the current threat callback |
isInitialized |
bool |
Whether SDK is initialized |
RaspConfig #
| Parameter | Type | Default | Description |
|---|---|---|---|
enabledThreats |
Set<Threat> |
All threats | Threats to check |
policy |
ThreatPolicy |
ThreatPolicy.none |
Enforcement policy |
monitoringInterval |
Duration |
10 seconds | Time between scans (min 1s) |
androidConfig |
AndroidRaspConfig? |
null |
Android integrity configuration |
iosConfig |
IosRaspConfig? |
null |
iOS integrity configuration |
ThreatPolicy #
| Preset | Exit Threats |
|---|---|
ThreatPolicy.none |
None |
ThreatPolicy.low |
tampered |
ThreatPolicy.medium |
root, hook, tampered |
ThreatPolicy.high |
root, hook, tampered, debug |
AndroidRaspConfig #
| Parameter | Type | Default | Description |
|---|---|---|---|
signingCertHashes |
List<String> |
Required | SHA-256 certificate hashes in Base64 |
supportedStores |
List<String> |
Play, Amazon, Huawei, Samsung | Trusted installer package names |
IosRaspConfig #
| Parameter | Type | Default | Description |
|---|---|---|---|
teamId |
String |
Required | Apple Developer Team ID |
bundleIds |
List<String> |
Required | Expected bundle identifiers |
RaspResult #
| Property | Type | Description |
|---|---|---|
threats |
Map<Threat, bool> |
All check results |
isCompromised |
bool |
true if any threat detected |
detectedThreats |
Set<Threat> |
Set of detected threats |
isRooted |
bool |
Root/jailbreak result |
isEmulator |
bool |
Emulator result |
isDebugged |
bool |
Debugger result |
isHooked |
bool |
Hook result |
isTampered |
bool |
Integrity result |
isVpnConnected |
bool |
VPN result |
isDeveloperMode |
bool |
Developer mode result |
ThreatCallback #
| Parameter | Type | Description |
|---|---|---|
onRoot |
VoidCallback? |
Called when root/jailbreak is detected |
onEmulator |
VoidCallback? |
Called when emulator/simulator is detected |
onDebug |
VoidCallback? |
Called when debugger is attached |
onHook |
VoidCallback? |
Called when hooks are detected |
onTampered |
VoidCallback? |
Called when app integrity is compromised |
onVpn |
VoidCallback? |
Called when VPN is active |
onDeveloperMode |
VoidCallback? |
Called when developer mode is enabled |
Threat Enum #
enum Threat {
root,
emulator,
debug,
hook,
tampered,
vpn,
developerMode,
}
Detection Details #
Root / Jailbreak #
Android:
- Scans for
subinaries in system paths - Detects root management apps (Magisk, SuperSU, KingRoot)
- Checks
ro.debuggableandro.securesystem properties - Verifies build tags for
test-keys
iOS:
- Checks for jailbreak artifacts (Cydia, MobileSubstrate, apt)
- Performs sandbox write test outside app container
- Detects Cydia URL scheme
- Uses
posix_spawnto verify process restrictions
Hook Detection #
Android:
- Scans running processes for Frida
- Reads
/proc/self/mapsfor injected libraries - Tests Frida default ports (27042, 27043)
- Detects Xposed/LSPosed/EdXposed packages
- Scans stack trace for Xposed class names
- Checks
/proc/self/fdfor Frida named pipes
iOS:
- Checks
DYLD_INSERT_LIBRARIESenvironment variable - Scans loaded dylibs via
_dyld_image_count() - Tests Frida default port (27042)
App Integrity #
Android:
- Verifies installation source against trusted stores
- Compares APK signing certificate SHA-256 hash against expected values (when configured)
- Detects repackaged apps signed with a different certificate
iOS:
- Checks
appStoreReceiptURLfor App Store vs sandbox/TestFlight - Detects
embedded.mobileprovisionpresence - Verifies PIE flag in Mach-O binary header
- Validates bundle ID against expected values (when configured)
- Extracts and validates Team ID from provisioning profile (when configured)
VPN Detection #
Android:
- Checks active network transport via
ConnectivityManagerforTRANSPORT_VPN - Scans network interfaces for VPN prefixes (tun, ppp, pptp, tap, ipsec)
iOS:
- Enumerates network interfaces via
getifaddrs()for VPN prefixes (ppp, tap, tun, ipsec) - Inspects
CFNetworkCopySystemProxySettingsscoped proxy entries for VPN tunnels (including utun)
Utilities #
HashConverter #
The hashConverter utility helps convert signing certificate hashes between formats:
import 'package:flutter_rasp/flutter_rasp.dart';
final sha256Hex = 'AE:4F:12:31:E0:AF:E1:35:E9:BC:0A:F5:21:AF:9B:C6:'
'7E:09:76:B1:B4:D6:4E:79:90:DB:AC:30:82:E4:6E:69';
final base64Hash = hashConverter.fromSha256toBase64(sha256Hex);
final backToHex = hashConverter.fromBase64toSha256(base64Hash);
hashConverter.isValidSha256Format('abcdef...');
hashConverter.isValidBase64Sha256(base64Hash);
Architecture #
Flutter App
│
FlutterRasp (Singleton)
│
FlutterRaspPlatform (Interface)
│
MethodChannelFlutterRasp
├── MethodChannel (commands/checks)
└── EventChannel (threat stream)
Android (Kotlin) iOS (Swift)
───────────────── ─────────────────
DetectorRegistry DetectorRegistry
├── RootDetector ├── JailbreakDetector
├── EmulatorDetector ├── SimulatorDetector
├── DebugDetector ├── DebugDetector
├── HookDetector ├── HookDetector
├── IntegrityDetector ├── IntegrityDetector
├── VpnDetector ├── VpnDetector
└── DeveloperModeDetector └── ScreenCaptureManager
ScreenCaptureManager
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/new-detection) - Commit your changes (
git commit -m 'Add new detection') - Push to the branch (
git push origin feature/new-detection) - Open a Pull Request
Adding a New Detector #
To add a new threat detector, you only need to:
- Create a detector class implementing
ThreatDetector(Android) orThreatDetectable(iOS) - Add it to the
DetectorRegistrylist - Add the corresponding
Threatenum value in Dart
License #
This project is licensed under the MIT License - see the LICENSE file for details.