If you need some example pcap traces generated by any of these tools, just send an email to fasferraz@gmail.com


Showing posts with label MME. Show all posts
Showing posts with label MME. Show all posts

7/23/20

MME Part I - eNB Emulator

After years of doing applications that interact with the GGSN/PGW, I finally had the time to start and finish and old project that i had in mind for some time:

Create an eNB emulator to interact with MME (S1AP) and SGW (S1-U), once again to abstract the radio component. Something that I could use in a laptop, with connectivity to a real MME and SGW, to perform and simulate EPS Attach, TAU, PDN Connectivity Requests, and so on, and also user plane traffic.

This was done using python3, and in the following post I will talk about some of the issues I faced, and how I solved them.

So when we want to create an eNB to interact with a real network, the first concern, is that this time, I need to take into account the authentication, integrity and ciphering. When you emulate an MME or SGW to communicate with a PGW, you are bypassing all this, speaking GTP directly with the node, and filling each information element of the GTP protocol with the values you want. You can chose the IMSI, the MSISDN, the APN, the User Location, the IMEI, and so on.

With the eNB is different: if you choose an IMSI, you need to have the capability to answer with the proper XRES for the RAND/AUTN present in the Authentication Request.
But once you have that ability, you can also perform whatever procedures you want, define and change the characteristics and capabilities of the emulated terminal, and also the underlying features of the emulated eNB (like supporting 4G, 5G or NB-IoT, etc...).

So, a lot of interesting stuff can be done!


The following picture shows a simplified version of what is needed:



So as stated before, the eNB emulator has to implement S1AP and S1-U interfaces, and in case we are using a real HSS/AuC, we need also to handle the authentication and key derivation.

As I already spoke about in previous posts, I have a ZTE dongle that supports the needed AT commands (AT+CRSM and AT+CSIM) which allows me to get the CK and IK from the RAND/AUTN.

With the CK and IK and Serving Network (MCC/MNC) I can calculate the KASME, and derive the NAS keys for ciphering and integrity according to the algorithms negotiated in the Security Mode Command - So this part of the problem is solved!

Nevertheless, to simplify the process, I implemented also a simple Diameter HSS Server, that returns always the same quartet (RAND, AUTN, XRES and KASME) to the MME, so that the eNB can work without the dongle and a real HSS.

On of the issues i faced with the authentication process, was that Ubuntu was blocking my serial communication with the modem from time to time, due to the fact of the modem manager was also trying to retrieve information from the modem.
To avoid this conflict I used the answer from this post in stackoverflow: https://stackoverflow.com/questions/24696527/modem-manager-and-ttyacm-in-use
       
root@ubuntu:/home/fabricio/Documents# lsusb | grep ZTE
Bus 002 Device 000: ID 19d2:2000 ZTE WCDMA Technologies MSM MF627/MF628/MF628+/MF636+ HSDPA/HSUPA

root@ubuntu:/home/fabricio/Documents# more /lib/systemd/system/ModemManager.service 
[Unit]
Description=Modem Manager
After=polkit.service
Requires=polkit.service

[Service]
Type=dbus
BusName=org.freedesktop.ModemManager1
ExecStart=/usr/sbin/ModemManager --filter-policy=default
StandardError=null
Restart=on-abort
CapabilityBoundingSet=CAP_SYS_ADMIN
ProtectSystem=true
ProtectHome=true
PrivateTmp=true
RestrictAddressFamilies=AF_NETLINK AF_UNIX
NoNewPrivileges=true
User=root

[Install]
WantedBy=multi-user.target
Alias=dbus-org.freedesktop.ModemManager1.service


root@ubuntu:/home/fabricio/Documents# more /etc/udev/rules.d/99zte.rules 
ATTRS{idVendor}=="19d2" ATTRS{idProduct}=="0001", ENV{ID_MM_DEVICE_IGNORE}="1"
ATTRS{idVendor}=="19d2" ATTRS{idProduct}=="2000", ENV{ID_MM_DEVICE_IGNORE}="1"


root@ubuntu:/home/fabricio/Documents# udevadm control --reload-rules
   
In resume, you need to get the idVendor and idProduct from the modem using lsusb to create a new rules file for this modem with ENV{ID_MM_DEVICE_IGNORE}="1", and then change the filter-policy from strict to default in ModemManager.service, and reload the rules in the end.
After doing this no Ubuntu process tries to communicate with the modem.


One of the main challenges of building an eNB emulator, is the fact that S1AP interface uses SCTP, and is based on PER (Packed Encoding Rules) ASN.1.

I had previously done some experiments with SCTP using the native socket module from python3, but starting a ASN.1 module from scratch was a big challenge. Fortunately I found some magnificent python modules for ASN.1 and S1AP done by P1 Security that I highly recommend:


They have plenty of modules for almost anything related to Mobile Developments, but for my project i just used the S1AP from pycrate_asn1dir module, and CM from the CryptoMobile module (that has all the ciphering and integrity protocols needed for NAS). In order to derive the integrity and ciphering keys from KASME/CK/IK) i used another module: the Crypto.Hash (pip3 install pycryptodome) that has the HMAC and SHA256 functions needed for KDF.
For serial communication i use the pyserial module (pip3 install pyserial)

In resume:
       
from pycrate_asn1dir import S1AP
from pycrate_asn1rt.utils import *

from CryptoMobile.CM import *

from Crypto.Hash import HMAC
from Crypto.Hash import SHA256

import serial

 

For the usage of SCTP under S1AP, specification 36.412 has some requirements:

  1. SCTP (IETF RFC 4960 [5]) shall be supported as the transport layer of S1-MME signalling bearer. The Payload Protocol Identifier assigned by IANA to be used by SCTP for the application layer protocol S1AP is 18.
  2. There shall be only one SCTP association established between one MME and eNB pair.

  3. The eNB shall establish the SCTP association. The SCTP Destination Port number value assigned by IANA to be used for S1AP is 36412.

  4. Within the SCTP association established between one MME and eNB pair:

    -     a single pair of stream identifiers shall be reserved for the sole use of S1AP elementary procedures that utilize non UE-associated signalling.

    -     At least one pair of stream identifiers shall be reserved for the sole use of S1AP elementary procedures that utilize UE-associated signallings. However a few pairs (i.e. more than one) should be reserved.

    -     A single UE-associated signalling shall use one SCTP stream and the stream should not be changed during the communication of the UE-associated signalling.


The following code shows how to create an SCTP socket for S1AP, with Payload Protocol Identifier = 18. In this example, MME has the IP 1.1.1.1, and eNB client has the IP 2.2.2.2. The last line is to setup the variable PDU that is used to set and read the S1AP messages:
       
import socket

def main():

    server_address = ('1.1.1.1', 36412)

    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM,socket.IPPROTO_SCTP) 
    client.bind(('2.2.2.2', 0))  
    
    sctp_default_send_param = bytearray(client.getsockopt(132,10,32))
    sctp_default_send_param[11]= 18
    client.setsockopt(132, 10, sctp_default_send_param)

    client.connect(server_address)

PDU = S1AP.S1AP_PDU_Descriptions.S1AP_PDU


In order to handle the SCTP streams, I decided that I would use stream 0 for non UE associated signalling, and stream 1 for UE associated signalling.
To change the stream before sending the S1AP message over SCTP, I use the following code:

def set_stream(client, stream):    
    sctp_default_send_param = bytearray(client.getsockopt(132,10,32))
    sctp_default_send_param[0]= stream
    client.setsockopt(132, 10, sctp_default_send_param)    
    return client      


One important variable that I use across the application, is a dictionary with all the settings and parameters the application uses. This dictionary has state session variables, like ciphering and integrity keys, GUTI, M-TMSI, APNs, IP-Adresses, IMEI, NAS Message (Received/To be Sent), State information, etc... related to S1AP and NAS protocols.

This is the initialization of the dictionary. (Some keys could also be initialized through the CLI options when starting the app):
          

