GSM Sniffing入門之軟件篇:GSMTAP抓取與SMS(Short Message Service)


重點介紹如何利用50元左右的設備,抓包並還原SMS短信內容:

ps:研究GSM Sniffing純屬個人興趣,能抓SMS報文只是撿了個明文傳輸的漏子,切勿用於非法用途。就像sylvain說的,osmocomBB並不是為抓包而實現的,如果沒有足夠的GSM相關知識,想實現還原語音通話內容根本就無從下手。

---------------------------------------------------------------------------------------------------

第二部分-軟件篇:GSMTAP抓取與SMS(Short Message Service)還原

之前介紹了OsmocomBB的硬件與刷機,這里重點介紹下其附帶軟件的使用。

參考官方wiki可以知道osmocomBB的代碼可以分為兩種:一種是在手機基帶芯片上跑的layer1(物理傳輸層);另一種是在PC上跑的與layer1通信,提供上層服務的程序:

代碼:
[root@ArchDev ~]# cd osmocom-bb/src/
[root@ArchDev src]# ls
Makefile  README.building  README.development  host  shared  target  target_dsp  wireshark

target下就是針對各手機的固件,bin位於target/firmware/board/compal_e88下。Baseband firmware一節介紹了不同固件的功能和對應程序,*.compalram是軟刷用的,斷電后需要重新刷機。*.e88flash/*.e88loader是配合loader使用的,刷入前需要參考 http://bb.osmocom.org/trac/wiki/flashing_new 把loader寫到手機中,然后在手機上用loader運行。
后面cell_log和ccch_scan都是對應layer1的,因為直接寫入有一定危險性,本文只演示軟刷(layer1.compalram)的使用方法。
回到src目錄下,接着看PC側的工具:

代碼:
[root@ArchDev ~]# cd ~/osmocom-bb/src/
[root@ArchDev host]# ls
calypso_pll  fb_tools  gsmmap  layer23  osmocon  rita_pll

osmocon是刷入固件,並與固件通信的程序,使用方法(注意C118選compal_e88/layer1.compalram.bin這個固件):

代碼:
$ cd host/osmocon/
$ ./osmocon -p /dev/ttyUSB0 -m c123xor ../../target/firmware/board/compal_e88/layer1.compalram.bin

將C118關機后,短按電源鍵就開始運行了。刷機過程和常見問題硬件篇都已經提過,這里不再詳述。

layer23下,有實現不同功能的數據鏈路層/網絡層程序,比如模擬手機功能的mobile(接入網絡需要SIM卡),以及抓取相關信息的雜項程序。直接進入misc目錄:

代碼:
cd layer23/src/misc/

cell_log是一個掃描有效運營商頻率,並收集BCCH上基本信息的工具,我們先用它來獲取運營商的ARFCN、MNC和MCC等信息。這里不需要gprs數據,直接使用這個參數:

代碼:
-O --only-scan  Do a scan and show available ARFCNs, no data logging

./cell_log --only-scan
...
<000e> cell_log.c:248 Cell: ARFCN=56 PWR=-67dB MCC=460 MNC=00 (China, China Mobile)

例如這里選取信號最強的ARFCN=56 (China Mobile),有了這個就可以開始抓取Common Control Channel (CCCH)了:

代碼:
./ccch_scan -a 56 -i 127.0.0.1

看到ccch_scan開始輸出burst內容后,就可以

代碼:
sudo wireshark -k -i lo -f 'port 4729'

打開Wireshark來抓GSMTAP,設置 gsm_sms 過濾器即可看到SMS報文內容:

-------------------------------------------------------------------------

為了加深對SMS傳輸的理解,我寫了個Python腳本來重組短信的PDU。
下面部分需要些GSM網絡相關的知識,推薦 GSM network and services 2G1723 2006 

從協議圖中得知,移動設備(MS)和基站(BTS)間使用Um接口,最底層就是刷入手機的layer1物理傳輸層,之上分別是layer2數據鏈路層layer3網絡層

位於圖中layer2的LAPDm,是一種保證數據傳輸不會出錯的協議。一個LAPDm幀共有23個字節(184個比特),提供分片管理控制等功能:

layer3的協議則可以分為RR/MM/CM三種,這里只列出嗅探相關的功能:

代碼:
RR(Radio Resource Management):channel, cell控制等信息,可以忽略
MM(Mobility Management):Location updating(如果需要接收方號碼,需要關注這個動作)
CM(Connection Management):Call Control(語音通話時的控制信息,可以知道何時開始捕獲TCH), SMS(這里的重點)

參考GSM的文檔 TS 04.06 得知 LAPDm 的Address field字段中,定義了 3.3.3 Service access point identifier (SAPI)

代碼:

SAPI value  Related entity 
0  Call control signalling, mobility management signalling and radio resource management signalling
3  Short message service

SAPI=3就是我們要的Short message service,如圖:

3gpp的GSM文檔看得比較暈,這里直接對照Wireshark里的gsm_sms報文分析,發現SMS幀實際是重組LAPDm的payload得到的。也就說如果想自己處理SMS幀,就必須也和Wireshark一樣重組LAPDm的payload,並解析其中的SMS PDU。

這是一個SAPI=3的LAPDm報文頭部。GSMTAP是一種偽頭部http://bb.osmocom.org/trac/wiki/GSMTAP,記錄了burst的一些基本信息(如ChannelType,ARFCN,上行還是下行等)。因為是用ccch_scan捕獲的流量,編碼時只用關注 Channel Type: SDCCH/8 的LADPm協議。
為了方便訪問,定義GSMTAP類如下,傳入udp payload部分,解析GSMTAP並提供其后的數據:

代碼:
class GSMTAP:
  def __init__(self, gsmtap):
    self.gsmtap = gsmtap

    setattr(self, "version", ord(gsmtap[0]))
    setattr(self, "hdr_len", ord(gsmtap[1]) << 2)
    setattr(self, "payload_type", ord(gsmtap[2]))

    setattr(self, "time_slot", ord(gsmtap[3]))
    ARFCN = (ord(gsmtap[4])&0x3F)*0x100 + ord(gsmtap[5])
    UPLINK = ord(gsmtap[4]) >> 6
    setattr(self, "arfcn", ARFCN)
    setattr(self, "link", UPLINK)

    setattr(self, "signal_noise", ord(gsmtap[6]))
    setattr(self, "signal_level", ord(gsmtap[7]))
    # GSM Frame Number
    setattr(self, "channel_type", ord(gsmtap[12]))
    setattr(self, "antenna_number", ord(gsmtap[13]))
    setattr(self, "sub_slot", ord(gsmtap[14]))

  def get_payload(self):
    return self.gsmtap[self.hdr_len:]
GSMTAP Header之后是 Link Access Procedure, Channel Dm,即LAPDm。參考TS 04.06有3個關鍵字段:  Address Field,Control Field,Length Field
Address Field除了上面說的SAPI外都可以不關注。
Control Field比較關鍵,里面記錄了該LAPDm的分片信息。Frame type: Information frame說明當前是I幀(I frame),其余bit為N(S)和N(R)。Send sequence number N(S)標記該分片的順序,從0開始遞增。看Wireshark源碼說實際有些N(S)可能不是從0開始的,這里組包就不判斷N(S)是否為0直接按順序附加。N(R)是Receive sequence number,看文檔上I幀傳輸時N(R)的狀態沒看明白,直接默認同時間只有1個下行短信了,這樣收到的N(R)基本是一樣的(事實上大部分時候都是如此)
Length Field除了長度信息,還有 More segments 標記,直到這個位為0才表示接收完一個完整的SMS報文

代碼:
class LAPDm:
  def __init__(self, lapdm):
    setattr(self, "lapdm", lapdm)

    setattr(self, "addr_field", ord(lapdm[0]))
    setattr(self, "lpd", (ord(lapdm[0])>>5)&0x3)
    setattr(self, "sapi", (ord(lapdm[0])>>2)&0x7)

    setattr(self, "ctrl_field", ord(lapdm[1]))
    setattr(self, "n_r", ord(lapdm[1])>>5)
    setattr(self, "n_s", (ord(lapdm[1])>>1)&0x7)

    setattr(self, "len_field", ord(lapdm[2]))
    setattr(self, "has_more", (ord(lapdm[2])>>1)&0x1)
    setattr(self, "length", ord(lapdm[2])>>2)

  def get_data(self):
    return self.lapdm[3:]

之后就可以這樣,獲得LAPDm的相關信息了:

代碼:
gsmtap = GSMTAP(gsm_payload)
lapdm = LAPDm(gsmtap.get_payload())

if (gsmtap.channel_type == 8) and (lapdm.sapi == 3):  # TS 04.06, 3.3.3, SAPI: 3 - Short message service
  debug_printf("LINK[%d] ARFCN=%d TIME_SLOT=%d CHANNEL=%d, N(R)=%d N(S)=%d, segment more[%d], payload len=%d\n" % \
    (gsmtap.link, gsmtap.arfcn, gsmtap.time_slot, gsmtap.channel_type, lapdm.n_r, lapdm.n_s, lapdm.has_more, lapdm.length))

  last_sms_payload += lapdm.get_data()    # 附加本次收到的數據
  if (lapdm.has_more == 0):      # 最后一個分片,解析整個 SMS payload
    hexdump(last_sms_payload)
    last_sms_payload = ""

接着看wireshark中重組的payload,確認得到的last_sms_payload和wireshark中解析的一致。
在wireshark中展開一個重組后的SMS報文

可以看到,在 GSM SMS TPDU (GSM 03.40) SMS-DELIVER 之前,還有CP-DATA/RP-DATA頭,RP-DATA中有短信中心的信息,但沒什么作用直接跳過。我們只需要知道后面SMS TPDU的長度即可:

代碼:
class SMS:
  def __init__(self, payload):
    self.payload = payload

    iOff = 0
    # CP-DATA
    setattr(self, "protocol", ord(payload[iOff])&0xF); iOff+=1
    iOff += 2

    # RP-DATA (Network to MS)
    iOff += 2
    setattr(self, "RP_origin_len", ord(payload[iOff])); iOff+=1
    setattr(self, "RP_origin_ext", ord(payload[iOff]));
    setattr(self, "RP_origin", bcdDigits(payload[iOff+1:iOff+self.RP_origin_len]))
    iOff += self.RP_origin_len

    setattr(self, "RP_dest_len", ord(payload[iOff])); iOff+=1
    iOff += self.RP_dest_len

    setattr(self, "length", ord(payload[iOff])); iOff+=1
    setattr(self, "tpdu_off", iOff);

  def get_tpdu(self):
    return self.payload[self.tpdu_off:self.tpdu_off+self.length]

調用 get_tpdu() 就會返回TPDU內容,里面TP-Originating-Address就是發送者的號碼,TP-User-Data就是我們要的短信內容。

代碼:
class TPDU:
  def __init__(self, tpdu):
    setattr(self, "tpdu", tpdu)
    
    iOff = 0
    # SMS-DELIVER
    iOff += 1
    setattr(self, "TP_origin_num", ord(tpdu[iOff])); iOff+=1
    setattr(self, "TP_origin_len", (self.TP_origin_num>>1)+(self.TP_origin_num%2))
    setattr(self, "TP_origin_ext", ord(tpdu[iOff])); iOff+=1
    setattr(self, "TP_origin", bcdDigits(tpdu[iOff:iOff+self.TP_origin_len]))
    iOff += self.TP_origin_len

    iOff += 2
    iOff += 7  # TimeStamp

    setattr(self, "tpu_len", ord(tpdu[iOff])); iOff+=1
    setattr(self, "data", tpdu[iOff:iOff+self.tpu_len])

  def get_data(self):
    return self.data.decode("utf-16be").encode("utf-8")

中文在SMS中是UCS2編碼的,get_data() 是用python的utf-16be解碼原始數據,並轉成UTF-8輸出。

好了,加上process_sms_tpdu()函數,最終代碼就是這樣:

代碼:
def process_sms_tpdu(sms_payload):
  hexdump(sms_payload)

  sms = SMS(sms_payload)
  tpdu = TPDU(sms.get_tpdu())
  debug_printf("[SMS from %s] %s" % (tpdu.TP_origin, tpdu.get_data()))

def handle_tcpdump_buffer(title, buffer):
  raw_struct = str2rawbuf(buffer)
  udp_packet = UDP(raw_struct)
  gsm_payload = udp_packet.get_payload()
  #hexdump(gsm_payload)

  gsmtap = GSMTAP(gsm_payload)
  lapdm = LAPDm(gsmtap.get_payload())

  if (gsmtap.channel_type == 8) and (lapdm.sapi == 3):    # TS 04.06, 3.3.3, SAPI: 3 - Short message service
    debug_printf("LINK[%d] ARFCN=%d TIME_SLOT=%d CHANNEL=%d, N(R)=%d N(S)=%d, segment more[%d], payload len=%d\n" % \
      (gsmtap.link, gsmtap.arfcn, gsmtap.time_slot, gsmtap.channel_type, lapdm.n_r, lapdm.n_s, lapdm.has_more, lapdm.length))

    global last_sms_payload
    last_sms_payload += lapdm.get_data()
    if (lapdm.has_more == 0):
      process_sms_tpdu(last_sms_payload)
      last_sms_payload = ""

注:文末的 gsmtap_sms_decode_src.7z 里有完整的解析腳本 使用 ./ccch_scan -a ARFCN -i 127.0.0.1 將GSMTAP轉發到本機的4729端口后,可以用這個腳本來重組SMS報文:

tcpdump -l -ilo -nXs0 udp and port 4729 | python2 -u show_gsmtap_sms.py

運行截圖:

-----------------------------------------------------------------------------------------

上面腳本只是為了熟悉lapdm的重組,並未處理N(S)非零,以及並發時下行短信的重組建議有一定編碼能力的同學,可以參考wireshark源碼進行數據還原:

代碼:
static void
dissect_lapdm(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
            ... ...
            /* Rely on caller to provide a way to group fragments */
            fragment_id = (pinfo->circuit_id << 4) | (sapi << 1) | pinfo->p2p_dir;

            /* This doesn't seem the best way of doing it as doesn't
               take N(S) into account, but N(S) isn't always 0 for
               the first fragment!
             */
            fd_m = fragment_add_seq_next (&lapdm_reassembly_table, payload, 0,
                                pinfo,
                                fragment_id, /* guint32 ID for fragments belonging together */
                                NULL,
                                /*n_s guint32 fragment sequence number */
                                len, /* guint32 fragment length */
                                m); /* More fragments? */
            ... ...
}

另外細心的各位可能會奇怪,下行短信里怎么沒有短信接受者的號碼,這里有篇關於SMS傳輸的基本原理說明:
http://robinlea.com/pub/Amphol/Secur...arch_Labs.html
簡單來講,短信接受者的號碼、IMEI等數據,只有在"Location Update"時才會在網絡中出現,並且是以加密形式傳輸的。當接收短信時,基站根據之前位置更新時注冊的信息,判斷接收者的位置。所以,想要拿到接受者的號碼,需要破解A5/1算法並還原出"Location Update"時的原文
Airprobe項目里有介紹如何破解A5/1算法找到Kc:https://srlabs.de/airprobe-how-to/ 只不過需要價格昂貴的USRP2...
另外還看到個RTL-SDR的文章(就是以前傳說中可以跟蹤飛機的電視棒),也支持Airprobe:
http://www.rtl-sdr.com/rtl-sdr-tutor...and-wireshark/
到此,GSM Sniffering入門算是告一段落了,感謝各位!

附件:gsmtap_sms_decode_src.7z

-----------------------------------------------------------------------------------------

關於抓上行短信或語音嗅探。看到這里有篇討論:

http://baseband-devel.722152.n3.nabb...td3531044.html
以及http://wulujia.com/2013/11/10/OsmocomBB-Guide/的文末也有圖片
里面都提到,除了代碼里增加ARFCN的上行偏移,還需要移除C118上的一個RX過濾器。這里是官方的一個指引:
http://bb.osmocom.org/trac/wiki/Hardware/FilterReplacement
語音除了需要抓TCH外(sniff_tch_sched_set也還有定義),還需要算出Kc才能解碼。

這篇論文附錄里有提到如何操作,他是在USRP2上實現的(A5/1 rainbow-table攻擊)。
OsmocomBB上好像做不到實時,不過mail list中倒是有些資料。TCH部分目前還是一頭霧水,如果有什么比較好的思路可以探討一二


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM