Stock firmware feature map (fw1, dumps/w41h1_dump1.bin)
Synthesis of the six raw analysis notes (analysis/cloud-jcmd-tls.md, ota-handler.md,
local-attack-surface.md, matter-stack.md, kv-sysdata-layout.md, strings-census.md).
Claims below were cross-checked across the notes; where they disagreed, the note with
byte-exact recomputation or pointer-pool evidence wins. Addresses are XIP runtime
addresses unless tagged as file/flash offsets.
BASE CORRECTION (load-bearing for all xref work): fw1 executes at XIP base
0x9b6e0000(= flash base0x9b6d0000+ slot offset0x10000), with a second alias window at0x9b000000used by the Hisense app core (AT handlers, BLE provisioning). The previously recorded base0x9b7d0000is wrong: string-pointer pools only resolve at0x9b6e0000(verified on ~20 independent pools in three analyses). file offset = XIP −0x9b6e0000.fw2 (flash
0x190000) is NOT the same firmware as fw1. It is an older factory MP-test build: versionS1798.MP_TEST_VERSION_SE, builtAug 15 2023, no Matter / CHIP strings at all, MP-only commands (ATSGGPIO test,HS_OTA_TEST), and a hardcoded HOTA URLhttp://download.hismarttv.com/Content/WifiDeviceVersionFile/154003147849100920.bin. Nearly every 16-byte block differs from fw1; FWHS size fields differ; both slots carry serial 100.
Identity / build IDs
| Item | Value | Evidence |
|---|---|---|
Module SW version (version_mp) |
S2292.6.02.08.MK |
fw1 file 0x1242d6 |
| fw1 build date | May 26 2025 (2025/05/26-14:16:37) |
fw1 file 0x125b88 / 0x13366b |
| Bootloader build | Apr 17 2023 15:19:48 |
sysdata off 0xbc98 |
| Matter base | connectedhomeip thermostat example, SDK drop matter_1102 |
source paths /home/hisense/lyd/matter_1102/ambz2_matter/... at fw1 file 0x1481fc+ |
| Default softAP SSID | HIS-8888 |
fw1 file 0x866, string 0x9b80a134 |
version_ext is a second version concept: the A/C mainboard MCU version, fetched over
the RS-485 bus.
Cloud channel: jcmd v5 over pinned TLS
- Long-lived HTTPS (TLS 443) chunked-transfer stream to
ac-eu-link.hijuconn.com(host composed at runtime as<env>-link.hijuconn.com; host and port stored in the cloud config record at flash0x3DB061/0x3DB094). RX chunks feedcloud_handle_package→jcmd_parse_msg. - TLS verification is pinned-CA
VERIFY_REQUIRED.check_crt_methodpicks: 1 = built-in CA (fresh units), 2/3 = cloud-fetched pemA/pemB slots (A/B ping-pong, stored AES-encrypted + base64 in flash). All three end atmbedtls_ssl_conf_authmode(REQUIRED)with a CA chain configured. Method 0 (VERIFY_NONE) is unreachable dead code: it needs RAM byte0x10009050 == 6, and no writer of value 6 exists in fw1. MITM of the gateway link is not possible without the pinned CA’s private key (doc 08 reached the same verdict from the network side, see 08-cloud-mitm-feasibility.md). - CA rollover without bricking: on handshake failure with method 1 (or a state
flag at connect),
pem_updatePOSTs signed JSON totest-clife-eu-gateway.hijuconn.com/crt/fetchLastestGaand stores fresh CAs into the two pem slots. - jcmd v5 wire format:
"HKEYV2-" base64( AES-256-CBC( json ‖ ascii-hex(HMAC-SHA256(dkey, json)) ) ) "\n". AES key = the 32-byte per-device dkey, IV = first 16 bytes of the dkey, zero-padded. The v5 flag (0x1000c124) is set by theCMD_SENDENCKEYprovisioning handler (44-char base64 payload = 32 raw bytes); before key delivery, jcmd falls back to plaintext JSON. dkey persists in the cloud config record (flash0x3DB1B9) and is unpacked at boot through a “hi-aes” layer. - No device-initiated version poll. The only REST path in fw1 is
/crt/fetchLastestGa(CA fetch). The device passively reports devinfo/version; the cloud decides and pushesCLOUD_JSON_UPDATE(version, URL, SHA-256). Firmware file IDs are only visible to the phone app / cloud.
OTA image acceptance (fully decoded, byte-exact verified)
There are two HMAC relationships, both keyed by the same partition-table hash_key
(flash 0x140 for FW1, 0x180 for FW2; the SDK default 0001…1e5f, cleartext in the
partition table). A re-signing recipe must reproduce both, in this order.
- (a) Manifest signature. FWHS
+0x00(32 B) = HMAC-SHA256 over the 0x60-byte manifestimg[0xE0:0x140]. It covers the serial and sizes, not the image body. Enforced by the OTA code and used for slot validity. -
(b) Inner image HMAC, one per sub-image. A hash-then-trailer at a content-derived offset, so fixed-offset searching does not find it. Enforced by the bootloader, not by the OTA code. The general rule, for sub-image
iwhose header sits at offsetH(the first is atH = 0xE0):S = u32le(img[H]) # segment SIZE END = H + 0x60 + S # the trailer offset START = 0 if i == 0 else H img[END : END+0x20] == HMAC-SHA256(hash_key, img[START:END]) next header = H + u32le(img[H+4]) # RELATIVE; 0xFFFFFFFF terminates the chain⚠️ Naming trap.
u32le(img[0xE0])is the SIZE fieldS. It is notnext_img.next_imglives atH+4and is a relative offset. An earlier revision of this section and ofota-release.shcalledimg[0xE0]“next_img”; the code read the right offset so it worked, but generalising the wrong name to sub-images 1..N puts the trailer at0x4060instead of0x3d80on a real image, and every check then reports MISMATCH.Because sub-image 0’s span starts at image offset 0, it is the only one that covers the serial at
+0xF4and the+0x00signature from (a), so a serial patch stales exactly that one trailer. Verified on 37 real files: every image has 3 sub-images. Custom builds are0xE0 / 0x4000 / 0xF8000. Stock has two layouts in the wild,0xE0 / 0x8000 / 0x114000(office, 0x1589c0 bytes) and0xE0 / 0x8000 / 0x120000(kitchen /w41h1_dump1, 0x16ab40 bytes), so walk the chain, never hardcode offsets. The last trailer does not land at EOF:elf2binappends 0 or 0x20 bytes of0x87padding (both occur across the archive) before the 4-byte byte-sum, so anEND+0x20 == lenassert rejects most genuine images.⚠️ Omitting (b) is what bricked a unit on 2026-07-21 (issue #75): every host-side check still passes, but
boot_loadprints"Hash Result Incorrect!", clears the flash Quad-Enable bit, and hangs, with no fall-back to the other slot. Verified across 29 real images: (b) holds on every genuine image and failed on exactly the four payloads the oldrevert --repackageproduced. - Serial at manifest
+0xF4(LE u32, 100 on both stock slots): the bootloader boots the signature-valid slot with the highest serial. - Transport checksum trailer: last 4 bytes = u32 LE byte-sum of everything before
it (appended by
checksum.linux). Enforced by the OTA code (USE_CHECKSUM=1) against the backed-up real first 32 bytes. fw1’s in-slot length:0x16ab40+ 4. - The device never computes the signature:
update_ota_signaturereplaces the first 32 bytes with0xFFduring download and writes the saved bytes back after the checksum passes. The served file must already carry a valid HMAC. - No cryptographic signature anywhere. The
+0x20“pubkey” field is the SDK example key on every image; secure boot is not enabled. The download itself is plain HTTP with a cloud-supplied SHA-256 (integrity only). See 07-firmware-and-ota.md. - Delivery paths: cloud push, UART console
HOTA=<url>,<sha-256>andATWO(raw TCP, no auth), Matter OTA.DOTA=updates the A/C mainboard over RS-485, not the module.
Local network surface
Exhaustive bind() census of fw1 (every call site resolved; only two listen()
sites exist in the image):
| Listener | Port | When up | Notes |
|---|---|---|---|
| softAP provisioning server | TCP 5020 | pairing mode only | AP HIS-8888; plaintext jcmd fallback pre-dkey: set Wi-Fi creds, dkey, country code, cloud env |
| LAN discovery | UDP 5030 | always (cloud thread select loop) | scans datagram for HS at any offset; reply leaks identity fields unauthenticated; 0x65/0x66-gated path with 60 s penalty [OPEN] |
| self-advertisement beacon | UDP 5010 (dest) | ~60 s periodic | module beacons to the app listening port |
| Matter | UDP 5540 + mDNS 5353 | always / window-gated | IPv6; commissionable advertisement only while the window is open |
| iperf test servers | TCP/UDP 5001 (default) | while ATWT/ATWU runs |
console-triggered, transient |
- No telnet, SSH, MQTT, or hidden listener anywhere; no hardcoded-credential
network backdoor (the
abc/abcdefgcommands are inert UART test stubs). - Both AT consoles are UART-only (case open): the AmebaZ2 SDK
log_serviceset plus a Hisense custom table. Notable:RKEY/WKEY(read/write dkey),WPEM/RPEM(TLS PEM incl. device key),WENV(cloud host/port override),HOTA(OTA from arbitrary URL),ATSE(arbitrary MMIO write),ATSC/ATSR(clear / recover OTA slot signature),ATWO,R485/RS485(bus injection),WMAC/WWID/WMOD(identity/mode writes).
Matter stack
- connectedhomeip AmebaZ2 thermostat example (CHIP ~1.0-era), Hisense-customized. Test credentials: passcode 20202021, discriminator 3840 (02-matter-local-control.md).
- ep0 (root): Descriptor, AccessControl, BasicInformation, GeneralCommissioning, NetworkCommissioning, GeneralDiagnostics, WiFiNetworkDiagnostics, AdministratorCommissioning, OperationalCredentials, GroupKeyManagement, OtaSoftwareUpdateRequestor (0x2A), UserDirectedCommissioning.
- ep1 (app): Identify, Groups, Thermostat 0x0201 bridged to the RS-485 bus
(
hisense_to_matter/matter_uart_process; bus protocol in 03-rs485-ac-protocol.md). - OTA Requestor has no requestor-side validation: stock
ProcessHeaderonly decodes the header fortotalFileBytes; no vid/pid or version check gates the download.ConfirmCurrentImageruns after rebooting into the new image. All vid/pid/version filtering is provider-side, so whoever runs the provider sets the rules (this is both the conversion path and a revert path). - Commissioning window auto-opens for 900 s at boot whenever no fabric exists
(
OpenBasicCommissioningWindow(900, kAllSupported)); with a fabric, BLE advertising is disabled. A fabric admin can re-open the window via AdministratorCommissioning with the same test passcode. - Cloud/Matter coexistence gate: at cloud connect, if the configured cloud port is
not
"443", the firmware prints “only matter running” and skips the cloud stack entirely. With the stock endpoint (443) both run concurrently off the same HS-core.
Persistent storage map
Offsets and sizes only; no values. Full record layouts in
analysis/kv-sysdata-layout.md.
| Flash range | Content |
|---|---|
0x0–0x1000 |
boot-image FWHS header + partition entries (hash_keys at +0x140/+0x180) |
0x1000–0x2000 |
SoC sysdata sector; SPI flash calibration record at 0x1060 (self-heals if erased) |
0x4000–0xC000 |
bootloader |
0x10000–0x180000 |
FW1 slot (running stock app, serial 100) |
0x190000–0x260000 |
FW2 slot (factory MP-test image, serial 100) |
0x2FF000–0x300000 |
stock Wi-Fi fast-reconnect profile (struct wlan_fast_reconnect, VERSION_V01) |
0x3DA000 |
provisioning event log (52 B ASCII entries) |
0x3DB000 |
cloud config record (476 B, tag 0x1989): host, port, wifi_id, dkey |
0x3DD000 |
device-identity record (144 B, tag 0x1989): model, uuid, key material |
0x3E0000–0x3EA000 |
Matter DCT2 (KVS2, fabric credentials; 10 × 4 KB modules) |
0x3ED000–0x3FA000 |
Matter DCT1 (KVS1, config/counters; 13 × 4 KB modules) |
0x3FC000 |
BT FTL backup sector |
The custom Matter build reuses DCT1/DCT2 (stock Matter commissioning state is
overwritten) but never touches 0x2FF000, 0x3DA000–0x3DE000 (verified by constant
scan of the deployed custom image).
Strings census summary
fw1 yields 8879 strings; ~6450 are Thumb-2 false positives from interleaved code/rodata. Approximate real counts: Wi-Fi/network ~556, Matter/CHIP ~398, TLS/crypto ~358, BLE/BT ~294, cloud/jcmd ~256, RS-485/A-C bus ~193, AT console ~155, OTA ~124, RTOS/HAL ~64, factory/MP test ~23, build provenance ~10.
Factory/test features: on-device TEST MODE (scan/BLE/RF/USART loopback with OK/Fail
verdicts), MP-test AT surface (ATM?/ATM#/bridge), fw2-only HS_OTA_TEST and GPIO
test, factory-sector auto-init on blank flash, dev/test cloud environment switching
(dev-ac-/test-ac- SSIDs, per-env pubkey). The ATSK set burns secure-boot key
material to eFuse (ENC_KEY|HASH_KEY|SB_KEY|ROOT_KEY|ROOT_SEED|SEC_BOOT_EN), one-way
and irreversible; never run it. Undocumented findings: the SENDENCKEY provisioning
key-push, a persisted cloud_mode flag gating cloud-vs-Matter, Matter QR/manual
pairing codes printed to the log UART, WDOG TEST and heap-low auto-reboot hooks,
RS-485 cmd 0x29 rebooting the module into DOTA mode. t_* capability keys map to
per-model gating (11-model-capability-map.md).
Revert-to-stock implications (GitHub issue #19)
- Slot-flip revert is safe. The stock config regions
0x2FF000(Wi-Fi profile),0x3DB000(cloud config + dkey) and0x3DD000(identity) stay byte-intact under the custom firmware (verified againstflash_is-v10211-DEPLOYED.bin), so a reverted stock boot rejoins ConnectLife without re-provisioning. The stock Matter DCT state does not survive (the custom build reuses the same DCT addresses). Caveats: this only works while the other slot still holds stock (serial 100); a second custom OTA overwrites it. On virgin units the fallback slot holds the fw2 MP-test image, not the production app. - Repackage revert recipe. Carve fw1 at flash
0x10000, length0x16ab40, then, in this order (each step’s input contains the previous step’s output):- patch
*(u32le*)(img+0xF4)to a serial strictly above the running one; img[0:32] = HMAC-SHA256(00010203…1c1d1e5f, img[0xE0:0x140])(relationship (a));L = u32le(img[0xE0]) + 0x140; img[L:L+0x20] = HMAC-SHA256(key, img[0:L])((b));- append
u32le(bytesum(img)).
Step 3 was missing until 2026-07-25 and is what bricked a unit (#75). Round-trip proof the recipe is now complete: running it with the serial left unchanged reproduces the original stock image byte-for-byte. Implemented and self-checked in
firmware/scripts/ota-release.sh revert --repackage; still awaiting one on-hardware confirmation. Delivery via Matter OTA,ATWO, orHOTA=. - patch
- The cloud channel cannot be spoofed (pinned CA + per-device dkey), and no
public stock image exists: the example file on
download.hismarttv.comis from an older module generation with zero overlap. Official image IDs can only be harvested by MITM-ing the phone app (doc 08); the device never asks for them.
Open questions
- Which writer (if any) can set state byte
0x10009050to 6 (VERIFY_NONE path); treated as dead/test code. - The UDP 5030 0x65/0x66-gated path: possible rate-limited A/C-control passthrough.
- Whether the ‘77’ reset chain erases the Matter fabric KV; if not, previously commissioned units skip the auto-open window.
- Bootloader serial tie-break (both stock slots carry 100) and fw2’s fallback role.
- Exact compare site of the
HOTA=SHA-256 argument (bootloader does not need it).