A Technical Walkthrough from Binary Analysis to Runtime Query Manipulation

Welcome to this detailed walkthrough of the Flipcoin Wallet iOS challenge, part of the Mobile Hacking Lab training platform.

Our mission?

✅ Unpack and reverse the application.
✅ Identify insecure SQL query construction.
✅ Confirm the presence of a SQL Injection vulnerability.
✅ Bypass UI limitations using Frida instrumentation.
✅ Inject a custom SQL payload and extract the hidden flag — documenting every step with clarity.

🎯 Objective

This project aims to identify and exploit a SQL Injection vulnerability in the Flipcoin Wallet iOS application. We will demonstrate how a poorly constructed SQL query can be manipulated to extract sensitive data, even when there is no visible input from the user.


🛠 Tools Used

Tool Purpose
Frida Dynamic instrumentation and runtime memory manipulation
Radare2 / rabin2 Static analysis of the binary
TrollStore Installing unsigned apps on a jailbroken iPhone
iPhone SE (jailbroken) Target device used for dynamic testing
Terminal Execution of analysis commands and scripts

🧩 Analysis Steps (In Detail)

1. IPA Extraction and Binary Inspection

We started by extracting the .ipa file:

unzip com.mobilehackinglab.Flipcoin-Wallet6.ipa -d flipcoin_wallet_lab

Inside the Payload/Flipcoin Wallet.app folder, we located the app binary. We used rabin2 to inspect embedded strings:

rabin2 -zqq Payload/Flipcoin\ Wallet.app/Flipcoin\ Wallet | grep -iE 'select|insert|update|delete|sqlite'

Image.png

Key findings:

  • A SQLite file: your_database_name.sqlite
  • Table: wallet
  • SQL strings including INSERT OR IGNORE INTO wallet and SELECT * FROM wallet
  • The recovery key column contains the flag: FLAG{fl1p_d4_c01nz}}

This confirmed that the app interacts with a local SQLite database.

2. Function Discovery via Imports and Cross-references

We searched for imported SQLite functions:

rabin2 -i Payload/Flipcoin\ Wallet.app/Flipcoin\ Wallet | grep sqlite3

Image.png

We found the app uses functions like sqlite3_prepare, sqlite3_exec, and sqlite3_column_text.

To locate where these were used in the binary:

r2 -A Payload/Flipcoin\ Wallet.app/Flipcoin\ Wallet
> axt sym.imp.sqlite3_prepare

Two key functions were identified: createWallets and getWallets. We disassembled getWallets:

s 0x10000f98c
pdf

We confirmed that it performs:

SELECT * FROM wallet

without using parameter binding — indicating potential for SQL Injection.


🔧 Why We Switched to Frida Instead of Manual Input

After installing the app using TrollStore on a jailbroken iPhone SE, the interface was completely broken: the layout was oversized and unusable.

We attempted several UI fix strategies:

  • Applying view scaling using Frida scripts
  • Iterating subviews to locate the root UIView

All failed due to the app likely being developed in Flutter or SwiftUI, where traditional UIKit methods no longer apply.

As we could not interact via the UI, we used Frida to simulate SQL Injection directly at the memory level.


⚔️ Simulating SQL Injection with Frida

We built a script to intercept and replace the vulnerable query at runtime.

Script: extract_flag_sqlite.js

// Intercept and replace vulnerable query
Interceptor.attach(Module.getExportByName(null, "sqlite3_prepare"), {
  onEnter: function (args) {
    const originalQuery = args[1].readUtf8String();
    if (originalQuery.includes("SELECT * FROM wallet")) {
      const newQuery = "SELECT 1, 2, 3, 4, recovery_key FROM wallet";
      Memory.writeUtf8String(args[1], newQuery);
      console.log("[+] SQL query replaced:", newQuery);
    }
  }
});

// Monitor data extraction from each column
Interceptor.attach(Module.getExportByName(null, "sqlite3_column_text"), {
  onEnter: function (args) {
    this.columnIndex = args[1].toInt32();
  },
  onLeave: function (retval) {
    const value = Memory.readUtf8String(retval);
    if (value && value.includes("FLAG{")) {
      console.warn("🚩 FLAG FOUND:", value);
    }
  }
});

How This Works (Line by Line)

  • sqlite3_prepare is called when SQL queries are constructed.
  • We check if the query contains SELECT * FROM wallet
  • If so, we replace the query string in memory with a malicious one that extracts the recovery_key column
  • When sqlite3_column_text is called, we read each column's content
  • If the result contains FLAG{, we print it

Note: The app must still execute the original function. We manually triggered it by interacting with whatever broken UI was visible, until getWallets was called.

Execution:

frida -U -n "Flipcoin Wallet" -l extract_flag_sqlite.js

Output:

[+] SQL query replaced: SELECT 1, 2, 3, 4, recovery_key FROM wallet
🚩 FLAG FOUND: FLAG{fl1p_d4_c01nz}}

🧠 Why This is SQL Injection

SQL Injection is not defined by typing a payload — it’s about injecting or modifying a SQL query due to poor query construction.

In this case:

  • The query is built as a static string without input sanitization
  • No parameter binding is used
  • Although we couldn’t inject via UI, we demonstrated the flaw by changing the query directly, proving it is vulnerable and exploitable

This is a valid example of SQL Injection — only the delivery method changed (from input field to in-memory injection).


📫 About the Author

[Júnior Carreiro]
🔐 Mobile AppSec | iOS Security | Reverse Engineering
📍 Let's connect: [GitHub] · [Linkedin]
🏷️ Tags: #ios #reverseengineering #frida #infosec #mobile