# Developer Attacks Robot! (adventures in reverse engineering) ## outline - Introduction - - I'm writing this up because these skills aren't just useful for dealing with robots; they're a good way to figure out existing systems debugging legacy code. - I'm doing this all with Linux, but this all can be done - snoop the traffic - let's set up as an access point and just use Wireshark to see what it's doing. - close everything, put it in battery-saving mode - DNS! Fine! - HTTPS! Urgh. - And what's this stuff? - XMPP - what's this protocol - it's controlling the vacuum, so let's dig in - what can I use? aha, mitmproxy - wow, that's a bunch of junk! - boil it down some - boil it down some more - start hacking - simple XMPP script - what's this auth stuff - an RFC side trip - send some commands - oh my god, it works! - auth troubles - now it doesn't work, what happened? - the android app is breaking things - now we have to look at the HTTPS stuff - mitmproxy to the rescue - HTTPS - 6 calls in 2 sets but hey, there in #4 is a token that works - Let's make our hack script work for the second set - it works! But what about this garbage? - We can't figure it out from the protocol alone. To the code! - APK decompliation - Let's search for key strings - Well, that's some strings. Where are the rest? - Ok, more strings. Let's try implementing something. - Can we send it with no signature? Nope. - Can we send it with a fake signature? Nope. - Gotta do it for real. Let's bring it! - Hack, fail, hack, fail. (Scary! Low feedback!) Hack, succeed! - Wrapping up - Let's make a tidy final script (and share it!) - What did we learn? ## tl;dr I bought a robot vacuum. The control app didn't do quite what I wanted. Here I explain in too much detail how I figured out how the app was talking to the robot so that I could control it from my own code. Tools used include Wireshark, mitmproxy, xmppproxy, adb, and a Java decompiler. ## introduction After many years as a sysadmin and developer, after thousands of hours spent running down problems, this is basically how I feel about other people's code: > I don't know who you are. I don't know what you want. If you are looking for > ransom I can tell you I don't have money, but what I do have are a very > particular set of skills. Skills I have acquired over a very long career. > Skills that make me a nightmare for people like you. > -- [Liam Neeson, Taken](http://www.esquire.com/entertainment/movies/a31775/taken-speech/) Recently, I bought a robot vacuum, the [Wirecutter-recommended](https://thewirecutter.com/reviews/best-robot-vacuum/) [EcoVacs Deebot N79](https://www.ecovacs.com/us/deebot-robotic-vacuum-cleaner/DEEBOT-N79/). It's honestly a pretty swell piece of hardware. Watching it go, it's clear that it's the product not just of smarts, but of a lot of thoughtful experimentation. Compared with the early Roomba I had, it feels *evolved*. But the Android app is... not as good. In particular, you can have it start cleaning at a certain time. However, it only stops cleaning when it runs out of battery, or circa 90 minutes. My place is small; I need maybe 10 minutes of daily cleaning. That bothered me. It's a mechanical device, and its brushes and filters are consumables. It's not expensive, but having it waste 8/9ths of its lifespan cleaning already-clean floors bugged me. I also didn't need the extra noise and hassle. So first, to Google! Did anybody already fix this? Nope. Is there a public API? Nope. Have other people looked into this? Turns out yes! There was a forum post with a little discussion and some sample code. Which was interesting, but not enough to build from. How to I find out how to control my property? Here follows my answer to that. It's a cleaned-up version of my explorations, written to share some of my tricks with others, and help people get more comfortable figuring out what the hell is going on with their tech. My target audience is people who code at least enough to be dangerous. By the end, I hope you'll be more dangerous. ## you say you wanna snoop The first quesiton to answer: what the hell is that app doing?: Modern phones are, for good reason, basically sealed boxes. I could root my phone so that I could do more effective debugging from there. But since I mainly care about what it's doing over the network, I'm going to start with snooping the network traffic. ### Wireshark to the rescue! An easy way to capture all that traffic is to make your laptop a wifi router. I"m using an Ubuntu laptop, so I needed to plug into an Ethernet network and then make a few [simple networking changes](https://askubuntu.com/questions/180733/how-to-setup-an-access-point-mode-wi-fi-hotspot). (Other OSes have similar functionality, so you should be able to play along.) Once I got that working, I connected my phone to the new wifi access point. The next step was to make things clean and simple. Complexity is the enemy of any good debugging effort. I put my phone in Battery Saver mode, which minimizes background activity. I force-stopped the EcoVacs app and cleared out its data, so we were starting fresh. Then I rebooted my phone, turned Battery Saver back on, and let it sit for a couple minutes so its startup activity was hopefully complete. I also checked out the network settings to see what my phone's IP was. That will become important once we have some packets captured. That ready, I started up one of my favorite tools, Wireshark. This is basicially x-ray vision for networks, and it is fucking magic. It has a zillion options and tools and protocol decoders, most of which we will studiously ignore. I told it to start capturing my wireless interface. It shows you what's happening in real time, so I saw various traffic ticking by, including a bunch of Google stuff. That seemed good, and it was my phone's IP address, so I was capturing the right activity. Now to start up the app! I jotted down the exact time in my notes and started up the EcoVacs app. I noted the time I hit the login button, and watched it churn for a bit as it found my vacuum and got connected. I noted the time again when I told the vaccum to start cleaning. After a bit, I told it to go back to its charger. When all was quiet again, I stopped the capture. [SIDEBAR: In this article, I'm making it sound like I did things once. The truth is that I did most of these steps many times, separated by periods of confusion and swearing. You're getting the edited version here, because the truth would be deadly dull. But as you go through this, don't be discouraged if you have to try something again and again because the results were messy or puzzling.] ### what's all this garbage? If you're playing along, you now have a screen with a lot of technical-looking stuff, done up in the [angry fruit salad](http://catb.org/jargon/html/A/angry-fruit-salad.html) school of user interface design. Scrolling, there are thousands of entries, each more arcane than the last. What is it? Well, honestly, for the moment I don't care , and you shouldn't either. There's 30 years of layered technical history that is undeniably cool and interesting. Your phone does a lot! Other things on your network are also doing a lot! Later on you can come back, pick a random packet, and try to figure out what it's doing. But for now, you should treat this like you're an easily distractable person shopping at a megastore. Don't look at everything; just find what you need and get the hell out. A great place to start is DNS. That is the protocol where domain names get turned into internet addresses, a necessary step before any connection is made Since DNS mostly runs over UDP port 53, I type in a filter at the top of the window: "udp.port==53". (How did I know it was 53? Experience. But you can check out a [short list](http://www.pearsonitcertification.com/articles/article.aspx?p=1868080) of common port numbers or Wikipedia's [more comprehensive list](https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers).) Now I have a fair bit less garbage. I see queries about things I know are on my phone: Wikipedia, Lastpass, Mint, and of course Google. As I get down to the timestamp for when I started the app, I see a familiar name: ecovacs.com. That's a good sign! Now I'm in the right place. I set the this a a timestamp reference. Now the times are relative to when the app got going, making it easier for me to keep track of things. Scrolling down, I see it look up some related domain names: eco-us-api.ecovacs.com, users-na.ecouser.net, ne-na.ecouser.net, msg-na.ecouser.net, and a few others. I keep those in mind for future investigation. Now that I'm in the right ballpark, I clear the filter and start looking at the raw traffic. What does it do right after it looks up the eco-us-api name? It connects to it! Yay! There are a whole bunch of packets going back and forth between my phone and that server. If I clieck on the first, Wireshark even shows in the left edge that all those packets are related. However, the fact that it's connecting to port 443 is a problem. That's the port for HTTPS. The S meaning Secure, meaning encrypted, meaning naughty snoopers like us can't see anything useful. There's a solution for that, but let's keep looking and see what else we have. Further down, there's a connection on port 5223. That's unusual, and Wireshark just calls the protocol TCP, meaning it's not sure what to make of it. What is it? To find out, I bring up the menu for one of the packets, pick "Follow", and then "TCP Stream". This pulls all the packets together for connection, extracts the payload as texts, and then displays the client as red and the server as blue. This is one of my favorite Wireshark tools. Expecially for plain HTTP and other non-encrypted protocols, it's great for seeing what's really going on. Here, we see some XML at the top and then it decends into mostly garbage. Let's look at the XML. ``` PLAIN ``` I don't know much about this, but there's an obvious hint. So I look at jabber.org, and it mentions XMPP. I hazily recall Jabber as an instant messaging tool, and XMPP is the protocol. That seems weird, but the [XMPP website](https://xmpp.org/) mentions that a common use case is Internet of Things. That makes some sense. If you have a lot of things, like my phone, wanting to talk occasionally to other things, like my robot vacuum, IM is a well-tested approach. The TLS thing, though, is a bit of a problem. When it says starttls, it's upgrading from the insecure connection, which we can read, to a secure connection, which we can't. (For more, see Wikipedia's [Opportunistic TLS](https://en.wikipedia.org/wiki/Opportunistic_TLS) article.) But there's some good news. As I scroll down in the Wireshark extract, I see this bit: ``` AU1.0...U... Some-State1!0...U. ..Internet Widgits Pty Ltd ``` AU? Some State? Interenet Widgits? I recognize those as default values for a [self-signed certificate](https://en.wikipedia.org/wiki/Self-signed_certificate). That means the app is maybe not so serious about security here. So this seems like a good place to break in. ## robot jabber I do a little more reading on [XMPP](https://en.wikipedia.org/wiki/XMPP). It's a proper internet standard and everything, so I flip through [RFC 6120](https://tools.ietf.org/html/rfc6120), [RFC 6121](https://tools.ietf.org/html/rfc6121), and [RFC 6122](https://tools.ietf.org/html/rfc6122), the standards documents that describe it. It feels like (and is) something from the late 90s, when XML was all the rage. I rummage around a bit, an a search for "xmpp mitm" (that being Man In The Middle), I find [xmpppeek](https://www.beneaththewaves.net/Software/XMPPPeek.html), an XMPP proxy. I love this kind of thing: somebody made it for their own needs, found it useful, and put it up on the Internet just in case. ### looking inside the XMPP I install xmpppeek. But how to get the app to talk to my server instead of the real one? Lies. In particular, my router lets me set up hostnames. That's probably so I can easily set up a home server, but here, I'll abuse it. I tell it to lie about the address for [TODO: server name] and return my laptop address instead. Then I start up xmpppeek and tell it to forward to the real EcoVacs server. It complains, saying it needs its own encryption certificate. I can never remember the incantation for that, but the Internet does, so shortly it's up and running. Will it work? I start up the Ecovacs app and shit starts scrolling on my laptop screen: I don't know what half of this means, but we can definitely see farther than before into the XMPP transaction, so this looks like a step forward. And this bit is particularly interesting: ``` [2017-11-15 14:49:48.016593-0MjAxNzEwMjU1OWYwZWU2M2M1ODhkADIwMTcxMDI1NTlmMGVlNjNjNTg4ZAAwLzEzYjQxMjk5L2V5aDlXZ0NOOVFkWGxEZkNDY1I1cVBBMUtLSnZ4eWts7:00] [(S2C) 47.88.66.164:5223 -> 192.168.1.227:48607] }}}PLAIN{{{ [2017-11-15 14:49:48.255088-07:00] [(C2S) 192.168.1.227:48607 -> 47.88.66.164:5223] }}}MjAxNzEwMjU1OWYwZWU2M2M1ODhkADIwMTcxMDI1NTlmMGVlNjNjNTg4ZAAwL2VmY2Y5M2U2L2dyOWhDZkc0Q3NmeVU3QlRQeVRDNkJaWVgxQjFVazhE{{{ [2017-11-15 14:49:48.444622-07:00] [(S2C) 47.88.66.164:5223 -> 192.168.1.227:48607] }}}{{{ ``` The server says, "for authenticating, I like the PLAIN method". The client says, "cool, here's a lump of PLAIN auth". The server says, "success". And then we say, in our best hacker voices, "I'm in!" If we can authenticate with something straightforward, maybe we're good to go. Fishing further, I see something that looks like a username "2017102559f0ee63c588d@ecouser.net". Maybe that will come in handy.There are also nice bits of XML like `` when I hit the auto-clean button in the app, and `` when I have it stop. Once we're connected, bossing the robot around should be easy. So how do we get connected? ## ruby slippers For things like this, I like Ruby and Python. The languages are easy to get started with, forgiving, and they have a ton of libraries. For this, let's try Ruby. I quickly find an [XMPP client library](http://xmpp4r.github.io/) that isn't too stale and even a [simple how-to article](https://www.sitepoint.com/looking-xmpp-xmpp4r-gem/). So let's see if we can build something that at least talks to the server. I use bundler to install the xmpp4r gem and then write the dumbest possible program: ``` require 'xmpp4r' require 'xmpp4r/client' include Jabber Jabber.debug = true jid = JID.new('2017102559f0ee63c588d@ecouser.net') client = Client.new(jid) client.connect ``` I run that and it works! It connects. It doesn't do anything, but I'm on the right trail. Can we log in just using the magic string from the XML above? I add this: ``` client.auth('MjAxNzEwMjU1OWYwZWU2M2M1ODhkADIwMTcxMDI1NTlmMGVlNjNjNTg4ZAAwL2VmY2Y5M2U2L2dyOWhDZkc0Q3NmeVU3QlRQeVRDNkJaWVgxQjFVazhE') ``` Does it work? No. In the debugging output, it sends a different, longer string than the one we gave it. Now, alas, we have to actually learn something about how this works. Luckily, XML is an implausibly verbose protocol, and looking at the auth exchange there are valuable clues like SASL and PLAIN. Going back to the [XMPP spec](https://tools.ietf.org/html/rfc6120#section-6) there is a whole section on SASL, and SASL even has an [RFC of its own](https://tools.ietf.org/html/rfc4422). The XMPP RFC tells us that data gets encoded as base64. So let's try decoding that. ``` $ base64 -d MjAxNzEwMjU1OWYwZWU2M2M1ODhkADIwMTcxMDI1NTlmMGVlNjNjNTg4ZAAwL2VmY2Y5M2U2L2dyOWhDZkc0Q3NmeVU3QlRQeVRDNkJaWVgxQjFVazhE 2017102559f0ee63c588d2017102559f0ee63c588d0/efcf93e6/gr9hCfG4CsfyU7BTPyTC6BZYX1B1Uk8D ``` I give it the first line; it gives me the second. The first part of that looks like the username above, but then it trails off into nonsense. What's the rest? More rummaging leads me to [RFC 4616](https://tools.ietf.org/html/rfc4616), which is specifically about SASL PLAIN. There it explains the format: ``` message = [authzid] UTF8NUL authcid UTF8NUL passwd authcid = 1*SAFE ; MUST accept up to 255 octets authzid = 1*SAFE ; MUST accept up to 255 octets passwd = 1*SAFE ; MUST accept up to 255 octets UTF8NUL = %x00 ; UTF-8 encoded NUL character ``` That's somewhat helpful. A password is somethign I understand. The docs say that the two ids are the *authentication id* and the *authorization id*. I don't know what the difference is, but I have learned that there is probably a NUL character, which doesn't show up on my screen. To see it, I whip out one of my favorite tools: od. This is an old-school Unix utility, short for "octal dump", because octal numbers were used heavily back in the day. (Now generally we use hex.) But it does a ton more than that. In particular, its `-c` option will dump things as characters when it can, and then show us the hidden things. So now I try again: ``` $ base64 -d | od -c MjAxNzEwMjU1OWYwZWU2M2M1ODhkADIwMTcxMDI1NTlmMGVlNjNjNTg4ZAAwL2VmY2Y5M2U2L2dyOWhDZkc0Q3NmeVU3QlRQeVRDNkJaWVgxQjFVazhE 0000000 2 0 1 7 1 0 2 5 5 9 f 0 e e 6 3 0000020 c 5 8 8 d \0 2 0 1 7 1 0 2 5 5 9 0000040 f 0 e e 6 3 c 5 8 8 d \0 0 / e f 0000060 c f 9 3 e 6 / g r 9 h C f G 4 C 0000100 s f y U 7 B T P y T C 6 B Z Y X 0000120 1 B 1 U k 8 D 0000127 ``` This is much better. The NUL character shows up as \0 here. So we can see that the `authcid` is our username. And the `authzid` is too, so I guess we don't have to understand the difference. And the rest must be the password. Let's try authenticating with that. And no! It doesn't work. But why? Looking at the debugging output from the script, I try feeding that into od -c as well. And I discover that it is sending the @ecouser.net as part of the username. That's no good! Maybe the username's just the first part. SOMETHING ELSE IS FUCKED UP ABOUT THIS LIBRARY. I taught it to do EcoVacs style auth and it is definitely not working. So there's something else in the flow that isn't right. But what?