react-native-device-defense 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/LICENSE +21 -0
- package/README.md +236 -0
- package/android/build.gradle +90 -0
- package/android/proguard-rules.pro +28 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/cpp/CMakeLists.txt +45 -0
- package/android/src/main/cpp/device-security.cpp +314 -0
- package/android/src/main/java/vn/osp/security/DebugDetection.kt +131 -0
- package/android/src/main/java/vn/osp/security/DeviceSecurityModule.kt +277 -0
- package/android/src/main/java/vn/osp/security/DeviceSecurityPackage.kt +58 -0
- package/android/src/main/java/vn/osp/security/EmulatorDetection.kt +204 -0
- package/android/src/main/java/vn/osp/security/HookDetection.kt +270 -0
- package/android/src/main/java/vn/osp/security/NativeSecurityCheck.kt +66 -0
- package/android/src/main/java/vn/osp/security/RootDetection.kt +349 -0
- package/lib/commonjs/NativeDeviceSecurity.js +9 -0
- package/lib/commonjs/NativeDeviceSecurity.js.map +1 -0
- package/lib/commonjs/api.js +213 -0
- package/lib/commonjs/api.js.map +1 -0
- package/lib/commonjs/components/SecurityBlockedScreen.js +177 -0
- package/lib/commonjs/components/SecurityBlockedScreen.js.map +1 -0
- package/lib/commonjs/components/index.js +13 -0
- package/lib/commonjs/components/index.js.map +1 -0
- package/lib/commonjs/hooks/index.js +13 -0
- package/lib/commonjs/hooks/index.js.map +1 -0
- package/lib/commonjs/hooks/useDeviceSecurity.js +81 -0
- package/lib/commonjs/hooks/useDeviceSecurity.js.map +1 -0
- package/lib/commonjs/index.js +48 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/NativeDeviceSecurity.js +3 -0
- package/lib/module/NativeDeviceSecurity.js.map +1 -0
- package/lib/module/api.js +206 -0
- package/lib/module/api.js.map +1 -0
- package/lib/module/components/SecurityBlockedScreen.js +169 -0
- package/lib/module/components/SecurityBlockedScreen.js.map +1 -0
- package/lib/module/components/index.js +2 -0
- package/lib/module/components/index.js.map +1 -0
- package/lib/module/hooks/index.js +2 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/useDeviceSecurity.js +73 -0
- package/lib/module/hooks/useDeviceSecurity.js.map +1 -0
- package/lib/module/index.js +21 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/NativeDeviceSecurity.d.ts +16 -0
- package/lib/typescript/NativeDeviceSecurity.d.ts.map +1 -0
- package/lib/typescript/api.d.ts +55 -0
- package/lib/typescript/api.d.ts.map +1 -0
- package/lib/typescript/components/SecurityBlockedScreen.d.ts +23 -0
- package/lib/typescript/components/SecurityBlockedScreen.d.ts.map +1 -0
- package/lib/typescript/components/index.d.ts +2 -0
- package/lib/typescript/components/index.d.ts.map +1 -0
- package/lib/typescript/hooks/index.d.ts +3 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -0
- package/lib/typescript/hooks/useDeviceSecurity.d.ts +7 -0
- package/lib/typescript/hooks/useDeviceSecurity.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +12 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +81 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/package.json +72 -0
- package/react-native-device-security.podspec +18 -0
- package/src/NativeDeviceSecurity.ts +33 -0
- package/src/api.ts +225 -0
- package/src/components/SecurityBlockedScreen.tsx +204 -0
- package/src/components/index.ts +1 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useDeviceSecurity.ts +91 -0
- package/src/index.ts +27 -0
- package/src/types.ts +95 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
package vn.osp.security
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.bridge.*
|
|
5
|
+
import org.json.JSONObject
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* React Native module for device security detection
|
|
9
|
+
*/
|
|
10
|
+
class DeviceSecurityModule(reactContext: ReactApplicationContext) :
|
|
11
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
12
|
+
|
|
13
|
+
private val rootDetection by lazy { RootDetection(reactContext) }
|
|
14
|
+
private val hookDetection by lazy { HookDetection(reactContext) }
|
|
15
|
+
private val debugDetection by lazy { DebugDetection(reactContext) }
|
|
16
|
+
private val emulatorDetection by lazy { EmulatorDetection(reactContext) }
|
|
17
|
+
|
|
18
|
+
override fun getName(): String = NAME
|
|
19
|
+
|
|
20
|
+
override fun getConstants(): Map<String, Any> {
|
|
21
|
+
val nativeLibLoaded = try {
|
|
22
|
+
NativeSecurityCheck.isNativeLibraryLoaded()
|
|
23
|
+
} catch (e: Exception) {
|
|
24
|
+
Log.e(NAME, "Error checking native library", e)
|
|
25
|
+
false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return mapOf(
|
|
29
|
+
"NAME" to NAME,
|
|
30
|
+
"NATIVE_LIBRARY_LOADED" to nativeLibLoaded
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if device is rooted (synchronous)
|
|
36
|
+
*/
|
|
37
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
38
|
+
fun isRooted(): Boolean {
|
|
39
|
+
return try {
|
|
40
|
+
val result = rootDetection.performDetection()
|
|
41
|
+
result.isRooted
|
|
42
|
+
} catch (e: Exception) {
|
|
43
|
+
Log.e(NAME, "Error checking root status", e)
|
|
44
|
+
false
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get detailed root detection result
|
|
50
|
+
*/
|
|
51
|
+
@ReactMethod
|
|
52
|
+
fun isRootedWithDetails(promise: Promise) {
|
|
53
|
+
try {
|
|
54
|
+
val result = rootDetection.performDetection()
|
|
55
|
+
val json = JSONObject().apply {
|
|
56
|
+
put("isRooted", result.isRooted)
|
|
57
|
+
put("hasRootBeerDetected", result.hasRootBeerDetected)
|
|
58
|
+
put("hasNativeRootDetected", result.hasNativeRootDetected)
|
|
59
|
+
put("hasDangerousBins", result.hasDangerousBins)
|
|
60
|
+
put("hasRootApps", result.hasRootApps)
|
|
61
|
+
put("hasSystemPropsModified", result.hasSystemPropsModified)
|
|
62
|
+
put("details", JSONObject(result.details))
|
|
63
|
+
}
|
|
64
|
+
promise.resolve(json.toString())
|
|
65
|
+
} catch (e: Exception) {
|
|
66
|
+
Log.e(NAME, "Error getting root details", e)
|
|
67
|
+
promise.reject("ROOT_CHECK_ERROR", e.message)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if Frida is present
|
|
73
|
+
*/
|
|
74
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
75
|
+
fun hasFrida(): Boolean {
|
|
76
|
+
return try {
|
|
77
|
+
hookDetection.hasFrida()
|
|
78
|
+
} catch (e: Exception) {
|
|
79
|
+
Log.e(NAME, "Error checking Frida", e)
|
|
80
|
+
false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if Xposed is present
|
|
86
|
+
*/
|
|
87
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
88
|
+
fun hasXposed(): Boolean {
|
|
89
|
+
return try {
|
|
90
|
+
hookDetection.hasXposed()
|
|
91
|
+
} catch (e: Exception) {
|
|
92
|
+
Log.e(NAME, "Error checking Xposed", e)
|
|
93
|
+
false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if Magisk is present
|
|
99
|
+
*/
|
|
100
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
101
|
+
fun hasMagisk(): Boolean {
|
|
102
|
+
return try {
|
|
103
|
+
hookDetection.hasMagisk()
|
|
104
|
+
} catch (e: Exception) {
|
|
105
|
+
Log.e(NAME, "Error checking Magisk", e)
|
|
106
|
+
false
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if app is debuggable
|
|
112
|
+
*/
|
|
113
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
114
|
+
fun isDebuggable(): Boolean {
|
|
115
|
+
return try {
|
|
116
|
+
debugDetection.isDebuggable()
|
|
117
|
+
} catch (e: Exception) {
|
|
118
|
+
Log.e(NAME, "Error checking debuggable", e)
|
|
119
|
+
false
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Check if running on emulator
|
|
125
|
+
*/
|
|
126
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
127
|
+
fun isEmulator(): Boolean {
|
|
128
|
+
return try {
|
|
129
|
+
emulatorDetection.isEmulator()
|
|
130
|
+
} catch (e: Exception) {
|
|
131
|
+
Log.e(NAME, "Error checking emulator", e)
|
|
132
|
+
false
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get comprehensive security status
|
|
138
|
+
*/
|
|
139
|
+
@ReactMethod
|
|
140
|
+
fun getSecurityStatus(promise: Promise) {
|
|
141
|
+
try {
|
|
142
|
+
val rootResult = rootDetection.performDetection()
|
|
143
|
+
val hasFrida = hookDetection.hasFrida()
|
|
144
|
+
val hasXposed = hookDetection.hasXposed()
|
|
145
|
+
val hasMagisk = hookDetection.hasMagisk()
|
|
146
|
+
val isDebuggable = debugDetection.isDebuggable()
|
|
147
|
+
val isEmulator = emulatorDetection.isEmulator()
|
|
148
|
+
|
|
149
|
+
val threats = mutableListOf<String>()
|
|
150
|
+
|
|
151
|
+
if (rootResult.isRooted) {
|
|
152
|
+
threats.add("root_detected")
|
|
153
|
+
if (rootResult.hasRootBeerDetected) threats.add("root_beer_detected")
|
|
154
|
+
if (rootResult.hasNativeRootDetected) threats.add("native_root_detected")
|
|
155
|
+
if (rootResult.hasDangerousBins) threats.add("dangerous_bins_detected")
|
|
156
|
+
if (rootResult.hasRootApps) threats.add("root_apps_detected")
|
|
157
|
+
if (rootResult.hasSystemPropsModified) threats.add("system_props_modified")
|
|
158
|
+
}
|
|
159
|
+
if (hasFrida) threats.add("frida_detected")
|
|
160
|
+
if (hasXposed) threats.add("xposed_detected")
|
|
161
|
+
if (hasMagisk) threats.add("magisk_detected")
|
|
162
|
+
if (isDebuggable) threats.add("debugger_detected")
|
|
163
|
+
if (isEmulator) threats.add("emulator_detected")
|
|
164
|
+
|
|
165
|
+
val securityStatus = JSONObject().apply {
|
|
166
|
+
put("isSecure", threats.isEmpty())
|
|
167
|
+
put("threats", org.json.JSONArray(threats))
|
|
168
|
+
put("isRooted", rootResult.isRooted)
|
|
169
|
+
put("hasRootBeerDetected", rootResult.hasRootBeerDetected)
|
|
170
|
+
put("hasNativeRootDetected", rootResult.hasNativeRootDetected)
|
|
171
|
+
put("hasDangerousBins", rootResult.hasDangerousBins)
|
|
172
|
+
put("hasRootApps", rootResult.hasRootApps)
|
|
173
|
+
put("hasSystemPropsModified", rootResult.hasSystemPropsModified)
|
|
174
|
+
put("hasFrida", hasFrida)
|
|
175
|
+
put("hasXposed", hasXposed)
|
|
176
|
+
put("hasMagisk", hasMagisk)
|
|
177
|
+
put("isDebuggable", isDebuggable)
|
|
178
|
+
put("isEmulator", isEmulator)
|
|
179
|
+
put("details", JSONObject().apply {
|
|
180
|
+
put("emulatorType", emulatorDetection.getEmulatorType())
|
|
181
|
+
val nativeLibLoaded = try {
|
|
182
|
+
NativeSecurityCheck.isNativeLibraryLoaded()
|
|
183
|
+
} catch (e: Exception) {
|
|
184
|
+
false
|
|
185
|
+
}
|
|
186
|
+
put("nativeLibraryLoaded", nativeLibLoaded)
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
promise.resolve(securityStatus.toString())
|
|
191
|
+
} catch (e: Exception) {
|
|
192
|
+
Log.e(NAME, "Error getting security status", e)
|
|
193
|
+
promise.reject("SECURITY_STATUS_ERROR", e.message)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Check if device is secure (no threats)
|
|
199
|
+
*/
|
|
200
|
+
@ReactMethod
|
|
201
|
+
fun isDeviceSecure(promise: Promise) {
|
|
202
|
+
try {
|
|
203
|
+
val rootResult = rootDetection.performDetection()
|
|
204
|
+
val isSecure = !rootResult.isRooted &&
|
|
205
|
+
!hookDetection.hasFrida() &&
|
|
206
|
+
!hookDetection.hasXposed() &&
|
|
207
|
+
!hookDetection.hasMagisk() &&
|
|
208
|
+
!debugDetection.isDebuggable() &&
|
|
209
|
+
!emulatorDetection.isEmulator()
|
|
210
|
+
|
|
211
|
+
promise.resolve(isSecure)
|
|
212
|
+
} catch (e: Exception) {
|
|
213
|
+
Log.e(NAME, "Error checking device security", e)
|
|
214
|
+
promise.reject("SECURITY_CHECK_ERROR", e.message)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Block app when security threat detected
|
|
220
|
+
* Shows an alert and exits the app
|
|
221
|
+
*/
|
|
222
|
+
@ReactMethod
|
|
223
|
+
fun blockOnSecurityThreat(
|
|
224
|
+
showAlert: Boolean,
|
|
225
|
+
alertTitle: String,
|
|
226
|
+
alertMessage: String,
|
|
227
|
+
alertButtonText: String
|
|
228
|
+
) {
|
|
229
|
+
try {
|
|
230
|
+
val rootResult = rootDetection.performDetection()
|
|
231
|
+
val hasFrida = hookDetection.hasFrida()
|
|
232
|
+
val hasXposed = hookDetection.hasXposed()
|
|
233
|
+
val hasMagisk = hookDetection.hasMagisk()
|
|
234
|
+
val isDebuggable = debugDetection.isDebuggable()
|
|
235
|
+
val isEmulator = emulatorDetection.isEmulator()
|
|
236
|
+
|
|
237
|
+
val hasThreat = rootResult.isRooted ||
|
|
238
|
+
hasFrida ||
|
|
239
|
+
hasXposed ||
|
|
240
|
+
hasMagisk ||
|
|
241
|
+
isDebuggable ||
|
|
242
|
+
isEmulator
|
|
243
|
+
|
|
244
|
+
if (hasThreat) {
|
|
245
|
+
Log.w(NAME, "Security threat detected, blocking app")
|
|
246
|
+
|
|
247
|
+
// Show alert if requested
|
|
248
|
+
if (showAlert) {
|
|
249
|
+
val activity = currentActivity
|
|
250
|
+
activity?.runOnUiThread {
|
|
251
|
+
android.app.AlertDialog.Builder(activity)
|
|
252
|
+
.setCancelable(false)
|
|
253
|
+
.setTitle(alertTitle)
|
|
254
|
+
.setMessage(alertMessage)
|
|
255
|
+
.setPositiveButton(alertButtonText) { _, _ ->
|
|
256
|
+
activity.finishAffinity()
|
|
257
|
+
System.exit(0)
|
|
258
|
+
}
|
|
259
|
+
.show()
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
// Exit without alert
|
|
263
|
+
currentActivity?.finishAffinity()
|
|
264
|
+
System.exit(0)
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
Log.i(NAME, "No security threat detected, app continues")
|
|
268
|
+
}
|
|
269
|
+
} catch (e: Exception) {
|
|
270
|
+
Log.e(NAME, "Error blocking on security threat", e)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
companion object {
|
|
275
|
+
const val NAME = "DeviceSecurity"
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
package vn.osp.security
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* React Native package for device security module
|
|
12
|
+
*/
|
|
13
|
+
class DeviceSecurityPackage : ReactPackage {
|
|
14
|
+
|
|
15
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
16
|
+
return listOf(DeviceSecurityModule(reactContext))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
20
|
+
return emptyList()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override fun getModuleInfoProvider(): ReactModuleInfoProvider {
|
|
24
|
+
return ReactModuleInfoProvider {
|
|
25
|
+
val moduleInfo: MutableMap<String, ReactModuleInfo> = java.util.HashMap()
|
|
26
|
+
|
|
27
|
+
val methods = listOf(
|
|
28
|
+
"isRooted",
|
|
29
|
+
"isRootedWithDetails",
|
|
30
|
+
"hasFrida",
|
|
31
|
+
"hasXposed",
|
|
32
|
+
"hasMagisk",
|
|
33
|
+
"isDebuggable",
|
|
34
|
+
"isEmulator",
|
|
35
|
+
"getSecurityStatus",
|
|
36
|
+
"isDeviceSecure",
|
|
37
|
+
"blockOnSecurityThreat"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
val constants = listOf("NAME", "NATIVE_LIBRARY_LOADED")
|
|
41
|
+
|
|
42
|
+
moduleInfo["DeviceSecurity"] = ReactModuleInfo(
|
|
43
|
+
"DeviceSecurity",
|
|
44
|
+
"DeviceSecurity",
|
|
45
|
+
false, // isTurboModule
|
|
46
|
+
false, // isCxxModule
|
|
47
|
+
true, // canOverrideExistingModule
|
|
48
|
+
methods,
|
|
49
|
+
constants,
|
|
50
|
+
false, // supportsEventEmitter
|
|
51
|
+
false, // needsDispatchEvent
|
|
52
|
+
true // hasConstants
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
moduleInfo
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
package vn.osp.security
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.pm.PackageManager
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.telephony.TelephonyManager
|
|
7
|
+
import java.io.File
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Detection for Android emulators
|
|
11
|
+
*/
|
|
12
|
+
class EmulatorDetection(private val context: Context) {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if running on emulator
|
|
16
|
+
*/
|
|
17
|
+
fun isEmulator(): Boolean {
|
|
18
|
+
return checkKnownEmulatorProperties() ||
|
|
19
|
+
checkEmulatorBuildProps() ||
|
|
20
|
+
checkEmulatorFiles() ||
|
|
21
|
+
checkEmulatorFeatures() ||
|
|
22
|
+
checkNetworkInterfaces() ||
|
|
23
|
+
checkCamera()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check for known emulator properties
|
|
28
|
+
*/
|
|
29
|
+
private fun checkKnownEmulatorProperties(): Boolean {
|
|
30
|
+
val emulatorProps = listOf(
|
|
31
|
+
"generic" to Build.BRAND,
|
|
32
|
+
"generic" to Build.PRODUCT,
|
|
33
|
+
"google_sdk" to Build.PRODUCT,
|
|
34
|
+
"sdk" to Build.PRODUCT,
|
|
35
|
+
"sdk_gphone" to Build.PRODUCT,
|
|
36
|
+
"sdk_gphone64_arm64" to Build.PRODUCT,
|
|
37
|
+
"sdk_gphone_x86" to Build.PRODUCT,
|
|
38
|
+
"sdk_gphone64_x86_64" to Build.PRODUCT,
|
|
39
|
+
"vbox86p" to Build.HARDWARE,
|
|
40
|
+
"vmos" to Build.HARDWARE,
|
|
41
|
+
"nox" to Build.HARDWARE,
|
|
42
|
+
"ttVM_x86" to Build.MANUFACTURER,
|
|
43
|
+
"Genymotion" to Build.MANUFACTURER,
|
|
44
|
+
"Genymotion" to Build.PRODUCT,
|
|
45
|
+
"Android SDK built for x86" to Build.MANUFACTURER,
|
|
46
|
+
"Android SDK built for x86_64" to Build.MANUFACTURER
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
for ((key, value) in emulatorProps) {
|
|
50
|
+
if (value.contains(key, ignoreCase = true)) {
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check emulator build properties
|
|
60
|
+
*/
|
|
61
|
+
private fun checkEmulatorBuildProps(): Boolean {
|
|
62
|
+
// Check device model
|
|
63
|
+
val model = Build.MODEL.lowercase()
|
|
64
|
+
if (model.contains("sdk") ||
|
|
65
|
+
model.contains("google_sdk") ||
|
|
66
|
+
model.contains("emulator") ||
|
|
67
|
+
model.contains("android sdk")) {
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check device
|
|
72
|
+
val device = Build.DEVICE.lowercase()
|
|
73
|
+
if (device.contains("generic") ||
|
|
74
|
+
device.contains("emulator") ||
|
|
75
|
+
device.contains("sdk")) {
|
|
76
|
+
return true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check fingerprint
|
|
80
|
+
val fingerprint = Build.FINGERPRINT.lowercase()
|
|
81
|
+
if (fingerprint.contains("generic") ||
|
|
82
|
+
fingerprint.contains("sdk_gphone")) {
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check hardware
|
|
87
|
+
val hardware = Build.HARDWARE.lowercase()
|
|
88
|
+
if (hardware.contains("goldfish") ||
|
|
89
|
+
hardware.contains("ranchu") ||
|
|
90
|
+
hardware.contains("vbox")) {
|
|
91
|
+
return true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check for emulator-specific files
|
|
99
|
+
*/
|
|
100
|
+
private fun checkEmulatorFiles(): Boolean {
|
|
101
|
+
val emulatorPaths = listOf(
|
|
102
|
+
"/dev/socket/qemud",
|
|
103
|
+
"/dev/qemu_pipe",
|
|
104
|
+
"/system/lib/libc_malloc_debug_qemu.so",
|
|
105
|
+
"/system/bin/qemu-props",
|
|
106
|
+
"/system/lib/libc_malloc_debug_qemu.so",
|
|
107
|
+
"/dev/socket/genyd",
|
|
108
|
+
"/dev/socket/baseband_genyd",
|
|
109
|
+
"/system/lib/libnim-dumo.so",
|
|
110
|
+
"/system/lib/libdum.so"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return emulatorPaths.any { File(it).exists() }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check for emulator features
|
|
118
|
+
*/
|
|
119
|
+
private fun checkEmulatorFeatures(): Boolean {
|
|
120
|
+
val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager
|
|
121
|
+
?: return false
|
|
122
|
+
|
|
123
|
+
// Check for invalid network operator
|
|
124
|
+
val networkOperator = telephonyManager.networkOperator
|
|
125
|
+
if (networkOperator == null || networkOperator == "000000000000000") {
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check for invalid subscriber ID
|
|
130
|
+
val subscriberId = telephonyManager.subscriberId
|
|
131
|
+
if (subscriberId == null || subscriberId == "000000000000000") {
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check for invalid IMEI/MEID
|
|
136
|
+
val deviceId = telephonyManager.deviceId
|
|
137
|
+
if (deviceId == null || deviceId == "000000000000000" || deviceId == "0") {
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check network interfaces for emulator indicators
|
|
146
|
+
*/
|
|
147
|
+
private fun checkNetworkInterfaces(): Boolean {
|
|
148
|
+
val emulatorInterfaces = listOf(
|
|
149
|
+
"vboxnet",
|
|
150
|
+
"vnic",
|
|
151
|
+
"tun",
|
|
152
|
+
"tap"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
val interfaces = java.net.NetworkInterface.getNetworkInterfaces()
|
|
157
|
+
for (networkInterface in interfaces.toList()) {
|
|
158
|
+
val name = networkInterface.name.lowercase()
|
|
159
|
+
for (emulatorInterface in emulatorInterfaces) {
|
|
160
|
+
if (name.contains(emulatorInterface)) {
|
|
161
|
+
return true
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (e: Exception) {
|
|
166
|
+
// Ignore errors
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check camera availability (emulators often lack cameras)
|
|
174
|
+
* Note: This is just a hint, many real devices also lack cameras
|
|
175
|
+
*/
|
|
176
|
+
private fun checkCamera(): Boolean {
|
|
177
|
+
return try {
|
|
178
|
+
// Check if device has camera hardware feature
|
|
179
|
+
val hasCamera = context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
|
180
|
+
|| context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)
|
|
181
|
+
|
|
182
|
+
// If no camera feature, might be emulator (but not guaranteed)
|
|
183
|
+
!hasCamera
|
|
184
|
+
} catch (e: Exception) {
|
|
185
|
+
// On error, assume it's not an emulator based on camera check
|
|
186
|
+
false
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get emulator type if detected
|
|
192
|
+
*/
|
|
193
|
+
fun getEmulatorType(): String? {
|
|
194
|
+
return when {
|
|
195
|
+
Build.HARDWARE.contains("ranchu") -> "QEMU/KVM"
|
|
196
|
+
Build.HARDWARE.contains("vbox") -> "VirtualBox"
|
|
197
|
+
Build.MANUFACTURER.contains("Genymotion") -> "Genymotion"
|
|
198
|
+
Build.PRODUCT.contains("nox") -> "Nox"
|
|
199
|
+
Build.HARDWARE.contains("vmos") -> "VMOS"
|
|
200
|
+
Build.BRAND.contains("google") && Build.PRODUCT.contains("sdk") -> "Android Emulator"
|
|
201
|
+
else -> "Unknown"
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|