GSM Sniffing入门之软件篇:GSMTAP抓取与SMS(Short Message Service)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

重点介绍如何利用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部分目前还是一头雾水,如果有什么比较好的思路可以探讨一二

本文转自 K1two2 博客园博客,原文链接: http://www.cnblogs.com/k1two2/p/4539681.html  ,如需转载请自行联系原作者
相关文章
java中整型数据(byte、short、int、long)溢出的现象及原理
java中整型数据(byte、short、int、long)溢出的现象及原理
|
Java C++
Java - 数据类型 short VS int 转换原理
Java - 数据类型 short VS int 转换原理
242 0
Java - 数据类型 short VS int 转换原理
|
Linux C语言 存储
printf中的short int, int, long int和long long int
hd: short int d: int ld: long int lld: long long int Linux基本数据类型大小——int,char,long int,long long int 在Linux操作系统下使用GCC进行编程,目前一般处理器为32位字宽,下面是/usr/include/limit.h文件对Linux下数据类型的限制及存储字节大小的说明。
1166 0
|
3月前
|
缓存 API Windows
【服务总线 Azure Service Bus】Service Bus在使用预提取(prefetching)后出现Microsoft.Azure.ServiceBus.MessageLockLostException异常问题
【服务总线 Azure Service Bus】Service Bus在使用预提取(prefetching)后出现Microsoft.Azure.ServiceBus.MessageLockLostException异常问题
|
3月前
|
Java 容器
【Azure 媒体服务】记使用 Media Service 的官网示例代码 Audio Analyzer 出现卡顿在 Creating event processor host .. 直到 Timeout 问题
【Azure 媒体服务】记使用 Media Service 的官网示例代码 Audio Analyzer 出现卡顿在 Creating event processor host .. 直到 Timeout 问题
UE Http Server Plug -in description
UE Http Server Plug -in description
97 0
|
iOS开发
苹果手机请求程序报network error错误
苹果手机请求程序报network error错误
483 0
|
安全 图形学
iTunes Connect TERMS OF SERVICE
iTunes Connect TERMS OF SERVICE

热门文章

最新文章