<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mobile Security and development]]></title><description><![CDATA[Mobile Security and development]]></description><link>https://emmanueliyke.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Mobile Security and development</title><link>https://emmanueliyke.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 23 Sep 2026 05:39:09 GMT</lastBuildDate><atom:link href="https://emmanueliyke.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I Reverse-Engineered Nigeria's Top Fintech Apps — Here's What I Found]]></title><description><![CDATA[Something has changed over the last two years, and most engineering teams have not yet noticed.
Hacking is no longer a specialist skill. It is becoming a commodity — and AI is the reason why.
In 2025 ]]></description><link>https://emmanueliyke.hashnode.dev/i-reverse-engineered-nigeria-s-top-fintech-apps-here-s-what-i-found</link><guid isPermaLink="true">https://emmanueliyke.hashnode.dev/i-reverse-engineered-nigeria-s-top-fintech-apps-here-s-what-i-found</guid><category><![CDATA[reverse engineering]]></category><category><![CDATA[Android]]></category><category><![CDATA[android app security]]></category><category><![CDATA[android app development]]></category><category><![CDATA[Reactnative]]></category><dc:creator><![CDATA[Iyke Emmanuel]]></dc:creator><pubDate>Mon, 18 May 2026 10:14:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a007e03e3eebc2e207a4109/bad7aa67-3862-4194-837a-4560cbedb3aa.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Something has changed over the last two years, and most engineering teams have not yet noticed.</p>
<p>Hacking is no longer a specialist skill. It is becoming a commodity — and AI is the reason why.</p>
<p>In 2025 alone, finance-related attacks hit a new record. \(3.4 billion stolen — and that is only what was reported publicly, because most companies quietly absorb losses rather than disclose them. \)1.5 billion of that disappeared from a single institution in a single day. Finance leads all industries in breach frequency, accounting for 27% of all reported incidents globally. The average cost of a single mobile app security breach reached $6.99 million. As a matter of fact, there has been a 400% surge in attacks on financial institutions in the last two years alone, and most of this is fuelled directly by AI tools that have lowered the skill barrier for every attacker on the planet.</p>
<p>These illicit activities are no longer just the work of professional hackers. AI has lowered the barrier when it comes to cyberattacks — and in the mobile world specifically, that means reverse engineering. If you are not familiar with the term, <mark class="bg-yellow-200 dark:bg-yellow-500/30">reverse engineering is the process of decompiling a mobile application to extract sensitive information from it</mark>. Before now, this was done by professionals with years of experience, Cisco certifications, and deep knowledge of penetration testing. It required specialist tooling, specialist knowledge, and significant time investment. In 2026, it can be done by anyone with free time, an internet connection, and an AI assistant like claude code.</p>
<p>I know because I did it. Using open source tools and an AI assistant, I reverse-engineered the top fintech applications in Nigeria — and within a few minutes, I found critical issues, including production cryptographic keys. Live API credentials powering biometric identity verification for millions of users. Complete backend infrastructure maps, including staging environments. All sitting inside publicly downloadable files that anyone with a phone and a Play Store account already has.</p>
<p>Here are a few of the vulnerabilities I found and how you could prevent them from being in your app</p>
<h3>What I Actually Did</h3>
<p>An Android APK is a ZIP file. That is it. Every app on the Play Store — the one handling your salary, your transfers, your loan applications, buying of airtime, and data subscription — is a compressed archive that anyone can download free from playstore and open.</p>
<p>Inside that archive is your compiled code, your assets, your configuration files, and, depending on how careful your team was — your secret keys.</p>
<p>I used jadx (an open source decompiler), Claude Code (an AI coding assistant), and basic terminal commands. No exotic tooling. No advanced exploit frameworks. The kind of setup any senior mobile engineer can replicate on a Sunday afternoon.</p>
<p>Here is what I found across multiple production fintech applications:</p>
<p><strong>Cryptographic keys are hardcoded directly in the source code.</strong> Not in a <code>.env</code> file, not behind a config flag — sitting in Java constructors and utility classes, compiled into the APK, readable by anyone who runs a decompiler. RSA private keys. AES keys. Biometric SDK credentials.</p>
<p><strong>Third-party API tokens are shipped inside asset files.</strong> Services that handle KYC verification, biometric identity checks, and BVN lookups — their production authentication tokens bundled as plaintext JSON files inside the APK. Readable with <code>cat</code>. No tooling required.</p>
<p><strong>Broken cryptography that exists only on paper.</strong> In most applications, encryption was implemented — but with hardcoded initialization vectors like <code>"12345678"</code> and deprecated algorithms that NIST retired in 2023. The encryption exists, but it's outdated and easy to break. If a dependency or a library is outdated, it's outdated for a reason.</p>
<p><strong>Staging and pilot environment URLs are shipped in production builds.</strong> In most APKs I analysed, I found staging environments bundled right inside the production app — which is already bad enough — but what made it truly dangerous is that some of those staging environments shared the same database as production. Same real user data. Same real accounts. Just a far less protected door into it.</p>
<p>Staging servers are not built to withstand attacks. They have weaker rate limiting, relaxed firewall rules, and almost no monitoring. Nobody is watching staging logs at 2 am in the middle of the night. An attacker who finds that URL does not need to touch your hardened production API at all — they walk in through the back door, probe everything they need, and come to production already knowing exactly what to do. And you handed them the address.</p>
<p><strong>Network security configurations that defeat themselves.</strong> Apps that explicitly re-enable cleartext HTTP traffic. Apps that explicitly trust user-installed certificates in production. These are not oversights buried in complex code — they are single lines in an XML file that someone wrote, reviewed, and shipped.</p>
<p>The most uncomfortable part? Most of these are free to fix. Android Keystore is built into the platform. Play Integrity API is free. Hermes for React Native is one line in a build file. Certificate pinning is built into OkHttp.</p>
<p>The issue was never resources. The issue was attention.</p>
<p><strong>Why AI Changes Everything</strong></p>
<p>Security teams have always known that APKs can be reverse-engineered. The implicit assumption was that it required specialized skills, significant time investment, and motivated attackers. That assumption is no longer valid in 2026.</p>
<p>AI-assisted analysis collapses the time and skill barrier simultaneously.</p>
<p>When I analyzed these applications, the AI assistant was not just running commands — it was reading decompiled Java files across tens of thousands of classes, identifying patterns, tracing call flows from UI interactions through ViewModels and repositories down to HTTP calls, and surfacing findings that mattered. Work that would have taken a skilled security researcher days of manual analysis happened in minutes.</p>
<p>This is not a hypothetical future risk. This is the current reality.</p>
<p>Consider what this means practically: 93% of organizations believe their mobile app protections are sufficient to prevent attacks. 62% of those same organizations experienced at least one mobile app security incident in the past year. The gap between perceived security and actual security has never been wider — and AI is widening it further every month by making analysis accessible to anyone.</p>
<p>An attacker today does not need to be a security expert to reverse engineer your APK. They need to be curious, have an AI assistant, and have a reason to look. The first two conditions describe millions of people. The third condition — having a reason — is a function of how much money flows through your application.</p>
<p>Fintech applications are not just high-value targets. They are the highest-value targets in the mobile ecosystem. Every endpoint you expose is a map to your users' money. Every credential you leak is a key to a system that handles real financial transactions. And the person who finds it might not be a professional attacker — it might be a curious engineer, a competitor, or someone who just wanted to see what was inside.</p>
<blockquote>
<p>If your team has not done a mobile security audit in the last two years, you are not behind. You are exposed.</p>
</blockquote>
<h3>The Vulnerabilities — Ranked by How Easy They Are to Fix</h3>
<p>What follows is not a theoretical security framework. It is a practical list of what was broken in these real production applications and exactly what should replace it, ordered by the effort required to fix it.</p>
<p><strong>1. Secrets in the APK — The Root Cause of Most Critical Findings</strong></p>
<p>Every critical finding I encountered traces back to one decision: baking secrets into the APK at build time. And before you think "it's probably an old key, no big deal" — that is, Old secrets are often more dangerous than new ones, because the team has stopped thinking about them entirely. Nobody rotated them. Nobody revoked them. They just sit there, valid and active, in a file that has been publicly downloadable for years.</p>
<p>API tokens, cryptographic keys, SDK credentials — once they are in your codebase, they ship to every device that installs your app. And when you push a new version, the old APK does not disappear. It lives on APKMirror, APKPure, and archive sites indefinitely. Every secret that ever shipped in any version of your app is permanently out there. <mark class="bg-yellow-200 dark:bg-yellow-500/30">AI can now scan thousands of APKs automatically, extract every credential across every version, and test which ones are still active — without any human involvement.</mark></p>
<p><strong>What was found:</strong> Production authentication tokens for identity verification services — the kind that handle BVN lookups and facial verification — shipped as plaintext JSON files inside the <code>assets/</code> folder. Readable without any tooling. Identical tokens in both the production config and the debug config, meaning environment separation did not exist at the credential level.</p>
<p><strong>The fix — remote secret delivery:</strong></p>
<pre><code class="language-plaintext">APK contains: zero secrets
             + authentication logic
             + public key fingerprint of your secret delivery endpoint

After successful login + Play Integrity verification:
Server delivers secrets encrypted to the device's hardware-backed public key
Secrets are decrypted inside Android Keystore
Secrets never touch Java heap
Secrets rotate on schedule
</code></pre>
<p>Nothing sensitive ships in the APK. An attacker who fully decompiles your application finds no credentials — only the logic to request them from a server they cannot reach without a valid, authenticated session.</p>
<p><strong>2. Cryptographic Keys — Use the Hardware You Already Have</strong></p>
<p>Multiple applications implemented their own key management: storing keys as string constants in Java source, embedding them in native <code>.so</code> libraries, or deriving them from hardcoded values. Every one of these approaches is extractable — statically from the source, or dynamically with Frida at runtime.</p>
<p><strong>What was found:</strong> 3DES encryption with a hardcoded IV of <code>"12345678"</code>. AES-GCM with a static all-zero IV — <code>new byte[12]</code> — across every single encryption call. An RSA private key stored as a 1,200-character string literal in a Java class. These were present in production code handling real financial transactions.</p>
<p>A hardcoded IV means that two messages encrypted with the same key produce related ciphertexts. With a static IV and enough captured traffic, the keystream can be recovered. The encryption is theater.</p>
<p><strong>The fix — Android Keystore:</strong></p>
<pre><code class="language-kotlin">// Generate a key that never leaves hardware
val keyPairGenerator = KeyPairGenerator.getInstance(
    KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"
)
keyPairGenerator.initialize(
    KeyGenParameterSpec.Builder(
        "api_signing_key",
        KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
    )
    .setDigests(KeyProperties.DIGEST_SHA256)
    .setUserAuthenticationRequired(true)
    .build()
)
keyPairGenerator.generateKeyPair()
// The private key never leaves the Strongbox hardware
// You call sign() — hardware does it internally and returns only the signature
// No Frida hook, no static extraction can retrieve this key
</code></pre>
<p>For AES encryption — generate a random IV on every single operation:</p>
<pre><code class="language-kotlin">fun encrypt(plaintext: ByteArray, key: SecretKey): Pair&lt;ByteArray, ByteArray&gt; {
    val iv = ByteArray(12).also { SecureRandom().nextBytes(it) }
    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    cipher.init(Cipher.ENCRYPT_MODE, key, GCMParameterSpec(128, iv))
    return Pair(iv, cipher.doFinal(plaintext))
// Store/transmit the IV alongside the ciphertext — it is not secret
// Each encryption produces a unique IV — static IV attacks are impossible
}
</code></pre>
<p><strong>3: Network Security Configuration — One XML File That Controls Everything</strong> Every application I analyzed had a network_security_config.xml. In several cases this file was configured in ways that actively undermined transport security — not through complexity, but through explicit flags that someone wrote and nobody challenged.</p>
<p><strong>What was found:</strong></p>
<pre><code class="language-xml">&lt;!-- Found in production builds --&gt;
&lt;network-security-config&gt;
    &lt;base-config cleartextTrafficPermitted="true"&gt;
        &lt;trust-anchors&gt;
            &lt;certificates src="system"/&gt;
            &lt;certificates src="user"/&gt; &lt;!-- This line is the problem --&gt;
        &lt;/trust-anchors&gt;
    &lt;/base-config&gt;
