Skip to content
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

Closed
mirko opened this issue May 23, 2017 · 77 comments
Closed

Building an Espressif OTA (user{1,2}.bin) compatible Tasmota FW image #476

mirko opened this issue May 23, 2017 · 77 comments
Labels
stale Action - Issue left behind - Used by the BOT to call for attention

Comments

@mirko
Copy link

mirko commented May 23, 2017

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

@mirko mirko changed the title Building an Espressiv OTA (user{1,2}.bin) compatible Tasmota FW image Building an Espressif OTA (user{1,2}.bin) compatible Tasmota FW image May 23, 2017
@khcnz
Copy link

khcnz commented May 23, 2017

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..?

@khcnz
Copy link

khcnz commented May 23, 2017

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)

@khcnz
Copy link

khcnz commented May 23, 2017

@mirko
Copy link
Author

mirko commented May 23, 2017

Glad to hear somebody else is also looking into this!
The hint towards the 2 image formats explains a lot - unfortunately it doesn't provide an easy solution to the issue.
From analyzing the original firmware there's no (proper) way of flashing the bootloader - only the "user1" and "user2" partitions - so flashing the bootloader from within the internal original upgrade procedure doesn't seem feasible.
So, if I'm understanding correctly, Tasmota images use the old image format (0xE9), including an bootloader which supports the old image type, while the original FW uses a bootloader which supports the new image format?
If correct, I guess the way to go would be to build (minimal) Tasmota images with the new 0xEA header(?)

Update: it seems esptool.py as well as the arduino esptool only create 0xE9 images. :/

@mirko
Copy link
Author

mirko commented May 24, 2017

Wohoo! I just managed to flash and boot a tasmota firmware image onto a sealed Sonoff Basic!
Tutorial and code will follow.

@khcnz
Copy link

khcnz commented May 24, 2017

(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....).

@khcnz
Copy link

khcnz commented May 24, 2017

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?

@mirko
Copy link
Author

mirko commented May 24, 2017

Yes, I didn't touch the bootloader, just modified the image.

@davidelang
Copy link
Collaborator

davidelang commented May 24, 2017 via email

@mirko
Copy link
Author

mirko commented May 24, 2017

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.
My first thought was to figure out which image the device asks for and provide a faulty image to get the device booting from the other partition, however I noticed quite quickly that the only faulty thing in this logic is me:

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.
But that sounds rather nasty and requires us to know the exact flash size to calculate the exact extra padding.

@davidelang
Copy link
Collaborator

davidelang commented May 24, 2017 via email

@mirko
Copy link
Author

mirko commented May 24, 2017

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-update the whole flash starting from 0x0 and therewith also replace the bootloader, wouldn't it?
Or does tasmota use the bootloader to perform the upgrade? That would indeed be an issue then.

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)

Good to know! That will definitely make certain things easier.
However the current problem is, that the tasmota build (with the 0xEA header) only boots when being flashed as user1.bin. When being flashed as user2.bin it doesn't.
Taking a closer look at the original upgrade images user1.bin and user2.bin also slightly differ.
I assume the place where they'll be flashed onto flash is somehow encoded and read/used by the bootloader.

@davidelang
Copy link
Collaborator

davidelang commented May 24, 2017 via email

@khcnz
Copy link

khcnz commented May 24, 2017

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..?

@khcnz
Copy link

khcnz commented May 24, 2017

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

@mirko
Copy link
Author

mirko commented May 24, 2017

It's hard to say without knowing a little more about the process you are using to modify your tasmota image to boot?

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.

I would guess that itead are using the espressif OTA mechanism

Yes, according to my image analysis they most certainly are.

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..?

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:

user1.bin and user2.bin are same software placed to different regions of flash. The only difference is address mapping on flash.

However it doesn't get into detail.. will now try my luck on the OTA source

@khcnz
Copy link

khcnz commented May 24, 2017

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

@khcnz
Copy link

khcnz commented May 24, 2017

Check this out - tasmota only uses linker script 1
https://github.com/arendst/Sonoff-Tasmota/tree/master/arduino/version%202.3.0/tools/sdk/ld

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

@khcnz
Copy link

khcnz commented May 24, 2017

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

@arendst
Copy link
Owner

arendst commented May 25, 2017

With these user1 and user2 images I see two concerns:

  • What happens to the arduino EEPROM flash area at address fb000? Is it left untouched by the max size image?
  • How about the nice arduino feature of being able to OTA upload a larger image than half the flash size as it uses dynamic image loading

