LogoLogo
HomeArticlesCommunity ProductsPremium ProductsGitHubTalsec Website
  • Introduction
  • articles
    • Simple Root Detection: Implementation and verification
    • OWASP Top 10 For Flutter - M5: Insecure Communication for Flutter and Dart
    • OWASP Top 10 For Flutter – M4: Insufficient Input/Output Validation in Flutter
    • OWASP Top 10 For Flutter – M3: Insecure Authentication and Authorization in Flutter
    • OWASP Top 10 For Flutter – M2: Inadequate Supply Chain Security in Flutter
    • OWASP Top 10 For Flutter - M1: Mastering Credential Security in Flutter
    • Hook, Hack, Defend: Frida’s Impact on Mobile Security & How to Fight Back
    • Emulators in Gaming: Threats and Detections
    • Exclusive Research: Unlocking Reliable Crash Tracking with PLCrashReporter for iOS SDKs
    • 🚀A Developer’s Guide to Implement End-to-End Encryption in Mobile Apps 🛡️
    • How to Block Screenshots, Screen Recording, and Remote Access Tools in Android and iOS Apps
    • Flutter Security 101: Restricting Installs to Protect Your App from Unofficial Sources
    • How to test a RASP? OWASP MAS: RASP Techniques Not Implemented [MASWE-0103]
    • How to implement Secure Storage in Flutter?
    • User Authentication Risks Coverage in Flutter Mobile Apps | TALSEE
    • Fact about the origin of the Talsec name
    • React Native Secure Boilerplate 2024: Ignite with freeRASP
    • Flutter CTO Report 2024: Flutter App Security Trends
    • Mobile API Anti-abuse Protection with AppiCrypt®: A New Play Integrity and DeviceCheck Alternative
    • Hacking and protection of Mobile Apps and backend APIs | 2024 Talsec Threat Modeling Exercise
    • Detect system VPNs with freeRASP
    • Introducing Talsec’s advanced malware protection!
    • Fraud-Proofing an Android App: Choosing the Best Device ID for Promo Abuse Prevention
    • Enhancing Capacitor App Security with freeRASP: Your Shield Against Threats 🛡️
    • Safeguarding Your Data in React Native: Secure Storage Solutions
    • Secure Storage: What Flutter can do, what Flutter could do
    • 🔒 Flutter Plugin Attack: Mechanics and Prevention
    • Protecting Your API from App Impersonation: Token Hijacking Guide and Mitigation of JWT Theft
    • Build secure apps in React Native
    • How to Hack & Protect Flutter Apps — Simple and Actionable Guide (Pt. 1/3)
    • How to Hack & Protect Flutter Apps — OWASP MAS and RASP. (Pt. 2/3)
    • How to Hack & Protect Flutter Apps — Steal Firebase Auth token and attack the API. (Pt. 3/3)
    • freeRASP meets Cordova
    • Philosophizing security in a mobile-first world
    • 5 Things John Learned Fighting Hackers of His App — A must-read for PM’s and CISO’s
    • Missing Hero of Flutter World
Powered by GitBook
On this page
  • Introduction
  • How Unauthorized Installs Lead to Vulnerabilities
  • How to Check to Install Sources in Your Mobile App
  • Additional Security Measures
  • Code Obfuscation
  • Runtime Protection and Jailbreak/Root Detection
  • Conclusion

Was this helpful?

  1. articles

Flutter Security 101: Restricting Installs to Protect Your App from Unofficial Sources

PreviousHow to Block Screenshots, Screen Recording, and Remote Access Tools in Android and iOS AppsNextHow to test a RASP? OWASP MAS: RASP Techniques Not Implemented [MASWE-0103]

Last updated 4 months ago

Was this helpful?

LogoLogo

Company

  • General Terms and Conditions

Stay Connected

  • LinkedIn
  • X
  • YouTube

Introduction

In today’s mobile app ecosystem, ensuring your app is secure from piracy, tampering, and sideloading is more important than ever. With unofficial app stores and third-party platforms on the rise, developers face the risk of their apps being stolen, modified, or misused, often leading to revenue loss, security breaches, and reputation damage. One simple but effective measure to safeguard your app is enforcing install source restrictions, ensuring it only runs if downloaded from official stores like Google Play or the Apple App Store. This article explores how you can implement these checks and why it’s crucial for app security.

How Unauthorized Installs Lead to Vulnerabilities

Distributing an app through unofficial channels opens the door to numerous security threats, many of which could have devastating consequences for both the developer and users. Unauthorized installs can lead to significant breaches in security for several reasons:

  1. Tampered versions of the app

  2. Lack of official updates and security patches

  3. Data leaks and privacy concerns

  4. Exploits for ads or in-app purchases

  5. Gateway for broader attacks

