pycrate/pycrate_mobile/NAS5G.py

266 lines
8.4 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: UTF-8 -*-
#/**
# * Software Name : pycrate
# * Version : 0.4
# *
# * Copyright 2019. Benoit Michau. P1Sec.
# *
# * This library is free software; you can redistribute it and/or
# * modify it under the terms of the GNU Lesser General Public
# * License as published by the Free Software Foundation; either
# * version 2.1 of the License, or (at your option) any later version.
# *
# * This library is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# * Lesser General Public License for more details.
# *
# * You should have received a copy of the GNU Lesser General Public
# * License along with this library; if not, write to the Free Software
# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# * MA 02110-1301 USA
# *
# *--------------------------------------------------------
# * File Name : pycrate_mobile/NAS5G.py
# * Created : 2019-12-02
# * Authors : Benoit Michau
# *--------------------------------------------------------
#*/
from pycrate_core.utils import *
from .TS24501_FGMM import FGMMTypeClasses, FGMMSecProtNASMessage
from .TS24501_FGSM import FGSMTypeClasses
from .TS24501_UEPOL import FGUEPOLTypeClasses
from .TS24519_TSNAF import FGTSNAFEthPortTypeClasses, FGTSNAFBridgeTypeClasses
from .TS24011_PPSMS import PPSMSCPTypeClasses
def parse_NAS5G(buf, inner=True, sec_hdr=True):
"""Parses a 5G NAS message bytes' buffer
Args:
buf: 5G NAS message bytes' buffer
inner: if True, decode NASMessage within security header if possible
decode ?
sec_hdr: if True, handle the 5GMM security header
otherwise, just consider the NAS message is in plain text
Returns:
element, err: 2-tuple
element: Element instance, if err is null (no error)
element: None, if err is not null (standard 5G NAS error code)
"""
try:
# this corresponds actually only to the layout of the 5GMM header
pd, shdr, typ = unpack('>BBB', buf[:3])
except Exception:
# error 111, unspecified protocol error
return None, 111
#
if pd == 126:
# 5GMM
if sec_hdr and shdr in (1, 2, 3, 4):
# 5GMM security protected NAS message
Msg = FGMMSecProtNASMessage()
try:
Msg.from_bytes(buf)
except Exception:
# error 96, invalid mandatory info
return None, 96
if inner and shdr in (1, 3):
# parse clear-text NAS message container
cont, err = parse_NAS5G(Msg[3].get_val(), inner=inner)
if cont is not None:
Msg.replace(Msg[3], cont)
return Msg, err
else:
return Msg, 0
else:
# sec hdr == 0 or undefined
# no security, straight 5GMM message
try:
Msg = FGMMTypeClasses[typ]()
except KeyError:
# error 97, message type non-existent or not implemented
return None, 97
#
elif pd == 46:
# 5GSM
try:
if python_version < 3:
typ = ord(buf[3:4])
else:
typ = buf[3]
except:
# error 111, unspecified protocol error
return None, 111
try:
Msg = FGSMTypeClasses[typ]()
except KeyError:
# error 97, message type non-existent or not implemented
return None, 97
#
else:
# error 97: message type non-existent or not implemented
return None, 97
#
try:
Msg.from_bytes(buf)
except Exception:
# error 96, invalid mandatory info
return None, 96
#
if inner:
if pd == 126:
if typ in (65, 76, 79, 94):
nasc = Msg['NASContainer']
if not nasc.get_trans():
# NAS Container present in Msg
Cont, err = parse_NAS5G(nasc[-1].get_val(), inner=inner)
if err == 0:
nasc.replace(nasc['V'], Cont)
#
if typ in (65, 79, 103, 104):
payct, payc = Msg['PayloadContainerType'], Msg['PayloadContainer']
if not payct.get_trans() and not payc.get_trans():
# Payload container present in Msg
conttype, contbuf = payct['V'].get_val(), payc['V'].get_val()
Cont, err = parse_PayCont(conttype, contbuf)
if err == 0:
payc.replace(payc['V'], Cont)
#
elif pd == 46:
if typ in (193, 201, 203, 204):
ethc = Msg['PortMgmtInfoContainer']
if not ethc.get_trans():
# PortMgmtInfoContainer present in Msg
Cont, err = parse_PortMgmtInfoCont(ethc['V'].get_val())
if err == 0:
ethc.replace(ethc['V'], Cont)
#
return Msg, 0
def parse_PayCont(conttype, buf):
if conttype == 1 and len(buf) >= 2:
# 5GSM
return parse_NAS5G(buf, inner=True)
elif conttype == 2 and len(buf) >= 2:
# SMS PP
pd, typ = unpack('>BB', buf[:2])
pd &= 0xF
if pd == 9 and typ in (1, 4, 16):
Cont = PPSMSCPTypeClasses[typ]()
try:
Cont.from_bytes(buf)
except Exception:
# error 96, invalid mandatory info
return None, 96
else:
return Cont, 0
else:
# error 97, Message type non-existent or not implemented
return None, 97
elif conttype == 3:
# LPP, TODO
pass
elif conttype == 4 and len(buf) >= 17:
# SOR
Cont = SORTransContainer()
try:
Cont.from_bytes(buf)
except Exception:
# error 96, invalid mandatory info
return None, 96
else:
return Cont, 0
elif conttype == 5 and len(buf) >= 2:
# UE policy
_, typ = unpack('>BB', buf[:2])
if 1 <= typ <= 4:
Cont = FGUEPOLTypeClasses[typ]()
try:
Cont.from_bytes(buf)
except Exception:
# error 96, invalid mandatory info
return None, 96
else:
return Cont, 0
else:
# error 97, Message type non-existent or not implemented
return None, 97
elif conttype == 6 and len(buf) >= 17:
# UPU
Cont = UPUTransContainer()
try:
Cont.from_bytes(buf)
except Exception:
# error 96, invalid mandatory info
return None, 96
else:
return Cont, 0
elif conttype == 7:
# LCS, TODO
pass
elif conttype == 8 and len(buf) >= 1:
# CIoT
Cont = CIoTSmallDataContainer()
try:
Cont.from_bytes(buf)
except Exception:
# error 96, invalid mandatory info
return None, 96
else:
return Cont, 0
elif conttype == 15 and len(buf) >= 1:
# multi
Cont = PayloadContainerMult()
try:
Cont.from_bytes(buf)
except Exception:
# error 96, invalid mandatory info
return None, 96
else:
# parse each entry
for entry in cont['Entries']:
e_conttype, e_buf = Cont['Type'].get_val(), Cont['Cont'].get_val()
e_cont, e_err = parse_NAS5GPayCont(e_conttype, e_buf)
if e_err == 0:
entry.replace(entry['Cont'], e_cont)
# error 96, invalid mandatory info
return None, 96
def parse_PortMgmtInfoCont(buf):
try:
# this corresponds actually only to the layout of the 5GMM header
typ = unpack('>B', buf[:1])
except Exception:
# error 96, invalid mandatory info
return None, 96
if 1 <= typ <= 6:
Cont = FGTSNAFEthPortTypeClasses[typ]()
try:
Cont.from_bytes(buf)
except Exception:
# error 96, invalid mandatory info
return None, 96
else:
return Cont, 0
else:
# error 97, Message type non-existent or not implemented
return None, 97