From b5a3e8df5761da5058d54aeb8eb909749b3b617c Mon Sep 17 00:00:00 2001 From: William Pietri Date: Mon, 4 Dec 2017 19:57:09 -0800 Subject: [PATCH] removing accidentally committed cruft --- article/index.md | 398 ---------------------- sucks/__pycache__/__init__.cpython-35.pyc | Bin 12810 -> 0 bytes sucks/__pycache__/cli.cpython-35.pyc | Bin 6106 -> 0 bytes 3 files changed, 398 deletions(-) delete mode 100644 article/index.md delete mode 100644 sucks/__pycache__/__init__.cpython-35.pyc delete mode 100644 sucks/__pycache__/cli.cpython-35.pyc diff --git a/article/index.md b/article/index.md deleted file mode 100644 index 4bdb314..0000000 --- a/article/index.md +++ /dev/null @@ -1,398 +0,0 @@ -# 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? diff --git a/sucks/__pycache__/__init__.cpython-35.pyc b/sucks/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index db39b2a735c02c065d8bc28075ccc0c003d242f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12810 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 F{{vyA!)9h2#aFd>ePza;!J%ag*d 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