Add packaged DLP broker and protected Windows service foundation
This commit is contained in:
1 parent
95d23f4e0b
commit
7b5057901a
140 files changed
+11215
-60
No files matched your search
@@ -0,0 +1,126 @@
|
||||
"""Read-only native Windows WFP observations; never adds/deletes filters."""
|
||||
import ctypes as C
|
||||
import os
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
U32=C.c_uint32
|
||||
class GUID(C.Structure):
|
||||
_fields_=[('data1',U32),('data2',C.c_uint16),('data3',C.c_uint16),('data4',C.c_ubyte*8)]
|
||||
def text(self): return str(uuid.UUID(bytes_le=bytes(self)))
|
||||
class Blob(C.Structure):
|
||||
_fields_=[('size',U32),('data',C.POINTER(C.c_ubyte))]
|
||||
class Data(C.Union):
|
||||
_fields_=[('u8',C.c_uint8),('u16',C.c_uint16),('u32',U32),('pointer',C.c_void_p)]
|
||||
class Value(C.Structure):
|
||||
_anonymous_=['data']
|
||||
_fields_=[('type',U32),('data',Data)]
|
||||
class Condition(C.Structure):
|
||||
_fields_=[('field',GUID),('match',U32),('value',Value)]
|
||||
class Display(C.Structure):
|
||||
_fields_=[('name',C.c_wchar_p),('description',C.c_wchar_p)]
|
||||
class Action(C.Structure):
|
||||
_fields_=[('type',U32),('key',GUID)]
|
||||
class Context(C.Union):
|
||||
_fields_=[('raw',C.c_uint64),('provider',GUID)]
|
||||
class Filter(C.Structure):
|
||||
_fields_=[('key',GUID),('display',Display),('flags',U32),('provider',C.POINTER(GUID)),('provider_data',Blob),('layer',GUID),('sublayer',GUID),('weight',Value),('count',U32),('conditions',C.POINTER(Condition)),('action',Action),('context',Context),('reserved',C.POINTER(GUID)),('id',C.c_uint64),('effective_weight',Value)]
|
||||
|
||||
TYPES={1:'FWP_UINT8',2:'FWP_UINT16',3:'FWP_UINT32',12:'FWP_BYTE_BLOB_TYPE',14:'FWP_SECURITY_DESCRIPTOR_TYPE'}
|
||||
MATCHES={0:'equal',10:'not-equal'}
|
||||
def _blob(blob):
|
||||
if not blob.data or not 0<blob.size<=65536:raise ValueError('Invalid bounded blob')
|
||||
return C.string_at(blob.data,blob.size)
|
||||
def _decode(native):
|
||||
if native.count>16 or (native.count and not native.conditions):raise ValueError('Invalid bounded conditions')
|
||||
conditions=[]
|
||||
for index in range(native.count):
|
||||
condition=native.conditions[index];value=condition.value
|
||||
if value.type==1: parsed=value.u8
|
||||
elif value.type==2: parsed=value.u16
|
||||
elif value.type==3: parsed=value.u32
|
||||
elif value.type in (12,14):
|
||||
if not value.pointer:raise ValueError('Missing blob')
|
||||
parsed=_blob(C.cast(value.pointer,C.POINTER(Blob)).contents).hex()
|
||||
else:raise ValueError('Unsupported condition type')
|
||||
conditions.append({'field':condition.field.text(),'match':MATCHES.get(condition.match,'unsupported'),'type':TYPES[value.type],'value':parsed})
|
||||
weight=C.cast(native.weight.pointer,C.POINTER(C.c_uint64)).contents.value if native.weight.type==4 and native.weight.pointer else None
|
||||
return {'key':native.key.text(),'provider':native.provider.contents.text() if native.provider else None,'sublayer':native.sublayer.text(),'layer':native.layer.text(),'flags':native.flags,'action':native.action.type,'conditions':conditions,'weight':weight}
|
||||
|
||||
def verify_observed(plan,observed,provider,sublayer):
|
||||
"""Compare complete native observations to trusted deployment metadata."""
|
||||
provider=str(uuid.UUID(provider));sublayer=str(uuid.UUID(sublayer))
|
||||
selected=[item for item in observed if item.get('provider')==provider or item.get('sublayer')==sublayer]
|
||||
expected=plan.get('filters',[])
|
||||
if len(expected) not in (6,8) or len(selected)!=len(expected):return {'verified':False,'reason':'filter-count-mismatch'}
|
||||
def signature(conditions):return sorted((c['field'],c['match'],c['type'],str(c['value'])) for c in conditions)
|
||||
pending=list(expected)
|
||||
for item in selected:
|
||||
if item['provider']!=provider or item['sublayer']!=sublayer or not item['flags']&1 or item['flags']&(8|32) or (len(expected)==6 and item['action']!=0x1001):return {'verified':False,'reason':'filter-metadata-mismatch'}
|
||||
def expected_conditions(rule):
|
||||
result=[]
|
||||
for condition in rule['conditions']:
|
||||
condition=dict(condition)
|
||||
if condition['type']=='FWP_SECURITY_DESCRIPTOR_TYPE':
|
||||
from windows_wfp_receiver import service_descriptor
|
||||
with service_descriptor(condition['value'][len('D:(A;;CC;;;'):-1]) as blob:condition['value']=_blob(blob).hex()
|
||||
result.append(condition)
|
||||
return result
|
||||
matches=[rule for rule in pending if item['action']=={'FWP_ACTION_BLOCK':0x1001,'FWP_ACTION_PERMIT':0x1002}.get(rule['action']) and rule['clearActionRight'] is False and rule['layer']==item['layer'] and item.get('weight')==rule.get('weight') and signature(expected_conditions(rule))==signature(item['conditions'])]
|
||||
if len(matches)!=1:return {'verified':False,'reason':'filter-condition-mismatch'}
|
||||
pending.remove(matches[0])
|
||||
return {'verified':not pending,'reason':'verified' if not pending else 'filter-missing'}
|
||||
|
||||
class ReadOnlyWfp:
|
||||
def __init__(self):
|
||||
if os.name!='nt':raise RuntimeError('Windows required')
|
||||
self.api=C.WinDLL('fwpuclnt',use_last_error=True)
|
||||
definitions={
|
||||
'FwpmEngineOpen0':([C.c_wchar_p,U32,C.c_void_p,C.c_void_p,C.POINTER(C.c_void_p)],U32),
|
||||
'FwpmEngineClose0':([C.c_void_p],U32),
|
||||
'FwpmGetAppIdFromFileName0':([C.c_wchar_p,C.POINTER(C.POINTER(Blob))],U32),
|
||||
'FwpmFreeMemory0':([C.POINTER(C.c_void_p)],None),
|
||||
'FwpmFilterCreateEnumHandle0':([C.c_void_p,C.c_void_p,C.POINTER(C.c_void_p)],U32),
|
||||
'FwpmFilterDestroyEnumHandle0':([C.c_void_p,C.c_void_p],U32),
|
||||
'FwpmFilterEnum0':([C.c_void_p,C.c_void_p,U32,C.POINTER(C.POINTER(C.POINTER(Filter))),C.POINTER(U32)],U32)}
|
||||
for name,(arguments,result) in definitions.items():
|
||||
method=getattr(self.api,name);method.argtypes=arguments;method.restype=result
|
||||
def app_id(self,executable):
|
||||
target=Path(executable).resolve(strict=True)
|
||||
if not target.is_file() or target.suffix.lower()!='.exe':raise ValueError('Executable required')
|
||||
pointer=C.POINTER(Blob)();code=self.api.FwpmGetAppIdFromFileName0(str(target),C.byref(pointer))
|
||||
if code:return {'ok':False,'reason':'app-id-unavailable','code':int(code)}
|
||||
try:
|
||||
data=_blob(pointer.contents)
|
||||
return {'ok':True,'blobHex':data.hex()}
|
||||
finally:
|
||||
memory=C.cast(pointer,C.c_void_p);self.api.FwpmFreeMemory0(C.byref(memory))
|
||||
def inspect(self,plan,provider,sublayer,max_entries=4096):
|
||||
provider=str(uuid.UUID(provider));sublayer=str(uuid.UUID(sublayer))
|
||||
if not isinstance(max_entries,int) or not 6<=max_entries<=4096:raise ValueError('Invalid enumeration bound')
|
||||
engine=C.c_void_p();code=self.api.FwpmEngineOpen0(None,10,None,None,C.byref(engine))
|
||||
if code:return {'verified':False,'reason':'engine-unavailable','code':int(code)}
|
||||
enumeration=C.c_void_p()
|
||||
try:
|
||||
code=self.api.FwpmFilterCreateEnumHandle0(engine,None,C.byref(enumeration))
|
||||
if code:return {'verified':False,'reason':'enumeration-unavailable','code':int(code)}
|
||||
selected=[];total=0
|
||||
while True:
|
||||
entries=C.POINTER(C.POINTER(Filter))();count=U32()
|
||||
code=self.api.FwpmFilterEnum0(engine,enumeration,128,C.byref(entries),C.byref(count))
|
||||
if code:return {'verified':False,'reason':'enumeration-failed','code':int(code)}
|
||||
try:
|
||||
if count.value>128 or total+count.value>max_entries:return {'verified':False,'reason':'enumeration-limit'}
|
||||
total+=count.value
|
||||
for index in range(count.value):
|
||||
native=entries[index].contents
|
||||
if (native.provider and native.provider.contents.text()==provider) or native.sublayer.text()==sublayer:selected.append(_decode(native))
|
||||
if not count.value:break
|
||||
finally:
|
||||
if entries:
|
||||
memory=C.cast(entries,C.c_void_p);self.api.FwpmFreeMemory0(C.byref(memory))
|
||||
return verify_observed(plan,selected,provider,sublayer)
|
||||
except (ValueError,KeyError,TypeError):return {'verified':False,'reason':'native-contract-invalid'}
|
||||
finally:
|
||||
if enumeration:self.api.FwpmFilterDestroyEnumHandle0(engine,enumeration)
|
||||
self.api.FwpmEngineClose0(engine)
|
||||
Reference in new issue
Block a user