-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Building an Espressif OTA (user{1,2}.bin) compatible Tasmota FW image #476
Comments
The world is full of funny coincidences... after sometime thinking about this i had finally started looking into this last night as well. I presume you are using a little DNS trickery and setting up a websocket which issues the update command (along with custom url). One thing to note is that i don't believe with standard tools you can update the bootloader OTA (but maybe you can by directly overwriting the flash... it wouldn't be 'safe' from possible corruption - but in this case the only thing that could happen is to have to flash it via serial which is where we have come from anyway). I think the first question to determine is which bootloader they are using - using esptool i believe you can dump flash from an arbitrary memory location maybe we can get some clues on their bootloader. PS: The latest firmware that i sniffed an upgrade to (2.0.2) seems to use SSL end to end for the websockets so this could close this avenue in the future if they start shipping with newer firmware PPS: From sniffing it would also be possible to maintain compatibility with the ewelink app infrastructure so devices could be controlled by both mqtt and the ewelink - not sure if that would be usefull for anyone..? |
http://esp8266-re.foogod.com/wiki/SPI_Flash_Format#0xea_Header Seems its a newer boot mode format (boot mode 1 vs boot mode 2) |
Glad to hear somebody else is also looking into this! Update: it seems esptool.py as well as the arduino esptool only create 0xE9 images. :/ |
Wohoo! I just managed to flash and boot a tasmota firmware image onto a sealed Sonoff Basic! |
(Assuming we can find a toolchain that support building the new format) I think you could maybe create a very minimal 0xEA firmware whose sole purpose is to flash the bootloader to the tasmota and enable the second stage firmware flash - however still dependent on being able to flash over the current bootloader while its running... my knowledge gets very slim here but from some reading I think the bootloader is loaded in ram... so it could be possible to flash while its running (maybe....). |
Edit - wow! nice! A wiki page would probably be the best place Assuming you managed to compile tasmota with 0xEA bootloader I also guess this means we could retain backwards compatibility with the existing firmware for those who want/need to roll back? |
Yes, I didn't touch the bootloader, just modified the image. |
please double-check that you can do an OTA upgrade from tasmota to a new tasmota
image after this.
|
Could you elaborate your concerns? I'm still struggling with forcing the Sonoff to flash our custom image to the user1 partition, as -- depending what part is (in)active -- it might get flashed to user2 which is bad for several reasons. When the currently running system is stored on user1 (user1 == active), then we'd flash user2. Providing a faulty image only result in the bootloader not being able to boot user2 and again boot into user1. UPDATE: The only way I come up with right now is, in case the device asks for the user2 image (meaning user1 == active), to override the internal data segment, which comes right after the user2 one, as the internal data section stores the information for the bootloader which partition is the currently active one. |
you have been able to get tasmota running from the base build, but by not
replacing the bootloader.
my concern is that since you didn't replace the bootloader, trying to d a
tasmota -> tasmota OTA upgrade could possibly not work (or require changes to
tasmota)
If it's possible/required to do different update URLs for the two 'slots' in the
router, that may not be a bad thing, we are getting to the point where size
limits are pushing us to the need to do a 'two step' upgrade (first to a minimal
build, then to a full build)
|
I see, though I figured tasmota also flashes the bootloader, doesn't it?
Good to know! That will definitely make certain things easier. |
On Wed, 24 May 2017, Mirko Vogt wrote:
> my concern is that since you didn't replace the bootloader, trying to d a tasmota -> tasmota OTA upgrade could possibly not work (or require changes to tasmota)
I see, though I figured tasmota also flashes the bootloader, doesn't it?
If so, then -- once tasmota is running (even with the original bootloader) -- it would OTA-updates the whole flash starting from 0x0 and therewith also replaces the bootloader, wouldn't it?
Or does tasmota *use* the bootloader to perform the upgrade? That would indeed be an issue then.
I'm not sure. The max size you can OTA on tasmota is 1/2 (memory size -
bootloader) so I think it doesn't replace the bootloader with an OTA upgrade.
When arendst is back he can comment on this with a lot more knowledge. I'm just
raising potential issues in the hope that you can check them and make this rock
solid.
David Lang
k
|
It's hard to say without knowing a little more about the process you are using to modify your tasmota image to boot? I presume you have the user1.1024 and user2.1024 images from itead? When you diff them there are considerable differences (starting at the simple like byte 4 is 01 or 02 depending on which file). I would guess that itead are using the espressif OTA mechanism - you can see more here http://www.espressif.com/sites/default/files/99c-esp8266_ota_upgrade_en_v1.6.pdf and it describes how to create the two versions aside from the bye mentioned above I believe memory addresses differ ti think..? |
You may also want to look at the code in the Arduino ESP8266 updater https://github.com/esp8266/Arduino/blob/master/cores/esp8266/Updater.cpp - may give some hints |
I'm sorry, I didn't mean to beat around the bush. esptool.py can indeed create v2 images, although that's undocumented. From the source I figured that the "--version" parameter -- while usually (and according to the docs) printing the version string of the tool -- in combination with "elf2image" accepts an parameter, specifying the version of the image to be created. tl;dr: < esptool.py elf2image --version 2 sonoff.ino.elf > - sonoff.ino.elf in this case is the tasmota image created by the Arduino frontend.
Yes, according to my image analysis they most certainly are.
Oh, interesting! So far I only stumbled over documents from Espressif describing how to use their SDK to build user1.bin and user2.bin which wasn't very helpful. The document you mentioned is way more detailed and helpful, let's see what to make out of it. EDIT: The most interesting part of the doc probably is:
However it doesn't get into detail.. will now try my luck on the OTA source |
Great! One other thing that was confusing me that may help you... based on my reading of the Arduino IDE Uploader.cpp Arduino OTA mechanism (that tasmota uses) doesn't seem to ever flash OTA user 1 image... only ever user 2. User1 is left for fallback as far as I can tell (see how it calculates the start address of where to write the flash to. Thats why the fact that the tasmota/arduino image has the first 0x1000 as the bootloader is irrelevant. its written to a space that isn't used (but according to the link above its kept for symmetry reasons). This differs from espressif as far as i can tell where they write alternatively between the two - you can tell which one is currently executing with the call system_upgrade_userbin_check |
Check this out - tasmota only uses linker script 1 Look at this example of a make script building 1 & 2 images https://github.com/jeelabs/esp-link/blob/master/Makefile I think we just need to have a secondary ld script |
One last link :) https://github.com/espressif/ESP8266_RTOS_SDK/tree/master/ld you can compare eagle.app.v6.new.1024.app1.ld and eagle.app.v6.new.1024.app2.ld I think if you changed the tasmota over-ride and change irom0_0_seg from 0x40201010 to 0x40281010 it will work |
With these user1 and user2 images I see two concerns:
Just some thoughts during holiday... |
Well, I'd say (for now) this method only allows flashing images < 0xfb000−0x81000 = 488KB which at least a minimal tasmota image shouldn't exceed (or would it?).
Honestly, I've absolutely no idea as this is my first arduino project I kinda involve myself and frankly this arduino world doesn't appear to be mine so I'm not sure I'd be of any help here..
Enjoy your holidays! When are you gonna be back? And where are you? And how is it? :) |
@mirko How did you go with building a user2 image? @arendst I can't find any info on dynamic sized arduino images but I have done a lot more digging and finally have my head around how the boot rom, bootloader and ota is working.
So.. in conclusion
Obviously depending on how small the small upgrade rom could get - we could have a lot more flash available for the main application Thoughts? (edited images to be inline) |
the #define BE_MINIMAL is intended to do this. It builds an image that has
almost all features disabled, but still has the wifi and webserver functionality
so that you can tweak configs and download/install a new image.
If it's not small enough, we would need to figure out what's eating the space
and can be disabled.
|
From memory the minimal still includes MQTT and there is a ton of config upgrade logic that wouldn't need to exist, and the webserver code is massive as well. Even just the base arduino and espressif libraries add a ton. Clearly the smaller it can be the better - but the smaller it is made the less ways we could support upgrade (the smallest way would probably only to support connecting to a defined wifi network and pulling from a defined url). Of course then you get into questions like dns/mdns, HTTPS, static ips etc etc. |
there isn't actually a way right now to not compile in the mqtt code.
I think the webserver is still needed to be able to direct an upgrade and
configure things like network connection.
As long as the minimal build + real build < flash space (- bootloader), it
should be good enough
It should also be possible for the firmware to overwrite the bootloader to
conver it from the stock sonoff firmware to the one that tasmota has been using.
|
This indeed sounded like the hint, however the images I managed to create (user1 = without that change, user2 = with that change) only differ in what I think is the build timestamp. |
BTW, that fails for me (current master) with:
|
that section should be wrapped with #ifdef USE_DHT
a patch similar to:
diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino
index c022965..203d60a 100644
--- a/sonoff/sonoff.ino
+++ b/sonoff/sonoff.ino
@@ -2380,6 +2380,7 @@ void GPIO_init()
led_inverted[mpin - GPIO_LED1_INV] = 1;
mpin -= 4;
}
+#ifdef USE_DHT
else if ((mpin >= GPIO_DHT11) && (mpin <= GPIO_DHT22)) {
if (dht_setup(i, mpin)) {
dht_flg = 1;
@@ -2388,6 +2389,7 @@ void GPIO_init()
mpin = 0;
}
}
+#endif
}
if (mpin) {
pin[mpin] = i;
|
BTW, this is the current state of my implementation instructing a sonoff device to fetch and flash a custom image from an arbitrary (reachable) server: https://pb.nanl.de/show.php?id=2765&hash=14959135 |
Thanks for responding Richard - much appreciated - I have some follow up questions though
Well clearly it is interpretable - after all that's how its run as instructions on the CPU! The offsets are actually very simple (different by exactly 0x080000 of course). If you binary compare the two builds you can see the changes are pretty straight forwards, and with a simple/small build you can get away with some logic basically saying rewrite on the fly any 4byte aligned 32bit address between 0x40280000 -> 0x402B0000 by subtracting 0x80000. This actually works fine with smallish builds (mostly by chance to be fair). On larger builds the chance goes up you get something that shouldn't actually be modified as its not an address but matches the pattern described above. I guess I was hoping there would be some info on how to read the actual section (have read out the headers etc), that way could iterate through the sections and only manipulate the address. Tools like idapro can obviously do this - however the question would become if a) its feasible to do in a small micro, especially when you can't hold the entire section in ram at once and b) worth the time :)
Yea I started down this rabbit hole briefly while looking at your rboot source but alas as you say you can only map in 1mB chunks.
Have just pushed my original code up to here https://github.com/khcnz/Espressif2Arduino that effectively does this. I haven't yet tried this with your code @mirko but pretty sure it will work end to end - i would be interested to know if it picks up the wifi credentials you entered in the itead app? Have also created a tiny utility for flip-flopping / ping-ponging between the two images on the device https://github.com/khcnz/EspressifRomSwitcher |
@mirko I think with a bit more work on both of our ends I think we could
That way we / people can 'jailbreak' the device very simply with no setup other than needing to be able to run one python script. |
True, but I didn't say the CPU couldn't understand them, I said "you" (though that wasn't supposed to be personal or rude - I meant your code, unless you plan to write an embedded version of IDA in less than half a mb).
You can identify 4 byte sequences, that are 4 byte aligned, which when interpreted as uint32 fall within that range, but you have no idea if it is an address or even a number, so it would be unsafe to modify them, as you have found. Also, the ram sections are checksummed, and if you alter those sections without updating the checksum it will not boot. I'm afraid the only option to having two copies of the rom on flash is to have two differently linked images. There are other strategies, which I think have been mentioned in passing before here, like downloading the new image to the end of the rom, and having the boot loader copy it to the first slot on next boot, so you only ever have a slot one rom. The extra complexity and the lack of fallback in case of a bad flash though make this a much less sensible option. |
@raburton Thanks a lot for for jumping in here and helping out!
Jan Almeroth and me noticed independently -- but didn't put much more thought into this -- that dumping an original image from one Sonoff device and flashing it to another will result in it not booting. That doesn't mean the Sonoff OTA upgrade images are also device specific, however you can't download them without passing your deviceid and a sign parameter which looks like a sha256 hash, however -- again -- I just noticed and didn't dig into it any further. Without those CGI parameters the server just returns a HTTP 500. So, even if it sounds unlikely, the server might generate deviceid specific images on the fly and provide them for download. That needs to be ruled out beforehand I guess. About the CGI parameters there's some context in this ticket: mirko/SonOTA#1 Apart from that I'm really thrilled by the overall progress being made and seeing we're getting closer and closer! I'll hopefully have the time to take a look and try out your (@khcnz) code later today. PS: @khcnz, can I reach you in IRC / XMPP / email / whatever-medium? I'm mirko on freenode (IRC) and mirko@[DOMAIN-OF-MY-BLOG] on xmpp/email. |
the problem is that you would have to decide the binary to figure out what is an
address and what is data and change only the bytes for the address.
Unless the binary is explicitly compiled to be relocateable, this is virtually
impossible to do (unreliable even on large systems, completely impractical to
consider on small systems like this)
|
I assume some protection against clones. The esp doesn't have any useful hardware protection (as far as we know) to help with this, but it could do something simple like check the mac address on boot. Easy to hack to make a clone, but they could detect the clones and prevent them getting ota updates.
As above. As an example the mac address could be inserted into the image file for checking at run time. Sonoff could record all the legit device ids and deny any other ota updates, so a rom hacked to run on a clone wouldn't be able to get any updates. I don't have a device with which to try getting roms. Perhaps you could message me about that and I could have a look at them |
If the program is compile to only use relative addressing internally, then it
can be put anywhere in the flash, but I don't know what it would take to do that
(this should be happening in the linker step, and since it's possible to have
custom linker scripts, it should be possible to do)
On Mon, 5 Jun 2017, Richard Antony Burton wrote:
… You can't remap the application at runtime - it isn't simply a case of the base address being stored in the header, the elf is linked with that address baked in in ways you will not be able to understand / adjust in the completed binary. You need to have two separate roms - one for the lower 1/2 and one for the upper 1/2. Yes, that isn't as neat but it's no big deal. If there had been a bigger flash than 8mbita on the device you could have used a single rom, by spacing them in separate mbs of flash each mb will be mapped to the same base address in memory. Of course they have used the smallest flash they can for cost purposes. Luckily it's trivial to create both versions at link time and then when you ota you just need to request the correct one file be downloaded and flashed.
|
Yes, it can be put anywhere in flash, after being correctly linked - that's how it works already. The question was about runtime relocation (or possibly flash-time relocation). |
The linking can be done using relative addressing so that it can be relocated at
runtime.
|
Agreed - I have some 'vigin' sonoffs at home - i will dump their entire flash and post / compare - must either be using
|
Although a PC can support relative addressing I didn't think the ESP could.. do you have some references to the contrary? There is possibly some related/helpful info here but I haven't had a chance to properly digest it yet jcmvbkbc/gcc-xtensa#3 |
I don't know what it takes to make the tools do this, but if you think about it,
the ojects that get linked together are all compiled with relative addressing
(aka Position Independent Code) internally, and then the linker is tieing them
together and determines any absolute addressing.
for what it's worth, I haven't ever seen an assembly language that doesn't
support relative addressing (although I have seen systems where the relative
addressing could not span the entire address space and so couldn't be used
everywhere, including 8086 assembly) There's no reason why a 32 bit system with
only a few MB of address space should not be able to use relative addressing
throughout.
If everything is compiled with -fpic it should just come down to the linker, and
assuming it supports the -r option, you should be good to go.
|
No, it really isn't that simple. The esp does not support relocatable code. You would have to write a loader yourself, which is pretty much what was already discussed (be it boot time or flash time). Tweaking the linker options could make this process easier, but it does not make it magically just work. You would still need to do a lot more work, all to solve a problem that isn't really a problem in first place. If this was simple, or even necessary, don't you think espressif would have done it? |
Solving random problems like these is how i best like to learn how they work under the hood :) Even if the result is a little theoretical. If I was only doing a home automation project to save time, I think i would have invested more than a life time of manually switching light switches on and off already :) |
Actually found I have 6 original sonoff pows I hadn't used due to the recall. Have dumped them all and it seems clear there is some encoding of the MAC address in a slightly obfuscated way. Fortunately I got some nice close mac address so it makes it much easier to determine what they have done. Anyone want them let me know and I can upload otherwise will create a quick python script in coming days |
This is also interesting to me because the new Sonoff Smart LED does not have serial pins or a button. (I bought a few to have a play and just pulled one apart). |
I just want to confirm, that the OTA firmware images are not branded/specific with their MAC-address or anything. I have got two Sonoff Basics here and they both get the same DL URL and the same digests. |
Hu? For me - at least regarding the DL URL - the device ID is transmitted as CGI GET variable, so it is specific:
Or are you talking about the actually downloaded images - downloaded with two different (as in: different deviceids) DL URLs - and they are the same? |
Yes, I am talking about the latter: Device 10000xxxxx receives:
and Device 10000yyyyy will receive:
As the exact same digest will be sent to the device via upgrade message, there's no way that the images get modified server-side. |
But the device will append those CGI variables to the given DL string - no matter how the DL string provided by AWS looks like. When I tried to download the images with the provided DL URLs without the appended CGI vars, the HTTP request failed ( |
Makes sense. I don't have the download files, but comparing the dumps there is no difference in the code area. The important bit(s), such as device id, is/are in a data area. The code will validate this at boot time, against something fixed like the mac address. This prevents simple cloning. Hacking the app to run on a clone by bypassing that check will work fine, until the user tries to do an OTA. The device info sent will not be recognised and so the server will not send back the update (and if it did that rom would not be hacked and so would fail the boot check). I've decompiled the rom, but it's tough going trying to find the code that does the boot check (and I haven't had a lot of time to spend on it). This is purely for interest - I don't want to make clone,s let alone non-OTAable clones - the option of buying the hardware to run your own software seems much more appealing to me than the other way around. |
Interesting - hadn't considered they wouldn't do the same per device custom firmware as they come out from the factory. As per @raburton no intention of making counterfeit devices (and i'm assuming their cloud platform wouldn't even recognize them, so not usable with ewelink). But I do like the idea of having a repository of stock images that someone can reflash back to (and if all devices share the same then even easier). I know I reflashed to stock a lot while testing the OTA itead -> Arduino process. Should be easy pretty straight forwards to dump the stock bootloader and OTA upgrade image for each device to make reverting straight forwards. |
@khcnz I would like to support the idea of having this repo of stock images. I could provide some older versions for different Sonoff devices. But I am wondering about the legal implications of re-distributing binaries files here. As a workaround we could provide/safe the base-urls instead, as long as the files will stick under the same address after a new version has been released, which needs further testing. In any case, it would be beneficial to understand the generation of the sign-parameter for the download-requests, which seems to be the last piece of the puzzle. |
Is there anywhere with a step by step guide on how this should be used? I can't find any clear documentation.. ${SONOTA} pops up a few times but I have NO idea what it's referring to.. Is it some sort of build environment path variable? If so what, where, how? The information is all over the place and segmented so you're never sure if it's relevant to what you're trying to achieve. A simple guide would be VERY much appreciated. EDIT: I got as far as trying to create the user 2 image but even when I edit ~/.arduino15/packages/esp8266/hardware/esp8266/2.3.0/tools/sdk/ld/eagle.flash.1m64.ld as required it always generates image_user2.ino-0x01000.bin when I run it through esptool.py. I think this might be the only thing preventing me from getting this to work now. NOTE: In case this makes any difference, I am using the 1M (no SPIFFS) flash size option as required by 5.x TASMOTA. EDIT2: Well I'm an idiot.. Of course it does.. I need to edit eagle.flash.1m0.ld EDIT3: Now no matter what I do I seem to be getting:
even though:
I've also tried setting the SONOTA env variable in case that was the problem. EDIT4: OK.. It took me a while to notice that ".ino" in the filenames. Fixed it but now the device seems to be saying that it can't find the file (404). If I follow the link being provided (http://192.168.1.41:8080/ota/image_user2-0x81000.bin) I can download the firmware. Here's what I'm seeing:
|
It seems like the device is not accepting the image for whatever reason. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
This issue will be auto-closed because there hasn't been any activity for a few months. Feel free to open a new one if you still experience this problem. |
5.1.4 20170601 * Removed pre-compiled versions from repository as they are available within the release * Changed HUE Device type to color supporting version (arendst#464) * Fix compile error when BE_MINIMAL is selected (arendst#467, arendst#476) * Add multiple compiled versions to release using updated Travis script and platformio.ini (arendst#467)
This is slightly off-topic, however -- in case my poor google skills didn't leave me in the lurch and indeed nobody managed to do so before -- might ease flashing the Tasmota supported itead/sonoff devices without HW/serial access.
I took a closer look at the original firmware, reversed engineered a few pieces and implemented a basic server which can communicate with the Sonoff devices (basically a subset of what the amazon services are doing for itead).
Goal is to use the original internal upgrade mechanism to flash custom firmware onto the devices without opening them, attaching a serial cable, potential soldering, pulling down GPIO0, you know the game...
By now I managed to trick a Sonoff device to download a custom FW from servers under my control, passing the verification, and eventually flashing the image. That's some fair progress, however the device doesn't boot.
My first naive try consisted of providing a sonoff.ino.bin instead of the original upgrade file the device would usually fetch.
Then I noticed, the original upgrade image doesn't contain the bootloader section, so I chopped off the first 0x1000 bytes.
Still, the device doesn't boot.
That could mean that
a) the original bootloader can't load the tasmota firmware (e.g. tasmota requires a modified bootloader to be loaded appropriately)?
b) the internal upgrade mechanism expects a different image format
After some research I figured the original FW uses the Espressif OTA functionality. This apparently makes use of some Ping-Pong, meaning, it splits the usable flash and always flashes the inactive part.
That also matches with the requests for files named "user1.bin" and/or "user2.bin".
Those -- the original upgrade files -- start with (HEX):
EA 04 00 01 04 00 10 40
The Tasmota built starts with (HEX at offset 0x1000):
E9 04 02 00 04 00 10 40
As you can see, the first 4 Bytes don't match, while the second 4 Bytes do.
The Tasmota image header matches the Espressif documentation[1] about how an image should like (always starting with E9).
I couldn't find any documentation on what the image header in the original upgrade images is supposed to represent.
Long story short: If case b) applies ("the internal upgrade mechanism expects a different image format"), what is the that image format / header and (how) could build I build a Tasmota FW matching that image format criteria? If a) applies ("the original bootloader can't load the tasmota firmware"), can we workaround that somehow, at least for an intermediate image providing the Tasmota OTA functionality?
[1]http://www.espressif.com/sites/default/files/esp8266-sdk_application_note_firmware_download_protocol_en.pdf
The text was updated successfully, but these errors were encountered: