Unpacking encrypted Ecoflow firmware

As I was curious how my microinverter works, I decided to have a closer look.

The first thing to do was some Man in the Middle on the app’s traffic.

At some point it asked Ecoflow’s server for updates:

GET https://api-e.ecoflow.com/iot-ota/device/v2/getUpgradePackageList?supportSelfRD=1 HTTP/2.0

Request headers:

vcode: 86
x-appid: -1
lang: de-de
did: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX
x-timestamp: 1772100147907
user-agent: EcoFlow_oversea/6.12.0 (iPad; iOS 16.7.14; Scale/2.00)
dtype: 2
cache-control: no-cache
phonemodel: iPad6,4
systemname: user_app
version: 6.12.0
sysversion: 16.7.14
countrycode: DE
platform: ios
x-sign: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
timestamp: 20260226110227
accept-language: de-DE;q=1, uk-DE;q=0.9
productid: 1000
eftraceid: XXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXX-XXXX-X
authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
accept: */*
content-type: application/json
accept-encoding: gzip, deflate, br
x-nonce: xxxxxxx

Response:

{
"code": "0",
"message": "Success",
"data": {
"batchList": [
{
"sn": "BKxxxxxxxxxxxxxx",
"deviceName": "STREAM Mikro-Wechselrichter-xxxx",
"productType": 55,
"model": 1,
"online": 1,
"latestPackInfo": {
"id": 1907688624846458882,
"version": "1.0.0.152",
"module": {
"231552253": "1.2.9.52",
"22155534": "1.0.0.157"
},
"type": 1,
"url": "https://ecoflow-service-eu-prod.oss-eu-central-1.aliyuncs.com/ar/pack/2025-03-30/55_1.0.0.152_2Oe2O50ed.pack",
"size": 1507138,
"md5": "d415cd89aadc44a2c1bd9c2dbf875c7a",
"updateTime": "2025-04-16 22:27:19",
"note": "1. Einige bekannte Fehler wurden behoben.",
"remindWay": 1,
"upgradeWay": 0,
"moduleIsLoader": {}
},
"updateBeforeNotice": "1. Vergewissern Sie sich, dass das Gerät ausreichend geladen oder an eine Stromquelle angeschlossen ist, bevor Sie das Update starten.\n2 Schalten Sie das Gerät während der Aktualisierung nicht aus und trennen Sie es nicht von der Stromquelle.\n3. Das Gerät wird automatisch neu gestartet, wenn die Aktualisierung abgeschlossen ist. Es wird empfohlen, die Firmware zu aktualisieren, wenn sich das Gerät im Ruhezustand befindet.\n4. Wenn Sie ein Zubehör haben, das ein Update benötigt, schließen Sie es an das Gerät an, um es gemeinsam zu aktualisieren.\n5. Die Ports werden während der Aktualisierung abgeschaltet. Sie müssen sie nach dem Update manuell einschalten.\n6. Während des Aktualisierens werden die AC-Ausgangsanschlüsse abgeschaltet. Stellen Sie sicher, dass der mit dem Gerät verbundene Router nicht an den AC-Ausgangsanschluss des Geräts angeschlossen ist, da dies dazu führen kann, dass das Gerät offline geht und nicht aktualisiert wird.\n7. Stellen Sie sicher, dass keine kritischen oder medizinischen Geräte an die AC-Ausgangsanschlüsse des Geräts angeschlossen sind. Prüfen Sie nach dem Aktualisieren, ob der Ausgang wiederhergestellt ist.",
"updatingNotice": "1. Vergewissern Sie sich, dass das Gerät ausreichend geladen oder an eine Stromquelle angeschlossen ist, bevor Sie das Update starten.\n2 Schalten Sie das Gerät während der Aktualisierung nicht aus und trennen Sie es nicht von der Stromquelle.\n3. Das Gerät wird automatisch neu gestartet, wenn die Aktualisierung abgeschlossen ist. Es wird empfohlen, die Firmware zu aktualisieren, wenn sich das Gerät im Ruhezustand befindet.\n4. Wenn Sie ein Zubehör haben, das ein Update benötigt, schließen Sie es an das Gerät an, um es gemeinsam zu aktualisieren.\n5. Die Ports werden während der Aktualisierung abgeschaltet. Sie müssen sie nach dem Update manuell einschalten.\n6. Während des Aktualisierens werden die AC-Ausgangsanschlüsse abgeschaltet. Stellen Sie sicher, dass der mit dem Gerät verbundene Router nicht an den AC-Ausgangsanschluss des Geräts angeschlossen ist, da dies dazu führen kann, dass das Gerät offline geht und nicht aktualisiert wird.\n7. Stellen Sie sicher, dass keine kritischen oder medizinischen Geräte an die AC-Ausgangsanschlüsse des Geräts angeschlossen sind. Prüfen Sie nach dem Aktualisieren, ob der Ausgang wiederhergestellt ist.",
"dependencyEnable": true,
"upgradeRisk": {
"title": "",
"upgradeEnable": true,
"alertEnable": false,
"riskLevel": 1,
"riskReason": ""
},
"remindPrompt": {
"alertEnable": false,
"title": "",
"content": ""
},
"cpackInfo": {
"version": "1.0.0.0",
"module": {
"22155534": "1.0.0.126",
"231552242": "1.1.1.128",
"231552253": "1.2.9.50"
},
"isUpgrading": false,
"orderUpgradeStatus": 0,
"upgradeDetail": {
"upgradeProgress": "0"
},
"moduleIsLoader": {}
}
}
],
"orderUpdateResponse": {}
},
"eagleEyeTraceId": "",
"tid": ""
}

I wonder how I can make code look good with WordPress…

Anyway, I downloaded that file and hexdumped around a bit and realised it was encrypted.

But I soon noticed some repetition, leading to the conclusion that it might be encrypted by applying a key with XOR.

Since applying XOR to sequences of zero just leads to parts of the key being output, this was actually quite easy to figure out.

After some quick trial and error, I found the key:

00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff

(Yes, this is the actual key, I did not replace it, I have no clue why they used that one…)

After applying this key with an XOR operation, we get the decrypted firmware file.

We can get an ESP32 firmware image out of it:

dd if=decrypted_file.bin skip=126976 bs=1 of=esp32-firmware.bin

 

Binwalk confirms that this is indeed the firmware file:

DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 ESP Image (ESP32-C3): segment count: 7, flash mode: DIO, flash speed: 80MHz, flash size: 4MB, entry address: 0x4038041a, hash: sha256

Haven’t done anything with it yet, other than some hexdumping, but this looks interesting.

 

 

(If anyone got a tip on how I can make the codeblocks look better in WordPress, do let me know…)