Files
2nd/10_Wiki/Topics/Coding/Security_Mobile_Hardening.md
T
2026-05-10 22:08:15 +09:00

8.8 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
security-mobile-hardening Mobile Security — root / jailbreak / SSL pin Coding draft B conceptual 2026-05-09 2026-05-09
mobile
security
vibe-coding
language applicable_to
Swift / Kotlin
iOS
Android
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

// 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://")!)
}
// 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

// 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)
    }
}
// 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)

// app/build.gradle
android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
# 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

// 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
}
// Android
fun isDebuggerAttached(): Boolean = Debug.isDebuggerConnected()

Secret 의 storage

// 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)
// 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

// iOS
let context = LAContext()
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: 'Login') { success, error in
    if success {
        // Allow access.
    }
}
// Android
val biometric = BiometricPrompt(this, executor, callback)
biometric.authenticate(BiometricPrompt.PromptInfo.Builder()
    .setTitle('Login')
    .setNegativeButtonText('Cancel')
    .build())

Token refresh

// 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 (안 됨)

// ❌ API key in code
let API_KEY = 'sk_live_xxx'

→ Decompile 가 trivial. Server 가 proxy.

Network security config (Android)

<!-- 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>
<!-- AndroidManifest.xml -->
<application android:networkSecurityConfig='@xml/network_security_config'>

App Transport Security (iOS)

<!-- 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

// 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

// iOS App Attest
let attestService = DCAppAttestService.shared
attestService.generateKey { keyId, error in
    attestService.attestKey(keyId, clientDataHash: hash) { attestation, error in
        // Send to server.
    }
}
// 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.

🔗 관련 문서