-
Notifications
You must be signed in to change notification settings - Fork 22
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
Support for Wi-Fi sensor messages #34
Comments
As a first look, the KAWL9SHkwMZP3ZPZGxCS5ToZMulL16lnjYXJJY6EDCo= string at the end, looks like some kind of hash of the preceeding data. My fear is that it's an encryption string... When I have some time, I might try to run it through a bunch of different things, like sha1, base64, etc etc, and see if any of them pop. Hopefully it's not an actual encryption. |
A base64 encoded sha256 digest is 44 characters. Bad news... if that's what it is, I can't get the same value from base64 encoding a sha256 digest of the characters up to (or including) the comma. Going to work through some other variations too. |
So, when I crack a CRC like this.. this is how I do it: Write a tiny chunk of code, that puts the "payload" section into a string array or similar. Then, write some for loops, to iterate over it. First, try the whole string, then the whole string minus the last char, then keep knocking chars off the end and iterate. If you have multiple algos, you can iterate those too. Then do the whole thing over, this time skipping chars from the front. If I get desperate, I add another loop, where as I knock each char off the end, I loop through knocking each char off the front too. The reason for this is, sometimes a CRC will only CRC portions of the data, for example, in a message like: 0a55bleahbleahFA where 0a55 is the "command", sometimes they only CRC the payload of "bleahbleah". Sometimes they don't CRC the "length" argument, etc etc. Sometimes there is an "end of command" like the FA, which is or is not included. You never know. Generally speaking, just write something that iterates over as many combos as you can think of, and let it run overnight if you have to. Brute force it. |
That was my first thought, to try trimming the ends of the payload. No luck with sha256. Aware of any popular hash functions that yield 256 bits for embedded applications? The problem with trying to hash only the data is that I haven't definitively identified what is/isn't data. I've hacked together a parser that treats the structure as:
It seems to be stable so far. The only interesting issue I ran into, which I identified last time around, is that when the last byte of a value is >= 0x80, there will be one more byte than normal in that value. I've seen it in both the 0x0a and temperature values. Next chance I have to look at this, I'm going to try building on to the parser to hash some/all of the data and compare to the checksum. |
That's why I'm saying don't try to figure out the data yet. Just take everything from the comma forward in the packet, and hash the whole thing, through the brute force method I mentioned. |
That's what I did. Of the hash functions that yield 256 bits, I'm only aware of sha256 being commonly used. We know it's 256 bits, because that will yield 43 bytes once base 64 encoded. (The 44th byte is just padding.) With such a short list (3600 combinations) it only took a few seconds to iterate through every possibility. I also ran every possible 32-byte chunk of the sha512 hash and base 64 encoding of every possible 32-byte chunk of the payload unhashed with no luck.. Either the checksum is only based off of some pieces of data (and excludes any message structure), or there's something other than bits from the payload used to calculate it (like a string hardcoded on both ends). We also can't yet rule out it not being verified by the thermostat. |
I was about to say they're probably just adding a salt to it. Interesting, the firmware image appears to be a compressed kernel with a jffs2 filesystem. On a lark, thinking I might be able to find something related to the above, I binwalk'd it, extracted the jjfs2 and mounted it. Looks to be a pretty boring busybox linux-2.6.30 arm. Appears that most thermostat functions are done with drivers statically linked into the kernel. Anyone want to try going after them for driver source given the GPL should confer those rights on us. :-) It appears to use a GUI app called 'maestro' which is probably some internal thing that writes to /dev/fb. There are some Sqlite queries but no .db file. There are also some RSA Private keys and certificates. Possibly for Skyport access or something.
|
The manifest.json.gz appears to list a bunch of files that are not in the JFFS2 filesystem. Perhaps there's some other mtd partition on the device that has these files. Still no .ko's listed.
|
So, you can rule out it not being verified by the thermostat reasonably easy, with netcat. Power off one of your sensors, and replay older messages from it, to the thermostat, and see what happens. If the thermostat responds to them, then try swapping the hash out with your own hash, or swapping the hash from two messages around, and see if it ignores you. I'd hate to go all GPL on them, and lose one of the only local api thermostats out there. :) ./lib/modules/temperaturelocal.ko Those look like ko's tho. You might try pulling the init scripts out, and see if any of them start a listener, though it's probably in-app rather than a separate service. That second dump looks like where you store all the images for the backgrounds. I do wish there was an API to switch it to the xmas one. :) |
oh, I totally missed those. They're listed in the manifest but not in the jffs2 that I extracted from the update; so I don't know where they are. |
Nothing illuminating other than I still can't find the rest of the files listed in the manifest.json. I only extracted the 'VC' firmware, there's also "VR", "VH", "VW". I'll look in those and see if there's anything useful in those. The settings.json in the VC firmware claims it's for a T6800 Commercial. |
I was just unpacking the VR and it says 'Residential', so yes. Seems to be the same type of 'stuff' inside... ie: no .ko's. the VH firmware has more goodies inside. |
well anyway, I'm over my 'play time' allowance for the day. I'm sure there's a fitImage or something inside the VH firmware that has an initramfs. Though it appears to have a u-boot inside of it so maybe it's a straight dump of /dev/mtd. VH also appears to be a more modern kernel: Linux version 4.9.127-linux4sam_5.8+ (root@a9ad1ef288b9) (gcc version 7.3.0 (Buildroot 2018.08.1-00003-g576b333) ) #1 Thu Oct 15 23:40:14 UTC 2020q |
Maestro appears to be more than just the GUI. Based on strings, it seems to also serve the local API, SSDP, "OTA" updates, wpa_supplicant config, amongst other things. It only references EVP_sha256 and EVP_sha384, so it likely only supports those two functions for hashing (although they may only be for HTTPS, I haven't found evidence that maestro handles messages on udp 5001). It also references the library libmthermostat.so, which appears to handle thermostat functions. Wonder if this'll be good for anything:
That's md5, so it may be viable to crack. |
Anyone good with assembly? Found the bits that process/validate the sensor messages. Definitely base64 encoded sha256. If there's a salt in there, I don't know enough to find it.
|
Demangling the unique symbols: shared::Logger::log(shared::Logger::Level, char const*, int, char const*, char const*, ...) Probably we'd want to look at maestro::server::SensorManager::validateHMAC or maestro::server::SensorManager::handleDataMessage I have work-pressures getting in the way of fun, at the moment. |
Based on what we're seeing in maestro, I'm feeling fairly confident we're dealing with base 64 encoded HMAC-SHA256 and am trying to properly brute force several variations of the payload:
After testing some things yesterday, I've learned three new things...
I didn't spend a ton of time trying to manipulate different portions of the data, since the only way to know with some level of certainty that it considers the message valid is to wait until it marks the sensor offline before trying, then see if it becomes available after sending the message. (It only considers the sensor offline if it hasn't received any messages for some time.) |
Got a packet capture of the Venstar Configurator app interacting with a sensor and found something interesting...
Beginning here...
Which looks nearly identical to what the sensor broadcasts, just without the digest:
Also, notice it identifies the content type as "application/x-protobuf". Here's the relevant bit of the .proto from the android app: syntax = "proto2";
package sensor;
import "nanopb.proto";
//Sensor state
message INFO {
enum SensorType {
OUTDOOR = 1;
RETURN = 2;
REMOTE = 3;
SUPPLY = 4;
}
enum PowerSource {
BATTERY = 1;
WIRED = 2;
}
enum SensorModel {
TEMPSENSOR = 1;
}
required uint32 sequence = 1 [(nanopb).int_size = IS_16];
required uint32 sensorId = 2 [(nanopb).int_size = IS_8];
required string mac = 3 [(nanopb).max_size = 13];
required uint32 fwMajor = 4 [(nanopb).int_size = IS_8];
required uint32 fwMinor = 5 [(nanopb).int_size = IS_8];
required SensorModel model = 6;
required PowerSource power = 7;
optional string name = 8 [(nanopb).max_size = 15];
optional SensorType type = 9;
optional uint32 temperature = 10 [(nanopb).int_size = IS_8];
optional uint32 battery = 11 [(nanopb).int_size = IS_8];
optional uint32 humidity = 12 [(nanopb).int_size = IS_8];
} Apparently, there are two special temperature values: if($scope.data.deviceInfo.temperature == 255) {
$scope.data.deviceInfo.temperatureString = 'Sensor Open';
}
else if($scope.data.deviceInfo.temperature == 254) {
$scope.data.deviceInfo.temperatureString = 'Sensor Shorted';
} That apk also has firmware for the various wifi sensors, which could be interesting:
|
Nice find (protobuf). I wonder what RTOS they're using. Interesting. |
Have you cracked one open? Is it just an ESP with a sensor? :) |
ESP is not ARM. :) |
Wow.. the sensors are ARM's, that seems so wildly over-powered. I knew the base unit was from my investigations on a broken one, but the sensors. Wow. |
The ACC-TSENWIFIMini is running a FCI FC9000. The original ACC-TSENWIFI is a TI CC3200R1M2, which, based on the firmware in the apk, has been replaced by the similar looking ACC-TSENWIFIPRO also running the FC9000. (I suspect the TI got dumped after it couldn't meet both of their goals of 1 year battery life and a 1-minute update interval, because they actually sent updates every 2 minutes on battery, despite saying 1 minute in the documentation.) Both the FCI and TI variants have 6 small contacts labeled "ICP" that aren't coated like the rest of the PCB. The TI variant also has a spot for 6 header pins. Can't find much info on these. Both have one 3.3v and one ground. I soldered pins on the TI and tried hooking each pin to the RX on a rs232 adapter. Found one that was doing something, but only got garbage (or nothing) at every baud rate I tried. ACC-TSENWIFIMiniACC-TSENWIFI |
You probably found the JTAG pins. |
That was a thought I had. Definitely wayyyy outside my area of expertise here. (I'm a network/server/security person at $dayjob.) |
yeah, and probably wouldn't yield much useful information. Or rather, there's still plenty of potential traction without resorting to JTAG. |
Hopefully not. Seems like we've got just about everything we need except for the HMAC secret at this point. Still working on that, but I've got to slow it down to keep my office a reasonable temp during the day. |
I spent a couple mostly fruitless hours manually decompiling the code above. I should have looked at it more before making my stupid comments in #34 (comment) :-) Anyway, I'm not good at decompiling EABI but I'm left with the impression that the code seems to be checking the characteristics of the MacAddr (probably of the inbound message) and possibly passing all or part of it off to sha256. maestro::server::SensorManager::handleDataMessage() is calling maestro::server::MacAddress::operator==(char const*) const, comparing it against one of the arguments of handleDataMessage() (returning a fail if the cmp doesn't succeed) and then calling maestro::server::SensorManager::validateHMAC() which does the base64 decode and sha256. But since the 'identifier' in the message already contains a mac address, I'm not convinced they're also using it as a SALT. |
It's like a scab. I can't stop picking at it. I used Chrome to format 'stat.mxe' and then started maestro2 using that and it no longer crashes. Now it appears to be 'running' (though it's unahppy about sensors obviously.
I set a breakpoint at HMAC() and of course it doesn't get there. I haven't tried sending a sensor packet to it.
|
That's how I was yesterday. Try spoofing a packet with this:
That's a "button press" packet, so it should get it to do things, even if it's not paired. |
Nothin. The packet did show up on the Pi though on the 'eth0' interface because I don't have my 'wlan0' configured. Not sure if it specifically binds to
I'll play more maybe later or tomorrow. I'm over my playtime allotment again. |
Hmm. That sucks. I feel like we're so close. Were you running that on the same RPi as maestro2? It won't receive it's own broadcast traffic, so you'd have to do something more like:
Maybe with something other than the loopback interface as the destination. You can check what's listening on UDP 5001 with I'm stuck trying to get a working frame buffer in qemu. It definitely won't work without one. I never did find the missing files from the manifest. I'm guessing some of the stuff like the kernel modules may only exist in the initrd. Looking at {
"uri":"https://ctupdate.skyport.io/feed",
"clientCert": "/home/secure/skyport_public.pem",
"clientKey": "/home/secure/skyport_private.pem",
"caInfo": "/home/volatile/skyport_root.pem",
"firmwareCert":"/home/gui/codesign.pem",
"env":"/dev/mtd2",
"envVar":"bank",
"kernelA":"/dev/mtd4",
"kernelB":"/dev/mtd5",
"rootA":"/dev/mtd6",
"rootB":"/dev/mtd7",
"interval":240
}
|
Compiled the virtual framebuffer kernel module and I've got it running now. Bad news... it's not listening on UDP 5001:
Tried running the startgui script, but still nothing listening on UDP 5001. (Just got spammed with |
Built a virtual touchscreen driver, loaded it, and launcher.mxe is much happier. Had to change Even so, it still gives up for some reason:
It looks like the last file it's interacting with is |
Yes, I was sending packets from a different host. I thought for sure I saw something listening at 5001 yesterday but clearly not. whatever is camped out on 1900/udp isn't reading from its socket. It's recvq is full. anyway, I can't get sucked into this today. btw, the .db's have fairly simple schemas if you go at them with sqlite3 CLI. |
Isn't 1900 SSDP? |
It should be, and there are references to SSDP all over in maestro, so it'd make sense. I did look at the data in gui.db a bit. Nothing obvious that'd change the behavior for remote sensors. Guess it could be waiting for connectivity on the wireless interface before actually taking its network responsibilities seriously. I should hopefully have a bunch of microSDs later today to begin working on this from an RPi. Curious to see what output (if any) I get on the framebuffer at each stage. Hopefully that shines some light on why we aren't seeing normal behavior. The virtual touchscreen interface I found does have a client to send touch events, so we should be able to interact with the interface as normal. I also have a second thermostat on order to attempt some more potentially dangerous things. It's been dipping below freezing lately, so I can't risk breaking the one that actually keeps the house warm. |
Yeah, it definitely does SSDP. I have C code that talks to that, but I'll be eventually be adding discovery to the HA integration. |
I just remembered, when I was working on discovery for my HA integration, SSDP was not at all reliable. It would typically be several days between SSDP announcements. Maybe it's a buggy implementation and whatever is causing that delay is related to the receive queue being full. |
I wonder if the SocketHandler thread is blocked on the WiFi Setup dialog. Maybe I'll try to drive the touchscreen through /dev/input/event1 later after my 'work day' ends. I once hacked this into streaming from /dev/fb0 ... https://github.com/n3wtron/simple_mjpeg_streamer_http_server |
I was able to get SSDP 100% reliable in C. My firmware is older tho. I'll have to test again with my more modern one. I'm not sure about the announcements, but normally you force one by asking for them with an SSDP scan. |
I let HA handle the scan and it'd rarely work. It may be possible to make it more reliable, but doing it HA's way wasn't – at least on the latest stat firmware. It's also possible the bug is only in newer firmware or only certain models. |
I hacked this up to stream the contents of /dev/fb0 while running maestro. It did correctly stream the above Wifi dialog but after restarting maestro to see what intermediate screens it would display, there weren't any and I can't get that dialog back. So now I'm not sure what to think. But I also don't have time to dig into it any more this week.
Edit: I lied. It does come up, if I wait long enough. |
Could we do a replay attack? Capture sensor messages for every 0.5 C increment in a reasonable range and replay them to the thermostat? I'm looking to get my own temperature data into the thermostat via my home automation system. The data will come from various Arduino and Acurite sensors throughout my house. All I need is to fake one Venstar Wifi sensor to get a temperature value in. |
@pavlohamov any hints? |
Guys, could you please advice, how can I dump firmware from my own Colortouch? |
We were working with the firmware images on the Venstar site and simply mounting them as images either using qemu tools or losetup under Linux. I don't think we've figured out (or tried to figure out) how to dump images straight from the devices themselves. |
Got it. Are those images still available to download so that I can play with them? |
I don't know. I would imagine so since they're the images that the devices download to upgrade themselves. Before you ask, I don't know the URL anymore so I would have to figure that out again. |
Do I need to use Wireshark to capture this link it somehow? |
Yes. It wouldn't scale well for faking multiple sensors since every message includes everything about the sensor (name, id, MAC, power source, battery level, temp). If you only want to fake one, you could.
https://apps.skyportlabs.com/ColorTouchDesktopApp/setup.exe You'll need to run the app to extract the firmware. I gave up on Venstar, due to poor handling of aux heat with heat pumps (and an unwillingness to address issues), and switched to a RPi with one of these running Node-RED. After finding out Venstar stats were just ARM devices running linux, I was a lot less afraid of reliability issues doing the same thing to build my own. |
That's okay. I suspect whoever needs this functionality could get away with only faking one sensor as well. That's because their automation system could decide what actual sensors to listen to or average in software, and send the data via the fake one.
Were you able to get this replay to work? It doesn't seem to be doing anything for me :( |
Replaying messages caused an unavailable (timed out) sensor to return to an available state when I was testing in the past. That message may not be enough to get the stat to add a new sensor. I never captured the initial pairing to see if there was any unicast traffic exchanged. No reason you can't do the pairing with the real sensor and replay the messages for the fake. Like I said, I've moved on from Venstar, so I've not got anything set up to test with at this point. |
Want to ship me your old wifi sensor? I'll pay for shipping and publish any of my findings. I'm having a hell of a time finding them in Canada conveniently. Also be nice to reduce e-waste. |
Sure. Email me, (redacted). |
Hey folks, My temp pros wont stay on the network and I was thinking of replacing with my own ESP or RP2020 + BME devices. Did you ever get any further? |
The protocol for Venstar Wi-Fi sensors is undocumented. Let's try to figure it out, so we can decode/encode them.
What I've identified so far
The sensors broadcast their data to the local network (255.255.255.255) and multicast to the all hosts group (224.0.0.1) on UDP port 5001. Updates are sent every minute for mini Wi-Fi sensors set to "remote", every two minutes for the original Wi-Fi sensors set to "remote", and every 20 seconds for "supply" and "return".
Messages appear to be a series of TLVs, with the length being unspecified for some types.
Type:
08
= State,10
= Unit ID,1a
= Identifier,30
= Firmware version,42
= Name,48
= Sensor Type,50
= Temperature,58
= BatteryState:
2a
= Normal,2b
= Ready to link (button pressed)Unit ID:
00
to13
Identifier: Only MAC address observed
Sensor type:
01
= Outdoor,02
= Return,03
= Remote,04
= SupplyTemperature:
00
= -40.0°C toff
= 87.5°C, in 0.5° steps. Temperature increases at a rate no faster than 1.5°/update and decreases at 0.5°/update.Sample messages
First line is when the message was received, for an understanding of timing.
Second is the message in hex.
Third is the message in ascii with non-printable characters replaced by a period.
Messages sent by sensors during normal operation
Messages sent by sensors during link mode
What are your thoughts, @garbled1?
The text was updated successfully, but these errors were encountered: