## This file is part of Scapy ## See http://www.secdev.org/projects/scapy for more informations ## Copyright (C) Philippe Biondi ## This program is published under a GPLv2 license """ IPv4 (Internet Protocol v4). """ import os,time,struct,re,socket,new from select import select from collections import defaultdict from scapy.utils import checksum from scapy.layers.l2 import * from scapy.config import conf from scapy.fields import * from scapy.packet import * from scapy.volatile import * from scapy.sendrecv import sr,sr1,srp1 from scapy.plist import PacketList,SndRcvList from scapy.automaton import Automaton,ATMT import scapy.as_resolvers #################### ## IP Tools class ## #################### class IPTools: """Add more powers to a class that have a "src" attribute.""" def whois(self): os.system("whois %s" % self.src) def ottl(self): t = [32,64,128,255]+[self.ttl] t.sort() return t[t.index(self.ttl)+1] def hops(self): return self.ottl()-self.ttl-1 _ip_options_names = { 0: "end_of_list", 1: "nop", 2: "security", 3: "loose_source_route", 4: "timestamp", 5: "extended_security", 6: "commercial_security", 7: "record_route", 8: "stream_id", 9: "strict_source_route", 10: "experimental_measurement", 11: "mtu_probe", 12: "mtu_reply", 13: "flow_control", 14: "access_control", 15: "encode", 16: "imi_traffic_descriptor", 17: "extended_IP", 18: "traceroute", 19: "address_extension", 20: "router_alert", 21: "selective_directed_broadcast_mode", 23: "dynamic_packet_state", 24: "upstream_multicast_packet", 25: "quick_start", 30: "rfc4727_experiment", } class _IPOption_HDR(Packet): fields_desc = [ BitField("copy_flag",0, 1), BitEnumField("optclass",0,2,{0:"control",2:"debug"}), BitEnumField("option",0,5, _ip_options_names) ] class IPOption(Packet): name = "IP Option" fields_desc = [ _IPOption_HDR, FieldLenField("length", None, fmt="B", # Only option 0 and 1 have no length and value length_of="value", adjust=lambda pkt,l:l+2), StrLenField("value", "",length_from=lambda pkt:pkt.length-2) ] def extract_padding(self, p): return "",p registered_ip_options = {} @classmethod def register_variant(cls): cls.registered_ip_options[cls.option.default] = cls @classmethod def dispatch_hook(cls, pkt=None, *args, **kargs): if pkt: opt = ord(pkt[0])&0x1f if opt in cls.registered_ip_options: return cls.registered_ip_options[opt] return cls class IPOption_EOL(IPOption): name = "IP Option End of Options List" option = 0 fields_desc = [ _IPOption_HDR ] class IPOption_NOP(IPOption): name = "IP Option No Operation" option=1 fields_desc = [ _IPOption_HDR ] class IPOption_Security(IPOption): name = "IP Option Security" copy_flag = 1 option = 2 fields_desc = [ _IPOption_HDR, ByteField("length", 11), ShortField("security",0), ShortField("compartment",0), ShortField("handling_restrictions",0), StrFixedLenField("transmission_control_code","xxx",3), ] class IPOption_LSRR(IPOption): name = "IP Option Loose Source and Record Route" copy_flag = 1 option = 3 fields_desc = [ _IPOption_HDR, FieldLenField("length", None, fmt="B", length_of="routers", adjust=lambda pkt,l:l+3), ByteField("pointer",4), # 4 is first IP FieldListField("routers",[],IPField("","0.0.0.0"), length_from=lambda pkt:pkt.length-3) ] def get_current_router(self): return self.routers[self.pointer/4-1] class IPOption_RR(IPOption_LSRR): name = "IP Option Record Route" option = 7 class IPOption_SSRR(IPOption_LSRR): name = "IP Option Strict Source and Record Route" option = 9 class IPOption_Stream_Id(IPOption): name = "IP Option Stream ID" option = 8 fields_desc = [ _IPOption_HDR, ByteField("length", 4), ShortField("security",0), ] class IPOption_MTU_Probe(IPOption): name = "IP Option MTU Probe" option = 11 fields_desc = [ _IPOption_HDR, ByteField("length", 4), ShortField("mtu",0), ] class IPOption_MTU_Reply(IPOption_MTU_Probe): name = "IP Option MTU Reply" option = 12 class IPOption_Traceroute(IPOption): name = "IP Option Traceroute" copy_flag = 1 option = 18 fields_desc = [ _IPOption_HDR, ByteField("length", 12), ShortField("id",0), ShortField("outbound_hops",0), ShortField("return_hops",0), IPField("originator_ip","0.0.0.0") ] class IPOption_Address_Extension(IPOption): name = "IP Option Address Extension" copy_flag = 1 option = 19 fields_desc = [ _IPOption_HDR, ByteField("length", 10), IPField("src_ext","0.0.0.0"), IPField("dst_ext","0.0.0.0") ] class IPOption_Router_Alert(IPOption): name = "IP Option Router Alert" copy_flag = 1 option = 20 fields_desc = [ _IPOption_HDR, ByteField("length", 4), ShortEnumField("alert",0, {0:"router_shall_examine_packet"}), ] class IPOption_SDBM(IPOption): name = "IP Option Selective Directed Broadcast Mode" copy_flag = 1 option = 21 fields_desc = [ _IPOption_HDR, FieldLenField("length", None, fmt="B", length_of="addresses", adjust=lambda pkt,l:l+2), FieldListField("addresses",[],IPField("","0.0.0.0"), length_from=lambda pkt:pkt.length-2) ] TCPOptions = ( { 0 : ("EOL",None), 1 : ("NOP",None), 2 : ("MSS","!H"), 3 : ("WScale","!B"), 4 : ("SAckOK",None), 5 : ("SAck","!"), 8 : ("Timestamp","!II"), 14 : ("AltChkSum","!BH"), 15 : ("AltChkSumOpt",None), 25 : ("Mood","!p") }, { "EOL":0, "NOP":1, "MSS":2, "WScale":3, "SAckOK":4, "SAck":5, "Timestamp":8, "AltChkSum":14, "AltChkSumOpt":15, "Mood":25 } ) class TCPOptionsField(StrField): islist=1 def getfield(self, pkt, s): opsz = (pkt.dataofs-5)*4 if opsz < 0: warning("bad dataofs (%i). Assuming dataofs=5"%pkt.dataofs) opsz = 0 return s[opsz:],self.m2i(pkt,s[:opsz]) def m2i(self, pkt, x): opt = [] while x: onum = ord(x[0]) if onum == 0: opt.append(("EOL",None)) x=x[1:] break if onum == 1: opt.append(("NOP",None)) x=x[1:] continue olen = ord(x[1]) if olen < 2: warning("Malformed TCP option (announced length is %i)" % olen) olen = 2 oval = x[2:olen] if TCPOptions[0].has_key(onum): oname, ofmt = TCPOptions[0][onum] if onum == 5: #SAck ofmt += "%iI" % (len(oval)/4) if ofmt and struct.calcsize(ofmt) == len(oval): oval = struct.unpack(ofmt, oval) if len(oval) == 1: oval = oval[0] opt.append((oname, oval)) else: opt.append((onum, oval)) x = x[olen:] return opt def i2m(self, pkt, x): opt = "" for oname,oval in x: if type(oname) is str: if oname == "NOP": opt += "\x01" continue elif oname == "EOL": opt += "\x00" continue elif TCPOptions[1].has_key(oname): onum = TCPOptions[1][oname] ofmt = TCPOptions[0][onum][1] if onum == 5: #SAck ofmt += "%iI" % len(oval) if ofmt is not None and (type(oval) is not str or "s" in ofmt): if type(oval) is not tuple: oval = (oval,) oval = struct.pack(ofmt, *oval) else: warning("option [%s] unknown. Skipped."%oname) continue else: onum = oname if type(oval) is not str: warning("option [%i] is not string."%onum) continue opt += chr(onum)+chr(2+len(oval))+oval return opt+"\x00"*(3-((len(opt)+3)%4)) def randval(self): return [] # XXX class ICMPTimeStampField(IntField): re_hmsm = re.compile("([0-2]?[0-9])[Hh:](([0-5]?[0-9])([Mm:]([0-5]?[0-9])([sS:.]([0-9]{0,3}))?)?)?$") def i2repr(self, pkt, val): if val is None: return "--" else: sec, milli = divmod(val, 1000) min, sec = divmod(sec, 60) hour, min = divmod(min, 60) return "%d:%d:%d.%d" %(hour, min, sec, int(milli)) def any2i(self, pkt, val): if type(val) is str: hmsms = self.re_hmsm.match(val) if hmsms: h,_,m,_,s,_,ms = hmsms = hmsms.groups() ms = int(((ms or "")+"000")[:3]) val = ((int(h)*60+int(m or 0))*60+int(s or 0))*1000+ms else: val = 0 elif val is None: val = int((time.time()%(24*60*60))*1000) return val class IP(Packet, IPTools): name = "IP" fields_desc = [ BitField("version" , 4 , 4), BitField("ihl", None, 4), XByteField("tos", 0), ShortField("len", None), ShortField("id", 1), FlagsField("flags", 0, 3, ["MF","DF","evil"]), BitField("frag", 0, 13), ByteField("ttl", 64), ByteEnumField("proto", 0, IP_PROTOS), XShortField("chksum", None), #IPField("src", "127.0.0.1"), #Emph(SourceIPField("src","dst")), Emph(IPField("src", "16.0.0.1")), Emph(IPField("dst", "48.0.0.1")), PacketListField("options", [], IPOption, length_from=lambda p:p.ihl*4-20) ] def post_build(self, p, pay): ihl = self.ihl p += "\0"*((-len(p))%4) # pad IP options if needed if ihl is None: ihl = len(p)/4 p = chr(((self.version&0xf)<<4) | ihl&0x0f)+p[1:] if self.len is None: l = len(p)+len(pay) p = p[:2]+struct.pack("!H", l)+p[4:] if self.chksum is None: ck = checksum(p) p = p[:10]+chr(ck>>8)+chr(ck&0xff)+p[12:] return p+pay def extract_padding(self, s): l = self.len - (self.ihl << 2) return s[:l],s[l:] def send(self, s, slp=0): for p in self: try: s.sendto(str(p), (p.dst,0)) except socket.error, msg: log_runtime.error(msg) if slp: time.sleep(slp) def route(self): dst = self.dst if isinstance(dst,Gen): dst = iter(dst).next() return conf.route.route(dst) def hashret(self): if ( (self.proto == socket.IPPROTO_ICMP) and (isinstance(self.payload, ICMP)) and (self.payload.type in [3,4,5,11,12]) ): return self.payload.payload.hashret() else: if conf.checkIPsrc and conf.checkIPaddr: return strxor(inet_aton(self.src),inet_aton(self.dst))+struct.pack("B",self.proto)+self.payload.hashret() else: return struct.pack("B", self.proto)+self.payload.hashret() def answers(self, other): if not isinstance(other,IP): return 0 if conf.checkIPaddr and (self.dst != other.src): return 0 if ( (self.proto == socket.IPPROTO_ICMP) and (isinstance(self.payload, ICMP)) and (self.payload.type in [3,4,5,11,12]) ): # ICMP error message return self.payload.payload.answers(other) else: if ( (conf.checkIPaddr and (self.src != other.dst)) or (self.proto != other.proto) ): return 0 return self.payload.answers(other.payload) def mysummary(self): s = self.sprintf("%IP.src% > %IP.dst% %IP.proto%") if self.frag: s += " frag:%i" % self.frag return s def fragment(self, fragsize=1480): """Fragment IP datagrams""" fragsize = (fragsize+7)/8*8 lst = [] fnb = 0 fl = self while fl.underlayer is not None: fnb += 1 fl = fl.underlayer for p in fl: s = str(p[fnb].payload) nb = (len(s)+fragsize-1)/fragsize for i in range(nb): q = p.copy() del(q[fnb].payload) del(q[fnb].chksum) del(q[fnb].len) if i == nb-1: q[IP].flags &= ~1 else: q[IP].flags |= 1 q[IP].frag = i*fragsize/8 r = conf.raw_layer(load=s[i*fragsize:(i+1)*fragsize]) r.overload_fields = p[IP].payload.overload_fields.copy() q.add_payload(r) lst.append(q) return lst class TCP(Packet): name = "TCP" fields_desc = [ ShortEnumField("sport", 20, TCP_SERVICES), ShortEnumField("dport", 80, TCP_SERVICES), IntField("seq", 0), IntField("ack", 0), BitField("dataofs", None, 4), BitField("reserved", 0, 4), FlagsField("flags", 0x2, 8, "FSRPAUEC"), ShortField("window", 8192), XShortField("chksum", None), ShortField("urgptr", 0), TCPOptionsField("options", {}) ] def post_build(self, p, pay): p += pay dataofs = self.dataofs if dataofs is None: dataofs = 5+((len(self.get_field("options").i2m(self,self.options))+3)/4) p = p[:12]+chr((dataofs << 4) | ord(p[12])&0x0f)+p[13:] if self.chksum is None: if isinstance(self.underlayer, IP): if self.underlayer.len is not None: ln = self.underlayer.len-20 else: ln = len(p) psdhdr = struct.pack("!4s4sHH", inet_aton(self.underlayer.src), inet_aton(self.underlayer.dst), self.underlayer.proto, ln) ck=checksum(psdhdr+p) p = p[:16]+struct.pack("!H", ck)+p[18:] elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_TCP, self.underlayer, p) p = p[:16]+struct.pack("!H", ck)+p[18:] else: warning("No IP underlayer to compute checksum. Leaving null.") return p def hashret(self): if conf.checkIPsrc: return struct.pack("H",self.sport ^ self.dport)+self.payload.hashret() else: return self.payload.hashret() def answers(self, other): if not isinstance(other, TCP): return 0 if conf.checkIPsrc: if not ((self.sport == other.dport) and (self.dport == other.sport)): return 0 if (abs(other.seq-self.ack) > 2+len(other.payload)): return 0 return 1 def mysummary(self): if isinstance(self.underlayer, IP): return self.underlayer.sprintf("TCP %IP.src%:%TCP.sport% > %IP.dst%:%TCP.dport% %TCP.flags%") elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6): return self.underlayer.sprintf("TCP %IPv6.src%:%TCP.sport% > %IPv6.dst%:%TCP.dport% %TCP.flags%") else: return self.sprintf("TCP %TCP.sport% > %TCP.dport% %TCP.flags%") class UDP(Packet): name = "UDP" fields_desc = [ ShortEnumField("sport", 53, UDP_SERVICES), ShortEnumField("dport", 53, UDP_SERVICES), ShortField("len", None), XShortField("chksum", None), ] def post_build(self, p, pay): p += pay l = self.len if l is None: l = len(p) p = p[:4]+struct.pack("!H",l)+p[6:] if self.chksum is None: if isinstance(self.underlayer, IP): if self.underlayer.len is not None: ln = self.underlayer.len-20 else: ln = len(p) psdhdr = struct.pack("!4s4sHH", inet_aton(self.underlayer.src), inet_aton(self.underlayer.dst), self.underlayer.proto, ln) ck=checksum(psdhdr+p) p = p[:6]+struct.pack("!H", ck)+p[8:] elif isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, self.underlayer, p) p = p[:6]+struct.pack("!H", ck)+p[8:] else: warning("No IP underlayer to compute checksum. Leaving null.") return p def extract_padding(self, s): l = self.len - 8 return s[:l],s[l:] def hashret(self): return self.payload.hashret() def answers(self, other): if not isinstance(other, UDP): return 0 if conf.checkIPsrc: if self.dport != other.sport: return 0 return self.payload.answers(other.payload) def mysummary(self): if isinstance(self.underlayer, IP): return self.underlayer.sprintf("UDP %IP.src%:%UDP.sport% > %IP.dst%:%UDP.dport%") elif isinstance(self.underlayer, scapy.layers.inet6.IPv6): return self.underlayer.sprintf("UDP %IPv6.src%:%UDP.sport% > %IPv6.dst%:%UDP.dport%") else: return self.sprintf("UDP %UDP.sport% > %UDP.dport%") icmptypes = { 0 : "echo-reply", 3 : "dest-unreach", 4 : "source-quench", 5 : "redirect", 8 : "echo-request", 9 : "router-advertisement", 10 : "router-solicitation", 11 : "time-exceeded", 12 : "parameter-problem", 13 : "timestamp-request", 14 : "timestamp-reply", 15 : "information-request", 16 : "information-response", 17 : "address-mask-request", 18 : "address-mask-reply" } icmpcodes = { 3 : { 0 : "network-unreachable", 1 : "host-unreachable", 2 : "protocol-unreachable", 3 : "port-unreachable", 4 : "fragmentation-needed", 5 : "source-route-failed", 6 : "network-unknown", 7 : "host-unknown", 9 : "network-prohibited", 10 : "host-prohibited", 11 : "TOS-network-unreachable", 12 : "TOS-host-unreachable", 13 : "communication-prohibited", 14 : "host-precedence-violation", 15 : "precedence-cutoff", }, 5 : { 0 : "network-redirect", 1 : "host-redirect", 2 : "TOS-network-redirect", 3 : "TOS-host-redirect", }, 11 : { 0 : "ttl-zero-during-transit", 1 : "ttl-zero-during-reassembly", }, 12 : { 0 : "ip-header-bad", 1 : "required-option-missing", }, } class ICMP(Packet): name = "ICMP" fields_desc = [ ByteEnumField("type",8, icmptypes), MultiEnumField("code",0, icmpcodes, depends_on=lambda pkt:pkt.type,fmt="B"), XShortField("chksum", None), ConditionalField(XShortField("id",0), lambda pkt:pkt.type in [0,8,13,14,15,16,17,18]), ConditionalField(XShortField("seq",0), lambda pkt:pkt.type in [0,8,13,14,15,16,17,18]), ConditionalField(ICMPTimeStampField("ts_ori", None), lambda pkt:pkt.type in [13,14]), ConditionalField(ICMPTimeStampField("ts_rx", None), lambda pkt:pkt.type in [13,14]), ConditionalField(ICMPTimeStampField("ts_tx", None), lambda pkt:pkt.type in [13,14]), ConditionalField(IPField("gw","0.0.0.0"), lambda pkt:pkt.type==5), ConditionalField(ByteField("ptr",0), lambda pkt:pkt.type==12), ConditionalField(X3BytesField("reserved",0), lambda pkt:pkt.type==12), ConditionalField(IPField("addr_mask","0.0.0.0"), lambda pkt:pkt.type in [17,18]), ConditionalField(IntField("unused",0), lambda pkt:pkt.type not in [0,5,8,12,13,14,15,16,17,18]), ] def post_build(self, p, pay): p += pay if self.chksum is None: ck = checksum(p) p = p[:2]+chr(ck>>8)+chr(ck&0xff)+p[4:] return p def hashret(self): if self.type in [0,8,13,14,15,16,17,18]: return struct.pack("HH",self.id,self.seq)+self.payload.hashret() return self.payload.hashret() def answers(self, other): if not isinstance(other,ICMP): return 0 if ( (other.type,self.type) in [(8,0),(13,14),(15,16),(17,18)] and self.id == other.id and self.seq == other.seq ): return 1 return 0 def guess_payload_class(self, payload): if self.type in [3,4,5,11,12]: return IPerror else: return None def mysummary(self): if isinstance(self.underlayer, IP): return self.underlayer.sprintf("ICMP %IP.src% > %IP.dst% %ICMP.type% %ICMP.code%") else: return self.sprintf("ICMP %ICMP.type% %ICMP.code%") class IPerror(IP): name = "IP in ICMP" def answers(self, other): if not isinstance(other, IP): return 0 if not ( ((conf.checkIPsrc == 0) or (self.dst == other.dst)) and (self.src == other.src) and ( ((conf.checkIPID == 0) or (self.id == other.id) or (conf.checkIPID == 1 and self.id == socket.htons(other.id)))) and (self.proto == other.proto) ): return 0 return self.payload.answers(other.payload) def mysummary(self): return Packet.mysummary(self) class TCPerror(TCP): name = "TCP in ICMP" def answers(self, other): if not isinstance(other, TCP): return 0 if conf.checkIPsrc: if not ((self.sport == other.sport) and (self.dport == other.dport)): return 0 if conf.check_TCPerror_seqack: if self.seq is not None: if self.seq != other.seq: return 0 if self.ack is not None: if self.ack != other.ack: return 0 return 1 def mysummary(self): return Packet.mysummary(self) class UDPerror(UDP): name = "UDP in ICMP" def answers(self, other): if not isinstance(other, UDP): return 0 if conf.checkIPsrc: if not ((self.sport == other.sport) and (self.dport == other.dport)): return 0 return 1 def mysummary(self): return Packet.mysummary(self) class ICMPerror(ICMP): name = "ICMP in ICMP" def answers(self, other): if not isinstance(other,ICMP): return 0 if not ((self.type == other.type) and (self.code == other.code)): return 0 if self.code in [0,8,13,14,17,18]: if (self.id == other.id and self.seq == other.seq): return 1 else: return 0 else: return 1 def mysummary(self): return Packet.mysummary(self) bind_layers( Ether, IP, type=2048) bind_layers( CookedLinux, IP, proto=2048) bind_layers( GRE, IP, proto=2048) bind_layers( SNAP, IP, code=2048) bind_layers( IPerror, IPerror, frag=0, proto=4) bind_layers( IPerror, ICMPerror, frag=0, proto=1) bind_layers( IPerror, TCPerror, frag=0, proto=6) bind_layers( IPerror, UDPerror, frag=0, proto=17) bind_layers( IP, IP, frag=0, proto=4) bind_layers( IP, ICMP, frag=0, proto=1) bind_layers( IP, TCP, frag=0, proto=6) bind_layers( IP, UDP, frag=0, proto=17) bind_layers( IP, GRE, frag=0, proto=47) conf.l2types.register(101, IP) conf.l2types.register_num2layer(12, IP) conf.l3types.register(ETH_P_IP, IP) conf.l3types.register_num2layer(ETH_P_ALL, IP) conf.neighbor.register_l3(Ether, IP, lambda l2,l3: getmacbyip(l3.dst)) conf.neighbor.register_l3(Dot3, IP, lambda l2,l3: getmacbyip(l3.dst)) ################### ## Fragmentation ## ################### @conf.commands.register def fragment(pkt, fragsize=1480): """Fragment a big IP datagram""" fragsize = (fragsize+7)/8*8 lst = [] for p in pkt: s = str(p[IP].payload) nb = (len(s)+fragsize-1)/fragsize for i in range(nb): q = p.copy() del(q[IP].payload) del(q[IP].chksum) del(q[IP].len) if i == nb-1: q[IP].flags &= ~1 else: q[IP].flags |= 1 q[IP].frag = i*fragsize/8 r = conf.raw_layer(load=s[i*fragsize:(i+1)*fragsize]) r.overload_fields = p[IP].payload.overload_fields.copy() q.add_payload(r) lst.append(q) return lst def overlap_frag(p, overlap, fragsize=8, overlap_fragsize=None): if overlap_fragsize is None: overlap_fragsize = fragsize q = p.copy() del(q[IP].payload) q[IP].add_payload(overlap) qfrag = fragment(q, overlap_fragsize) qfrag[-1][IP].flags |= 1 return qfrag+fragment(p, fragsize) @conf.commands.register def defrag(plist): """defrag(plist) -> ([not fragmented], [defragmented], [ [bad fragments], [bad fragments], ... ])""" frags = defaultdict(PacketList) nofrag = PacketList() for p in plist: ip = p[IP] if IP not in p: nofrag.append(p) continue if ip.frag == 0 and ip.flags & 1 == 0: nofrag.append(p) continue uniq = (ip.id,ip.src,ip.dst,ip.proto) frags[uniq].append(p) defrag = [] missfrag = [] for lst in frags.itervalues(): lst.sort(key=lambda x: x.frag) p = lst[0] lastp = lst[-1] if p.frag > 0 or lastp.flags & 1 != 0: # first or last fragment missing missfrag.append(lst) continue p = p.copy() if conf.padding_layer in p: del(p[conf.padding_layer].underlayer.payload) ip = p[IP] if ip.len is None or ip.ihl is None: clen = len(ip.payload) else: clen = ip.len - (ip.ihl<<2) txt = conf.raw_layer() for q in lst[1:]: if clen != q.frag<<3: # Wrong fragmentation offset if clen > q.frag<<3: warning("Fragment overlap (%i > %i) %r || %r || %r" % (clen, q.frag<<3, p,txt,q)) missfrag.append(lst) break if q[IP].len is None or q[IP].ihl is None: clen += len(q[IP].payload) else: clen += q[IP].len - (q[IP].ihl<<2) if conf.padding_layer in q: del(q[conf.padding_layer].underlayer.payload) txt.add_payload(q[IP].payload.copy()) else: ip.flags &= ~1 # !MF del(ip.chksum) del(ip.len) p = p/txt defrag.append(p) defrag2=PacketList() for p in defrag: defrag2.append(p.__class__(str(p))) return nofrag,defrag2,missfrag @conf.commands.register def defragment(plist): """defrag(plist) -> plist defragmented as much as possible """ frags = defaultdict(lambda:[]) final = [] pos = 0 for p in plist: p._defrag_pos = pos pos += 1 if IP in p: ip = p[IP] if ip.frag != 0 or ip.flags & 1: ip = p[IP] uniq = (ip.id,ip.src,ip.dst,ip.proto) frags[uniq].append(p) continue final.append(p) defrag = [] missfrag = [] for lst in frags.itervalues(): lst.sort(key=lambda x: x.frag) p = lst[0] lastp = lst[-1] if p.frag > 0 or lastp.flags & 1 != 0: # first or last fragment missing missfrag += lst continue p = p.copy() if conf.padding_layer in p: del(p[conf.padding_layer].underlayer.payload) ip = p[IP] if ip.len is None or ip.ihl is None: clen = len(ip.payload) else: clen = ip.len - (ip.ihl<<2) txt = conf.raw_layer() for q in lst[1:]: if clen != q.frag<<3: # Wrong fragmentation offset if clen > q.frag<<3: warning("Fragment overlap (%i > %i) %r || %r || %r" % (clen, q.frag<<3, p,txt,q)) missfrag += lst break if q[IP].len is None or q[IP].ihl is None: clen += len(q[IP].payload) else: clen += q[IP].len - (q[IP].ihl<<2) if conf.padding_layer in q: del(q[conf.padding_layer].underlayer.payload) txt.add_payload(q[IP].payload.copy()) else: ip.flags &= ~1 # !MF del(ip.chksum) del(ip.len) p = p/txt p._defrag_pos = max(x._defrag_pos for x in lst) defrag.append(p) defrag2=[] for p in defrag: q = p.__class__(str(p)) q._defrag_pos = p._defrag_pos defrag2.append(q) final += defrag2 final += missfrag final.sort(key=lambda x: x._defrag_pos) for p in final: del(p._defrag_pos) if hasattr(plist, "listname"): name = "Defragmented %s" % plist.listname else: name = "Defragmented" return PacketList(final, name=name) ### Add timeskew_graph() method to PacketList def _packetlist_timeskew_graph(self, ip, **kargs): """Tries to graph the timeskew between the timestamps and real time for a given ip""" res = map(lambda x: self._elt2pkt(x), self.res) b = filter(lambda x:x.haslayer(IP) and x.getlayer(IP).src == ip and x.haslayer(TCP), res) c = [] for p in b: opts = p.getlayer(TCP).options for o in opts: if o[0] == "Timestamp": c.append((p.time,o[1][0])) if not c: warning("No timestamps found in packet list") return d = map(lambda (x,y): (x%2000,((x-c[0][0])-((y-c[0][1])/1000.0))),c) g = Gnuplot.Gnuplot() g.plot(Gnuplot.Data(d,**kargs)) return g PacketList.timeskew_graph = new.instancemethod(_packetlist_timeskew_graph, None, PacketList) ### Create a new packet list class TracerouteResult(SndRcvList): def __init__(self, res=None, name="Traceroute", stats=None): PacketList.__init__(self, res, name, stats) self.graphdef = None self.graphASres = 0 self.padding = 0 self.hloc = None self.nloc = None def show(self): return self.make_table(lambda (s,r): (s.sprintf("%IP.dst%:{TCP:tcp%ir,TCP.dport%}{UDP:udp%ir,UDP.dport%}{ICMP:ICMP}"), s.ttl, r.sprintf("%-15s,IP.src% {TCP:%TCP.flags%}{ICMP:%ir,ICMP.type%}"))) def get_trace(self): trace = {} for s,r in self.res: if IP not in s: continue d = s[IP].dst if d not in trace: trace[d] = {} trace[d][s[IP].ttl] = r[IP].src, ICMP not in r for k in trace.values(): m = filter(lambda x:k[x][1], k.keys()) if not m: continue m = min(m) for l in k.keys(): if l > m: del(k[l]) return trace def trace3D(self): """Give a 3D representation of the traceroute. right button: rotate the scene middle button: zoom left button: move the scene left button on a ball: toggle IP displaying ctrl-left button on a ball: scan ports 21,22,23,25,80 and 443 and display the result""" trace = self.get_trace() import visual class IPsphere(visual.sphere): def __init__(self, ip, **kargs): visual.sphere.__init__(self, **kargs) self.ip=ip self.label=None self.setlabel(self.ip) def setlabel(self, txt,visible=None): if self.label is not None: if visible is None: visible = self.label.visible self.label.visible = 0 elif visible is None: visible=0 self.label=visual.label(text=txt, pos=self.pos, space=self.radius, xoffset=10, yoffset=20, visible=visible) def action(self): self.label.visible ^= 1 visual.scene = visual.display() visual.scene.exit = True start = visual.box() rings={} tr3d = {} for i in trace: tr = trace[i] tr3d[i] = [] ttl = tr.keys() for t in range(1,max(ttl)+1): if t not in rings: rings[t] = [] if t in tr: if tr[t] not in rings[t]: rings[t].append(tr[t]) tr3d[i].append(rings[t].index(tr[t])) else: rings[t].append(("unk",-1)) tr3d[i].append(len(rings[t])-1) for t in rings: r = rings[t] l = len(r) for i in range(l): if r[i][1] == -1: col = (0.75,0.75,0.75) elif r[i][1]: col = visual.color.green else: col = visual.color.blue s = IPsphere(pos=((l-1)*visual.cos(2*i*visual.pi/l),(l-1)*visual.sin(2*i*visual.pi/l),2*t), ip = r[i][0], color = col) for trlst in tr3d.values(): if t <= len(trlst): if trlst[t-1] == i: trlst[t-1] = s forecol = colgen(0.625, 0.4375, 0.25, 0.125) for trlst in tr3d.values(): col = forecol.next() start = (0,0,0) for ip in trlst: visual.cylinder(pos=start,axis=ip.pos-start,color=col,radius=0.2) start = ip.pos movcenter=None while 1: visual.rate(50) if visual.scene.kb.keys: k = visual.scene.kb.getkey() if k == "esc" or k == "q": break if visual.scene.mouse.events: ev = visual.scene.mouse.getevent() if ev.press == "left": o = ev.pick if o: if ev.ctrl: if o.ip == "unk": continue savcolor = o.color o.color = (1,0,0) a,b=sr(IP(dst=o.ip)/TCP(dport=[21,22,23,25,80,443]),timeout=2) o.color = savcolor if len(a) == 0: txt = "%s:\nno results" % o.ip else: txt = "%s:\n" % o.ip for s,r in a: txt += r.sprintf("{TCP:%IP.src%:%TCP.sport% %TCP.flags%}{TCPerror:%IPerror.dst%:%TCPerror.dport% %IP.src% %ir,ICMP.type%}\n") o.setlabel(txt, visible=1) else: if hasattr(o, "action"): o.action() elif ev.drag == "left": movcenter = ev.pos elif ev.drop == "left": movcenter = None if movcenter: visual.scene.center -= visual.scene.mouse.pos-movcenter movcenter = visual.scene.mouse.pos def world_trace(self): from modules.geo import locate_ip ips = {} rt = {} ports_done = {} for s,r in self.res: ips[r.src] = None if s.haslayer(TCP) or s.haslayer(UDP): trace_id = (s.src,s.dst,s.proto,s.dport) elif s.haslayer(ICMP): trace_id = (s.src,s.dst,s.proto,s.type) else: trace_id = (s.src,s.dst,s.proto,0) trace = rt.get(trace_id,{}) if not r.haslayer(ICMP) or r.type != 11: if ports_done.has_key(trace_id): continue ports_done[trace_id] = None trace[s.ttl] = r.src rt[trace_id] = trace trt = {} for trace_id in rt: trace = rt[trace_id] loctrace = [] for i in range(max(trace.keys())): ip = trace.get(i,None) if ip is None: continue loc = locate_ip(ip) if loc is None: continue # loctrace.append((ip,loc)) # no labels yet loctrace.append(loc) if loctrace: trt[trace_id] = loctrace tr = map(lambda x: Gnuplot.Data(x,with_="lines"), trt.values()) g = Gnuplot.Gnuplot() world = Gnuplot.File(conf.gnuplot_world,with_="lines") g.plot(world,*tr) return g def make_graph(self,ASres=None,padding=0): if ASres is None: ASres = conf.AS_resolver self.graphASres = ASres self.graphpadding = padding ips = {} rt = {} ports = {} ports_done = {} for s,r in self.res: r = r.getlayer(IP) or (conf.ipv6_enabled and r[scapy.layers.inet6.IPv6]) or r s = s.getlayer(IP) or (conf.ipv6_enabled and s[scapy.layers.inet6.IPv6]) or s ips[r.src] = None if TCP in s: trace_id = (s.src,s.dst,6,s.dport) elif UDP in s: trace_id = (s.src,s.dst,17,s.dport) elif ICMP in s: trace_id = (s.src,s.dst,1,s.type) else: trace_id = (s.src,s.dst,s.proto,0) trace = rt.get(trace_id,{}) ttl = conf.ipv6_enabled and scapy.layers.inet6.IPv6 in s and s.hlim or s.ttl if not (ICMP in r and r[ICMP].type == 11) and not (conf.ipv6_enabled and scapy.layers.inet6.IPv6 in r and scapy.layers.inet6.ICMPv6TimeExceeded in r): if trace_id in ports_done: continue ports_done[trace_id] = None p = ports.get(r.src,[]) if TCP in r: p.append(r.sprintf(" %TCP.sport% %TCP.flags%")) trace[ttl] = r.sprintf('"%r,src%":T%ir,TCP.sport%') elif UDP in r: p.append(r.sprintf(" %UDP.sport%")) trace[ttl] = r.sprintf('"%r,src%":U%ir,UDP.sport%') elif ICMP in r: p.append(r.sprintf(" ICMP %ICMP.type%")) trace[ttl] = r.sprintf('"%r,src%":I%ir,ICMP.type%') else: p.append(r.sprintf("{IP: IP %proto%}{IPv6: IPv6 %nh%}")) trace[ttl] = r.sprintf('"%r,src%":{IP:P%ir,proto%}{IPv6:P%ir,nh%}') ports[r.src] = p else: trace[ttl] = r.sprintf('"%r,src%"') rt[trace_id] = trace # Fill holes with unk%i nodes unknown_label = incremental_label("unk%i") blackholes = [] bhip = {} for rtk in rt: trace = rt[rtk] k = trace.keys() for n in range(min(k), max(k)): if not trace.has_key(n): trace[n] = unknown_label.next() if not ports_done.has_key(rtk): if rtk[2] == 1: #ICMP bh = "%s %i/icmp" % (rtk[1],rtk[3]) elif rtk[2] == 6: #TCP bh = "%s %i/tcp" % (rtk[1],rtk[3]) elif rtk[2] == 17: #UDP bh = '%s %i/udp' % (rtk[1],rtk[3]) else: bh = '%s %i/proto' % (rtk[1],rtk[2]) ips[bh] = None bhip[rtk[1]] = bh bh = '"%s"' % bh trace[max(k)+1] = bh blackholes.append(bh) # Find AS numbers ASN_query_list = dict.fromkeys(map(lambda x:x.rsplit(" ",1)[0],ips)).keys() if ASres is None: ASNlist = [] else: ASNlist = ASres.resolve(*ASN_query_list) ASNs = {} ASDs = {} for ip,asn,desc, in ASNlist: if asn is None: continue iplist = ASNs.get(asn,[]) if ip in bhip: if ip in ports: iplist.append(ip) iplist.append(bhip[ip]) else: iplist.append(ip) ASNs[asn] = iplist ASDs[asn] = desc backcolorlist=colgen("60","86","ba","ff") forecolorlist=colgen("a0","70","40","20") s = "digraph trace {\n" s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n" s += "\n#ASN clustering\n" for asn in ASNs: s += '\tsubgraph cluster_%s {\n' % asn col = backcolorlist.next() s += '\t\tcolor="#%s%s%s";' % col s += '\t\tnode [fillcolor="#%s%s%s",style=filled];' % col s += '\t\tfontsize = 10;' s += '\t\tlabel = "%s\\n[%s]"\n' % (asn,ASDs[asn]) for ip in ASNs[asn]: s += '\t\t"%s";\n'%ip s += "\t}\n" s += "#endpoints\n" for p in ports: s += '\t"%s" [shape=record,color=black,fillcolor=green,style=filled,label="%s|%s"];\n' % (p,p,"|".join(ports[p])) s += "\n#Blackholes\n" for bh in blackholes: s += '\t%s [shape=octagon,color=black,fillcolor=red,style=filled];\n' % bh if padding: s += "\n#Padding\n" pad={} for snd,rcv in self.res: if rcv.src not in ports and rcv.haslayer(conf.padding_layer): p = rcv.getlayer(conf.padding_layer).load if p != "\x00"*len(p): pad[rcv.src]=None for rcv in pad: s += '\t"%s" [shape=triangle,color=black,fillcolor=red,style=filled];\n' % rcv s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n" for rtk in rt: s += "#---[%s\n" % `rtk` s += '\t\tedge [color="#%s%s%s"];\n' % forecolorlist.next() trace = rt[rtk] k = trace.keys() for n in range(min(k), max(k)): s += '\t%s ->\n' % trace[n] s += '\t%s;\n' % trace[max(k)] s += "}\n"; self.graphdef = s def graph(self, ASres=None, padding=0, **kargs): """x.graph(ASres=conf.AS_resolver, other args): ASres=None : no AS resolver => no clustering ASres=AS_resolver() : default whois AS resolver (riswhois.ripe.net) ASres=AS_resolver_cymru(): use whois.cymru.com whois database ASres=AS_resolver(server="whois.ra.net") type: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option target: filename or redirect. Defaults pipe to Imagemagick's display program prog: which graphviz program to use""" if ASres is None: ASres = conf.AS_resolver if (self.graphdef is None or self.graphASres != ASres or self.graphpadding != padding): self.make_graph(ASres,padding) return do_graph(self.graphdef, **kargs) @conf.commands.register def traceroute(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), l4 = None, filter=None, timeout=2, verbose=None, **kargs): """Instant TCP traceroute traceroute(target, [maxttl=30,] [dport=80,] [sport=80,] [verbose=conf.verb]) -> None """ if verbose is None: verbose = conf.verb if filter is None: # we only consider ICMP error packets and TCP packets with at # least the ACK flag set *and* either the SYN or the RST flag # set filter="(icmp and (icmp[0]=3 or icmp[0]=4 or icmp[0]=5 or icmp[0]=11 or icmp[0]=12)) or (tcp and (tcp[13] & 0x16 > 0x10))" if l4 is None: a,b = sr(IP(dst=target, id=RandShort(), ttl=(minttl,maxttl))/TCP(seq=RandInt(),sport=sport, dport=dport), timeout=timeout, filter=filter, verbose=verbose, **kargs) else: # this should always work filter="ip" a,b = sr(IP(dst=target, id=RandShort(), ttl=(minttl,maxttl))/l4, timeout=timeout, filter=filter, verbose=verbose, **kargs) a = TracerouteResult(a.res) if verbose: a.show() return a,b ############################# ## Simple TCP client stack ## ############################# class TCP_client(Automaton): def parse_args(self, ip, port, *args, **kargs): self.dst = iter(Net(ip)).next() self.dport = port self.sport = random.randrange(0,2**16) self.l4 = IP(dst=ip)/TCP(sport=self.sport, dport=self.dport, flags=0, seq=random.randrange(0,2**32)) self.src = self.l4.src self.swin=self.l4[TCP].window self.dwin=1 self.rcvbuf="" bpf = "host %s and host %s and port %i and port %i" % (self.src, self.dst, self.sport, self.dport) # bpf=None Automaton.parse_args(self, filter=bpf, **kargs) def master_filter(self, pkt): return (IP in pkt and pkt[IP].src == self.dst and pkt[IP].dst == self.src and TCP in pkt and pkt[TCP].sport == self.dport and pkt[TCP].dport == self.sport and self.l4[TCP].seq >= pkt[TCP].ack and # XXX: seq/ack 2^32 wrap up ((self.l4[TCP].ack == 0) or (self.l4[TCP].ack <= pkt[TCP].seq <= self.l4[TCP].ack+self.swin)) ) @ATMT.state(initial=1) def START(self): pass @ATMT.state() def SYN_SENT(self): pass @ATMT.state() def ESTABLISHED(self): pass @ATMT.state() def LAST_ACK(self): pass @ATMT.state(final=1) def CLOSED(self): pass @ATMT.condition(START) def connect(self): raise self.SYN_SENT() @ATMT.action(connect) def send_syn(self): self.l4[TCP].flags = "S" self.send(self.l4) self.l4[TCP].seq += 1 @ATMT.receive_condition(SYN_SENT) def synack_received(self, pkt): if pkt[TCP].flags & 0x3f == 0x12: raise self.ESTABLISHED().action_parameters(pkt) @ATMT.action(synack_received) def send_ack_of_synack(self, pkt): self.l4[TCP].ack = pkt[TCP].seq+1 self.l4[TCP].flags = "A" self.send(self.l4) @ATMT.receive_condition(ESTABLISHED) def incoming_data_received(self, pkt): if not isinstance(pkt[TCP].payload, NoPayload) and not isinstance(pkt[TCP].payload, conf.padding_layer): raise self.ESTABLISHED().action_parameters(pkt) @ATMT.action(incoming_data_received) def receive_data(self,pkt): data = str(pkt[TCP].payload) if data and self.l4[TCP].ack == pkt[TCP].seq: self.l4[TCP].ack += len(data) self.l4[TCP].flags = "A" self.send(self.l4) self.rcvbuf += data if pkt[TCP].flags & 8 != 0: #PUSH self.oi.tcp.send(self.rcvbuf) self.rcvbuf = "" @ATMT.ioevent(ESTABLISHED,name="tcp", as_supersocket="tcplink") def outgoing_data_received(self, fd): raise self.ESTABLISHED().action_parameters(fd.recv()) @ATMT.action(outgoing_data_received) def send_data(self, d): self.l4[TCP].flags = "PA" self.send(self.l4/d) self.l4[TCP].seq += len(d) @ATMT.receive_condition(ESTABLISHED) def reset_received(self, pkt): if pkt[TCP].flags & 4 != 0: raise self.CLOSED() @ATMT.receive_condition(ESTABLISHED) def fin_received(self, pkt): if pkt[TCP].flags & 0x1 == 1: raise self.LAST_ACK().action_parameters(pkt) @ATMT.action(fin_received) def send_finack(self, pkt): self.l4[TCP].flags = "FA" self.l4[TCP].ack = pkt[TCP].seq+1 self.send(self.l4) self.l4[TCP].seq += 1 @ATMT.receive_condition(LAST_ACK) def ack_of_fin_received(self, pkt): if pkt[TCP].flags & 0x3f == 0x10: raise self.CLOSED() ##################### ## Reporting stuff ## ##################### def report_ports(target, ports): """portscan a target and output a LaTeX table report_ports(target, ports) -> string""" ans,unans = sr(IP(dst=target)/TCP(dport=ports),timeout=5) rep = "\\begin{tabular}{|r|l|l|}\n\\hline\n" for s,r in ans: if not r.haslayer(ICMP): if r.payload.flags == 0x12: rep += r.sprintf("%TCP.sport% & open & SA \\\\\n") rep += "\\hline\n" for s,r in ans: if r.haslayer(ICMP): rep += r.sprintf("%TCPerror.dport% & closed & ICMP type %ICMP.type%/%ICMP.code% from %IP.src% \\\\\n") elif r.payload.flags != 0x12: rep += r.sprintf("%TCP.sport% & closed & TCP %TCP.flags% \\\\\n") rep += "\\hline\n" for i in unans: rep += i.sprintf("%TCP.dport% & ? & unanswered \\\\\n") rep += "\\hline\n\\end{tabular}\n" return rep def IPID_count(lst, funcID=lambda x:x[1].id, funcpres=lambda x:x[1].summary()): idlst = map(funcID, lst) idlst.sort() classes = [idlst[0]]+map(lambda x:x[1],filter(lambda (x,y): abs(x-y)>50, map(lambda x,y: (x,y),idlst[:-1], idlst[1:]))) lst = map(lambda x:(funcID(x), funcpres(x)), lst) lst.sort() print "Probably %i classes:" % len(classes), classes for id,pr in lst: print "%5i" % id, pr def fragleak(target,sport=123, dport=123, timeout=0.2, onlyasc=0): load = "XXXXYYYYYYYYYY" # getmacbyip(target) # pkt = IP(dst=target, id=RandShort(), options="\x22"*40)/UDP()/load pkt = IP(dst=target, id=RandShort(), options="\x00"*40, flags=1)/UDP(sport=sport, dport=sport)/load s=conf.L3socket() intr=0 found={} try: while 1: try: if not intr: s.send(pkt) sin,sout,serr = select([s],[],[],timeout) if not sin: continue ans=s.recv(1600) if not isinstance(ans, IP): #TODO: IPv6 continue if not isinstance(ans.payload, ICMP): continue if not isinstance(ans.payload.payload, IPerror): continue if ans.payload.payload.dst != target: continue if ans.src != target: print "leak from", ans.src, # print repr(ans) if not ans.haslayer(conf.padding_layer): continue # print repr(ans.payload.payload.payload.payload) # if not isinstance(ans.payload.payload.payload.payload, conf.raw_layer): # continue # leak = ans.payload.payload.payload.payload.load[len(load):] leak = ans.getlayer(conf.padding_layer).load if leak not in found: found[leak]=None linehexdump(leak, onlyasc=onlyasc) except KeyboardInterrupt: if intr: raise intr=1 except KeyboardInterrupt: pass def fragleak2(target, timeout=0.4, onlyasc=0): found={} try: while 1: p = sr1(IP(dst=target, options="\x00"*40, proto=200)/"XXXXYYYYYYYYYYYY",timeout=timeout,verbose=0) if not p: continue if conf.padding_layer in p: leak = p[conf.padding_layer].load if leak not in found: found[leak]=None linehexdump(leak,onlyasc=onlyasc) except: pass conf.stats_classic_protocols += [TCP,UDP,ICMP] conf.stats_dot11_protocols += [TCP,UDP,ICMP] if conf.ipv6_enabled: import scapy.layers.inet6