Vous êtes sur la page 1sur 15

Python Protocol Simulator

By Sergej repfler (sergej.srepfler@gmail.com)


Ver 0.3

A quick guide
1.

How to build diameter message flow using libDiameter.......................................2


1.1
Copying existing diameter packets..............................................................2
1.2
Building request with AVPs..........................................................................3
1.3
Mixing AVPs and unknown values...............................................................4
2. How to build radius message flow using radClient...............................................6
2.1
Copying existing radius packets..................................................................6
2.2
Building request with AVPs..........................................................................7
2.3
Mixing AVPs and unknown values...............................................................8
3. EAP-Payload/EAP-Message..............................................................................10
3.1
Real time EAP-SIM calculations (A3/A8 algorithm)...................................10
3.2
Real time EAP-AKA calculations (milenage algorithm)..............................10
3.3
Real time EAP-AKA' calculations (milenage algorithm).............................11
4. Bug reports........................................................................................................12
5. Appendix 1 - EAP calculations...........................................................................13
6. Appendix 2 Compiling calc..............................................................................14
7. Appendix 3 External sources & Licences........................................................15

1. How to build diameter message flow using


libDiameter
You can use several aproaches:
1. copy/paste whole diameter packet
2. build every request one AVP at the time
3. do the mixture of previous two. It requires a little fiddling and a bit of
understanding of Diameter protocol. But the result is fast and flexible.

1.1

Copying existing diameter packets

Let's start with copying existing diameter packets. Ladies and gentlemans start
your Wiresharks and open your snoops.
Open the diameter message you want to copy/emulate, and do the right-click on
Diameter message, and Copy the Bytes as Hex-Stream.

In my example, I will copy Capabilities-ExchangeRequest, so the bytes would be


0100008c8000010100000000000000010860000100000108400000167374612e7370
72696e742e636f6d00000000012840000012737072696e742e636f6d000000000101
4000000e00010a1e60c800000000010a4000000c000028af0000010d000000154c61
6e64736c69646548534757000000000001024000000c01000022000001164000000c
4f32a086
So let's build our CER message to include into python
CER='0100008c800001010000000000000001086000010000010840000016737461
2e737072696e742e636f6d00000000012840000012737072696e742e636f6d000000
0001014000000e00010a1e60c800000000010a4000000c000028af0000010d000000
154c616e64736c69646548534757000000000001024000000c010000220000011640
00000c4f32a086'

Note that I just copy-pasted the value from wireshark, put them under quotes and
gave them appropriate name.
Here is simplified and minimalistic example. See example files for more info. Greyed
is the python script.
#!/usr/bin/env python
from diamClient import *
if __name__ == '__main__':
HOST='server'
PORT=3868
Please modify HOST and PORT to proper values (you'll definitely want to change
HOST to your server hostname/IP). Dictionary is not used in this example.
Conn=Connect(HOST,PORT)
# Let's build CER
Please insert newly created CER variable after this. Note that python uses
indentation, so please do it right. Examples in this document are NOT aligned
properly. Please see examples directory or read some python manual. This variable
should be in single line
CER='0100008c8000010100000000000000010860000100000108400000167374
612e737072696e742e636f6d00000000012840000012737072696e742e636f6d0
000000001014000000e00010a1e60c800000000010a4000000c000028af000001
0d000000154c616e64736c69646548534757000000000001024000000c0100002
2000001164000000c4f32a086'
# send data
Conn.send(CER.decode('hex'))
# Receive response
received = Conn.recv(1024)
You now can add in exactly the same way any diameter message. When done,
please close connection.
Conn.close()
And we are done. This is NOT the full message flow, but it showed you how to
quickly build the diameter messages using diamClient and existing snoop from
Wireshark.

1.2

Building request with AVPs

This is rather simple manually add all AVPs to message. Just be sure that ALL
AVPs are in dictionary. Commands and Vendors are defined in dictionary, so make
sure that you DID load the diameter dictionary with command
LoadDictionary("dictDiameter.xml")
Let's build CER (Note: Use your values for ORIGIN_HOST and ORIGIN_REALM):
# Let's build CER

