react-native-device-integrity 0.3.1 → 0.3.2
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/README.md
CHANGED
|
@@ -384,7 +384,7 @@ The [`with-frida-gadget`](example/plugins/with-frida-gadget.js) config plugin do
|
|
|
384
384
|
|
|
385
385
|
Observed on an iPhone XS Max (A12, iOS 18.7.10, stock): `status: compromised` with a single signal, **`hooking_libraries`**. Launched from Xcode with the debugger attached, the same build also reports `debugger_attached` and `hooking_dyld_insert` (Xcode injects its view-debugging library) — expected, not false positives.
|
|
386
386
|
|
|
387
|
-
Observed on a rooted OnePlus Nord 3 (CPH2493, OxygenOS 15, unlocked bootloader, Magisk 31, no hiding): `status: compromised` with **`bootloader_unlocked`**, **`root_su_binary`**, **`root_management_apps`** and **`root_magisk_files`**, in about
|
|
387
|
+
Observed on a rooted OnePlus Nord 3 (CPH2493, OxygenOS 15, unlocked bootloader, Magisk 31, no hiding): `status: compromised` with **`bootloader_unlocked`**, **`root_su_binary`**, **`root_management_apps`** and **`root_magisk_files`**, in about 40 ms (release build). With Zygisk + DenyList hiding Magisk from the app, `root_su_binary` and `root_magisk_files` disappear (Magisk removes `su` and its mounts from the app's view), but **`bootloader_unlocked`** and **`root_management_apps`** still fire, so the status stays `compromised`. Hiding the Magisk app (randomised package name) or a `resetprop` module would remove those too — pair with server-side attestation for high-value flows.
|
|
388
388
|
|
|
389
389
|
The same phone, built with each [`bootloaderUnlocked`](#unlocked-bootloaders) value:
|
|
390
390
|
|
|
@@ -7,6 +7,7 @@ import android.os.Debug
|
|
|
7
7
|
import java.io.BufferedReader
|
|
8
8
|
import java.io.File
|
|
9
9
|
import java.io.FileReader
|
|
10
|
+
import java.lang.reflect.Method
|
|
10
11
|
import java.util.concurrent.FutureTask
|
|
11
12
|
import java.util.concurrent.TimeUnit
|
|
12
13
|
|
|
@@ -338,14 +339,23 @@ internal class IntegrityChecks(private val context: Context) {
|
|
|
338
339
|
}
|
|
339
340
|
|
|
340
341
|
/**
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
342
|
+
* A system property, read in-process through `android.os.SystemProperties`
|
|
343
|
+
* (microseconds). If that hidden API is unavailable, falls back to one
|
|
344
|
+
* `getprop` dump per run — spawning a process costs ~40–100 ms from an app.
|
|
345
|
+
* Null when neither can be read; a missing property is "".
|
|
344
346
|
*/
|
|
345
|
-
private
|
|
347
|
+
private fun readSystemProperty(name: String): String? {
|
|
348
|
+
systemPropertiesGet?.let { get ->
|
|
349
|
+
try {
|
|
350
|
+
return get.invoke(null, name) as? String ?: ""
|
|
351
|
+
} catch (_: Throwable) {
|
|
352
|
+
// fall through to the getprop dump
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return systemPropertiesDump?.let { it[name].orEmpty() }
|
|
356
|
+
}
|
|
346
357
|
|
|
347
|
-
private
|
|
348
|
-
systemProperties?.let { it[name].orEmpty() }
|
|
358
|
+
private val systemPropertiesDump: Map<String, String>? by lazy { readAllSystemProperties() }
|
|
349
359
|
|
|
350
360
|
private fun readAllSystemProperties(): Map<String, String>? {
|
|
351
361
|
var process: Process? = null
|
|
@@ -385,13 +395,17 @@ internal class IntegrityChecks(private val context: Context) {
|
|
|
385
395
|
BufferedReader(FileReader("/proc/mounts")).use { reader ->
|
|
386
396
|
var line: String?
|
|
387
397
|
while (reader.readLine().also { line = it } != null) {
|
|
388
|
-
|
|
398
|
+
// "<source> <mount point> <fstype> <options> <dump> <pass>"; the kernel
|
|
399
|
+
// separates fields with single spaces and escapes spaces inside them.
|
|
400
|
+
val parts = line!!.split(' ', limit = 5)
|
|
389
401
|
if (parts.size < 4) {
|
|
390
402
|
continue
|
|
391
403
|
}
|
|
392
404
|
val mountPoint = parts[1]
|
|
393
|
-
|
|
394
|
-
|
|
405
|
+
if (mountPoint !in targets) {
|
|
406
|
+
continue
|
|
407
|
+
}
|
|
408
|
+
if ("rw" in parts[3].split(',')) {
|
|
395
409
|
return Signal(
|
|
396
410
|
id = "root_rw_system",
|
|
397
411
|
category = "root",
|
|
@@ -504,18 +518,15 @@ internal class IntegrityChecks(private val context: Context) {
|
|
|
504
518
|
|
|
505
519
|
// --- helpers ---
|
|
506
520
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
return false
|
|
518
|
-
}
|
|
521
|
+
/**
|
|
522
|
+
* `/proc/self/maps`, read once per run and shared by the hooking checks. The
|
|
523
|
+
* kernel regenerates it on every read, and a React Native process maps
|
|
524
|
+
* thousands of regions. A failed read throws again on the next access.
|
|
525
|
+
*/
|
|
526
|
+
private val selfMaps: String by lazy { File("/proc/self/maps").readText() }
|
|
527
|
+
|
|
528
|
+
private fun mapsContainAny(markers: List<String>): Boolean =
|
|
529
|
+
markers.any { marker -> selfMaps.contains(marker) }
|
|
519
530
|
|
|
520
531
|
private fun taskCommContains(name: String): Boolean {
|
|
521
532
|
val taskDir = File("/proc/self/task")
|
|
@@ -536,4 +547,15 @@ internal class IntegrityChecks(private val context: Context) {
|
|
|
536
547
|
}
|
|
537
548
|
return false
|
|
538
549
|
}
|
|
550
|
+
|
|
551
|
+
companion object {
|
|
552
|
+
/** `android.os.SystemProperties.get(String)`, or null if hidden-API access is blocked. */
|
|
553
|
+
private val systemPropertiesGet: Method? by lazy {
|
|
554
|
+
try {
|
|
555
|
+
Class.forName("android.os.SystemProperties").getMethod("get", String::class.java)
|
|
556
|
+
} catch (_: Throwable) {
|
|
557
|
+
null
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
539
561
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-device-integrity",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Jailbreak, root, hooking, debugger and emulator detection for React Native and Expo",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|