LimeSDR experiment tutorial (14) GSM sniffing

In addition to realizing private GSM base stations, LimeSDR can also realize GSM sniffing (operator base stations).

https://github.com/ptrkrysik/gr-gsm

The software package used is called gr-gsm, which is also based on gnuradio, and its structure and usage are a bit similar to our previous use of limesdr to sniff wifi packets. Because it is based on gnuradio, it has a relatively high hardware compatibility. As long as the hardware parameters meet the requirements, you can use this package with various sdr hardware. All you have to do is replace the source it calls with you The source of the device on hand, if gr-osmosdr is used, can even directly support limesdr. (You have to install the new version of gr-osmosdr and soapy, as well as limesuite) If you don't want to install so many middleware, you can also directly modify its .py file and replace the source with the source provided by gr-limesdr.

In addition, we also need to use a package called kalibrate.

https://github.com/scateu/kalibrate-hackrf

The function of this program is similar to the lte-cell-scanner program introduced before, except that it is looking for gsm base stations instead of lte base stations. It will output the frequency points of the gsm base stations near you, so you know which frequency points around have gsm base stations, and you can use gr-gsm to demodulate its information in a targeted manner. If you already know the base station data on which frequency you want to analyze in advance, you don't need this program.

There are several versions of this program, which can support rtlsdr and hackrf. Because it is implemented by directly calling the hardware api and not based on gnuradio, it cannot be used directly by limesdr without extensive modification. Because I have portapack on hand, I use kalibrate which supports hackrf.

git clone https://github.com/scateu/kalibrate-hackrf
cd kalibrate-hackrf
./bootstrap && CXXFLAGS='-W -Wall -O3' ./configure && make
cd src
./kal -s GSM900

The last sentence./kal -s GSM900 represents the type of gsm base station to be searched. It can be replaced with other types and can be viewed with the kal -h command.

I searched, and the base station information near me is as follows:

GSM-900:
    chan:   65 (948.0MHz + 39.769kHz)    power:  195129.78
    chan:   66 (948.2MHz + 12.669kHz)    power:  195200.97
    chan:   67 (948.4MHz + 9.453kHz)    power:  195570.60
E-GSM-900:
    chan:   42 (943.4MHz + 31.281kHz)    power: 1364627.49
    chan:   43 (943.6MHz + 37.942kHz)    power: 1453469.82
    chan:   44 (943.8MHz + 11.674kHz)    power: 1535186.90
    chan:   45 (944.0MHz - 18.588kHz)    power: 1562454.86
    chan:   46 (944.2MHz - 39.050kHz)    power: 1582645.60
 

With the base station information, we can start to install and use gr-gsm.

Installation reference

https://github.com/ptrkrysik/gr-gsm/wiki/Installation-on-RaspberryPi-3

Note that I chose the installation steps of Raspberry Pi 3, which can also be used on the computer, because I don't like to use Pybombs so I didn't use the computer installation method they provided.

I extracted part of the installation of dependent libraries. I installed all of these apt installations, but I did not compile and install them because there are already many libraries on my computer. I am afraid there will be conflicts. If you are using a clean system, you You can follow the steps on its page.

sudo apt-get install gnuradio gnuradio-dev
sudo apt-get install cmake
sudo apt-get install build-essential libtool shtool autoconf automake git-core pkg-config make gcc
sudo apt-get install libpcsclite-dev libtalloc-dev gnutls-dev libsctp-dev

After installing the dependent libraries, you can try to compile gr-gsm. If you are prompted for missing something during compiling or cmake, you can make up.

git clone https://github.com/ptrkrysik/gr-gsm.git
cd gr-gsm
mkdir build
cd build
cmake ..
make
sudo make install
sudo ldconfig

Then you can run grgsm_livemon

sudo grgsm_livemon

This will pop up a window based on gnuradio, you can enter the base station frequency found by kalibrate in frequency, pay attention to whether there is a crest in the spectrogram below, if the crest is off the center, you need to click the button to fine tune it to the middle. After reaching the middle, command There will be a lot of hexadecimal data jump out on the line.

Run at this time

sudo wireshark

Then select loopback:lo to see a lot of gsm packets, wireshark will convert the data in the command line into readable data.

You can also passively sniff imsi in the following way

First install tshark

sudo apt install tshark

Then open two terminals and run the following two lines of commands respectively. 

sudo grgsm_livemon -p 35 -f 948M

sudo tshark -i lo -Y "e212.imsi" -V 2>&1 | sed 's/^[ \t]*//;s/[ \t]*$//' 2>&1 | grep "IMSI:"

