Flutter Mobile Apps

Flutter mobile app reversing using automated scripts and tools in bulk.

Introduction

Google’s open source Flutter has quickly become one of the most popular development toolkits for building cross platform mobile applications. In this article we will examine vulnerabilities in fultter mobile apps through reverse engineering.

Flutter is an open-source UI framework developed by Google for building natively compiled mobile, web, and desktop applications from a single codebase.

Advantages

  • Cross-Platform Functionality

  • High Performance

  • Powerful Design Capabilities

  • Time Efficiency

  • Lower Development Costs

Research Process

  • Gather apps (Selenium and Python)

  • Inspect (Bash)

  • Blutter (Reverse Engineering Tool for Flutter)

  • Scan (Gitleak)

  • Analyze


Gather Applications

Python script with Selenium was used to bulk download applications from APKCombo.com website.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import time

# Set up the WebDriver
driver = webdriver.Chrome()  # Ensure chromedriver is in your PATH

# Define the APKCombo URL
apkcombo_url = "https://apkcombo.com/"

# Open the APKCombo website
driver.get(apkcombo_url)
time.sleep(2)  # Wait for the page to load

# Define a list of app names you want to download
app_names = ["WhatsApp", "Facebook", "Instagram"]  # Add your app names here

for app_name in app_names:
    # Find the search box and search for the app
    search_box = driver.find_element(By.NAME, "q")
    search_box.clear()
    search_box.send_keys(app_name)
    search_box.send_keys(Keys.RETURN)
    time.sleep(3)

    # Click on the first app result
    first_result = driver.find_element(By.CSS_SELECTOR, ".title > a")
    first_result.click()
    time.sleep(3)

    # Locate the download button and click on it
    download_button = driver.find_element(By.CSS_SELECTOR, ".download-btn")
    download_button.click()
    time.sleep(5)  # Adjust if there are multiple steps to complete download

    # Go back to the search page to download the next app
    driver.back()
    time.sleep(2)

# Close the driver
driver.quit()

Inspect

Bash script was used to verify whether the applications were built with Flutter

#!/bin/bash

# Directory containing APK files
APK_DIR="./apks"
# Output file for results
OUTPUT_FILE="flutter_check_results.txt"

# Clear output file if it exists
> "$OUTPUT_FILE"

# Loop through all APK files in the directory
for apk in "$APK_DIR"/*.apk; do
  echo "Checking $apk for Flutter files..."

  # Extract APK contents to a temporary directory
  TEMP_DIR=$(mktemp -d)
  unzip -q "$apk" -d "$TEMP_DIR"

  # Check for Flutter-specific files
  if find "$TEMP_DIR" -name "libflutter.so" | grep -q "libflutter.so"; then
    echo "$apk: Built with Flutter" | tee -a "$OUTPUT_FILE"
  else
    echo "$apk: Not built with Flutter" | tee -a "$OUTPUT_FILE"
  fi

  # Clean up temporary directory
  rm -rf "$TEMP_DIR"
done

echo "Flutter verification complete. Results saved in $OUTPUT_FILE."

Blutter (Reverse Engineering Tool)

  • B(l)utter was used to reverse engineer each Flutter application.

  • Bash script was used to automate the process.

To reverse engineer Flutter applications using B(l)utter, you can automate the process with a Bash script. The B(l)utter tool provides functionality to extract Dart code from Flutter APKs.

Here's an example Bash script that automates the reverse engineering of multiple Flutter APKs using B(l)utter:

#!/bin/bash

# Directory containing APK files
APK_DIR="./apks"
# Output directory for extracted Dart code
OUTPUT_DIR="./flutter_extracted"
# Create output directory if it doesn't exist
mkdir -p "$OUTPUT_DIR"

# Clear previous results
> "$OUTPUT_DIR/reverse_engineering_results.txt"

# Loop through all APK files in the directory
for apk in "$APK_DIR"/*.apk; do
  echo "Processing $apk..."

  # Extract the package name from the APK filename
  pkg_name=$(basename "$apk" .apk)

  # Create a directory for the extracted code
  pkg_output_dir="$OUTPUT_DIR/$pkg_name"
  mkdir -p "$pkg_output_dir"

  # Use B(l)utter to reverse engineer the APK
  # Make sure you have B(l)utter installed and in your PATH
  blutter extract "$apk" -o "$pkg_output_dir"

  # Check if the extraction was successful
  if [ $? -eq 0 ]; then
    echo "$apk: Extraction successful" | tee -a "$OUTPUT_DIR/reverse_engineering_results.txt"
  else
    echo "$apk: Extraction failed" | tee -a "$OUTPUT_DIR/reverse_engineering_results.txt"
  fi
done

echo "Reverse engineering complete. Results saved in $OUTPUT_DIR/reverse_engineering_results.txt."

A Flutter Mobile Application Reverse Engineering Tool by Compiling Dart AOT Runtime. It Directly analyzes the libapp.so to extract Dart objects directly from binary and generates Frida scripts to dump data in a running Flutter application.

Scan (Gitleak)

Gitleaks is an open-source secret scanner for git repositories, files, and directories.

Gitleaks was used to scan the pp.txt file of each application. A Bash script was used to automate the process.

#!/bin/bash

# Directory containing the pp.txt files
PP_DIR="./pp_files"
# Output directory for Gitleaks reports
OUTPUT_DIR="./gitleaks_reports"
# Create output directory if it doesn't exist
mkdir -p "$OUTPUT_DIR"

# Clear previous results
> "$OUTPUT_DIR/gitleaks_summary.txt"

# Loop through all pp.txt files in the directory
for pp_file in "$PP_DIR"/pp.txt; do
  # Check if the pp.txt file exists
  if [[ ! -f "$pp_file" ]]; then
    echo "No pp.txt files found in $PP_DIR."
    exit 1
  fi

  echo "Scanning $pp_file with Gitleaks..."

  # Extract the application name from the pp.txt filename
  app_name=$(basename "$(dirname "$pp_file")")  # Assuming pp.txt is in app-name directory

  # Run Gitleaks against the pp.txt file and output results
  gitleaks detect --source "$pp_file" --report-path "$OUTPUT_DIR/${app_name}_gitleaks_report.json" --report-format json

  # Check if the scan was successful
  if [ $? -eq 0 ]; then
    echo "$pp_file: Scan completed successfully" | tee -a "$OUTPUT_DIR/gitleaks_summary.txt"
  else
    echo "$pp_file: Scan failed" | tee -a "$OUTPUT_DIR/gitleaks_summary.txt"
  fi
done

echo "Gitleaks scanning complete. Summary saved in $OUTPUT_DIR/gitleaks_summary.txt."

A Bash script was created to search for keywords within the pp.txt file of each application.

#!/bin/bash

# Directory containing the pp.txt files
PP_DIR="./pp_files"
# Output file for search results
OUTPUT_FILE="scan_keyword_results.txt"

# Clear previous results
> "$OUTPUT_FILE"

# Loop through all pp.txt files in the directory
for pp_file in "$PP_DIR"/*/pp.txt; do
  # Check if the pp.txt file exists
  if [[ -f "$pp_file" ]]; then
    echo "Searching for 'Scan(2)' in $pp_file..."

    # Search for the keyword and append results to the output file
    if grep -q "Scan(2)" "$pp_file"; then
      echo "$pp_file: Found 'Scan(2)'" >> "$OUTPUT_FILE"
    else
      echo "$pp_file: 'Scan(2)' not found" >> "$OUTPUT_FILE"
    fi
  else
    echo "No pp.txt files found in $PP_DIR."
    exit 1
  fi