def session_dict_initialization(session_dict):

    session_dict['STATE'] = 0
    session_dict['ENB-UE-S1AP-ID'] = 1000
    session_dict['ENB-NAME'] = 'Fabricio-eNB'
    session_dict['ENB-PLMN'] = return_plmn(PLMN)
    session_dict['XRES'] = b'xresxres'

    session_dict['KASME'] = b'kasme   kasme   kasme   kasme   '
    # hex: 6b61736d652020206b61736d652020206b61736d652020206b61736d65202020
    
    session_dict['ENB-GTP-ADDRESS-INT'] = ''
    
    session_dict['RAB-ID'] = []
    session_dict['SGW-GTP-ADDRESS'] = []
    session_dict['SGW-TEID'] = []
    
    session_dict['EPS-BEARER-IDENTITY'] = []
    session_dict['EPS-BEARER-TYPE'] = []  # default 0, dedicated 1
    session_dict['EPS-BEARER-STATE']  = [] # active 1, inactive 0
    session_dict['EPS-BEARER-APN'] = []
    session_dict['PDN-ADDRESS'] = []

    session_dict['PDN-ADDRESS-IPV4'] = None
    session_dict['PDN-ADDRESS-IPV6'] = None
    
    session_dict['ENB-TAC'] = b'\x00\x01'
    session_dict['ENB-TAC-NBIOT'] = b'\x00\x02'    
    session_dict['ENB-ID'] = 1
    session_dict['ENB-CELLID'] = 1000000
    
    session_dict['NAS-KEY-EEA1'] = return_key(session_dict['KASME'],1,'NAS-ENC')
    session_dict['NAS-KEY-EEA2'] = return_key(session_dict['KASME'],2,'NAS-ENC')
    session_dict['NAS-KEY-EEA3'] = return_key(session_dict['KASME'],3,'NAS-ENC')
    session_dict['NAS-KEY-EIA1'] = return_key(session_dict['KASME'],1,'NAS-INT')
    session_dict['NAS-KEY-EIA2'] = return_key(session_dict['KASME'],2,'NAS-INT')
    session_dict['NAS-KEY-EIA3'] = return_key(session_dict['KASME'],3,'NAS-INT')  
    session_dict['UP-COUNT'] = -1    
    session_dict['DOWN-COUNT'] = -1
  
    session_dict['ENC-ALG'] = 0
    session_dict['INT-ALG'] = 0 
    session_dict['ENC-KEY'] = None
    session_dict['INT-KEY'] = None  
    session_dict['APN'] = APN
    
    
    session_dict['NAS-SMS-MT'] = None
    
    if session_dict['LOCAL_KEYS'] == True:
        if session_dict['IMSI'] == None:
            session_dict['IMSI'] = IMSI
        
    else:
        if session_dict['IMSI'] == None:
            try:
            
                session_dict['IMSI'] = get_imsi(session_dict['SERIAL-INTERFACE'])
            except:
                session_dict['LOCAL_KEYS'] = True
                session_dict['IMSI'] = IMSI
        
    if session_dict['IMEISV'] == None:
        session_dict['IMEISV'] = IMEISV
    
    session_dict['ENCODED-IMSI'] = eNAS.encode_imsi(session_dict['IMSI'])
    session_dict['ENCODED-IMEI'] = eNAS.encode_imei(IMEISV)
    session_dict['ENCODED-GUTI'] = eNAS.encode_guti(12345,32769,1,12345678)
    
    session_dict['S-TMSI'] = None
    
    session_dict['TMSI'] = None
    session_dict['LAI'] = None
    
    session_dict['CPSR-TYPE'] = 0
    
    session_dict['S1-TYPE'] = "4G"
    session_dict['MOBILE-IDENTITY'] = session_dict['ENCODED-IMSI'] 
    session_dict['MOBILE-IDENTITY-TYPE'] = "IMSI" 
    session_dict['SESSION-SESSION-TYPE'] = None
    session_dict['SESSION-TYPE'] = "4G"
    session_dict['SESSION-TYPE-TUN'] = 1
    session_dict['PDP-TYPE'] = 1
    session_dict['ATTACH-PDN'] = None
    session_dict['ATTACH-TYPE'] = 1
    session_dict['TAU-TYPE'] = 0
    session_dict['SMS-UPDATE-TYPE'] = False
    session_dict['NBIOT-SESSION-TYPE'] = "NONE"
    session_dict['CPSR-TYPE'] = 0

    session_dict['UECONTEXTRELEASE-CSFB'] = False
    
    session_dict['PROCESS-PAGING'] = True

    session_dict['LOG'] = []

    return session_dict
  
 

So, a lot of different settings and variables inside this dictionary!

The first step after being connected to the MME is sending the non-UE related S1SetupRequest message to bring-up the eNB.

The pycrate module uses a very python-way of creating the ASN.1 messages for S1AP.

It's not straightforward, but once you get into it, it becomes more easier.
Of course you need to be good with ASN.1, at least reading it properly in the 3GPP 36.413 specification! 😅 

You need to know the exact names of information elements!

This is what a S1SetupRequest message looks like:
       

def S1SetupRequest(dic):

    IEs = []
    IEs.append({'id': 59, 'value': ('Global-ENB-ID', {'pLMNidentity': dic['ENB-PLMN'], 'eNB-ID' : ('macroENB-ID', (dic['ENB-ID'], 20))}), 'criticality': 'reject'})
    IEs.append({'id': 60, 'value': ('ENBname', dic['ENB-NAME']), 'criticality': 'ignore'})    
    if dic['S1-TYPE'] == "4G" :
        IEs.append({'id': 64, 'value': ('SupportedTAs', [{'tAC': dic['ENB-TAC'], 'broadcastPLMNs': [dic['ENB-PLMN']]}]), 'criticality': 'reject'})    
    elif dic['S1-TYPE'] == "NBIOT":
        IEs.append({'id': 64, 'value': ('SupportedTAs', [{'tAC': dic['ENB-TAC-NBIOT'], 'broadcastPLMNs': [dic['ENB-PLMN']], 'iE-Extensions': [{'id':232, 'criticality': 'reject', 'extensionValue':('RAT-Type','nbiot')}]}]), 'criticality': 'reject'})        
    elif dic['S1-TYPE'] == "BOTH":
        IEs.append({'id': 64, 'value': ('SupportedTAs', [{'tAC': dic['ENB-TAC'], 'broadcastPLMNs': [dic['ENB-PLMN']]}, {'tAC': dic['ENB-TAC-NBIOT'], 'broadcastPLMNs': [dic['ENB-PLMN']], 'iE-Extensions': [{'id':232, 'criticality': 'reject', 'extensionValue':('RAT-Type','nbiot')}]}]), 'criticality': 'reject'})        
    IEs.append({'id': 137, 'value': ('PagingDRX', 'v128'), 'criticality': 'ignore'})
    if dic['S1-TYPE'] == "NBIOT" or dic['S1-TYPE'] == "BOTH":
        IEs.append({'id': 234, 'value': ('NB-IoT-DefaultPagingDRX', 'v256'), 'criticality': 'ignore'})  
    val = ('initiatingMessage', {'procedureCode': 17, 'value': ('S1SetupRequest', {'protocolIEs': IEs}), 'criticality': 'ignore'})
    dic = eMENU.print_log(dic, "S1AP: sending S1SetupRequest")
    return val


       
 