Just some thoughts during holiday...

@mirko
Copy link
Author

mirko commented May 25, 2017

What happens to the arduino EEPROM flash area at address fb000? Is it left untouched by the max size image?

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?).
From there we could second-stage flash a normal image, then also incl. the bootloader.
Does that make sense?

How about the nice arduino feature of being able to OTA upload a larger image than half the flash size as it uses dynamic image loading

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..

Just some thoughts during holiday...

Enjoy your holidays! When are you gonna be back? And where are you? And how is it? :)

@khcnz
Copy link

khcnz commented May 27, 2017

@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.

  1. Boot Rom - this is flashed into the actual ESP (not the flash chip). Used to setup system / spi flash etc and then run normal bootloader. Also takes care of the process for flashing via UART when put into flash mode. The boot rom can not be updated but can be dumped out as it is mapped to a flash memory address Reverse engineering info here

  2. Bootloader - this is normally flashed to 0x0 - espressif and arduino both have their own bootloaders and both work very differently and have have quite strict flash layouts. The bootloader is essentially responsible for any custom setup and also determining the entry point to the 'real' application. See more below

  3. Espressif Bootloader - Splits flash essentially in 1/2 - an image can be flashed to either half and the bootloader is responsible for determining which to execute. 'Safety' is created by writing to the other 1/2 of flash from the one being used and then switching over at next boot.

  4. Arduino OTA - Works quite differently - still splits the flash in 1/2 but OTA updates always save the new sketch to the second half of flash. The bootloader then copies the newsketch from the second half of flash back to 0x0 on next boot.

So.. in conclusion

  1. It seems should be possible for sure to create both user1/user2 images that the espressif bootloader will work with - just need to check out how the layout is flashed (especially since the images from iteads servers don't seem to have the bootloader AFAIK - just the application code. Perhaps these are even just stub images that don't do much but allow for a secondary image to be loaded? Flashed to match the arduino bootloader?

  2. The the arduino bootloader is pretty restrictive if we want to include larger than 1/2 of flash size images (and when i say restrictive i mean not possible).

  3. There is a 3rd party esp8266 bootloader rboot that seems to fit the bill and allows arbitrary # of roms and rom sizes. I would suggest we would have 3 binaries

    • Bootloader
    • Very very small (smaller than minimal) upgrade rom - would need to be able to boot into this rom and accept a new image. Whats the best way to do this while still maintaining flexibility - download from a url? webpage? The more options we add the less space is available for the main application.
    • Normal application / sketch

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)

@davidelang
Copy link
Collaborator

davidelang commented May 27, 2017 via email

@khcnz
Copy link

khcnz commented May 27, 2017

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.

@davidelang
Copy link
Collaborator

davidelang commented May 27, 2017 via email

@mirko
Copy link
Author

mirko commented May 27, 2017

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

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.
Nothing like the diff between the original user1/user2 upgrade images :/

@mirko
Copy link
Author

mirko commented May 27, 2017

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.

BTW, that fails for me (current master) with:

