This article also has an English version.
也发点存货吧。这个是去年7月份做的一些工作,现在应该有些已经失效了。
去年暑假发现了喵喵机这个东西,作为一个折腾小能手,搞了点喵喵机相关的逆向和开发。 所有东西见: https://github.com/ihciah/miaomiaoji-tool
喵喵机是个蓝牙热敏打印机,可以直接利用它的 App 走蓝牙连接并打印。之前在微博看到了 DIYGOD 折腾了一个远程推送打印,也想搞一个类似的东西,可是我不想一直手动戳着手机。考虑到手头有一个树莓派3,带蓝牙功能,于是决定拆了喵喵机的 App ,拿到通信协议,然后在树莓派上起控制脚本。
App 逆向
这部分本来懒得拆的,只想拿到蓝牙日志猜一猜的,找了个安卓手机搞,果然没猜出来==
那就拆 Apk 吧。下了最新的 Apk 和好几个旧版本,发现都有 360 加固,就很烦。后来折腾了一波虚拟机脱壳,就是在系统加载 dex 的时候把 dex dump 出来。蛮通用的做法,毕竟现有加固就和多年前的 PC 应用加壳一样,加载时动态解密代码放内存,只要找到 OEP 然后 dump 即可。在安卓里,加载 dex 时 dump 就可以拿到加固的内容。
当然,除了 dex ,这款 App 还有一点点的 Native 代码,拖 IDA 里可以拿到一个 key。这个稍后再讲。
蓝牙通信
我们先 Focus 蓝牙通信部分。在看了这么多垃圾代码(自带天然人工混淆)后,逆出了通信协议:
1 | (1 byte) 2 |
当 payload 过长时,拆成长度不大于 2016
的多个包,依次发送。
其中, CRC32
校验码的初始值为 crcKey = 0x35769521
。控制命令包括 PRT_PRINT_DATA
等,数字常量,1.0.2
版本 Apk 中有 26 个, 2.0.3
版本中增加到 47 个,向下兼容。
机器返回的消息同样遵循上述协议,当最后一个包的 command 字段为 \x00
的时候表示信息发送完毕。
这时候我们只需要使用 Python 等语言复现一遍协议即可。
这里我实现了一个图片打印接口,然后在微信公众号后台接收图片,发送至树莓派自动进行打印。
微信端 | 喵喵机端(microUSB纯粹充电用) |
---|---|
另附几个折腾样例:
不同深度的打印测试页 | 失败样例 |
---|---|
服务端通信
请求包:
1 | POST /api/ap/Login HTTP/1.1 |
其中:
ts
为time.time()
取整sign
为字符串userId=xxx&msg=xxx&ts=xxx&signKey=ab69a9d9-94a5-4111-9554-00af2917732f
的小写MD5
msg
在签名字符串中为一串json:{"ip":"","language":"CN","remark":"{\"brand\":\"samsung\",\"device\":\"SMA3000\",\" os\":\"and\",\"release\":\"5.0.2\",\"baseObjId\":0}","userName":"ihciah@gmail.com","userPassWord":"md5_of_password","type":1,"
- 消息正文中的
msg
为原始msg
(即上文提到的json
串,下记作msg_original
)经加密后组成的json
串(需urllib.quote
,但ios
版app抓包后发现似乎不quote也行?) - 原始消息:
{"swType":"mmj_p1_fw"}
奇怪的AES加密:QpJ42KMTg1f9msKsG5Xz3JY5nGL0UVQWZtAcFRG5dA8=
组成json串:{"parameter":"QpJ42KMTg1f9msKsG5Xz3JY5nGL0UVQWZtAcFRG5dA8\\u003d\\n","baseObjId":0}
转码:%7B%22parameter%22%3A%22QpJ42KMTg1f9msKsG5Xz3JY5nGL0UVQWZtAcFRG5dA8%5Cu003d%5Cn%22%2C%22baseObjId%22%3A0%7D
AES
加密算法采用AES_ECB
,逆向libalf_h_sdkcore.so
可以得到key:f3e15c3a845d48dc
(classes.dex
文件中也包含该密钥),输出时使
用b64encode
,特殊的是还需要将所有+
替换为-
响应包为 {"data":"xxxxx"}
的 json
串,其中data 的值也遵循上面提到的奇怪的AES ,进行逆操作即可得到未加密的 json
响应。
1 | from Crypto.Cipher import AES |
服务端
手机 App
加载图片等资源通过阿里云 OSS SDK
加载,很奇怪的是 Burp
中未出现。应该是没有走系统 HTTP
代理,于是还是上 wireshark
。抓包得到这些请求都
是使用了 BUCKETID
为 mb-mm
内的文件,该 BUCKET
为私有 BUCKET
,需要 OSSAccessKeyId
和 Signature
。
按理说这个签名应该在服务器端做,但是尝试在 iOS上下载字体,从服务器上却没有返回任何签名,那么可以断定签名是在 App 内部做的。查看 App 的相关代码
和阿里云 OSS 的 Android SDK ,可以通过 OSSPlainTextAKSKCredentialProvider
设置 AccessKeyId
, AccessKeySecret
,于是得到:
1 | AccessKeyId: LTAIrPvoTOwid4QD |
之前抓包并解密后得到最新固件名: mmj_p1_fw_v127.bin
,打算尝试从OSS下载;现在直接下载到所有历史版本的APK和固件了2333333。
也就是说,如果我们生成了符合签名的固件并替换上去,可以让所有喵喵机在更新固件之后变砖=。= 或者加进去一些后门代码== 并且,该 OSS 内还有所有用户远程打印的图片,如果你仔细探索,会发现一些奇♂怪的图(捂脸)。
另外,在测试过程中也发现了一些注入漏洞。如:
1 | POST /AjaxSub/GetSubscriptionCategory |
我们可以直接得到表名:
1 | Database: mm_subscription_db |
硬件
一番尝试后没有分析出固件格式或者是入口点,但是在观察hex数据时发现 YC1021
字样,搜索发现这个是蓝牙芯片名称。
第二天拆了机器,找到了芯片制造商 Nuvoton
,芯片型号 NUC123LD4BN0
,使用的处理芯片是 STM32F071CBU6
, Cortex M0
架构。
IDA按照ARM6M 格式载入,发现似乎还能看,搜索立即数0x180 ,该数字十进制表示为384 ,即每行的像素数。
1 | SRAM_BASE = 0x20000000 |
固件修改后可以通过两种方式上传,一种是 Nuvoton
所支持的 ISP
,被固件本身实现为蓝牙上传后调用接口更新;另一种是 USB
上传。考虑到改错的话蓝牙方式更新固件后可能会GG,所以先确保USB上传固件方式可以工作。
于是找到官网,然而又打出GG,需要编程器刷入。。。
本打算修改固件,让机器接受灰度图,然后能将吐出去的纸吃回去,多次打印来实现有深浅区别的打印。然而凉了呢。有 dalao 愿意改的话求戳。
另: 某小哥一年前曾因逆向喵喵机戳过我,现在…emmm去了这家公司。。感觉也是很神奇。