8 月份的时候买了个除湿器,这东西可以联网,用他们自己的 APP 进行控制。当然也可以直接按实体按钮控制,说好听点叫没网也能用😂
我一直不喜欢用装这种 APP,那么既然如此,就要搞一波这个德业智能 APP 了。
思路
- 先拿到除湿器的 IP 地址, 先 nmap 一波看看,也许有什么隐藏的接口
- 在路由器上用 tcpdump 抓这个 IP 地址的包,又回到了我搞小米摄像头的时候(这个也许以后会写文章讲讲我都做了哪些奇葩的事情)
- 在 iOS 上抓包,看看都发了什么请求控制开启和关闭,也许是简单的 http
除湿器 IP
在路由器的 DHCP 分配记录中可以看到我有一个叫 MiCO 的设备,IP 地址是192.168.7.119
。全部 TCP 65535 端口都扫了一波,发现只开了一个 53 端口。难道这东西是 DNS?咱也不知道,因为所有的 DNS 请求都会被拦截到路由器,因此也没有进行进一步测试。
路由器抓包
有点懒,还要先搞 tcpdump,再加上好像不太会用 tcpdump,嗯先不管这个
iOS 抓包
在 iOS 上抓包,有很多选择,比较简单的用 stream,在 APP 里设置一下就可以解密 HTTPS 了。不过缺点是 stream 只能抓 https 和 http 的,其他协议看不到
抓包之后可以分享 har 文件,其实就是个 json。抓包结果中有这样几个关键信息:
- https://api.deye.com.cn/v3/enduser/userInfo/
Authorization JWT xxxx
看来是 Json Web Token 做的认证- 接口包含
userInfo, device_list, refresh_token, auth, mqttinfo
等。
随手拿浏览器打开这个 api 看了一下,令人痴呆惊呆的事情发生了
这竟然是个 Django WebApp,并且还开启了 DEBUG 模式,这……😓
简单整理了下有这么几个接口比较管用
登录接口
https://api.deye.com.cn/v3/enduser/login/
body 里提交 loginname
、password
、appid
(任意字符串就可以)、extend
(json string),POST 方法,会在返回的数据中拿到token
和clientid
- {
- "meta": {
- "code": 0,
- "message": "signup ok."
- },
- "data": {
- "token": "xxxxx",
- "clientid": "123456"
- }
- }
这个TOKEN
就是后续的 JWT TOKEN
注意这个登录是单点登录的!但是 TOKEN 一直有效就是了。
获取设备列表
https://api.deye.com.cn/v3/enduser/deviceList/?app=new
GET 请求,携带 JWT TOKEN,会返回当前帐号绑定的所有设备
- {
- "meta": {
- "code": 0,
- "message": "device list by user."
- },
- "data": [
- {
- "online": true,
- "product_icon": "https://deye-cloud.oss-cn-shanghai.aliyuncs.com/profile/1597889707825.png",
- "product_id": "c2c34567df",
- "product_name": "DYD-E12A3",
- "device_id": "609",
- "mac": "04xxxx",
- "payload": "1411xxxxxhiddenxxxx",
- "role": 1,
- "gatewaytype": 0,
- "device_name": "不再潮湿",
- "product_type": "dehumidifier",
- "is_combo": false,
- "protocol_version": "wifi_V2.4"
- }
- ]
- }
比较关键的信息有product_id,product_name,device_id
等。Payload
还不知道是个啥看起来像是一段 hex
MQTT info
https://api.deye.com.cn/v3/enduser/mqttInfo/
同样是 GET 请求,会返回如下数据
- {
- "meta": {
- "code": 0,
- "message": "mqtt info"
- },
- "data": {
- "loginname": "ff123/123abc",
- "clientid": "app_123abc",
- "endpoint": "ff123",
- "password": "123456",
- "mqtthost": "ff123.mqtt.iot.gz.baidubce.com",
- "mqttport": 1883,
- "sslport": 1884
- }
- }
看来用户名和密码都有了,并且用的是 MQTT 协议,这是个啥?
MQTT 协议
国内有一家做 MQTT 协议的公司,叫 EMQ,文档写的非常不错!
简单的来说,MQTT 主要就是一种适合物联网等处理能力比较差、内存比较小、网络环境复杂不可靠的终端设备用的协议。
这东西简单的说有点像消息队列,属于 publish/subscribe 的模式,但是设计非常简单。盗个图是这样的
publisher 向 news 这个 topic 发送消息,所有订阅了 news 的用户都会收到消息。
当然了每个用户既可以是 publisher 也可以是 subscriber。
这个消息,图片中的 some msg 就是 payload,格式一般有 json,plaintext,base64 和 hex
那既然如此,肯定是我的 iPhone 发了 MQTT 协议的数据,看来只能继续抓包了
Wireshark 抓包 iOS
想要抓到 iOS 全部通信的数据,那最好的办法是用 Wireshark 了。当然我这里不是说在 iOS 上装 Wireshark,想啥呢,App Store 可没有 Wireshark。
具体来说其实很简单,让 iPhone 的全部流量走你的电脑,电脑抓包就可以了。Windows 本本的话,只能想办法开个热点,然后抓无线网卡了,这个操作过于简单,我就不说了。
macOS 的话,这样也行,但是还有一种更方便的、开发 iOS APP 时用到的办法🤔
获取 iPhone UDID
把 iPhone 用线连接到电脑,打开 Apple Music,就可以看到 UDID,一大长串,别认错了不是序列号。iPhone 记得解锁。
创建虚拟网卡
这一步需要rvictl
这个命令,如果你不开发 iOS APP,那么大概率这个命令是找不到的,因为这个命令包含在 Xcode 之中。
当然这并不意味着我们要为了这么一点小事安装 10G + 的 Xcode。这就像你爸爸想要捶你并不需要拿出鸡毛掸子,他只要拎起拳头就够了。同理,我们只要 Xcode 的一小部分就可以了。当然了,整个 Xcode 还是要下载的,爸爸毕竟还要从箱子里拿出来鸡毛掸子吓唬吓唬你嘛。
在 Apple 官网下载适合你的版本的 Xcode 之后,解压缩,然后进到 Xcode.app 中(右键 - 显示包内容),找到这两个文件:
- Xcode.app/Contents/Resources/Packages/MobileDevice.pkg
- Xcode.app/Contents/Resources/Packages/MobileDeviceDevelopment.pkg
安装这两个就可以了。
如果你不想下载 10G + 的 Xcode 的话,那么也可以用我的,Catalina 10.15.7,Xcode 12.4
https://www.dropbox.com/sh/4sdl3zyx1m0sw08/AADNcp8WdFEBtNHdUjkiIQmIa?dl=0
安装完成后
sudo rvictl -s UDID
即可看到 success
想要停止可以用sudo rvictl -x UDID
如果失败,可能需要先运行
- sudo launchctl load -w /Library/Apple/System/Library/LaunchDaemons/com.apple.rpmuxd.plist
之后会多出来一个rvi0
的网卡,Wireshark 打开,开始吧!
分析 MQTT 协议
首先我们要在 wireshark 的设置中开启 MQTT 协议,这样 Wireshark 会解析出来,更方便我们看。然后需要过滤一下
- mqtt and ip.addr==192.168.7.116
connect
就像在教科书中说的一样,德业智能先是连接到了 MQTT 服务器
- Cliend ID: 其实是任意一个随机尽量不可能重复的字符串就可以了,德业智能这里是 login 接口中的 clientid 和时间戳的拼接
- username 可以从 mqttinfo 接口中获得,格式其实是
endpoint/clientid
- 密码从接口中获得
- host 当然也是接口中获得了,看起来是百度云哦
subscribe
随后德业智能订阅了两条消息,分别为
status/hex
endpoint/product_id/device_id/status/hex
online/json
endpoint/product_id/device_id/online/json
- {
- "type": "online",
- "data": {
- "online": true
- }
- }
看起来是德业智能在获取当前除湿器的状态,然后 APP 动态显示按钮类别
publish
publish 证明德业智能向某个 topic 发布了消息,那么这非常有可能是在控制除湿器,这就需要一点点尝试了
publish online/json
德业智能向online/json
发布了一条消息,内容是一串十六进制
7b2274797065223a226f6e6c696e65222c2264617461223a7b226f6e6c696e65223a747275657d7d
随便找个网站转换一下,发现竟然是如下结果
尝试在 MQTTX 中连接到这台设备的 MQTT 服务器,然后以 hex 格式发布消息到 topic,除湿器没有响应
publish command/hex
从名字上来看,这个非常像在控制除湿器
而且 message 是 16 进制的0001
,会让人联想是不是0001
是开,0000
是关?
在 MQTTX 中设置一波,发送,除湿器并没有开……
再往下看,还有一串神奇的数字 080203403c0000000000
,MQTTX 中尝试一下,奇迹出现了,除湿器开了。🧐
再抓个包,就会发现080202403c0000000000
是关机代码。
尽管俺也不知道这神秘代码是什么,但是这就像是伏拉夫经常说的 “我爱中国”,“咱们中国真是太棒了” 一样,直接拿去用就能过上心想事成的生活。
Python 实现
已经知道了神秘代码和 topic,那么接下来封装一下就可以了。使用 paho.mqtt.python
- server.publish("endpoint/product_id/device_id/command/hex", "080203403c0000000000")
嗯??没开啊,咋回事?完全没效果。网络不好吗,再试一次,还是如此
莫非财富密码神秘代码失灵了?打开 MQTTX 订阅这个 topic,发现好像不对,怎么是那么一长串数字……
仔细想一下……
噢要 hex 格式,可是这个库并没有额外参数设置 payload 的格式啊
不慌不慌,经过我的摸着石头过河、然后搬起石头砸自己的脚的艰苦探索的经历,完全不用考虑struct.pack
什么的,直接发送这样的消息就可以啦
- codecs.decode(b"080203403c0000000000","hex")
- # 当然你也可以选择使用base64
- base64.b64decode("CAICQDwAAAAAAA==")
封装
那么要素已经齐全了,接下来就是上 click、requests 等,写个 setup.py,做成 console application,直接pip install deye
然后就可以啦!包含登录功能,登录之后会把信息写到 ~/.deye.dbm
中
可以看下面这个视频
后续
- 这个 Python package 的 bug 很多!我知道!因为我不太会用 click
- 我并没有什么打算添加其他设备的想法,也不想添加更多的 command。因为既没有时间、也没有钱购入更多德业的设备,不过接口已经写好了就是…… 按照原本的想法,是想把这个接入米家的,可是一想这个工作量 ++
- 花了好几个小时,写完了,把不需要的文件丢到回收站,清空。发现写好的文章也被我顺带着删了……🤬想嘴自己
代码开源在这里 https://github.com/BennyThink/deye
-- 本评论由 Telegram Bot 回复~❤️
-- 本评论由 Telegram Bot 回复~❤️