def decipher(self, key=16*'\0', dir=0): # key: K_NAS_enc # cnt: NAS uplink or downlink counter # dir: direction (uplink / downlink) # using self.EEA # # if no deciphering to apply if self.SH() not in (2, 3) \ or self.EEA not in (None, EEA1, EEA2, EEA3): return if not hasattr(self, '_pay') \ or (not isinstance(self[-1], Str) and self[-1].CallName != '_enc'): log(ERR, 'Layer3NAS - decipher: not ready for deciphering') return # if self.EEA is None: # null ciphering EEA0 dec = str(self[-1]) else: dec = self.EEA(key, self.SN(), 0, dir, str(self[-1])) # # get the complete packet buffer buf = str(self[:4]) + dec # reinsert NAS payload IEs self.remove(self[-1]) self.extend(self._pay) # remap the complete deciphered buffer to the NAS layer Layer3.map(self, buf) self._map_eps_id()
def interpret_IE(self, field, cn): # interpret field as cn # cn is looked up in L3Mobile_IE, L3GSM_IE or L3GSM_RR libs if self.dbg >= DBG: log(DBG, 'Layer3 - interpret IE: %s' % cn) # check if direct field, or (T)LV-like field if hasattr(field, 'V'): # bypass IE interpretation in case of LV or TLV with null length if hasattr(field, 'L') and field.L() == 0: return f_val = field.V else: f_val = field # replace the raw field value by pointing to the retrieved IE buf = str(f_val) ie = IE_lookup[cn]() try: ie.map(buf) except: if self.dbg >= WNG: log(WNG, 'Layer3 - IE mapping failed for %s' % ie.__class__) else: # only update the field if we got correct length data for the IE if str(ie) == buf: #if len(ie) == len(buf): f_val < None f_val > ie # and go for human representation... f_val.Repr = 'hum'
def test_dict(): e = 0 for pd in L3Call: for msg in L3Call[pd]: error = '' cl = L3Call[pd][msg] if not issubclass(cl, Layer3): log(WNG, 'message %s not subclass of Layer3' % repr(cl)) else: try: m = cl(with_options=True) except: error = '__init__(with_options=True)' else: try: buf = str(m) except: error = '__str__()' else: try: m2 = cl(with_options=False) m2.parse(buf) except: error = 'parse()' else: if str(m2) != buf: error = '__str__() result discrepancy ' + \ ' with parse()d buffer' if error: log(ERR, 'message %s test returns error: %s' \ % (repr(cl), error)) e += 1 return e
def _build_path(self, csn1iter): # WNG : there is probably an issue as the ._offset attribute # is not changed during the several recursive calls... # cnt=0 # recursive loading of CSN.1 fields, following the path of conditions for f in csn1iter: if self.dbg >= DBG: log(DBG, '(_build_path - %s) iterating over: %s' \ % (self.__class__, repr(f))) if isinstance(f, CSN1FIELDS): #print('<<<<<<<<<<<<<<<<<<<<<< %s' % f.CallName) self._append_csn1_field(f) elif isinstance(f, tuple): self._build_path(f) # elif isinstance(f, dict): # in case there is no more conditions privided if not self._cur_path: self._build_auto(csn1iter[cnt:]) return # this is the case were we need to consume the _cur_path # and append the values as of the provided conditions # self._build_from_dict(f) # cnt += 1
def map(self, s=''): if not s: return # check the security header s0 = ord(s[0]) sh, pd = s0>>4, s0&0xF # ESM, or EMM with no security header if pd == 2 or sh in (0, 12): self.__init__(with_security=False) Layer3.map(self, s) self._map_eps_id() # EMM with security header elif pd == 7 and sh in (1, 2, 3, 4): self.ins_sec_hdr() # if no ciphering applied if sh in (1, 3): #if sh in (1, 3) or self.EEA not in (EEA1, EEA2, EEA3): # if no payload is already there, just add a ciphered-like one if len(self.elementList) == 4: self << Str('_enc') # map directly the buffer onto the NAS payload Layer3.map(self, s) self._map_eps_id() else: # keep track of all IE of the original packet self._pay = self[4:] # replace them with a dummy string for ie in self._pay: self.remove(ie) self << Str('_enc') Layer3.map(self, s) else: log(ERR, '[ERR] invalid Security Header value %i' % sh)
def _cond_error(self, condset): #if self.dbg >= ERR: # log(ERR, '(CSN1._get_cond - %s) undefined CSN1 condition set: %s' \ # % (condset, self.__class__)) log(ERR, '(CSN1._get_cond - %s) undefined CSN1 condition set: %s' \ % (condset, self.__class__)) if self.safe: assert()
def _select_tag(self, s='\0', taglist=[]): # check for 4 bits and 8 bits tags t4, t8 = ord(s[0])>>4, ord(s[0]) if self.dbg >= DBG: log(DBG, '(Layer3 - %s) t4, t8: %s, %s\ntaglist: %s' \ % (self.CallName, t4, t8, taglist)) # # try another way: # prefer the tag which is 1st in the taglist, whatever tag length for t in taglist: if t in (t4, t8): return t return None
def _select_tag(self, s='\0', taglist=[]): # check for 4 bits and 8 bits tags t4, t8 = ord(s[0]) >> 4, ord(s[0]) if self.dbg >= DBG: log(DBG, '(Layer3 - %s) t4, t8: %s, %s\ntaglist: %s' \ % (self.CallName, t4, t8, taglist)) # # try another way: # prefer the tag which is 1st in the taglist, whatever tag length for t in taglist: if t in (t4, t8): return t return None
def _append_path(self, path): if self.dbg >= DBG: log(DBG, '(build - %s) going path: %s' \ % (self.__class__, repr(path))) cond = '' for c in path: # if LH, get the correct value with self._offset if c in 'LH': L = self.L[self._offset%8] self.append(Bit('%s' % self.cond_name, \ Pt={'L':L, 'H':(1, 0)[L]}[c] , BitLen=1)) elif c in '01': self.append(Bit('%s' % self.pad_name, Pt=int(c, 2), BitLen=1)) else: self._cond_error(path) self._offset += 1
def _build_auto(self, csn1iter): #print self.CallName # recursive automatic loading of CSN.1 fields for f in csn1iter: if self.dbg >= DBG: log(DBG, '(_build_auto - %s) iterating over: %s' \ % (self.__class__, repr(f))) if isinstance(f, CSN1FIELDS): self._append_csn1_field(f) elif isinstance(f, tuple): self._build_auto(f) elif isinstance(f, dict): # select the shortest path: BREAK, BREAK_LOOP, # or the shortest value values = f.values() # easiest path is to get straight out of the dict if BREAK in values or BREAK_LOOP in values: for i in f.items(): if i[1] in (BREAK, BREAK_LOOP): self._append_path(i[0]) break # 2nd easiest path is to take a single CSN1FIELDS if exist elif True in [isinstance(i, CSN1FIELDS) for i in values]: for i in f.items(): # "au petit bonheur la chance" if isinstance(i[1], CSN1FIELDS): self._append_path(i[0]) self._append_csn1_field(i[1]) break # 3rd easiest path is to take the shortest tuple available elif True in [isinstance(i, tuple) for i in values]: min_tuple = min(map(len, values)) for i in f.items(): if len(i[1]) == min_tuple: # "au petit bonheur la chance" again self._append_path(i[0]) self._build_auto(i[1]) break # 4th easiest path is to enter the shortest dict available elif True in [isinstance(i, dict) for i in values]: min_dict = min(map(len, values)) for i in f.items(): if len(i[1]) == min_dict: # "au petit bonheur la chance" once again self._append_path(i[0]) self._build_auto(i[1]) break
def _select_tag_old_(self, s='\0', taglist=[]): # check for 4 bits and 8 bits tags t4, t8 = ord(s[0]) >> 4, ord(s[0]) if self.dbg >= DBG: log(DBG, '(Layer3 - %s) t4, t8: %s, %s\ntaglist: %s' \ % (self.CallName, t4, t8, taglist)) # # handle 4 bits tag in priority # guessing from 3GPP spec: # 4 bits and 8 bits tags should never clash... # actually, this is not always the case # e.g. for tag 4 (BearerCap) and 64 (SuppCodecs) # TODO: this is actually a real issue, # e.g. with GSM RR ASSIGNMENT_COMMAND if t4 in taglist: return t4 elif t8 in taglist: return t8 return None
def __get_opts(self): found_opt, opt_ie = False, [] # we have at least 3 mandatory fields (L3 header: SI, PD, Type) # and we should not have any IE left, except StrRR for GSM RR if isinstance(self[-1], StrRR): ie_to_check = self[3:-1] else: ie_to_check = self[3:] # for ie in ie_to_check: if hasattr(ie, 'T') or isinstance(ie, (Type2, StrRR)): found_opt = True opt_ie.append(ie) # make all optional fields not transparent (so all of them # can be mapped, whatever Layer3.__init__() options given) ie.Trans = False elif found_opt and self.dbg >= ERR: # we should not find mandatory IE after optional log(ERR, '(Layer3 - %s) mandatory IE found after ' \ 'optional ones' % self.CallName) return opt_ie
def _select_tag_old_(self, s='\0', taglist=[]): # check for 4 bits and 8 bits tags t4, t8 = ord(s[0])>>4, ord(s[0]) if self.dbg >= DBG: log(DBG, '(Layer3 - %s) t4, t8: %s, %s\ntaglist: %s' \ % (self.CallName, t4, t8, taglist)) # # handle 4 bits tag in priority # guessing from 3GPP spec: # 4 bits and 8 bits tags should never clash... # actually, this is not always the case # e.g. for tag 4 (BearerCap) and 64 (SuppCodecs) # TODO: this is actually a real issue, # e.g. with GSM RR ASSIGNMENT_COMMAND if t4 in taglist: return t4 elif t8 in taglist: return t8 return None
def map(self, string='', byte_offset=0): # pop each member of the initial csn1List # and see what we have # 1) CSN1FIELD -> map it directly # 2) dict -> conditions -> CSN1FIELD | dict | tuple # 3) tuple -> CSN1FIELD | dict # consume the string buffer bit per bit depending of the # csn1List member poped # # as a CSN1 field does not always start on a byte limit, # we need to keep track of the byte offset, # for solving padding (L | H) adequately # # in case CSN1 list is empty if len(self.csn1List) == 0: return # initialize the string buffer to be mapped self.BUF, self._buflen = shtr(string), len(string)*8 self._consumed, self._offset, self._map_exit = 0, byte_offset, False self.elementList = [] # # MAIN loop # go over the string, bit per bit index = 0 while (self._consumed + self._offset) < self._buflen: if self._map_exit: break # get csn1 fields 1 by 1 self._eval_csn1(self.csn1List[index]) index += 1 # when csn1List is empty, finish with padding if index >= len(self.csn1List): break # # TODO: the padding should only be done when instructed so in CSN1 remaining = self._buflen - (self._consumed + self._offset) if remaining: if self.dbg >= DBG: log(DBG, '(CSN1._eval_csn1_dict) bits are remaining unmapped') # clean temporary data del self.BUF, self._buflen, self._consumed, \ self._offset, self._map_exit
def _WtoF(self, index, fmt): # J := GREATEST_POWER_OF_2_LESSER_OR_EQUAL_TO(INDEX); J = [j for j in [1, 2, 4, 8, 16, 32, 64, 128] if j <= index].pop() N = getattr(self, "W_%i" % index)() # while index > 1: if (2 * index) < (3 * J): index = index - (J / 2) # for range 1024, we have to take W(PARENT) par = self._get_W_parent(index, fmt) N = ((N + par + (fmt / J) - 2) % (((fmt * 2) / J) - 1)) + 1 else: index = index - J # for range 1024, we have to take W(PARENT) par = self._get_W_parent(index, fmt) N = (N + par + ((fmt * 2) / J) - 2) % (((fmt * 2) / J) - 1) + 1 J = J / 2 if self.dbg >= DBG: log(DBG, "L3GSM_IE ARFCN WtoF - N: %i" % N) return N
def _append_from_dict(self, from_dict, condlen): # xtra verbose debugging if self.dbg > DBG: if type(from_dict) is tuple: obj = list(from_dict) elif hasattr(from_dict, 'CallName'): obj = from_dict.CallName else: obj = from_dict log(DBG, '(CSN1._eval_csn1_dict) selected from_dict: %s' % obj) # if condition lead to None: padding if from_dict == BREAK: self._append_map_csn1_field(Bit('%s' % self.pad_name, BitLen=condlen)) # if condition lead to breaking a loop # for crappy CSN1 syntax like {1 expr}**0 elif from_dict == BREAK_LOOP: self._append_map_csn1_field(Bit('%s' % self.pad_name, BitLen=condlen)) self._loop = False # if condition lead to something: else: self._append_map_csn1_field(Bit('%s' % self.cond_name, BitLen=condlen)) # recurse to the main csn1 eval method applied to object from dict self._eval_csn1(from_dict)
def _append_csn1_field(self, field): if isinstance(field, LHFlag): #print self._offset if self.L[self._offset%8] == 1: field.LHdict = iad({1:'L', 0:'H'}) if isinstance(field, CSN1): # initialize the field without building it any specific way f = field.__class__(False, None) # and then call build() in order to pass the _offset attribute path = self._cur_path if hasattr(self, '_cur_path') else None f.build(f.csn1List, self._offset, path) # if self.dbg >= DBG: log(DBG, '(_append_csn1_field) field %s with path %s'\ % (f.CallName, path)) self.append(f) self._offset += f.bit_len() # else: self.append(field) self._offset += field.bit_len()
def get_deciphered(self, key=16*'\0', dir=0): # this is to get the deciphered stream of a NAS EMM message # # no NAS security if self.SH() == 0 and not hasattr(self, 'MAC'): return str(self) # # NAS security, only integrity protection elif self.SH() in (1, 3) and hasattr(self, 'MAC'): return str(self[4:]) # # NAS security, ciphered elif self.SH() in (2, 4) and hasattr(self, 'MAC'): if self.EEA is None: # null ciphering EEA0 return str(self[4:]) elif self[-1].CallName == '_enc': return self.EEA(key, self.SN(), 0, dir, str(self[-1])) # log(ERR, 'Layer3NAS - get_deciphered: could not retrieve NAS payload') return ''
def _get_ccch(self, string=''): # this is to decode reliably GSM broadcast # if len(string) < 3: self << RawLayer() self[-1].map(string) return # l = LengthRR() l.map(string[0:1]) h = Header() h.map(string[1:3]) # should check length (l) coherence here (including with M bit) # ... # check for non-RR protocol, or truncated string #print('L1CTL decoding ; h.PD %s, string length %s' % (h.PD(), len(string))) if h.PD() != 6 or len(string) < 22: self << l self | RawL3() self[-1].map(string[1:]) return # # if we have the complete RR decoder if h.Type() in L3Call[6].keys(): rr = L3Call[6][h.Type()]() try: rr.map(string) self << rr return except: pass # # otherwise, it's the generic RR decoder # what is not very smart... however ! if self.dbg >= DBG: log(DBG, '(L1CTL - L3GSM_RR) message parsing failed with:\n%s' \ % hexlify(string)) self << RR_gene() self[-1].map(string)
def __map_opt(self, tag, string, opt_ie): # retrieve 1st optional IE for the given tag: in opt opt = None for ie in opt_ie: if ie[0]() == tag: opt = ie break # remove it from the opt_ie list that will be further processed # python's "remove()" remove the 1st iteration of an element in a list if not opt: log(ERR, '(Layer3 - %s) no remaining optional field for tag %i' \ % (self.CallName, tag)) return '', opt_ie opt_ie.remove(opt) # force it to be not transparent, map it and truncate the string opt.Trans = False opt.map(string) if self.dbg >= DBG: log(DBG, '(Layer3 - %s) mapping %s on %s' \ % (self.__class__, hexlify(string), opt.CallName)) string = string[opt.map_len():] return string, opt_ie
def _decode_range(self, fmt): # go over all W fields and apply the computation algorithm # as of 10.5.2.3.13.3 # some discrepancies exist between the 4 ranges: # 1) computing the next F # 2) initial and number of F if fmt == 1024: if self.F0(): ar_list = [0] else: ar_list = [] ind_max = 17 app = lambda N: ar_list.append(N) elif fmt in (128, 256, 512): ar_list = [self.orig_arfcn()] if fmt == 128: ind_max = 29 elif fmt == 256: ind_max = 22 elif fmt == 512: ind_max = 18 app = lambda N: ar_list.append((ar_list[0] + N) % 1024) # # loop over all non-null W values to compute corresponding F for ind in range(1, ind_max): if getattr(self, "W_%i" % ind)(): app(self._WtoF(ind, fmt)) # if self.dbg >= DBG: log( DBG, "L3GSM_IE ARFCN WtoF - index %i; W %i; F %i" % (ind, getattr(self, "W_%i" % ind)(), ar_list[-1]), ) else: break # return sorted(ar_list)
def _build_from_dict(self, d): # TODO: if BREAK_LOOP in the dict, # we should iterate as much time as provided in the path list # conds = d.keys() #vals = d.values() # # select the value corresponding to the condition in _cur_path if self.dbg >= DBG: log(DBG, '(_build_path) _cur_path to process: %s' % self._cur_path) cond_from_path = self._cur_path.pop(0) if cond_from_path not in conds: raise(Exception('path %s does not correspond to any condition'\ ' for building %s' % (cond_from_path, self.__class__))) # self._append_path(cond_from_path) field = d[cond_from_path] if field not in (BREAK, BREAK_LOOP): if isinstance(field, CSN1FIELDS): #print('<<<<<<<<<<<<<<<<<<<<<< %s' % field.CallName) self._append_csn1_field(field) else: # for tuple or dict, go the recursive way self._build_path(field)
def parse(self, s='', phy_included=True, fcs_included=True): # if self.PHY_INCL: # insert PHY() preamble and map the buffer to it if len(self.layerList) > 0 : self.layerList = [] self << PHY() self[0].map(s) # # verify preamble if self[0].Preamble() != '\0\0\0\0' and self.dbg >= ERR: log(ERR, 'bad 802.15.4 preamble in PHY') if len(s) != self.PHY.Length()+6 and self.dbg >= ERR: log(ERR, 'buffer longer than indicated in PHY header') # truncate string buffer s=s[6:6+self.PHY.Length()] else: if len(self.layerList) > 0: self.layerList = [] # # standard 802.15.4 MAC header # insert MAC and map the buffer to it self << MAC() self[-1].map(s) s = s[len(self[-1]):] # insert DATA layer if self.FCS_INCL: if len(s) > 2: self << Data() self[-1].map(s[:-2]) if len(s) >= 2: self >> FCS() self[-1].map(s[-2:]) # verify CRC crc = self.FCS.FCS() self.FCS.FCS < None if self.FCS.FCS() != crc and self.dbg >= ERR: log(ERR, 'bad 802.15.4 CRC16 in MAC suffix') # refill with the original value self.FCS.FCS < crc elif self.dbg >= ERR: log(ERR, 'buffer not long enough for FCS') elif len(s) > 0: self << Data() self[-1].map(s)
def _append_map_csn1_field(self, csn1f): # ensure there is still enough buffer to map on new fields if hasattr(csn1f, 'bit_len') \ and csn1f.bit_len() > (self._buflen - (self._consumed+self._offset)): if self.dbg >= WNG: log(WNG, '(CSN1.map - %s) buffer not long enough for field: ' \ '%s from csn1List' % (self.__class__, csn1f.CallName)) self._map_exit = True return # append the field to the layer if self.dbg >= DBG: log(DBG, '(CSN1._append_map_csn1_field) appending csn1 field: %s' \ % csn1f.CallName) # # map the BUF on the element: this solves its bit length # moreover, we transmit the byte offset, for solving correctly L|H flags if type(csn1f) == type and issubclass(csn1f, CSN1): self.append(csn1f().clone()) else: self.append(csn1f) # if isinstance(self[-1], CSN1): self[-1].map(self.BUF, (self._consumed+self._offset)%8) bitlen = self[-1].bit_len() else: self[-1].map(self.BUF) bitlen = self[-1].bit_len() # check if LHFlag to possibly update its LH dictionnary if isinstance(self[-1], LHFlag): l = self.L[(self._consumed+self._offset)%8] h = (1, 0)[l] self[-1].LHdict = iad({l:'L', h:'H'}) # # update global BUFFER and consumed bit length self.BUF = self.BUF << bitlen self._consumed += bitlen
def __map_opts(self, string=''): # retrieve all optional IE from the Layer3 into opt_ie list opt_ie = self.__get_opts() taglist = [ie[0]() for ie in opt_ie if not isinstance(ie, StrRR)] if self.dbg >= DBG: log(DBG, '(Layer3 - %s) opt_ie: %s' % (self.CallName, opt_ie)) # go over the string and map to optional IE found while len(string) > 0: # check each iteration for the right tag t = self._select_tag(string[0], taglist) if t: string, opt_ie = self.__map_opt(t, string, opt_ie) taglist.remove(t) # else: if self.dbg >= ERR: log(WNG, '(Layer3 - %s) unknown optional IE' \ % self.CallName) # could try to map it as a TLV, or TLVextended, or TV, or T... # check the TS 24.007, section 11.2.4, for being amazed... string2 = self.__map_unknown_opt(string) if string2 == string: break string = string2 # # optional IE that have not been mapped have to go "transparent" if len(opt_ie) > 0: [setattr(ie, 'Trans', True) for ie in opt_ie] if self.dbg >= DBG: log(DBG, '(Layer3 - %s) not all optional IE used\nremaining:' \ ' %s' % (self.CallName, opt_ie)) # this should only happen when __map_unknown_opt() cannot # consume remaining string. if len(string) > 0 and self.dbg >= ERR: log(ERR, '(Layer3 - %s) string not completely mapped\nremaining: ' \ '%s' % (self.CallName, string))
def map(self, string=''): # Layer3 have optional fields that need special processing # they all have a tag (attribute .T) opt_fields = (Type1_TV, Type2, Type3_TV, Type4_TLV, Type6_TLVE) GSM_RR = False # handle special string truncature for L3GSM_RR that is on CCCH # cause opts may not be there, but we still have rest octets to map if self.CallName in RR_in_CCCH: if self.safe: assert (isinstance(self[0], Bit) and self[0].CallName == 'len') GSM_RR, l2_len = True, self._len_gsmrr(string) string, rest = string[:l2_len + 1], string[l2_len + 1:] if self.dbg >= DBG: log(DBG, '(Layer3 GSM_RR - %s)\nl2len: %i\nstring: %s\nrest: %s' \ % (self.CallName, l2_len, hexlify(string), hexlify(rest))) # Otherwise we mimic standard Layer().map() behaviour self._Layer__BitStack = [] self._Layer__BitStack_len = 0 # for e in self: # special processing for Bit() element: if isinstance(e, Bit): self._Layer__add_to_bitstack(e) # if BitStack is byte aligned, map string to it: if self._Layer__BitStack_len % 8 == 0: if self.dbg >= DBG: log(DBG, '(Layer3 - %s) mapping %s to bitstack %s' \ % (self.__class__, hexlify(string), \ self._Layer__BitStack)) string = self._Layer__map_to_bitstack(string) else: if self._Layer__BitStack_len > 0 and self.dbg >= ERR: log(ERR, '(Layer3 - %s) some of the Bit elements have not ' \ 'been mapped in the "Layer": not byte-aligned' \ % self.CallName) ### special Layer3 processing ### # need to manage smartly optional / conditionnal fields # Tagged IE always come after mandatory IE # so we handle it in another sub method and break the parsing if isinstance(e, opt_fields): # for standard L3 messages self.__map_opts(string) break ### # and for mandatory IE (not tagged) if isinstance(e, (Layer, Element)) and not e.is_transparent(): if self.dbg >= DBG: log(DBG, '(Layer3 - %s) mapping %s on %s' \ % (self.__class__, hexlify(string), e.CallName)) e.map(string) string = string[e.map_len():] # for GSM RR: map rest octets, that comes after tagged IE if GSM_RR and rest: if isinstance(self[-1], StrRR): self[-1].Trans = False else: self.append(StrRR('RestOctets', Repr='hex')) self[-1].map(rest) # delete .map() *internal* attributes del self._Layer__BitStack del self._Layer__BitStack_len # ### special Layer3 processing ### if not self._interpret_IE: return # # Go again through all L3 fields that are not transparent # (tested with their length, WNG: do not work for LV(), however, # LV field should always be there...) # and check if L3Mobile_IE is available for even more interpretation for e in [f for f in self if len(f)]: cn = e.CallName if self.dbg >= DBG: log(DBG, 'L3Mobile_24007 - map: checking for IE %s ' \ 'interpretation' % cn) # truncate possible digit addition at the end of the CallName # 11/06/2012: this ugly hack is not supported anymore... # 13/06/2012: this ugly hack is back ! # with an '_' in front of the digit digit = search('_[1-9]{1,}$', e.CallName) if digit: cn = cn[:digit.start()] # check for potential IE interpretation #if hasattr(L3Mobile_IE, cn) or hasattr(L3GSM_IE, cn): if cn in IE_list: self.interpret_IE(e, cn)
def map(self, string=''): # Layer3 have optional fields that need special processing # they all have a tag (attribute .T) opt_fields = (Type1_TV, Type2, Type3_TV, Type4_TLV, Type6_TLVE) GSM_RR = False # handle special string truncature for L3GSM_RR that is on CCCH # cause opts may not be there, but we still have rest octets to map if self.CallName in RR_in_CCCH: if self.safe: assert(isinstance(self[0], Bit) and self[0].CallName=='len') GSM_RR, l2_len = True, self._len_gsmrr(string) string, rest = string[:l2_len+1], string[l2_len+1:] if self.dbg >= DBG: log(DBG, '(Layer3 GSM_RR - %s)\nl2len: %i\nstring: %s\nrest: %s' \ % (self.CallName, l2_len, hexlify(string), hexlify(rest))) # Otherwise we mimic standard Layer().map() behaviour self._Layer__BitStack = [] self._Layer__BitStack_len = 0 # for e in self: # special processing for Bit() element: if isinstance(e, Bit): self._Layer__add_to_bitstack(e) # if BitStack is byte aligned, map string to it: if self._Layer__BitStack_len % 8 == 0: if self.dbg >= DBG: log(DBG, '(Layer3 - %s) mapping %s to bitstack %s' \ % (self.__class__, hexlify(string), \ self._Layer__BitStack)) string = self._Layer__map_to_bitstack(string) else: if self._Layer__BitStack_len > 0 and self.dbg >= ERR: log(ERR, '(Layer3 - %s) some of the Bit elements have not ' \ 'been mapped in the "Layer": not byte-aligned' \ % self.CallName) ### special Layer3 processing ### # need to manage smartly optional / conditionnal fields # Tagged IE always come after mandatory IE # so we handle it in another sub method and break the parsing if isinstance(e, opt_fields): # for standard L3 messages self.__map_opts(string) break ### # and for mandatory IE (not tagged) if isinstance(e, (Layer, Element)) and not e.is_transparent(): if self.dbg >= DBG: log(DBG, '(Layer3 - %s) mapping %s on %s' \ % (self.__class__, hexlify(string), e.CallName)) e.map(string) string = string[e.map_len():] # for GSM RR: map rest octets, that comes after tagged IE if GSM_RR and rest: if isinstance(self[-1], StrRR): self[-1].Trans = False else: self.append(StrRR('RestOctets', Repr='hex')) self[-1].map(rest) # delete .map() *internal* attributes del self._Layer__BitStack del self._Layer__BitStack_len # ### special Layer3 processing ### if not self._interpret_IE: return # # Go again through all L3 fields that are not transparent # (tested with their length, WNG: do not work for LV(), however, # LV field should always be there...) # and check if L3Mobile_IE is available for even more interpretation for e in [f for f in self if len(f)]: cn = e.CallName if self.dbg >= DBG: log(DBG, 'L3Mobile_24007 - map: checking for IE %s ' \ 'interpretation' % cn) # truncate possible digit addition at the end of the CallName # 11/06/2012: this ugly hack is not supported anymore... # 13/06/2012: this ugly hack is back ! # with an '_' in front of the digit digit = search('_[1-9]{1,}$', e.CallName) if digit: cn = cn[:digit.start()] # check for potential IE interpretation #if hasattr(L3Mobile_IE, cn) or hasattr(L3GSM_IE, cn): if cn in IE_list: self.interpret_IE(e, cn)
def parse_L3(buf, L2_length_incl=0): ''' This is a global parser for mobile layer 3 signalling. It works fine as is with MM, CC, GMM and SM protocols. For GSM RR signalling, the length of the L2 pseudo-length header (1 byte) needs to be passed as parameter "L2_length_incl" to retrieve correctly the protocol discriminator and message type. E.g. for messages passed over GSM BCCH or CCCH: L2_length_incl=1 parse_L3(string_buffer, L2_length_incl=0) -> Layer3 instance ''' # select message from PD and Type if len(buf) < 2: log(ERR, '(parse_L3) message too short for L3 mobile') return RawLayer(buf) # ### # Protocol Discriminator ### # protocol discriminator is 4 last bits (LSB) of 1st byte PD = ord(buf[L2_length_incl]) & 0x0F # ### # Message Type ### # for MM, CC, SS and GSM RR, only 6 1st bits for the message type if PD in (3, 5, 6, 11): Type = ord(buf[L2_length_incl+1]) & 0x3F # # for LTE NAS messages: message content (including Type) can be ciphered # (the security processing is available in the Layer3NAS class) # EMM header has a security header elif PD == 7: # check Security Header SH = ord(buf[L2_length_incl]) >> 4 # no security: if SH == 0: Type = ord(buf[1]) # integrity protection only: elif SH in (1, 3): if len(buf) < 8: Type = None Type = ord(buf[7]) # ciphering, hence not possible to know the payload Type elif SH in (2, 4): Type = None elif SH == 12 and len(buf) >= 4: # LTE NAS service request l3 = SERVICE_REQUEST() l3.map(buf) return l3 else: log(WNG, '(parse_L3) unknown NAS EMM security header: %i' % SH) Type = None # # ESM header has bearer ID in the 4 MSB of 1st byte # and Transaction ID between PD and Type elif PD == 2: if len(buf) < 3: Type = None Type = ord(buf[2]) # # for other 2G and 3G core protocols (GMM, SM, SMS, ...), nothing special else: Type = ord(buf[L2_length_incl+1]) # if PD not in L3Call: if PD not in PD_dict: log(ERR, '(parse_L3) unknown L3 protocol discriminator: %i' % PD) else: log(WNG, '(parse_L3) L3 protocol %s not implemented' % PD_dict[PD]) l3 = RawL3() l3.map(buf) # # get the right type from Type elif Type not in L3Call[PD] and Type is not None: log(ERR, '(parse_L3) L3 message type %i undefined for protocol %s' \ % (Type, PD_dict[PD])) l3 = RawL3() l3.map(buf) # for L3GSM_RR, still use the msg type dict: # because GSM RR are not all implemented if PD == 6: l3.Type.Dict = GSM_RR_dict # # select the correct L3 signalling message else: # for LTE NAS, if ciphered #log(DBG, '(parse_L3) PD %i, Type %i' % (PD, Type)) if Type is None: l3 = Layer3NAS(with_security=True) else: l3 = L3Call[PD][Type]() # try: #log(DBG, '(parse_L3) mapping ?') l3.map(buf) #log(DBG, '(parse_L3) mapped correctly') except: log(ERR, '(parse_L3) mapping buffer on L3 message failed') l3 = RawL3() l3.map(buf) # return l3
def test_regr(print_infos=True): ''' L3GSM_RR and L3Mobile_* regression testing: checking all signalling messages from the L3Call dictionnary ''' # libmich settings e_safe = Element.safe l_safe = Layer.safe Element.safe = True Layer.safe = True Element.dbg = ERR Layer.dbg = ERR # glob_errors = 0 # def test_dict(): e = 0 for pd in L3Call: for msg in L3Call[pd]: error = '' cl = L3Call[pd][msg] if not issubclass(cl, Layer3): log(WNG, 'message %s not subclass of Layer3' % repr(cl)) else: try: m = cl(with_options=True) except: error = '__init__(with_options=True)' else: try: buf = str(m) except: error = '__str__()' else: try: m2 = cl(with_options=False) m2.parse(buf) except: error = 'parse()' else: if str(m2) != buf: error = '__str__() result discrepancy ' + \ ' with parse()d buffer' if error: log(ERR, 'message %s test returns error: %s' \ % (repr(cl), error)) e += 1 return e # Layer3._initiator = 'Net' if print_infos: log(DBG, 'testing with Net initiator') glob_errors += test_dict() Layer3._initiator = 'ME' if print_infos: log(DBG, 'testing with ME initiator') glob_errors += test_dict() # Element.safe = e_safe Layer.safe = l_safe # if not glob_errors and print_infos: log(DBG, '[Heeeeha!!!] all L3Mobile tests passed successfully') return glob_errors
def parse_L3(buf, L2_length_incl=0): ''' This is a global parser for mobile layer 3 signalling. It works fine as is with MM, CC, GMM and SM protocols. For GSM RR signalling, the length of the L2 pseudo-length header (1 byte) needs to be passed as parameter "L2_length_incl" to retrieve correctly the protocol discriminator and message type. E.g. for messages passed over GSM BCCH or CCCH: L2_length_incl=1 parse_L3(string_buffer, L2_length_incl=0) -> Layer3 instance ''' # select message from PD and Type if len(buf) < 2: log(ERR, '(parse_L3) message too short for L3 mobile') return RawLayer(buf) # ### # Protocol Discriminator ### # protocol discriminator is 4 last bits (LSB) of 1st byte PD = ord(buf[L2_length_incl]) & 0x0F # ### # Message Type ### # for MM, CC, SS and GSM RR, only 6 1st bits for the message type if PD in (3, 5, 6, 11): Type = ord(buf[L2_length_incl + 1]) & 0x3F # # for LTE NAS messages: message content (including Type) can be ciphered # (the security processing is available in the Layer3NAS class) # EMM header has a security header elif PD == 7: # check Security Header SH = ord(buf[L2_length_incl]) >> 4 # no security: if SH == 0: Type = ord(buf[1]) # integrity protection only: elif SH in (1, 3): if len(buf) < 8: Type = None Type = ord(buf[7]) # ciphering, hence not possible to know the payload Type elif SH in (2, 4): Type = None elif SH == 12 and len(buf) >= 4: # LTE NAS service request l3 = SERVICE_REQUEST() l3.map(buf) return l3 else: log(WNG, '(parse_L3) unknown NAS EMM security header: %i' % SH) Type = None # # ESM header has bearer ID in the 4 MSB of 1st byte # and Transaction ID between PD and Type elif PD == 2: if len(buf) < 3: Type = None Type = ord(buf[2]) # # for other 2G and 3G core protocols (GMM, SM, SMS, ...), nothing special else: Type = ord(buf[L2_length_incl + 1]) # if PD not in L3Call: if PD not in PD_dict: log(ERR, '(parse_L3) unknown L3 protocol discriminator: %i' % PD) else: log(WNG, '(parse_L3) L3 protocol %s not implemented' % PD_dict[PD]) l3 = RawL3() l3.map(buf) # # get the right type from Type elif Type not in L3Call[PD] and Type is not None: log(ERR, '(parse_L3) L3 message type %i undefined for protocol %s' \ % (Type, PD_dict[PD])) l3 = RawL3() l3.map(buf) # for L3GSM_RR, still use the msg type dict: # because GSM RR are not all implemented if PD == 6: l3.Type.Dict = GSM_RR_dict # # select the correct L3 signalling message else: # for LTE NAS, if ciphered #log(DBG, '(parse_L3) PD %i, Type %i' % (PD, Type)) if Type is None: l3 = Layer3NAS(with_security=True) else: l3 = L3Call[PD][Type]() # try: #log(DBG, '(parse_L3) mapping ?') l3.map(buf) #log(DBG, '(parse_L3) mapped correctly') except: log(ERR, '(parse_L3) mapping buffer on L3 message failed') l3 = RawL3() l3.map(buf) # return l3
def test_regr(print_infos=True): ''' L3GSM_RR and L3Mobile_* regression testing: checking all signalling messages from the L3Call dictionnary ''' # libmich settings e_safe = Element.safe l_safe = Layer.safe Element.safe = True Layer.safe = True Element.dbg = ERR Layer.dbg = ERR # glob_errors = 0 # def test_dict(): e = 0 for pd in L3Call: for msg in L3Call[pd]: error = '' cl = L3Call[pd][msg] if not issubclass(cl, Layer3): log(WNG, 'message %s not subclass of Layer3' % repr(cl)) else: try: m = cl(with_options=True) except: error = '__init__(with_options=True)' else: try: buf = str(m) except: error = '__str__()' else: try: m2 = cl(with_options=False) m2.parse(buf) except: error = 'parse()' else: if str(m2) != buf: error = '__str__() result discrepancy ' + \ ' with parse()d buffer' if error: log(ERR, 'message %s test returns error: %s' \ % (repr(cl), error)) e += 1 return e # Layer3._initiator = 'Net' if print_infos: log(DBG, 'testing with Net initiator') glob_errors += test_dict() Layer3._initiator = 'ME' if print_infos: log(DBG, 'testing with ME initiator') glob_errors += test_dict() # Element.safe = e_safe Layer.safe = l_safe # if not glob_errors and print_infos: log(DBG, '[Heeeeha!!!] all L3Mobile tests passed successfully') return glob_errors # #
def unprotect(self, key_int=16*'\0', key_enc=16*'\0', dir=0): ret = self.verify_mac(key_int, dir) if not ret: log(WNG, 'Layer3NAS - unprotect: MAC verificaion failed') self.decipher(key_enc, dir)