Commonly, fake applications are installed through the internet browser, file manager, cloud storage, or various messaging apps.

One common scenario is that the app being installed has been tampered in order to skip verifications of some kind or in order to add malicious code to it. As an example, an attacker can use apktool to decompile the apk:

apktool d mynotsosecureapp.apk

This gives back a smali version of the app that can be changed later on. Think of smali code like an assembly-like language that is a low-level representation of the compiled Java code. That can be modified by maybe adding a key logger function of some sort.

.method public onCreate(Landroid/os/Bundle;)V
    .locals 1
    .param p1, "savedInstanceState"    # Landroid/os/Bundle;

    .prologue
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    # Call malicious keylogger here
    invoke-static {}, Lcom/malicious/Keylogger;->logKeyStrokes()V

    # Original code here

.end method
apktool b mynotsosecureapp.apk

and then resign the apk

jarsigner -keystore fake.keystore mynotsosecureapp.apk fakealias

This high-level example shows how dangerous it can be to install an app like this for both developer and user. Users who unknowingly download these tampered versions may be exposed to malware, spyware, or data-stealing mechanisms that compromise their personal information. This type of unauthorized access could include reading sensitive files, intercepting user data, or even hijacking user accounts. Developers can be harmed both in terms of their reputation and earnings. Modified app versions may disable ads, unlock premium features for free, or provide unauthorized access to paid content.

How to Check to Install Sources in Your Mobile App

One basic strategy is to check the install source every time the user launches the app and react accordingly.

Let’s see a quick and easy example using Flutter. Keep in mind that we will write some native code for Android and iOS, so you can use the same code if your app is native only or if you want to use it in any other framework.

So first of all, let’s create a new Flutter project with flutter create check_install_source. You can remove the main.dart file entirely and replace it with the following, starting with the necessary imports and the classi main declaration:

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MaterialApp(home: MyApp()));

Then we can create a MyApp class

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  MyAppState createState() => MyAppState();
}

In the actual MyAppState we can implement a simple build method and the initState :

class MyAppState extends State<MyApp> {
  static const platform = MethodChannel('flutter_app_restrictions');

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Install Source Restriction'),
      ),
      body: const Center(
        child: Text('App is running...'),
      ),
    );
  }
}

Now let’s add a new method called _checkInstallSource in the initState

@override
  void initState() {
    super.initState();
    _checkInstallSource();
  }

So that we can implement it as following:

Future<void> _checkInstallSource() async {
    try {
      String? installerSource;
      if (Platform.isAndroid) {
        // Call Android-specific code
        installerSource =
            await platform.invokeMethod('getInstallerPackageName');
        if (installerSource != 'com.android.vending') {
          _showErrorAndExit();
        }
      } else if (Platform.isIOS) {
        // Call iOS-specific code
        bool isValid = await platform.invokeMethod('validateAppStoreReceipt');
        if (!isValid) {
          _showErrorAndExit();
        }
      }
    } on PlatformException catch (e) {
      print("Failed to get install source: ${e.message}");
      _showErrorAndExit();
    }
  }

When we call getInstallerPackageName **Android method we will ask what the install source is. In this case we are checking against com.android.vending that’s the Google Play Store but you can also check plenty of other alternative stores like Samsung Galaxy Store com.sec.android.app.samsungapps.

Now, before checking kotlin and swift code, let’s see what we can do with those informations and let’s implement the _showErrorAndExit() method.

  void _showErrorAndExit() {
    // Show error and exit the app
    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text("Invalid Installation"),
          content:
              const Text("This app was not installed from an official source."),
          actions: [
            TextButton(
              onPressed: () {
                // Exit the app
                exit(0);
              },
              child: const Text("Exit"),
            ),
          ],
        );
      },
    );
  }

Keep in mind that’s just an example to alert the user. Exiting an app with exit(0) it’s not usually recommended because Apple may reject your app when submitting it to the AppStore.

Now open the MainActivity.kt file in Android folder and swap the content with necessary import first:

import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.annotation.NonNull
import androidx.annotation.RequiresApi
import io.flutter.embedding.android.FlutterActivity
import io.flutter.plugin.common.MethodChannel

and than change the MainActivity class as following:

class MainActivity: FlutterActivity() {
    private val CHANNEL = "flutter_app_restrictions"

    @RequiresApi(Build.VERSION_CODES.R)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        flutterEngine?.dartExecutor?.let {
            MethodChannel(it.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
                if (call.method == "getInstallerPackageName") {
                    val packageName = packageName
                    val installerPackageName = packageManager.getInstallSourceInfo(packageName).initiatingPackageName
                    result.success(installerPackageName)
                } else {
                    result.notImplemented()
                }
            }
        }
    }
}