&lt;/network-security-config&gt;
</code></pre>
<p><code>cleartextTrafficPermitted="true"</code> means plain HTTP is allowed. <code>&lt;certificates src="user"/&gt;</code> means user-installed certificates are trusted — installing a proxy certificate on the device is sufficient to intercept all HTTPS traffic. No certificate pinning existed to prevent this.</p>
<p>The result: anyone on the same network, with a proxy certificate installed, can read all API traffic in plaintext. Session tokens, account balances, transaction data everything.</p>
<p><strong>The fix:</strong></p>
<pre><code class="language-xml">&lt;!-- Production hardened configuration --&gt;
&lt;network-security-config&gt;
    &lt;base-config cleartextTrafficPermitted="false"&gt;
        &lt;trust-anchors&gt;
            &lt;certificates src="system"/&gt;
        &lt;!-- Never include user certificates in production --&gt;
        &lt;/trust-anchors&gt;
    &lt;/base-config&gt;

    &lt;!-- Certificate pinning for your API domain --&gt;
    &lt;domain-config&gt;
        &lt;domain includeSubdomains="true"&gt;api.yourapp.com&lt;/domain&gt;
        &lt;pin-set expiration="2026-01-01"&gt;
            &lt;pin digest="SHA-256"&gt;your_leaf_certificate_pin=&lt;/pin&gt;
            &lt;pin digest="SHA-256"&gt;your_backup_pin=&lt;/pin&gt;
        &lt;/pin-set&gt;
    &lt;/domain-config&gt;
&lt;/network-security-config&gt;
</code></pre>
<p>Generate your pin:</p>
<pre><code class="language-graphql">openssl s_client -connect api.yourapp.com:443 | \
openssl x509 -pubkey -noout | \
openssl pkey -pubin -outform DER | \
openssl dgst -sha256 -binary | \
base64
</code></pre>
<p>Two pins minimum — one for the current certificate, one backup for rotation. The <code>expiration</code> field forces you to revisit the pin before the certificate expires. Without it, certificate rotation breaks your app silently.</p>
<p><strong>4. ProGuard Is Not Security — Use DexGuard</strong></p>
<p>Every application that used ProGuard or R8 was fully analysed, and here is why — ProGuard is not an obfuscation tool in the way most engineers think it is. It does not protect what is <em>inside</em> your files. All it does is rename them. <code>UserAuthenticationRepository</code> becomes <code>a3.b</code>. <code>validateToken()</code> becomes <code>f()</code>. The class name changes. Everything inside it stays exactly as you wrote it.</p>
<p>That distinction matters more than most people realise.</p>
<p>Because strings are never renamed. Every URL, every API endpoint, every hardcoded credential, every environment configuration — they survive ProGuard completely untouched. An attacker does not need to understand your class structure at all. They just search for what they know must exist:</p>
<pre><code class="language-bash"># This bypasses ProGuard entirely — one command
grep -r "https://" --include="*.java" decompiled-sources/
# Returns every URL in your entire codebase, regardless of obfuscation
</code></pre>
<p>That is it. One terminal command. Every base URL, every endpoint, every internal service address — all returned instantly. The renaming ProGuard spent time doing is completely irrelevant to this approach.</p>
<p>If your security strategy relies on ProGuard to protect sensitive strings, it is not a security strategy. It is a false sense of safety.</p>
<p><strong>What actually stops static analysis — DexGuard:</strong></p>
<p>DexGuard (by Guardsquare) replaces string literals with runtime-decrypted blobs:</p>
<pre><code class="language-plaintext">Before DexGuard: "https://api.yourapp.com"  ← visible in decompiled code
After DexGuard:  decrypt(new byte[]{0x3f, 0xa1, 0x2c...})  ← grep finds nothing
</code></pre>
<p>Beyond string encryption, DexGuard encrypts entire classes as AES blobs inside the DEX file. They only exist in readable form when the ClassLoader loads them at runtime. jadx decompiles an encrypted blob, not Java code.</p>
<p>For React Native applications specifically — enable Hermes. This is one line:</p>
<p>gradle</p>
<pre><code class="language-gradle">// android/app/build.gradle
project.ext.react = [
    hermesEnabled: true  // Makes your JS bundle opaque bytecode
]
</code></pre>
<p>One application I analyzed shipped its entire business logic — including hundreds of API endpoints, every environment URL, and all configuration values — as plain readable JavaScript. Every function name. Every variable. Enabling Hermes would have made the bundle opaque bytecode. It was not enabled.</p>
<p><strong>5. Google Play Integrity API — Server-Side Device Verification</strong></p>
<p>Several applications implemented their own root and emulator detection in Java — checking for Magisk paths, scanning for Frida signatures. These checks are compiled into the APK and readable by anyone who decompiles it. Once you can read the checks, you can bypass them by hooking the return value.</p>
<p>Play Integrity works differently. The verification happens on Google's servers, not on the device.</p>
<pre><code class="language-kotlin">val integrityManager = IntegrityManagerFactory.create(context)
val request = StandardIntegrityTokenRequest.newBuilder()
    .setRequestHash(hashOfYourRequest)
    .build()

