snippet: view plain - save this
1 #!/usr/bin/env python
2 # -*- coding: iso-8859-1 -*-
3 #
4 # 04/01/2007
5 #
6 # "Tous les matins je me léve, je casse le vent, je fais chier les gens"
7 # -- brice
8 #By : SM
9 import logging
10 import os
11 import sys
12 from itertools import ifilter
13 from random import randint, choice
14 from scapy import *
15
16 __metaclass__ = type # Use new-style classes by default
17
18
19 if True:
20 # logs
21 logging.getLogger("scapy").setLevel(1)
22
23 LOGPATH = os.getcwd()
24 # setup logging functionality
25 logging.basicConfig(level=logging.DEBUG,
26 format='%(levelname)s %(message)s',
27 filename=os.path.join(LOGPATH, 'fuzzer.log'),
28 filemode='wb')
29 console = logging.StreamHandler()
30 console.setLevel(logging.INFO)
31 logging.getLogger('').addHandler(console)
32
33
34
35 fid = lambda x: x
36
37 # taken from elsewhere
38 formatstrings = ["", "", "", "%s" * 4, "%s" * 8, "%s" * 15, "%s" * 30, "%x" * 1024, "%n" * 1025 , "%s" * 2048, "%s%n%x%d" * 5000, "%s" * 30000, "%s" * 40000, "%.1024d", "%.2048d", "%.4096d", "%.8200d", "%99999999999s", "%99999999999d", "%99999999999x", "%99999999999n", "%99999999999s" * 1000, "%99999999999d" * 1000, "%99999999999x" * 1000, "%99999999999n" * 1000, "%08x" * 100, "%%20s" * 1000,"%%20x" * 1000,"%%20n" * 1000,"%%20d" * 1000, "%#0123456x%08x%x%s%p%n%d%o%u%c%h%l%q%j%z%Z%t%i%e%g%f%a%C%S%08x%%#0123456x%%x%%s%%p%%n%%d%%o%%u%%c%%h%%l%%q%%j%%z%%Z%%t%%i%%e%%g%%f%%a%%C%%S%%08x"]
39
40
41 def frange(b):
42 r = [-1, 0]
43 for i in range(b):
44 e = 2**i
45 r.append(e)
46 r.append(e-1)
47 return r
48
49 class RandFormatString:
50 def __init__(self, size, fstr=formatstrings):
51 self.fstr = fstr
52 self.size = size
53
54 def __call__(self):
55 # fixme: pas varier un peu plus ?
56 return choice(self.fstr)[:self.size]
57
58 class FRandNum:
59 def __init__(self, size):
60 self.size = size
61
62 def __call__(self):
63 return choice(frange(self.size))
64
65
66 def newrandval(self):
67 fmtt = self.fmt[-1]
68 if fmtt in "BHIQ":
69 return {"B":FRandNum(8), "H":FRandNum(16), "I":FRandNum(32),
70 "Q":RandLong}[fmtt]()
71 elif fmtt == "s":
72 if self.fmt[0] in "0123456789":
73 l = int(self.fmt[:-1])
74 else:
75 l = int(self.fmt[1:-1])
76 return RandFormatString(l)()
77 else:
78 warning("no random class for [%s] (fmt=%s)." % (self.name, self.fmt))
79
80 Field.randval = newrandval
81
82
83 class Stat:
84 def __init__(self):
85 self.stats = {}
86
87 def inc(self, ff):
88 name = ff.name
89 if not name:
90 return
91 if name in self.stats:
92 self.stats[name] = (self.stats[name][0] + 1, ff.prio)
93 else:
94 self.stats[name] = (1, ff.prio)
95
96 def __repr__(self):
97 return '\n%-25s %-8s %s\n' % ('field', 'count', 'prio') + \
98 ''.join(["%-25s %-8d %d\n" % (k,c,p) \
99 for k, (c, p) in self.stats.iteritems()]) + \
100 '%-25s %-8d\n' % ('Total:', sum(map(lambda x: x[0],
101 self.stats.values())))
102
103 def __str__(self):
104 return repr(self)
105
106
107 class FF:
108 prio_def = 10
109 prio_min = 1
110 prio_max = 20
111
112 def __init__(self, fields, prio=None):
113 self.name = ''
114 self.fields = []
115 for i in fields:
116 if isinstance(i, Field):
117 self.name += i.name
118 self.fields.append(i)
119 self.prio = prio or FF.prio_def
120
121 def __add__(self, other):
122 self.prio = min(FF.prio_max, self.prio + other)
123 return self
124
125 def __sub__(self, other):
126 self.prio = max(FF.prio_min, self.prio - other)
127 return self
128
129
130 class Fuzz:
131 """ add flexibility to fuzz() """
132 include = []
133 exclude = []
134 cls = None
135
136 def __init__(self, force=True, verbose=True):
137 self.verbose = verbose
138 self.force = force
139 self.ff = []
140 self.current = None
141 self.initfields()
142
143 def initfields(self):
144 for grp in self.include:
145 acc = []
146 for name in filter(lambda f: f not in self.exclude, grp[0]):
147 for field in self.cls.fields_desc:
148 if field.name == name:
149 acc.append(field)
150 self.ff.append(FF(acc))
151 self.ff.extend(map(lambda x: FF([x]),
152 filter(lambda f: f.name not in self.exclude,
153 self.cls.fields_desc)))
154
155 def pickone(self):
156 # fixme: gerer un random sur les combinaisons
157 l = []
158 for ff in self.ff:
159 for i in range(ff.prio):
160 l.append(ff)
161 self.current = choice(l)
162
163 def fuzz(self, pkt, inplace):
164 cpkt = pkt
165 if not inplace:
166 cpkt = pkt.copy()
167
168 self.pickone()
169 for field in self.current.fields:
170 rnd = field.randval()
171 if rnd is not None:
172 if self.force:
173 cpkt.fields[field] = rnd
174 else:
175 cpkt.default_fields[field] = rnd
176 if self.verbose:
177 logging.info('> fuzz %s with value=%s' % (field, rnd))
178 return cpkt
179
180 def penalize(self):
181 self.current -= 1
182
183 def reward(self):
184 self.current += 1
185
186 def __call__(self, pkt, inplace=False):
187 return self.fuzz(pkt, inplace)
188
189
190 class BOOTPFuzz(Fuzz):
191 cls = BOOTP
192 include = [(('htype', 'hlen', 'chaddr'), 10), # 10 <=> None
193 (('hlen', 'chaddr'), 10)]
194 exclude = []
195
196 def __call__(self, pkt, inplace=False):
197 assert(isinstance(pkt, BOOTP))
198 return Fuzz.fuzz(self, pkt, inplace)
199
200
201 class DHCPFuzz(Fuzz):
202 cls = DHCP
203 include = []
204 exclude = []
205
206 def __call__(self, pkt, inplace=False):
207 assert(isinstance(pkt, DHCP))
208 return Fuzz.fuzz(self, pkt, inplace)
209
210
211 def fuzz_it(fuzz_bootp=fid, fuzz_dhcp=fid, **kargs):
212 pkt, ans = (None, None)
213 iface = conf.iface
214 fam,hw = get_if_raw_hwaddr(iface)
215 try:
216 pkt = (Ether(dst="ff:ff:ff:ff:ff:ff") /
217 IP(src="0.0.0.0", dst="255.255.255.255") /
218 UDP(sport=68, dport=67) /
219 fuzz_bootp(BOOTP(chaddr=hw)) /
220 fuzz_dhcp(DHCP(options=[("message-type", "discover"), "end"])))
221 logging.debug('> packet sent:%s' % pkt)
222 ans = srp1(pkt, iface=iface,**kargs)
223 logging.debug('> packet received:%s' % ans)
224 except Exception, err:
225 logging.info('> error in sending packet: %s' % err)
226 if isinstance(pkt, Packet):
227 pkt.show()
228 else:
229 return ans
230
231
232 def is_up(ans, verbose=True):
233 if ans is None or BOOTP not in ans:
234 logging.info('> reception error: packet invalid')
235 return False
236 if verbose:
237 logging.info('> request well performed, attributed ip: %s' % \
238 ans[BOOTP].yiaddr)
239 return True
240
241
242 def make_fuzz(iface=None, verbose=True):
243 # conf
244 conf.checkIPaddr = 0
245 if iface:
246 conf.iface = iface
247
248 # stats
249 stats = Stat()
250
251 # fuzzers
252 fuzz_bootp = BOOTPFuzz(verbose=True)
253 fuzz_dhcp = fid # DHCPFuzz(verbose=True)
254
255 # infinite fuzzing
256 count = 0
257 while True:
258 logging.info('>>>> Iteration %d' % count)
259
260 # fuzzed packet
261 ansf = fuzz_it(fuzz_bootp=fuzz_bootp, fuzz_dhcp=fuzz_dhcp, timeout=10)
262 fres = is_up(ansf)
263 stats.inc(fuzz_bootp.current)
264
265 # valid packet
266 ansv = dhcp_request() #fixme: timeout 30s ?
267 vres = is_up(ansv)
268
269 # feedback
270 if not fres:
271 fuzz_bootp.penalize()
272 #fuzz_dhcp.penalize() #fixme: useless
273 else:
274 fuzz_bootp.reward()
275 #fuzz_dhcp.reward() #fixme: useless
276
277 # stats
278 if not (count % 20):
279 logging.info('%s' % stats)
280 count += 1
281
282
283 if __name__ == "__main__":
284 #make_fuzz('eth0') # force to this interface
285 make_fuzz()

0 comments