"/home/mirko/.arduino15/packages/esp8266/tools/xtensa-lx106-elf-gcc/1.20.0-26-gb404fb9-2/bin/xtensa-lx106-elf-g++" -D__ets__ -DICACHE_FLASH -U__STRICT_ANSI__ "-I/home/mirko/.arduino15/packages/esp8266/hardware/esp8266/2.3.0/tools/sdk/include" "-I/home/mirko/.arduino15/packages/esp8266/hardware/esp8266/2.3.0/tools/sdk/lwip/include" "-I/tmp/arduino_build_518542/core" -c -w -Os -g -mlongcalls -mtext-section-literals -fno-exceptions -fno-rtti -falign-functions=4 -std=c++11 -MMD -ffunction-sections -fdata-sections -DF_CPU=80000000L -DLWIP_OPEN_SRC   -DARDUINO=10803 -DARDUINO_ESP8266_ESP01 -DARDUINO_ARCH_ESP8266 -DARDUINO_BOARD="ESP8266_ESP01"  -DESP8266 "-I/home/mirko/.arduino15/packages/esp8266/hardware/esp8266/2.3.0/cores/esp8266" "-I/home/mirko/.arduino15/packages/esp8266/hardware/esp8266/2.3.0/variants/generic" "-I/home/mirko/Arduino/libraries/PubSubClient/src" "-I/home/mirko/.arduino15/packages/esp8266/hardware/esp8266/2.3.0/libraries/Ticker" "-I/home/mirko/.arduino15/packages/esp8266/hardware/esp8266/2.3.0/libraries/ESP8266WiFi/src" "-I/home/mirko/.arduino15/packages/esp8266/hardware/esp8266/2.3.0/libraries/ESP8266HTTPClient/src" "-I/home/mirko/.arduino15/packages/esp8266/hardware/esp8266/2.3.0/libraries/ESP8266httpUpdate/src" "-I/home/mirko/Arduino/libraries/ArduinoJson/src" "-I/home/mirko/.arduino15/packages/esp8266/hardware/esp8266/2.3.0/libraries/ESP8266WebServer/src" "-I/home/mirko/.arduino15/packages/esp8266/hardware/esp8266/2.3.0/libraries/DNSServer/src" "/tmp/arduino_build_518542/sketch/sonoff.ino.cpp" -o "/tmp/arduino_build_518542/sketch/sonoff.ino.cpp.o"
/home/mirko/src/Sonoff-Tasmota.git/sonoff/sonoff.ino: In function 'void GPIO_init()':
sonoff:2384: error: 'dht_setup' was not declared in this scope
         if (dht_setup(i, mpin)) {
                              ^
[..]
exit status 1
'dht_setup' was not declared in this scope

@davidelang
Copy link
Collaborator

davidelang commented May 27, 2017 via email

@mirko
Copy link
Author

mirko commented May 27, 2017

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

@khcnz
Copy link

khcnz commented Jun 5, 2017

Thanks for responding Richard - much appreciated - I have some follow up questions though

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.

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 :)

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.

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.

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.

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

@khcnz
Copy link

khcnz commented Jun 5, 2017

@mirko I think with a bit more work on both of our ends I think we could

  1. Provide small one off python script to end users that provisions device and points to
  2. Public webserver running websockets code + Espressif2Arduino x2 Images + Stock sonoff Image

That way we / people can 'jailbreak' the device very simply with no setup other than needing to be able to run one python script.

@raburton
Copy link

raburton commented Jun 5, 2017

Well clearly it is interpretable - after all that's how its run as instructions on the CPU!

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).

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.

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.

@mirko
Copy link
Author

mirko commented Jun 5, 2017

@raburton Thanks a lot for for jumping in here and helping out!

  • Stock sonoff Image

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.

@davidelang
Copy link
Collaborator

davidelang commented Jun 5, 2017 via email

@raburton
Copy link

raburton commented Jun 5, 2017

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.

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.

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.

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

@davidelang
Copy link
Collaborator

davidelang commented Jun 5, 2017 via email

@raburton
Copy link

raburton commented Jun 5, 2017

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)

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).

@davidelang
Copy link
Collaborator

davidelang commented Jun 5, 2017 via email

@khcnz
Copy link

khcnz commented Jun 5, 2017

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

Agreed - I have some 'vigin' sonoffs at home - i will dump their entire flash and post / compare - must either be using

  1. Chip id / MAC Address
  2. Something saved to flash outside at the end of each rom slot
  3. Something saved to RTC

@khcnz
Copy link

khcnz commented Jun 5, 2017

The linking can be done using relative addressing so that it can be relocated at
runtime.

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

@davidelang
Copy link
Collaborator

davidelang commented Jun 5, 2017 via email

@raburton
Copy link

raburton commented Jun 5, 2017

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?

@khcnz
Copy link

khcnz commented Jun 7, 2017

You would still need to do a lot more work, all to solve a problem that isn't really a problem in first place.

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 :)

@khcnz
Copy link

khcnz commented Jun 7, 2017

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

@mianos
Copy link

mianos commented Jun 10, 2017

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).

@jalmeroth
Copy link

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.

@mirko
Copy link
Author

mirko commented Jun 16, 2017

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:

http://52.28.103.75:8088/ota/rom/xpiAOwgVUJaRMqFkRBsoAAAAAAAAAAAA/user1.1024.new.2.bin?deviceid=10000XXXXX&ts=DDDDDDDDD&sign=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Or are you talking about the actually downloaded images - downloaded with two different (as in: different deviceids) DL URLs - and they are the same?

@jalmeroth
Copy link

Yes, I am talking about the latter:

Device 10000xxxxx receives:

