Skip to content

Mobile nas messages

mitshell edited this page Feb 27, 2024 · 1 revision

How to encode and decode mobile NAS messages

modules for handling NAS messages

All the NAS-related messages are available in the pycrate_mobile directory.

The following modules provide structures for dealing with 2G and 3G NAS messages:

All those structures are corresponding mostly to the descriptions from sections 9 of the 3GPP TS 24.008 and TS 44.018 specifications. They are themselves using IEs (Information Elements) defined in section 10 of both specifications, and available in the modules TS24008_IE and TS44018_IE. Moreover, some GSM and GPRS IEs are using CSN.1, which are defined in the pycrate_csn1dir directory.

For SMS and Supplementary Services, other modules are available:

For LTE NAS, two main modules are available:

Those structures are corresponding to the descriptions from section 8 of the 3GPP TS 24.301 specification. IEs defined in section 9 are available in the module TS24301_IE. Moreover, in case the CryptoMobile library is installed and can be imported, the EMMSecProtNASMessage and EMMServiceRequest classes have methods dedicated to the LTE NAS cryptographic operations.

For 5G NAS, three main modules are available:

Those structures are corresponding to the descriptions from section 8 and annex D of the 3GPP TS 24.501 specification. IEs defined in section 9 are available in the module TS24501_IE. Moreover, in case the CryptoMobile library is installed and can be imported, the FGMMSecProtNASMessage class has methods dedicated to the 5G NAS cryptographic operations.

All classes representing NAS messages and information elements are deriving from few base classes defined in the TS24007 module, which implements some specific aspects of cellular layer 3 message structures as defined in the TS 24.007 3GPP specification.

Finally, three top-level modules are enabling the import of most of those NAS messages' structures and providing custom functions for decoding any cellular NAS messages:

  • NASLTE: for LTE-only EMM and ESM NAS messages,
  • NAS5G: for 5G-only 5GMM, 5GSM and 5G UE Policy NAS messages,
  • NAS: for all 2G-3G, LTE and 5G NAS messages.

Decoding NAS messages

In order to decode NAS messages, the simplest way is to use one of the two functions used in the NAS module:

  • parse_NAS_MO(buf) -> (element, err), for parsing Mobile Originating message (i.e. uplink)
  • parse_NAS_MT(buf) -> (element, err), for parsing Mobile Terminating message (i.e. downlink)

In case there is an error during the decoding of the buffer, this will be returned in the err code, with a code corresponding to standard 3GPP NAS error code (e.g. which can be used in a Status NAS message in return). The element contains an Element's instance (or None, if the whole decoding failed), with the whole message header and information elements.

Let's see some examples from the test directory:

>>> from pycrate_mobile.NAS import *
>>> Msg, err = parse_NAS_MO(unhexlify('05080200f11040005705f44c6a94c033035758a6'))
>>> err
0
>>> show(Msg)
### MMLocationUpdatingRequest ###
 <SkipInd : 0>
 <ProtDisc : 5 (MM)>
 <Seqn : 0>
 <Type : 8 (Registration - LOCATION UPDATING REQUEST)>
 <CKSN : 0>
 ### LocUpdateType ###
  <FollowOnReq : 0>
  <spare : 0>
  <Type : 2 (IMSI attach)>
 ### LAI ###
  <PLMN : 00101>
  <LAC : 0x4000>
 ### MSCm1 ###
  <spare : 0>
  <RevLevel : 2 (MS supporting R99 or later)>
  <EarlyCmCap : 1>
  <NoA51 : 0>
  <RFClass : 7>
 ### ID ###
  <L : 5>
  ### ID ###
   <Digit1 : 0xf>
   <Odd : 0>
   <Type : 4 (TMSI)>
   <TMSI : 0x4c6a94c0>
 ### MSCm2 ###
  <T : 51>
  <L : 3>
  ### MSCm2 ###
   <spare : 0>
   <RevLevel : 2 (MS supporting R99 or later)>
   <EarlyCmCap : 1>
   <NoA51 : 0>
   <RFClass : 7>
   <spare : 0>
   <PSCap : 1>
   <SSScreeningCap : 1 (capability of handling of ellipsis notation and phase 2 error handling)>
   <MTSMSCap : 1>
   <VBSNotifCap : 0>
   <VGCSNotifCap : 0>
   <FCFreqCap : 0>
   <MSCm3Cap : 1>
   <spare : 0>
   <LCSVACap : 1>
   <UCS2 : 0>
   <SoLSACap : 0>
   <CMServPrompt : 1>
   <A53 : 1>
   <A52 : 0>
