重點介紹如何利用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:]
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入門算是告一段落了,感謝各位!
-----------------------------------------------------------------------------------------
關於抓上行短信或語音嗅探。看到這里有篇討論:
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部分目前還是一頭霧水,如果有什么比較好的思路可以探討一二