You can see the imsi of the surrounding phones.

In addition, there are grgsm_capture and grgsm_decode are also very useful things, you can get more information.

Reference: https://github.com/ptrkrysik/gr-gsm/wiki/Usage:-Decoding-How-To

(Do not use the above for illegal purposes, you will be responsible for the consequences)

Video: https://www.bilibili.com/video/BV1Zt4y117NU

If you cannot run normally, it may be because the versions of soapy, gnuradio and osmosdr are not compatible. You can test a simple flow graph in gnuradio-companion to see if you can use osmosdr source to receive data from limesdr. If not, then you can only use gr-limesdr instead of gr-osmosdr.

I changed grgsm_livemon.py to use the gr-limesdr package. If you want to use grgsm_capture, you can also refer to this method.

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
##################################################
# GNU Radio Python Flow Graph
# Title: Gr-gsm Livemon
# Author: Piotr Krysik
# Description: Interactive monitor of a single C0 channel with analysis performed by Wireshark (command to run wireshark: sudo wireshark -k -f udp -Y gsmtap -i lo)
# GNU Radio version: 3.7.13.5
##################################################

if __name__ == '__main__':
    import ctypes
    import sys
    if sys.platform.startswith('linux'):
        try:
            x11 = ctypes.cdll.LoadLibrary('libX11.so')
            x11.XInitThreads()
        except:
            print "Warning: failed to XInitThreads()"

from PyQt4 import Qt
from gnuradio import blocks
from gnuradio import eng_notation
from gnuradio import gr
from gnuradio import qtgui
from gnuradio.eng_option import eng_option
from gnuradio.filter import firdes
from gnuradio.qtgui import Range, RangeWidget
from grgsm import arfcn
from math import pi
from optparse import OptionParser
import grgsm
#import osmosdr
import limesdr
import pmt
import sip
import sys
import time
from gnuradio import qtgui


class grgsm_livemon(gr.top_block, Qt.QWidget):

    def __init__(self, args="", collector='localhost', collectorport='4729', fc=941.8e6, gain=30, osr=4, ppm=0, samp_rate=2000000.052982, serverport='4729', shiftoff=400e3):
        gr.top_block.__init__(self, "Gr-gsm Livemon")
        Qt.QWidget.__init__(self)
        self.setWindowTitle("Gr-gsm Livemon")
        qtgui.util.check_set_qss()
        try:
            self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))
        except:
            pass
        self.top_scroll_layout = Qt.QVBoxLayout()
        self.setLayout(self.top_scroll_layout)
        self.top_scroll = Qt.QScrollArea()
        self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)
        self.top_scroll_layout.addWidget(self.top_scroll)
        self.top_scroll.setWidgetResizable(True)
        self.top_widget = Qt.QWidget()
        self.top_scroll.setWidget(self.top_widget)
        self.top_layout = Qt.QVBoxLayout(self.top_widget)
        self.top_grid_layout = Qt.QGridLayout()
        self.top_layout.addLayout(self.top_grid_layout)

        self.settings = Qt.QSettings("GNU Radio", "grgsm_livemon")
        self.restoreGeometry(self.settings.value("geometry").toByteArray())


        ##################################################
        # Parameters
        ##################################################
        self.args = args
        self.collector = collector
        self.collectorport = collectorport
        self.fc = fc
        self.gain = gain
        self.osr = osr
        self.ppm = ppm
        self.samp_rate = samp_rate
        self.serverport = serverport
        self.shiftoff = shiftoff

        ##################################################
        # Variables
        ##################################################
        self.ppm_slider = ppm_slider = ppm
        self.gain_slider = gain_slider = gain
        self.fc_slider = fc_slider = fc

        ##################################################
        # Blocks
        ##################################################
        self._ppm_slider_range = Range(-150, 150, 0.1, ppm, 100)
        self._ppm_slider_win = RangeWidget(self._ppm_slider_range, self.set_ppm_slider, 'PPM Offset', "counter", float)
        self.top_grid_layout.addWidget(self._ppm_slider_win)
        self._gain_slider_range = Range(0, 100, 0.5, gain, 100)
        self._gain_slider_win = RangeWidget(self._gain_slider_range, self.set_gain_slider, 'Gain', "counter", float)
        self.top_grid_layout.addWidget(self._gain_slider_win)
        self._fc_slider_range = Range(800e6, 1990e6, 2e5, fc, 100)
        self._fc_slider_win = RangeWidget(self._fc_slider_range, self.set_fc_slider, 'Frequency', "counter_slider", float)
        self.top_grid_layout.addWidget(self._fc_slider_win)

        #self.limesdr_source_0 = limesdr.source('00090726074E3A26', 0, '')



        #self.limesdr_source_0.set_antenna(2,0)



        #self.rtlsdr_source_0 = osmosdr.source( args="numchan=" + str(1) + " " + str(grgsm.device.get_default_args(args)) )
        self.rtlsdr_source_0 = limesdr.source( '', 0, '' )
        self.rtlsdr_source_0.set_sample_rate(samp_rate)
        self.rtlsdr_source_0.set_center_freq(fc_slider-shiftoff, 0)
        #self.rtlsdr_source_0.set_freq_corr(ppm_slider, 0)
        #self.rtlsdr_source_0.set_dc_offset_mode(2, 0)
        #self.rtlsdr_source_0.set_iq_balance_mode(2, 0)
        #self.rtlsdr_source_0.set_gain_mode(False, 0)
        #self.rtlsdr_source_0.set_gain(gain_slider, 0)
        self.rtlsdr_source_0.set_gain(30,0)
        #self.rtlsdr_source_0.set_if_gain(20, 0)
        #self.rtlsdr_source_0.set_bb_gain(20, 0)
        #self.rtlsdr_source_0.set_antenna('', 0)
        self.rtlsdr_source_0.set_bandwidth(250e3+abs(shiftoff), 0)
        self.rtlsdr_source_0.calibrate(samp_rate, 0)

        self.qtgui_freq_sink_x_0 = qtgui.freq_sink_c(
        	1024, #size
        	firdes.WIN_BLACKMAN_hARRIS, #wintype
        	fc_slider, #fc
        	samp_rate, #bw
        	"", #name
        	1 #number of inputs
        )
        self.qtgui_freq_sink_x_0.set_update_time(0.10)
        self.qtgui_freq_sink_x_0.set_y_axis(-140, 10)
        self.qtgui_freq_sink_x_0.set_y_label('Relative Gain', 'dB')
        self.qtgui_freq_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, 0.0, 0, "")
        self.qtgui_freq_sink_x_0.enable_autoscale(False)
        self.qtgui_freq_sink_x_0.enable_grid(False)
        self.qtgui_freq_sink_x_0.set_fft_average(1.0)
        self.qtgui_freq_sink_x_0.enable_axis_labels(True)
        self.qtgui_freq_sink_x_0.enable_control_panel(False)

        if not True:
          self.qtgui_freq_sink_x_0.disable_legend()

        if "complex" == "float" or "complex" == "msg_float":
          self.qtgui_freq_sink_x_0.set_plot_pos_half(not True)

        labels = ['', '', '', '', '',
                  '', '', '', '', '']
        widths = [1, 1, 1, 1, 1,
                  1, 1, 1, 1, 1]
        colors = ["blue", "red", "green", "black", "cyan",
                  "magenta", "yellow", "dark red", "dark green", "dark blue"]
        alphas = [1.0, 1.0, 1.0, 1.0, 1.0,
                  1.0, 1.0, 1.0, 1.0, 1.0]
        for i in xrange(1):
            if len(labels[i]) == 0:
                self.qtgui_freq_sink_x_0.set_line_label(i, "Data {0}".format(i))
            else:
                self.qtgui_freq_sink_x_0.set_line_label(i, labels[i])
            self.qtgui_freq_sink_x_0.set_line_width(i, widths[i])
            self.qtgui_freq_sink_x_0.set_line_color(i, colors[i])
            self.qtgui_freq_sink_x_0.set_line_alpha(i, alphas[i])

        self._qtgui_freq_sink_x_0_win = sip.wrapinstance(self.qtgui_freq_sink_x_0.pyqwidget(), Qt.QWidget)
        self.top_grid_layout.addWidget(self._qtgui_freq_sink_x_0_win)
        self.gsm_sdcch8_demapper_0 = grgsm.gsm_sdcch8_demapper(
            timeslot_nr=1,
        )
        self.gsm_receiver_0 = grgsm.receiver(osr, ([arfcn.downlink2arfcn(fc)]), ([]), False)
        self.gsm_message_printer_1 = grgsm.message_printer(pmt.intern(""), False,
            False, False)
        self.gsm_input_0 = grgsm.gsm_input(
            ppm=ppm-int(ppm),
            osr=osr,
            fc=fc_slider-shiftoff,
            samp_rate_in=samp_rate,
        )
        self.gsm_decryption_0 = grgsm.decryption(([]), 1)
        self.gsm_control_channels_decoder_0_0 = grgsm.control_channels_decoder()
        self.gsm_control_channels_decoder_0 = grgsm.control_channels_decoder()
        self.gsm_clock_offset_control_0 = grgsm.clock_offset_control(fc_slider-shiftoff, samp_rate, osr)
        self.gsm_bcch_ccch_demapper_0 = grgsm.gsm_bcch_ccch_demapper(
            timeslot_nr=0,
        )
        self.blocks_socket_pdu_0_1 = blocks.socket_pdu("UDP_CLIENT", collector, collectorport, 1500, False)
        self.blocks_socket_pdu_0_0 = blocks.socket_pdu("UDP_SERVER", '127.0.0.1', serverport, 10000, False)
        self.blocks_rotator_cc_0 = blocks.rotator_cc(-2*pi*shiftoff/samp_rate)



        ##################################################
        # Connections
        ##################################################
        self.msg_connect((self.blocks_socket_pdu_0_0, 'pdus'), (self.gsm_message_printer_1, 'msgs'))
        self.msg_connect((self.gsm_bcch_ccch_demapper_0, 'bursts'), (self.gsm_control_channels_decoder_0, 'bursts'))
        self.msg_connect((self.gsm_clock_offset_control_0, 'ctrl'), (self.gsm_input_0, 'ctrl_in'))
        self.msg_connect((self.gsm_control_channels_decoder_0, 'msgs'), (self.blocks_socket_pdu_0_1, 'pdus'))
        self.msg_connect((self.gsm_control_channels_decoder_0_0, 'msgs'), (self.blocks_socket_pdu_0_1, 'pdus'))
        self.msg_connect((self.gsm_decryption_0, 'bursts'), (self.gsm_control_channels_decoder_0_0, 'bursts'))
        self.msg_connect((self.gsm_receiver_0, 'C0'), (self.gsm_bcch_ccch_demapper_0, 'bursts'))
        self.msg_connect((self.gsm_receiver_0, 'measurements'), (self.gsm_clock_offset_control_0, 'measurements'))
        self.msg_connect((self.gsm_receiver_0, 'C0'), (self.gsm_sdcch8_demapper_0, 'bursts'))
        self.msg_connect((self.gsm_sdcch8_demapper_0, 'bursts'), (self.gsm_decryption_0, 'bursts'))
        self.connect((self.blocks_rotator_cc_0, 0), (self.gsm_input_0, 0))
        self.connect((self.blocks_rotator_cc_0, 0), (self.qtgui_freq_sink_x_0, 0))
        self.connect((self.gsm_input_0, 0), (self.gsm_receiver_0, 0))
        self.connect((self.rtlsdr_source_0, 0), (self.blocks_rotator_cc_0, 0))

    def closeEvent(self, event):
        self.settings = Qt.QSettings("GNU Radio", "grgsm_livemon")
        self.settings.setValue("geometry", self.saveGeometry())
        event.accept()

    def get_args(self):
        return self.args

    def set_args(self, args):
        self.args = args

    def get_collector(self):
        return self.collector

    def set_collector(self, collector):
        self.collector = collector

    def get_collectorport(self):
        return self.collectorport

    def set_collectorport(self, collectorport):
        self.collectorport = collectorport

    def get_fc(self):
        return self.fc

    def set_fc(self, fc):
        self.fc = fc
        self.set_fc_slider(self.fc)

    def get_gain(self):
        return self.gain

    def set_gain(self, gain):
        self.gain = gain
        self.set_gain_slider(self.gain)

    def get_osr(self):
        return self.osr

    def set_osr(self, osr):
        self.osr = osr
        self.gsm_input_0.set_osr(self.osr)

    def get_ppm(self):
        return self.ppm

    def set_ppm(self, ppm):
        self.ppm = ppm
        self.set_ppm_slider(self.ppm)
        self.gsm_input_0.set_ppm(self.ppm-int(self.ppm))

    def get_samp_rate(self):
        return self.samp_rate

    def set_samp_rate(self, samp_rate):
        self.samp_rate = samp_rate
        self.rtlsdr_source_0.set_sample_rate(self.samp_rate)
        self.qtgui_freq_sink_x_0.set_frequency_range(self.fc_slider, self.samp_rate)
        self.gsm_input_0.set_samp_rate_in(self.samp_rate)
        self.blocks_rotator_cc_0.set_phase_inc(-2*pi*self.shiftoff/self.samp_rate)

    def get_serverport(self):
        return self.serverport

    def set_serverport(self, serverport):
        self.serverport = serverport

    def get_shiftoff(self):
        return self.shiftoff

    def set_shiftoff(self, shiftoff):
        self.shiftoff = shiftoff
        self.rtlsdr_source_0.set_center_freq(self.fc_slider-self.shiftoff, 0)
        self.rtlsdr_source_0.set_bandwidth(250e3+abs(self.shiftoff), 0)
        self.gsm_input_0.set_fc(self.fc_slider-self.shiftoff)
        self.gsm_clock_offset_control_0.set_fc(self.fc_slider-self.shiftoff)
        self.blocks_rotator_cc_0.set_phase_inc(-2*pi*self.shiftoff/self.samp_rate)

    def get_ppm_slider(self):
        return self.ppm_slider

    def set_ppm_slider(self, ppm_slider):
        self.ppm_slider = ppm_slider
        self.rtlsdr_source_0.set_freq_corr(self.ppm_slider, 0)

    def get_gain_slider(self):
        return self.gain_slider

    def set_gain_slider(self, gain_slider):
        self.gain_slider = gain_slider
        self.rtlsdr_source_0.set_gain(self.gain_slider, 0)

    def get_fc_slider(self):
        return self.fc_slider

    def set_fc_slider(self, fc_slider):
        self.fc_slider = fc_slider
        self.rtlsdr_source_0.set_center_freq(self.fc_slider-self.shiftoff, 0)
        self.qtgui_freq_sink_x_0.set_frequency_range(self.fc_slider, self.samp_rate)
        self.gsm_input_0.set_fc(self.fc_slider-self.shiftoff)
        self.gsm_clock_offset_control_0.set_fc(self.fc_slider-self.shiftoff)


def argument_parser():
    description = 'Interactive monitor of a single C0 channel with analysis performed by Wireshark (command to run wireshark: sudo wireshark -k -f udp -Y gsmtap -i lo)'
    parser = OptionParser(usage="%prog: [options]", option_class=eng_option, description=description)
    parser.add_option(
        "", "--args", dest="args", type="string", default="",
        help="Set Device Arguments [default=%default]")
    parser.add_option(
        "", "--collector", dest="collector", type="string", default='localhost',
        help="Set IP or DNS name of collector point [default=%default]")
    parser.add_option(
        "", "--collectorport", dest="collectorport", type="string", default='4729',
        help="Set UDP port number of collector [default=%default]")
    parser.add_option(
        "-f", "--fc", dest="fc", type="eng_float", default=eng_notation.num_to_str(941.8e6),
        help="Set GSM channel's central frequency [default=%default]")
    parser.add_option(
        "-g", "--gain", dest="gain", type="eng_float", default=eng_notation.num_to_str(30),
        help="Set gain [default=%default]")
    parser.add_option(
        "", "--osr", dest="osr", type="intx", default=4,
        help="Set OverSampling Ratio [default=%default]")
    parser.add_option(
        "-p", "--ppm", dest="ppm", type="eng_float", default=eng_notation.num_to_str(0),
        help="Set ppm [default=%default]")
    parser.add_option(
        "-s", "--samp-rate", dest="samp_rate", type="eng_float", default=eng_notation.num_to_str(2000000.052982),
        help="Set samp_rate [default=%default]")
    parser.add_option(
        "", "--serverport", dest="serverport", type="string", default='4729',
        help="Set UDP server listening port [default=%default]")
    parser.add_option(
        "-o", "--shiftoff", dest="shiftoff", type="eng_float", default=eng_notation.num_to_str(400e3),
        help="Set Frequency Shiftoff [default=%default]")
    return parser


def main(top_block_cls=grgsm_livemon, options=None):
    if options is None:
        options, _ = argument_parser().parse_args()

    from distutils.version import StrictVersion
    if StrictVersion(Qt.qVersion()) >= StrictVersion("4.5.0"):
        style = gr.prefs().get_string('qtgui', 'style', 'raster')
        Qt.QApplication.setGraphicsSystem(style)
    qapp = Qt.QApplication(sys.argv)

    tb = top_block_cls(args=options.args, collector=options.collector, collectorport=options.collectorport, fc=options.fc, gain=options.gain, osr=options.osr, ppm=options.ppm, samp_rate=options.samp_rate, serverport=options.serverport, shiftoff=options.shiftoff)
    tb.start()
    tb.show()

    def quitting():
        tb.stop()
        tb.wait()
    qapp.connect(qapp, Qt.SIGNAL("aboutToQuit()"), quitting)
    qapp.exec_()


if __name__ == '__main__':
    main()

 

Guess you like

Origin blog.csdn.net/shukebeta008/article/details/105457331