>>> Msg['LAI'].get_val()
['\x00\xf1\x10', 16384]
>>> Msg['LAI'].decode() # some IEs have a specific .decode() method, like the LAI / PLMN, or the ID here
('00101', 16384)
>>> Msg['ID'][1]
<ID [TMSI] : 0x4c6a94c0>
>>> Msg['ID'][1].get_val()
[15, 0, 4, 1282053312]
>>> Msg['ID'][1].decode()
(4, 1282053312)
>>>
>>> Msg, err = parse_NAS_MO(unhexlify('034504066004020005815e068160000000001502010040080402600400021f00'))
>>> err
0
>>> show(Msg)
### CCSetupMO ###
 <TIFlag : 0 (initiator)>
 <TIO : 0>
 <ProtDisc : 3 (CC)>
 <Seqn : 1>
 <Type : 5 (Call establishment - SETUP)>
 ### BearerCap1 ###
  <T : 4>
  <L : 6>
  ### BearerCap ###
   <Ext : 0>
   <RadioChanReq : 3 (dual rate support MS/full rate preferred)>
   <CodingStd : 0 (GSM standardized coding)>
   <TransferMode : 0 (circuit)>
   <InfoTransferCap : 0 (speech)>
   ### Ext3a ###
    <Ext : 0>
    <Coding : 0 (octet used for extension of information transfer capability)>
    <CTM : 0 (CTM text telephony is not supported)>
    <spare : 0>
    <SpeechVersionInd : 4 (GSM FR v3 (FR AMR))>
   ### Ext3b ###
    ### _BearerCapExt3bRec ###
     <Ext : 0>
     <Coding : 0 (octet used for extension of information transfer capability)>
     <spare : 0>
     <SpeechVersionInd : 2 (GSM FR v2 (GSM EFR))>
    ### _BearerCapExt3bRec ###
     <Ext : 0>
     <Coding : 0 (octet used for extension of information transfer capability)>
     <spare : 0>
     <SpeechVersionInd : 0 (GSM FR v1 (GSM FR))>
    ### _BearerCapExt3bRec ###
     <Ext : 0>
     <Coding : 0 (octet used for extension of information transfer capability)>
     <spare : 0>
     <SpeechVersionInd : 5 (GSM HR v3 (HR AMR))>
    ### _BearerCapExt3bRec ###
     <Ext : 1>
     <Coding : 0 (octet used for extension of information transfer capability)>
     <spare : 0>
     <SpeechVersionInd : 1 (GSM HR v1 (GSM HR))>
 ### CalledPartyBCDNumber ###
  <T : 94>
  <L : 6>
  ### CalledPartyBCDNumber ###
   <Ext : 1>
   <Type : 0 (unknown)>
   <NumberingPlan : 1 (ISDN / telephony numbering plan (E.164 / E.163))>
   <Num : 0600000000>
 ### CCCap ###
  <T : 21>
  <L : 2>
  ### CCCap ###
   <MaxNumSupportedBearers : 0>
   <MultimediaCAT : 0>
   <ENICM : 0>
   <PCP : 0>
   <DTMF : 1>
   <spare : 0>
   <MaxNumSpeechBearers : 0>
 ### SupportedCodecs ###
  <T : 64>
  <L : 8>
  ### SupportedCodecs ###
   ### CodecSysID ###
    <SysID : 4 (UMTS)>
    <BMLen : 2>
    ### CodecBM ###
     <TDMA_EFR : 0>
     <UMTS_AMR2 : 1>
     <UMTS_AMR : 1>
     <HR_AMR : 0>
     <FR_AMR : 0>
     <GSM_EFR : 0>
     <GSM_HR : 0>
     <GSM_FR : 0>
     <reserved : 0>
     <reserved : 0>
     <OHR_AMR-WB : 0>
     <OFR_AMR-WB : 0>
     <OHR_AMR : 0>
     <UMTS_AMR-WB : 1>
     <FR_AMR-WB : 0>
     <PDC_EFR : 0>
     <spare : ''>
   ### CodecSysID ###
    <SysID : 0 (GSM)>
    <BMLen : 2>
    ### CodecBM ###
     <TDMA_EFR : 0>
     <UMTS_AMR2 : 0>
     <UMTS_AMR : 0>
     <HR_AMR : 1>
     <FR_AMR : 1>
     <GSM_EFR : 1>
     <GSM_HR : 1>
     <GSM_FR : 1>
     <reserved : 0>
     <reserved : 0>
     <OHR_AMR-WB : 0>
     <OFR_AMR-WB : 0>
     <OHR_AMR : 0>
     <UMTS_AMR-WB : 0>
     <FR_AMR-WB : 0>
     <PDC_EFR : 0>
     <spare : ''>
