Skip to content

Commit

Permalink
Merge pull request #1023 from saturn597/p0f_impersonate
Browse files Browse the repository at this point in the history
Use existing TCP opts in p0f_impersonate
  • Loading branch information
p-l- authored Jan 19, 2018
2 parents 2facfe5 + 04bd9af commit 9d20c92
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 18 deletions.
73 changes: 55 additions & 18 deletions scapy/modules/p0f.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from scapy.error import warning, Scapy_Exception, log_runtime
from scapy.volatile import RandInt, RandByte, RandChoice, RandNum, RandShort, RandString
from scapy.sendrecv import sniff
from scapy.modules import six
from scapy.modules.six.moves import map, range
if conf.route is None:
# unused import, only to initialize conf.route
Expand Down Expand Up @@ -359,10 +360,7 @@ def p0f_impersonate(pkt, osgenre=None, osdetails=None, signature=None,

if not isinstance(pkt, IP) or not isinstance(pkt.payload, TCP):
raise TypeError("Not a TCP/IP packet")

if uptime is None:
uptime = random.randint(120,100*60*60*24*365)


db = p0f_selectdb(pkt.payload.flags)
if osgenre:
pb = db.get_base()
Expand All @@ -386,50 +384,89 @@ def p0f_impersonate(pkt, osgenre=None, osdetails=None, signature=None,
pers = pb[random.randint(0, len(pb) - 1)]

# options (we start with options because of MSS)
## TODO: let the options already set if they are valid
# Take the options already set as "hints" to use in the new packet if we
# can. MSS, WScale and Timestamp can all be wildcarded in a signature, so
# we'll use the already-set values if they're valid integers.
orig_opts = dict(pkt.payload.options)
int_only = lambda val: val if isinstance(val, six.integer_types) else None
mss_hint = int_only(orig_opts.get('MSS'))
wscale_hint = int_only(orig_opts.get('WScale'))
ts_hint = [int_only(o) for o in orig_opts.get('Timestamp', (None, None))]

options = []
if pers[4] != '.':
for opt in pers[4].split(','):
if opt[0] == 'M':
# MSS might have a maximum size because of window size
# specification
if pers[0][0] == 'S':
maxmss = (2**16-1) / int(pers[0][1:])
maxmss = (2**16-1) // int(pers[0][1:])
else:
maxmss = (2**16-1)
# disregard hint if out of range
if mss_hint and not 0 <= mss_hint <= maxmss:
mss_hint = None
# If we have to randomly pick up a value, we cannot use
# scapy RandXXX() functions, because the value has to be
# set in case we need it for the window size value. That's
# why we use random.randint()
if opt[1:] == '*':
options.append(('MSS', random.randint(1,maxmss)))
if mss_hint is not None:
options.append(('MSS', mss_hint))
else:
options.append(('MSS', random.randint(1, maxmss)))
elif opt[1] == '%':
coef = int(opt[2:])
options.append(('MSS', coef*random.randint(1,maxmss/coef)))
if mss_hint is not None and mss_hint % coef == 0:
options.append(('MSS', mss_hint))
else:
options.append((
'MSS', coef*random.randint(1, maxmss//coef)))
else:
options.append(('MSS', int(opt[1:])))
elif opt[0] == 'W':
if wscale_hint and not 0 <= wscale_hint < 2**8:
wscale_hint = None
if opt[1:] == '*':
options.append(('WScale', RandByte()))
if wscale_hint is not None:
options.append(('WScale', wscale_hint))
else:
options.append(('WScale', RandByte()))
elif opt[1] == '%':
coef = int(opt[2:])
options.append(('WScale', coef*RandNum(min=1,
max=(2**8-1)/coef)))
if wscale_hint is not None and wscale_hint % coef == 0:
options.append(('WScale', wscale_hint))
else:
options.append((
'WScale', coef*RandNum(min=1, max=(2**8-1)//coef)))
else:
options.append(('WScale', int(opt[1:])))
elif opt == 'T0':
options.append(('Timestamp', (0, 0)))
elif opt == 'T':
if 'T' in pers[5]:
# Determine first timestamp.
if uptime is not None:
ts_a = uptime
elif ts_hint[0] and 0 < ts_hint[0] < 2**32:
# Note: if first ts is 0, p0f registers it as "T0" not "T",
# hence we don't want to use the hint if it was 0.
ts_a = ts_hint[0]
else:
ts_a = random.randint(120, 100*60*60*24*365)
# Determine second timestamp.
if 'T' not in pers[5]:
ts_b = 0
elif ts_hint[1] and 0 < ts_hint[1] < 2**32:
ts_b = ts_hint[1]
else:
# FIXME: RandInt() here does not work (bug (?) in
# TCPOptionsField.m2i often raises "OverflowError:
# long int too large to convert to int" in:
# oval = struct.pack(ofmt, *oval)"
# Actually, this is enough to often raise the error:
# struct.pack('I', RandInt())
options.append(('Timestamp', (uptime, random.randint(1,2**32-1))))
else:
options.append(('Timestamp', (uptime, 0)))
ts_b = random.randint(1, 2**32-1)
options.append(('Timestamp', (ts_a, ts_b)))
elif opt == 'S':
options.append(('SAckOK', ''))
elif opt == 'N':
Expand Down Expand Up @@ -457,15 +494,15 @@ def p0f_impersonate(pkt, osgenre=None, osdetails=None, signature=None,
pkt.payload.window = int(pers[0])
elif pers[0][0] == '%':
coef = int(pers[0][1:])
pkt.payload.window = coef * RandNum(min=1,max=(2**16-1)/coef)
pkt.payload.window = coef * RandNum(min=1, max=(2**16-1)//coef)
elif pers[0][0] == 'T':
pkt.payload.window = mtu * int(pers[0][1:])
elif pers[0][0] == 'S':
## needs MSS set
mss = [x for x in options if x[0] == 'MSS']
if not mss:
raise Scapy_Exception("TCP window value requires MSS, and MSS option not set")
pkt.payload.window = filter(lambda x: x[0] == 'MSS', options)[0][1] * int(pers[0][1:])
pkt.payload.window = mss[0][1] * int(pers[0][1:])
else:
raise Scapy_Exception('Unhandled window size specification')

Expand All @@ -487,7 +524,7 @@ def p0f_impersonate(pkt, osgenre=None, osdetails=None, signature=None,
if db == p0fo_kdb:
pkt.payload.flags |= 0x20 # U
else:
pkt.payload.flags |= RandChoice(8, 32, 40) #P / U / PU
pkt.payload.flags |= random.choice([8, 32, 40]) # P/U/PU
elif qq == 'D' and db != p0fo_kdb:
pkt /= conf.raw_layer(load=RandString(random.randint(1, 10))) # XXX p0fo.fp
elif qq == 'Q': pkt.payload.seq = pkt.payload.ack
Expand Down
2 changes: 2 additions & 0 deletions scapy/volatile.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ def __rsub__(self, other):
return other - self._fix()
def __mul__(self, other):
return self._fix() * other
def __rmul__(self, other):
return other * self._fix()
def __floordiv__(self, other):
return self._fix() / other
__div__ = __floordiv__
Expand Down
62 changes: 62 additions & 0 deletions test/p0f.uts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
% Tests for Scapy's p0f module.

~ p0f


############
############
+ Basic p0f module tests

= Module loading
load_module('p0f')


############
############
+ Tests for p0f_impersonate

# XXX: a lot of pieces of p0f_impersonate don't have tests yet.

= Impersonate when window size must be multiple of some integer
sig = ('%467', 64, 1, 60, 'M*,W*', '.', 'Phony Sys', '1.0')
pkt = p0f_impersonate(IP()/TCP(), signature=sig)
assert pkt.payload.window % 467 == 0

= Handle unusual flags ("F") quirk
sig = ('1024', 64, 0, 60, 'W*', 'F', 'Phony Sys', '1.0')
pkt = p0f_impersonate(IP()/TCP(), signature=sig)
assert (pkt.payload.flags & 40) in (8, 32, 40)

= Use valid option values from original packet
sig = ('S4', 64, 1, 60, 'M*,W*,T', '.', 'Phony Sys', '1.0')
opts = [('MSS', 1400), ('WScale', 3), ('Timestamp', (97256, 0))]
pkt = p0f_impersonate(IP()/TCP(options=opts), signature=sig)
assert pkt.payload.options == opts

= Use valid option values when multiples required
sig = ('S4', 64, 1, 60, 'M%37,W%19', '.', 'Phony Sys', '1.0')
opts = [('MSS', 37*15), ('WScale', 19*12)]
pkt = p0f_impersonate(IP()/TCP(options=opts), signature=sig)
assert pkt.payload.options == opts

= Discard non-multiple option values when multiples required
sig = ('S4', 64, 1, 60, 'M%37,W%19', '.', 'Phony Sys', '1.0')
opts = [('MSS', 37*15 + 1), ('WScale', 19*12 + 1)]
pkt = p0f_impersonate(IP()/TCP(options=opts), signature=sig)
assert pkt.payload.options[0][1] % 37 == 0
assert pkt.payload.options[1][1] % 19 == 0

= Discard bad timestamp values
sig = ('S4', 64, 1, 60, 'M*,T', '.', 'Phony Sys', '1.0')
opts = [('Timestamp', (0, 1000))]
pkt = p0f_impersonate(IP()/TCP(options=opts), signature=sig)
# since option is "T" and not "T0":
assert pkt.payload.options[1][1][0] > 0
# since T quirk is not present:
assert pkt.payload.options[1][1][1] == 0

= Discard 2nd timestamp of 0 if "T" quirk is present
sig = ('S4', 64, 1, 60, 'M*,T', 'T', 'Phony Sys', '1.0')
opts = [('Timestamp', (54321, 0))]
pkt = p0f_impersonate(IP()/TCP(options=opts), signature=sig)
assert pkt.payload.options[1][1][1] > 0

0 comments on commit 9d20c92

Please sign in to comment.