P1sec / pycrate

A Python library to ease the development of encoders and decoders for various protocols and file formats; contains ASN.1 and CSN.1 compilers.
GNU Lesser General Public License v2.1
380 stars 130 forks source link

Additionnal Information Request and MSNetCap encoding cannot be decoded #226

Closed Marc-Egli closed 1 year ago

Marc-Egli commented 1 year ago

I am trying to add a MSNetCap and a AddInfoReq field to a EMMTrackingAreaUpdateRequest

Here is a small snippet of code describing what I do :

from pycrate_mobile.NAS import *
from pycrate_csn1dir.ms_network_capability_value_part import ms_network_capability_value_part

# Valid MSNetCap Object
val = [1, 1, 1, 0, 1, 0, 1, 1, [1, 1, 1, 1, 1, 1], 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, []]
MSNetCap = ms_network_capability_value_part.clone()
MSNetCap.set_val(val)
show(MSNetCap)

fields = {'MSNetCap' : val, 'AddInfoReq' : {'spare' : 0, 'CipherKey': 1}}
msg = EMMTrackingAreaUpdateRequest(val=fields)

# Valid message with MSNetCap field values set
show(msg)

Msg, err = parse_NAS_MT(msg.to_bytes())

if err != 0:
    print(err)

# Decode using from_bytes
EMMTrackingAreaUpdateRequest().from_bytes(msg.to_bytes())

The parse_NAS_MT function fails with error 96, which means I am missing a mandatory field. I do not understand why as I generate the MSNetCap according to the specifications. I noticed in the wiki that there was an explanation about decoding and encoding MSNetCap fields. I tried using the values from there instead of my own values. The difference is that the value from the wiki which is: [1, 1, 1, 0, 1, 0, 1, 1, [1, 1, 1, 1, 1, 1], 0, 0, 0, 1, 1, 0, 1, 0, 0], misses the last four single bit fields and the spare bit reference. To my surprise using the wiki value worked and the message was correctly re-encoded. I then tried the following value: [1, 1, 1, 0, 1, 0, 1, 1, [1, 1, 1, 1, 1, 1], 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, [0,0,0,0]] which also worked but the decoding was wrong as shown below.

  <ms_network_capability_value_part (CSN1List): [
   <(CSN1Ref): <gea1_bits (CSN1Bit): 1>>
   <sm_capabilities_via_dedicated_channels (CSN1Bit): 1>
   <sm_capabilities_via_gprs_channels (CSN1Bit): 1>
   <ucs2_support (CSN1Bit): 0>
   <ss_screening_indicator (CSN1Bit): 1>
   <solsa_capability (CSN1Bit): 0>
   <revision_level_indicator (CSN1Bit): 1>
   <pfc_feature_mode (CSN1Bit): 1>
   <(CSN1Ref): <extended_gea_bits (CSN1List): [
    <gea_2 (CSN1Bit): 1>
    <gea_3 (CSN1Bit): 1>
    <gea_4 (CSN1Bit): 1>
    <gea_5 (CSN1Bit): 1>
    <gea_6 (CSN1Bit): 1>
    <gea_7 (CSN1Bit): 1>]>>
   <lcs_va_capability (CSN1Bit): 0>
   <ps_inter_rat_ho_from_geran_to_utran_iu_mode_capability (CSN1Bit): 0>
   <ps_inter_rat_ho_from_geran_to_e_utran_s1_mode_capability (CSN1Bit): 0>
   <emm_combined_procedures_capability (CSN1Bit): 1>
   <isr_support (CSN1Bit): 1>
   <srvcc_to_geran_utran_capability (CSN1Bit): 0>
   <epc_capability (CSN1Bit): 1>
   <nf_capability (CSN1Bit): 0>
   <geran_network_sharing_capability (CSN1Bit): 0>
   <user_plane_integrity_protection_support (CSN1Bit): 1>
   <gia_4 (CSN1Bit): 0>
   <gia_5 (CSN1Bit): 0>
   <gia_6 (CSN1Bit): 0>
   <gia_7 (CSN1Bit): 0>
   <(CSN1Ref): <spare_bits (CSN1Bit): [0,
   0,
   0]>>]>

The spare_bits field contains only 3 bits whereas in my MSNetCap field I specified 4 bits set to 0 in the spare_bits reference field.

Another observation I made is that removing the AddinfoReq will solve the issue and make the 4th bit reappear in the message.

When I try to manually decode an EMMTrackingAreaUpdateRequest using from_bytes is get the following error :

  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_core/elt.py", line 652, in from_bytes
    self._from_char(char)
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_mobile/TS24007.py", line 165, in _from_char
    self._dec_unk_ie(T8, char)
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_mobile/TS24007.py", line 222, in _dec_unk_ie
    L = char.get_uint(8)
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_core/charpy.py", line 775, in get_uint
    raise(CharpyErr('bitlen overflow: {0}, max {1}'\
pycrate_core.charpy.CharpyErr: bitlen overflow: 8, max 0

Note that the following values also crash but in different locations :

[1, 1, 1, 0, 1, 0, 1, 1, [1, 1, 1, 1, 1, 1], 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0] [1, 1, 1, 0, 1, 0, 1, 1, [1, 1, 1, 1, 1, 1], 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0] [1, 1, 1, 0, 1, 0, 1, 1, [1, 1, 1, 1, 1, 1], 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, [0, 0]]

I am still looking into the source code to find out why this happens. I think there is an issue with overlapping fields.

Edit

line 222, in _dec_unk_ie in the error log is the computation of the Length for a Type4TLV.

Thanks in advance.

p1-bmu commented 1 year ago

Thanks for your feedback. CSN.1 fields are quite painful to manage unfortunately... Below, we can see that the get_len() method for them are not exactly aligned with get_bl() (i.e. for getting the length in bits), as it returns the round length in bytes, omitting the trailing / padding bits required for the NAS IE to align.

In [49]: MSNetCap.set_val([1, 1, 1, 0, 1, 0, 1, 1, [1, 1, 1, 1, 1, 1], 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, []])                                                                                        

In [50]: MSNetCap.get_len()                                                                                                                                                                                  
Out[50]: 3

In [51]: MSNetCap.get_bl()                                                                                                                                                                                   
Out[51]: 29

In [52]: MSNetCap.set_val([1, 1, 1, 0, 1, 0, 1, 1, [1, 1, 1, 1, 1, 1], 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, [0, 0, 0]])                                                                                 

In [53]: MSNetCap.get_bl()                                                                                                                                                                                   
Out[53]: 32

In [54]: MSNetCap.get_len()                                                                                                                                                                                  
Out[54]: 4

In [55]: m = EMMTrackingAreaUpdateRequest(val={'MSNetCap': [1, 1, 1, 0, 1, 0, 1, 1, [1, 1, 1, 1, 1, 1], 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, [0, 0, 0]], 'AddInfoReq': {'CipherKey': 1}})               

In [56]: m, e = parse_NAS_MO(m.to_bytes())                                                                                                                                                                   

In [57]: e                                                                                                                                                                                                   
Out[57]: 0

The workaround is to insert the padding at the end of the MSNetCap value explicitely. I need to check how easy it is to fix the get_len() method for the CSN1 objects.

p1-bmu commented 1 year ago

OK, that one was easy to fix: https://github.com/P1sec/pycrate/commit/7e904f993c97aebc299792e9c4f2d98947403a21

Marc-Egli commented 1 year ago

Thank you very much ! Works for me now.