The other day a collogue dropped a small box on my desk with the cryptic message "See what you can do".
Turns out he acquired a few Amazon Dash buttons and wanted
to use them for purposes other than ordering diapers and peppermints.
I had remarked a few weeks earlier how it's almost impossible to stop people from using Amazon Dash buttons outside their normal scope - securing hardware is tricky. When you own the network on which devices reside it's no problem to fool them it into accepting a reality you designed.
But talk is cheap... now with a button in front of me it was time to turn theory into practice.
Let's talk about what I knew about these buttons at this point: They are sold by Amazon for a low price of 5 US dollars, it talks to the servers of Amazon using your own WiFi network and a single button can be used for a few different products of a specific brand.
The low cost implies that they are using off-the-shelf components and are limited in the trickery they can perform, the fact that it uses your own WiFi means it should at least somewhat behave like a regular device. It should also have some kind of configuration step, otherwise you wouldn't be able to change the specific product that gets ordered. As long I never completed this setup I should never accidentally end up with a lifetime supply of diapers.
Now I was ready to turn on the button to see how it
works. I followed the short manual that came with the button: I
downloaded the Amazon app for my Android phone, logged in on my account
and pushed the Dash button. On my phone a popup showed up where I had to
enter the WiFi password. I pressed submit and bingo: Our DHCP server
saw a request coming by for a new device!
Feb 29 10:08:00 gatekeeper dnsmasq-dhcp[20311]: DHCPDISCOVER(eth1) a0:02:dc:a3:be:30
Feb 29 10:08:00 gatekeeper dnsmasq-dhcp[20311]: DHCPOFFER(eth1) 10.50.254.254 a0:02:dc:a3:be:30
Just for good measure I null-routed the MAC address of the button so it wouldn't be able to go outside our own network no matter what - no internet for you.

While encryption done properly is nearly impossible to break, it's not uncommon to have a badly set up system could still be susceptible to attacks. For example, the Python 2 binary used to skip the verification of the CN/SAN and also skip the certificate signature (PEP 476). Default connections over HTTPs used to be encrypted, but *not* secured!
A nice way to test this is the CLI tools provided by the OpenSSL project. I generated a self-signed key for parker-gateway-na.amazon.com by running
openssl req -x509 -new -keyout key.pem -out cert.pem -days 365 -nodes
and then I ran a simple TLS server with
openssl s_server -accept 443 -cert cert.pem -key key.pem
I tested it out with Firefox: When connecting I got a big warning in Firefox about the connection being unsecured due to a self signed certificate. When I told Firefox to go ahead anyway I saw Firefox's HTTP headers showing in my console.

To get the button to talk to my fake server I modified the DNS server to resolve parker-gateway-na.amazon.com to my own server's IP address. I hit the button and... not much happened. The button would connect, but instead of the button sending the HTTP headers it disconnected. This suggests that either the button can't handle the type of certificate / TLS setup or that it does proper certificate verification.
At this point I could do two things: Try to dig deeper
into the SSL business in order to get the button to spill its guts or
use the existing observable behaviour to detect when the button was
pressed.
A quick Google revealed some people were able to intercept the HTTP traffic,
which would be nice because then you could potentially also control the
LED color. A downside of this approach would be that any changes
introduced by Amazon could easily break the detection, requiring me to
fix it again.
Alternatively, I could use the ARP traffic that is used to
try to go online to detect button presses. It's a much simpler
approach, it's unlikely to break because of Amazon and it already worked
reliably.
In the end it came down to my preference for a simple
reliable system over a fancy, fragile piece of work: ARP detection it
is.
The last part of this blog post would describe the elaborate process of creating an ARP detection system. Unfortunately, with Python and Scapy it's all quite straight forward and there's not much to discuss.
The interpretation of the script is left as an exercise to the reader.
#!/usr/bin/env python3
from datetime import datetime
from scapy.all import sniff, ARP
BUTTONS = {
'a0:02:dc:a3:be:30': 'Ice Breakers',
}
def arp_display(pkt):
# op=1 -> who-has (request)
# psrc='0.0.0.0' -> ARP probe
if pkt[ARP].op == 1 and pkt[ARP].psrc == '0.0.0.0':
mac = pkt[ARP].hwsrc
if mac in BUTTONS:
print("{} ARP Probe from Dash button {} ({})".format(datetime.now().isoformat(), BUTTONS[mac], mac))
# insert mundane API calls here
def run_arp_monitor():
print('{} Monitoring ARP probes'.format(datetime.now()))
sniff(prn=arp_display, filter='arp', store=0)
if __name__ == '__main__':
run_arp_monitor()