root/scapy/utils.py

Revision 948:ae7ba3146894, 20.9 kB (checked in by Phil <phil@secdev.org>, 3 weeks ago)

Avoid using the same byte for corruption in corrupt_bytes() (ticket #155)

Line 
1 ## This file is part of Scapy
2 ## See http://www.secdev.org/projects/scapy for more informations
3 ## Copyright (C) Philippe Biondi <phil@secdev.org>
4 ## This program is published under a GPLv2 license
5
6 import os,sys,socket,types
7 import random,time
8 import gzip,zlib,cPickle
9 import re,struct,array
10
11 import warnings
12 warnings.filterwarnings("ignore","tempnam",RuntimeWarning, __name__)
13
14 from config import conf
15 from data import MTU
16 from error import log_runtime,log_loading,log_interactive
17 from base_classes import BasePacketList
18
19
20 ###########
21 ## Tools ##
22 ###########
23
24 def get_temp_file(keep=False):
25     f = os.tempnam("","scapy")
26     if not keep:
27         conf.temp_files.append(f)
28     return f
29
30 def sane_color(x):
31     r=""
32     for i in x:
33         j = ord(i)
34         if (j < 32) or (j >= 127):
35             r=r+conf.color_theme.not_printable(".")
36         else:
37             r=r+i
38     return r
39
40 def sane(x):
41     r=""
42     for i in x:
43         j = ord(i)
44         if (j < 32) or (j >= 127):
45             r=r+"."
46         else:
47             r=r+i
48     return r
49
50 def lhex(x):
51     if type(x) in (int,long):
52         return hex(x)
53     elif type(x) is tuple:
54         return "(%s)" % ", ".join(map(lhex, x))
55     elif type(x) is list:
56         return "[%s]" % ", ".join(map(lhex, x))
57     else:
58         return x
59
60 @conf.commands.register
61 def hexdump(x):
62     x=str(x)
63     l = len(x)
64     i = 0
65     while i < l:
66         print "%04x  " % i,
67         for j in range(16):
68             if i+j < l:
69                 print "%02X" % ord(x[i+j]),
70             else:
71                 print "  ",
72             if j%16 == 7:
73                 print "",
74         print " ",
75         print sane_color(x[i:i+16])
76         i += 16
77
78 @conf.commands.register
79 def linehexdump(x, onlyasc=0, onlyhex=0):
80     x = str(x)
81     l = len(x)
82     if not onlyasc:
83         for i in range(l):
84             print "%02X" % ord(x[i]),
85         print "",
86     if not onlyhex:
87         print sane_color(x)
88
89 def chexdump(x):
90     x=str(x)
91     print ", ".join(map(lambda x: "%#04x"%ord(x), x))
92    
93 def hexstr(x, onlyasc=0, onlyhex=0):
94     s = []
95     if not onlyasc:
96         s.append(" ".join(map(lambda x:"%02x"%ord(x), x)))
97     if not onlyhex:
98         s.append(sane(x))
99     return "  ".join(s)
100
101
102 @conf.commands.register
103 def hexdiff(x,y):
104     """Show differences between 2 binary strings"""
105     x=str(x)[::-1]
106     y=str(y)[::-1]
107     SUBST=1
108     INSERT=1
109     d={}
110     d[-1,-1] = 0,(-1,-1)
111     for j in range(len(y)):
112         d[-1,j] = d[-1,j-1][0]+INSERT, (-1,j-1)
113     for i in range(len(x)):
114         d[i,-1] = d[i-1,-1][0]+INSERT, (i-1,-1)
115
116     for j in range(len(y)):
117         for i in range(len(x)):
118             d[i,j] = min( ( d[i-1,j-1][0]+SUBST*(x[i] != y[j]), (i-1,j-1) ),
119                           ( d[i-1,j][0]+INSERT, (i-1,j) ),
120                           ( d[i,j-1][0]+INSERT, (i,j-1) ) )
121                          
122
123     backtrackx = []
124     backtracky = []
125     i=len(x)-1
126     j=len(y)-1
127     while not (i == j == -1):
128         i2,j2 = d[i,j][1]
129         backtrackx.append(x[i2+1:i+1])
130         backtracky.append(y[j2+1:j+1])
131         i,j = i2,j2
132
133        
134
135     x = y = i = 0
136     colorize = { 0: lambda x:x,
137                 -1: conf.color_theme.left,
138                  1: conf.color_theme.right }
139    
140     dox=1
141     doy=0
142     l = len(backtrackx)
143     while i < l:
144         separate=0
145         linex = backtrackx[i:i+16]
146         liney = backtracky[i:i+16]
147         xx = sum(len(k) for k in linex)
148         yy = sum(len(k) for k in liney)
149         if dox and not xx:
150             dox = 0
151             doy = 1
152         if dox and linex == liney:
153             doy=1
154            
155         if dox:
156             xd = y
157             j = 0
158             while not linex[j]:
159                 j += 1
160                 xd -= 1
161             print colorize[doy-dox]("%04x" % xd),
162             x += xx
163             line=linex
164         else:
165             print "    ",
166         if doy:
167             yd = y
168             j = 0
169             while not liney[j]:
170                 j += 1
171                 yd -= 1
172             print colorize[doy-dox]("%04x" % yd),
173             y += yy
174             line=liney
175         else:
176             print "    ",
177            
178         print " ",
179        
180         cl = ""
181         for j in range(16):
182             if i+j < l:
183                 if line[j]:
184                     col = colorize[(linex[j]!=liney[j])*(doy-dox)]
185                     print col("%02X" % ord(line[j])),
186                     if linex[j]==liney[j]:
187                         cl += sane_color(line[j])
188                     else:
189                         cl += col(sane(line[j]))
190                 else:
191                     print "  ",
192                     cl += " "
193             else:
194                 print "  ",
195             if j == 7:
196                 print "",
197
198
199         print " ",cl
200
201         if doy or not yy:
202             doy=0
203             dox=1
204             i += 16
205         else:
206             if yy:
207                 dox=0
208                 doy=1
209             else:
210                 i += 16
211
212    
213 crc32 = zlib.crc32
214
215 if struct.pack("H",1) == "\x00\x01": # big endian
216     def checksum(pkt):
217         if len(pkt) % 2 == 1:
218             pkt += "\0"
219         s = sum(array.array("H", pkt))
220         s = (s >> 16) + (s & 0xffff)
221         s += s >> 16
222         s = ~s
223         return s & 0xffff
224 else:
225     def checksum(pkt):
226         if len(pkt) % 2 == 1:
227             pkt += "\0"
228         s = sum(array.array("H", pkt))
229         s = (s >> 16) + (s & 0xffff)
230         s += s >> 16
231         s = ~s
232         return (((s>>8)&0xff)|s<<8) & 0xffff
233
234 def warning(x):
235     log_runtime.warning(x)
236
237 def mac2str(mac):
238     return "".join(map(lambda x: chr(int(x,16)), mac.split(":")))
239
240 def str2mac(s):
241     return ("%02x:"*6)[:-1] % tuple(map(ord, s))
242
243 def strxor(x,y):
244     return "".join(map(lambda x,y:chr(ord(x)^ord(y)),x,y))
245
246 # Workarround bug 643005 : https://sourceforge.net/tracker/?func=detail&atid=105470&aid=643005&group_id=5470
247 try:
248     socket.inet_aton("255.255.255.255")
249 except socket.error:
250     def inet_aton(x):
251         if x == "255.255.255.255":
252             return "\xff"*4
253         else:
254             return socket.inet_aton(x)
255 else:
256     inet_aton = socket.inet_aton
257
258 inet_ntoa = socket.inet_ntoa
259 try:
260     inet_ntop = socket.inet_ntop
261     inet_pton = socket.inet_pton
262 except AttributeError:
263     log_loading.info("inet_ntop/pton functions not found. Python IPv6 support not present")
264
265
266 def atol(x):
267     try:
268         ip = inet_aton(x)
269     except socket.error:
270         ip = inet_aton(socket.gethostbyname(x))
271     return struct.unpack("!I", ip)[0]
272 def ltoa(x):
273     return inet_ntoa(struct.pack("!I", x&0xffffffff))
274
275 def itom(x):
276     return (0xffffffff00000000L>>x)&0xffffffffL
277
278 def do_graph(graph,prog=None,format="svg",target=None, type=None,string=None,options=None):
279     """do_graph(graph, prog=conf.prog.dot, format="svg",
280          target="| conf.prog.display", options=None, [string=1]):
281     string: if not None, simply return the graph string
282     graph: GraphViz graph description
283     format: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option
284     target: filename or redirect. Defaults pipe to Imagemagick's display program
285     prog: which graphviz program to use
286     options: options to be passed to prog"""
287        
288
289     if string:
290         return graph
291     if type is not None:
292         format=type
293     if prog is None:
294         prog = conf.prog.dot
295     if target is None:
296         target = "| %s" % conf.prog.display
297     if format is not None:
298         format = "-T %s" % format
299     w,r = os.popen2("%s %s %s %s" % (prog,options or "", format or "", target))
300     w.write(graph)
301     w.close()
302
303 _TEX_TR = {
304     "{":"{\\tt\\char123}",
305     "}":"{\\tt\\char125}",
306     "\\":"{\\tt\\char92}",
307     "^":"\\^{}",
308     "$":"\\$",
309     "#":"\\#",
310     "~":"\\~",
311     "_":"\\_",
312     "&":"\\&",
313     "%":"\\%",
314     "|":"{\\tt\\char124}",
315     "~":"{\\tt\\char126}",
316     "<":"{\\tt\\char60}",
317     ">":"{\\tt\\char62}",
318     }
319    
320 def tex_escape(x):
321     s = ""
322     for c in x:
323         s += _TEX_TR.get(c,c)
324     return s
325
326 def colgen(*lstcol,**kargs):
327     """Returns a generator that mixes provided quantities forever
328     trans: a function to convert the three arguments into a color. lambda x,y,z:(x,y,z) by default"""
329     if len(lstcol) < 2:
330         lstcol *= 2
331     trans = kargs.get("trans", lambda x,y,z: (x,y,z))
332     while 1:
333         for i in range(len(lstcol)):
334             for j in range(len(lstcol)):
335                 for k in range(len(lstcol)):
336                     if i != j or j != k or k != i:
337                         yield trans(lstcol[(i+j)%len(lstcol)],lstcol[(j+k)%len(lstcol)],lstcol[(k+i)%len(lstcol)])
338
339 def incremental_label(label="tag%05i", start=0):
340     while True:
341         yield label % start
342         start += 1
343
344 #########################
345 #### Enum management ####
346 #########################
347
348 class EnumElement:
349     _value=None
350     def __init__(self, key, value):
351         self._key = key
352         self._value = value
353     def __repr__(self):
354         return "<%s %s[%r]>" % (self.__dict__.get("_name", self.__class__.__name__), self._key, self._value)
355     def __getattr__(self, attr):
356         return getattr(self._value, attr)
357     def __str__(self):
358         return self._key
359     def __eq__(self, other):
360         return self._value == int(other)
361
362
363 class Enum_metaclass(type):
364     element_class = EnumElement
365     def __new__(cls, name, bases, dct):
366         rdict={}
367         for k,v in dct.iteritems():
368             if type(v) is int:
369                 v = cls.element_class(k,v)
370                 dct[k] = v
371                 rdict[v] = k
372         dct["__rdict__"] = rdict
373         return super(Enum_metaclass, cls).__new__(cls, name, bases, dct)
374     def __getitem__(self, attr):
375         return self.__rdict__[attr]
376     def __contains__(self, val):
377         return val in self.__rdict__
378     def get(self, attr, val=None):
379         return self._rdict__.get(attr, val)
380     def __repr__(self):
381         return "<%s>" % self.__dict__.get("name", self.__name__)
382
383
384
385 ###################
386 ## Object saving ##
387 ###################
388
389
390 def export_object(obj):
391     print gzip.zlib.compress(cPickle.dumps(obj,2),9).encode("base64")
392
393 def import_object(obj=None):
394     if obj is None:
395         obj = sys.stdin.read()
396     return cPickle.loads(gzip.zlib.decompress(obj.strip().decode("base64")))
397
398
399 def save_object(fname, obj):
400     cPickle.dump(obj,gzip.open(fname,"wb"))
401
402 def load_object(fname):
403     return cPickle.load(gzip.open(fname,"rb"))
404
405 @conf.commands.register
406 def corrupt_bytes(s, p=0.01, n=None):
407     """Corrupt a given percentage or number of bytes from a string"""
408     s = array.array("B",str(s))
409     l = len(s)
410     if n is None:
411         n = max(1,int(l*p))
412     for i in random.sample(xrange(l), n):
413         s[i] = (s[i]+random.randint(1,255))%256
414     return s.tostring()
415
416 @conf.commands.register
417 def corrupt_bits(s, p=0.01, n=None):
418     """Flip a given percentage or number of bits from a string"""
419     s = array.array("B",str(s))
420     l = len(s)*8
421     if n is None:
422         n = max(1,int(l*p))
423     for i in random.sample(xrange(l), n):
424         s[i/8] ^= 1 << (i%8)
425     return s.tostring()
426
427    
428
429
430 #############################
431 ## pcap capture file stuff ##
432 #############################
433
434 @conf.commands.register
435 def wrpcap(filename, pkt, *args, **kargs):
436     """Write a list of packets to a pcap file
437 gz: set to 1 to save a gzipped capture
438 linktype: force linktype value
439 endianness: "<" or ">", force endianness"""
440     PcapWriter(filename, *args, **kargs).write(pkt)
441
442 @conf.commands.register
443 def rdpcap(filename, count=-1):
444     """Read a pcap file and return a packet list
445 count: read only <count> packets"""
446     return PcapReader(filename).read_all(count=count)
447
448
449
450 class RawPcapReader:
451     """A stateful pcap reader. Each packet is returned as a string"""
452
453     def __init__(self, filename):
454         self.filename = filename
455         try:
456             self.f = gzip.open(filename,"rb")
457             magic = self.f.read(4)
458         except IOError:
459             self.f = open(filename,"rb")
460             magic = self.f.read(4)
461         if magic == "\xa1\xb2\xc3\xd4": #big endian
462             self.endian = ">"
463         elif  magic == "\xd4\xc3\xb2\xa1": #little endian
464             self.endian = "<"
465         else:
466             raise Scapy_Exception("Not a pcap capture file (bad magic)")
467         hdr = self.f.read(20)
468         if len(hdr)<20:
469             raise Scapy_Exception("Invalid pcap file (too short)")
470         vermaj,vermin,tz,sig,snaplen,linktype = struct.unpack(self.endian+"HHIIII",hdr)
471
472         self.linktype = linktype
473
474
475
476     def __iter__(self):
477         return self
478
479     def next(self):
480         """impliment the iterator protocol on a set of packets in a pcap file"""
481         pkt = self.read_packet()
482         if pkt == None:
483             raise StopIteration
484         return pkt
485
486
487     def read_packet(self):
488         """return a single packet read from the file
489         
490         returns None when no more packets are available
491         """
492         hdr = self.f.read(16)
493         if len(hdr) < 16:
494             return None
495         sec,usec,caplen,wirelen = struct.unpack(self.endian+"IIII", hdr)
496         s = self.f.read(caplen)
497         return s,(sec,usec,wirelen) # caplen = len(s)
498
499
500     def dispatch(self, callback):
501         """call the specified callback routine for each packet read
502         
503         This is just a convienience function for the main loop
504         that allows for easy launching of packet processing in a
505         thread.
506         """
507         for p in self:
508             callback(p)
509
510     def read_all(self,count=-1):
511         """return a list of all packets in the pcap file
512         """
513         res=[]
514         while count != 0:
515             count -= 1
516             p = self.read_packet()
517             if p is None:
518                 break
519             res.append(p)
520         return res
521
522     def recv(self, size):
523         """ Emulate a socket
524         """
525         return self.read_packet()[0]
526
527     def fileno(self):
528         return self.f.fileno()
529
530     def close(self):
531         return self.f.close()
532
533    
534
535 class PcapReader(RawPcapReader):
536     def __init__(self, filename):
537         RawPcapReader.__init__(self, filename)
538         try:
539             self.LLcls = conf.l2types[self.linktype]
540         except KeyError:
541             warning("PcapReader: unknown LL type [%i]/[%#x]. Using Raw packets" % (self.linktype,self.linktype))
542             self.LLcls = conf.raw_layer
543     def read_packet(self):
544         rp = RawPcapReader.read_packet(self)
545         if rp is None:
546             return None
547         s,(sec,usec,wirelen) = rp
548        
549         try:
550             p = self.LLcls(s)
551         except KeyboardInterrupt:
552             raise
553         except:
554             if conf.debug_dissector:
555                 raise
556             p = conf.raw_layer(s)
557         p.time = sec+0.000001*usec
558         return p
559     def read_all(self,count=-1):
560         res = RawPcapReader.read_all(self, count)
561         import plist
562         return plist.PacketList(res,name = os.path.basename(self.filename))
563     def recv(self, size):
564         return self.read_packet()
565        
566
567
568 class RawPcapWriter:
569     """A stream PCAP writer with more control than wrpcap()"""
570     def __init__(self, filename, linktype=None, gz=False, endianness="", append=False, sync=False):
571         """
572         linktype: force linktype to a given value. If None, linktype is taken
573                   from the first writter packet
574         gz: compress the capture on the fly
575         endianness: force an endianness (little:"<", big:">"). Default is native
576         append: append packets to the capture file instead of truncating it
577         sync: do not bufferize writes to the capture file
578         """
579        
580         self.linktype = linktype
581         self.header_present = 0
582         self.append=append
583         self.gz = gz
584         self.endian = endianness
585         self.filename=filename
586         self.sync=sync
587         bufsz=4096
588         if sync:
589             bufsz=0
590
591         self.f = [open,gzip.open][gz](filename,append and "ab" or "wb", gz and 9 or bufsz)
592        
593     def fileno(self):
594         return self.f.fileno()
595
596     def _write_header(self, pkt):
597         self.header_present=1
598
599         if self.append:
600             # Even if prone to race conditions, this seems to be
601             # safest way to tell whether the header is already present
602             # because we have to handle compressed streams that
603             # are not as flexible as basic files
604             g = [open,gzip.open][self.gz](self.filename,"rb")
605             if g.read(16):
606                 return
607            
608         self.f.write(struct.pack(self.endian+"IHHIIII", 0xa1b2c3d4L,
609                                  2, 4, 0, 0, MTU, self.linktype))
610         self.f.flush()
611    
612
613     def write(self, pkt):
614         """accepts a either a single packet or a list of packets
615         to be written to the dumpfile
616         """
617         if not self.header_present:
618             self._write_header(pkt)
619         if type(pkt) is str:
620             self._write_packet(pkt)
621         else:
622             for p in pkt:
623                 self._write_packet(p)
624
625     def _write_packet(self, packet, sec=None, usec=None, caplen=None, wirelen=None):
626         """writes a single packet to the pcap file
627         """
628         if caplen is None:
629             caplen = len(packet)
630         if wirelen is None:
631             wirelen = caplen
632         if sec is None or usec is None:
633             t=time.time()
634             it = int(t)
635             if sec is None:
636                 sec = it
637             if usec is None:
638                 usec = int((t-it)*1000000)
639         self.f.write(struct.pack(self.endian+"IIII", sec, usec, caplen, wirelen))
640         self.f.write(packet)
641         if self.gz and self.sync:
642             self.f.flush()
643
644     def flush(self):
645         return self.f.flush()
646     def close(self):
647         return self.f.close()
648                
649 class PcapWriter(RawPcapWriter):
650     def _write_header(self, pkt):
651         if self.linktype == None:
652             if type(pkt) is list or type(pkt) is tuple or isinstance(pkt,BasePacketList):
653                 pkt = pkt[0]
654             try:
655                 self.linktype = conf.l2types[pkt.__class__]
656             except KeyError:
657                 warning("PcapWriter: unknown LL type for %s. Using type 1 (Ethernet)" % pkt.__class__.__name__)
658                 self.linktype = 1
659         RawPcapWriter._write_header(self, pkt)
660
661     def _write_packet(self, packet):       
662         sec = int(packet.time)
663         usec = int((packet.time-sec)*1000000)
664         s = str(packet)
665         caplen = len(s)
666         RawPcapWriter._write_packet(self, s, sec, usec, caplen, caplen)
667
668
669 re_extract_hexcap = re.compile("^(0x[0-9a-fA-F]{2,}[ :\t]|(0x)?[0-9a-fA-F]{2,}:|(0x)?[0-9a-fA-F]{3,}[: \t]|) *(([0-9a-fA-F]{2} {,2}){,16})")
670
671 def import_hexcap():
672     p = ""
673     try:
674         while 1:
675             l = raw_input().strip()
676             try:
677                 p += re_extract_hexcap.match(l).groups()[3]
678             except:
679                 warning("Parsing error during hexcap")
680                 continue
681     except EOFError:
682         pass
683    
684     p = p.replace(" ","")
685     p2=""
686     for i in range(len(p)/2):
687         p2 += chr(int(p[2*i:2*i+2],16))
688     return p2
689        
690
691
692 @conf.commands.register
693 def wireshark(pktlist):
694     """Run wireshark on a list of packets"""
695     f = get_temp_file()
696     wrpcap(f, pktlist)
697     os.spawnlp(os.P_NOWAIT, conf.prog.wireshark, conf.prog.wireshark, "-r", f)
698
699 @conf.commands.register
700 def hexedit(x):
701     x = str(x)
702     f = get_temp_file()
703     open(f,"w").write(x)
704     os.spawnlp(os.P_WAIT, conf.prog.hexedit, conf.prog.hexedit, f)
705     x = open(f).read()
706     os.unlink(f)
707     return x
708
709 def __make_table(yfmtfunc, fmtfunc, endline, list, fxyz, sortx=None, sorty=None, seplinefunc=None):
710     vx = {}
711     vy = {}
712     vz = {}
713     vxf = {}
714     vyf = {}
715     l = 0
716     for e in list:
717         xx,yy,zz = map(str, fxyz(e))
718         l = max(len(yy),l)
719         vx[xx] = max(vx.get(xx,0), len(xx), len(zz))
720         vy[yy] = None
721         vz[(xx,yy)] = zz
722
723     vxk = vx.keys()
724     vyk = vy.keys()
725     if sortx:
726         vxk.sort(sortx)
727     else:
728         try:
729             vxk.sort(lambda x,y:int(x)-int(y))
730         except:
731             try:
732                 vxk.sort(lambda x,y: cmp(atol(x),atol(y)))
733             except:
734                 vxk.sort()
735     if sorty:
736         vyk.sort(sorty)
737     else:
738         try:
739             vyk.sort(lambda x,y:int(x)-int(y))
740         except:
741             try:
742                 vyk.sort(lambda x,y: cmp(atol(x),atol(y)))
743             except:
744                 vyk.sort()
745
746
747     if seplinefunc:
748         sepline = seplinefunc(l, map(lambda x:vx[x],vxk))
749         print sepline
750
751     fmt = yfmtfunc(l)
752     print fmt % "",
753     for x in vxk:
754         vxf[x] = fmtfunc(vx[x])
755         print vxf[x] % x,
756     print endline
757     if seplinefunc:
758         print sepline
759     for y in vyk:
760         print fmt % y,
761         for x in vxk:
762             print vxf[x] % vz.get((x,y), "-"),
763         print endline
764     if seplinefunc:
765         print sepline
766
767 def make_table(*args, **kargs):
768     __make_table(lambda l:"%%-%is" % l, lambda l:"%%-%is" % l, "", *args, **kargs)
769    
770 def make_lined_table(*args, **kargs):
771     __make_table(lambda l:"%%-%is |" % l, lambda l:"%%-%is |" % l, "",
772                  seplinefunc=lambda a,x:"+".join(map(lambda y:"-"*(y+2), [a-1]+x+[-2])),
773                  *args, **kargs)
774
775 def make_tex_table(*args, **kargs):
776     __make_table(lambda l: "%s", lambda l: "& %s", "\\\\", seplinefunc=lambda a,x:"\\hline", *args, **kargs)
777
Note: See TracBrowser for help on using the browser.