>>> show(Msg['CalledPartyBCDNumber'][2])
### CalledPartyBCDNumber ###
 <Ext : 1>
 <Type : 0 (unknown)>
 <NumberingPlan : 1 (ISDN / telephony numbering plan (E.164 / E.163))>
 <Num : 0600000000>
>>> Msg['CalledPartyBCDNumber'][2].get_val()
[1, 0, 1, '`\x00\x00\x00\x00']
>>> Msg['CalledPartyBCDNumber'][2]['Num'].decode()
'0600000000'

>>> Msg, err = parse_NAS_MO(unhexlify('0748610bf602f8108003c8c2e65e9a5804e060c0405202f810c4c25c0a00570220003103e5e0341302f810040511035758a65d0100c1'))
>>> err
0
>>> show(Msg)
### EMMTrackingAreaUpdateRequest ###
 <SecHdr : 0 (No security)>
 <ProtDisc : 7 (EMM)>
 <Type : 72 (Tracking area update request)>
 ### NAS_KSI ###
  <TSC : 0 ( native security context)>
  <Value : 6>
 ### EPSUpdateType ###
  <Active : 0 (No bearer establishment requested)>
  <Value : 1 (combined TA/LA updating)>
 ### OldGUTI ###
  <L : 11>
  ### EPSID ###
   <Digit1 : 0xf>
   <Odd : 0>
   <Type : 6 (GUTI)>
   <PLMN : 20801 (France.Orange)>
   <MMEGroupID : 0x8003>
   <MMECode : 0xc8>
   <MTMSI : 0xc2e65e9a>
 ### UENetCap ###
  <T : 88>
  <L : 4>
  ### UENetCap ###
   <EEA0 : 1>
   <EEA1_128 : 1>
   <EEA2_128 : 1>
   <EEA3_128 : 0>
   <EEA4 : 0>
   <EEA5 : 0>
   <EEA6 : 0>
   <EEA7 : 0>
   <EIA0 : 0>
   <EIA1_128 : 1>
   <EIA2_128 : 1>
   <EIA3_128 : 0>
   <EIA4 : 0>
   <EIA5 : 0>
   <EIA6 : 0>
   <EIA7 : 0>
   <UEA0 : 1>
   <UEA1 : 1>
   <UEA2 : 0>
   <UEA3 : 0>
   <UEA4 : 0>
   <UEA5 : 0>
   <UEA6 : 0>
   <UEA7 : 0>
   <UCS2 : 0>
   <UIA1 : 1>
   <UIA2 : 0>
   <UIA3 : 0>
   <UIA4 : 0>
   <UIA5 : 0>
   <UIA6 : 0>
   <UIA7 : 0>
 ### OldTAI ###
  <T : 82>
  ### TAI ###
   <PLMN : 20801 (France.Orange)>
   <TAC : 0xc4c2>
 ### DRXParam ###
  <T : 92>
  ### DRXParam ###
   <SPLIT_PG_CYCLE_CODE : 10>
   <DRXCycleLen : 0 (DRX not specified by the MS)>
   <SPLITonCCCH : 0>
   <NonDRXTimer : 0 (no non-DRX mode after transfer state)>
 ### EPSBearerCtxtStat ###
  <T : 87>
  <L : 2>
  ### EPSBearerCtxtStat ###
   <EBI_7 : 0 (BEARER CONTEXT-INACTIVE)>
   <EBI_6 : 0 (BEARER CONTEXT-INACTIVE)>
   <EBI_5 : 1 (BEARER CONTEXT-ACTIVE)>
   <EBI_4 : 0>
   <EBI_3 : 0>
   <EBI_2 : 0>
   <EBI_1 : 0>
   <EBI_0 : 0>
   <EBI_15 : 0 (BEARER CONTEXT-INACTIVE)>
   <EBI_14 : 0 (BEARER CONTEXT-INACTIVE)>
   <EBI_13 : 0 (BEARER CONTEXT-INACTIVE)>
   <EBI_12 : 0 (BEARER CONTEXT-INACTIVE)>
   <EBI_11 : 0 (BEARER CONTEXT-INACTIVE)>
   <EBI_10 : 0 (BEARER CONTEXT-INACTIVE)>
   <EBI_9 : 0 (BEARER CONTEXT-INACTIVE)>
   <EBI_8 : 0 (BEARER CONTEXT-INACTIVE)>
 ### MSNetCap ###
  <T : 49>
  <L : 3>
  <MS_network_capability_value_part: [
   <<GEA1_bits: 1>>
   <SM_capabilities_via_dedicated_channels: 1>
   <SM_capabilities_via_GPRS_channels: 1>
   <UCS2_support: 0>
   <SS_Screening_Indicator: 1>
   <SoLSA_Capability: 0>
   <Revision_level_indicator: 1>
   <PFC_feature_mode: 1>
   <<Extended_GEA_bits: [
    <GEA_2: 1>
    <GEA_3: 1>
    <GEA_4: 0>
    <GEA_5: 0>
    <GEA_6: 0>
    <GEA_7: 0>]>>
   <LCS_VA_capability: 0>
   <PS_inter_RAT_HO_from_GERAN_to_UTRAN_Iu_mode_capability: 0>
   <PS_inter_RAT_HO_from_GERAN_to_E_UTRAN_S1_mode_capability: 0>
   <EMM_Combined_procedures_Capability: 1>
   <ISR_support: 1>
   <SRVCC_to_GERAN_UTRAN_capability: 0>
   <EPC_capability: 1>
   <NF_capability: 0>
   <GERAN_network_sharing_capability: 0>]>
 ### OldLAI ###
  <T : 19>
  ### LAI ###
   <PLMN : 20801 (France.Orange)>
   <LAC : 0x0405>
 ### MSCm2 ###
  <T : 17>
  <L : 3>
  ### MSCm2 ###
   <spare : 0>
   <RevLevel : 2 (MS supporting R99 or later)>
   <EarlyCmCap : 1>
   <NoA51 : 0>
   <RFClass : 7>
   <spare : 0>
   <PSCap : 1>
   <SSScreeningCap : 1 (capability of handling of ellipsis notation and phase 2 error handling)>
   <MTSMSCap : 1>
   <VBSNotifCap : 0>
   <VGCSNotifCap : 0>
   <FCFreqCap : 0>
   <MSCm3Cap : 1>
   <spare : 0>
   <LCSVACap : 1>
   <UCS2 : 0>
   <SoLSACap : 0>
   <CMServPrompt : 1>
   <A53 : 1>
   <A52 : 0>
 ### VoiceDomPref ###
  <T : 93>
  <L : 1>
  ### VoiceDomPref ###
   <spare : 0>
   <UEUsage : 0 (Voice centric)>
   <VoiceDomPref : 0 (CS Voice only)>
 ### MSNetFeatSupp ###
  <T : 12>
  ### MSNetFeatSupp ###
   <spare : 0>
   <ExtPeriodTimers : 1>
