
بروتوكول BLE مُهندس عكسيًا لـ CMF Watch Pro 2، مع توثيق تخطيط GATT، إطارات أوامر مشفرة بـ AES-128-CBC، مصافحة توثيق، ومزامنة بيانات الصحة لتطوير تطبيق رفيق بديل.
غير رسمي. يصف هذا المستند بروتوكول البلوتوث منخفض الطاقة (BLE) الخاص بساعة CMF Watch Pro 2 (CMF by Nothing)، الذي أُعيد بناؤه عبر الهندسة العكسية لتطبيق مصاحب بديل. لا يرتبط هذا المستند بـ Nothing/CMF ولا يحظى بتأييدها. استخدمه على مسؤوليتك الخاصة.
جميع الأعداد الصحيحة متعددة البايت في ترويسة الإطار ورموز التشغيل تكون بالترتيب الكبير (big-endian). الأعداد الصحيحة داخل
حمولات الأوامر تكون بالترتيب الصغير (little-endian) ما لم يُذكر خلاف ذلك (وهذا يعكس برنامج الساعة الثابت) —
انتبه للاستثناءات (GOALS_SET، GPS_PUSH، إزاحة/طول النقل الجماعي تكون بالترتيب الكبير).
كل ادعاء غير بديهي أدناه مُوسوم بكيفية إثباته:
حيثما يصحح قسم لاحق قسمًا سابقًا، يُحتفظ بالنص السابق مع مؤشر بدلًا من حذفه — معرفة القراءات التي جُرّبت ودُحضت توفر على الشخص التالي نفس الالتفاف.
جهاز الاختبار لجميع الالتقاطات: CMF Watch Pro 2-5485، البرنامج الثابت 1.0.0.73، الرقم التسلسلي CI04102520008192،
MCU Actions ATS3089C (Cortex-M4)، الشاشة 466×360.
الهاتف هو عميل GATT؛ الساعة هي الطرف المحيط، وتُعلن عن نفسها باسم CMF Watch Pro 2-XXXX
(4 أحرف سداسية عشرية).
| الغرض | الخدمة | الخاصية | الخصائص |
|---|---|---|---|
| كتابة الأوامر | 0000fff0-0000-1000-8000-00805f9b34fb | 0000fff2-… | كتابة |
| إشعار الأوامر | 0000fff0-… | 0000fff1-… | إشعار |
| كتابة الصدفة (AT) | — | 77d4ff01-2fe2-2334-0d35-9ccd078f529c | كتابة |
| إشعار الصدفة (AT) | — | 77d4ff02-… | إشعار |
| كتابة البيانات الجماعية | — | 02f00000-0000-0000-0000-00000000ffe1 | كتابة |
| إشعار البيانات الجماعية | — | 02f00000-…ffe2 | إشعار |
فعّل الإشعارات بكتابة 01 00 إلى كل CCCD (00002902-…). قناة الأوامر
(fff1/fff2) تحمل البروتوكول المؤطَّر أدناه. قناة الصدفة (77d4…) تحمل نصًا
عاديًا بنمط AT (مثل AT GETSECRET؛ انظر §14). قناة البيانات (02f0…) تحمل كتلًا ثنائية
كبيرة (واجهة الساعة، البرنامج الثابت، AGPS)، تُنسَّق عبر رموز تشغيل تحكم على قناة الأوامر.
معرّفات الخدمة UUID — تُعلن الساعة عن حوالي 10 خدمات أساسية. جرى تعدادها على وحدة حقيقية:
0xfff0 (الأوامر)، 0x180f (البطارية)، 0x180a (معلومات الجهاز)، 0xefe7، 0xffd0،
02f00000-…ffe0 و02f00000-…fe00 (البيانات)، 77d4e67c-2fe2-2334-0d35-9ccd078f529c (الصدفة /
الاقتران)، e49a3001-f69a-11e8-8eb2-f2801f1b9fd1، f48a23c0-f69a-11e8-8eb2-f2801f1b9fd1.
⚠️ معرّف خدمة الصدفة هو
77d4e67c-…، وليس77d4ff00-…. افترضت المراجعات السابقة لهذا المستند أن الخدمة تشترك في بادئةff00لخصائصها (77d4ff01/77d4ff02، §14) — لكنها لا تفعل، على الأقل على الوحدة التي فُحصت عليها (نتيجة من freethinkel/fmc، انظر §المصادر). معرّفات الخصائص لم تتغير. لم يُتحقق مما إذا كان77d4e67cثابتًا عبر الوحدات — عدِّد بدلًا من الترميز الثابت.
🌐 ملاحظة حول Web Bluetooth. لا يكتشف Chromium إلا الخدمات التي أدرجتها الصفحة في
optionalServices، حتى عند استدعاءgetPrimaryServices()غير المفلتر — صفحة تُدرج 3 خدمات ترى 3، بينما يُظهرchrome://bluetooth-internals(طبقة C++ الخاصة بـ Chrome، غير مُقيَّدة) كل الـ 10. إذا كتبت عميل متصفح، أدرج كل معرّف UUID أعلاه مسبقًا أو سيفشل الاقتران مع خدمات موجودة بوضوح. لا يوجد Web Bluetooth في Firefox/Safari؛ يتطلب إيماءة مستخدم + HTTPS/localhost.
✅ جرى تشغيل جلسة حقيقية كاملة على قناة الأوامر الواحدة — خلال التقاط استخدام كثيف لمدة 160 ثانية لم يكن هناك أي حركة مرور على قنوات البيانات/البرنامج الثابت أو الصدفة باستثناء أثناء نقل OTA/واجهة ساعة صريح.
0xF5)كل رسالة على قناة الأوامر مغلفة في إطار واحد أو أكثر بترويسة من 11 بايت:``` +------+-----------+--------+-------------+-------------+--------+-------------------+ | 0xF5 | chunkLen | cmd1 | chunkCount | chunkIndex | cmd2 | chunk bytes … | | 1 B | 2 B (BE) | 2 B BE | 2 B BE | 2 B BE | 2 B BE | chunkLen bytes | +------+-----------+--------+-------------+-------------+--------+-------------------+ __________________________ 11-byte header ____________________________/
- يشكّل `cmd1`/`cmd2` معًا **رمز العملية** (انظر §6). 🔎 تم التحقق منه مقابل مُنشئ الإطارات في التطبيق الرسمي
(`C6117b.m30831g`).
- `chunkCount` = إجمالي الأجزاء لهذا الأمر؛ `chunkIndex` يبدأ من **1**.
- `chunkLen` = عدد بايتات `chunk` في هذا الإطار.
- قد يتم تجزئة كتابة BLE واحدة بواسطة MTU للرابط؛ يقوم المستقبِل بتخزين البايتات الخام مؤقتًا و
إعادة استخراج الإطارات الكاملة. يتم تقسيم الحمولات الكبيرة إلى أجزاء متعددة (نفس `cmd1/cmd2`،
مع زيادة `chunkIndex`) وإعادة تجميعها بالترتيب.
### اصطلاح رمز العملية (✅ تم تأكيده على الشبكة)
- `cmd1 = 0xFFFF`: `cmd2` في `0x80xx`/`0x90xx` = من الهاتف إلى الساعة (طلب/تعيين)؛ `0x00xx`/`0xa0xx` =
من الساعة إلى الهاتف (رد). تتطابق الأزواج بالبايت المنخفض (`0x9055`↔`0xa055`، `0x8051`↔`0x0051`).
- `cmd1` خاص بالميزة: لاحقة `cmd2` = `0x0001` **SET**، `0x0002` **GET**، `0x0003` **ACK**.
### جسم الجزء
لكل جزء، الجسم هو `payloadPiece ‖ CRC32_LE(payloadPiece)` (CRC من 4 بايتات، بترتيب البايت الأقل أهمية،
zlib/IEEE). إذا كان الأمر **مشفّرًا** (انظر §3)، يتم بعد ذلك تشفير `payloadPiece ‖ CRC` بالكامل
باستخدام AES-128-CBC/PKCS7 ويصبح هذا النص المشفر هو `chunk` للإطار.
**خصوصية النص الصريح:** لرموز العمليات غير المشفرة، *تحسب* الساعة CRC من 4 بايتات في `chunkLen` ولكنها
**لا** ترسله. لذا عند فك تشفير إطار غير مشفر، يكون طول البيانات الفعلي هو `chunkLen − 4`.
(تحمل الإطارات المشفرة CRC داخل النص المشفر كالمعتاد.)
حجم الأجزاء (بحيث تقع الأجزاء المشفرة على حدود كتل AES)، مع `maxWrite = mtu − 3`:
- مشفّر: `floor((maxWrite − 11) / 16) * 16 − 4 − 1`
- غير مشفّر: `maxWrite − 11 − 4 − 1`
✅ كانت جميع قيم `chunkLen` المرصودة للإطارات المشفرة مضاعفات للعدد 16 (محاذاة الكتل صحيحة).
---
## 3. الأوليات التشفيرية
- **AES-128-CBC** مع حشو **PKCS7** و**IV ثابت** (من البرنامج الثابت
`CmfCharacteristic.AES_IV`):
`50 51 52 53 54 55 56 57 60 61 62 63 64 65 66 5A`.
- **CRC32** (zlib/IEEE)، يُرسل كـ 4 بايتات بترتيب البايت الأقل أهمية.
- **SHA-256** على تسلسل الأجزاء.
اشتقاق المفتاح:```
authkey = SHA256( rnd1 ‖ rnd2 ‖ secret )[0..16] // persisted across sessions
sessionKey = SHA256( nonce ‖ authkey )[0..16] // per connection
secret = سر الجهاز (16 بايت) (يمكن الحصول عليه من الساعة عبر أمر الصدفة
AT GETSECRET → GETSECRET:<32-hex>,OK).rnd1 = 16 بايت عشوائي يختارها الهاتف؛ rnd2 = 16 بايت عشوائي من الساعة.nonce = بايتات من رد nonce الخاص بالساعة.بعد تعيين المفتاح، جميع إطارات قناة الأوامر مشفرة بـ AES باستثناء رموز التشغيل النصية العادية المدرجة في §5.
✅ تم التحقق من كلا الاشتقاقين: تم استرداد authkey من ntwatch.db لهاتف مُجذّر وتطابق مع
القيمة المشتقة من rnd1/rnd2/secret الملتقطة؛ وتم إعادة إنتاج sessionKey من nonce ملتقط
يفك تشفير الإطارات الحية.
يشارك مسارا الدخول نفس ذيل nonce/confirm.