#!/usr/bin/env python
#
# This script has room for improvement.  Cases not handled here:
#  1. Fuzzing EAP code field (we only deal with EAP response data, code 2)
#  2. Inappropriate length fields (we properly report length based on frame len)
#  3. Mismatched EAP ID
#  4. No fuzzing of the 802.1X header is performed
#  5. Mismatched 802.1X and EAP length reporting fields

import warnings
import sys
import time
import struct
import socket
import time
import os
from scapy.all import *

# Sulley really needs someone to pick up maintenance; deprecated hashing functions used
with warnings.catch_warnings():
    warnings.filterwarnings("ignore",category=DeprecationWarning)
    from sulley import *
    

###  Authentication parameters
USER = "test"
PASS = "toto"
DEV = "eth0"


### Constants
ETHERTYPE_PAE = 0x888e
PAE_GROUP_ADDR = "\x01\x80\xc2\x00\x00\x03"

EAPOL_VERSION = 1
EAPOL_EAPPACKET = 0
EAPOL_START = 1
EAPOL_LOGOFF = 2
EAPOL_KEY = 3
EAPOL_ASF = 4

EAP_REQUEST = 1
EAP_RESPONSE = 2
EAP_SUCCESS = 3
EAP_FAILURE = 4

EAP_TYPE_ID = 1
EAP_TYPE_MD5 = 4

DOT1XHEADER="\x01\x00"
FUZZEAPCODERESP="\x02"

### Packet builders
def EAPOL(type, payload=""):
    return struct.pack("!BBH", EAPOL_VERSION, type, len(payload))+payload

def EAP(code, id, type=0, data=""):
    if code in [EAP_SUCCESS, EAP_FAILURE]:
        return struct.pack("!BBH", code, id, 4)
    else:
        return struct.pack("!BBHB", code, id, 5+len(data), type)+data

def ethernet_header(src, dst, type):
    return dst+src+struct.pack("!H",type)


# The s_sizer below does not take into account the leading two bytes for the 
# EAP code and the EAP ID.  These fields are not part of the fuzzer, due to the
# state requirement handling the variable EAP ID field.  This function takes
# the mutated length and increases it by two to accommodate a properly reported
# length field.
def eaplenfixer(len):
    return len+2

def sendmutant():
    eapfuzz = s_render()
    fuzzydot1x = DOT1XHEADER + struct.pack(">H",len(eapfuzz)+2) + FUZZEAPCODERESP + id + eapfuzz
    p = Ether(src=mymac, dst="01:80:c2:00:00:03", type=0x888e)
    p /= Raw(fuzzydot1x[0:1490]) # MTU limitation with Ethernet header
    sendp(p)

if __name__ == '__main__':
    
    conf.iface = DEV
    conf.verb = 0
    s_initialize("Wired-EAP-Auth-Request")
    
    # The fields preceding the eap.id field are mostly static, and dependent on
    # prior fields (eap.id must be taken from the prior EAP Request frame).
    # Use Sulley to fuzz only the following fields
    
    if s_block_start("Response-Data"):          # Sizer for length+response
        s_sizer("Response-Data", length=2, endian=">", math=eaplenfixer)
        s_byte(4, full_range="True")            # EAP Type
        s_string("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f")
        s_string("")                            # Proprietary extra data
    s_block_end("Response-Data")
    
    
    print "Mutations: " + str(s_num_mutations())
    
    while s_mutate():

        # Bounce the interface to restart 802.1X on the port
        print "Bouncing interface %s to restart 802.1X on port"%DEV
        os.system("ifconfig %s down"%DEV)
        time.sleep(1)
        os.system("ifconfig %s up"%DEV)
    
        # Enter EAP-MD5 state machine and pick ip EAP ID field for fuzz response
        s=socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETHERTYPE_PAE))
        s.bind((DEV, ETHERTYPE_PAE))
        
        mymac=s.getsockname()[4]
        llhead=ethernet_header(mymac, PAE_GROUP_ADDR, ETHERTYPE_PAE)

        print "--> Sent EAPOL Start"
        s.send(llhead+EAPOL(EAPOL_START))
        
        try:
            while 1:
                p = s.recv(1600)[14:]
                vers,type,eapollen  = struct.unpack("!BBH",p[:4])
                if type == EAPOL_EAPPACKET:
                    code, id, eaplen = struct.unpack("!BBH", p[4:8])
                    if code == EAP_SUCCESS:
                        print "Got EAP Success"
                    elif code == EAP_FAILURE:
                        print "Got EAP Failure"
                    elif code == EAP_RESPONSE:
                        print "?? Got EAP Response"
                    elif code == EAP_REQUEST:
                        reqtype = struct.unpack("!B", p[8:9])[0]
                        reqdata = p[9:4+eaplen]
                        if reqtype == EAP_TYPE_ID:
                            print "Got EAP Request for identity"
                            s.send(llhead+
                                   EAPOL(EAPOL_EAPPACKET,
                                         EAP(EAP_RESPONSE,
                                             id,
                                             reqtype,
                                             USER)))
                            print "--> Sent EAP response with identity = [%s]" % USER
                        elif reqtype == EAP_TYPE_MD5:
                            print "Got EAP Request for MD5 challenge, sending malformed response"
                            sendmutant()
                        else:
                            print "?? Got unknown Request type (%i)" % reqtype
                    else:
                        print "?? Got unknown EAP code (%i)" % code
                else:
                    print "Got EAPOL type %i" % type
        except KeyboardInterrupt:
            print "Interrupted by user"
            sys.exit(0)
