智能家居设备的另一种打开方式——如何控制局域网中的小米设备

近两年,物联网技术发展迅猛,各样的智能设备渐渐地走进了我们的家居生活。在众多的智能设备厂商中,小米是较早的布局智能家居生态的厂商,购买智能家居设备的用户几乎都会有一到两个小米设备。那么是否可以控制这些小米设备呢,其中过程是否会有安全风险呢?本文接下来会主要介绍这些内容。

具体地,除了米家app控制小米设备外,小米还提供一种局域网控制的方式,但前提是要获得用于设备认证一串字符串(即token),所以接下来主要介绍如何获取设备token,以及如何实现局域网控制设备。

一、总体流程介绍

在同一局域网下,小米设备可以使用专有的加密UDP网络协议miio协议通信控制。在网络可达的前提下,向小米设备发送一串hello bytes即可获得含有token的结构体数据。之后,构造相应的结构体,并以同样的方式发送给设备即可实现控制。具体流程如下图所示:

二、小米设备token获取

小米设备的token获取有三种途径,如下所述:

2.1 miio获取token

miio有基于Python实现的库,其Github项目地址为:https://github.com/rytilahti/python-miio。该项目支持所有兼容miio协议的设备,并将设备发现、识别和控制的方法进行了分类。

2.1.1 环境安装

python-miio需要Python3.5以上版本上才能运行,所以首先搭建Python环境。下面,我们在操作系统为Ubuntu的电脑或者树莓派中安装Python3.5:

  • 安装5依赖(本机存在的会忽略)
sudoapt-getinstallbuild-essentiallibsqlite3-devsqlite3bzip2libbz2-devlibssl-devopenssllibgdbm-devliblzma-devlibreadline-devlibncursesw5-dev
  • 编译安装5
wgethttps://www.python.org/ftp/python/3.5.2/Python-3.5.2.tgz

tarzxvfPython-3.5.2.tgz

cd./Python-3.5.2

./configure--prefix=/usr/bin/python3.5

sudomake

sudomakeinstall
  • 编译后运行一下5,结果如下证明安装成功
sean@ubuntu:~/Desktop/week/ProcessAndDeadline$ python3.5

Python3.5.2(default,Nov232017,16:37:01)

[GCC5.4.020160609]onlinux

Type"help","copyright","credits" or"license" formoreinformation.

>>>
  • 安装miio库,下载库代码到本地并安装
gitclonehttps://github.com/rytilahti/python-miio

cd python-miio/

python3.5 setup.py install

2.1.2 通过脚本获取token

下面就以小米智能插座为例,说明如何获取该设备的token。

  • 脚本编写

首先要保证获取token的客户端要与插座网络可达。为了显示直观,我们将主要实现代码从库中提取出来(如下)。将文件放在python-miio/miio目录下(该脚本主要就是使用socket向设备ip的54321端口发送固定字符串,返回值即为设备token):

#-*-coding:utf8-*-

importcodecs

importsocket

fromprotocolimportMessage

helobytes=bytes.fromhex('21310020ffffffffffffffffffffffffffffffffffffffffffffffffffffffff')

s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

s.sendto(helobytes,('192.168.42.17',54321))#插座ip,端口54321

data,addr=s.recvfrom(1024)

m=Message.parse(data)

tok=codecs.encode(m.checksum,'hex')

print(m)

print(tok)
  • 运行:5 miio_test.py

返回如下消息结构,其中checksum即为设备的token,解码之后为:’1d0616858062b8f836ebcacc98e62dd2’。

root@raspberrypi:~/python-miio/miio#python3.5miio_test.py

Container:

    data=Container:

        offset2=32

        offset1=32

        length=0

        value=  (total0)

        data=  (total0)

    header=Container:

        offset2=16

        offset1=0

        length=16

        value=Container:

            length=32

            unknown=0

            device_id=\x03\xa9\x84\xb6(total4)

            ts=1970-01-2222:10:05

        data=!1\x00\x00\x00\x00\x00\x03\xa9\x84\xb6\x00\x1c\xe7=(total16)

    checksum=\x1d\x06\x16\x85\x80b\xb8\xf86\xeb\xca\xcc\x98\xe6-\xd2(total16)

 b'1d0616858062b8f836ebcacc98e62dd2'

支持这种方式拿token的还有小米的空气净化器、净水器、扫地机器人、智能插座插线板等。具体列表见https://github.com/rytilahti/python-miio

2.2 从米家app获取token