CER_avps=[ ]
CER_avps.append(encodeAVP('Origin-Host', ORIGIN_HOST))
CER_avps.append(encodeAVP('Origin-Realm', ORIGIN_REALM))
CER_avps.append(encodeAVP('Vendor-Id', 11))
CER_avps.append(encodeAVP('Origin-State-Id', 1094807040))
CER_avps.append(encodeAVP('Supported-Vendor-Id', 11))
CER_avps.append(encodeAVP('Acct-Application-Id', 16777265))
# 3GPP SWx=16777265
# Create message header (empty)
CER=HDRItem()
# Set command code
CER.cmd=dictCOMMANDname2code('Capabilities-Exchange')
# Set Hop-by-Hop and End-to-End
initializeHops(CER)
# Add AVPs to header and calculate remaining fields
msg=createReq(CER,CER_avps)
# msg now contains CER Request as hex string
And to send it, we will use exactly the same code as before
# send data
Conn.send(msg.decode('hex'))
# Receive response
received = Conn.recv(1024)
So just replace single line we copied in previous example with these lines, and... you
are done! That way you can quickly start, and replace copied message with one you
can change/adapt.

1.3

Mixing AVPs and unknown values

Sometimes you'll need something in between. E.g I want to change one AVP, but
do not want to touch others in copied message.
Let's show how it can be done using message containing AVP we DO NOT WANT
TO CHANGE, and User-Name WE DO WANT TO CHANGE.
Note: You can copy the RAW values directly from wireshark snoop. Open AVP you
want to copy, right-click on it, and Copy Bytes as Hex-Stream. Remember always
click on TOPMOST level of AVP you want to copy, not on expanded ones.
Once we can see values in wireshark we want to copy/change, we can make any mix
we want to. Capital letter variables are for clarity. Replace them with expected values
# Let's build DER
DER_avps=[]
DER_avps.append(encodeAVP("Origin-Host", ORIGIN_HOST))
DER_avps.append(encodeAVP("Origin-Realm", ORIGIN_REALM))
DER_avps.append(encodeAVP("Session-Id", SESSION_ID))
DER_avps.append(encodeAVP("Destination-Host", DEST_HOST))
DER_avps.append(encodeAVP("Destination-Realm", DEST_REALM))
DER_avps.append(encodeAVP("Origin-State-Id", 1329853127))
DER_avps.append(encodeAVP("User-Name",
"0121112222000623@wlan.mnc023.mcc262.3gppnetwork.org"))
# And now some values I do NOT want to change or understand