To send this S1SetupRequest to the MME, I use this code:
       
    PDU.set_val(S1SetupRequest(session_dict))
    message = PDU.to_aper()
    client = set_stream(client, 0)        
    bytes_sent = client.send(message)

The first line puts the list in the PDU variable, and the second line transform that into bytes in PER ASN.1 format according to 36.413.
The third line sets the stream to 0, because we are sending this message in the non UE strream, and the forth line is just sending the bytes out through the SCTP socket towards the MME.

12/16/13

MME/SGW/SGSN GTPv2 Emulator

This is a new application that I've done recently, that is an evolution from the GTP Emulator that was only GTPv1.

I started to implement only the interface S5/S8 to interact with the PGW, but later on I also added the MME interface S11 and SGSN interface S4 towards the SGW.

This application has the following main interface:



This tab has the SGSN Gn part like the previous GTP Emulator App, with the addition of the UpdatePDPContext message, the SGW part with four GTPcV2 messages, and the MME/SGSN part with five GTPcV2 messages, but for nine types of procedures.

The App is ready to answer to CreateBearerRequest or UpdateBearerRequest sent from the PGW/SGW, even with DedicatedBearers (and with several bearers at the same time), and generally speaking, any message originated from the PGW or SGW nodes (DataDownlinkNotification, DeleteBearerRequest).

In the second tab of this app we have the QOS part, which is the same tab of the GTP Emulator App, plus the 4G part:



In this tab I added also the PCO bytes to send in the CreatePDPContext or CreateSessionRequest, mainly due the lack of space in the main tab.

The third tab is a Log, which shows some relevant information form the messages sent and receive. Here is an example:



The fourth tab is one of the most interesting ones. Is the test tab.
This part of the application allow us to do the following things using some specific CLI that I've created:
  • Send any of the supported messages
  • Set some specific parameters like QOS, Comon Flags, RAT Type, CGI/SAI/EGCI in these messages
  • Set the IPs of the PGW or SGW for the following tests
  • Set specific parameters (like the QOS or Event-Triggers) in other two Apps that I also have (OCS and PCRF Server), using an UDP interface and protocol that I've develop specific for this feature


For example the test presented in this next image is the Creation, Update and Delete of PDP Contexts, using different QOS, and different Common Flags values, to test all the possible combinations of a scenario that I needed to test:





Without this app, some of the combinations were impractical to reproduce and some would take a big amount of time. With this app, it toked less than one minute. Just check this cap file of the test above.

For example it's so easy to start a session in 4G, do an update to 3G, then an update to 2G, then an update to 4G, and so on, setting all the parameters accordingly. Things that in real live networks, using real terminals are very hard to achieve, and take lots, and lots of time.

With this tool: just seconds!

Again, this type of tools allow us to set non standard messages just to see how the nodes react to erroneous messages.

6/2/11

EIR Diameter Server

This is an application that I've made in 2011. It's an EIR Diameter Server that implements the EPC/LTE s13 interface towards an SGSN/MME.

It complies with 3GPP 29.272 "Evolved Packet System (EPS); Mobility Management Entity (MME) and Serving GPRS Support Node (SGSN) related interfaces based on Diameter protocol", and basically it answers to the ME-Identity-Check-Request with the IMEI Equipment-Status:

->WhiteListed
->GreyListed
->BlackListed




With this application it's possible to define a list of white, grey and black IMEIs (or initial digits of IMEI), in order to test completely the s13 interface.

The Logging option displays the result sent in the last twenty ME-Identity-Check-Answer messages.

This application was done easily in two days, since it uses the Diameter framework already developed for the OCS and PCRF Diameter Server Applications.

I've tested it, with a real MME recently. It works as expected!

The 3GPP-ME-Indentity-Check-Request:


The 3GPP-ME-Identity-Check-Answer: