首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
IoT安全
发新帖
7
10
[原创]某品牌电动牙刷小程序"越权控制"逆向
发表于: 2026-7-10 17:40
6734
[原创]某品牌电动牙刷小程序"越权控制"逆向
Haicaji
2026-7-10 17:40
6734
> 说是"越权",其实产品设计时可能根本没考虑鉴权。 ## 逆向对象 - 微信小程序:某品牌智能设备控制小程序。 ## 工具 - wux1an\wxapkg 微信小程序解包工具 + 3.9 版本 Windows 端微信。 - GitHub Copilot 代码辅助分析 AI + VS Code 编辑器。 - Wireshark 流量分析工具。 - <a href="elink@163K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Z5j5h3W2U0j5h3A6A6i4K6u0W2k6$3W2@1K9s2g2T1i4K6u0W2K9h3!0Q4x3V1k6Q4x3U0y4Q4x3V1k6H3j5h3N6W2i4K6u0r3i4K6t1#2c8e0k6Q4x3U0f1&6x3q4)9J5y4f1q4p5i4K6t1#2c8e0g2Q4x3U0g2n7b7W2)9J5y4f1u0m8M7$3&6A6k6X3k6D9k6g2)9J5y4f1f1#2i4K6t1#2b7U0N6Q4x3U0g2m8y4g2)9J5y4f1f1#2i4K6t1#2z5o6g2Q4x3U0g2n7y4#2)9J5y4f1f1%4i4K6t1#2z5p5g2Q4x3U0g2m8c8W2)9J5y4f1f1#2i4K6t1#2b7e0u0Q4x3U0f1^5x3#2)9J5k6r3u0D9k6g2)9J5y4f1f1@1i4K6t1#2b7U0S2Q4x3U0g2m8c8q4)9J5y4f1f1%4i4K6t1#2b7V1u0Q4x3U0g2m8y4#2)9J5y4f1f1$3i4K6t1#2z5e0c8Q4x3U0g2n7b7W2)9J5y4f1f1#2i4K6t1#2z5o6N6Q4x3U0g2n7b7R3`.`.">Sniffle 抓包环境</a> - <a href="elink@cecK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6z5L8%4u0V1K9h3y4e0k6h3#2A6j5$3!0F1k6s2g2U0N6r3!0J5i4K6u0r3b7h3&6V1M7X3!0A6k6q4)9J5k6r3&6d9c8W2)9J5k6p5y4G2L8X3&6W2j5%4c8Q4x3V1j5`.">nRF Connect</a> ## 逆向过程与漏洞利用 ### 解包目标小程序并进行逆向分析 - 使用 3.9 版本 Windows 端微信打开目标小程序。 - 使用 <a href="elink@f38K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Z5j5h3W2U0j5h3A6A6i4K6u0W2k6$3W2@1K9s2g2T1i4K6u0W2K9h3!0Q4x3V1k6Q4x3U0y4Q4x3V1k6H3j5h3N6W2i4K6u0r3N6%4g2^5x3h3q4F1i4K6t1#2y4f1y4%4P5r3q4H3K9$3N6Q4x3X3c8Q4x3U0g2q4y4g2)9J5y4f1u0q4i4K6t1#2b7f1g2Q4x3U0g2q4y4q4)9J5y4f1u0r3i4K6t1#2b7e0q4Q4x3U0g2q4y4g2)9J5y4f1t1H3i4K6t1#2z5p5k6Q4x3U0g2q4y4#2)9J5y4f1p5^5i4K6t1#2z5p5u0Q4x3U0g2q4y4g2)9J5y4f1u0m8i4K6t1#2z5p5k6Q4x3U0g2q4z5q4)9J5y4f1p5%4i4K6t1#2b7e0y4Q4x3U0g2q4y4g2)9J5y4e0S2o6i4K6t1#2z5o6g2Q4x3U0g2q4y4g2)9J5y4f1t1%4i4K6t1#2b7e0g2Q4x3U0g2q4y4g2)9J5y4e0R3#2i4K6t1#2b7U0M7`.">wxapkg</a> 工具对微信小程序进行解包。  #### 分析小程序整体代码结构 - 微信小程序本质上还是前端 JavaScript 代码,所以这里不需要使用 IDA 等二进制分析工具,直接使用 VS Code 打开解包后的项目文件夹,对微信小程序的项目结构进行整体分析。  - 发现该小程序解包后的项目结构还是较为清晰的,主要关注 `Page`,`packageHairdrier`,`packageBrush` 这几个文件夹以及主目录下的这些 `.js` 文件。 - 通过文件名大致可以猜测 `packageHairdrier` 为该品牌吹风机控制的相关代码,`packageBrush` 为该品牌电动牙刷控制的相关代码。 - 而在 `Page` 文件夹中,有如下页面,应该是用户登录、设备连接等通用功能代码。  #### 确认电动牙刷代码目录结构 - 因为身边只有该品牌的电动牙刷,为了方便验证,这里对电动牙刷的相关代码优先进行分析,即 `packageBrush` 文件夹下的内容。  - 由于解包逆向的缘故,`pages` 文件夹下的 `.html` 代码并未有关键的内容,只有对其他文件的引用,所以逆向分析的重心还是放在 `packageBrush` 下的 `.js` 文件。 #### 结合前端文案定位关键代码 - `.js` 的代码量不算少,为了能快速定位小程序控制电动牙刷的关键代码,需要结合前端显示的文字定位关键代码。 - 先逆向追踪 `开关` 这个按钮的功能。 - 直接在 VS Code 编辑器中搜索 `开关` 关键字。  - 因为 `开关` 按钮的开关文字是单独出现的,因此可以大致确定相关代码位于 `.\packageBrush\chunk_7.appservice.js` 和 `.\packageBrush\app-service.js` 两个文件中。 - 根据以往微信小程序解包的经验,`app-service.js` 一般包含所有分片代码,上述两个文件中所出现的 `开关` 相关代码实际是同一份代码。 - 因此,接下来只需要对 `.\packageBrush\chunk_7.appservice.js` 相关代码进行逆向分析。 #### 定位 `.\packageBrush\chunk_7.appservice.js` 中的“开关”文案  - 发现 `开关` 位于 `btnList` 列表中,所以尝试去搜索这个列表的调用。  - 发现 `btnList` 在这里被 `F()` 函数调用,根据经验猜测,`F()` 函数应该对应类似 `for` 循环的结构,对 `btnList` 的按钮进行逐个渲染,并且在 `F()` 的参数中,发现传入了 `i` 函数,推测它就是具体的渲染函数。 #### 进入 `i` 函数定义继续分析  - 但是遗憾的是,该函数虽然为按钮绑定了事件,但绑定的 `showDialog()` 并非蓝牙控制相关函数,而是校验是否登录的函数。  - 此时可以联想到,进入小程序之后,如果是未登录的状态,依旧可以显示控制电动牙刷的界面,但是按下任意按钮后,都会跳转到登录页面。 - 在 `.\packageBrush\chunk_7.appservice.js` 代码的最上方,可以看到我们上文分析函数所处的页面,即 `displayControl`,看来我们现在所分析的页面,并非电动牙刷的真实控制页面,而是未登录状态下的展示页面。  #### 搜索其他页面,定位真实控制页面 - 重新观察 `packageBrush\pages` 文件夹下的页面名称,发现了两个可能用于实际控制电动牙刷的页面 `brushControl` 和 `brushControlPlus`  - 尝试搜索 `brushControl` 和 `brushControlPlus` 关键词,试图找出它们在哪个 `chunk_*.appservice.js` 文件中出现。 - 最终确认 `brushControl` 对应 `chunk_1.appservice.js`,而 `brushControlPlus` 对应 `chunk_2.appservice.js` - 但是进入 `chunk_2.appservice.js` 却发现其代码仅有 51 行,应该是一个未启用的页面。 - 而在 `chunk_1.appservice.js` 中发现代码引用了 `ble` 蓝牙相关的库函数,看来这个代码文件应该就是我们想要的了。  #### 定位蓝牙控制函数 - 在该页面中继续寻找开关按钮,定位到了 `开启` 关键字。 - 这一点比较意外,在真正的控制界面中,开启电动牙刷按钮下写的是 `开启`,而非展示页面下显示的 `开关`,在这个地方浪费了点时间,刚开始就应该进入真实的页面去锁定 UI。 - 至此,基本定位到了真正的开启按钮。  - `开启` 关键词是在 `j` 函数中(第 57 行),其功能看起来是控制该按钮的显示。 - 接下来需要查看该按钮所绑定的函数,于是尝试搜索 `j` 函数在哪里调用。 - 在 `j` 函数下方(第 66 行)发现了对 `j` 函数的调用。 - 同时,在同一个 `g` 函数中找到了绑定的控制函数 `onIsSwitch` 的相关代码,根据 `g` 函数的结构和 UI 的界面推断:第 61~63 行代码是对开关图片的事件绑定,而第 64~66 行是对开关下方文字的事件绑定。 - 所以 `onIsSwitch` 就是我们需要的函数。 #### 定位开启与关闭函数  - 在 `onIsSwitch` 函数中,可以明显看到 `onOff()` 和 `onOpen()` 函数。 - 继续分析这两个函数,但直接搜索后发现,发现这两个函数并未在 `chunk_1.appservice.js` 中定义,因此需要找到这两个函数所属对象(`o`)的定义位置。 - 向上查看代码,发现了 `o` 的包装位置,并且包装函数 `h` 也位于其下方方。  - 在整个项目中搜索模块 `bluetooth/protocol`,最终在 `appservice.app.js` 中定位到相关代码。   - 最终找到开启与关闭各有两个函数,因为有新旧协议的区别,具体使用哪套协议取决于电动牙刷型号,而身边的牙刷为 P 系列,对应 `oldBrushProtocol` 的协议,对应如下函数。   #### 分析开关函数发送的蓝牙指令 - 分析上述两个函数,可以在第 5382 行和第 5404 行,找到发送蓝牙指令的调用。  - 再次搜索 `sendMessage` 函数后,可以确认上文的 第 5382 行和第 5404 行的字符串就是蓝牙指令。  #### 逆向结果小结 - 至此,基本完成了对小程序发送蓝牙指令控制设备开关过程的解析,整个蓝牙交互过程没有认证步骤,蓝牙指令也未进行加密或混淆,下一步通过实际蓝牙流量验证逆向结果。 ### AI 辅助加速逆向进度(以下内容由 AI 生成) - 通过上文对开启电动牙刷的蓝牙控制过程的分析,发现该小程序中的蓝牙通信指令构造规则其实较为简单,未采用加密或混淆,因此可借助 AI 对整体代码进行辅助分析,定位其他功能的控制函数,以及蓝牙通信指令的构造规则。 ``` AA [TYPE] [LEN] [FUNC] [DATA] [CRC] │ │ │ │ │ └─ CRC校验码 (XOR异或) │ │ │ │ └──────── 数据字节 │ │ │ └─────────────── 功能码 │ │ └───────────────────── 数据长度 │ └──────────────────────────── 指令类型 └──────────────────────────────── 帧头 (固定) ``` #### 指令列表 ##### 开关控制 - `onOpen()`: `aa0f010101a4` (类型: 0f,数据: 01,响应: 0f) - 开启按钮。 - `onOff()`: `aa0f010100a5` (类型: 0f,数据: 00,响应: 0f) - 开启按钮。 ##### 飞行模式 - `onOpenAirplane()`: `aa07010101ac` (类型: 07,数据: 01,响应: 07) - 旅行模式(开) - `onOffAirplane()`: `aa07010100ad` (类型: 07,数据: 00,响应: 07) - 旅行模式(关) ##### 震动提醒 - `onOpenVibration()`: `aa0b010101a0` (类型: 0b,数据: 01,响应: 0b) - 换区提醒(开) - `onOffVibration()`: `aa0b010100a1` (类型: 0b,数据: 00,响应: 0b) - 换区提醒(关) ##### 高频模式 - `onOpenTreble()`: `aa0e010101a5` (类型: 0e,数据: 01,响应: 0e) - 高频模式(开) - `onOffTreble()`: `aa0e010100a4` (类型: 0e,数据: 00,响应: 0e) - 高频模式(关) ### 蓝牙抓包验证逆向结果 #### 确定电动牙刷的蓝牙 MAC 地址 - 首先使用 `nRF Connect` 扫描附近的蓝牙设备,并激活电动牙刷,发现信号最强的设备名称中,带有该品牌的拼音缩写,尝试连接后,电动牙刷的指示灯发生变化,由此确定电动牙刷的蓝牙 MAC 地址。 #### 抓取并分析蓝牙通信流量 - 筛选出 ATT 层流量,定位到开关操作对应的蓝牙流量。   ### 总结蓝牙通信协议并编写控制脚本 - 这里直接根据上文分析出的蓝牙指令规则,编写一个简单的蓝牙交互脚本。 ```python #!/usr/bin/env python3 # -*- coding: utf-8 -*- import asyncio from bleak import BleakClient, BleakScanner WRITE_CHAR = "0000ff02-0000-1000-8000-00805f9b34fb" NOTIFY_CHAR = "0000ff02-0000-1000-8000-00805f9b34fb" CMD_OPEN = "aa0f010101a4" CMD_OFF = "aa0f010100a5" CMD_AIRPLANE_ON = "aa07010101ac" CMD_AIRPLANE_OFF = "aa07010100ad" CMD_VIBRATE_ON = "aa0b010101a0" CMD_VIBRATE_OFF = "aa0b010100a1" CMD_TREBLE_ON = "aa0e010101a5" CMD_TREBLE_OFF = "aa0e010100a4" class Controller: def __init__(self, mac): self.mac = mac.upper() self.client = None self.response = None self.waiting = False async def connect(self): print(f"搜索:{self.mac}") devices = await BleakScanner.discover(timeout=10.0) target = None for dev in devices: if dev.address.upper() == self.mac: target = dev print(f"找到:{dev.name}") break if not target: print("未找到设备") return False print("连接中……") self.client = BleakClient(target.address) await self.client.connect() if self.client.is_connected: print("连接成功") await self.client.start_notify(NOTIFY_CHAR, self._on_notify) return True return False def _on_notify(self, sender, data): self.response = [f"{byte:02x}" for byte in data] self.waiting = False print(" ".join(self.response)) async def send(self, cmd, expected=None): if not self.client or not self.client.is_connected: return None print(cmd) self.waiting = True self.response = None await self.client.write_gatt_char( WRITE_CHAR, bytes.fromhex(cmd), ) timeout = 0 while self.waiting and timeout < 30: await asyncio.sleep(0.1) timeout += 1 if timeout >= 30: return None if expected and self.response and len(self.response) > 1: return self.response[1] == expected return self.response async def disconnect(self): if self.client and self.client.is_connected: await self.client.disconnect() async def turn_on(self): result = await self.send(CMD_OPEN, "0f") print("已打开" if result else "操作失败") async def turn_off(self): result = await self.send(CMD_OFF, "0f") print("已关闭" if result else "操作失败") async def airplane(self, enable): cmd = CMD_AIRPLANE_ON if enable else CMD_AIRPLANE_OFF status = "开启" if enable else "关闭" result = await self.send(cmd, "07") print(f"旅行模式已{status}" if result else "操作失败") async def vibrate(self, enable): cmd = CMD_VIBRATE_ON if enable else CMD_VIBRATE_OFF status = "开启" if enable else "关闭" result = await self.send(cmd, "0b") print(f"换区提醒已{status}" if result else "操作失败") async def treble(self, enable): cmd = CMD_TREBLE_ON if enable else CMD_TREBLE_OFF status = "开启" if enable else "关闭" result = await self.send(cmd, "0e") print(f"高频模式已{status}" if result else "操作失败") async def menu(ctrl): while True: print("\n" + "=" * 40) print("1. 打开 2. 关闭") print("3. 旅行 4. 换区") print("5. 高频 q. 退出") print("=" * 40) choice = input("选择:").strip() if choice == "1": await ctrl.turn_on() elif choice == "2": await ctrl.turn_off() elif choice == "3": await ctrl.airplane(input("1 开 / 0 关:") == "1") elif choice == "4": await ctrl.vibrate(input("1 开 / 0 关:") == "1") elif choice == "5": await ctrl.treble(input("1 开 / 0 关:") == "1") elif choice.lower() == "q": break input("按 Enter 继续……") async def main(): mac = input("MAC 地址:").strip() if not mac: return ctrl = Controller(mac) if await ctrl.connect(): try: await menu(ctrl) finally: await ctrl.disconnect() if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: print("\n已中断") ``` ## 总结与延伸 - 虽然本次只逆向了电动牙刷某一款的蓝牙协议,但是在该小程序中,无论是该品牌的吹风机还是其他款的牙刷,它们的蓝牙通信指令均未进行鉴权或加密。 - 由于这些设备缺少鉴权,理论上直接使用官方小程序即可连接附近的同类设备,这可能是产品设计阶段未充分考虑设备侧鉴权,毕竟牙刷和吹风机这种东西除了在宿舍这种场景,通常也较少出现连接他人设备的实际场景。
登录后可查看完整内容
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于
2026-7-11 09:56 被Haicaji编辑 ,原因: 优化一下markdown格式, 把一些 - 节点改为 # 标题
#漏洞挖掘
#家用设备
收藏
・
7
点赞
・
10
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
mb_yvylrygw
感谢你的贡献,论坛因你而更加精彩!
2026-8-20 17:31
mb_oxqwhbqt
感谢你的积极参与,期待更多精彩内容!
2026-7-21 15:32
mb_shzsxtje
非常支持你的观点!
2026-7-17 17:16
lenyuan
为你点赞!
2026-7-17 15:30
wx_晨梦
感谢你的贡献,论坛因你而更加精彩!
2026-7-16 11:05
lvcoffee
你的帖子非常有用,感谢分享!
2026-7-15 14:37
yuanyouran
+1
你的帖子非常有用,感谢分享!
2026-7-14 15:21
bananaships
感谢你的积极参与,期待更多精彩内容!
2026-7-14 11:42
水边村夫
+1
非常支持你的观点!
2026-7-13 10:07
Ally Switch
感谢你的贡献,论坛因你而更加精彩!
2026-7-11 18:08
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
4
)
wx_WantC
雪 币:
749
活跃值:
(558)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
42
粉丝
1
关注
私信
wx_WantC
2
楼
学习了
2026-7-12 14:28
0
葱葱油油
雪 币:
246
活跃值:
(35)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
3
粉丝
0
关注
私信
葱葱油油
3
楼
学习了
2026-7-13 16:45
0
枫叶woaini
雪 币:
45
活跃值:
(242)
能力值:
( LV2,RANK:10 )
在线值:
发帖
4
回帖
40
粉丝
0
关注
私信
枫叶woaini
4
楼
蓝牙怎么抓包的呀?
2026-7-14 22:32
0
lvcoffee
雪 币:
228
活跃值:
(1382)
能力值:
( LV2,RANK:10 )
在线值:
发帖
3
回帖
28
粉丝
6
关注
私信
lvcoffee
5
楼
厉害啊,一些小玩具是不是也可以这么搞
2026-7-15 14:38
1
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Haicaji
2
发帖
0
回帖
10
RANK
关注
私信
他的文章
[原创]Apple Find My 网络原理浅析
4005
[原创]某品牌电动牙刷小程序"越权控制"逆向
6733
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部