Mototrbo TMS 协议分析(数字电台短信协议分析)
Mototrbo TMS 协议分析
作者:BG7NYT
三年前我就分析出了Mototrbo TMS协议,一直动笔写这篇文章,最近比较闲,就想起了这事。
起初只是好玩,使用P8668发送短信输入太麻烦,我就想开发个软件从电脑发送短信跟 Mototrbo 终端。
另外当时微信很火,微信推出了菜单服务。例如关注微信公众号后出现:当时微信很火,微信推出了菜单服务。例如关注微信公众号后出现:
- 主菜单
- XXX服务
- XXX
通过输出数字跟微信交互。我就相处了Mototrbo 的短信服务也能这么干。可以实现订阅天气预报,发布公告信息等等.....
还有一个原因是当时摩托罗拉DMR无法跟海能达电台互通短信。于是我就好奇,分析除了TMS短信协议,当时很想把这个分享给海能达,可惜我不认识海能达的人。
回到主题
DMR 网络
Mototrbo DMR 实际上就是 TCP/IP网络的延伸。我们可以理解为就是一个TCP/IP网络。
首先Radio ID (八位数字)就是 IP地址的后三位。
色码就是IP地址的第一位。例如 12 色码对应IP地址就是 12.0.0.0 。
色码加上RadioID通过算法就能实现 Radio ID 与 IP 地址的相互转换。
组呼可以理解为通过子网掩码控制广播域,在相同子网下的IP地址可以接收组播信息。
代码如下:
class Protocol(): def __init__(self, buffer = None): self.buffer = buffer def header(self): return(self.message) def message(self): return(self.message) def id2ip(self, cai, id): return (str(cai)+"."+str((id >> 16) & 0xff) +'.'+ str((id >> 8) & 0xff) + '.' + str(id & 0xff)); def ip2id(self, ipaddr): a, b, c, d = ipaddr.split('.'); return ((int(b) << 16) + (int(c) << 8 ) + int(d))
id2ip 是Radio ID 转 IP 地址,算法如下
(str(cai)+"."+str((id >> 16) & 0xff) +'.'+ str((id >> 8) & 0xff) + '.' + str(id & 0xff));
ip2id 是 IP 地址 转 Radio ID
a, b, c, d = ipaddr.split('.');((int(b) << 16) + (int(c) << 8 ) + int(d))
判断电台是否开机
数字电台使用的多了,就不想喊CQ了,直接进入通信录,找到朋友,检查状态。如果对方在线就会出现一个绿色的"对号",同时对方也会振铃。
其实我们判断电台是否开机很简单。每个电台都是一计算机终端,色码 + Radio ID 就能算出对方的IP 地址。
直接 ping 对方的IP地址就可以了,呵呵。
工具程序地址: https://github.com/netkiller/Mototrbo/blob/master/Mototrbo.py
elif self.options.ping : #直接 ping IP 地址 os.system("ping %s" % self.options.ping)
elif self.options.online : # ping radio id 需要做一次转换 os.system("ping %s" % protocol.id2ip(int(self.options.cai), int(self.options.online)))
Mototrbo TMS 协议
TMS短信协议为头与内容两部分。
头部:0x00开始,然后是短信内容的长度,0x0e 0x00 分割,然后是序号,最后是 0x04 结尾 内容:r回车符,0x00,换行符n,信息内容,0x00结束。
短信的字符集是 utf-16
一条完整短信最终协议包如下:
b'x00x14xe0x00x88x04rx00nx00Bx00Gx007x00Nx00Yx00Tx00'
短信的内容就是 BG7NYT
短信发送
class TMS(Protocol): def __init__(self, buffer = None): super().__init__(buffer) self.port = 4007 if buffer : self.header = self.buffer[:6] self.message = self.buffer[6:] def encode(self, msg): return(msg.encode('utf-16').replace(b'xffxfe', b'x00'))
def decode(self, msg = None): if msg : return( msg.replace(b'x00', b'xffxfe', 1).decode('utf-16') ) else: return( self.message.decode('utf-16') ) def sequence(self): return(self.header[4:5]) def lenght(self): return (len(self.message)) def sendtoip(self, ipaddr, sms): import socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP message = self.encode(sms) length = len(message)+7 #print(hex(length)) protocol = b'x00'+ bytes([length])+ b'xe0x00x88x04rx00n' + message #print(protocol) sock.sendto(protocol, (ipaddr, self.port)) def sendtoid(self, cai, radioid, sms): ipaddr = self.id2ip(cai, radioid) self.sendtoip(ipaddr, sms)
编码和解码
因为我们目前计算机通常使用UTF-8字符集,所以短信需要编码为 UTF-16 才能发送,否则在终端上看到的是乱码。 同理计算机收到来自电台的短信也需要解码 UTF-16才能阅读。代码如下
def encode(self, msg): return(msg.encode('utf-16').replace(b'xffxfe', b'x00'))
def decode(self, msg = None): if msg : return( msg.replace(b'x00', b'xffxfe', 1).decode('utf-16') ) else: return( self.message.decode('utf-16') )
短信的发送
发送短信很简单
- 通过 色码 + Radio ID 计算出对方的IP地址。
- UDP协议与对方IP地址建立连接,然后发送
代码如下:
def sendtoip(self, ipaddr, sms): import socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP message = self.encode(sms) length = len(message)+7 #print(hex(length)) protocol = b'x00'+ bytes([length])+ b'xe0x00x88x04rx00n' + message #print(protocol) sock.sendto(protocol, (ipaddr, self.port))
程序运行后对方的电台就会收到你的短信。
到此为止,我也曾经尝试分析 ARS,LRRP ......等等协议,很想实现ARS将GPS坐标抓出来,放到地图上,实现aprs.is那样的功能。 逆向工程太复杂,放弃了。
相关软件:https://github.com/netkiller/Mototrbo
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- python第二十九课——文件读写(写数据的操作)
- python第二十九课——文件读写(复制文件)
- python第三十课--异常(异常处理定义格式和常见类型)
- python第三十课--异常(finally讲解)
- python第三十课--异常(else讲解)
- Linux系统——shell脚本编程基础介绍
- python第三十课--异常(raise关键字)
- python第三十课--异常(异常对象传递过程)
- python第三十课--异常(with as操作)
- linux系统运维企业常见面试题集合(二)
- Linux系统Shell编程—企业生产案例(一)
- python第三十一课--递归(1.简单递归函数的定义和使用)
- python第三十一课--递归(2.遍历某个路径下面的所有内容)
- python第三十一课--递归(3.递归的弊端)
- python第三十二课——栈