done

echo "Search complete. Results saved in $OUTPUT_FILE."

Analyze

The results from the scan phase were used to analyze the ASM folder and the pp.txt file.


Types of Sensitive Data

Password

Passwords that are inserted directly into software application’s source code are referred to as hardcoded or embedded passwords.

API Key

  • Requests connected to a project can be authenticated using the API key, which is a special identification.

  • Some developers may choose to leave it on public shares or hardcode them.

Private Key

  • A cryptographic variable called a private key is used to encrypt and decrypt data along with an algorithm.

  • Only those who are allowed to decrypt the material or the key generator should have access to private keys.

Access Token

  • Access tokens grant users access to a website, application, or API and are utilized in token-based authentication.

  • The token acts as the user's entry ticket, so once their identity has been verified, they won't need to enter their credentials again for the duration of the token.

  • Example: AWS Access Token, JWT, etc

API Endpoint

  • When an application programming interface (API) is visible to ecosystems outside of its immediate environment, it's known as API exposure.


Case Study Commands

Reverse engineer an application with blutter

python3 blutter.py path/to/app/lib/arm64-v8a out_dir

Gitleaks to scan for hardcoded password in pp.txt file

gitleaks detect –s path/to/source –no-git –verbose –r filename.json

Curl command to make API request

curl https://api.stripe.com/v1/charges -u sk_live_<Secret-Key>:
curl https://api.stripe.com/v1/balance -u sk_live_<Secret-Key>:
curl https://api.stripe.com/v1/files -u sk_live_<Secret-Key>:

Search for assembly files that contains "private"

ripgrep is a command line tool that searches files for patterns that was given.

rg '<pattern>'
rg 'private'

AWS Configuration

aws configure

# Command to get IAM information
aws sts get-caller-identity

Hooking function with Frida

Frida is a powerful dynamic instrumentation tool that lets you interact with running applications in real time, which is useful for extracting sensitive information, like secret keys, from mobile applications.

If you have the function address, you can hook it directly using Frida’s Interceptor API, which allows you to hook functions at specific memory addresses rather than by class and function names.

hook_by_address.js

// Define the function address as a hexadecimal value
const functionAddress = ptr("0xYOUR_FUNCTION_ADDRESS_HERE");  // Replace with the actual address

// Attach to the function at the specified address
Interceptor.attach(functionAddress, {
    onEnter: function (args) {
        console.log("Function entered at address:", functionAddress);

        // If the secret key or sensitive data is in one of the arguments, capture it
        // Example: args[0] if the key is the first argument
        console.log("Secret Key Argument:", args[0].readUtf8String());
        
        // Modify args if needed, e.g., args[0] = ptr("0xNEW_VALUE");
    },
    onLeave: function (retval) {
        // Capture the return value if it holds the secret key or sensitive data
        console.log("Secret Key Return Value:", retval.readUtf8String());

        // Optionally modify the return value if needed
        // retval.replace(ptr("0xMODIFIED_RETURN_VALUE"));
    }
});
frida -U -f com.example.app -l hook_by_address.js --no-pause


REFERENCES

Last updated