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
382 stars 132 forks source link

LCSClientID optional field cannot be set in an EMM message because of error during ber-encoding #223

Closed Marc-Egli closed 1 year ago

Marc-Egli commented 1 year ago

Hello,

I am currently working on generating some NAS packets and I observed some strange behavior when setting the LCSClientID optional field inside an EMMCSServiceNotification message.

From the below code, I concluded that I needed to generate an LCSClientID message using the ASN.1 specifications.

LCSClientId = wrapper.gen_ber_wrapper(MAP.MAP_LCS_DataTypes.LCS_ClientID,
                                          asn_map_acquire,
                                          asn_map_release)

I obtained the below LCSClientID message which is correctly decoded and encoded. All values are random but conform to the specifications.

{'lcsClientType': 'emergencyServices', 'lcsClientExternalID': {'externalAddress': b'J', 'extensionContainer': {'privateExtensionList': [{'extId': (179, 17, 158, 19), 'extType': ('BOOLEAN', True)}], 'pcs-Extensions': {}}}, 'lcsClientDialedByMS': b'"\xed', 'lcsClientInternalID': 'o-andM-VPLMN', 'lcsClientName': {'dataCodingScheme': b'\x11', 'nameString': b'h\xecQ\xc9\x89@U\x03X\xaf\xda\xcd\\\x8c\xfc\xc3B\xea'}}

I then use this dictionary to set the value of the LCSClientID in the EMMCSServiceNotification message. When doing so I get the following error :

File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/wrapper.py", line 123, in set_val
    self._buf = self.OBJ.to_ber()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj.py", line 1680, in to_ber
    return pack_val(*self._to_ber())[0]
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj.py", line 1629, in _to_ber
    pc, lval, V = self._encode_ber_cont()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj_construct.py", line 2205, in _encode_ber_cont
    comp_tlv = Comp._to_ber()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj.py", line 1629, in _to_ber
    pc, lval, V = self._encode_ber_cont()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj_construct.py", line 2205, in _encode_ber_cont
    comp_tlv = Comp._to_ber()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj.py", line 1629, in _to_ber
    pc, lval, V = self._encode_ber_cont()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj_construct.py", line 2205, in _encode_ber_cont
    comp_tlv = Comp._to_ber()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj.py", line 1629, in _to_ber
    pc, lval, V = self._encode_ber_cont()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj_construct.py", line 3156, in _encode_ber_cont
    tlv = Comp._to_ber()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj.py", line 1629, in _to_ber
    pc, lval, V = self._encode_ber_cont()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj_construct.py", line 2205, in _encode_ber_cont
    comp_tlv = Comp._to_ber()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj.py", line 1629, in _to_ber
    pc, lval, V = self._encode_ber_cont()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj_ext.py", line 582, in _encode_ber_cont
    TLV = Obj._to_ber()
  File "/usr/local/lib/python3.10/dist-packages/pycrate-0.5.5-py3.10.egg/pycrate_asn1rt/asnobj.py", line 1630, in _to_ber
    if not self._tagc:
AttributeError: 'BOOL' object has no attribute '_tagc'. Did you mean: '_tag'?

I traced back the error to the privateExtensionList field that contains an extId and a extType. The issue is that the type I provide cannot be ber encoded because of the missing _tagc attribute. I verified the definition of the extType, which is an ASN.1 OpenType, I should be able to provide any type to this field. The LCSClientID packet is valid but the encoding to put it into the EMMCSServiceNotification message fails.

Thank you in advance.

p1-bmu commented 1 year ago

Thanks for the report. Can you share a code snippet to reproduce the issue ?

Marc-Egli commented 1 year ago

Sure, here is a small example:

from pycrate_mobile.NAS import *

lcs_packet = MAP.MAP_LCS_DataTypes.LCS_ClientID
lcs_fields = {'lcsClientType': 'lawfulInterceptServices', 'lcsClientExternalID': {'externalAddress': b'\xaa\x8e\xa9\xdaqg', 'extensionContainer': { 'privateExtensionList': [{'extId': (0,1,2,3,4), 'extType' : ('BOOLEAN', True)}],'pcs-Extensions': {}}}, 'lcsClientDialedByMS': b'[\x129\x10\xd47\x02w\x10\xbb\xe0\x1fM\xc8\xc9', 'lcsClientInternalID': 'o-andM-HPLMN', 'lcsClientName': {'dataCodingScheme': b';', 'nameString': b'\x97\xfe6yh<\xbe\\Q\x91\x14\xbaVT\xa3'}}           
lcs_packet.set_val(lcs_fields)

# Detect invalid format by encoding and decoding
try:
    lcs = MAP.MAP_LCS_DataTypes.LCS_ClientID
    lcs.from_uper(lcs_packet.to_uper())
except Exception as err:
    print("Decoding failed")
    exit(0)

# Set LCSClientID fields in EMMCSServiceNotification message
val = {'LCSClientId' : lcs_fields}
msg = EMMCSServiceNotification(val=val)

This code snippet crashes when the LCSClientID field is set in the EMMCSServiceNotification message.

p1-bmu commented 1 year ago

Here is a shorter code snippet that exhibits the issue:

from pycrate_asn1dir.MAP import *
p = MAP_LCS_DataTypes.LCS_ClientID
v = {'lcsClientType': 'lawfulInterceptServices', 'lcsClientExternalID': {'externalAddress': b'\xaa\x8e\xa9\xdaqg', 'extensionContainer': { 'privateExtensionList': [{'extId': (0,1,2,3,4), 'extType': ('BOOLEAN', True)}],'pcs-Extensions': {}}}, 'lcsClientDialedByMS': b'[\x129\x10\xd47\x02w\x10\xbb\xe0\x1fM\xc8\xc9', 'lcsClientInternalID': 'o-andM-HPLMN', 'lcsClientName': {'dataCodingScheme': b';', 'nameString': b'\x97\xfe6yh<\xbe\\Q\x91\x14\xbaVT\xa3'}}
p.set_val(v)
p.to_ber()

One remark with your code: you use from_uper() and to_uper() with the MAP object, which is incorrect, as MAP relies on the BER codec. The issue lies in the use of an arbitrary object (here, a raw boolean) in the privateExtensionList which contains OPEN objects, together with the BER encoding which requires object tagging. I'll need to check more carefully on how to address the issue properly.

Marc-Egli commented 1 year ago

Thank you very much for looking into this issue and also for the explanation about the incorrect function calls from_uper() and to_uper() on the MAP object.

p1-bmu commented 1 year ago

I just pushed https://github.com/P1sec/pycrate/commit/581a50fcc65f75d82e7199b98e68e4dfe19838aa, that should fix the issue you reported. Can you test it on your side and give me a feedback ? Thanks for your help.

Marc-Egli commented 1 year ago

I just tested on my side and the fix works for me. Thank you very much for having looked into it and finding a patch !