>>> Msg['OldGUTI'][1].decode()
(6, '20801', 32771, 200, 3269877402L)

This is basically it. For CSN.1-based information elements, the selection of inner part is not as friendly as with normal objects: the list of objects is available under the attribute _list, and the corresponding value under _val:

>>> Msg['MSNetCap'][2]._list[3]
<UCS2_support(CSN1Bit)>
>>> Msg['MSNetCap'][2]._list[3]._name
'UCS2_support'
>>> Msg['MSNetCap'][2]._val[3]
0

Encoding NAS messages

NAS messages are almost always structured in the same way, with a header indicating the protocol and type of message, a list of mandatory information elements (those with variable length are prefixed with a length field), and a list of optional information elements. Those last ones are all prefixed with a tag field, and eventually a length field if of variable length.

When instantiating a NAS message class, if no values are passed in the initialization, the message builds only with mandatory IEs, set with default values:

>>> Msg = MMLocationUpdatingRequest()
>>> show(Msg)
### MMLocationUpdatingRequest ###
 <SkipInd : 0>
 <ProtDisc : 5 (MM)>
 <Seqn : 0>
 <Type : 8 (Registration - LOCATION UPDATING REQUEST)>
 <CKSN : 0>
 ### LocUpdateType ###
  <FollowOnReq : 0>
  <spare : 0>
  <Type : 0 (Normal location updating)>
 ### LAI ###
  <PLMN : 000000>
  <LAC : 0x0000>
 ### MSCm1 ###
  <spare : 0>
  <RevLevel : 2 (MS supporting R99 or later)>
  <EarlyCmCap : 0>
  <NoA51 : 0>
  <RFClass : 0 (class 1)>
 ### ID ###
  <L : 5>
  <V : 0xf400000000>
>>> Msg.to_bytes()
'\x05\x08\x00\x00\x00\x00\x00\x00@\x05\xf4\x00\x00\x00\x00'

From here, it is possible to set IEs one by one, and also the optional ones. A list with all optional IEs is available under the attribute _opts. Optional IEs are by default set to transparent, hence it is require to set them non transparent, to make them appear in the message encoding.

Moreover, many IEs with a Length-Value or Tag-Length-Value structure have by default a value which is a bytes buffer, and a complete IE structure available under the _IE_stat attribute. This is used to encode and decode the exact structure and replace the default bytes buffer. To set the IE exact structure, the method set_IE() can be used: it will initialize the IE with provided arguments, and replace the default bytes buffer.

>>> Msg._opts
[(8, 51, <MSCm2 [transparent] : <T : 51><L : 3><V : 0x400000>>), (4, 12, <AddUpdateParams [transparent] : <T : 12><V : 0>>), (4, 13, <DeviceProp [transparent] : <T : 13><V : 0>>), (4, 14, <MSNetFeatSupp [transparent] : <T : 14><V : 0>>)]
>>> Msg['CKSN'].set_val(2)
>>> Msg['LocUpdateType']['Type'].set_val(1)
>>> Msg['LAI'].set_val((u'20810', 0x1234))
>>> Msg['ID'].set_IE(val={'type': 1, 'ident': '208100123456789'}) # Length-Value
>>> Msg['ID']._IE_stat
<ID : >
>>> Msg['ID']._IE
<ID [IMSI] : 208100123456789>
>>> Msg['AddUpdateParams']
<AddUpdateParams [transparent] : <T : 12><V : 0>>
>>> Msg['AddUpdateParams'].set_trans(False)
>>> Msg['AddUpdateParams'].set_IE(val={'CSMO':1, 'CSMT':1})
>>> show(Msg)
### MMLocationUpdatingRequest ###
 <SkipInd : 0>
 <ProtDisc : 5 (MM)>
 <Seqn : 0>
 <Type : 8 (Registration - LOCATION UPDATING REQUEST)>
 <CKSN : 2>
 ### LocUpdateType ###
  <FollowOnReq : 0>
  <spare : 0>
  <Type : 1 (Periodic updating)>
 ### LAI ###
  <PLMN : 20810 (France.S.F.R.)>
  <LAC : 0x1234>
 ### MSCm1 ###
  <spare : 0>
  <RevLevel : 2 (MS supporting R99 or later)>
  <EarlyCmCap : 0>
  <NoA51 : 0>
  <RFClass : 0 (class 1)>
 ### ID ###
  <L : 8>
  ### ID ###
   <Digit1 : 0x2>
   <Odd : 1>
   <Type : 1 (IMSI)>
   <Digits : 0x80011032547698>
 ### AddUpdateParams ###
  <T : 12>
  ### AddUpdateParams ###
   <spare : 0>
   <CSMO : 1 (CS fallback MO call)>
   <CSMT : 1 (CS fallback MT call)>
>>> Msg.to_bytes()
'\x05\x08!\x02\xf8\x01\x124@\x08)\x80\x01\x102Tv\x98\xc3'

It is also possible to prepare a dict containing all IEs values that are required to appear in the message, and to initialize the message class with it. In this way, all optional IEs that are set, will appear directly in the message encoding:

>>> IEs = {}
>>> IEs['CKSN']= 4
>>> IEs['LocUpdateType'] = {'Type': 1}
>>> IEs['LAI'] = (u'20820', 0x4321)
>>> IEs['ID'] = {'type': 1, 'ident': u'208209876543210'}
>>> IEs['AddUpdateParams'] = {'CSMO': 1, 'CSMT': 1}
>>> IEs
{'LocUpdateType': {'Type': 1}, 'ID': {'ident': u'208209876543210', 'type': 1}, 'AddUpdateParams': {'CSMT': 1, 'CSMO': 1}, 'CKSN': 4, 'LAI': (u'20820', 17185)}
>>> Msg = MMLocationUpdatingRequest(val=IEs)
>>> show(Msg)
### MMLocationUpdatingRequest ###
 <SkipInd : 0>
 <ProtDisc : 5 (MM)>
 <Seqn : 0>
 <Type : 8 (Registration - LOCATION UPDATING REQUEST)>
 <CKSN : 4>
 ### LocUpdateType ###
  <FollowOnReq : 0>
  <spare : 0>
  <Type : 1 (Periodic updating)>
 ### LAI ###
  <PLMN : 20820 (France.Bouygues Telecom)>
  <LAC : 0x4321>
 ### MSCm1 ###
  <spare : 0>
  <RevLevel : 2 (MS supporting R99 or later)>
  <EarlyCmCap : 0>
  <NoA51 : 0>
  <RFClass : 0 (class 1)>
 ### ID ###
  <L : 8>
  ### ID ###
   <Digit1 : 0x2>
   <Odd : 1>
   <Type : 1 (IMSI)>
   <Digits : 0x80028967452301>
 ### AddUpdateParams ###
  <T : 12>
  ### AddUpdateParams ###
   <spare : 0>
   <CSMO : 1 (CS fallback MO call)>
   <CSMT : 1 (CS fallback MT call)>
>>> Msg.to_bytes()
'\x05\x08A\x02\xf8\x02C!@\x08)\x80\x02\x89gE#\x01\xc3'