Files
2nd/10_Wiki/Topic_Programming/Coding/Security_Mobile_Hardening.md
T
Antigravity Agent 9148c358d0 docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
2026-07-05 00:33:48 +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.

🔗 관련 문서