In this chunk of code we are implementing Flutter method channel in Android counterpart, getting ready to listen for the same method getInstallerPackageName that we implemented in Dart. Using packageManager we can get the installer package name and pass it back to Dart.

We can move to iOS folder opening AppDelegate.swift and adding imports:

import UIKit
import Flutter
import StoreKit

We can proceed editing didFinishLaunchingWithOptions method to let Swift know what method need to be respond to:

@main
@objc class AppDelegate: FlutterAppDelegate {
  private let CHANNEL = "flutter_app_restrictions"
  
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
    let methodChannel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger)
    
    methodChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
      if call.method == "validateAppStoreReceipt" {
        result(self.isValidAppStoreReceipt())
      } else {
        result(FlutterMethodNotImplemented)
      }
    }
    
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Last step is to implement the isValidAppStoreReceipt() method in the AppDelegate class:

private func isValidAppStoreReceipt() -> Bool {
    guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL else {
      return false
    }
    
    do {
      let receiptData = try Data(contentsOf: appStoreReceiptURL)
      // Perform additional verification or send receipt to your server for validation
      return receiptData.count > 0
    } catch {
      return false
    }
  }

Your app should now be able to check the install source and force exit if it’s an unofficial source (in this example).

Additional Security Measures

Even if you have verified the install source of an app this is just the initial stage of ensuring that only genuine and authorized versions are installed, developers still need to undertake other steps to ensure that their apps are safe. Here are some effective strategies and tools that developers can leverage to enhance security.

Code Obfuscation

Flutter / Dart are typically more resilient against reverse engineering as the code is minified during the compilation (not really obfuscated). It's common to offload sensitive parts of code to domains that can be obfuscated more easily: Java/Kotlin, Swift, and especially C/C++ languages can be obfuscated by ready-made obfuscators.

Code obfuscation remains one of the easiest ways by which one can protect his or her application. Consumer objectives can be achieved using tools such as ProGuard for Android or LLVM Obfuscator for iOS because such tools distort code within the app, making it impossible for an attacker to understand what the app does. While renaming classes, methods, and variables, the work of an attacker significantly becomes more complex, depriving him even an attempt to begin to decompile and adjust the internal functioning of the application.

dProtect an Android bytecode obfuscator based on Proguard O-MVLL is an obfuscator based on LLVM that uses the new LLVM pass manager, -fpass-plugin to perform native code obfuscation

Runtime Protection and Jailbreak/Root Detection

One must recognize when an app is running in an environment such as a jailbroken iPhone or iPad or a rooted Android device. These devices usually eliminate crucial security components, thus rendering them prone to hacking. There is always the possibility of root and jailbreak detection, so existing applications cannot run within these breeches.

Integrating Security Tools: freeRASP by Talsec

Integrity Check: Identifies the state or level of modification of the app in order to determine if it has been modified in anyway. Debugging Detection: Recognizes whether in a specific app it is currently being debugged, or, for instance, during reverse engineering. Repackaging Protection: They also shield users against other attackers altering and repackaging their applications. Anti-Hooking: Intercepts and protects against such tool as Frida from injecting their code into the app during its execution. With the help of freeRASP, developers can prevent complex threats, including tampering, reverse engineering, and runtime manipulation. The application is free of charge and comes with powerful addressing security options compared to other powerful addressing security options that can easily be incorporated into existing mobile applications.

Conclusion

It is, therefore, important to consider multiple levels of security when developing applications for the current mobile environment. Verifying install sources is a good start, but by using tools such as freeRASP, runtime protections, and server-side checks, developers can offer a much safer environment for the applications. This shields the users from such applications and possible data leakage and your brand and business from the possible repercussions of a malicious app.

Than is as simple as repacking the app with

What’s happening here is that we are calling specific native function using flutter’s method channels () so that we can perform some action in the native side. Of course we are catching exceptions that may occur.

On the other hand, when calling validateAppStoreReceipt iOS method we are checking that an App Store receipt is present. Even on free apps, Apple attach a receipt file to verify the authenticity of the executable. Of course just checking if the receipt is there or not could not be enough so you can perform additional verification or send receipt to your server for validation ().

Another interesting project worth checking out is, that’s based on

freeRASP (Runtime Application Self-Protection) () refers to an all-inclusive security solution whereby your mobile application can be monitored in real-time. It goes beyond basic checks like jailbreak or root detection by offering a full suite of security features, including:

apktool
https://docs.flutter.dev/platform-integration/platform-channels
https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device
https://obfuscator.re
https://docs.talsec.app/freerasp
Cover

Marco Galetta, Senior Software Engineer

Experienced and dedicated Mobile App Developer with impressive expertise in Flutter Framework. Directs the design, development, and implementation of mobile applications and delivers products ahead of schedule.

LinkedIn