Set up an access point on Linux using hostapd and dnsmasq

FinchSecFinchSec
6 min read

In this blog post, we'll learn how to set-up an access point on Linux, using hostapd and dnsmasq, to provide Internet to WiFi clients.

During penetration tests, whether wireless or wired, sometimes we need to share Internet. It could be for just having connectivity to do some quick research, a MITM attack to capture client traffic for reconnaissance, a captive portal, or to make an attack look more realistic to name a few.

While there are tools to help with that task, they may not always fit the attack scenario due to all the changes they are making, and this post breaks down the different steps, which allows you to integrate it in your workflow or script(s).

Requirements

We will need the following tools:

Package installation

For debian-based distributions:

sudo apt install hostapd dnsmasq aircrack-ng iproute2 iw nftables

For most RPM-based distributions, run the following command

sudo apt install hostapd dnsmasq aircrack-ng iproute iw nftables

Assumptions

We are making the following assumptions about the network interfaces:

  1. Internet is coming from eth0.
  2. WLAN interface is wlan0, and is in managed mode (aka client mode).

If this differs on your system, you will just have to adjust the interface names. Internet could be coming from another WLAN connection or Mobile broadband such as LTE or 5G. In those cases, there will often be manual set-up needed, that is not covered in this post. Using a WLAN as Internet will be covered in a later post.

Ensuring our WiFi adapter supports AP mode

Not all WiFi adapters support access point mode, so it is important to ensure the one we'll be using does. The command listing that information requires to know the physical interface name of the adapter.

While it can be done with iw dev, the output of sudo airmon-ng is easier to understand, in my opinion:

┌──(kali㉿kali)-[~]
└─$ sudo airmon-ng 

PHY     Interface       Driver          Chipset

phy0    wlan0           mt7921u         MediaTek Inc. Wireless_Device

The first column, PHY, is the physical interface. What is displayed with ifconfig or ip are the virtual interfaces, which can be seen here under the Interface column, and in this case, there is currently only one: wlan0.

Note: adapters can sometimes have multiple virtual interfaces, in different modes (access point, client mode, monitor mode). However, there are caveats. That will be the topic of a different blog post.

Now, we'll run iw phy0 info to ensure it can do AP mode. Look for Supported interface modes somewhat at the top of the output:

...
        Supported interface modes:
                 * managed
                 * AP
                 * AP/VLAN
                 * monitor
...

This confirms our adapter supports AP mode.

Enable forwarding

By default, Linux doesn't do any packet forwarding. We enable it with echo "1" | sudo tee > /proc/sys/net/ipv4/ip_forward

It won't survive a reboot, and in the event it needs to be permanent, run the following commands:

echo 'net.ipv4.ip_forward=1' | sudo tee > /etc/sysctl.d/99-ipforward.conf
sysctl -a

That will create a new sysctl configuration file, which gets loaded at boot time. To disable it, simply delete 99-ipforward.conf and rerun sysctl -a or reboot.

IP Set-up

First, we need to stop network managers and other applications because they will interfere with the AP:

sudo airmon-ng check kill

We obviously need Internet, and stopping all the network managers remove the interfaces configuration. We need to set up IP information on it, which can either be done statically, or if a DHCP server is present on the network, with sudo dhclient eth0.

We need to set a static IP for wlan0 where we have all the WiFi clients:

ip link set wlan0 up
ip addr add 192.168.0.1/24 dev wlan0

Note: Both interfaces must have a different network range.

Firewall rules

In order to make Internet available to the connected WiFi clients, we need to masquerade anything that goes out through eth0:

nft add table nat
nft -- add chain nat prerouting { type nat hook prerouting priority -100 \; }
nft add chain nat postrouting { type nat hook postrouting priority 100 \; }
nft add rule nat postrouting oifname "eth0" masquerade

While we are forwarding packets, they have an IP address which doesn't match the one on eth0, and thus packets will be dropped. This rewrites the origin IP in the packets before sending them to eth0. The kernel also does connection tracking to match incoming packets and rewrite the correct IP address when sending the responses back through wlan0.

In the event we need to integrate into existing iptables rules, add the following rules:

iptables -P FORWARD ACCEPT
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o eth0 -j MASQUERADE

Set-up and start dnsmasq

Create dnsmasq configuration file (dnsmasq.conf) to provide DHCP and DNS:

domain-needed
bogus-priv
filterwin2k
server=1.1.1.1
# Listen address is better because, with "interface", it will also listen on "lo"
listen-address=192.168.0.1
no-hosts
dhcp-range=192.168.0.50,192.168.0.150,12h

In the event a different network range was used for wlan0, update the dhcp-range value accordingly.

Then start it with sudo dnsmasq -C dnsmasq.conf

Set-up and run hostapd

Create hostapd.conf for an 802.11n AP on channel 6 called tacos using WPA2 CCMP with correct horse battery staple as a passphrase:

interface=wlan0
ssid=tacos
hw_mode=g
channel=6
ieee80211n=1
wpa=2
wpa_passphrase=correct horse battery staple
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP
wmm_enabled=1

Note: To use WPA3 PSK (SAE), replace WPA-PSK by SAE in wpa_key_mgmt, and add ieee80211w=2.

Now, start hostapd with sudo hostapd hostapd.conf

When all goes well, we should see an output similar to:

wlan0: interface state UNINITIALIZED->ENABLED
wlan0: AP-ENABLED

The last line indicates it started successfully. Next time we run hostapd, we now can add -B to the above command so hostapd starts in the background.

And then?

We're now ready to accept clients, who will have Internet access.

One last thing

Logs for hostapd and dnsmasq will also be visible in journalctl. Before starting dnsmasq and hostapd, run sudo journalctl --follow. This will display the last few lines in the log, and display new ones as they arrive, until you hit Ctrl-C.

When a new WiFi client connects, the following output will be displayed:

hostapd: wlan0: STA 10:ab:03:9a:9c:b7 IEEE 802.11: authenticated
hostapd: wlan0: STA 10:ab:03:9a:9c:b7 IEEE 802.11: associated (aid 1)
hostapd: wlan0: STA 10:ab:03:9a:9c:b7 RADIUS: starting accounting session BDF235E5988CF859
hostapd: wlan0: STA 10:ab:03:9a:9c:b7 WPA: pairwise key handshake completed (RSN)
dnsmasq-dhcp[1536]: DHCPDISCOVER(wlan0) 10:ab:03:9a:9c:b7
dnsmasq-dhcp[1536]: DHCPOFFER(wlan0) 192.168.0.83 10:ab:03:9a:9c:b7
dnsmasq-dhcp[1536]: DHCPREQUEST(wlan0) 192.168.0.83 10:ab:03:9a:9c:b7
dnsmasq-dhcp[1536]: DHCPACK(wlan0) 192.168.0.83 10:ab:03:9a:9c:b7

In this log, we can see that the station 10:ab:03:9a:9c:b7 connected to the access point (authentication then association), and successfully did the 4-way handshake (RSN stands for Robust Security Network, and it's WPA2), then requested an IP via DHCP and received a lease for 192.168.0.83.

0
Subscribe to my newsletter

Read articles from FinchSec directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

FinchSec
FinchSec

802.11 Information Security