揭秘如何用Python黑掉智能锅炉
引文
去年我买了一个新的冷凝式锅炉(家用取暖产品),于是考虑上面必须有一个“智能恒温器”,而选择也很多,包括Google Nest、 Hive(英国天然气公司设计的) 以及伍斯特·博世‘Wave’。但最终还是选择了后者。
最后它被安装在家中楼梯下面的墙上,并利用电线连接到设备上。正如你所看到的上面有一个触摸面板,你可以在上面调整恒温器的参数,当然你也可以在上面看到温度以及蓄水情况,而你也可以切换自动模式来自动加热,其实这里最应该关心的是设备的工作状态。如果想要使用类似定时之类的功能,就不得不用到移动应用程序了,就像这样。
看起来似曾相识吧?也许你也有一款应用程序的主页面和这个看起来很像,当然你也可以设置更高级的功能,比如定时等。
对于目前还是再使用伍斯特恒温(调节)器,它可以通过手机上的应用程序来进行定时功能的设置。当然这里面还有不少其它高级的功能,例如这个功能“optimisation”,系统可以根据你室内温度的情况来自动调节,来维持你选择的温度,而这个时候你可外出游玩或者做一些别的事情。
无论怎么样,当我第一次接触这个设备的时候,就开始安装了应用程序,这样就可以监控该设备了。而作者理想的情况是通过一个Python脚本程序来完成这个功能,但设备官网上没有这样的程序。作者后来又打电话给客服人员,客服人员称没有这样的程序提供给作者。于是作者开始分析设备,并使用Python脚本程序来完成这些工作。
于是开始分析设备来完成需要做的工作,首先需要知道的一点就是应用程序会将一些设备情况以及信息发送给远程的服务器(可能是有效监控设备),然后再将反馈信息回传到恒温器,反之亦然。这就意味着可以在不同的地方且不必通过同一个无线网络就可以完成对设备的控制。当然设备也可以连接在家用电脑上,所以任何开放的端口都是需要通过防火墙才能够和服务器连接的。
在查看产品信息的时候发现了硬件以及软件版本信息,包括该设备使用的开源软件信息,打开后发现了开源软件包列表以及各种许可协议,包括
AndroidPlot-开源图表库
Guava-一个 Google 的基于java1.6的类库集合的扩展项目
XMP Toolkit -可扩展元数据管理平台
Smack-一个开源,易于使用的XMPP(jabber)客户端类库
JSR305 Expert Group-代码缺陷检测(Java)
Lucent technologies-原文作者没有注明
Chromium -Google 的chrome浏览器
Takayua OOURA-Ooura Mathematical(数学)软件
Eigen-C++矩阵处理工具
libresample-数据重采样(主要是音频)工具
这样你就可以看到确实存在基于标准通用标记语言的子集XML的协议,基于XMPP的应用具有超强的可扩展性。经过扩展以后的XMPP可以通过发送扩展的信息来处理用户的需求,以及在XMPP的顶端建立如内容发布系统和基于地址的服务等应用程序,同时XMPP提供一个通用的可扩展的框架来交换XML数据,用于准实时消息和出席信息以及请求-响应服务。而XMPP可用于服务类实时通讯、表示和需求响应服务中的XML数据元流式传输。前面那个图表库的部分与这次工作没有太大关联,而对于数学软件部分可以做出这样一个猜测,即利用这一点可以完成一些高级功能的应用。
现在我们可以清楚了XMPP协议很重要,但是目前需要发送数据以及接收数据是什么情况,于是打开Wireshark软件,然后监控流量信息,然后进行协议过滤,之后就会看到这个
好吧,现在这些证明我的猜测是正确的。安装在手机上的设备应用程序传输信息使用的是XMPP协议(本地IP92.168.0.42),博世设备的服务器(wa2-mz36-qrmzh6.bosch.de)。现在就清楚协议具体是什么情况了。
危险终究还是来了!
然后坏消息是,出现了STARTTLS,可以看出这是一个电子邮件设置对话框,同时这也是一个 POP3/IMAP/SMTP 服务安全选项。基本上可以说是“传输层安全协议”,可以说成是“我想从现在开始对这段对话进行加密,可以吗”(当然这也是一个参考,有些通信可以一开始就加密来确保通信安全),当然设备的服务器可以继续运行,而现在就已经看到了 TLS安全协议,然后就可以移动设备来发送信息了。
当然我们没有看到任何的明文信息,这些信息都是加密过的,例如这样80414a90ca64968de3a0acc5eb1b50108bbc5b26973626daaa…,这些信息都不是很有用的。
上面已经对 XMPP协议以及TLS安全协议进行了分析,但由于做了加密处理所以我们看不到信息内容。想要解密的话,需要完成一个中间人攻击,在攻击中将会截取以及分析信息的内容,在这里虽然被称为“攻击”,但是用的手机连接的网络是自己的,所以这里算是安全测试。
这里将会用到一个工具sslsplit,而在这里我无法利用sslsplit 来分析 STARTTLS(前文有对应,STARTTLS出现,然后利用TLS安全协议通信)。
作者使用的方法
开始搭建linux 服务器(NAT转换)-它工作起来像是路由器。同时也意味着我可以在网络上使用其它设备并将Linux作为网关服务器(NAT转换),
我在服务器上创建了服务器自签名根证书,而这个证书是可以信任的。
我将这个证书创建在了一个备用的安卓手机上,通过手机连接WiFi最后将Linux作为网关服务器。最后作者测试了一下,可以通过手机访问网络,然后所有的通信都经过了服务器。
然后当我在使用设备应用程序的时候,手机连接服务器,也就是说所有的通信都连接我的服务器然后在连接到厂商那边的服务器,当然证书是必不可少的。
现在我们已经创建了证书,配置了网络,我们需要开始解密信息了。正如上面所述,开始使用SSLSplit,但是这款工具似乎不能应对STARTTLS,所以需要另外一个解决途径。幸运的是,我又发现了一款工具starttls-mitm,值得注意的是它仅仅有80行Python程序代码,但可能功能上比一些相对复杂的工具要少,比如SSLSplit。但是这也都不重要,这就是我想要的。它甚至默认支持XMPP协议。
所以现在可以通过指令来运行 starttls-mitm(基本上是密匙、证书等),现在就可以分析未加密前的 STARTTLS,或者解密已加密的 STARTTLS ,如果我们分析已经运行的应用程序会得到什么?
好吧,现在我们利用starttls-mitm来分析一下登录信息看看它在做什么
LISTENER ready on port 8443
CLIENT CONNECT from: (’192.168.0.42′, 57913)
RELAYING
我们得到的初始信息
C->S 129 '<stream:stream to="wa2-mz36-qrmzh6.bosch.de" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">'
S->C 442 '
相当明显,C->S是客户端发送信息到服务器的意思,S->C意思就是刚刚那个反过来(数字代表信息的长度),这些也是刚刚分析 XMPP协议的结果,我们之前在Wireshark也看到了这些,然而我们看见了更有趣的事情。
C->S 51 '<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>'
S->C 50 '<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>'
Wrapping sockets.
STARTTLS验证信息发送,服务器会发出响应PROCEED,starttls-mitm分析之后会有提示“‘wrapping sockets”(而这一点也表示已经可以解密通信信息了)
而这里我会跳过TLS协议的握手过程,直接切入到XMPP协议部分。我没有分析XMPP协议的经验,而info/query也是代表握手过程的一部分,整个过程都是在交流信息比如它们是谁,做什么的等等,每一部分完成之后都会发出消息“presence”(xmpp甚至可以被看做是即时通讯协议,就相当于你在使用即时通讯软件,发出是否在线等信息)
C->S 110 '<iq id="lj8Vq-1" type="set"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>70</resource></bind></iq>'
S->C 188 '<iq type="result" id="lj8Vq-1" to="wa2-mz36-qrmzh6.bosch.de/260d2859"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>rrccontact_458921440@wa2-mz36-qrmzh6.bosch.de/70</jid></bind></iq>'
C->S 87 '<iq id="lj8Vq-2" type="set"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>'
S->C 86 '<iq type="result" id="lj8Vq-2" to="rrccontact_458921440@wa2-mz36-qrmzh6.bosch.de/70"/>'
C->S 74 '<iq id="lj8Vq-3" type="get"><query xmlns="jabber:iq:roster" ></query></iq>'
S->C 123 '<iq type="result" id="lj8Vq-3" to="rrccontact_458921440@wa2-mz36-qrmzh6.bosch.de/70"><query xmlns="jabber:iq:roster"/></iq>'
C->S 34 '<presence id="lj8Vq-4"></presence>'
C->S 34 '<presence id="lj8Vq-5"></presence>'
现在信息都已经处理好了,下面是客户端(手机应用程序)到服务器之间的信息
C->S 162 '<message id="lj8Vq-6" to="rrcgateway_458921440@wa2-mz36-qrmzh6.bosch.de" type="chat"><body>GET /ecus/rrc/uiStatus HTTP /1.0\nUser-Agent: NefitEasy</body></message>'
而这基本上就是一个http GET请求并用到了XMPP 协议,而在应用程序“home screen”选项下,你可以看到主页面以及用户设备信息(当前的温度、湿度),现在我们就可以看到服务器的响应信息了
S->C 904 '<message to="rrccontact_458921440@wa2-mz36-qrmzh6.bosch.de/70" type="chat" xml:lang="en" from="rrcgateway_458921440@wa2-mz36-qrmzh6.bosch.de/RRC-RestApi"><body>HTTP/1.0 200 OK\nContent-Length: 640\nContent-Type: application/json\nconnection: close\n\n5EBW5RuFo7QojD4F1Uv0kOde1MbeVA46P3RDX6ZEYKaKkbLxanqVR2I8ceuQNbxkgkfzeLgg6D5ypF9jo7yGVRbR/ydf4L4MMTHxvdxBubG5HhiVqJgSc2+7iPvhcWvRZrRKBEMiz8vAsd5JleS4CoTmbN0vV7kHgO2uVeuxtN5ZDsk3/cZpxiTvvaXWlCQGOavCLe55yQqmm3zpGoNFolGPTNC1MVuk00wpf6nbS7sFaRXSmpGQeGAfGNxSxfVPhWZtWRP3/ETi1Z+ozspBO8JZRAzeP8j0fJrBe9u+kDQJNXiMkgzyWb6Il6roSBWWgwYuepGYf/dSR9YygF6lrV+iQdZdyF08ZIgcNY5g5XWtm4LdH8SO+TZpP9aocLUVR1pmFM6m19MKP+spMg8gwPm6L9YuWSvd62KA8ASIQMtWbzFB6XjanGBQpVeMLI1Uzx4wWRaRaAG5qLTda9PpGk8K6LWOxHwtsuW/CDST/hE5jXvWqfVmrceUVqHz5Qcb0sjKRU5TOYA+JNigSf0Z4CIh7xD1t7bjJf9m6Wcyys/NkwZYryoQm99J2yH2khWXyd2DRETbsynr1AWrSRlStZ5H9ghPoYTqvKvgWsyMVTxbMOht86CzoufceI2W+Rr9</body></message>'
这看起来有些复杂不容易解释,但是整理一下信息格式或许看起来更好一些。
HTTP/1.0 200 OK
Content-Length: 640
Content-Type: application/json
connection: close
5EBW5RuFo7QojD4F1Uv0kOde1MbeVA46P3RDX6ZEYKaKkbLxanqVR2I8ceuQNbxkgkfzeLgg6D5ypF9jo7yGVRbR/ydf4L4MMTHxvdxBubG5HhiVqJgSc2+7iPvhcWvRZrRKBEMiz8vAsd5JleS4CoTmbN0vV7kHgO2uVeuxtN5ZDsk3/cZpxiTvvaXWlCQGOavCLe55yQqmm3zpGoNFolGPTNC1MVuk00wpf6nbS7sFaRXSmpGQeGAfGNxSxfVPhWZtWRP3/ETi1Z+ozspBO8JZRAzeP8j0fJrBe9u+kDQJNXiMkgzyWb6Il6roSBWWgwYuepGYf/dSR9YygF6lrV+iQdZdyF08ZIgcNY5g5XWtm4LdH8SO+TZpP9aocLUVR1pmFM6m19MKP+spMg8gwPm6L9YuWSvd62KA8ASIQMtWbzFB6XjanGBQpVeMLI1Uzx4wWRaRaAG5qLTda9PpGk8K6LWOxHwtsuW/CDST/hE5jXvWqfVmrceUVqHz5Qcb0sjKRU5TOYA+JNigSf0Z4CIh7xD1t7bjJf9m6Wcyys/NkwZYryoQm99J2yH2khWXyd2DRETbsynr1AWrSRlStZ5H9ghPoYTqvKvgWsyMVTxbMOht86CzoufceI2W+Rr9
它似乎看起来像是HTTP标准的响应,但主要的消息看起来像是某种编码格式,我认为解码之后会出现例如 JSON 、XML或者另外的一些东西,但是该这么做呢?
我尝试做了不少事情( MD5、Base64解码 ),但似乎没有起作用。于是我开始放下手头的工作,思考它。当我在工作的时候,忽然意识到数据是加密的。当你想要访问设备代码时候还需要注意你的登录密码(第一次登录设备程序时候设置的),当然解密就需要知道加密方式是什么样子的,于是需要用到反编译器。
为了了解设备的程序,就需要分析其apk文件,通过反编译的方式来查看代码。我用到的工具是 Android APK Decompiler,值得注意的是得到了通俗易懂的Java程序代码。(这里面还有一些goto语句,并设置了合理的变量名)
作者表示显然完全解释加密以及解密详细情况是非常困难的,包括下面作者的 Python程序代码,但作者做了一个简短的解释,主要的加密是AES( ECB),通过 MD5 sums命令(访问程序代码整合)生成密钥、密码以及secret(应用程序中的硬编码)
def encode(s):
abyte1 = get_md5(access + secret)
abyte2 = get_md5(secret + password)
key = abyte1 + abyte2
a = AES.new(key)
a = AES.new(key, AES.MODE_ECB)
res = a.encrypt(s)
encoded = base64.b64encode(res)
return encoded
def decode(data):
decoded = base64.b64decode(data)
abyte1 = get_md5(access + secret)
abyte2 = get_md5(secret + password)
key = abyte1 + abyte2
a = AES.new(key)
a = AES.new(key, AES.MODE_ECB)
res = a.decrypt(decoded)
return res
利用这些函数就可以在GET /ecus/rrc/uiStatus响应获得信息,然后就看到了这些
{'id': '/ecus/rrc/uiStatus',
'recordable': 0,
'type': 'uiUpdate',
'value': {'ARS': 'init',
'BAI': 'CH',
'BBE': 'false',
'BLE': 'false',
'BMR': 'false',
'CPM': 'auto',
'CSP': '31',
'CTD': '2014-12-26T12:34:27+00:00 Fr',
'CTR': 'room',
'DAS': 'off',
'DHW': 'on',
'ESI': 'off',
'FPA': 'off',
'HED_DB': '',
'HED_DEV': 'false',
'HED_EN': 'false',
'HMD': 'off',
'IHS': 'ok',
'IHT': '16.70',
'MMT': '15.5',
'PMR': 'false',
'RS': 'off',
'TAS': 'off',
'TOD': '0',
'TOR': 'on',
'TOT': '17.0',
'TSP': '17.0',
'UMD': 'clock'},
'writeable': 0}
更多的信息
当然信息不可能立刻显示出具体含义(其中三个变量名设置比较好),当然里面还是有一些比较明显的,比如CTD(可能代表像当前时间/日期)、DHW(生活用热水,在暖通领域内牵涉到建筑热负荷时会用到,一般会用日平均每人用量(因建筑功能而异)与人数来确定)。在这一部分,我分析了所有状态信息,并利用了温度监控系统,下面将会进行一些其它的事情(设置新温度等),然后再来看看作者的python 库。
在上面我们介绍了通信加密的信息,以及当前系统的状态。当然如果能利用Python程序设置温度、以及定时时间等,而这些就是要做的事情。我将会再一次的使用“中间人攻击”来监控应用程序。当发现温度改变的时候,你就会出现以下信息。
<message id="lj8Vq-31" to="rrcgateway_458921440@wa2-mz36-qrmzh6.bosch.de"
type="chat">
<subject>/heatingCircuits/hc1/manualTempOverride/temperature</subject><body>PUT /heatingCircuits/hc1/manualTempOverride/temperature HTTP:/1.0\nContent-Type: application/json\nContent-Length: 25\nUser-Agent: NefitEasy\n\n\n\nXmuIR7wCfDZpPrPkrb/CqQ==\n</body></message>
在整合XMPP协议(去掉头信息)之后,你就会看到这个
PUT /heatingCircuits/hc1/manualTempOverride/temperature
HTTP:/1.0
Content-Type: application/json
Content-Length: 25
User-Agent: NefitEasy
XmuIR7wCfDZpPrPkrb/CqQ==
在得到的信息最后,解码可以得到下面信息
{"value":16}\x00\x00\x00\x00
这看起来像是JSON,最后那些补位数据主要是出于对加密程序数据类型的长度考虑的。这可能和我的设置有关系,我将温度设置在了16度。
如果我设置了不同的数据(不同的数字信息),然后温度预设定会改变吗?
当然需要改变温度,取决于你选择的模式,这里有两种模式可供选择,当前状态信息(UMD)会告诉你处于哪种模式,手动或定时模式。如果处于前面那种模式,你可以发送一个简单的 PUT 请求/heatingCircuits/hc1/temperatureRoomManual以及JSON({“value”:21}),这样做主要是就取决于你想要的温度。
如果选择后者那种模式,你就需要设置“override temperature”(一个 PUT请求/heatingCircuits/hc1/manualTempOverride/temperature),然后需要设置“temperature override function”(一个PUT请求/heatingCircuits/hc1/manualTempOverride/status以及 JSON {“value”: “on”})。
如果你想要改变模式可以向 /heatingCircuits/hc1/usermode( JSON {“value”: “clock”} 或{“value”:”manual”})发出一个 PUT请求,当然你会得到一个回应,只要不出现错误信息,你会看到这些信息内容。
No Content
Content-Type: application/json
connection: close
而也可以通过发送其它一些信息来做一些更复杂的事情(例如改变定时器程序),但作者表示还没有去测试这些事情。但这些大致相同,他们可能利用了更复杂的JSON payload,并需要更多的信息来确认。现在我可以控制设备的基本状态(模式以及温度)。
作者到这里并没有提及太多有关Python的事情,但作者表示自己的大部分测试工作都是利用了 基于Python的sleekxmpp库,作者表示可以设计一个真正的有限状态机,来收发信息。而这些工作模式都是异步模式,看起来真的不容易。
所以为了发送信息以及解密文件作者创建了一个BaseWaveMessageBot class文件,并可以做出一些简单的错误处理。由于创建的文件有很多不同的内容,于是作者又在里面添加了一些新的内容(StatusBot 以及SetBot),利用它可以发出信息以及做出响应。作者将这些文件称为WaveThermo,作者需要在里面加入更多的内容,更新一些新功能。
作者提供的程序(点击我)
而作者只是在温控器上面测试过它,当然你可以利用作者的方法再结合实际情况去做出更多的方案。