integrityManager.requestIntegrityToken(request)
    .addOnSuccessListener { response -&gt;
        // Send response.token() to YOUR backend
        // Your backend verifies with Google
        // Google confirms: genuine device, unmodified APK, //real Play Store install
    }
</code></pre>
<p>The token is signed by Google's private key. It cannot be forged. A rooted device, an emulator, a repackaged APK — all produce tokens that fail verification. Your server rejects the request before processing.</p>
<p>This does not stop static analysis. But it stops the next step: using what was found to make scripted API calls at scale.</p>
<p><strong>6. Build Configuration — Stop Shipping Your Internal Infrastructure</strong></p>
<p>This requires no new tooling. It requires a code review.</p>
<p>Staging and pilot environment URLs should never appear in a production build. i have explained why earlier but i would enfacise on it again, Internal service names, debug blob storage endpoints, test environment credentials — none of these belong in an APK that ships to the Play Store.</p>
<pre><code class="language-kotlin">// Wrong — all environments ship to users
object ApiConfig {
    const val PROD = "https://api.yourapp.com"
    const val STAGING = "https://staging.yourapp.com"    // ← ships to users
    const val PILOT = "https://pilot.yourapp.com"        // ← ships to users
}

// Correct — build flavor controls what ships
// build.gradle
buildTypes {
    release {
        buildConfigField("String", "BASE_URL", "\"https://api.yourapp.com\"")
    }
    debug {
        buildConfigField("String", "BASE_URL", "\"https://staging.yourapp.com\"")
    }
}
</code></pre>
<p>Staging environments typically have weaker rate limiting, relaxed WAF rules, and sometimes share backend infrastructure with production. Shipping their URLs in your production APK hands attackers a map to your softest targets.</p>
<h2>The Security Stack — Ranked by Impact</h2>
<p>If your team is starting from zero, implement in this order:</p>
<table>
<thead>
<tr>
<th>Priority</th>
<th>Control</th>
<th>Cost</th>
<th>What It Stops</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Android Keystore for all keys</td>
<td>Free</td>
<td>Key extraction from source or native libs</td>
</tr>
<tr>
<td>2</td>
<td>Play Integrity API</td>
<td>Free</td>
<td>Scripted API abuse from rooted/emulated devices</td>
</tr>
<tr>
<td>3</td>
<td>Hermes enabled (React Native)</td>
<td>Free</td>
<td>JS bundle reading</td>
</tr>
<tr>
<td>4</td>
<td>Certificate pinning in OkHttp</td>
<td>Free</td>
<td>Traffic interception via proxy</td>
</tr>
<tr>
<td>5</td>
<td>Remote secret delivery</td>
<td>Engineering</td>
<td>Secrets in APK</td>
</tr>
<tr>
<td>6</td>
<td>Build flavor URL separation</td>
<td>Engineering</td>
<td>Infrastructure exposure</td>
</tr>
<tr>
<td>7</td>
<td>DexGuard</td>
<td>Enterprise cost</td>
<td>Static analysis via jadx</td>
</tr>
<tr>
<td>8</td>
<td>mTLS client certificates</td>
<td>Engineering</td>
<td>API calls without valid client identity</td>
</tr>
<tr>
<td>9</td>
<td>RASP (Appdome/Zimperium)</td>
<td>$$</td>
<td>Frida, repackaging, runtime manipulation</td>
</tr>
</tbody></table>
<p>The first four are free. They require only engineering time. For a fintech application, they are not optional — they are the minimum viable security posture.</p>
<p>Conclusion :</p>
<p>Nothing I found in this research was exotic. Every vulnerability I encountered has a well-documented fix, most of which cost nothing to implement. The Android platform provides hardware-backed key storage, device integrity verification, and secure network configuration — all built in, all free, all underused.</p>
<p>The pattern I found across multiple applications was not malice. It was not incompetence. It was the result of shipping under pressure without treating security as a first-class requirement. Deadlines are real. Sprint velocity is real. The business pressure to ship is real.</p>
<p>But so is this: a motivated person with an AI assistant and an afternoon can now do what previously required a security specialist and several days. That gap is only going to widen. The question for every engineering lead reading this is not whether your APK can be analyzed. It can. The question is what an analyst finds when they look.</p>
<p>Do not hand them the weapon. Do not leave your secrets in the APK.</p>
<p>Two minutes is a long time to be vulnerable.</p>
]]></content:encoded></item></channel></rss>