DER_avps.append("000001ce40000040020000380130313231313132323232
30303036323340776c616e2e6d6e633032332e6d63633236322e336770706e6
574776f726b2e6f7267")
DER_avps.append("00000408c0000010000028af00000000")
# Just to ilustrate that you can mix it any way you want
DER_avps.append("000005e0c0000010000028af48525044")
DER_avps.append(encodeAVP("Auth-Session-State", 0))
DER_avps.append(encodeAVP("Origin-State-Id", 1321976431))
DER_avps.append(encodeAVP("Calling-Station-Id",
'310006232157383')
DER_avps.append(encodeAVP("Vendor-Id", 10415)
And now proceed with adding header fields and making it the proper Diameter
message
# Create empty message header
DER_H=HDRItem()
# Set command code
DER_H.cmd=getCommandCode('Diameter-EAP')
# Set Hop-by-Hop and End-to-End
initializeHops(DER_H)
# Add AVPs to header and calculate remaining fields
msg=createReq(DER_H,DER_avps)
# msg now contains Diameter-EAP-Request as hex string
Note:
If you have to send Enumerated AVP, use numeric value, not enumerated
string
If you have to send Grouped AVP, see example
# Example for ENUMERATED AVP
# 0-WLAN 1-UTRAN 2-GERAN 2001-HRPD 2003-EHRPD ...
DER_avps.append(encodeAVP("RAT-Type", 0))

# Example for GROUPED AVP


# Grouped AVPs are encoded like this
DER_avps.append(encodeAVP("Vendor-Specific-Application-Id",
encodeAVP("Vendor-Id",dictVENDORid2code('TGPP')),
encodeAVP("Auth-Application-Id",APPLICATION_ID)]))

That is it. Happy coding.

2. How to build radius message flow using radClient


You can use several aproaches:
1. copy/paste whole radius packet
2. build every request one AVP at the time
3. do the mixture of previous two. It requires a little fiddling and a bit of
understanding of Radius protocol. But the result is fast and flexible.

2.1

Copying existing radius packets

Let's start with copying existing radius packets. Ladies and gentlemans start your
Wiresharks and open your snoops.
Open the radius message you want to copy/emulate, and do the right-click on Radius
message, and Copy the Bytes as Hex-Stream.

In my example, I will copy the radius message, so the bytes would be


0b03005616af199ad8906acbc1ab69c2eff86a4c4f0e0102000c170500000d01000050
12989f092fca647c2b5973ed1bfc3a180d1822d51ee1f848bcc9a44973282f625303e4
e9023a9562b8ff9c02f6ddc28d5e00eb
So let's build our message to include into python
MSG="0b03005616af199ad8906acbc1ab69c2eff86a4c4f0e0102000c170500000d01
00005012989f092fca647c2b5973ed1bfc3a180d1822d51ee1f848bcc9a44973282f62
5303e4e9023a9562b8ff9c02f6ddc28d5e00eb"
Note that I just copy-pasted the value from wireshark, put them under quotes and
gave them appropriate name.

Here is simplified and minimalistic example. See example files for more info. Greyed
is the python script.
#!/usr/bin/env python
from radClient import *
if __name__ == '__main__':
HOST='server'
PORT=3868
Please modify HOST and PORT to proper values (you'll definitely want to change
HOST to your server hostname/IP). Dictionary is not used in this example.
Conn=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Let's build message
Please insert newly created variable after this. Note that python uses indentation, so
please do it right. Examples in this document are NOT aligned properly. Please see
examples directory or read some python manual. This variable should be in single
line
msg="0b03005616af199ad8906acbc1ab69c2eff86a4c4f0e0102000c17050000
0d0100005012989f092fca647c2b5973ed1bfc3a180d1822d51ee1f848bcc9a44
973282f625303e4e9023a9562b8ff9c02f6ddc28d5e00eb"
# send data
Conn.sendto(msg.decode("hex"),(HOST,PORT))
# Receive response
received = Conn.recv(1024)
You now can add in exactly the same way any radius message. When done, please
close connection.
Conn.close()
And we are done. This is NOT the full message flow, but it showed you how to
quickly build the radius messages using radClient and existing snoop from
Wireshark.

2.2

Building request with AVPs

This is rather simple manually add all AVPs to message. Just be sure that ALL
AVPs are in dictionary. Commands and Vendors are defined in dictionary, so make
sure that you DID load the radius dictionary with command
LoadDictionary("dictRadius.xml")
Let's build radius message (Note: Use your values for ORIGIN_HOST and
ORIGIN_REALM):
# Let's build message
RES_avps=[]
RES_avps.append(encodeAVP("State", STATE))
RES_avps.append(encodeAVP("Calling-Station-Id",
CALLING_ID))

RES_avps.append(encodeAVP("Called-Station-Id", CALLED_ID))
RES_avps.append(encodeAVP("NAS-Identifier", "default"))
RES_avps.append(encodeAVP("User-Name", "testuser"))
RES_avps.append(encodeAVP("Acct-Session-Id", "a1"))
RES_avps.append(encodeAVP("NAS-IP-Address", NAS_IP))
RES_avps.append(encodeAVP("NAS-Port-Id", NAS_PORT))
# Create message header (empty)
RES=HDRItem()
# Set command code
RES.Code=dictCOMMANDname2code("Access-Request")
RES.Identifier=RID
# Add AVPs to header and calculate remaining fields
msg=createReq(RES,RES_avps)
# msg now contains Response as hex string
And to send it, we will use exactly the same code as before
# send data
Conn. sendto(msg.decode("hex"),(HOST,PORT))
# Receive response
received = Conn.recv(1024)
So just replace single line we copied in previous example with these lines, and... you
are done! That way you can quickly start, and replace copied message with one you
can change/adapt.

2.3

Mixing AVPs and unknown values

Sometimes you'll need something in between. E.g I want to change one AVP, but
do not want to touch others in copied message.
Let's show how it can be done using message containing AVP we DO NOT WANT
TO CHANGE, and User-Name WE DO WANT TO CHANGE.
Note: You can copy the RAW values directly from wireshark snoop. Open AVP you
want to copy, right-click on it, and Copy Bytes as Hex-Stream. Remember always
click on TOPMOST level of AVP you want to copy, not on expanded ones.
Once we can see values in wireshark we want to copy/change, we can make any mix
we want to.
# Let's build RAD
RAD_avps=[]
RAD_avps.append(encodeAVP("User-Name",
"0121112222000623@wlan.mnc023.mcc262.3gppnetwork.org"))
# And now some values I do NOT want to change or understand
RAD_avps.append("000001ce40000040020000380130313231313132323232
30303036323340776c616e2e6d6e633032332e6d63633236322e336770706e6
574776f726b2e6f7267")
RAD_avps.append("00000408c0000010000028af00000000")
# Just to ilustrate that you can mix it any way you want
RAD_avps.append("000005e0c0000010000028af48525044")
RAD_avps.append(encodeAVP("Auth-Session-State", 0))
RAD_avps.append(encodeAVP("Calling-Station-Id",
'310006232157383')

And now proceed with adding header fields and making it the proper Radius
message
# Create message header (empty)
RES=HDRItem()
# Set command code
RES.Code=dictCOMMANDname2code("Access-Request")
RES.Identifier=RID
# Add AVPs to header and calculate remaining fields
msg=createReq(RES,RES_avps)
# msg now contains Response as hex string
Note:
If you have to send Enumerated AVP, use numeric value, not enumerated
string
# Example for ENUMERATED AVP
# 1-Login 2-Framed 17-Authorize only...
DER_avps.append(encodeAVP("Service-Type", 1))
That is it. Happy coding.

3. EAP-Payload/EAP-Message
To be able to create/modify/decode EAP message, import EAP module.
#!/usr/bin/env python
from ???Client import *
import eap
if __name__ == '__main__':
HOST='server'
PORT=3868
LoadDictionary("dict????.xml")
eap.LoadEAPDictionary("dictEAP.xml")
And from now on you can access EAP messages in similar manner.
See included examples for more details.

3.1

Real time EAP-SIM calculations (A3/A8 algorithm)

There is no "standard" algorithm to calculate EAP-SIM values. 3GPP published


COMP128 as private algorithm (available only to members), but there is no
guarantee that your provider will use exact algorithm (it changed over the years due
to security breach). Still it is included for completeness.

3.2

Real time EAP-AKA calculations (milenage algorithm)

If you know OP and K for your subscriber (Operator-Specific Constant and


Subscriber Secret Key), it is possible to dynamically calculate all keys.
See example/eap_AKA_calc.py for example how to do it. Here is only interesting
part:
# Part just to show key calculation
# Calculation of EAP Keys
OP="aaaabbbbccccddddaaaabbbbccccdddd"
K="77777777777777777777777777777777"
Identity="111122223333456@mnc1.mcc2.3gppnetwork.org"
SQN = "000000000001"
AMF = "3333"
# RAND is copied from Challenge
RAND="ce9e2d867cc86dde4cc87899136184d5"
XRES,CK,IK,AK,AKS=eap.aka_calc_milenage(OP,K,RAND)
KENCR,KAUT,MSK,EMSK,MK=eap.aka_calc_keys(Identity,CK,IK)
# AUTN is actually SQN xor AK + AMF + XMAC
params="0x"+OP+" 0x"+K+" 0x"+RAND+" 0x"+SQN+" 0x"+AMF
XMAC,MACS=eap.exec_calc("milenage-f1",params)
# 1) From OP,K,RAND calculate XRES,Ck,Ik (milenage-f2345)
# 2) Using OP,K,RAND,SQN,AMF calculate XMAC, MAC_S
(milenage-f1) to verify AUTN
# 3) From Identity,Ck,Ik calculate keys (aka)
Remember this will only calculate EAP-AKA keys. You still need to know what to do
with them and place proper values it into Diameter message.

3.3

Real time EAP-AKA' calculations (milenage


algorithm)

If you know OP and K for your subscriber (Operator-Specific Constant and


Subscriber Secret Key), it is possible to dynamically calculate all keys.
See example/eap_AKAP_calc.py for example how to do it.
The only difference between AKA and AKA' is fuction for key calculation (will it use
SHA-1 or SHA-256). Here is only interesting part
# Part just to show key calculation
# Calculation of EAP Keys
OP="aaaabbbbccccddddaaaabbbbccccdddd"
K="77777777777777777777777777777777"
Identity="111122223333456@wlan.mnc111.mcc222.3gppnetwork.org"
SQN = "000000000001"
AMF = "3333"
# RAND is copied from Challenge
RAND="ce9e2d867cc86dde4cc87899136184d5"
XRES,CK,IK,AK,AKS=eap.aka_calc_milenage(OP,K,RAND)
params="0x"+OP+" 0x"+K+" 0x"+RAND+" 0x"+SQN+" 0x"+AMF
XMAC,MACS=eap.exec_calc("milenage-f1",params)
KENCR,KAUT,MSK,EMSK,KRE=eap.akap_calc_keys(Identity,CK,IK)
Remember this will only calculate EAP-AKA' keys. You still need to know what to
do with them and place proper values it into Diameter message.

4. Bug reports
Please send any changes, enhancements, fixes, bug reports and successful
examples to my email. If you like this tool, a small donation might encourage me to
extend it a bit further.

5. Appendix 1 - EAP calculations


For all calculations, external C program is used. If you run it without any parameters,
it will show you usage examples. Prefix 0x indicate that value should be HEX
encoded with 0x at the beginning.
aka <Identity> <0xCk> <0xIk>
akaprime <Identity> <0xCk> <0xIk>
hmac1 <0xK_aut> <0xMSG>
hmac256 <0xK_aut> <0xMSG>
milenage-f2345 <0xOP> <0xK> <0xRAND>
milenage-f1 <0xOP> <0xK> <0xRAND> <0xSQN> <0xAMF>
encode <0xIV> <0xK_encr> <0xMSG>
decode <0xIV> <0xK_encr> <0xMSG>
Output of aka: MK, PRF, KENCR, KAUT, MSK, EMSK
Output of akaprime: KENCR, KAUT, KRE, MSK, EMSK
Output of hmac1 or hmac256: MAC
Output of milenage-f2345: XRES, CK, IK, AK, AK*
Output of milenage-f1: XMAC, MACS
Output of encode: encrypted value to be placed in AT_ENCR_DATA
Output of decode: decrypted AVPs

6. Appendix 2 Compiling calc


The source for all calculations is included. To compile it, use enclosed
calc_compile.sh. It should be possible to compile it on other platforms as well (with
probably some changes due to endianness). All supporting functions for calc.c were
taken from hostapd source (http://w1.fi/hostapd/ ), so if you are wandering about it
take a look at the original sources
Compilation was tested:
On Linux with gcc
On Intel-based Solaris 10 with gcc
On Windows XP with MinGW (www.mingw.org)
To verify calculations for newly built application, included is calc_test.sh script. It
should report PASS or FAIL for all supported calculations.

7. Appendix 3 External sources & Licences


PyPS itself is available under the terms of BSD licence. See README for more
details.
EAP Calculations are performed using code from hostapd
Copyright (c) 2002-2012, Jouni Malinen <j@w1.fi> and contributors.
http://hostap.epitest.fi/hostapd/
This software may be distributed, used, and modified under the terms of BSD
license. See README for more details.
Dictionary contains data from Wireshark dictionary
http://www.wireshark.org
Wireshark is available under the GNU General Public License version 2.
COMP128 A3/A8 Algorithm implementation
Copyright 1998, Marc Briceno, Ian Goldberg, and David Wagner.
http://www.scard.org/gsm

Vous aimerez peut-être aussi