{"bizRtnCode":10001,"deviceid":"10000xxxxx","model":"ITA-GZ1-GL","version":"1.5.5","binList":[
{"downloadUrl":"http://52.28.103.75:8088/ota/rom/xpiAOwgVUJaRMqFkRBsoI4AVtnozgwp1/user1.1024.new.2.bin","digest":"1aee969af1daf96f3f120323cd2c167ae1aceefc23052bb0cce790afc18fc634","name":"user1.bin"},
{"downloadUrl":"http://52.28.103.75:8088/ota/rom/xpiAOwgVUJaRMqFkRBsoI4AVtnozgwp1/user2.1024.new.2.bin","digest":"6c4e02d5d5e4f74d501de9029c8fa9a7850403eb89e3d8f2ba90386358c59d47","name":"user2.bin"}]},

and Device 10000yyyyy will receive:

{"bizRtnCode":10001,"deviceid":"10000yyyyy","model":"ITA-GZ1-GL","version":"1.5.5","binList":[
{"downloadUrl":"http://52.28.103.75:8088/ota/rom/xpiAOwgVUJaRMqFkRBsoI4AVtnozgwp1/user1.1024.new.2.bin","digest":"1aee969af1daf96f3f120323cd2c167ae1aceefc23052bb0cce790afc18fc634","name":"user1.bin"},
{"downloadUrl":"http://52.28.103.75:8088/ota/rom/xpiAOwgVUJaRMqFkRBsoI4AVtnozgwp1/user2.1024.new.2.bin","digest":"6c4e02d5d5e4f74d501de9029c8fa9a7850403eb89e3d8f2ba90386358c59d47","name":"user2.bin"}]},

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.

@mirko
Copy link
Author

mirko commented Jun 16, 2017

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 (500 Server error 400 Bad request).

@raburton
Copy link

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.

@khcnz
Copy link

khcnz commented Jun 24, 2017

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.

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.

@jalmeroth
Copy link

@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.

@mihalski
Copy link

mihalski commented Jul 5, 2017

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:

2017-07-05 12:02:24,417 (WARNING) [Errno 2] No such file or directory: 'static/image_user1-0x01000.bin'
2017-07-05 12:02:24,417 (WARNING) [Errno 2] No such file or directory: 'static/image_user2-0x81000.bin'

even though:

ls -l static/
total 1904
-rw-r--r--  1 michal  staff  487040  5 Jul 11:40 image_user1.ino-0x01000.bin
-rw-r--r--  1 michal  staff  487040  5 Jul 11:54 image_user2.ino-0x81000.bin

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:

2017-07-05 12:23:47,505 (INFO) 200 POST /dispatch/device (192.168.1.132) 0.47ms
<< HTTP POST /dispatch/device
>> /dispatch/device
>> {
    "error": 0,
    "reason": "ok",
    "IP": "192.168.1.41",
    "port": 4223
}
2017-07-05 12:23:47,673 (INFO) 101 GET /api/ws (192.168.1.132) 0.32ms
2017-07-05 12:23:47,673 (DEBUG) << WEBSOCKET OPEN
~~~ device sent action request,  acknowledging / answering...
~~~~ register
>> {
    "error": 0,
    "deviceid": "10000bd813",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "config": {
        "hb": 1,
        "hbInterval": 145
    }
}
2017-07-05 12:23:47,680 (DEBUG) << WEBSOCKET INPUT
2017-07-05 12:23:47,680 (DEBUG) << {
    "userAgent": "device",
    "apikey": "113af144-a8a9-4b59-a733-4cc910dbf557",
    "deviceid": "10000bd813",
    "action": "register",
    "version": 2,
    "romVersion": "1.5.5",
    "model": "ITA-GZ1-GL",
    "ts": 743
}
2017-07-05 12:23:47,680 (INFO) We are dealing with a ITA-GZ1-GL model.
~~~ device sent action request,  acknowledging / answering...
~~~~ date
>> {
    "error": 0,
    "deviceid": "10000bd813",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "date": "2017-07-05T12:23:47.687Z"
}
2017-07-05 12:23:47,686 (DEBUG) << WEBSOCKET INPUT
2017-07-05 12:23:47,687 (DEBUG) << {
    "userAgent": "device",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "deviceid": "10000bd813",
    "action": "date"
}
2017-07-05 12:23:47,694 (DEBUG) << WEBSOCKET INPUT
2017-07-05 12:23:47,694 (DEBUG) << {
    "userAgent": "device",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "deviceid": "10000bd813",
    "action": "update",
    "params": {
        "switch": "off",
        "fwVersion": "1.5.5",
        "rssi": -58,
        "staMac": "5C:CF:7F:41:EF:89",
        "startup": "off"
    }
}
~~~ device sent action request,  acknowledging / answering...
~~~~ update
>> {
    "error": 0,
    "deviceid": "10000bd813",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294"
}
>> {
    "action": "update",
    "deviceid": "10000bd813",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "userAgent": "app",
    "sequence": "1499221427694",
    "ts": 0,
    "params": {
        "switch": "off"
    },
    "from": "hackepeter"
}
>> {
    "action": "update",
    "deviceid": "10000bd813",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "userAgent": "app",
    "sequence": "1499221427694",
    "ts": 0,
    "params": {
        "switch": "on"
    },
    "from": "hackepeter"
}
>> {
    "action": "update",
    "deviceid": "10000bd813",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "userAgent": "app",
    "sequence": "1499221427695",
    "ts": 0,
    "params": {
        "switch": "off"
    },
    "from": "hackepeter"
}
>> {
    "action": "update",
    "deviceid": "10000bd813",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "userAgent": "app",
    "sequence": "1499221427695",
    "ts": 0,
    "params": {
        "switch": "on"
    },
    "from": "hackepeter"
}
>> {
    "action": "update",
    "deviceid": "10000bd813",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "userAgent": "app",
    "sequence": "1499221427695",
    "ts": 0,
    "params": {
        "switch": "off"
    },
    "from": "hackepeter"
}
>> {
    "action": "upgrade",
    "deviceid": "10000bd813",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "userAgent": "app",
    "sequence": "1499221427697",
    "ts": 0,
    "params": {
        "binList": [
            {
                "downloadUrl": "http://192.168.1.41:8080/ota/image_user1-0x01000.bin",
                "digest": "a34a9cf1825f8c0f007239d974b8d3c9b1939fb1de07d94396254f7106f83a9d",
                "name": "user1.bin"
            },
            {
                "downloadUrl": "http://192.168.1.41:8080/ota/image_user2-0x81000.bin",
                "digest": "05698c30ae130ea573730798ec31fd61995143a7ceb351e2076064888f4781c6",
                "name": "user2.bin"
            }
        ],
        "model": "ITA-GZ1-GL",
        "version": "23.42.5"
    }
}
~~~ device sent action request,  acknowledging / answering...
~~~~ query
>> {
    "error": 0,
    "deviceid": "10000bd813",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "params": 0
}
2017-07-05 12:23:47,704 (DEBUG) << WEBSOCKET INPUT
2017-07-05 12:23:47,704 (DEBUG) << {
    "userAgent": "device",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "deviceid": "10000bd813",
    "action": "query",
    "params": [
        "timers"
    ]
}
2017-07-05 12:23:48,447 (INFO) 206 GET /ota/image_user2-0x81000.bin?deviceid=10000bd813&ts=1700375105&sign=b53480d51b506e93eb072bc53af0c1ae467194db95fc056e56b5b8a936eeb1ec (192.168.1.132) 0.53ms
2017-07-05 12:23:48,461 (DEBUG) << WEBSOCKET INPUT
2017-07-05 12:23:48,461 (DEBUG) << {
    "error": 404,
    "userAgent": "device",
    "apikey": "a2a39b12-e099-486c-977c-84869d465294",
    "deviceid": "10000bd813",
    "sequence": "1499221427697"
}
2017-07-05 12:23:48,461 (DEBUG) ~~~ device acknowledged our action request (seq 1499221427697) with error code 404

@mirko
Copy link
Author

mirko commented Jul 6, 2017

It seems like the device is not accepting the image for whatever reason.
"404" in this regard gets returned by the Sonoff whenever it doesn't like the downloaded image (e.g. sha256-checksum wrong).
Please also mind, that this issue/thread is to gather information. For issues on how to use SonOTA please open an issue in the respective project (https://github.com/mirko/SonOTA).

@stale
Copy link

stale bot commented Apr 23, 2018

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.

@stale stale bot added the stale Action - Issue left behind - Used by the BOT to call for attention label Apr 23, 2018
@stale
Copy link

stale bot commented May 7, 2018

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.

@stale stale bot closed this as completed May 7, 2018
curzon01 pushed a commit to curzon01/Tasmota that referenced this issue Sep 6, 2018
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stale Action - Issue left behind - Used by the BOT to call for attention
Projects
None yet
Development

No branches or pull requests

9 participants