diff --git a/tools/nasparse.py b/tools/nasparse.py new file mode 100755 index 0000000..11cc8ad --- /dev/null +++ b/tools/nasparse.py @@ -0,0 +1,60 @@ +#!/usr/bin/python3 +import pycrate_mobile +from pycrate_mobile import NASLTE +import pycrate_core +import binascii +import sys +import pprint +from enum import Enum + +import pycrate_mobile.TS24301_EMM + +EPS_IMSI_ATTACH = 2 + +def parse_nas_message(buffer, uplink=None): + if isinstance(buffer, str): #handle string argument or raw bytes + bin = binascii.unhexlify(buffer) + else: + bin = buffer + if uplink: + parsed = NASLTE.parse_NASLTE_MO(bin) + elif uplink == None: #We don't know if its an up or downlink + parsed = NASLTE.parse_NASLTE_MO(bin) + if parsed[0] == None: + parsed = NASLTE.parse_NASLTE_MT(bin) + else: + parsed = NASLTE.parse_NASLTE_MT(bin) + + if parsed[0] is None: # Not a NAS Packet + raise TypeError("Not a nas packet") + return parsed[0] + +def heur_ue_imsi_sent(msg): + output = "device transmitted IMSI to base station!" + + if type(msg) not in [pycrate_mobile.TS24301_EMM.EMMAttachRequest, pycrate_mobile.TS24301_EMM.EMMSecProtNASMessage]: + return (False, None) + + if isinstance(msg, pycrate_mobile.TS24301_EMM.EMMSecProtNASMessage): + try: + msg = msg['EMMAttachRequest'] + except pycrate_core.elt.EltErr: + return (False, None) + + if msg['EPSAttachType']['V'].to_int() == EPS_IMSI_ATTACH: #EPSAttachType Value is 'Combined EPS/IMSI Attach (2)' + return (True, output) + return (False, None) + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("usage: nasparse.py [hex encoded nas message]") + exit(1) + + buffer = sys.argv[1] + msg = parse_nas_message(buffer) + pprint.pprint(msg) + triggered, message = heur_ue_imsi_sent(msg) + if triggered: + print(message) + exit(1) \ No newline at end of file diff --git a/tools/nasparse_test.py b/tools/nasparse_test.py new file mode 100644 index 0000000..bba24be --- /dev/null +++ b/tools/nasparse_test.py @@ -0,0 +1,38 @@ +#!/usr/bin/python3 +import unittest +import nasparse + + +class TestNasparse(unittest.TestCase): + imsi_sent_msg = '07412208391185184409309005f0700000100030023ed031d127298080211001000010810600000000830600000000000d00000300ff0003130184000a000005000010005c0a009011034f18a6f15d0103c1000000000000' + sec_imsi_sent_msg = '1727db4b7c0207412208391185184409309005f0700000100030023ed031d127298080211001000010810600000000830600000000000d00000300ff0003130184000a000005000010005c0a009011034f18a6f15d0103c1' + non_nas_msg = 'deadbeefcafe' + other_nas_msg = '074413780004023fd121' + other_nas_mt_msg = "023fd12100000000000000000000000000000000000000000000000000000000" + ciphered_nas_msg = "27ed6146bd0162a5d62d62e1ce501720dc8bd84f1167fd" + + def run_heur(self, msg): + buf = nasparse.parse_nas_message(msg) + return nasparse.heur_ue_imsi_sent(buf)[0] + + def test_imsi_sent(self): + self.assertEqual(self.run_heur(self.imsi_sent_msg), True, "imsi_sent_msg should trigger heuristic") + + def test_sec_imsi_sent(self): + self.assertEqual(self.run_heur(self.imsi_sent_msg), True, "sec_imsi_sent_msg should trigger heuristic") + + def test_non_nas_msg(self): + with self.assertRaises(TypeError): + self.run_heur(self.non_nas_msg) + + def test_other_nas(self): + self.assertEqual(self.run_heur(self.other_nas_msg), False, "other_nas_msg should not trigger heuristic") + + def test_other_nas_mt(self): + self.assertEqual(self.run_heur(self.other_nas_mt_msg), False, "other_nas_mt_msg should not trigger heuristic") + + def test_ciphered_nas(self): + self.assertEqual(self.run_heur(self.ciphered_nas_msg), False, "ciphered_nas_msg should not trigger heuristic") + +if __name__ == '__main__': + unittest.main() diff --git a/tools/pcap_check.py b/tools/pcap_check.py new file mode 100755 index 0000000..70ec5fa --- /dev/null +++ b/tools/pcap_check.py @@ -0,0 +1,38 @@ +#!/usr/bin/python3 +import nasparse +from scapy.utils import RawPcapNgReader +import sys + +TYPE_LTE_NAS = 0x12 +UDP_LEN = 28 + +def process_pcap(pcap_path): + print('Opening {}...'.format(pcap_path)) + + count = 0 + for pkt_data, pkt_metadata in RawPcapNgReader(pcap_path): + count += 1 + gsmtap_len = pkt_data[UDP_LEN+1] * 4 # gsmtap header length is stored in the 2nd byte of GSMTAP as a number of 32 bit words + header_end = gsmtap_len + UDP_LEN #length of UDP/IP header plus GSMTAP header + + gsmtap_hdr = pkt_data[UDP_LEN:header_end] + + if gsmtap_hdr[2] != TYPE_LTE_NAS: + continue + + # uplink status is the 7th bit of the 5th byte of the GSMTAP header. + # Uplink (Mobile originated) = 0 Downlink (mobile terminated) = 1 + uplink = (gsmtap_hdr[4] & 0b01000000) >> 6 + buffer = pkt_data[header_end:] + msg = nasparse.parse_nas_message(buffer, uplink) + triggered, message = nasparse.heur_ue_imsi_sent(msg) + if triggered: + print(f"Frame {count} triggered heuristic: {message}") + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("usage: pcap_check.py [path/to/pcap/file]") + exit(1) + + pcap_path = sys.argv[1] + process_pcap(pcap_path) \ No newline at end of file