---
id: security-mobile-hardening
title: Mobile Security — root / jailbreak / SSL pin
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [mobile, security, vibe-coding]
tech_stack: { language: "Swift / Kotlin", applicable_to: ["iOS", "Android"] }
applied_in: []
aliases: [mobile security, root detection, jailbreak detection, SSL pinning, ProGuard, code obfuscation, anti-tamper]
---

# Mobile Security Hardening

> 사용자 device 가 untrusted. **Root/jailbreak detect, SSL pinning, obfuscation, anti-tamper**. 100% 막을 수 X — cost ↑.

## 📖 핵심 개념
- 매 device 가 attacker (악성 user / malware).
- Defense-in-depth (multiple layer).
- Crypto key 가 client X.
- Server 가 trust boundary.

## 💻 코드 패턴

### Root / Jailbreak detection
```swift
// iOS
func isJailbroken() -> Bool {
    let paths = ["/Applications/Cydia.app", "/usr/sbin/sshd", "/etc/apt"]
    return paths.contains { FileManager.default.fileExists(atPath: $0) }
        || canOpen(URL(string: "cydia://")!)
}
```

```kotlin
// Android
fun isRooted(): Boolean {
    val paths = listOf("/system/app/Superuser.apk", "/system/xbin/su", "/system/bin/su")
    if (paths.any { File(it).exists() }) return true
    return try { Runtime.getRuntime().exec("which su").waitFor() == 0 } catch (e: Exception) { false }
}
```

→ Detect 만. Block 가 user-friendly X.

### SSL Pinning
```swift
// iOS — URLSessionDelegate
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    guard let serverTrust = challenge.protectionSpace.serverTrust,
          let cert = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }
    
    let serverCertData = SecCertificateCopyData(cert) as Data
    let pinnedCertData = // ... loaded from bundle
    
    if serverCertData == pinnedCertData {
        completionHandler(.useCredential, URLCredential(trust: serverTrust))
    } else {
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}
```

```kotlin
// Android (OkHttp)
val pinner = CertificatePinner.Builder()
    .add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .build()

val client = OkHttpClient.Builder().certificatePinner(pinner).build()
```

→ MITM 방지. Cert rotation 시 update.

### Code obfuscation (Android)
```gradle
// app/build.gradle
android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
```

```proguard
# proguard-rules.pro
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile

# Keep models for serialization
-keep class com.example.models.** { *; }
```

→ R8 (modern) — 작은 + 빠른.

### iOS obfuscation
```
Xcode 가 native obfuscation X.
- Symbol stripping (release build).
- Swift name mangling 가 자체.
- 외부 tool (Swift Shield, etc.) 가 추가.
```

→ Symbol stripping 가 baseline.

### Anti-debug
```swift
// iOS
func isDebuggerAttached() -> Bool {
    var info = kinfo_proc()
    var size = MemoryLayout<kinfo_proc>.size
    var name: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
    let r = sysctl(&name, 4, &info, &size, nil, 0)
    return r == 0 && (info.kp_proc.p_flag & P_TRACED) != 0
}
```

```kotlin
// Android
fun isDebuggerAttached(): Boolean = Debug.isDebuggerConnected()
```

### Secret 의 storage
```swift
// iOS Keychain
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: 'token',
    kSecValueData as String: token.data(using: .utf8)!,
    kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
]
SecItemAdd(query as CFDictionary, nil)
```

```kotlin
// Android EncryptedSharedPreferences
val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val prefs = EncryptedSharedPreferences.create(
    context, "secret-prefs", masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

prefs.edit().putString('token', token).apply()
```

→ Hardware-backed (TEE / Secure Enclave).

### Biometric auth
```swift
// iOS
let context = LAContext()
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: 'Login') { success, error in
    if success {
        // Allow access.
    }
}
```

```kotlin
// Android
val biometric = BiometricPrompt(this, executor, callback)
biometric.authenticate(BiometricPrompt.PromptInfo.Builder()
    .setTitle('Login')
    .setNegativeButtonText('Cancel')
    .build())
```

### Token refresh
```ts
// Short-lived access token (15 min).
// Long-lived refresh token (Keychain).
// 401 → refresh → retry.

async function fetchWithRefresh(url: string) {
  let r = await fetch(url, { headers: { Authorization: `Bearer ${accessToken}` } });
  if (r.status === 401) {
    accessToken = await refresh();
    r = await fetch(url, { headers: { Authorization: `Bearer ${accessToken}` } });
  }
  return r;
}
```

### Anti-tamper
```
- App signature check (release build).
- Integrity check (file hash).
- Native code (NDK) 가 더 어려움 to tamper.
```

→ 100% prevent X. Cost ↑.

### Secret in code (안 됨)
```swift
// ❌ API key in code
let API_KEY = 'sk_live_xxx'
```

→ Decompile 가 trivial. Server 가 proxy.

### Network security config (Android)
```xml
<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <domain-config>
        <domain includeSubdomains='true'>api.example.com</domain>
        <pin-set>
            <pin digest='SHA-256'>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
        </pin-set>
        <trust-anchors>
            <certificates src='system' />
        </trust-anchors>
    </domain-config>
</network-security-config>
```

```xml
<!-- AndroidManifest.xml -->
<application android:networkSecurityConfig='@xml/network_security_config'>
```

### App Transport Security (iOS)
```xml
<!-- Info.plist -->
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
</dict>
```

→ HTTPS-only default.

### Reverse engineering tool
```
- Frida (runtime hook).
- Cycript (iOS).
- IDA Pro / Ghidra (decompile).
- Charles Proxy / mitmproxy (network).
- Cydia / jailbroken iOS.
- Magisk (root Android).

→ Defense-in-depth 가 cost ↑.
```

### Frida detection
```swift
// iOS
let dlopen_handle = dlopen('frida-gum', RTLD_NOLOAD)
if dlopen_handle != nil {
    // Frida detected.
}
```

→ 또 bypass 가능. Cat-and-mouse.

### 결론
```
모든 client = trustless.
- 매 critical logic = server.
- 매 secret = server.
- Client = UI 만 신뢰.

Hardening:
- Rate limit (server).
- Anomaly detect (server).
- Token rotation.
- Device attestation.
```

### Device attestation
```swift
// iOS App Attest
let attestService = DCAppAttestService.shared
attestService.generateKey { keyId, error in
    attestService.attestKey(keyId, clientDataHash: hash) { attestation, error in
        // Send to server.
    }
}
```

```kotlin
// Android Play Integrity
val integrityManager = IntegrityManagerFactory.create(context)
val task = integrityManager.requestIntegrityToken(
    IntegrityTokenRequest.builder().setNonce(nonce).build()
)
```

→ Apple / Google 가 device 의 integrity 검증.
서버 가 token 가 valid 면 trust.

### OWASP Mobile Top 10
```
M1: Improper credential usage
M2: Inadequate supply chain
M3: Insecure auth
M4: Insufficient input validation
M5: Insecure communication
M6: Inadequate privacy
M7: Insufficient binary protection
M8: Security misconfiguration
M9: Insecure data storage
M10: Insufficient cryptography
```

→ Reference.

### Cost 인지
```
Strict hardening:
- Setup cost.
- Maintenance (cert rotation, ProGuard tuning).
- Crash 가능 (false positive).
- 사용자 friction (jailbreak detect).

→ 위험 가 가치 가져야.
- Banking app: 큰 hardening.
- Casual game: 작은.
```

## 🤔 의사결정 기준
| 위험 | 추천 |
|---|---|
| Banking | 모든 (root + pin + obfuscate + attest) |
| E-commerce | SSL pin + token rotation |
| Casual | HTTPS + Keychain |
| Internal | Token + biometric |
| Game | Anti-cheat (server) |

## ❌ 안티패턴
- **Secret in code**: decompile.
- **Plain HTTP**: MITM.
- **Custom crypto**: bug.
- **Local storage 가 plain**: extract.
- **No SSL pin (sensitive)**: MITM.
- **Trust client**: server 가 trust boundary.

## 🤖 LLM 활용 힌트
- Defense-in-depth (multiple layer).
- Server 가 trust boundary.
- App attestation 가 modern.
- Hardening cost vs 위험 trade-off.

## 🔗 관련 문서
- [[Security_Auth_Authz_Patterns]]
- [[Security_Secrets_Management]]
- [[iOS_Keychain_Storage]]
