android-sdd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: encrypted-storage
|
|
3
|
+
description: >
|
|
4
|
+
Encrypted local storage for Android apps.
|
|
5
|
+
Load this skill when storing sensitive data (tokens, user credentials,
|
|
6
|
+
personal info) locally, using EncryptedSharedPreferences, EncryptedFile,
|
|
7
|
+
or integrating encryption with DataStore or Room.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Encrypted Storage
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Encrypted Storage protects sensitive data at rest on the device. Android provides `EncryptedSharedPreferences` and `EncryptedFile` via the Security library, both backed by the Android Keystore. For DataStore and Room, encryption is applied at the file or database level.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- **Never store sensitive data in plain SharedPreferences or files**
|
|
20
|
+
- Use **Android Keystore** as the key provider — keys never leave the secure hardware
|
|
21
|
+
- **EncryptedSharedPreferences** for key-value sensitive data (tokens, settings)
|
|
22
|
+
- **EncryptedFile** for sensitive file content
|
|
23
|
+
- **SQLCipher** for encrypted Room databases
|
|
24
|
+
- Encryption keys are **generated once and stored in Keystore** — never hardcoded
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## EncryptedSharedPreferences
|
|
29
|
+
|
|
30
|
+
```kotlin
|
|
31
|
+
// ✅ Create encrypted SharedPreferences
|
|
32
|
+
fun createEncryptedPreferences(context: Context): SharedPreferences {
|
|
33
|
+
val masterKey = MasterKey.Builder(context)
|
|
34
|
+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
35
|
+
.build()
|
|
36
|
+
|
|
37
|
+
return EncryptedSharedPreferences.create(
|
|
38
|
+
context,
|
|
39
|
+
"secure_prefs", // file name
|
|
40
|
+
masterKey,
|
|
41
|
+
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
42
|
+
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ✅ Wrapper for type-safe access
|
|
47
|
+
class SecurePreferences @Inject constructor(
|
|
48
|
+
@ApplicationContext context: Context
|
|
49
|
+
) {
|
|
50
|
+
private val prefs = createEncryptedPreferences(context)
|
|
51
|
+
|
|
52
|
+
var authToken: String?
|
|
53
|
+
get() = prefs.getString(KEY_AUTH_TOKEN, null)
|
|
54
|
+
set(value) = prefs.edit {
|
|
55
|
+
if (value != null) putString(KEY_AUTH_TOKEN, value)
|
|
56
|
+
else remove(KEY_AUTH_TOKEN)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
var refreshToken: String?
|
|
60
|
+
get() = prefs.getString(KEY_REFRESH_TOKEN, null)
|
|
61
|
+
set(value) = prefs.edit {
|
|
62
|
+
if (value != null) putString(KEY_REFRESH_TOKEN, value)
|
|
63
|
+
else remove(KEY_REFRESH_TOKEN)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fun clearAll() = prefs.edit { clear() }
|
|
67
|
+
|
|
68
|
+
companion object {
|
|
69
|
+
private const val KEY_AUTH_TOKEN = "auth_token"
|
|
70
|
+
private const val KEY_REFRESH_TOKEN = "refresh_token"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## EncryptedFile
|
|
78
|
+
|
|
79
|
+
```kotlin
|
|
80
|
+
// ✅ Write encrypted file
|
|
81
|
+
fun writeEncryptedFile(context: Context, fileName: String, content: ByteArray) {
|
|
82
|
+
val masterKey = MasterKey.Builder(context)
|
|
83
|
+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
84
|
+
.build()
|
|
85
|
+
|
|
86
|
+
val file = File(context.filesDir, fileName)
|
|
87
|
+
if (file.exists()) file.delete() // EncryptedFile cannot overwrite
|
|
88
|
+
|
|
89
|
+
val encryptedFile = EncryptedFile.Builder(
|
|
90
|
+
context,
|
|
91
|
+
file,
|
|
92
|
+
masterKey,
|
|
93
|
+
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
|
|
94
|
+
).build()
|
|
95
|
+
|
|
96
|
+
encryptedFile.openFileOutput().use { output ->
|
|
97
|
+
output.write(content)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ✅ Read encrypted file
|
|
102
|
+
fun readEncryptedFile(context: Context, fileName: String): ByteArray {
|
|
103
|
+
val masterKey = MasterKey.Builder(context)
|
|
104
|
+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
105
|
+
.build()
|
|
106
|
+
|
|
107
|
+
val file = File(context.filesDir, fileName)
|
|
108
|
+
val encryptedFile = EncryptedFile.Builder(
|
|
109
|
+
context,
|
|
110
|
+
file,
|
|
111
|
+
masterKey,
|
|
112
|
+
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
|
|
113
|
+
).build()
|
|
114
|
+
|
|
115
|
+
return encryptedFile.openFileInput().use { it.readBytes() }
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Encrypted DataStore
|
|
122
|
+
|
|
123
|
+
```kotlin
|
|
124
|
+
// ✅ Encrypt DataStore using EncryptedFile as backing storage
|
|
125
|
+
// Add dependency: androidx.security:security-crypto-ktx
|
|
126
|
+
|
|
127
|
+
fun createEncryptedDataStore(context: Context): DataStore<Preferences> {
|
|
128
|
+
val masterKey = MasterKey.Builder(context)
|
|
129
|
+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
130
|
+
.build()
|
|
131
|
+
|
|
132
|
+
return PreferenceDataStoreFactory.createWithPath(
|
|
133
|
+
produceFile = {
|
|
134
|
+
val file = File(context.filesDir, "secure_datastore.preferences_pb")
|
|
135
|
+
// For true file-level encryption, wrap with EncryptedFile
|
|
136
|
+
file.toOkioPath()
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ✅ Simpler: use Tink-based DataStore encryption
|
|
142
|
+
// (preferred for production)
|
|
143
|
+
class EncryptedDataStoreManager @Inject constructor(
|
|
144
|
+
private val dataStore: DataStore<Preferences>
|
|
145
|
+
) {
|
|
146
|
+
private val TOKEN_KEY = stringPreferencesKey("auth_token")
|
|
147
|
+
private val USER_ID_KEY = stringPreferencesKey("user_id")
|
|
148
|
+
|
|
149
|
+
val authToken: Flow<String?> = dataStore.data
|
|
150
|
+
.catch { if (it is IOException) emit(emptyPreferences()) else throw it }
|
|
151
|
+
.map { it[TOKEN_KEY] }
|
|
152
|
+
|
|
153
|
+
suspend fun saveAuthToken(token: String) {
|
|
154
|
+
dataStore.edit { it[TOKEN_KEY] = token }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
suspend fun clearAuth() {
|
|
158
|
+
dataStore.edit {
|
|
159
|
+
it.remove(TOKEN_KEY)
|
|
160
|
+
it.remove(USER_ID_KEY)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Encrypted Room (SQLCipher)
|
|
169
|
+
|
|
170
|
+
```kotlin
|
|
171
|
+
// ✅ SQLCipher for encrypted Room database
|
|
172
|
+
// dependency: net.zetetic:android-database-sqlcipher
|
|
173
|
+
// dependency: androidx.sqlite:sqlite-ktx
|
|
174
|
+
|
|
175
|
+
@Database(entities = [...], version = 1)
|
|
176
|
+
abstract class SecureDatabase : RoomDatabase() {
|
|
177
|
+
abstract fun sensitiveDataDao(): SensitiveDataDao
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fun buildSecureDatabase(context: Context, passphrase: ByteArray): SecureDatabase {
|
|
181
|
+
val factory = SupportFactory(passphrase)
|
|
182
|
+
return Room.databaseBuilder(context, SecureDatabase::class.java, "secure.db")
|
|
183
|
+
.openHelperFactory(factory)
|
|
184
|
+
.build()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ✅ Derive passphrase from Keystore-backed key
|
|
188
|
+
class DatabaseKeyProvider @Inject constructor(
|
|
189
|
+
@ApplicationContext private val context: Context
|
|
190
|
+
) {
|
|
191
|
+
fun getPassphrase(): ByteArray {
|
|
192
|
+
val keyAlias = "db_key"
|
|
193
|
+
val keyStore = KeyStore.getInstance("AndroidKeyStore").also { it.load(null) }
|
|
194
|
+
|
|
195
|
+
if (!keyStore.containsAlias(keyAlias)) {
|
|
196
|
+
val keyGenerator = KeyGenerator.getInstance(
|
|
197
|
+
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"
|
|
198
|
+
)
|
|
199
|
+
keyGenerator.init(
|
|
200
|
+
KeyGenParameterSpec.Builder(
|
|
201
|
+
keyAlias,
|
|
202
|
+
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
|
|
203
|
+
)
|
|
204
|
+
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
|
205
|
+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
|
206
|
+
.setKeySize(256)
|
|
207
|
+
.build()
|
|
208
|
+
)
|
|
209
|
+
keyGenerator.generateKey()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Derive a stable passphrase from the key
|
|
213
|
+
val key = keyStore.getKey(keyAlias, null) as SecretKey
|
|
214
|
+
return key.encoded ?: ByteArray(32).also { SecureRandom().nextBytes(it) }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Hilt Setup
|
|
222
|
+
|
|
223
|
+
```kotlin
|
|
224
|
+
@Module
|
|
225
|
+
@InstallIn(SingletonComponent::class)
|
|
226
|
+
object StorageModule {
|
|
227
|
+
|
|
228
|
+
@Provides
|
|
229
|
+
@Singleton
|
|
230
|
+
fun provideSecurePreferences(
|
|
231
|
+
@ApplicationContext context: Context
|
|
232
|
+
): SecurePreferences = SecurePreferences(context)
|
|
233
|
+
|
|
234
|
+
@Provides
|
|
235
|
+
@Singleton
|
|
236
|
+
fun provideSecureDatabase(
|
|
237
|
+
@ApplicationContext context: Context,
|
|
238
|
+
keyProvider: DatabaseKeyProvider
|
|
239
|
+
): SecureDatabase = buildSecureDatabase(context, keyProvider.getPassphrase())
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## What to Encrypt
|
|
246
|
+
|
|
247
|
+
| Data | Storage | Encrypted? |
|
|
248
|
+
|---|---|---|
|
|
249
|
+
| Auth token / refresh token | EncryptedSharedPreferences | ✅ Always |
|
|
250
|
+
| User PII (name, email, phone) | Encrypted Room / SQLCipher | ✅ Always |
|
|
251
|
+
| Payment data | Encrypted Room | ✅ Always |
|
|
252
|
+
| App preferences (theme, language) | Regular DataStore | ❌ Not needed |
|
|
253
|
+
| Cached images | File cache | ❌ Usually not needed |
|
|
254
|
+
| Health/medical data | Encrypted Room | ✅ Always |
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Anti-Patterns
|
|
259
|
+
|
|
260
|
+
- Storing tokens in plain `SharedPreferences` — readable by rooted devices and backup extraction
|
|
261
|
+
- Hardcoding encryption keys in source code — use Android Keystore
|
|
262
|
+
- Storing the encryption key alongside the encrypted data — defeats the purpose
|
|
263
|
+
- Using `MODE_WORLD_READABLE` or `MODE_WORLD_WRITEABLE` for any file
|
|
264
|
+
- Encrypting non-sensitive data — unnecessary overhead; encrypt only what needs protection
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Related Skills
|
|
269
|
+
- `keystore` — Android Keystore key generation and management
|
|
270
|
+
- `datastore` — DataStore patterns
|
|
271
|
+
- `room` — Room database setup
|
|
272
|
+
- `encrypted-database` — full SQLCipher Room setup
|
|
273
|
+
- `secure-networking` — protecting data in transit
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frida-detection
|
|
3
|
+
description: >
|
|
4
|
+
Frida dynamic instrumentation detection for Android apps.
|
|
5
|
+
Load this skill when detecting Frida gadget injection, Frida server
|
|
6
|
+
presence, or dynamic instrumentation attempts targeting the app at runtime.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Frida Detection
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
Frida is a dynamic instrumentation toolkit widely used by security researchers and attackers to hook functions, inspect memory, and bypass security checks at runtime. Unlike Xposed (which requires a framework installed on the device), Frida can be injected into a process dynamically. Detecting it requires checking for its artifacts at multiple levels.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Principles
|
|
17
|
+
|
|
18
|
+
- **Frida leaves multiple traces** — process maps, port, libraries, named pipes
|
|
19
|
+
- **Check at multiple points** — startup and before sensitive operations
|
|
20
|
+
- **Frida can hide itself** — combine with server-side integrity (Play Integrity API)
|
|
21
|
+
- **Native checks are harder to bypass** — JNI-based detection is more resilient than Java
|
|
22
|
+
- **No detection is perfect** — the goal is to raise the cost of attack
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Java-Level Detection
|
|
27
|
+
|
|
28
|
+
```kotlin
|
|
29
|
+
class FridaDetector @Inject constructor() {
|
|
30
|
+
|
|
31
|
+
data class FridaCheckResult(
|
|
32
|
+
val isDetected: Boolean,
|
|
33
|
+
val signals: List<String>
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
fun check(): FridaCheckResult {
|
|
37
|
+
val signals = mutableListOf<String>()
|
|
38
|
+
|
|
39
|
+
if (checkFridaServer()) signals.add("frida_server_port")
|
|
40
|
+
if (checkFridaFiles()) signals.add("frida_files")
|
|
41
|
+
if (checkProcessMaps()) signals.add("frida_in_maps")
|
|
42
|
+
if (checkFridaNamedPipes()) signals.add("frida_named_pipes")
|
|
43
|
+
if (checkFridaLibraries()) signals.add("frida_libraries")
|
|
44
|
+
|
|
45
|
+
return FridaCheckResult(
|
|
46
|
+
isDetected = signals.isNotEmpty(),
|
|
47
|
+
signals = signals
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ✅ Check if Frida server is listening on its default port (27042)
|
|
52
|
+
private fun checkFridaServer(): Boolean {
|
|
53
|
+
return try {
|
|
54
|
+
Socket().use { socket ->
|
|
55
|
+
socket.connect(InetSocketAddress("127.0.0.1", 27042), 100)
|
|
56
|
+
true // connection succeeded — Frida server likely running
|
|
57
|
+
}
|
|
58
|
+
} catch (e: Exception) {
|
|
59
|
+
false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ✅ Check for Frida-related files
|
|
64
|
+
private fun checkFridaFiles(): Boolean {
|
|
65
|
+
val fridaPaths = listOf(
|
|
66
|
+
"/data/local/tmp/frida-server",
|
|
67
|
+
"/data/local/tmp/frida",
|
|
68
|
+
"/sdcard/frida-server",
|
|
69
|
+
"/system/bin/frida-server"
|
|
70
|
+
)
|
|
71
|
+
return fridaPaths.any { File(it).exists() }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ✅ Check /proc/self/maps for Frida agent
|
|
75
|
+
private fun checkProcessMaps(): Boolean {
|
|
76
|
+
return try {
|
|
77
|
+
File("/proc/self/maps").readLines().any { line ->
|
|
78
|
+
line.contains("frida", ignoreCase = true) ||
|
|
79
|
+
line.contains("gum-js-loop", ignoreCase = true) ||
|
|
80
|
+
line.contains("gmain", ignoreCase = true) ||
|
|
81
|
+
line.contains("linjector", ignoreCase = true)
|
|
82
|
+
}
|
|
83
|
+
} catch (e: Exception) {
|
|
84
|
+
false
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ✅ Check for Frida named pipes in /proc/self/fd
|
|
89
|
+
private fun checkFridaNamedPipes(): Boolean {
|
|
90
|
+
return try {
|
|
91
|
+
val fdDir = File("/proc/self/fd")
|
|
92
|
+
fdDir.listFiles()?.any { fd ->
|
|
93
|
+
try {
|
|
94
|
+
val link = fd.canonicalPath
|
|
95
|
+
link.contains("frida", ignoreCase = true) ||
|
|
96
|
+
link.contains("linjector", ignoreCase = true)
|
|
97
|
+
} catch (e: Exception) {
|
|
98
|
+
false
|
|
99
|
+
}
|
|
100
|
+
} ?: false
|
|
101
|
+
} catch (e: Exception) {
|
|
102
|
+
false
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ✅ Check loaded libraries for Frida signature
|
|
107
|
+
private fun checkFridaLibraries(): Boolean {
|
|
108
|
+
return try {
|
|
109
|
+
File("/proc/self/maps").readLines().any { line ->
|
|
110
|
+
line.contains("frida-agent", ignoreCase = true) ||
|
|
111
|
+
line.contains("frida-gadget", ignoreCase = true)
|
|
112
|
+
}
|
|
113
|
+
} catch (e: Exception) {
|
|
114
|
+
false
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Native Detection (JNI — More Resilient)
|
|
123
|
+
|
|
124
|
+
```c
|
|
125
|
+
// ✅ native-lib.cpp — harder to hook than Java methods
|
|
126
|
+
#include <jni.h>
|
|
127
|
+
#include <string>
|
|
128
|
+
#include <fstream>
|
|
129
|
+
#include <sys/socket.h>
|
|
130
|
+
#include <netinet/in.h>
|
|
131
|
+
#include <arpa/inet.h>
|
|
132
|
+
#include <unistd.h>
|
|
133
|
+
|
|
134
|
+
extern "C" JNIEXPORT jboolean JNICALL
|
|
135
|
+
Java_com_example_app_security_FridaDetectorNative_checkFridaPort(JNIEnv*, jobject) {
|
|
136
|
+
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
137
|
+
if (sock < 0) return JNI_FALSE;
|
|
138
|
+
|
|
139
|
+
struct sockaddr_in addr{};
|
|
140
|
+
addr.sin_family = AF_INET;
|
|
141
|
+
addr.sin_port = htons(27042);
|
|
142
|
+
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
|
|
143
|
+
|
|
144
|
+
struct timeval tv{};
|
|
145
|
+
tv.tv_sec = 0;
|
|
146
|
+
tv.tv_usec = 100000; // 100ms timeout
|
|
147
|
+
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
148
|
+
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
|
149
|
+
|
|
150
|
+
bool detected = connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0;
|
|
151
|
+
close(sock);
|
|
152
|
+
return detected ? JNI_TRUE : JNI_FALSE;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
extern "C" JNIEXPORT jboolean JNICALL
|
|
156
|
+
Java_com_example_app_security_FridaDetectorNative_checkProcessMaps(JNIEnv*, jobject) {
|
|
157
|
+
std::ifstream maps("/proc/self/maps");
|
|
158
|
+
std::string line;
|
|
159
|
+
while (std::getline(maps, line)) {
|
|
160
|
+
if (line.find("frida") != std::string::npos ||
|
|
161
|
+
line.find("gum-js-loop") != std::string::npos ||
|
|
162
|
+
line.find("frida-agent") != std::string::npos) {
|
|
163
|
+
return JNI_TRUE;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return JNI_FALSE;
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```kotlin
|
|
171
|
+
// ✅ Kotlin wrapper for native checks
|
|
172
|
+
class FridaDetectorNative {
|
|
173
|
+
external fun checkFridaPort(): Boolean
|
|
174
|
+
external fun checkProcessMaps(): Boolean
|
|
175
|
+
|
|
176
|
+
companion object {
|
|
177
|
+
init { System.loadLibrary("native-lib") }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Combined Security Check
|
|
185
|
+
|
|
186
|
+
```kotlin
|
|
187
|
+
// ✅ Unified check combining all detectors
|
|
188
|
+
class AppSecurityChecker @Inject constructor(
|
|
189
|
+
private val fridaDetector: FridaDetector,
|
|
190
|
+
private val hookDetector: HookDetector,
|
|
191
|
+
private val rootDetector: RootDetector
|
|
192
|
+
) {
|
|
193
|
+
data class SecurityReport(
|
|
194
|
+
val passed: Boolean,
|
|
195
|
+
val threats: List<String>
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
suspend fun runChecks(): SecurityReport = withContext(Dispatchers.IO) {
|
|
199
|
+
val threats = mutableListOf<String>()
|
|
200
|
+
|
|
201
|
+
val frida = fridaDetector.check()
|
|
202
|
+
val hooks = hookDetector.check()
|
|
203
|
+
val root = rootDetector.check()
|
|
204
|
+
|
|
205
|
+
if (frida.isDetected) threats.addAll(frida.signals.map { "frida:$it" })
|
|
206
|
+
if (hooks.isHooked) threats.addAll(hooks.signals.map { "hook:$it" })
|
|
207
|
+
if (root.isRooted) threats.addAll(root.signals.map { "root:$it" })
|
|
208
|
+
|
|
209
|
+
SecurityReport(passed = threats.isEmpty(), threats = threats)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Anti-Patterns
|
|
217
|
+
|
|
218
|
+
- Only checking port 27042 — Frida server can be configured to use any port
|
|
219
|
+
- Java-only detection — Frida can hook Java methods; native checks are more reliable
|
|
220
|
+
- Checking only at startup — Frida can be attached after the app starts
|
|
221
|
+
- Crashing the app on detection without explanation — show a clear message
|
|
222
|
+
- No server-side corroboration — client detection alone is bypassable
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Related Skills
|
|
227
|
+
- `hook-detection` — Xposed/LSPosed framework detection
|
|
228
|
+
- `root-detection` — rooted device detection
|
|
229
|
+
- `reverse-engineering-resistance` — broader anti-tampering strategy
|
|
230
|
+
- `obfuscation` — obfuscating detection logic
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hook-detection
|
|
3
|
+
description: >
|
|
4
|
+
Runtime hook detection for Android apps.
|
|
5
|
+
Load this skill when detecting Xposed framework, LSPosed, or other
|
|
6
|
+
hooking frameworks that intercept method calls, alter app behavior,
|
|
7
|
+
or bypass security checks at runtime.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Hook Detection
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Hooking frameworks like Xposed and LSPosed allow attackers to intercept and modify method calls at runtime — bypassing authentication, logging sensitive data, or altering app behavior. Hook detection identifies the presence of these frameworks before they can tamper with critical operations.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- **Detect early** — check before any sensitive operation, not just at startup
|
|
20
|
+
- **Multiple signals** — Xposed/LSPosed can hide some traces but rarely all
|
|
21
|
+
- **Combine with root detection** — hooking frameworks typically require root
|
|
22
|
+
- **Server-side validation** — client-side detection can be bypassed; pair with Play Integrity
|
|
23
|
+
- **Fail-secure** — block sensitive operations if hooks are detected
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Xposed / LSPosed Detection
|
|
28
|
+
|
|
29
|
+
```kotlin
|
|
30
|
+
class HookDetector @Inject constructor(
|
|
31
|
+
@ApplicationContext private val context: Context
|
|
32
|
+
) {
|
|
33
|
+
data class HookCheckResult(
|
|
34
|
+
val isHooked: Boolean,
|
|
35
|
+
val signals: List<String>
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
fun check(): HookCheckResult {
|
|
39
|
+
val signals = mutableListOf<String>()
|
|
40
|
+
|
|
41
|
+
if (checkXposedInstaller()) signals.add("xposed_installer")
|
|
42
|
+
if (checkXposedBridge()) signals.add("xposed_bridge")
|
|
43
|
+
if (checkStackTrace()) signals.add("xposed_stack_trace")
|
|
44
|
+
if (checkXposedFiles()) signals.add("xposed_files")
|
|
45
|
+
if (checkLoadedModules()) signals.add("loaded_modules")
|
|
46
|
+
|
|
47
|
+
return HookCheckResult(
|
|
48
|
+
isHooked = signals.isNotEmpty(),
|
|
49
|
+
signals = signals
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ✅ Check for Xposed installer apps
|
|
54
|
+
private fun checkXposedInstaller(): Boolean {
|
|
55
|
+
val xposedPackages = listOf(
|
|
56
|
+
"de.robv.android.xposed.installer",
|
|
57
|
+
"org.meowcat.edxposed.manager",
|
|
58
|
+
"io.github.lsposed.manager",
|
|
59
|
+
"com.android.xposed"
|
|
60
|
+
)
|
|
61
|
+
return xposedPackages.any { pkg ->
|
|
62
|
+
try {
|
|
63
|
+
context.packageManager.getPackageInfo(pkg, 0)
|
|
64
|
+
true
|
|
65
|
+
} catch (e: PackageManager.NameNotFoundException) {
|
|
66
|
+
false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ✅ Check if XposedBridge class is loadable
|
|
72
|
+
private fun checkXposedBridge(): Boolean {
|
|
73
|
+
return try {
|
|
74
|
+
Class.forName("de.robv.android.xposed.XposedBridge")
|
|
75
|
+
true
|
|
76
|
+
} catch (e: ClassNotFoundException) {
|
|
77
|
+
false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ✅ Check stack trace for Xposed method wrappers
|
|
82
|
+
private fun checkStackTrace(): Boolean {
|
|
83
|
+
return try {
|
|
84
|
+
throw Exception("stack_check")
|
|
85
|
+
} catch (e: Exception) {
|
|
86
|
+
val stackTrace = e.stackTrace.joinToString { it.className }
|
|
87
|
+
stackTrace.contains("XposedBridge") ||
|
|
88
|
+
stackTrace.contains("de.robv.android.xposed")
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ✅ Check for Xposed-related files on disk
|
|
93
|
+
private fun checkXposedFiles(): Boolean {
|
|
94
|
+
val xposedFiles = listOf(
|
|
95
|
+
"/system/lib/libxposed_art.so",
|
|
96
|
+
"/system/lib64/libxposed_art.so",
|
|
97
|
+
"/system/xposed.prop",
|
|
98
|
+
"/data/data/de.robv.android.xposed.installer"
|
|
99
|
+
)
|
|
100
|
+
return xposedFiles.any { File(it).exists() }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ✅ Check loaded libraries for hooking framework signatures
|
|
104
|
+
private fun checkLoadedModules(): Boolean {
|
|
105
|
+
return try {
|
|
106
|
+
val mapsFile = File("/proc/self/maps")
|
|
107
|
+
mapsFile.readLines().any { line ->
|
|
108
|
+
line.contains("XposedBridge") ||
|
|
109
|
+
line.contains("libxposed") ||
|
|
110
|
+
line.contains("edxp") ||
|
|
111
|
+
line.contains("lspd")
|
|
112
|
+
}
|
|
113
|
+
} catch (e: Exception) {
|
|
114
|
+
false
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Runtime Integrity Check
|
|
123
|
+
|
|
124
|
+
```kotlin
|
|
125
|
+
// ✅ Verify critical method hasn't been hooked by checking its declaring class
|
|
126
|
+
fun isMethodHooked(methodName: String, clazz: Class<*>): Boolean {
|
|
127
|
+
return try {
|
|
128
|
+
val method = clazz.getDeclaredMethod(methodName)
|
|
129
|
+
// If method is hooked, its class will be XposedBridge's handler class
|
|
130
|
+
method.declaringClass != clazz
|
|
131
|
+
} catch (e: Exception) {
|
|
132
|
+
false
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ✅ Self-check on a known method
|
|
137
|
+
fun checkOwnMethodIntegrity(): Boolean {
|
|
138
|
+
return try {
|
|
139
|
+
val method = MySecurityClass::class.java.getDeclaredMethod("checkOwnMethodIntegrity")
|
|
140
|
+
method.declaringClass == MySecurityClass::class.java
|
|
141
|
+
} catch (e: Exception) {
|
|
142
|
+
false
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Response Strategy
|
|
150
|
+
|
|
151
|
+
```kotlin
|
|
152
|
+
@HiltViewModel
|
|
153
|
+
class SecurityViewModel @Inject constructor(
|
|
154
|
+
private val hookDetector: HookDetector,
|
|
155
|
+
private val rootDetector: RootDetector
|
|
156
|
+
) : ViewModel() {
|
|
157
|
+
|
|
158
|
+
val securityStatus: StateFlow<SecurityStatus> = flow {
|
|
159
|
+
val hookResult = hookDetector.check()
|
|
160
|
+
val rootResult = rootDetector.check()
|
|
161
|
+
|
|
162
|
+
emit(SecurityStatus(
|
|
163
|
+
isHooked = hookResult.isHooked,
|
|
164
|
+
isRooted = rootResult.isRooted,
|
|
165
|
+
hookSignals = hookResult.signals,
|
|
166
|
+
rootSignals = rootResult.signals
|
|
167
|
+
))
|
|
168
|
+
}.stateIn(viewModelScope, SharingStarted.Eagerly, SecurityStatus())
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
data class SecurityStatus(
|
|
172
|
+
val isHooked: Boolean = false,
|
|
173
|
+
val isRooted: Boolean = false,
|
|
174
|
+
val hookSignals: List<String> = emptyList(),
|
|
175
|
+
val rootSignals: List<String> = emptyList()
|
|
176
|
+
) {
|
|
177
|
+
val isCompromised: Boolean get() = isHooked || isRooted
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Anti-Patterns
|
|
184
|
+
|
|
185
|
+
- Only checking for Xposed installer — LSPosed and EdXposed use different package names
|
|
186
|
+
- Not checking at runtime before sensitive ops — startup-only check can be bypassed
|
|
187
|
+
- Crashing without explanation — show a clear message before blocking
|
|
188
|
+
- Trusting client-side detection alone — combine with Play Integrity API for server validation
|
|
189
|
+
- Storing detection result in a predictable variable — hooked code can modify it
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Related Skills
|
|
194
|
+
- `frida-detection` — Frida-specific detection
|
|
195
|
+
- `root-detection` — detecting rooted devices
|
|
196
|
+
- `reverse-engineering-resistance` — broader anti-tampering measures
|
|
197
|
+
- `obfuscation` — making detection logic harder to analyze
|