An Evil Twin is a wireless attack that works by impersonating a legitimate wireless access point. So long as the malicious access point has a stronger signal strength than its legitimate counterpart, all devices connected to the target AP will drop and connect to the attacker. The attacker can then act as a router between the connected devices and a network gateway, establishing a man-in-the-middle scenario. With the exception of karma attacks and the use of SDR, this is one of the most effective wireless attacks in practice today. It's also relatively simple to implement, as we're about to see. You can execute these attacks from over a quarter of a mile away with the right equipment.
Carrying out an evil twin attack involves using two network interfaces. One interface, which we will refer to as "upstream," is used as the rogue access point. The second interface is used to connect the attacker to an internet gateway. Impersonating an access point is as simple as launching a wireless hotspot with the same ESSID and channel as the target. The attacker can then use iptables to route traffic between the two interfaces, diverting and tampering with traffic as necessary.
There are a few basic ingredients needed to perform the evil twin attack. At a most basic
level, we're going to need a laptop running Linux. I'm using Fedora 22, although you can
use just about any distro you want so long as it supports the drivers for your wireless cards.
We're also going to need a external wireless card to serve as our access point. Any external card capable of running in Master mode will work, although I've had the most success with these:
We're also going to need a seperate network interface with which to connect to the internet. For PoC purposes, your laptop's internal wireless or ethernet cards should be sufficient. For an actual attack you'd probably want to use some kind of mobile usb hotspot for maximum tactical flexibility.
Also on our requirements list is the software needed to run our hotspot. We'll be implementing our access point from scratch using Python's Scapy library in later tutorials, but for now we're going to keep things simple and create a wrapper to hostapd. You can install hostapd in the terminal using your distro's package manager.
Additionally, we need a way of handling dhcp and dns for devices that connect to our access point. For this tutorial we'll be using dnsmasq to do both. Like hostapd, dnsmasq can be installed using your package manager.
Finally, we're going to be using sslstrip2 to bypass SSL encryption and sniff credentials from intercepted traffic. You can install it by running the following commands:
Now that we have our prerequisites out of the way, let's set up our project. We'll first create a new directory from which to work from, then cd into it:
We're then going to create an evil_twin.py file to contain the program's core logic:
Finally, we'll create a utils.py file to contain our utilities and wrapper classes.
We're not not going to be using any pypi packages for this project, so I'm not going to bother with a virtual environment for this tutorial. If you're a python dev and want to use virtualenv, feel free to do so.
utils.py - packet forwarding
The first thing we need to do is give our script a way to enable packet forwarding at the system level. This will allow us to use our computer as a router between the our victims and our network gateway.
You can enable packet forwarding on your machine by running the following command:
Linux provides us with an interface, an API if you will, called the proc file system that
gives us access to the kernel's internal data structures. By setting the ip_forward to true,
we tell the kernel that we want packet forwarding turned on. We can disable packet
forwarding much in the same way:
Let's add two function definitions to our utils.py file to provide our script with this
It's pretty straightforward. The code just opens the ip_forward file, and writes either '0' or '1' to it depending on whether we want to enable or disable packet forwarding.
utils.py - Wrapping iptables
We're going to use iptables to route packets between our network interfaces and manipulate the flow of network traffic. In later posts we'll go over using NFQueue to manage iptables,
but for now we're going to keep it simple and implement a wrapper class.
Let's first open up utils.py and add the following import statement to the top of the file.
We then implement a wrapper function to painlessly execute system commands using the
We then define our wrapper class to iptables:
Our IPTables class is responsible for interacting with the iptables daemon. This means that if we instantiate multiple IPTables objects, they will implicitly have shared state: the state of the iptables daemon itself. Since dealing with this can be messy, we're going to take the easy way out and only allow one instance of the IPTables class at any given time. We do this by giving the IPTables class an _instance attribute, which is set to None by default. Instead of calling the constructor to instantiate new IPTables objects, we call a static method named get_instance(). This static method first checks to see if _instance is set to None. If _instance is None, it means that we have not yet instantiated any IPTables objects, so we set _instance to a new IPTables object. We then return the sole IPTables instance stored in _instance. From the calling function, this operation would look like this:
Next we give our IPtables class a method with which it can route traffic between our two
network interfaces while diverting all http(s) traffic to sslstrip.
Finally, we want to be able to restore iptables to its default state before and after we
use it. We do this by implementing the following method:
Our completed IPTables class should look something like this:
utils.py - Wrapping hostapd
Let's open up utils.py once again, and add the following global constants to the top of the file.
These constants are used as an alternative to hardcoding the path of hostapd's config file, the default network driver, and hardware mode for our upstream interface.
We then define the HostAPD class and deal with shared state.
Next we provide wrapper methods that can be used to start and stop the hostapd daemon:
In HostAPD.start(), we first ensure that the hostapd daemon is not currently running. If it is, we throw an exception. We then start the hostapd daemon using our wrapper to the subprocess module, passing our config file as an argument. We then tell the script to sleep for two seconds to give hostapd enough time to start. HostAPD.stop() works in a similar fashion.
Finally, we provide an interface to hostapd's config file. We do this by implementing a HostAPD.configure() method. Since HostAPD.configure() relies on the shutil module to create a backup of HostAPD's config file, we first import it at the top of our file.
We then create a new method definition for HostAPD.configure():
Finally we implement a method that restores hostapd's config file to its previous state.
That's it. Our hostapd wrapper is ready to use. The complete class definition should look like this:
utils.py - Wrapping dnsmasq
Our wrapper to dnsmasq will follow a very similar structure to our hostapd wrapper. We first set the following global constants at the top of our utils.py file to avoid hardcoding the paths to our dnsmasq config and log files.
We then create our DNSMasq class definition, dealing with shared state in the same way
Next we create start() and stop() methods for our DNSMasq class using the service command. The service command is a good choice because it will work on most Linux distros, even if they are running systemd. These method definitions work much in the same way as the ones we created for HostAPD.
We next create an interface to dnsmasq's config file. The method signature should look
The positional argument, upstream, is set to the network interface serving as our access point. The positional argument dhcp_range will be set to the range of ip addresses available to hosts that connect to our AP. The keyword argument dhcp_options takes a list of DHCP Option parameters.
Finally, we finish our DNSMasq class by implementing a DNSMasq.restore() to reset dnsmasq's config file to its previous state.
Our DNSMasq class and utils.py file are now finished, and should look
something like this:
evil_twin.py - set_configs()
Now that we've finished our utils.py file, most the hard work is behind us.
We just need to implement our evil_twin.py driver file using the wrapper classes
we just defined. Let's first import our utils module, the time module, as well as
the ArgumentParser class from argparse.
We then create a function for handing command line arguments and setting our script's
The function uses the argparse module to parse command line arguments, then returns a dictionary containing important configurations for the script. There is a great tutorial on how to use argparse written by Tshepang Lekhonkhobe, which can be found here.
evil_twin.py - display_configs()
We then create a simple function to display the script's current configuration in the
terminal. It accepts a dictionary containing the script's configuration as an argument.
evil_twin.py - kill_daemons()
When our script first begins to run, it's going to need to be able to kill any existing
hostapd and dnsmasq processes to avoid running into problems. We deal with this by implementing the following function.
evil_twin.py - main()
We now have everything we need to implement the core logic of our Evil Twin script. Let's
start out by defining main() at the bottom of our evil_twin.py file.
The first thing we do in main() is grab the scripts configs from the command line. We then kill existing daemon processes and obtain interfaces to hostapd, iptables, and dnsmasq.
We then place our upstream interface into master mode, configuring it to run our wireless access point.
We then configure dnsmasq to handle dhcp and dns requests on our upstream interface.
Once that's out of the way, we enable packet forwarding and configure iptables to act as a malicious router.
We then write code to launch the dnsmasq and hostapd daemons. We place our code with a try catch block to allow the user to quickly and cleanly kill the script by pressing ctrl+c.
Finally, we write code to clean up our mess as the script exits. We also add a call to main() at the bottom of the file.
evil_twin.py - the finished script
Our finished evil_twin.py file is now ready to run, and should look something like this.
Demo - evil_twin.py
Let's test our new script and see what it's capable of. In this demo we will attack an open wireless network served using a Buffalo router running DD-WRT, with a single mobile phone connected as a client. Please note that it is highly illegal and ill advised to even attempt this kind of attack on networks and hardware that you do not own. I'm using my own router and my own cell phone for this demo, and I strongly suggest you do the same if this is something you're interested in trying. Also, just because it is legal to steal your own creds on your own network does not mean it is legal to steal them by attacking the servers that those creds came from. It goes without saying that you should not do this without permission under any circumstances.
Setting up the attack
The first thing we need to do is disable NetworkManager on our own machine. We do this by running the following commands.
We then need to connect our gateway interface to some network other than the one we are attacking. I'm connecting to my home wifi network using wpa_supplicant, but it might be a bit easier to connect using your ethernet interface. All the info you need to do this can be found here.
Once you connect, you will need to run the following commands to obtain an IP lease:
Once we've set up our gateway interface, we need to obtain the ESSID and channel of the access point we are currently attacking. We can do this by running the following commands and analyzing the output.
We then want to open up a new terminal and run our installation of sslstrip by using
The output of the command should look something like this.
We then want to open another terminal and use the tail command to display the output of
sslstrip from its output file in real time.
The syntax for doing this is as follows:
Finally, we need a way of knowing when client devices connect to our access point.
We can do this by watching dhcp leases using the following technique.
Launching the attack
To launch our attack, we simply run our evil_twin.py script as shown below.
The exact syntax for doing this is
You should now see the devices associated with the target network drop from the AP we just attacked, then connect to you instead. This output will come from your filtered dnsmasq log file, as shown below.
We have now established ourselves as a man-in-the-middle between the target devices and
our network gateway, and can begin sniffing traffic.
Stealing creds for sites with no encryption
We're only going to focus on sniffing http and https traffic for login
credentials. It's definitely possible sniff other protocols, but a bit out of the scope
of this tutorial. The traffic that we can successfully sniff using our current setup
falls into two basic categories: unencrypted http traffic, and https traffic with weak
encryption. Defeating stronger encryption will be addressed later tutorials.
Let's start out by showing how to harvest creds from unencrypted http traffic. To do this
I'm first going to log into my account at hackru.org from my hacked cell phone as show below.
I'm then going to quickly parse the contents of my sslstrip output file using grep, revealing my login info.
It's a simple approach, yet frighteningly effective.
Stealing creds for sites with weaksauce encryption
Now that we've shown how to sniff creds from unencrypted http traffic, let's go over how to sniff creds from weakly encrypted web traffic. To do this I'm going to log into my Hacker League account from my hacked cell phone.
I'm then going to steal my own creds using the same approach as before.
Once again, this is surprisingly easy to do if you have the right tools.
Hopefully we've learned some valuable lessons here, aside from how to build wireless auditing tools and learning basic network pentesting techniques. From a user's perspective, we've learned how important it is to be careful about the traffic we send over open networks. From the perspective of an engineer, we've hopefully learned to appreciate the importance of implenting strong encryption when handling sensitive transactions.