{"id":1042,"date":"2020-06-09T15:33:45","date_gmt":"2020-06-09T15:33:45","guid":{"rendered":"http:\/\/naich.net\/wordpress\/?p=1042"},"modified":"2021-02-18T19:59:17","modified_gmt":"2021-02-18T19:59:17","slug":"abusing-public-wifi-access-point-protocols-for-fun-and-beer-measurement-raspberry-pi","status":"publish","type":"post","link":"https:\/\/naich.net\/wordpress\/index.php\/abusing-public-wifi-access-point-protocols-for-fun-and-beer-measurement-raspberry-pi\/","title":{"rendered":"Abusing Public WiFi Access Point Protocols for Fun and Beer Measurement (Raspberry Pi)"},"content":{"rendered":"\n<div class=\"wp-block-image\"><figure class=\"alignright is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/naich.net\/wordpress\/wp-content\/uploads\/2020\/06\/20191224_153152-1024x769.jpg\" alt=\"\" class=\"wp-image-1043\" width=\"355\" height=\"264\"\/><\/figure><\/div>\n\n\n\n<p><em>This is a little sub-project of what I&#8217;ve been working on recently &#8211; a hideously over-engineered Raspberry Pi-based system to measure the amount of beer left in the kegs in my <a href=\"https:\/\/www.google.com\/search?sxsrf=ALeKk03vQQX71S2T302Wg8xq-e9qhWiA_w:1591283907539&amp;source=univ&amp;tbm=isch&amp;q=keezer&amp;sa=X&amp;ved=2ahUKEwi7qq7AuujpAhUyThUIHRLyBTIQsAR6BAgIEAE&amp;biw=1299&amp;bih=637\">keezer<\/a>.<\/em><\/p>\n\n\n\n<p class=\"has-text-align-left\">Normally I would simply set up a web server on the Pi and have it on the home network, so I could see the levels remotely. The problem is that the routers are all inside the house and the Pi is in the garage, invisible to them all thanks to the 2 external walls between them. I needed some way to read out the beer levels on my phone &#8211; after all, walking up to something and looking at the level gauge is so last millennium.<\/p>\n\n\n\n<p>So &#8211; Bluetooth or some sort of ad-hoc Wifi thing? I like to re-use stuff I&#8217;ve got lying around in drawers, so the solution seemed to be an old WiFi dongle that was gathering dust. And Bluetooth is awful. Setting up a Pi as an access point is fairly well covered on the internets, but this is a bit different in that we don&#8217;t want to forward traffic onto our network like an access point &#8211; not that it could connect anyway, being out of range. I also didn&#8217;t want to install a web server on the Pi. It&#8217;s only a Pi 1 model B, so sticking Apache and PHP on it might be asking a bit much &#8211; especially when you can do it all with one command and a small BASH script.<\/p>\n\n\n\n<p>So the cunning plan was to take advantage of a feature of public access points &#8211; the ones that show you a registration page for you to fill in with fake info. <\/p>\n\n\n\n<p>When you connect to a public WiFi hotspot your device tries to load a page on the internet using non-SSL http. It might be any page (captive.apple.com\/ seems to be popular), but it will be a web page that the device knows should exist and if it loads, your device knows the internet is working. <\/p>\n\n\n\n<p>A public access point intercepts the page request and, rather than forwarding it, sends a 30x redirect HTTP response back to the device &#8211; basically hijacking the request and spoofing the reply. Your device then loads up the page it has been redirected to and displays it as a sign-in page.<\/p>\n\n\n\n<p>It is this mechanism that I used to show the keg levels on any phone, just by connecting to the Wifi. This is how to do it if you want to do something similar. I&#8217;m assuming you SSH on to a Pi connected with an ethernet cable to your network, and you have a Wifi dongle hanging out of its USB port. In all likelihood they will be <code>eth0<\/code> and <code>wlan0<\/code> respectively, so I&#8217;ll use them.<\/p>\n\n\n\n<p><code>wlan0<\/code> is going to use a different range of IP addresses from the ones used by <code>eth0<\/code>, so edit <code>\/etc\/dhcpcd.conf<\/code> to manually assign an IP address to the <code>wlan0<\/code> interface. Add this at the bottom (comment out any existing definition for <code>wlan0<\/code>):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>interface wlan0\n    static ip_address=192.168.4.1\/24\n    nohook wpa_supplicant<\/code><\/pre>\n\n\n\n<p>Next we need to install <code>hostapd<\/code> to run the hotspot and <code>dnsmasq<\/code> to sort out assigning IP addresses to devices that connect.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sudo apt-get install hostapd\nsudo apt-get install dnsmasq\nsudo systemctl stop hostapd\nsudo systemctl stop dnsmasq<\/pre>\n\n\n\n<p>The second two commands disable the services we just installed so we can edit config files before starting them again.<\/p>\n\n\n\n<p>Create the file <code>\/etc\/dnsmasq.conf<\/code> and put this in it:<\/p>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\n<pre class=\"wp-block-code\"><code>interface=wlan0      # Usually wlan0\ndhcp-range=192.168.4.2,192.168.4.20,255.255.255.0,24h\naddress=\/#\/192.168.4.1<\/code><\/pre>\n<\/div><\/div>\n\n\n\n<p>This tells dnsmasq to assign the range 192.168.4.2 &#8211; 192.168.4.20 with a netmask of 255.255.255.0 and a lease time of 24 hours. The third line tells it to return the server address for all domain lookups that aren&#8217;t in <code>\/etc\/hosts<\/code>, i.e. all of them. When dnsmasq restarts it will look at this file and load up the config information.<\/p>\n\n\n\n<p>Now to set up <code>hostapd<\/code>. Create <code>\/etc\/hostapd\/hostapd.conf<\/code> and put this in it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>interface=wlan0\ndriver=nl80211\nssid=Your SSID here\nhw_mode=g\nchannel=7\nwmm_enabled=0\nmacaddr_acl=0\nignore_broadcast_ssid=0<\/code><\/pre>\n\n\n\n<p>It&#8217;s pretty obvious what is happening there, other than some of the technical bits; <code>wmm_enabled<\/code> is something to do with packets (no idea what, though), <code>macaddr_acl<\/code> tells it to whitelist all connections and <code>ignore_broadcast_ssid<\/code> tells it to broadcast the SSID &#8211; set it to 1 to hide it. There is no WPA password or setup, obviously. Change the SSID to something hilarious.<\/p>\n\n\n\n<p>Now you need to tell hostapd where to find the config file when it starts. Edit <code>\/etc\/default\/hostapd<\/code> and add (or uncomment and edit) the line:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DAEMON_CONF=\"\/etc\/hostapd\/hostapd.conf\"<\/code><\/pre>\n\n\n\n<p>We have now set up our access point. Start <code>dnsmasq<\/code> and <code>hostapd<\/code> again:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sudo systemctl start hostapd\nsudo systemctl start dnsmasq<\/pre>\n\n\n\n<p>If there are no errors, your AP should show up in the list of APs on your phone, laptop etc. Try connecting to it &#8211; it should connect but you won&#8217;t be able to see the internet because there is no forwarding. One thing you can still do however, is connect to SSH on the Pi. You really don&#8217;t want any ports other than 80 visible from an unsecured AP. We&#8217;ll use <code><a rel=\"noreferrer noopener\" href=\"https:\/\/www.hostinger.co.uk\/tutorials\/iptables-tutorial\" target=\"_blank\">iptables<\/a><\/code> to set up a firewall and do the test page hijacking.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sudo iptables -A INPUT -p tcp -i wlan0 --dport 80 -j ACCEPT\nsudo iptables -A INPUT -p tcp -i wlan0 --dport 53 -j ACCEPT\nsudo iptables -A INPUT -p tcp -i wlan0 -j DROP\nsudo iptables -t nat -A PREROUTING -p tcp -i wlan0 --dport 80 -j DNAT --to-destination 192.168.4.1:80<\/pre>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"alignleft size-large is-resized\"><a href=\"https:\/\/naich.net\/wordpress\/wp-content\/uploads\/2020\/06\/tables_traverse.jpg\" target=\"_blank\" rel=\"noopener noreferrer\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/naich.net\/wordpress\/wp-content\/uploads\/2020\/06\/tables_traverse-602x1024.jpg\" alt=\"\" class=\"wp-image-1084\" width=\"107\" height=\"182\" srcset=\"https:\/\/naich.net\/wordpress\/wp-content\/uploads\/2020\/06\/tables_traverse-602x1024.jpg 602w, https:\/\/naich.net\/wordpress\/wp-content\/uploads\/2020\/06\/tables_traverse-176x300.jpg 176w, https:\/\/naich.net\/wordpress\/wp-content\/uploads\/2020\/06\/tables_traverse.jpg 647w\" sizes=\"auto, (max-width: 107px) 100vw, 107px\" \/><\/a><\/figure><\/div>\n\n\n\n<p>The first two tell iptables to allow through connections on port 80 (HTTP) and 53 (DNS), the second tells iptables to drop all other TCP connections from <code>wlan0<\/code>. The third redirects any connection with a destination port 80 (regardless of the IP address) to the Pi at IP address 192.168.4.1, port 80, for our server to handle. If you are a bit confused about how iptables work, this flowchart will either clear things up or make it more confusing. Basically there are 4 tables &#8211; filter (default if no -t switch), nat, mangle and raw which each contain &#8220;chains&#8221; such as INPUT which are the instructions on how to route traffic. It&#8217;s a vast subject and I learned just enough to work out the 3 lines above. <a href=\"https:\/\/www.digitalocean.com\/community\/tutorials\/a-deep-dive-into-iptables-and-netfilter-architecture\">There are other guides that go into more details.<\/a><\/p>\n\n\n\n<p>One thing to do at this point is make it so that the <code>iptables<\/code> configuration is not lost when the system is rebooted. This command saves it to a file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sudo iptables-save &gt;\/etc\/iptables.ipv4.nat<\/pre>\n\n\n\n<p>To reload the configuration on boot put this in <code>\/etc\/rc.local<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>iptables-restore &lt; \/etc\/iptables.ipv4.nat<\/code><\/pre>\n\n\n\n<p>So, moving on to the web server. I&#8217;m using <code><a rel=\"noreferrer noopener\" href=\"https:\/\/www.linux.com\/news\/socat-general-bidirectional-pipe-handler\/\" target=\"_blank\">socat<\/a><\/code> and a bash script. <code>socat<\/code> is one of those amazing Linux tools that is impossible to explain to a layperson. &#8220;What it does is, it takes data from one place and puts it in another but it&#8217;s more complicated than that&#8230;&#8221; and so on. Best just to tell them it&#8217;s the computer equivalent of magic, before their eyes glaze over and they start thinking about feigning an illness in order to escape. We are going to use it to pipe data from an internet port to a script and back again. Incoming text from port 80 is sent to the script on <code>stdin<\/code> and anything written to <code>stdout<\/code> gets sent back to the port. It&#8217;s easy enough to set up with this command:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sudo socat TCP4-LISTEN:80,reuseaddr,fork EXEC:\"\/home\/your_path_here\/server.sh &gt;\/dev\/null\" 2&gt;\/dev\/null &amp;<\/pre>\n\n\n\n<p>Obviously change &#8220;your_path_here&#8221; to where you are doing all this stuff and put this line in <code>\/etc\/rc.local<\/code> if you want it to start automatically on boot. The command tells <code>socat<\/code> to listen on port 80 and then fork off the script when there is a connection. The script referred to as <code>\/home\/your_path_here\/server.sh<\/code> is this:<\/p>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n\nPAGE_NAME=\"kegs\"\nFOUND_URL=\"http:\/\/1.1.1.1\/$PAGE_NAME\"\n\nrequest=\"\"\nwhile read -r  -t 5 line; do\n  if &#91;&#91; ! -z \"${line:-}\" &amp;&amp; $line == *&#91;^&#91;:cntrl:]]* ]]; then\n    if &#91;&#91; ${line:0:4} == \"GET \" ]]; then\n      request=$(expr \"$line\" : 'GET \/\\(.*\\) HTTP.*')\n    fi\n  else\n    break\n  fi\ndone\n\nif &#91;&#91; \"$request\" == \"$PAGE_NAME\" ]]; then\n  printf \"HTTP\/1.1 200 OK\\n\"\n  printf \"Content-Type: text\/html\\n\\n\"\n  cat index.html\t# Show this as a registration page.\nelse\n  printf \"HTTP\/1.1 302 Found\\n\"\n  printf \"Location: $FOUND_URL\\n\"\n  printf \"Content-Type: text\/html\\n\\n\"\n  printf \"Redirect to &lt;a href=\\\"$FOUND_URL\\\"&gt;$FOUND_URL&lt;\/a&gt;\\n\"\nfi<\/code><\/pre>\n<\/div><\/div>\n\n\n\n<p>That&#8217;s pretty dinky for a web server, huh? Don&#8217;t forget to change permissions of <code>server.sh<\/code> with <code>chmod 755 server.sh<\/code>. Rename <code>PAGE_NAME<\/code> and <code>FOUND_URL<\/code> to whatever you want. Note that because we are grabbing all port 80 traffic coming in on <code>wlan0<\/code>, it doesn&#8217;t matter what you put for an IP address &#8211; it&#8217;ll all go to our server. The first block of code reads the HTTP request coming from the device, which will be saying something along the lines of:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">GET \/ HTTP\/1.1\nHost: captive.apple.com\nAccept: image\/gif, image\/jpeg, *\/*\n... and so on<\/pre>\n\n\n\n<p>The script ignores everything except the GET \/&#8230; part, from which it extracts the page name, if any. It won&#8217;t match (unless the test page is called &#8220;\/kegs&#8221; &#8211; unlikely), so it will respond with the redirect code 302, to send the device to &#8220;\/kegs&#8221;. The device sees the redirect, thinks it&#8217;s for a registration page and loads 1.1.1.1\/kegs. This time the script sees that \/kegs has been requested, sends a 200 OK code and  the contents of <code>index.html<\/code>, which the device displays. My beer measurement system generates <code>index.html<\/code> as a page showing how much is left in each keg.<\/p>\n\n\n\n<p>As a useful tool with which to quickly see the levels of my kegs without any fuss, this is rubbish, quite frankly. But then the whole raspberry-pi-based-keg-measurement thing could be replaced with cheap mechanical bathroom scales, so I might as well go all in on the pointless technology.<\/p>\n\n\n\n<p class=\"has-small-font-size\">Updated 10\/6\/2020 : Improved the firewall rules.<br>Updated 18\/2\/2021 : Improved DNS rules.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is a little sub-project of what I&#8217;ve been working on recently &#8211; a hideously over-engineered Raspberry Pi-based system to measure the amount of beer left in the kegs in my keezer. Normally I would simply set up a web server on the Pi and have it on the home network, so I could see [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[81,102],"tags":[112,110,111],"class_list":["post-1042","post","type-post","status-publish","format-standard","hentry","category-beer","category-raspberry-pi","tag-hacking","tag-raspberry-pi","tag-wifi"],"_links":{"self":[{"href":"https:\/\/naich.net\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/1042","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/naich.net\/wordpress\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/naich.net\/wordpress\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/naich.net\/wordpress\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/naich.net\/wordpress\/index.php\/wp-json\/wp\/v2\/comments?post=1042"}],"version-history":[{"count":77,"href":"https:\/\/naich.net\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/1042\/revisions"}],"predecessor-version":[{"id":1175,"href":"https:\/\/naich.net\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/1042\/revisions\/1175"}],"wp:attachment":[{"href":"https:\/\/naich.net\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=1042"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/naich.net\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=1042"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/naich.net\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=1042"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}