如果能用上述的探测方法获取token还是比较便捷的,但目前只有部分小米设备支持。接下来还有一种方法可以直接从app获取token。以小米绿米网关为例:首先下载米家app,将绿米网关配置入网后,点击网关设备。接下来步骤如下组图,最后的密码即为网关的token。

目前绿米的这种设计模式是用户友好的,而且设备的所有者还可以选择是否开放局域网控制以及刷新控制token的有效性,比较安全。个人还是很希望小米的其他设备同样开放app侧获取设备token,因为毕竟获取需要搭建复杂的环境以及调试代码,大部分使用者应该不能接受的。

2.3 从数据库获取token

该方法是读取手机中米家的app中的数据记录来获取设备的token,具体步骤如下:

  • 准备一部获取root权限的安卓手机
  • 安装米家app并登录账号
  • 进入/data/data/com.xiaomi.smarthome/databases/
  • 拷贝db,下载到电脑
  • 前往网站(http://miio2.yinhh.com/),上传db,点击提交,即可获得token。

因为没有root的安卓手机,笔者没有具体测试这种方式获取token的有效性,具体可以参考这篇文章(https://homekit.loli.ren/docs/show/12)

三、控制小米WiFi插座

如果获得了token,就能对小米的设备进行操作,接下来介绍使用miio协议控制小米插座的主要步骤。

3.1 控制脚本编写

基于1.1.2获取到的回传的token信息,构造如下数据结构,用来控制设备。

ts=m.header.value.ts+datetime.timedelta(seconds=1)

cmd={'id':1,'method':'set_power','params':['on']}

header={'length':0,'unknown':0x00000000,                  

          'device_id':device_id,'ts':ts}

msg={'data':{'value':cmd},

       'header':{'value':header},

       'checksum':0}

其中:

token为获取到的设备token;

device_id为获取token返回结构中的device_id字段;

ts是一个时间结构,控制传的ts的需要在获取到ts基础上加1秒;

cmd中的method包括:set_power(控制开关)、get_prop(获取状态),控制的params是[‘on’]/ [‘off’],获取状态的params是[‘power’, ‘temperature’]

下面的代码是实现打开插座的控制,其中插座的IP为192.168.42.17。

m0=Message.build(msg,token=m.checksum)

s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

s.sendto(m0,('192.168.42.17',54321))

data,addr=s.recvfrom(1024)

m1=Message.parse(data,token=tok)

print(m1)

3.2 执行控制脚本

执行上面编写的控制代码,得到返回状态,如果返回状态中value变量中的result字段为[ ‘ok’],即为控制成功。当cmd使用get_prop方法时,如果返回的value变量值为{‘id’: 1, ‘result’: [‘on’, 59]}时,表示插座状态是开的,温度为59℃。因为篇幅关系具体就不贴了。

root@raspberrypi:~/python-miio/miio#python3.5miio_test.py

Container:

    data=Container:

        value={'id':1,'result':['ok']}

        data=\x03\x88T\x86\x1a\xbd\xb5\xb24.\xdcm\xdb\xc3\xb4\xdb\x0e7\x80JR\x0e\xda\xa987\x91Q\xd0\xee\x9bV(total32)

        offset2=64

        offset1=32

        length=32

    header=Container:

        value=Container:

            length=64

            unknown=0

            device_id=\x03\xa9\x84\xb6(total4)

            ts=1970-01-2220:40:38

        data=!1\x00@\x00\x00\x00\x00\x03\xa9\x84\xb6\x00\x1c\xd2F(total16)

        offset2=16

        offset1=0

        length=16

    checksum=\xfc\xe2\r\x8b\xd9\xb6\x1d\xfda\xd5\x11\x04\xe1b\xbe\xfd(total16)

四、总结

从目前的智能家居市场来看,用户不会只使用单个智能设备厂商的设备,所以对于厂商来说,通过开放接口给用户一些局域网的控制“自由”,实现不同厂商设备的联动是一个不错的选择。

从另外一个角度,本文中体现的安全问题我们也不容忽视。如果如2.1所示在局域网中不经过认证就能获取物联网设备的访问凭证,并进而进行控制,无形中给入侵者留了一扇门。例如,攻击者可经过扫描互联网发现家庭路由器,并利用弱口令或设备漏洞获得路由器的shell权限,接下来就可按照文中步骤就可以获得设备token进而控制。

在接下来的文章中,我们会给大家介绍一些智能家居的平台,以及家庭环境中智能设备的一些安全防护方法,让智能与安全同行。

Spread the word. Share this post!

Meet The Author

Leave Comment